trace/services/notify-gateway/notifiers/telegram.py
Lee 19afaffbb7
All checks were successful
Java Maven 3.9.9 & JDK 26 CI/CD Pipeline / build-and-deploy (push) Successful in 3m22s
feat: add shared notification gateway
2026-09-07 17:50:25 +08:00

73 lines
3.0 KiB
Python

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"<b>{esc(title)}</b>\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)")