新增快手(Kuaishou)渠道扩展,支持在 Yuxi 平台中集成快手客服渠道。 包含以下功能模块: - api: 快手 API 客户端封装 - accounts: 账户管理 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - outbound: 外发消息管理 - streaming: 流式消息处理 - pairing: 用户配对与绑定 - security: 安全校验 - signature: 请求签名验证 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - media: 媒体资源处理 - types: 类型定义
103 lines
3.6 KiB
Python
103 lines
3.6 KiB
Python
from __future__ import annotations
|
||
|
||
import asyncio
|
||
import logging
|
||
|
||
from .api import KuaishouAPIClient, KuaishouAPIError
|
||
from .types import KuaishouAccount, TokenInfo
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 快手官方 access_token 有效期 48h,刷新周期设为 44h (158400s),留 4h 缓冲
|
||
TOKEN_REFRESH_INTERVAL = 158400
|
||
|
||
|
||
class KuaishouGateway:
|
||
def __init__(self, account: KuaishouAccount):
|
||
self._account = account
|
||
self._client: KuaishouAPIClient | None = None
|
||
self._running = False
|
||
self._refresh_task: asyncio.Task | None = None
|
||
self._awaiting_authorization = False
|
||
|
||
@property
|
||
def account_id(self) -> str:
|
||
return self._account.account_id
|
||
|
||
@property
|
||
def client(self) -> KuaishouAPIClient:
|
||
if self._client is None:
|
||
raise RuntimeError("Gateway 未启动")
|
||
return self._client
|
||
|
||
@property
|
||
def awaiting_authorization(self) -> bool:
|
||
return self._awaiting_authorization
|
||
|
||
async def start(self, ctx) -> object:
|
||
if not self._account.is_configured():
|
||
return {"running": False, "reason": "not-configured"}
|
||
|
||
self._client = KuaishouAPIClient(
|
||
app_id=self._account.app_id,
|
||
app_secret=self._account.app_secret,
|
||
timeout=self._account.http_timeout_ms / 1000,
|
||
)
|
||
|
||
# TODO(P1-3): API 开放后,如已有持久化 token,尝试 refresh;否则标记为 awaiting_authorization
|
||
# 当前阶段:快手 IM API 未开放,不自动获取 token(authorization_code 需用户授权)
|
||
self._awaiting_authorization = True
|
||
self._running = True
|
||
return {
|
||
"running": True,
|
||
"account_id": self._account.account_id,
|
||
"awaiting_authorization": True,
|
||
}
|
||
|
||
async def authorize(self, code: str, redirect_uri: str) -> TokenInfo:
|
||
"""OAuth 授权回调后,用 code 换取 token 并启动刷新循环。"""
|
||
if self._client is None:
|
||
raise RuntimeError("Gateway 未启动")
|
||
token_info = await self._client.fetch_access_token(code, redirect_uri)
|
||
self._awaiting_authorization = False
|
||
self._refresh_task = asyncio.create_task(self._token_refresh_loop())
|
||
logger.info("快手 OAuth 授权成功,access_token 已获取")
|
||
return token_info
|
||
|
||
async def stop(self, ctx) -> None:
|
||
self._running = False
|
||
if self._refresh_task and not self._refresh_task.done():
|
||
self._refresh_task.cancel()
|
||
try:
|
||
await self._refresh_task
|
||
except asyncio.CancelledError:
|
||
pass
|
||
if self._client:
|
||
await self._client.close()
|
||
self._client = None
|
||
self._awaiting_authorization = False
|
||
|
||
async def _token_refresh_loop(self) -> None:
|
||
while self._running:
|
||
await asyncio.sleep(TOKEN_REFRESH_INTERVAL)
|
||
if not self._running:
|
||
return
|
||
try:
|
||
await self._client.refresh_access_token()
|
||
logger.info("快手 access_token 刷新成功")
|
||
except KuaishouAPIError as e:
|
||
logger.exception(f"快手 access_token 刷新失败: {e}")
|
||
# TODO(P1-3): 刷新失败后标记 awaiting_authorization,提示重新授权
|
||
self._awaiting_authorization = True
|
||
except Exception:
|
||
logger.exception("快手 access_token 刷新失败")
|
||
|
||
async def probe(self) -> bool:
|
||
if not self._client:
|
||
return False
|
||
try:
|
||
await self._client.ensure_token()
|
||
return True
|
||
except Exception:
|
||
return False
|