-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Description
What happened / 发生了什么
由于未发布的QQ官方机器人仅加入用户私聊进行测试,而无法进入群聊/频道,因此只能确定私聊场景下存在问题
Bug简述
在定时任务/连续多次回复的场景下,在机器人会连续多次调用send_message_to_user 尝试向用户发送消息。此时工具会在第二次及以后调用send_message_to_user工具时失败并抛出botpy.errors.ServerError: 请求参数msg_id无效或越权异常。疑似AstrBot携带非法的msg_id向QQ机器人的API端口POST /channels/{channel_id}/messages发送请求
进一步排查:
出现Bug后拉取最新的提交并打日志进行测试,在以下位置添加了日志点:
https://github.com/AstrBotDevs/AstrBot/blob/master/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py#L564-L567
该日志点用于查看发送给QQ机器人官方API接口的payload与其返回的result。
# qqofficial_message_event.py
async def post_c2c_message(
...
# QQ API does not accept stream.id=None; remove it when not yet assigned
if "stream" in payload and payload["stream"] is not None:
stream_data = dict(payload["stream"])
if stream_data.get("id") is None:
stream_data.pop("id", None)
payload["stream"] = stream_data
+ logger.warning(f"post_c2c_message payload: {payload}")
route = Route("POST", "/v2/users/{openid}/messages", openid=openid)
result = await self.bot.api._http.request(route, json=payload)
+ logger.warning(f"post_c2c_message result: {result}")https://github.com/AstrBotDevs/AstrBot/blob/master/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py#L299
根据send_message_to_user链路在send_by_session_common方法下新增日志点
# qqofficial_platform_adapter.py
sent_message_id = self._extract_message_id(ret)
+ logger.warning(f"Extracted message id: {sent_message_id}")
if sent_message_id:
self.remember_session_message_id(session.session_id, sent_message_id)
await super().send_by_session(session, message_chain)以下为补充测试的推测结果:
- 用户发送数据时,携带的msg_id为
ROBOT1.0_rPi8nvmC5Vd7prorHpcvSx.FVqNgftgITNJlZJEwDOLHhnWCzgZTp23GuJgkfG34mXFVGFiu0y9gfEfQ4oQHJg!! - AstrBot在第一次发送消息时,直接使用用户发送的msg_id,并在在发送成功后通过
_extract_message_id方法将msg_id修改为自身所回复的消息的meg_idROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!
[15:04:16.478] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 0, 'content': '📬 测试消息 1:这是第一条测试消息,用于验证消息发送功能。', 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_BJkd-tCn0-jaN8Z9LqrswYvSio4gBHeEZ4Ll8fWlRBz5mBnCjAPEqFq-p328W.tO3rRho2CagBvejX2fLfQ0N2LBnPsOL4m9jxp8NoXJm8s!', 'msg_seq': 9390, 'event_id': None, 'markdown': None, 'keyboard': None, 'stream': None}
[15:04:16.762] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:569]: post_c2c_message result: {'id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!', 'timestamp': '2026-03-25T15:03:20+08:00', 'ext_info': {'ref_idx': 'REFIDX_6GHE5LhENdDipd9TCWRVGA=='}}
[15:04:16.763] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_platform_adapter:300]: Extracted message ID: ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!
- 第二次及之后的发送使用修改后的msg_id
ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!,此时接口返回异常:
[15:04:17.357] [Core] [INFO] [runners.tool_loop_agent_runner:686]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '📬 测试消息 2:这是第二条测试消息,消息发送正常进行中。'}]}
[15:04:17.357] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 0, 'content': '📬 测试消息 2:这是第二条测试消息,消息发送正常进行中。', 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!', 'msg_seq': 7579, 'event_id': None, 'markdown': None, 'keyboard': None, 'stream': None}
[botpy] 接口请求异常,请求连接: https://api.sgroup.qq.com/v2/users/6836D8522EE75AD3732E75C0D3B6F33A/messages, 错误代码: 400, 返回内容: {'message': '请求参数msg_id无效或越权', 'code': 40034024, 'err_code': 40034024, 'trace_id': '0db5bf0aaac4a197a5dfb69e12955c4f'}, trace_id:0db5bf0aaac4a197a5dfb69e12955c4f
可能性推测
由于官方文档并未对msg_id检验逻辑做详细说明,因此以下结论为推测
根据QQ机器人开放平台中POST /channels/{channel_id}/messages的接口文档中关于发送消息的说明:
主动消息:发送消息时,未填充 msg_id/event_id 字段的消息。
被动消息:发送消息时,填充了 msg_id/event_id 字段的消息。msg_id 和 event_id 两个字段任意填一个即为被动消息。接口使用此 msg_id/event_id 拉取用户的消息或事件,同时判断用户消息或事件的发送时间,如果超过被动消息回复时效,将会不允许发送该消息。
携带msg_id的消息被视为被动消息,接口会通过msg_id拉取用户的消息并进行判断。在第二次及以后发送消息时,msg_id被修改为了机器人自身所发送的消息,官方接口可能将机器人使用自身msg_id视为非法行为?,导致接口直接返回异常
{
'message': '请求参数msg_id无效或越权',
'code': 40034024,
'err_code': 40034024,
'trace_id': '0db5bf0aaac4a197a5dfb69e12955c4f'
}Reproduce / 如何复现?
- 通过QQ官方机器人发起连续消息请求,例如
向我连续发送3条消息用于测试,启用一个心跳任务,每分钟向我发送一次心跳信号 - 检查机器人回复,发现在第一次调用
send_message_to_user,能够正常发送消息,而此后的次数均无法正常发送消息
AstrBot version, deployment method (e.g., Windows Docker Desktop deployment), provider used, and messaging platform used. / AstrBot 版本、部署方式(如 Windows Docker Desktop 部署)、使用的提供商、使用的消息平台适配器
AstrBot版本: 4.22.0
部署方式: Docker
使用的提供商: OpenAI Compatible
使用的消息平台适配器: QQ Official Websocket
OS
Linux
Logs / 报错日志
以下为添加日志点后的日志输出
[15:04:09.610] [Core] [INFO] [core.event_bus:66]: [default] [dev(qq_official)] 6836D8522EE75AD3732E75C0D3B6F33A: [At:qq_official] 向我连续发送3条消息,测试用
[15:04:15.539] [Core] [INFO] [respond.stage:184]: Prepare to send - /6836D8522EE75AD3732E75C0D3B6F33A: 好的,我来为您发送3条测试消息。
[15:04:15.539] [Core] [INFO] [qqofficial.qqofficial_message_event:163]: _post_send send_buffer: MessageChain(chain=[Plain(type=<ComponentType.Plain: 'Plain'>, text='好的,我来为您发送3条测试消息。', convert=True)], use_t2i_=None, type=None)
[15:04:15.539] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 2, 'content': None, 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_BJkd-tCn0-jaN8Z9LqrswYvSio4gBHeEZ4Ll8fWlRBz5mBnCjAPEqFq-p328W.tO3rRho2CagBvejX2fLfQ0N2LBnPsOL4m9jxp8NoXJm8s!', 'msg_seq': 1337, 'event_id': None, 'markdown': {'content': '好的,我来为您发送3条测试消息。'}, 'keyboard': None, 'stream': None}
[15:04:16.477] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:569]: post_c2c_message result: {'id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvSx.FVqNgftgITNJlZJEwDOLHhnWCzgZTp23GuJgkfG34mXFVGFiu0y9gfEfQ4oQHJg!!', 'timestamp': '2026-03-25T15:03:20+08:00', 'ext_info': {'ref_idx': 'REFIDX_Qpu+5GrJUgHn+e6AnGVsDstG81ovPjw88HwjHppK6Gc='}}
[15:04:16.477] [Core] [INFO] [runners.tool_loop_agent_runner:640]: Agent 使用工具: ['send_message_to_user', 'send_message_to_user', 'send_message_to_user']
[15:04:16.477] [Core] [INFO] [runners.tool_loop_agent_runner:686]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '📬 测试消息 1:这是第一条测试消息,用于验证消息发送功能。'}]}
[15:04:16.478] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 0, 'content': '📬 测试消息 1:这是第一条测试消息,用于验证消息发送功能。', 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_BJkd-tCn0-jaN8Z9LqrswYvSio4gBHeEZ4Ll8fWlRBz5mBnCjAPEqFq-p328W.tO3rRho2CagBvejX2fLfQ0N2LBnPsOL4m9jxp8NoXJm8s!', 'msg_seq': 9390, 'event_id': None, 'markdown': None, 'keyboard': None, 'stream': None}
[15:04:16.762] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:569]: post_c2c_message result: {'id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!', 'timestamp': '2026-03-25T15:03:20+08:00', 'ext_info': {'ref_idx': 'REFIDX_6GHE5LhENdDipd9TCWRVGA=='}}
[15:04:16.763] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_platform_adapter:300]: Extracted message ID: ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!
[15:04:17.357] [Core] [INFO] [runners.tool_loop_agent_runner:686]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '📬 测试消息 2:这是第二条测试消息,消息发送正常进行中。'}]}
[15:04:17.357] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 0, 'content': '📬 测试消息 2:这是第二条测试消息,消息发送正常进行中。', 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!', 'msg_seq': 7579, 'event_id': None, 'markdown': None, 'keyboard': None, 'stream': None}
[botpy] 接口请求异常,请求连接: https://api.sgroup.qq.com/v2/users/6836D8522EE75AD3732E75C0D3B6F33A/messages, 错误代码: 400, 返回内容: {'message': '请求参数msg_id无效或越权', 'code': 40034024, 'err_code': 40034024, 'trace_id': '0db5bf0aaac4a197a5dfb69e12955c4f'}, trace_id:0db5bf0aaac4a197a5dfb69e12955c4f
[15:04:17.523] [Core] [WARN] [v4.22.0] [runners.tool_loop_agent_runner:844]: Traceback (most recent call last):
File "/home/remilia/Develop/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 740, in _handle_function_tools
async for resp in self._iter_tool_executor_results(executor): # type: ignore
File "/home/remilia/Develop/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 1038, in _iter_tool_executor_results
yield next_result_task.result()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_agent_tool_exec.py", line 175, in execute
async for r in cls._execute_local(tool, run_context, **tool_args):
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_agent_tool_exec.py", line 597, in _execute_local
resp = await asyncio.wait_for(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/.local/share/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/python3.12/asyncio/tasks.py", line 520, in wait_for
return await fut
^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_agent_tool_exec.py", line 738, in call_local_llm_tool
ret = await ready_to_call
^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_main_agent_resources.py", line 386, in call
await context.context.context.send_message(
File "/home/remilia/Develop/AstrBot/astrbot/core/star/context.py", line 456, in send_message
await platform.send_by_session(session, message_chain)
File "/home/remilia/Develop/AstrBot/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py", line 144, in send_by_session
await self._send_by_session_common(session, message_chain)
File "/home/remilia/Develop/AstrBot/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py", line 287, in _send_by_session_common
ret = await QQOfficialMessageEvent.post_c2c_message(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py", line 568, in post_c2c_message
result = await self.bot.api._http.request(route, json=payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/.venv/lib/python3.12/site-packages/botpy/http.py", line 190, in request
return await _handle_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/.venv/lib/python3.12/site-packages/botpy/http.py", line 85, in _handle_response
raise ServerError(message) from None # adding from None to prevent chain exception being raised
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
botpy.errors.ServerError: 请求参数msg_id无效或越权
[15:04:17.523] [Core] [INFO] [runners.tool_loop_agent_runner:686]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '📬 测试消息 3:这是第三条测试消息,测试完成!✅'}]}
[15:04:17.523] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 0, 'content': '📬 测试消息 3:这是第三条测试消息,测试完成!✅', 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvS0-fh9JLYG.SVBAt79PHlVhXDSoYEQHES9Hf88YVFSOxVLzfr4iOMkm8WJlNQJhc9A!!', 'msg_seq': 2875, 'event_id': None, 'markdown': None, 'keyboard': None, 'stream': None}
[botpy] 接口请求异常,请求连接: https://api.sgroup.qq.com/v2/users/6836D8522EE75AD3732E75C0D3B6F33A/messages, 错误代码: 400, 返回内容: {'message': '请求参数msg_id无效或越权', 'code': 40034024, 'err_code': 40034024, 'trace_id': '8cbfe05773d4402496e605e19336e396'}, trace_id:8cbfe05773d4402496e605e19336e396
[15:04:18.034] [Core] [WARN] [v4.22.0] [runners.tool_loop_agent_runner:844]: Traceback (most recent call last):
File "/home/remilia/Develop/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 740, in _handle_function_tools
async for resp in self._iter_tool_executor_results(executor): # type: ignore
File "/home/remilia/Develop/AstrBot/astrbot/core/agent/runners/tool_loop_agent_runner.py", line 1038, in _iter_tool_executor_results
yield next_result_task.result()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_agent_tool_exec.py", line 175, in execute
async for r in cls._execute_local(tool, run_context, **tool_args):
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_agent_tool_exec.py", line 597, in _execute_local
resp = await asyncio.wait_for(
^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/.local/share/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/python3.12/asyncio/tasks.py", line 520, in wait_for
return await fut
^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_agent_tool_exec.py", line 738, in call_local_llm_tool
ret = await ready_to_call
^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/astr_main_agent_resources.py", line 386, in call
await context.context.context.send_message(
File "/home/remilia/Develop/AstrBot/astrbot/core/star/context.py", line 456, in send_message
await platform.send_by_session(session, message_chain)
File "/home/remilia/Develop/AstrBot/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py", line 144, in send_by_session
await self._send_by_session_common(session, message_chain)
File "/home/remilia/Develop/AstrBot/astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py", line 287, in _send_by_session_common
ret = await QQOfficialMessageEvent.post_c2c_message(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py", line 568, in post_c2c_message
result = await self.bot.api._http.request(route, json=payload)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/.venv/lib/python3.12/site-packages/botpy/http.py", line 190, in request
return await _handle_response(response)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/remilia/Develop/AstrBot/.venv/lib/python3.12/site-packages/botpy/http.py", line 85, in _handle_response
raise ServerError(message) from None # adding from None to prevent chain exception being raised
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
botpy.errors.ServerError: 请求参数msg_id无效或越权
[15:04:18.034] [Core] [INFO] [runners.tool_loop_agent_runner:867]: Tool `send_message_to_user` Result: error: 请求参数msg_id无效或越权
[15:04:22.175] [Core] [INFO] [respond.stage:184]: Prepare to send - /6836D8522EE75AD3732E75C0D3B6F33A: 测试消息发送情况如下:
✅ **第1条消息**:发送成功!
❌ **第2条消息**:发送失败(参数错误)
❌ **第3条消息**:发送失败(参数错误)
看起来连续快速发送多条消息时遇到了限制。第一条测试消息已经成功送达您的会话。如果您需要继续测试,可以稍后再试,或者我可以尝试其他方式帮您测试消息功能。
请问还有其他需要我协助的吗?
[15:04:22.175] [Core] [INFO] [qqofficial.qqofficial_message_event:163]: _post_send send_buffer: MessageChain(chain=[Plain(type=<ComponentType.Plain: 'Plain'>, text='测试消息发送情况如下:\n\n✅ **第1条消息**:发送成功!\n❌ **第2条消息**:发送失败(参数错误)\n❌ **第3条消息**:发送失败(参数错误)\n\n看起来连续快速发送多条消息时遇到了限制。第一条测试消息已经成功送达您的会话。如果您需要继续测试,可以稍后再试,或者我可以尝试其他方式帮您测试消息功能。\n\n请问还有其他需要我协助的吗?', convert=True)], use_t2i_=None, type=None)
[15:04:22.175] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:567]: post_c2c_message payload: {'openid': '6836D8522EE75AD3732E75C0D3B6F33A', 'msg_type': 2, 'content': None, 'embed': None, 'ark': None, 'message_reference': None, 'media': None, 'msg_id': 'ROBOT1.0_BJkd-tCn0-jaN8Z9LqrswYvSio4gBHeEZ4Ll8fWlRBz5mBnCjAPEqFq-p328W.tO3rRho2CagBvejX2fLfQ0N2LBnPsOL4m9jxp8NoXJm8s!', 'msg_seq': 7061, 'event_id': None, 'markdown': {'content': '测试消息发送情况如下:\n\n✅ **第1条消息**:发送成功!\n❌ **第2条消息**:发送失败(参数错误)\n❌ **第3条消息**:发送失败(参数错误)\n\n看起来连续快速发送多条消息时遇到了限制。第一条测试消息已经成功送达您的会话。如果您需要继续测试,可以稍后再试,或者我可以尝试其他方式帮您测试消息功能。\n\n请问还有其他需要我协助的吗?'}, 'keyboard': None, 'stream': None}
[15:04:23.030] [Core] [WARN] [v4.22.0] [qqofficial.qqofficial_message_event:569]: post_c2c_message result: {'id': 'ROBOT1.0_rPi8nvmC5Vd7prorHpcvS.6JzcW3CMoLCreHT11YzbWfy3cWKCsE0QypCpYbB0BpVLzfr4iOMkm8WJlNQJhc9A!!', 'timestamp': '2026-03-25T15:03:26+08:00', 'ext_info': {'ref_idx': 'REFIDX_k2tzKyiEpo4xb9P+zfx5/g=='}}
Are you willing to submit a PR? / 你愿意提交 PR 吗?
- Yes!
Code of Conduct
- I have read and agree to abide by the project's Code of Conduct。