ForcePilot/backend/package/yuxi/channel/extensions/telegram/status.py
Kris 8a3601250e feat(channel): 添加 Telegram 渠道扩展
新增 Telegram 渠道扩展,支持在 Yuxi 平台中集成 Telegram 即时通讯渠道。

包含以下功能模块:
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- polling: 长轮询模式
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- session: 会话管理
- actions: 动作处理
- inline_keyboard: 内联键盘
- native_commands: 原生指令
- chat: 聊天管理
- delivery: 消息送达确认
- media: 媒体资源处理
- profile: 用户资料
- reactions: 表情反应
- sticker: 贴纸处理
- types: 类型定义
2026-05-21 11:48:09 +08:00

129 lines
4.7 KiB
Python

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