from __future__ import annotations import json import logging import time from typing import Any from fastapi import APIRouter, Request, Response from .signature import kwaisign_verify from .types import InboundKuaishouMessage logger = logging.getLogger(__name__) router = APIRouter(prefix="/webhook/kuaishou", tags=["kuaishou"]) # TODO(P1-2): 当快手开放 IM API 后,替换为官方实际事件类型 KUAISHOU_IM_EVENT_TYPE = "im_message_receive" def parse_inbound_message(raw: dict[str, Any]) -> InboundKuaishouMessage | None: try: event = raw.get("event", "") if event != KUAISHOU_IM_EVENT_TYPE: return None msg_data = raw.get("message", raw) return InboundKuaishouMessage( msg_id=str(msg_data.get("msg_id", "")), open_id=str(msg_data.get("open_id", "")), msg_type=msg_data.get("msg_type", "text"), content=msg_data.get("content", ""), create_time=msg_data.get("create_time", int(time.time() * 1000)), conversation_type=msg_data.get("conversation_type", "1"), nickname=msg_data.get("nickname", ""), avatar_url=msg_data.get("avatar", ""), raw_event=raw, ) except Exception: logger.exception("解析快手入站消息失败") return None def create_webhook_handler(app_secret: str): @router.post("/callback") async def kuaishou_webhook(request: Request): body_bytes = await request.body() # 快手官方 WebHook 签名通过 kwaisign header 传递,算法:MD5(body + appsecret) kwaisign = request.headers.get("kwaisign", "") if not kwaisign_verify(body_bytes, app_secret, kwaisign): return Response(content='{"status":"signature_invalid"}', status_code=403) event_data = json.loads(body_bytes.decode("utf-8")) if event_data.get("event") != KUAISHOU_IM_EVENT_TYPE: return {"status": "ok"} inbound = parse_inbound_message(event_data) if inbound is None: return {"status": "ok"} return {"status": "ok", "msg_id": inbound.msg_id} return kuaishou_webhook