ForcePilot/backend/package/yuxi/channel/extensions/telegram/status.py

129 lines
4.7 KiB
Python
Raw Normal View History

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