新增QQ Bot适配器完整代码栈,包含: 1. 基础适配器入口与工具类封装 2. 会话管理、重试队列与流量控制 3. 命令系统与内置指令(ping/help/status等) 4. 富媒体消息处理与格式转换 5. 引用存储与审批管理 6. 凭证备份与会话持久化 7. 健康检查与交互回调系统
149 lines
4.9 KiB
Python
149 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
|
|
import aiohttp
|
|
|
|
from yuxi.channels.models import HealthStatus
|
|
from yuxi.utils.logging_config import logger
|
|
|
|
|
|
@dataclass
|
|
class EndpointCheck:
|
|
name: str
|
|
url: str
|
|
status: str = "unknown"
|
|
latency_ms: float = 0
|
|
error: str = ""
|
|
http_status: int = 0
|
|
|
|
|
|
@dataclass
|
|
class AggregatedHealth:
|
|
status: str = "healthy"
|
|
overall_latency_ms: float = 0
|
|
checks: list[EndpointCheck] = field(default_factory=list)
|
|
ws_latency_ms: float | None = None
|
|
|
|
@property
|
|
def all_healthy(self) -> bool:
|
|
return all(c.status == "healthy" for c in self.checks)
|
|
|
|
|
|
async def health_check_dsm(
|
|
api_base: str,
|
|
token: str,
|
|
http_client: aiohttp.ClientSession | None = None,
|
|
sandbox: bool = False,
|
|
ws_connected: bool = False,
|
|
) -> HealthStatus:
|
|
headers = {"Authorization": f"QQBot {token}"}
|
|
timeout = aiohttp.ClientTimeout(total=10)
|
|
|
|
async def _check(session: aiohttp.ClientSession) -> HealthStatus:
|
|
start = time.monotonic()
|
|
async with session.get(f"{api_base}/gateway", headers=headers) as resp:
|
|
latency_ms = (time.monotonic() - start) * 1000
|
|
|
|
if resp.status == 200:
|
|
return HealthStatus(
|
|
status="healthy",
|
|
latency_ms=latency_ms,
|
|
metadata={
|
|
"sandbox": sandbox,
|
|
"ws_connected": ws_connected,
|
|
},
|
|
)
|
|
elif resp.status == 401:
|
|
return HealthStatus(
|
|
status="unhealthy",
|
|
last_error="Token expired or invalid",
|
|
metadata={
|
|
"sandbox": sandbox,
|
|
"auth_status": "failed",
|
|
},
|
|
)
|
|
else:
|
|
return HealthStatus(
|
|
status="degraded",
|
|
latency_ms=latency_ms,
|
|
last_error=f"Gateway returned {resp.status}",
|
|
)
|
|
|
|
if http_client:
|
|
try:
|
|
return await _check(http_client)
|
|
except Exception as e:
|
|
logger.warning(f"[QQBot] Health check failed: {e}")
|
|
return HealthStatus(status="unhealthy", last_error=str(e), metadata={"sandbox": sandbox})
|
|
|
|
try:
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
return await _check(session)
|
|
except Exception as e:
|
|
logger.warning(f"[QQBot] Health check failed: {e}")
|
|
return HealthStatus(status="unhealthy", last_error=str(e), metadata={"sandbox": sandbox})
|
|
|
|
|
|
async def health_check_multi_endpoint(
|
|
api_base: str,
|
|
token: str,
|
|
http_client: aiohttp.ClientSession | None = None,
|
|
ws_connected: bool = False,
|
|
ws_latency_ms: float | None = None,
|
|
) -> AggregatedHealth:
|
|
headers = {"Authorization": f"QQBot {token}"}
|
|
timeout = aiohttp.ClientTimeout(total=10)
|
|
endpoints = [
|
|
EndpointCheck(name="gateway", url=f"{api_base}/gateway"),
|
|
EndpointCheck(name="bot_info", url=f"{api_base}/v2/users/@me"),
|
|
]
|
|
|
|
async def _check(session: aiohttp.ClientSession) -> AggregatedHealth:
|
|
for ep in endpoints:
|
|
try:
|
|
start = time.monotonic()
|
|
async with session.get(ep.url, headers=headers) as resp:
|
|
ep.latency_ms = (time.monotonic() - start) * 1000
|
|
ep.http_status = resp.status
|
|
if resp.status == 200:
|
|
ep.status = "healthy"
|
|
elif resp.status == 401:
|
|
ep.status = "unhealthy"
|
|
ep.error = "Authentication failed"
|
|
elif resp.status >= 500:
|
|
ep.status = "degraded"
|
|
ep.error = f"Server error ({resp.status})"
|
|
else:
|
|
ep.status = "degraded"
|
|
ep.error = f"Unexpected status ({resp.status})"
|
|
except TimeoutError:
|
|
ep.status = "degraded"
|
|
ep.error = "Timeout"
|
|
except Exception as e:
|
|
ep.status = "unhealthy"
|
|
ep.error = str(e)
|
|
|
|
overall = "healthy"
|
|
if any(ep.status == "unhealthy" for ep in endpoints):
|
|
overall = "unhealthy"
|
|
elif any(ep.status == "degraded" for ep in endpoints):
|
|
overall = "degraded"
|
|
|
|
healthy_checks = [ep for ep in endpoints if ep.status == "healthy"]
|
|
avg_latency = sum(ep.latency_ms for ep in healthy_checks) / len(healthy_checks) if healthy_checks else 0
|
|
|
|
return AggregatedHealth(
|
|
status=overall,
|
|
overall_latency_ms=avg_latency,
|
|
checks=endpoints,
|
|
ws_latency_ms=ws_latency_ms,
|
|
)
|
|
|
|
if http_client:
|
|
return await _check(http_client)
|
|
|
|
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
return await _check(session)
|