From c8a2588ae2caf6e65c65be826468e0034754c812 Mon Sep 17 00:00:00 2001 From: shuiping233 <1944680304@qq.com> Date: Wed, 24 Jun 2026 13:07:57 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix:=20qq=5Fofficial=20websocket=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=E6=94=B6?= =?UTF-8?q?=E5=88=B0None=E6=97=B6=E7=8E=B0=E5=9C=A8=E4=BC=9A=E9=87=8D?= =?UTF-8?q?=E8=AF=95=E4=BA=86=20(#8977)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqofficial/qqofficial_message_event.py | 64 +++++++++++++------ 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index 68a93b09b9..8f3e9f6728 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -30,6 +30,10 @@ from astrbot.core.utils.media_utils import MediaResolver, file_uri_to_path, is_file_uri +class APIReturnNoneError(Exception): + pass + + def _patch_qq_botpy_formdata() -> None: """Patch qq-botpy for aiohttp>=3.12 compatibility. @@ -49,21 +53,25 @@ def _patch_qq_botpy_formdata() -> None: _patch_qq_botpy_formdata() -# Retry decorator for QQ Official API transient errors (HTTP 500/504) -_qqofficial_retry = retry( - retry=retry_if_exception_type( - ( - botpy.errors.ServerError, - botpy.errors.SequenceNumberError, - OSError, - asyncio.TimeoutError, - ) - ), - stop=stop_after_attempt(5), - wait=wait_exponential(multiplier=2, min=2, max=30), - before_sleep=before_sleep_log(logger, logging.WARNING), - reraise=True, -) + +def _qqofficial_retry(max_attempts: int = 5): + """Retry decorator for QQ Official API transient errors (HTTP 500/504)""" + return retry( + retry=retry_if_exception_type( + ( + botpy.errors.ServerError, + botpy.errors.SequenceNumberError, + OSError, + asyncio.TimeoutError, + APIReturnNoneError, + ) + ), + stop=stop_after_attempt(max_attempts), + wait=wait_exponential(multiplier=2, min=2, max=30), + before_sleep=before_sleep_log(logger, logging.WARNING), + reraise=True, + ) + _QQOFFICIAL_SEND_API_ERRORS = ( botpy.errors.ForbiddenError, @@ -558,7 +566,7 @@ async def upload_group_and_c2c_image( "srv_send_msg": False, } - @_qqofficial_retry + @_qqofficial_retry() async def _do_upload(): if "openid" in kwargs: payload["openid"] = kwargs["openid"] @@ -629,7 +637,7 @@ async def upload_group_and_c2c_media( else: return None - @_qqofficial_retry + @_qqofficial_retry() async def _do_upload(): return await self.bot.api._http.request(route, json=payload) @@ -680,11 +688,27 @@ async def post_c2c_message( stream_data.pop("id", None) payload["stream"] = stream_data route = Route("POST", "/v2/users/{openid}/messages", openid=openid) - result = await self.bot.api._http.request(route, json=payload) - if result is None: - logger.warning("[QQOfficial] post_c2c_message: API 返回 None,跳过本次发送") + retry_times = 3 + + @_qqofficial_retry(retry_times) + async def _do_request(): + result = await self.bot.api._http.request(route, json=payload) + if result is None: + err_msg = "发送消息API返回None,触发重试" + logger.warning(f"[QQOfficial] post_c2c_message: {err_msg}") + raise APIReturnNoneError(err_msg) + return result + + result = None + try: + result = await _do_request() + except APIReturnNoneError: + logger.warning( + f"[QQOfficial] post_c2c_message: 发送消息失败,API 返回 None,共尝试{retry_times}次后放弃" + ) return None + if not isinstance(result, dict): logger.error(f"[QQOfficial] post_c2c_message: 响应不是 dict: {result}") return None From d90f53ecfb5a194648d405ea5b749bbb73773693 Mon Sep 17 00:00:00 2001 From: shuiping233 <1944680304@qq.com> Date: Wed, 24 Jun 2026 13:13:38 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20qq=5Fofficial=20websocket=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=E9=87=8D=E8=AF=95=E5=8F=91=E9=80=81=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E7=9A=84=E6=97=B6=E5=80=99=E4=B8=8D=E4=BC=9A=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E6=89=93=E5=8D=B0=E9=87=8D=E8=AF=95=E5=91=8A=E8=AD=A6?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/platform/sources/qqofficial/qqofficial_message_event.py | 1 - 1 file changed, 1 deletion(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index 8f3e9f6728..a0a84db561 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -696,7 +696,6 @@ async def _do_request(): result = await self.bot.api._http.request(route, json=payload) if result is None: err_msg = "发送消息API返回None,触发重试" - logger.warning(f"[QQOfficial] post_c2c_message: {err_msg}") raise APIReturnNoneError(err_msg) return result From c8a506665710597858ce979490a7b045f2e2a218 Mon Sep 17 00:00:00 2001 From: shuiping233 <1944680304@qq.com> Date: Wed, 24 Jun 2026 13:30:04 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20qq=5Fofficial=20websocket=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=E5=8F=91=E9=80=81=E5=AA=92=E4=BD=93/?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=B6=88=E6=81=AF=E6=97=B6=E9=81=87=E5=88=B0?= =?UTF-8?q?api=E8=BF=94=E5=9B=9ENone=E4=B9=9F=E4=BC=9A=E9=87=8D=E8=AF=95?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqofficial/qqofficial_message_event.py | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index a0a84db561..1e7d4f96ef 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -573,7 +573,6 @@ async def _do_upload(): route = Route( "POST", "/v2/users/{openid}/files", openid=kwargs["openid"] ) - return await self.bot.api._http.request(route, json=payload) elif "group_openid" in kwargs: payload["group_openid"] = kwargs["group_openid"] route = Route( @@ -581,11 +580,20 @@ async def _do_upload(): "/v2/groups/{group_openid}/files", group_openid=kwargs["group_openid"], ) - return await self.bot.api._http.request(route, json=payload) else: raise ValueError("Invalid upload parameters") - result = await _do_upload() + result = await self.bot.api._http.request(route, json=payload) + if result is None: + err_msg = "上传图片API返回None,触发重试" + raise APIReturnNoneError(err_msg) + return result + + try: + result = await _do_upload() + except APIReturnNoneError: + logger.warning(f"上传图片API返回None,共尝试5次后放弃: {payload}") + raise if not isinstance(result, dict): raise RuntimeError( @@ -639,7 +647,11 @@ async def upload_group_and_c2c_media( @_qqofficial_retry() async def _do_upload(): - return await self.bot.api._http.request(route, json=payload) + result = await self.bot.api._http.request(route, json=payload) + if result is None: + err_msg = "上传文件API返回None,触发重试" + raise APIReturnNoneError(err_msg) + return result try: result = await _do_upload() @@ -654,6 +666,8 @@ async def _do_upload(): file_info=result["file_info"], ttl=result.get("ttl", 0), ) + except APIReturnNoneError: + logger.warning(f"上传文件API返回None,共尝试5次后放弃: {file_source}") except (botpy.errors.ServerError, botpy.errors.SequenceNumberError): logger.error(f"上传媒体文件失败,共尝试5次后放弃: {file_source}") except Exception as e: