from .base import ImageAttachment, Notifier from .qq import QQNotifier from .telegram import TelegramNotifier from .wecom_webhook import WecomWebhookNotifier def build_notifier(name: str, cfg: dict | None = None) -> Notifier: cfg = cfg or {} if name == "telegram": return TelegramNotifier( token=cfg.get("token", ""), chat_ids=cfg.get("chat_ids"), token_env=cfg.get("token_env", "TELEGRAM_BOT_TOKEN"), chat_ids_env=cfg.get("chat_ids_env", "TELEGRAM_NOTIFY_CHAT_ID"), ) if name == "wecom_webhook": return WecomWebhookNotifier(urls=cfg.get("urls")) if name == "qq": return QQNotifier( onebot_http=cfg.get("onebot_http", ""), access_token=cfg.get("access_token", ""), targets=cfg.get("targets", [])) raise ValueError(f"Unknown notification channel: {name}") def build_notifiers(names: list[str], channels_cfg: dict | None = None) -> "MultiNotifier": channels_cfg = channels_cfg or {} return MultiNotifier([build_notifier(n, channels_cfg.get(n, {})) for n in names]) class MultiNotifier(Notifier): """Fan-out to N channels; raises only if ALL fail.""" name = "multi" def __init__(self, notifiers: list[Notifier]): self.notifiers = notifiers async def send(self, title: str, text: str, images: list[ImageAttachment] | None = None) -> None: errors: dict[str, str] = {} for n in self.notifiers: try: await n.send(title, text, images) except Exception as exc: errors[n.name] = repr(exc) if errors and len(errors) == len(self.notifiers): raise RuntimeError(f"All notifiers failed: {errors}") if errors: import logging logging.getLogger("notify-gateway").warning("partial notify failure: %s", errors)