From 2a3d93a6c534265d425f8f27cab894243de5e44e Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:59:50 +0800 Subject: [PATCH] fix: clean up qq official websocket shutdown (#7395) * fix: clean up qq official websocket shutdown fixes: #7259 * fix: do not catch KeyboardInterrupt/SystemExit Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .../qqofficial/qqofficial_platform_adapter.py | 55 ++++++++++++++++++- .../qqofficial_webhook/qo_webhook_adapter.py | 2 +- astrbot/dashboard/server.py | 2 +- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py index 8419c0ea0..3037ab2d8 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py @@ -13,6 +13,7 @@ from typing import Any, cast import botpy import botpy.message from botpy import Client +from botpy.gateway import BotWebSocket from astrbot import logger from astrbot.api.event import MessageChain @@ -37,11 +38,37 @@ for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) +class ManagedBotWebSocket(BotWebSocket): + def __init__(self, session, connection: Any, client: botClient): + super().__init__(session, connection) + self._client = client + + async def on_closed(self, close_status_code, close_msg): + if self._client.is_shutting_down: + logger.debug("[QQOfficial] Ignore websocket reconnect during shutdown.") + return + await super().on_closed(close_status_code, close_msg) + + async def close(self) -> None: + self._can_reconnect = False + if self._conn is not None and not self._conn.closed: + await self._conn.close() + + # QQ 机器人官方框架 class botClient(Client): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self._shutting_down = False + self._active_websockets: set[ManagedBotWebSocket] = set() + def set_platform(self, platform: QQOfficialPlatformAdapter) -> None: self.platform = platform + @property + def is_shutting_down(self) -> bool: + return self._shutting_down or self.is_closed() + # 收到群消息 async def on_group_at_message_create( self, message: botpy.message.GroupMessage @@ -100,6 +127,30 @@ class botClient(Client): ), ) + async def bot_connect(self, session) -> None: + logger.info("[QQOfficial] Websocket session starting.") + + websocket = ManagedBotWebSocket(session, self._connection, self) + self._active_websockets.add(websocket) + try: + await websocket.ws_connect() + except Exception as e: + if not self.is_shutting_down: + await websocket.on_error(e) + finally: + self._active_websockets.discard(websocket) + + async def shutdown(self) -> None: + if self.is_shutting_down: + return + + self._shutting_down = True + await asyncio.gather( + *(websocket.close() for websocket in list(self._active_websockets)), + return_exceptions=True, + ) + await self.close() + @register_platform_adapter("qq_official", "QQ 机器人官方 API 适配器") class QQOfficialPlatformAdapter(Platform): @@ -542,5 +593,5 @@ class QQOfficialPlatformAdapter(Platform): return self.client async def terminate(self) -> None: - await self.client.close() - logger.info("QQ 官方机器人接口 适配器已被优雅地关闭") + await self.client.shutdown() + logger.info("QQ 官方机器人接口 适配器已被关闭") diff --git a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py index b4dbe36b0..d2e14826a 100644 --- a/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +++ b/astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py @@ -193,4 +193,4 @@ class QQOfficialWebhookPlatformAdapter(Platform): f"Exception occurred during QQOfficialWebhook server shutdown: {exc}", exc_info=True, ) - logger.info("QQ 机器人官方 API 适配器已经被优雅地关闭") + logger.info("QQ 机器人官方 API 适配器已经被关闭") diff --git a/astrbot/dashboard/server.py b/astrbot/dashboard/server.py index cbb7296bd..b3dc75945 100644 --- a/astrbot/dashboard/server.py +++ b/astrbot/dashboard/server.py @@ -451,4 +451,4 @@ class AstrBotDashboard: async def shutdown_trigger(self) -> None: await self.shutdown_event.wait() - logger.info("AstrBot WebUI 已经被优雅地关闭") + logger.info("AstrBot WebUI 已经被关闭")