import html import os import httpx from .base import ImageAttachment, Notifier from .image_utils import ( TELEGRAM_PHOTO_MAX_BYTES, compressed_preview, ) def esc(value: object) -> str: return html.escape(str(value or "")) class TelegramNotifier(Notifier): name = "telegram" def __init__(self, token: str = '', chat_ids: list[str] | None = None, token_env: str = "TELEGRAM_BOT_TOKEN", chat_ids_env: str = "TELEGRAM_NOTIFY_CHAT_ID") -> None: self.token = token or os.environ.get(token_env, "") raw = "" if chat_ids: raw = ",".join(chat_ids) else: raw = os.getenv(chat_ids_env) or ( os.getenv("TELEGRAM_HOME_CHANNEL") if chat_ids_env == "TELEGRAM_NOTIFY_CHAT_ID" else "" ) or "" # FIX vs old code: fan-out to ALL chat ids, not just split(',',1)[0]. self.chat_ids = [c.strip() for c in raw.split(",") if c.strip()] if not self.token: raise RuntimeError("Telegram token is not configured") if not self.chat_ids: raise RuntimeError("Telegram notification target is not configured") self.proxy = os.getenv("TELEGRAM_PROXY") or None async def send(self, title: str, text: str, images: list[ImageAttachment] | None = None) -> None: body = f"{esc(title)}\n{text}" if title else text failures = 0 async with httpx.AsyncClient(timeout=90, proxy=self.proxy, trust_env=False) as client: for chat_id in self.chat_ids: try: response = await client.post( f"https://api.telegram.org/bot{self.token}/sendMessage", json={"chat_id": chat_id, "text": body[:4096], "parse_mode": "HTML", "disable_web_page_preview": True}, ) ok = response.status_code < 400 and response.json().get("ok") is True if ok: for image in images or []: upload = ( image if len(image.data) <= TELEGRAM_PHOTO_MAX_BYTES else compressed_preview(image) ) response = await client.post( f"https://api.telegram.org/bot{self.token}/sendPhoto", data={"chat_id": chat_id}, files={"photo": ( upload.filename, upload.data, upload.content_type )}, ) if response.status_code >= 400 or response.json().get("ok") is not True: ok = False break except Exception: ok = False if not ok: failures += 1 if failures: raise RuntimeError(f"Telegram delivery failed for {failures} target(s)")