新增 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: 类型定义
129 lines
4.7 KiB
Python
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
|