Files
qyyfw 78ff261b5a 初始提交: Qihu-AIFlow v2.0.1 完整项目
- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
2026-07-02 14:40:46 +08:00

155 lines
5.8 KiB
Python

"""
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,
}