import logging import random import string import time from collections import defaultdict logger = logging.getLogger(__name__) CODE_LENGTH = 6 CODE_TTL_SECONDS = 600 class TaobaoPairing: id_label = "buyer_nick" def __init__(self): self._codes: dict[str, dict[str, tuple[str, float]]] = defaultdict(dict) async def generate_code(self, peer_id: str, account_id: str = "default") -> str: code = "".join(random.choices(string.digits, k=CODE_LENGTH)) self._codes[account_id][code] = (peer_id, time.monotonic()) self._cleanup_expired(account_id) logger.info("Pairing code generated for %s:%s: %s", account_id, peer_id, code) return code async def verify_code(self, peer_id: str, code: str, account_id: str = "default") -> bool: self._cleanup_expired(account_id) account_codes = self._codes.get(account_id, {}) entry = account_codes.get(code) if entry is None: return False stored_peer_id, created_at = entry if time.monotonic() - created_at > CODE_TTL_SECONDS: account_codes.pop(code, None) return False if stored_peer_id != peer_id: return False account_codes.pop(code, None) logger.info("Pairing code verified for %s:%s", account_id, peer_id) return True def normalize_allow_entry(self, entry: str) -> str: return entry.strip().lower() async def notify_approval(self, config: dict, peer_id: str, account_id: str | None = None) -> None: logger.info("Pairing approval notification for %s:%s", account_id, peer_id) try: from yuxi.channel.extensions.taobao.outbound import get_client client = get_client() if client is None: logger.warning("Cannot notify approval: client not initialized") return await client.send_customer_message( to_user=peer_id, content="您的配对已通过审批,现在可以开始与我对话了!请问有什么可以帮助您的?", msg_type=0, ) except Exception as e: logger.error("Failed to send approval notification to %s: %s", peer_id, e) def _cleanup_expired(self, account_id: str): now = time.monotonic() account_codes = self._codes.get(account_id, {}) expired = [k for k, (_, created) in account_codes.items() if now - created > CODE_TTL_SECONDS] for k in expired: account_codes.pop(k, None)