from __future__ import annotations import logging from dataclasses import dataclass from enum import StrEnum logger = logging.getLogger(__name__) TELEGRAM_API_BASE = "https://api.telegram.org" class BotStatus(StrEnum): ONLINE = "online" DEGRADED = "degraded" OFFLINE = "offline" UNKNOWN = "unknown" @dataclass class BotInfo: bot_id: int = 0 username: str = "" first_name: str = "" can_join_groups: bool = False can_read_all_group_messages: bool = False supports_inline_queries: bool = False status: BotStatus = BotStatus.UNKNOWN error: str | None = None @dataclass class ProbeResult: ok: bool = False bot_info: BotInfo | None = None latency_ms: float = 0.0 error: str | None = None class TelegramStatus: @staticmethod async def probe(token: str, timeout_seconds: float = 2.5) -> ProbeResult: import time as _time import httpx start = _time.monotonic() try: async with httpx.AsyncClient(timeout=httpx.Timeout(timeout_seconds)) as client: resp = await client.get(f"{TELEGRAM_API_BASE}/bot{token}/getMe") elapsed_ms = (_time.monotonic() - start) * 1000 if resp.status_code == 200: data = resp.json() if data.get("ok"): result = data.get("result", {}) return ProbeResult( ok=True, bot_info=BotInfo( bot_id=result.get("id", 0), username=result.get("username", ""), first_name=result.get("first_name", ""), can_join_groups=result.get("can_join_groups", False), can_read_all_group_messages=result.get("can_read_all_group_messages", False), supports_inline_queries=result.get("supports_inline_queries", False), status=BotStatus.ONLINE, ), latency_ms=elapsed_ms, ) return ProbeResult(ok=False, error=data.get("description", "Unknown error"), latency_ms=elapsed_ms) if resp.status_code == 401: return ProbeResult(ok=False, error="Token invalid or bot deleted", latency_ms=elapsed_ms) return ProbeResult(ok=False, error=f"HTTP {resp.status_code}", latency_ms=elapsed_ms) except Exception as e: elapsed_ms = (_time.monotonic() - start) * 1000 return ProbeResult(ok=False, error=str(e), latency_ms=elapsed_ms) @staticmethod async def probe_account(account: dict) -> bool: token = account.get("token", "") if not token: return False result = await TelegramStatus.probe(token) webhook_url = account.get("webhook_url", "") if webhook_url: from yuxi.channel.extensions.telegram.webhook import get_webhook_info webhook_info = await get_webhook_info(token) if webhook_info: pending = webhook_info.get("pending_update_count", 0) last_error_date = webhook_info.get("last_error_date", 0) last_error_msg = webhook_info.get("last_error_message", "") account_id = account.get("account_id", "") if pending > 100: logger.warning( "Telegram webhook pending: %d updates (account=%s, url=%s)", pending, account_id, webhook_info.get("url", ""), ) if last_error_date: logger.warning( "Telegram webhook last error (date=%s): %s", last_error_date, last_error_msg, ) return result.ok @staticmethod def build_summary(account: dict, probe_result: ProbeResult | None = None) -> dict: summary: dict = { "account_id": account.get("account_id", ""), "token_source": account.get("token_source", ""), "configured": bool(account.get("token")), "dm_policy": account.get("dm_policy", "pairing"), "group_policy": account.get("group_policy", "open"), } if probe_result: summary["bot_online"] = probe_result.ok summary["probe_latency_ms"] = round(probe_result.latency_ms, 1) if probe_result.error: summary["probe_error"] = probe_result.error if probe_result.bot_info: summary["bot_username"] = probe_result.bot_info.username summary["bot_id"] = probe_result.bot_info.bot_id return summary