新增快手(Kuaishou)渠道扩展,支持在 Yuxi 平台中集成快手客服渠道。 包含以下功能模块: - api: 快手 API 客户端封装 - accounts: 账户管理 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - signature: 请求签名验证 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - media: 媒体资源处理 - types: 类型定义
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
|