67 lines
2.1 KiB
Python
67 lines
2.1 KiB
Python
|
|
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
|