Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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):
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
"""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,
Expand Down Expand Up @@ -558,26 +566,34 @@ 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"]
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()
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(
Expand Down Expand Up @@ -629,9 +645,13 @@ 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)
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()
Expand All @@ -646,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:
Expand Down Expand Up @@ -680,11 +702,26 @@ 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,触发重试"
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
Expand Down
Loading