mirror of
https://github.com/AstrBotDevs/AstrBot.git
synced 2026-08-30 17:33:24 +08:00
fix: propagate QQ media upload failures (#9644)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user