新增淘宝(Taobao)渠道扩展,支持在 Yuxi 平台中集成淘宝电商客服渠道。 包含以下功能模块: - client: 淘宝 API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - message: 消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - crypto: 加解密处理 - status: 会话状态管理 - tools: Agent 工具集成 - types: 类型定义
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
import asyncio
|
|
import logging
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
|
|
from yuxi.channel.extensions.taobao.client import TaobaoClient
|
|
from yuxi.channel.extensions.taobao.config import TaobaoConfigAdapter
|
|
from yuxi.channel.extensions.taobao.types import TaobaoAccount
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_current_gateway: TaobaoGateway | None = None
|
|
|
|
|
|
class TaobaoGateway:
|
|
def __init__(self):
|
|
self._running = False
|
|
self._account: TaobaoAccount | None = None
|
|
self._client: TaobaoClient | None = None
|
|
self._token_refresh_task: asyncio.Task | None = None
|
|
self._message_queue: asyncio.Queue | None = None
|
|
self._last_message_at: float | None = None
|
|
|
|
async def start(self, ctx) -> dict:
|
|
global _current_gateway
|
|
|
|
account = _resolve_account(ctx)
|
|
if not account.is_configured():
|
|
logger.warning("Taobao account %s not configured, skipping", account.account_id)
|
|
return {"running": False, "reason": "not-configured", "account_id": account.account_id}
|
|
|
|
self._account = account
|
|
self._client = TaobaoClient(
|
|
app_key=account.app_key,
|
|
app_secret=account.app_secret,
|
|
sign_method=account.sign_method,
|
|
sandbox=account.sandbox,
|
|
timeout=account.timeout,
|
|
)
|
|
|
|
self._message_queue = asyncio.Queue(maxsize=1000)
|
|
_current_gateway = self
|
|
|
|
if account.has_valid_token():
|
|
self._token_refresh_task = asyncio.create_task(self._token_refresh_loop())
|
|
|
|
self._running = True
|
|
logger.info("Taobao gateway started for account %s", account.account_id)
|
|
return {"running": True, "account_id": account.account_id, "client": self._client}
|
|
|
|
async def stop(self, ctx) -> None:
|
|
self._running = False
|
|
|
|
if self._token_refresh_task:
|
|
self._token_refresh_task.cancel()
|
|
self._token_refresh_task = None
|
|
|
|
self._message_queue = None
|
|
self._client = None
|
|
self._account = None
|
|
|
|
logger.info("Taobao gateway stopped")
|
|
|
|
async def _token_refresh_loop(self):
|
|
while self._running:
|
|
await asyncio.sleep(600)
|
|
if not self._account or not self._account.refresh_token:
|
|
continue
|
|
try:
|
|
resp = await self._client.refresh_token(self._account.refresh_token)
|
|
token_data = resp.get("top_auth_token_create_response", {}).get("token_result", {})
|
|
if token_data:
|
|
self._account.access_token = token_data.get("access_token", "")
|
|
self._account.refresh_token = token_data.get("refresh_token", "")
|
|
expires_in = token_data.get("expires_in", 0)
|
|
self._account.token_expires_at = datetime.now() + timedelta(seconds=expires_in)
|
|
logger.info("Taobao token refreshed for %s", self._account.account_id)
|
|
except asyncio.CancelledError:
|
|
break
|
|
except Exception as e:
|
|
logger.error("Taobao token refresh failed for %s: %s", self._account.account_id, e)
|
|
|
|
async def enqueue_message(self, unified_msg: dict) -> None:
|
|
if self._message_queue is not None:
|
|
self._last_message_at = time.monotonic()
|
|
await self._message_queue.put(unified_msg)
|
|
|
|
def get_message_queue(self) -> asyncio.Queue | None:
|
|
return self._message_queue
|
|
|
|
def get_last_message_at(self) -> float | None:
|
|
return self._last_message_at
|
|
|
|
|
|
def _resolve_account(ctx) -> TaobaoAccount:
|
|
config = getattr(ctx, "config", {}) if ctx else {}
|
|
accounts = config.get("accounts", {})
|
|
account_id = getattr(ctx, "account_id", "default")
|
|
raw = accounts.get(account_id, {})
|
|
adapter = TaobaoConfigAdapter()
|
|
return adapter.build_account(account_id, raw)
|
|
|
|
|
|
def get_current_gateway() -> TaobaoGateway | None:
|
|
return _current_gateway
|