ForcePilot/backend/package/yuxi/channel/extensions/kuaishou/gateway.py
Kris f6df6e2c95 feat(channel): 添加快手渠道扩展
新增快手(Kuaishou)渠道扩展,支持在 Yuxi 平台中集成快手客服渠道。

包含以下功能模块:
- api: 快手 API 客户端封装
- accounts: 账户管理
- config: 渠道配置管理
- gateway: SSE/WebSocket 网关接入
- webhook: Webhook 事件处理
- outbound: 外发消息管理
- streaming: 流式消息处理
- pairing: 用户配对与绑定
- security: 安全校验
- signature: 请求签名验证
- dedupe: 消息去重
- monitor: 渠道状态监控
- status: 会话状态管理
- media: 媒体资源处理
- types: 类型定义
2026-05-21 11:09:37 +08:00

103 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 未开放,不自动获取 tokenauthorization_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