新增 Lazada 渠道扩展,支持在 Yuxi 平台中集成 Lazada 电商客服渠道。 包含以下功能模块: - client: Lazada API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - pairing: 用户配对与绑定 - security: 安全校验 - signature: 请求签名验证 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session: 会话管理 - tools: Agent 工具集成 - tools_config: 工具配置 - types: 类型定义
163 lines
5.8 KiB
Python
163 lines
5.8 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
from yuxi.channel.extensions.lazada.types import LazadaAccount
|
|
|
|
LAZADA_SITE_ENDPOINTS = {
|
|
"SG": "https://api.lazada.sg/rest",
|
|
"MY": "https://api.lazada.com.my/rest",
|
|
"TH": "https://api.lazada.co.th/rest",
|
|
"VN": "https://api.lazada.vn/rest",
|
|
"ID": "https://api.lazada.co.id/rest",
|
|
"PH": "https://api.lazada.com.ph/rest",
|
|
}
|
|
|
|
LAZADA_AUTH_ENDPOINT = "https://auth.lazada.com/rest"
|
|
|
|
COMMON_CONFIG_SCHEMA = {
|
|
"$schema": "https://json-schema.org/draft-07/schema#",
|
|
"type": "object",
|
|
"title": "Lazada 渠道配置",
|
|
"properties": {
|
|
"appKey": {
|
|
"type": "string",
|
|
"title": "App Key",
|
|
"description": "Lazada 开放平台应用 Key (ISV Console 获取)",
|
|
},
|
|
"appSecret": {
|
|
"type": "string",
|
|
"title": "App Secret",
|
|
"x-ui-password": True,
|
|
"description": "Lazada 开放平台应用密钥",
|
|
},
|
|
"accessToken": {
|
|
"type": "string",
|
|
"title": "Access Token",
|
|
"x-ui-password": True,
|
|
"description": "OAuth 2.0 授权获得的 access_token",
|
|
},
|
|
"refreshToken": {
|
|
"type": "string",
|
|
"title": "Refresh Token",
|
|
"x-ui-password": True,
|
|
"description": "OAuth 2.0 授权获得的 refresh_token",
|
|
},
|
|
"sellerId": {
|
|
"type": "string",
|
|
"title": "Seller ID",
|
|
"description": "Lazada 卖家 ID",
|
|
},
|
|
"siteCode": {
|
|
"type": "string",
|
|
"title": "站点代码",
|
|
"enum": ["SG", "MY", "TH", "VN", "ID", "PH"],
|
|
"default": "SG",
|
|
"description": "六国站点代码",
|
|
},
|
|
"webhookPath": {
|
|
"type": "string",
|
|
"title": "Webhook 回调路径",
|
|
"default": "/webhook/lazada/callback",
|
|
},
|
|
"webhookSecret": {
|
|
"type": "string",
|
|
"title": "Webhook 签名密钥",
|
|
"x-ui-password": True,
|
|
"description": "用于验证 LPM 推送签名",
|
|
},
|
|
"dmPolicy": {
|
|
"type": "string",
|
|
"title": "DM 安全策略",
|
|
"enum": ["open", "pairing", "allowlist", "disabled"],
|
|
"default": "open",
|
|
},
|
|
"maxTextLength": {
|
|
"type": "integer",
|
|
"title": "单条消息最大字符数",
|
|
"default": 2000,
|
|
"maximum": 2000,
|
|
},
|
|
"httpTimeoutMs": {
|
|
"type": "integer",
|
|
"title": "HTTP 请求超时(毫秒)",
|
|
"default": 15000,
|
|
},
|
|
"pollingIntervalMs": {
|
|
"type": "integer",
|
|
"title": "长轮询间隔(毫秒)",
|
|
"default": 5000,
|
|
"minimum": 1000,
|
|
},
|
|
"enabled": {
|
|
"type": "boolean",
|
|
"title": "启用",
|
|
"default": False,
|
|
"description": "⚠️ 启用前请确保已完成 IM API 权限申请",
|
|
},
|
|
},
|
|
"required": ["appKey", "appSecret"],
|
|
}
|
|
|
|
|
|
class LazadaConfig:
|
|
ENV_MAP = {
|
|
"app_key": "LAZADA_APP_KEY",
|
|
"app_secret": "LAZADA_APP_SECRET",
|
|
"seller_id": "LAZADA_SELLER_ID",
|
|
"site_code": "LAZADA_SITE_CODE",
|
|
"dm_policy": "LAZADA_DM_POLICY",
|
|
}
|
|
|
|
def list_account_ids(self, config: dict | None = None) -> list[str]:
|
|
channels = (config or {}).get("channels", {})
|
|
lazada_config = channels.get("lazada", {})
|
|
accounts = lazada_config.get("accounts", {})
|
|
if accounts:
|
|
return list(accounts.keys())
|
|
if self._env_or_config("app_key"):
|
|
return ["default"]
|
|
return []
|
|
|
|
async def resolve_account(self, account_id: str = "default", config: dict | None = None) -> LazadaAccount:
|
|
channels = (config or {}).get("channels", {})
|
|
lazada_config = channels.get("lazada", {})
|
|
accounts = lazada_config.get("accounts", {})
|
|
account_data = accounts.get(account_id, {})
|
|
|
|
return LazadaAccount(
|
|
account_id=account_id,
|
|
app_key=account_data.get("app_key") or self._env_or_config("app_key") or "",
|
|
app_secret=account_data.get("app_secret") or self._env_or_config("app_secret") or "",
|
|
access_token=account_data.get("access_token", ""),
|
|
refresh_token=account_data.get("refresh_token", ""),
|
|
token_expires_at=account_data.get("token_expires_at", 0),
|
|
refresh_expires_at=account_data.get("refresh_expires_at", 0),
|
|
seller_id=account_data.get("seller_id") or self._env_or_config("seller_id") or "",
|
|
site_code=account_data.get("site_code") or self._env_or_config("site_code") or "SG",
|
|
webhook_path=account_data.get("webhook_path", "/webhook/lazada/callback"),
|
|
webhook_secret=account_data.get("webhook_secret", ""),
|
|
dm_policy=account_data.get("dm_policy") or self._env_or_config("dm_policy") or "open",
|
|
allow_from=account_data.get("allow_from", []),
|
|
max_text_length=account_data.get("max_text_length", 2000),
|
|
http_timeout_ms=account_data.get("http_timeout_ms", 15000),
|
|
polling_interval_ms=account_data.get("polling_interval_ms", 5000),
|
|
enabled=account_data.get("enabled", False),
|
|
)
|
|
|
|
def is_configured(self, account: dict | None = None) -> bool:
|
|
if account:
|
|
return bool(account.get("app_key") and account.get("app_secret"))
|
|
return bool(self._env_or_config("app_key") and self._env_or_config("app_secret"))
|
|
|
|
def _env_or_config(self, key: str, config: dict | None = None) -> str | None:
|
|
env_key = self.ENV_MAP.get(key, "")
|
|
if env_key:
|
|
val = os.getenv(env_key)
|
|
if val:
|
|
return val
|
|
return None
|
|
|
|
def config_schema(self) -> dict:
|
|
return COMMON_CONFIG_SCHEMA
|