|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
"""
|
|
|
|
|
|
===================================
|
|
|
|
|
|
Bot Webhook 处理器
|
|
|
|
|
|
===================================
|
|
|
|
|
|
|
|
|
|
|
|
处理各平台的 Webhook 回调,分发到命令处理器。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
|
import logging
|
|
|
|
|
|
from typing import Dict, Any, Optional, TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
|
|
from bot.models import WebhookResponse
|
|
|
|
|
|
from bot.dispatcher import get_dispatcher
|
|
|
|
|
|
from bot.platforms import ALL_PLATFORMS
|
|
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
from bot.platforms.base import BotPlatform
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
# 平台实例缓存
|
|
|
|
|
|
_platform_instances: Dict[str, 'BotPlatform'] = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_platform(platform_name: str) -> Optional['BotPlatform']:
|
|
|
|
|
|
"""
|
|
|
|
|
|
获取平台适配器实例
|
|
|
|
|
|
|
|
|
|
|
|
使用缓存避免重复创建。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
platform_name: 平台名称
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
平台适配器实例,或 None
|
|
|
|
|
|
"""
|
|
|
|
|
|
if platform_name not in _platform_instances:
|
|
|
|
|
|
platform_class = ALL_PLATFORMS.get(platform_name)
|
|
|
|
|
|
if platform_class:
|
|
|
|
|
|
_platform_instances[platform_name] = platform_class()
|
|
|
|
|
|
else:
|
|
|
|
|
|
logger.warning(f"[BotHandler] 未知平台: {platform_name}")
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
return _platform_instances[platform_name]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_webhook(
|
|
|
|
|
|
platform_name: str,
|
|
|
|
|
|
headers: Dict[str, str],
|
|
|
|
|
|
body: bytes,
|
|
|
|
|
|
query_params: Optional[Dict[str, list]] = None
|
|
|
|
|
|
) -> WebhookResponse:
|
|
|
|
|
|
"""
|
|
|
|
|
|
处理 Webhook 请求
|
|
|
|
|
|
|
|
|
|
|
|
这是所有平台 Webhook 的统一入口。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
platform_name: 平台名称 (feishu, dingtalk, wecom, telegram)
|
|
|
|
|
|
headers: HTTP 请求头
|
|
|
|
|
|
body: 请求体原始字节
|
|
|
|
|
|
query_params: URL 查询参数(用于某些平台的验证)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
WebhookResponse 响应对象
|
|
|
|
|
|
"""
|
|
|
|
|
|
logger.info(f"[BotHandler] 收到 {platform_name} Webhook 请求")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查机器人功能是否启用
|
|
|
|
|
|
from src.config import get_config
|
|
|
|
|
|
config = get_config()
|
|
|
|
|
|
|
|
|
|
|
|
if not getattr(config, 'bot_enabled', True):
|
|
|
|
|
|
logger.info("[BotHandler] 机器人功能未启用")
|
|
|
|
|
|
return WebhookResponse.success()
|
|
|
|
|
|
|
|
|
|
|
|
# 获取平台适配器
|
|
|
|
|
|
platform = get_platform(platform_name)
|
|
|
|
|
|
if not platform:
|
|
|
|
|
|
return WebhookResponse.error(f"Unknown platform: {platform_name}", 400)
|
|
|
|
|
|
|
|
|
|
|
|
# 解析 JSON 数据
|
|
|
|
|
|
try:
|
|
|
|
|
|
data = json.loads(body.decode('utf-8')) if body else {}
|
|
|
|
|
|
except json.JSONDecodeError as e:
|
|
|
|
|
|
logger.error(f"[BotHandler] JSON 解析失败: {e}")
|
|
|
|
|
|
return WebhookResponse.error("Invalid JSON", 400)
|
|
|
|
|
|
|
|
|
|
|
|
logger.debug(f"[BotHandler] 请求数据: {json.dumps(data, ensure_ascii=False)[:500]}")
|
|
|
|
|
|
|
|
|
|
|
|
# 处理 Webhook
|
|
|
|
|
|
message, challenge_response = platform.handle_webhook(headers, body, data)
|
|
|
|
|
|
|
|
|
|
|
|
# 如果是验证请求,直接返回验证响应
|
|
|
|
|
|
if challenge_response:
|
|
|
|
|
|
logger.info(f"[BotHandler] 返回验证响应")
|
|
|
|
|
|
return challenge_response
|
|
|
|
|
|
|
|
|
|
|
|
# 如果没有消息需要处理,返回空响应
|
|
|
|
|
|
if not message:
|
|
|
|
|
|
logger.debug("[BotHandler] 无需处理的消息")
|
|
|
|
|
|
return WebhookResponse.success()
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"[BotHandler] 解析到消息: user={message.user_name}, content={message.content[:50]}")
|
|
|
|
|
|
|
|
|
|
|
|
# 分发到命令处理器
|
|
|
|
|
|
dispatcher = get_dispatcher()
|
|
|
|
|
|
response = dispatcher.dispatch(message)
|
|
|
|
|
|
|
|
|
|
|
|
# 格式化响应
|
|
|
|
|
|
if response.text:
|
|
|
|
|
|
webhook_response = platform.format_response(response, message)
|
|
|
|
|
|
return webhook_response
|
|
|
|
|
|
|
|
|
|
|
|
return WebhookResponse.success()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_feishu_webhook(headers: Dict[str, str], body: bytes) -> WebhookResponse:
|
|
|
|
|
|
"""处理飞书 Webhook"""
|
|
|
|
|
|
return handle_webhook('feishu', headers, body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_dingtalk_webhook(headers: Dict[str, str], body: bytes) -> WebhookResponse:
|
|
|
|
|
|
"""处理钉钉 Webhook"""
|
|
|
|
|
|
return handle_webhook('dingtalk', headers, body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_wecom_webhook(headers: Dict[str, str], body: bytes) -> WebhookResponse:
|
|
|
|
|
|
"""处理企业微信 Webhook"""
|
|
|
|
|
|
return handle_webhook('wecom', headers, body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_telegram_webhook(headers: Dict[str, str], body: bytes) -> WebhookResponse:
|
|
|
|
|
|
"""处理 Telegram Webhook"""
|
|
|
|
|
|
return handle_webhook('telegram', headers, body)
|