新增元宝(Yuanbao)渠道的完整适配器实现,包含以下核心模块: - 基础适配器与导出入口 - 协议编解码与WebSocket帧处理 - 会话管理与路由逻辑 - 事件队列与出站消息队列 - 消息格式转换与发送重试 - 安全审计与权限校验 - 配置映射与账户管理 - 视觉分析与工具函数 - 文档生成与设置向导
74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import aiohttp
|
|
|
|
from yuxi.channels.models import DeliveryResult
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
async def send_with_retry(
|
|
http_client: aiohttp.ClientSession,
|
|
token: str,
|
|
api_base: str,
|
|
payload: dict,
|
|
config: dict | None = None,
|
|
) -> DeliveryResult:
|
|
cfg = config or {}
|
|
retry_cfg = cfg.get("retry", {})
|
|
max_retries = retry_cfg.get("attempts", 3)
|
|
min_delay = retry_cfg.get("min_delay_ms", 400) / 1000
|
|
max_delay = retry_cfg.get("max_delay_ms", 30000) / 1000
|
|
send_timeout = cfg.get("send_timeout", 30)
|
|
|
|
headers = {
|
|
"Authorization": f"Bearer {token}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
url = f"{api_base}/api/v1/bot/send/message"
|
|
last_error = None
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
async with http_client.post(
|
|
url,
|
|
json=payload,
|
|
headers=headers,
|
|
timeout=aiohttp.ClientTimeout(total=send_timeout),
|
|
) as resp:
|
|
if resp.status == 200:
|
|
data = await resp.json()
|
|
return DeliveryResult(
|
|
success=True,
|
|
message_id=data.get("msg_id"),
|
|
)
|
|
elif resp.status == 401:
|
|
return DeliveryResult(
|
|
success=False,
|
|
error="Auth failed (401): token expired",
|
|
)
|
|
elif resp.status == 429:
|
|
retry_after = int(resp.headers.get("Retry-After", "30"))
|
|
logger.warning(f"[Yuanbao] Rate limited, retry after {retry_after}s")
|
|
await asyncio.sleep(retry_after)
|
|
continue
|
|
else:
|
|
error_body = await resp.text()
|
|
last_error = f"Send failed ({resp.status}): {error_body}"
|
|
except TimeoutError:
|
|
last_error = "Request timeout"
|
|
except aiohttp.ClientError as e:
|
|
last_error = f"Network error: {e}"
|
|
logger.debug(f"[Yuanbao] Send attempt {attempt + 1} failed: {e}")
|
|
|
|
if attempt < max_retries - 1:
|
|
delay = min(min_delay * (2**attempt), max_delay)
|
|
await asyncio.sleep(delay)
|
|
|
|
return DeliveryResult(
|
|
success=False,
|
|
error=str(last_error) if last_error else "Max retries exceeded",
|
|
)
|