新增拼多多(Pinduoduo)渠道扩展,支持在 Yuxi 平台中集成拼多多电商客服渠道。 包含以下功能模块: - client: 拼多多 API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - pairing: 用户配对与绑定 - security: 安全校验 - signature: 请求签名验证 - token: Token 管理 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - tools: Agent 工具集成 - window: 窗口管理 - types: 类型定义
68 lines
1.9 KiB
Python
68 lines
1.9 KiB
Python
from __future__ import annotations
|
|
|
|
import random
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
|
|
PAIRING_CODE_TTL = 600
|
|
PAIRING_RATE_LIMIT = 3600
|
|
CODE_LENGTH = 6
|
|
|
|
|
|
@dataclass
|
|
class PendingPairing:
|
|
buyer_id: str
|
|
code: str
|
|
created_at: float = field(default_factory=time.time)
|
|
|
|
def is_expired(self) -> bool:
|
|
return time.time() - self.created_at > PAIRING_CODE_TTL
|
|
|
|
|
|
class PinduoduoPairing:
|
|
def __init__(self):
|
|
self._pending: dict[str, PendingPairing] = {}
|
|
self._last_generated: dict[str, float] = {}
|
|
self._paired: set[str] = set()
|
|
|
|
def generate_code(self, buyer_id: str) -> str | None:
|
|
last = self._last_generated.get(buyer_id, 0)
|
|
if time.time() - last < PAIRING_RATE_LIMIT:
|
|
return None
|
|
|
|
self._clean_expired()
|
|
|
|
active = [p for p in self._pending.values() if p.buyer_id == buyer_id and not p.is_expired()]
|
|
if len(active) >= 3:
|
|
return None
|
|
|
|
code = f"{random.randint(0, 999999):06d}"
|
|
self._pending[buyer_id] = PendingPairing(buyer_id=buyer_id, code=code)
|
|
self._last_generated[buyer_id] = time.time()
|
|
return code
|
|
|
|
def verify_code(self, buyer_id: str, code: str) -> bool:
|
|
self._clean_expired()
|
|
pending = self._pending.get(buyer_id)
|
|
if pending is None:
|
|
return False
|
|
if pending.is_expired():
|
|
return False
|
|
if pending.code != code:
|
|
return False
|
|
self._paired.add(buyer_id)
|
|
del self._pending[buyer_id]
|
|
return True
|
|
|
|
def is_paired(self, buyer_id: str) -> bool:
|
|
return buyer_id in self._paired
|
|
|
|
@staticmethod
|
|
def normalize_allow_entry(entry: str) -> str:
|
|
return entry.strip().lower()
|
|
|
|
def _clean_expired(self) -> None:
|
|
expired = [bid for bid, p in self._pending.items() if p.is_expired()]
|
|
for bid in expired:
|
|
del self._pending[bid]
|