fix: propagate QQ media upload failures (#9644)

This commit is contained in:
Soulter
2026-08-12 23:11:21 +08:00
committed by GitHub
parent fbf5284aec
commit a9a1000e59
4 changed files with 96 additions and 20 deletions
@@ -627,8 +627,23 @@ class QQOfficialMessageEvent(AstrMessageEvent):
srv_send_msg: bool = False,
file_name: str | None = None,
**kwargs,
) -> Media | None:
"""上传媒体文件"""
) -> Media:
"""Upload media to a QQ group or C2C session.
Args:
file_source: Local file path or remote URL to upload.
file_type: QQ media type identifier.
srv_send_msg: Whether QQ should send the media immediately.
file_name: Optional display name for the uploaded file.
**kwargs: Recipient identifier as ``openid`` or ``group_openid``.
Returns:
Metadata for the uploaded media.
Raises:
ValueError: No supported recipient identifier was provided.
Exception: The upload request fails or returns an invalid response.
"""
# 构建基础payload
payload: dict = {"file_type": file_type, "srv_send_msg": srv_send_msg}
if file_name:
@@ -657,7 +672,7 @@ class QQOfficialMessageEvent(AstrMessageEvent):
group_openid=kwargs["group_openid"],
)
else:
return None
raise ValueError("Invalid upload parameters")
@_qqofficial_retry()
async def _do_upload():
@@ -669,25 +684,29 @@ class QQOfficialMessageEvent(AstrMessageEvent):
try:
result = await _do_upload()
if result:
if not isinstance(result, dict):
logger.error(f"上传文件响应格式错误: {result}")
return None
return Media(
file_uuid=result["file_uuid"],
file_info=result["file_info"],
ttl=result.get("ttl", 0),
)
except APIReturnNoneError:
logger.warning(f"上传文件API返回None,共尝试5次后放弃: {file_source}")
logger.warning(
"Media upload API returned None after 5 attempts: %s",
file_source,
)
raise
except (botpy.errors.ServerError, botpy.errors.SequenceNumberError):
logger.error(f"上传媒体文件失败,共尝试5次后放弃: {file_source}")
except Exception as e:
logger.error(f"上传请求错误: {e}")
logger.error("Media upload failed after 5 attempts: %s", file_source)
raise
except Exception as exc:
logger.error("Media upload request failed: %s", exc)
raise
return None
if not isinstance(result, dict):
raise RuntimeError(
f"Failed to upload media, response is not dict: {result}"
)
return Media(
file_uuid=result["file_uuid"],
file_info=result["file_info"],
ttl=result.get("ttl", 0),
)
async def post_c2c_message(
self,
+9 -1
View File
@@ -335,7 +335,15 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
return f"error: invalid session: {session}"
message_chain = MessageChain(chain=components)
await context.context.context.send_message(target_session, message_chain)
try:
sent = await context.context.context.send_message(
target_session,
message_chain,
)
except Exception as exc:
return f"error: failed to send message to session {target_session}: {exc}"
if not sent:
return f"error: failed to find platform for session {target_session}."
if str(target_session) == current_session:
context.context.event._has_send_oper = True
sent_plain_text = message_chain.get_plain_text().strip()
@@ -26,6 +26,9 @@ from astrbot.core.platform.sources.qqofficial.qqofficial_platform_adapter import
from astrbot.core.platform.sources.qqofficial.qqofficial_platform_adapter import (
botClient as QQOfficialBotClient,
)
from astrbot.core.platform.sources.qqofficial.qqofficial_message_event import (
QQOfficialMessageEvent,
)
from astrbot.core.platform.sources.qqofficial_webhook.qo_webhook_adapter import (
QQOfficialWebhookPlatformAdapter,
)
@@ -307,6 +310,31 @@ async def test_ws_group_send_by_session_with_cached_msg_id_still_omits_msg_id():
assert "msg_seq" in kwargs
@pytest.mark.asyncio
async def test_media_upload_propagates_qq_api_error(monkeypatch):
"""QQ upload errors propagate so callers cannot report a false success."""
request = AsyncMock(
side_effect=botpy.errors.ServerError("413 Request Entity Too Large")
)
send_helper = SimpleNamespace(
bot=SimpleNamespace(
api=SimpleNamespace(_http=SimpleNamespace(request=request))
)
)
monkeypatch.setattr(
"astrbot.core.platform.sources.qqofficial.qqofficial_message_event._qqofficial_retry",
lambda *args, **kwargs: lambda func: func,
)
with pytest.raises(botpy.errors.ServerError, match="413 Request Entity Too Large"):
await QQOfficialMessageEvent.upload_group_and_c2c_media(
send_helper,
"https://example.com/large.bin",
QQOfficialMessageEvent.FILE_FILE_TYPE,
group_openid="group-1",
)
@pytest.mark.asyncio
async def test_webhook_group_send_by_session_without_cached_msg_id_omits_msg_id():
adapter = QQOfficialWebhookPlatformAdapter(
+21
View File
@@ -153,6 +153,27 @@ async def test_send_message_defaults_to_current_session():
) == ["hello"]
@pytest.mark.asyncio
async def test_send_message_returns_platform_error_to_tool_result():
"""Platform send failures are returned to the agent as tool errors."""
tool = SendMessageToUserTool()
ctx = _make_context(current_session="qq_official:GroupMessage:group-1")
ctx.context.context.send_message.side_effect = RuntimeError(
"413 Request Entity Too Large"
)
result = await tool.call(
ctx,
messages=[{"type": "plain", "text": "hello"}],
)
assert result == (
"error: failed to send message to session "
"qq_official:GroupMessage:group-1: 413 Request Entity Too Large"
)
assert ctx.context.event._has_send_oper is False
@pytest.mark.asyncio
async def test_send_message_other_session_does_not_record_current_text():
"""Messages sent to another session do not affect current-session dedupe."""