""" Telegram平台适配器 """ import hashlib import hmac import json import logging from typing import Any from ..platform_adapter import register_platform_adapter, BasePlatformAdapter, PlatformMetadata logger = logging.getLogger(__name__) @register_platform_adapter( name="telegram", display_name="Telegram", description="Telegram Bot API消息适配器,支持文本、图片、文件、语音、视频、贴纸等消息类型", support_streaming=True, support_image=True, support_file=True, support_voice=True, ) class TelegramPlatformAdapter(BasePlatformAdapter): """Telegram平台适配器""" def get_metadata(self) -> PlatformMetadata: from ..platform_adapter import platform_registry return platform_registry["telegram"] def verify_signature(self, payload: dict, signature: str, timestamp: str) -> bool: """Telegram签名验证: HMAC-SHA256(BotToken, 可选)""" if not signature: return True # Telegram不使用签名头验证,通过setWebhook时设置secret_token bot_token = self.config.get("bot_token", "") if not bot_token: logger.warning("[Telegram] 未配置bot_token,跳过签名验证") return True try: # Telegram支持通过 X-Telegram-Bot-Api-Secret-Token 头验证 # 新式验证:secret_token = SHA256(bot_token) expected = hashlib.sha256(bot_token.encode("utf-8")).hexdigest() return signature == expected except Exception as e: logger.error(f"[Telegram] 签名验证异常: {e}") return False def parse_webhook(self, payload: dict) -> dict: """解析Telegram webhook payload为统一消息格式""" message = payload.get("message", payload.get("edited_message", {})) if not message: # 可能是callback_query等其他更新 callback = payload.get("callback_query", {}) if callback: message = callback.get("message", {}) message["_callback_data"] = callback.get("data", "") chat = message.get("chat", {}) from_user = message.get("from", {}) chat_id = str(chat.get("id", "")) chat_type = chat.get("type", "private") chat_title = chat.get("title", "") sender_id = str(from_user.get("id", "")) sender_name = from_user.get("first_name", "") or from_user.get("username", "") content = "" content_type = "text" if "text" in message: content = message.get("text", "") content_type = "text" elif "photo" in message: photos = message.get("photo", []) largest = photos[-1] if photos else {} content = f"[图片] file_id={largest.get('file_id', '')}" content_type = "image" elif "document" in message: doc = message.get("document", {}) content = f"[文件] {doc.get('file_name', 'unknown')} file_id={doc.get('file_id', '')}" content_type = "file" elif "voice" in message: voice = message.get("voice", {}) content = f"[语音] file_id={voice.get('file_id', '')} duration={voice.get('duration', 0)}s" content_type = "voice" elif "video" in message: video = message.get("video", {}) content = f"[视频] file_id={video.get('file_id', '')}" content_type = "video" elif "sticker" in message: sticker = message.get("sticker", {}) content = f"[贴纸] emoji={sticker.get('emoji', '')} set={sticker.get('set_name', '')}" content_type = "sticker" elif "location" in message: loc = message.get("location", {}) content = f"[位置] lat={loc.get('latitude')} lng={loc.get('longitude')}" content_type = "location" elif "contact" in message: contact = message.get("contact", {}) content = f"[联系人] {contact.get('first_name', '')} phone={contact.get('phone_number', '')}" content_type = "contact" elif "animation" in message: anim = message.get("animation", {}) content = f"[动画] file_id={anim.get('file_id', '')}" content_type = "animation" else: content = json.dumps(message, ensure_ascii=False) content_type = "unknown" # 群聊/频道标识 group_id = None group_name = None if chat_type in ("group", "supergroup"): group_id = chat_id group_name = chat_title elif chat_type == "channel": group_id = chat_id group_name = chat_title return { "content_type": content_type, "content": content, "sender_id": sender_id, "sender_name": sender_name, "message_id": str(message.get("message_id", "")), "timestamp": message.get("date", 0), "group_id": group_id, "group_name": group_name, "raw": payload, } def build_reply(self, channel_config: dict, content: str, reply_to: str = None) -> dict: """构建Telegram回复消息""" bot_token = channel_config.get("bot_token", "") chat_id = channel_config.get("chat_id", "") if not bot_token: logger.warning("[Telegram] 未配置bot_token,无法构建回复") return {} body = { "chat_id": chat_id, "text": content, "parse_mode": "HTML", } if reply_to: body["reply_to_message_id"] = reply_to return { "api_url": f"https://api.telegram.org/bot{bot_token}/sendMessage", "headers": {"Content-Type": "application/json"}, "body": body, }