新增拼多多(Pinduoduo)渠道扩展,支持在 Yuxi 平台中集成拼多多电商客服渠道。 包含以下功能模块: - client: 拼多多 API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - pairing: 用户配对与绑定 - security: 安全校验 - signature: 请求签名验证 - token: Token 管理 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - tools: Agent 工具集成 - window: 窗口管理 - types: 类型定义
178 lines
6.5 KiB
Python
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
|