ForcePilot/backend/package/yuxi/channel/extensions/kuaishou/webhook.py
Kris f6df6e2c95 feat(channel): 添加快手渠道扩展
新增快手(Kuaishou)渠道扩展,支持在 Yuxi 平台中集成快手客服渠道。

包含以下功能模块:
- api: 快手 API 客户端封装
- accounts: 账户管理
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- signature: 请求签名验证
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- media: 媒体资源处理
- types: 类型定义
2026-05-21 11:09:37 +08:00

67 lines
2.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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