fix: qq_official websocket适配器发送消息收到None时现在会重试了 (#8977) (#8979)

* fix: qq_official websocket适配器发送消息收到None时现在会重试了 (#8977)

* fix: qq_official websocket适配器重试发送消息的时候不会重复打印重试告警了

* fix: qq_official websocket适配器发送媒体/文件消息时遇到api返回None也会重试了
This commit is contained in:
shuiping233
2026-07-05 09:50:42 +08:00
committed by GitHub
parent 30426c4f67
commit cc0b347508

View File

@@ -30,6 +30,10 @@ from astrbot.api.platform import AstrBotMessage, PlatformMetadata
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,14 +566,13 @@ class QQOfficialMessageEvent(AstrMessageEvent):
"srv_send_msg": False,
}
@_qqofficial_retry
@_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(
@@ -573,11 +580,20 @@ class QQOfficialMessageEvent(AstrMessageEvent):
"/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(
@@ -629,9 +645,13 @@ class QQOfficialMessageEvent(AstrMessageEvent):
else:
return None
@_qqofficial_retry
@_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()
@@ -646,6 +666,8 @@ class QQOfficialMessageEvent(AstrMessageEvent):
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:
@@ -680,11 +702,26 @@ class QQOfficialMessageEvent(AstrMessageEvent):
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触发重试"
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