ForcePilot/backend/package/yuxi/channel/extensions/pinduoduo/monitor.py
Kris 1476e82ef0 feat(channel): 添加拼多多渠道扩展
新增拼多多(Pinduoduo)渠道扩展,支持在 Yuxi 平台中集成拼多多电商客服渠道。

包含以下功能模块:
- client: 拼多多 API 客户端封装
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- pairing: 用户配对与绑定
- security: 安全校验
- signature: 请求签名验证
- token: Token 管理
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- tools: Agent 工具集成
- window: 窗口管理
- types: 类型定义
2026-05-21 11:33:31 +08:00

178 lines
6.5 KiB
Python

from __future__ import annotations
import json
import logging
import time
from datetime import datetime
from yuxi.channel.extensions.pinduoduo.types import InboundPddMessage
from yuxi.channel.message.models import MessageType, PeerInfo, UnifiedMessage
from yuxi.channel.routing.models import PeerKind
logger = logging.getLogger(__name__)
class PinduoduoMonitor:
def __init__(self, mall_id: str, account_id: str):
self._mall_id = mall_id
self._account_id = account_id
def to_unified_message(self, msg: InboundPddMessage) -> UnifiedMessage:
content = msg.content or ""
msg_type = MessageType.TEXT
if msg.msg_type == "1":
msg_type = MessageType.IMAGE
elif msg.msg_type == "2":
try:
data = json.loads(content)
goods_id = data.get("goods_id", data.get("id", ""))
content = f"[商品卡片] 商品ID: {goods_id}"
except json.JSONDecodeError:
pass
elif msg.msg_type == "3":
try:
data = json.loads(content)
order_sn = data.get("order_sn", data.get("id", ""))
content = f"[订单卡片] 订单号: {order_sn}"
except json.JSONDecodeError:
pass
elif msg.msg_type == "4":
try:
data = json.loads(content)
coupon_id = data.get("coupon_id", data.get("id", ""))
content = f"[优惠券卡片] 优惠券ID: {coupon_id}"
except json.JSONDecodeError:
pass
return UnifiedMessage(
msg_id=msg.msg_id,
channel_type="pinduoduo",
account_id=self._account_id,
content=content,
sender=PeerInfo(
kind=PeerKind.DIRECT,
id=msg.buyer_id,
display_name=msg.buyer_nickname or f"买家{msg.buyer_id[-6:]}",
),
message_type=msg_type,
group=None,
body_for_agent=content,
raw_payload=msg.raw_payload,
metadata={
"mall_id": msg.mall_id,
"session_id": msg.session_id,
"pdd_msg_type": msg.msg_type,
"buyer_id": msg.buyer_id,
},
)
@staticmethod
def parse_poll_response(data: dict, mall_id: str) -> list[InboundPddMessage]:
messages = []
msg_list = data.get("logistics_cs_message_list", [])
if not isinstance(msg_list, list):
return messages
for item in msg_list:
messages.append(
InboundPddMessage(
msg_id=str(item.get("id", "")),
mall_id=str(item.get("mall_id", mall_id)),
buyer_id=str(item.get("from_user", "")),
session_id=str(item.get("session_id", "")),
msg_type=str(item.get("message_type", "0")),
content=str(item.get("content", "")),
create_time=_parse_time(item.get("send_time")),
raw_payload=item,
)
)
return messages
@staticmethod
def parse_push_event(data: dict) -> list[InboundPddMessage]:
messages = []
event_type = data.get("event_type", "")
event_data = data.get("data", {})
if event_type == "cs_message":
messages.append(
InboundPddMessage(
msg_id=str(event_data.get("id", "")),
mall_id=str(event_data.get("mall_id", "")),
buyer_id=str(event_data.get("from_user", "")),
session_id=str(event_data.get("session_id", "")),
msg_type=str(event_data.get("message_type", "0")),
content=str(event_data.get("content", "")),
create_time=datetime.now(),
raw_payload=event_data,
)
)
elif event_type == "cs_session_start":
buyer_id = str(event_data.get("from_user", ""))
session_id = str(event_data.get("session_id", ""))
logger.info(
"Session started: mall_id=%s, buyer=%s, session=%s",
event_data.get("mall_id"),
buyer_id,
session_id,
)
messages.append(
InboundPddMessage(
msg_id=f"cs_session_start-{int(time.time())}",
mall_id=str(event_data.get("mall_id", "")),
buyer_id=buyer_id,
session_id=session_id,
msg_type="0",
content="[系统通知] 买家进入客服会话",
create_time=datetime.now(),
raw_payload=data,
)
)
elif event_type.startswith("order_") or event_type.startswith("refund_"):
content = PinduoduoMonitor._format_business_event(event_type, event_data)
messages.append(
InboundPddMessage(
msg_id=f"{event_type}-{int(time.time())}",
mall_id=str(event_data.get("mall_id", "")),
buyer_id=str(event_data.get("buyer_id", "")),
session_id=str(event_data.get("session_id", "")),
msg_type="0",
content=content,
create_time=datetime.now(),
raw_payload=data,
)
)
return messages
@staticmethod
def _format_business_event(event_type: str, event_data: dict) -> str:
title_map = {
"order_create": "订单创建",
"order_paid": "订单支付成功",
"order_shipped": "订单已发货",
"order_delivered": "订单已签收",
"order_cancel": "订单已取消",
"refund_apply": "买家申请退款",
"refund_agree": "退款已同意",
"refund_refuse": "退款已拒绝",
"refund_success": "退款成功",
"refund_cancel": "退款关闭",
}
title = title_map.get(event_type, event_type)
order_sn = event_data.get("order_sn", event_data.get("order_id", "未知"))
if "order_sn" in event_data:
return f"[系统通知] {title} - 订单号: {order_sn}"
return f"[系统通知] {title}"
def _parse_time(time_str: str | None) -> datetime | None:
if not time_str:
return None
try:
return datetime.strptime(time_str, "%Y-%m-%d %H:%M:%S")
except ValueError:
return None