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 from app.services.telegram_templates import format_event_telegram_html, format_problem_telegram_html 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, parse_mode: str | None = "HTML", ) -> 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: dict[str, str | bool] = { "chat_id": cfg.chat_id, "text": message, "disable_web_page_preview": True, } if parse_mode: payload["parse_mode"] = parse_mode 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 message = format_event_telegram_html(event) 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 message = format_problem_telegram_html(problem, event) try: send_telegram_text(message, config=cfg) except TelegramSendError: logger.exception("notify_problem telegram failed problem_id=%s", problem.id)