""" Channel Webhook 入口 — /app/api/channel/ 任务 #935 · 2026-08-09 - Telegram 走 POST JSON - secret_token 校验(头部 X-Telegram-Bot-Api-Secret-Token) - 其他渠道后续扩展(飞书 / 钉钉加密回调 / 企业微信 AES 解密各不同,放各 adapter 自己) """ from __future__ import annotations import hashlib import hmac import logging from typing import Any from fastapi import APIRouter, HTTPException, Request from app.core.channel_adapter import ChannelRouter log = logging.getLogger(__name__) router = APIRouter(prefix="/app/api/channel", tags=["channel-webhook"]) @router.post("/{channel_id}") async def channel_webhook(channel_id: str, request: Request) -> dict: r = ChannelRouter.shared() if channel_id not in r.channels: raise HTTPException(404, f"channel not registered: {channel_id}") adapter = r._adapters.get(channel_id) # noqa: SLF001 (内部访问 OK) raw = await request.body() # secret 校验(各家 header 名不同,Telegram: X-Telegram-Bot-Api-Secret-Token) expected_secret = getattr(adapter, "webhook_secret", "") if expected_secret: secret_header = request.headers.get("x-telegram-bot-api-secret-token", "") if not secret_header or not hmac.compare_digest(secret_header, expected_secret): raise HTTPException(401, "invalid secret") try: payload: Any = await request.json() except Exception: raise HTTPException(400, "invalid json") # 调对应 adapter 的 receive_update(Telegram 是同步 await;其他渠道可异步) if hasattr(adapter, "receive_update"): import asyncio await adapter.receive_update(payload) else: raise HTTPException(501, "channel has no receive_update impl") return {"ok": True}