import logging import httpx from sqlalchemy.orm import Session from app.models import Event, Problem from app.services.notification_severity import severity_meets_minimum from app.services.notification_settings import TelegramConfig, get_effective_telegram_config logger = logging.getLogger(__name__) class TelegramNotConfiguredError(Exception): """Telegram disabled or missing token/chat_id.""" class TelegramSendError(Exception): def __init__(self, message: str, *, status_code: int | None = None) -> None: super().__init__(message) self.status_code = status_code def send_telegram_text(message: str, *, config: TelegramConfig | None = None, force: bool = False) -> None: cfg = config or get_effective_telegram_config() if not force and not cfg.active: return if not cfg.bot_token or not cfg.chat_id: if force: raise TelegramNotConfiguredError("Telegram: не задан bot token или chat_id") return url = f"https://api.telegram.org/bot{cfg.bot_token}/sendMessage" payload = {"chat_id": cfg.chat_id, "text": message, "disable_web_page_preview": True} try: with httpx.Client(timeout=8.0) as client: response = client.post(url, json=payload) response.raise_for_status() except httpx.HTTPStatusError as exc: detail = exc.response.text[:200] if exc.response is not None else str(exc) raise TelegramSendError(f"Telegram API HTTP {exc.response.status_code}: {detail}", status_code=exc.response.status_code) from exc except Exception as exc: logger.exception("telegram send failed") raise TelegramSendError(str(exc)) from exc def send_telegram_test_message(*, config: TelegramConfig | None = None) -> None: cfg = config or get_effective_telegram_config() if not cfg.enabled: raise TelegramNotConfiguredError("Telegram отключён (enabled=false)") if not cfg.configured: raise TelegramNotConfiguredError("Не задан bot token или chat_id") send_telegram_text( "✅ SAC: тестовое сообщение\nКанал Telegram для оповещений работает.", config=cfg, force=True, ) def notify_event(event: Event, *, db: Session | None = None, apply_policy_gate: bool = True) -> None: cfg = get_effective_telegram_config(db) if not cfg.active: return if apply_policy_gate and not severity_meets_minimum(event.severity, cfg.min_severity): return host = event.host.hostname if event.host else "unknown" message = ( f"🚨 SAC событие\n" f"Host: {host}\n" f"Severity: {event.severity}\n" f"Type: {event.type}\n" f"Title: {event.title}\n" f"Summary: {event.summary}" ) try: send_telegram_text(message, config=cfg) except TelegramSendError: logger.exception("notify_event telegram failed event_id=%s", event.event_id) def notify_problem(problem: Problem, event: Event | None = None, *, db: Session | None = None, apply_policy_gate: bool = True) -> None: cfg = get_effective_telegram_config(db) if not cfg.active: return if apply_policy_gate and not severity_meets_minimum(problem.severity, cfg.min_severity): return host = problem.host.hostname if problem.host else "unknown" related = "" if event is not None: related = f"\nEvent: {event.type} ({event.severity})" message = ( f"🔥 SAC Problem opened\n" f"Host: {host}\n" f"Severity: {problem.severity}\n" f"Rule: {problem.rule_id or '-'}\n" f"Title: {problem.title}\n" f"Summary: {problem.summary}{related}" ) try: send_telegram_text(message, config=cfg) except TelegramSendError: logger.exception("notify_problem telegram failed problem_id=%s", problem.id)