diff --git a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py index 97b2b2fb49..e510a6362f 100644 --- a/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +++ b/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py @@ -1,5 +1,6 @@ import asyncio import base64 +import logging import os import random import uuid @@ -15,6 +16,13 @@ from botpy.http import Route from botpy.types import message from botpy.types.message import MarkdownPayload, Media +from tenacity import ( + before_sleep_log, + retry, + retry_if_exception_type, + stop_after_attempt, + wait_exponential, +) from astrbot.api import logger from astrbot.api.event import AstrMessageEvent, MessageChain @@ -44,6 +52,20 @@ 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, + ) + ), + stop=stop_after_attempt(3), + wait=wait_exponential(multiplier=1, min=1, max=10), + before_sleep=before_sleep_log(logger, logging.WARNING), + reraise=True, +) + class QQOfficialMessageEvent(AstrMessageEvent): MARKDOWN_NOT_ALLOWED_ERROR = "不允许发送原生 markdown" @@ -453,21 +475,26 @@ async def upload_group_and_c2c_image( "srv_send_msg": False, } - result = None - if "openid" in kwargs: - payload["openid"] = kwargs["openid"] - route = Route("POST", "/v2/users/{openid}/files", openid=kwargs["openid"]) - result = await self.bot.api._http.request(route, json=payload) - elif "group_openid" in kwargs: - payload["group_openid"] = kwargs["group_openid"] - route = Route( - "POST", - "/v2/groups/{group_openid}/files", - group_openid=kwargs["group_openid"], - ) - result = await self.bot.api._http.request(route, json=payload) - else: - raise ValueError("Invalid upload parameters") + @_qqofficial_retry + async def _do_upload(): + if "openid" in kwargs: + payload["openid"] = kwargs["openid"] + 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( + "POST", + "/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() if not isinstance(result, dict): raise RuntimeError( @@ -490,7 +517,7 @@ async def upload_group_and_c2c_media( ) -> Media | None: """上传媒体文件""" # 构建基础payload - payload = {"file_type": file_type, "srv_send_msg": srv_send_msg} + payload: dict = {"file_type": file_type, "srv_send_msg": srv_send_msg} if file_name: payload["file_name"] = file_name @@ -519,9 +546,12 @@ async def upload_group_and_c2c_media( else: return None + @_qqofficial_retry + async def _do_upload(): + return await self.bot.api._http.request(route, json=payload) + try: - # 使用底层HTTP请求 - result = await self.bot.api._http.request(route, json=payload) + result = await _do_upload() if result: if not isinstance(result, dict): @@ -533,6 +563,8 @@ async def upload_group_and_c2c_media( file_info=result["file_info"], ttl=result.get("ttl", 0), ) + except (botpy.errors.ServerError, botpy.errors.SequenceNumberError): + logger.error(f"上传媒体文件失败,共尝试3次后放弃: {file_source}") except Exception as e: logger.error(f"上传请求错误: {e}")