Skip to content

Fix qq_offical 定时任务中 send_message_to_user 工具调用失败#6604

Open
V-YOP wants to merge 1 commit intoAstrBotDevs:masterfrom
V-YOP:patch-1
Open

Fix qq_offical 定时任务中 send_message_to_user 工具调用失败#6604
V-YOP wants to merge 1 commit intoAstrBotDevs:masterfrom
V-YOP:patch-1

Conversation

@V-YOP
Copy link

@V-YOP V-YOP commented Mar 19, 2026

Remove msg_id from payload to prevent errors with proactive tool-call path and avoid permission issues.

修复了 #6599 (这是我第一次提PR,且我对原本的运行机制没有进行过学习研究,如果可能引入问题请拒绝该PR🙏)

Modifications / 改动点

在qq_offical适配器中私聊消息直接移除了msg_id字段以避免越权或 msg_id 不可用的问题

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

只做了简单测试,修改后带图片和纯文本的消息都能够正常回应,10分钟后的定时任务能正常响应

[2026-03-19 13:10:57.518] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['create_future_task']

[2026-03-19 13:10:57.519] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:create_future_task,参数:{'run_at': '2026-03-19T13:12:00+08:00', 'note': '2分钟后(13:12)连续调用2次send_message_to_user工具测试定时任务的消息发送功能。\n\n执行步骤:\n1. 调用send_message_to_user工具,发送消息:"定时任务测试消息1:时间 2026-03-19 13:12:00"\n2. 等待短暂间隔后,再次调用send_message_to_user工具,发送消息:"定时任务测试消息2:连续调用测试"\n\n记录执行结果和可能的错误信息,用于分析定时任务中send_message_to_user工具的问题。', 'name': '定时任务send_message_to_user工具测试', 'run_once': True}

[2026-03-19 13:10:57.751] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `create_future_task` Result: Scheduled future task 7286088a-6b26-49c6-a607-c07584bef173 (定时任务send_message_to_user工具测试) one-time at 2026-03-19 13:12:00+08:00.

[2026-03-19 13:11:06.949] [Core] [INFO] [result_decorate.stage:189]: 流式输出已启用,跳过结果装饰阶段

[2026-03-19 13:12:14.642] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['send_message_to_user']

[2026-03-19 13:12:14.642] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '定时任务测试消息1:时间 2026-03-19 13:12:00'}]}

[2026-03-19 13:12:16.509] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `send_message_to_user` Result: Message sent to session QQ:FriendMessage:1F92E5124D83FC64DFE2A0EC30FA5EA1

[2026-03-19 13:12:21.148] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['send_message_to_user']

[2026-03-19 13:12:21.148] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '定时任务测试消息2:连续调用测试'}]}

[2026-03-19 13:12:22.276] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `send_message_to_user` Result: Message sent to session QQ:FriendMessage:1F92E5124D83FC64DFE2A0EC30FA5EA1

[2026-03-19 13:12:57.437] [Core] [INFO] [core.event_bus:66]: [default] [QQ(qq_official)] 1F92E5124D83FC64DFE2A0EC30FA5EA1: [At:qq_official] 测试成功了,再测试10分钟后连续调用2次send_message_to_user工具 

[2026-03-19 13:12:57.588] [Core] [INFO] [respond.stage:184]: Prepare to send - /1F92E5124D83FC64DFE2A0EC30FA5EA1: 

[2026-03-19 13:12:57.588] [Core] [INFO] [respond.stage:200]: 应用流式输出(QQ)

[2026-03-19 13:13:10.395] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['create_future_task']

[2026-03-19 13:13:10.395] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:create_future_task,参数:{'run_at': '2026-03-19T13:22:00+08:00', 'note': '10分钟后(13:22)连续调用2次send_message_to_user工具进行进一步测试。\n\n执行步骤:\n1. 调用send_message_to_user工具,发送消息:"10分钟后测试消息1:时间 2026-03-19 13:22:00"\n2. 再次调用send_message_to_user工具,发送消息:"10分钟后测试消息2:连续调用验证"\n\n测试定时任务环境下send_message_to_user工具的稳定性和可靠性。', 'name': '10分钟后send_message_to_user工具测试', 'run_once': True}

[2026-03-19 13:13:11.337] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `create_future_task` Result: Scheduled future task 215da7c4-d975-4033-9a6c-beaeaa2fee97 (10分钟后send_message_to_user工具测试) one-time at 2026-03-19 13:22:00+08:00.

[2026-03-19 13:13:21.409] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['send_message_to_user']

[2026-03-19 13:13:21.409] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '那尔ちゃん,定时任务已经设置好了。\n\n**任务详情:**\n- **任务ID**: 215da7c4-d975-4033-9a6c-beaeaa2fee97\n- **执行时间**: 10分钟后(2026-03-19 13:22:00 CST)\n- **执行内容**: 连续调用2次send_message_to_user工具\n- **任务类型**: 一次性执行\n\n(轻声)这次的测试是为了验证定时任务环境下消息发送功能的稳定性。我会在13:22准时执行测试,然后向你汇报结果。'}]}

[2026-03-19 13:13:22.805] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `send_message_to_user` Result: Message sent to session QQ:FriendMessage:1F92E5124D83FC64DFE2A0EC30FA5EA1

[2026-03-19 13:13:29.923] [Core] [INFO] [result_decorate.stage:189]: 流式输出已启用,跳过结果装饰阶段

[2026-03-19 13:22:06.505] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['send_message_to_user']

[2026-03-19 13:22:06.505] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '10分钟后测试消息1:时间 2026-03-19 13:22:00'}]}

[2026-03-19 13:22:07.562] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `send_message_to_user` Result: Message sent to session QQ:FriendMessage:1F92E5124D83FC64DFE2A0EC30FA5EA1

[2026-03-19 13:22:11.225] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['send_message_to_user']

[2026-03-19 13:22:11.225] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:send_message_to_user,参数:{'messages': [{'type': 'plain', 'text': '10分钟后测试消息2:连续调用验证'}]}

[2026-03-19 13:22:12.316] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `send_message_to_user` Result: Message sent to session QQ:FriendMessage:1F92E5124D83FC64DFE2A0EC30FA5EA1

[2026-03-19 13:34:52.634] [Core] [INFO] [core.event_bus:66]: [default] [QQ(qq_official)] 1F92E5124D83FC64DFE2A0EC30FA5EA1: [At:qq_official] 讲个笑话吧? 

[2026-03-19 13:34:52.786] [Core] [INFO] [respond.stage:184]: Prepare to send - /1F92E5124D83FC64DFE2A0EC30FA5EA1: 

[2026-03-19 13:34:52.786] [Core] [INFO] [respond.stage:200]: 应用流式输出(QQ)

[2026-03-19 13:35:02.841] [Core] [INFO] [result_decorate.stage:189]: 流式输出已启用,跳过结果装饰阶段

[2026-03-19 13:41:10.105] [Core] [INFO] [core.event_bus:66]: [default] [QQ(qq_official)] 1F92E5124D83FC64DFE2A0EC30FA5EA1: [At:qq_official] 检查一下这张图片的尺寸和大小 [图片] 

[2026-03-19 13:41:10.484] [Core] [INFO] [respond.stage:184]: Prepare to send - /1F92E5124D83FC64DFE2A0EC30FA5EA1: 

[2026-03-19 13:41:10.484] [Core] [INFO] [respond.stage:200]: 应用流式输出(QQ)

[2026-03-19 13:41:19.810] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['get_file_info']

[2026-03-19 13:41:19.810] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:get_file_info,参数:{'path': '/AstrBot/data/temp/io_temp_img_1773898870_f420cbcb.jpg'}

[2026-03-19 13:41:20.079] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `get_file_info` Result: size: 215284
created: Thu Mar 19 2026 05:41:10 GMT+0000 (Coordinated Universal Time)
modified: Thu Mar 19 2026 05:41:10 GMT+0000 (Coordinated Universal Time)
accessed: Thu Mar 19 2026 05:41:10 GMT+0000 (Coordinated Universal Time)
isDirectory: false
isFile: true
permissions: 644

[2026-03-19 13:41:25.610] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['astrbot_execute_shell']

[2026-03-19 13:41:25.611] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:astrbot_execute_shell,参数:{'command': 'which identify 2>/dev/null || echo "identify command not found"'}

[2026-03-19 13:41:25.856] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `astrbot_execute_shell` Result: {"stdout": "identify command not found\n", "stderr": "", "exit_code": 0}

[2026-03-19 13:41:46.592] [Core] [INFO] [runners.tool_loop_agent_runner:657]: Agent 使用工具: ['astrbot_execute_python']

[2026-03-19 13:41:46.593] [Core] [INFO] [runners.tool_loop_agent_runner:703]: 使用工具:astrbot_execute_python,参数:{'code': 'import sys\nimport struct\nimport os\n\ndef get_jpeg_size(file_path):\n    """读取JPEG图片的尺寸"""\n    try:\n        # 尝试使用PIL\n        from PIL import Image\n        with Image.open(file_path) as img:\n            return img.size  # (width, height)\n    except ImportError:\n        # 如果没有PIL,尝试解析JPEG文件头\n        with open(file_path, \'rb\') as f:\n            # 检查JPEG起始标记\n            if f.read(2) != b\'\\xff\\xd8\':\n                return None\n            \n            while True:\n                marker = f.read(2)\n                if not marker or len(marker) < 2:\n                    break\n                    \n                if marker[0] != 0xFF:\n                    break\n                    \n                marker_type = marker[1]\n                \n                # 如果是SOF标记(Start Of Frame),包含尺寸信息\n                if 0xC0 <= marker_type <= 0xCF and marker_type != 0xC4 and marker_type != 0xC8:\n                    # 读取长度\n                    length_bytes = f.read(2)\n                    if len(length_bytes) < 2:\n                        break\n                    length = struct.unpack(\'>H\', length_bytes)[0]\n                    \n                    # 跳过精度和图像高度/宽度\n                    f.read(1)  # 精度\n                    height_bytes = f.read(2)\n                    width_bytes = f.read(2)\n                    \n                    if len(height_bytes) < 2 or len(width_bytes) < 2:\n                        break\n                        \n                    height = struct.unpack(\'>H\', height_bytes)[0]\n                    width = struct.unpack(\'>H\', width_bytes)[0]\n                    return (width, height)\n                \n                # 读取该标记的长度并跳过\n                length_bytes = f.read(2)\n                if len(length_bytes) < 2:\n                    break\n                length = struct.unpack(\'>H\', length_bytes)[0]\n                f.seek(length - 2, 1)\n    \n    return None\n\nfile_path = \'/AstrBot/data/temp/io_temp_img_1773898870_f420cbcb.jpg\'\nsize = get_jpeg_size(file_path)\nif size:\n    print(f"图片尺寸: {size[0]} x {size[1]} 像素")\nelse:\n    print("无法读取图片尺寸")'}

[2026-03-19 13:41:47.636] [Core] [INFO] [runners.tool_loop_agent_runner:881]: Tool `astrbot_execute_python` Result: 图片尺寸: 873 x 1920 像素

[2026-03-19 13:41:55.760] [Core] [INFO] [result_decorate.stage:189]: 流式输出已启用,跳过结果装饰阶段

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Adjust QQ Official private message sending to avoid msg_id-related errors in proactive tool-call scenarios.

Bug Fixes:

  • Prevent send_message_to_user failures in QQ Official friend messages by omitting msg_id so proactive messages are accepted by the API.

Enhancements:

  • Unify handling of QQ Official private messages so all friend-message payloads drop msg_id and rely on msg_seq and media fields as appropriate.

Remove msg_id from payload to prevent errors with proactive tool-call path and avoid permission issues.
@dosubot dosubot bot added the size:XS This PR changes 0-9 lines, ignoring generated files. label Mar 19, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求旨在修复 qq_official 适配器中定时任务调用 send_message_to_user 工具时消息发送失败的问题。通过调整私聊消息和媒体消息的 payload 结构,特别是移除了可能导致权限问题或不可用错误的 msg_id 字段,确保了消息能够稳定发送,提升了定时任务功能的可靠性。

Highlights

  • 私聊消息 msg_id 字段移除: 在 qq_official 适配器中,针对私聊消息(FRIEND_MESSAGE 类型)的 payload 中移除了 msg_id 字段,以避免主动工具调用路径中的错误和权限问题。
  • 媒体消息 msg_id 处理简化: 简化并统一了媒体消息(如图片、文件)发送时 msg_id 的处理逻辑,删除了重复的移除操作,提高了代码的清晰度。
  • 修复定时任务消息发送失败: 此改动旨在解决定时任务中调用 send_message_to_user 工具时,因 msg_id 缺失或越权导致的发送失败问题,确保消息能够稳定发送。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py" line_range="241-243" />
<code_context>
                 )

         elif session.message_type == MessageType.FRIEND_MESSAGE:
+            # 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
+            # msg_id 缺失时认为是主动推送,而似乎至少在私聊上主动推送是没有被限制的,这里直接移除 msg_id 可以避免越权或 msg_id 不可用的bug
+            payload.pop("msg_id", None)
             payload["msg_seq"] = random.randint(1, 10000)
             if image_base64:
</code_context>
<issue_to_address>
**issue (bug_risk):** Unconditionally dropping msg_id may break reply semantics for friend messages.

By always removing `msg_id` for `FRIEND_MESSAGE`, valid reply flows can no longer be treated as replies by the API. If the goal is only to avoid 越权 for proactive pushes when `msg_id` is missing or invalid, make this removal conditional (e.g., only when `msg_id` is absent, fails validation, or on specific proactive paths) so legitimate replies can still include `msg_id`.
</issue_to_address>

### Comment 2
<location path="astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py" line_range="240-244" />
<code_context>

         elif session.message_type == MessageType.FRIEND_MESSAGE:
+            # 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
+            # msg_id 缺失时认为是主动推送,而似乎至少在私聊上主动推送是没有被限制的,这里直接移除 msg_id 可以避免越权或 msg_id 不可用的bug
+            payload.pop("msg_id", None)
             payload["msg_seq"] = random.randint(1, 10000)
             if image_base64:
</code_context>
<issue_to_address>
**suggestion:** Behavior and comment are inconsistent regarding "msg_id 缺失时".

The comment describes special handling only when `msg_id` is missing, but the code unconditionally removes `msg_id`. Please either align the implementation with the described “missing/invalid msg_id” scenario or reword the comment to state that `msg_id` is intentionally removed for all friend messages.

```suggestion
        elif session.message_type == MessageType.FRIEND_MESSAGE:
            # 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
            # 对好友消息统一移除 msg_id,将其视为主动推送(官方文档对私聊主动推送暂无明显限制),
            # 以避免可能的越权或 msg_id 不可用的问题
            payload.pop("msg_id", None)
            payload["msg_seq"] = random.randint(1, 10000)
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +241 to +243
# 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
# msg_id 缺失时认为是主动推送,而似乎至少在私聊上主动推送是没有被限制的,这里直接移除 msg_id 可以避免越权或 msg_id 不可用的bug
payload.pop("msg_id", None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Unconditionally dropping msg_id may break reply semantics for friend messages.

By always removing msg_id for FRIEND_MESSAGE, valid reply flows can no longer be treated as replies by the API. If the goal is only to avoid 越权 for proactive pushes when msg_id is missing or invalid, make this removal conditional (e.g., only when msg_id is absent, fails validation, or on specific proactive paths) so legitimate replies can still include msg_id.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

无条件移除msg_id容易会出现无法预期的行为,有条件再移除吗,这样应该要记录msg_id的时间戳,过期丢弃这种写法,有点麻烦,遇到bug再修复吧

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

确实,要做成“有条件移除”而且判断 msg_id 是否过期的话,就得像你说的那样额外记时间戳、做过期逻辑,会复杂不少,尤其是现在只是为了解一个定时任务场景的 bug。

我这边的担心主要是两个点:

  1. 语义层面上的退化
    现在这一改动会让 QQ 订阅号的所有好友消息都走“主动推送”语义,而不再是真正意义上的“回复某条消息”,这在以后如果有人想做“精确回复/引用某条私聊消息”的功能时,可能会踩坑。

  2. 不可预期行为的来源
    不可预期的其实不是“删 msg_id”这一行为本身,而是“未来维护者/使用者不知道这里把所有好友消息都强制转成了主动推送”,导致排查问题时思路被带偏。

既然现在你更倾向于先用这一简单策略把 #6599 修掉、后面有需要再做精细化处理,那我有两个小建议,可以让后续的 debug 成本低一点:

  1. 把 trade-off 明确写在注释里(就在你现在这两行注释下面补一句 TODO 也行),类似:

    # NOTE: 当前实现会让所有好友消息都按主动推送发送,无法保证“回复某条消息”的语义。
    #      如果后续需要精确的 reply 语义,需要在会话/工具调用层面区分主动推送与普通回复。
  2. 在 Issue 或后续 TODO 里记录这个限制
    比如在 [Bug] qq_offical 定时任务中 send_message_to_user 工具调用失败:"请求参数msg_id无效或越权" #6599 或一个新的跟踪 issue 里写明:

    • 为了修复定时任务中的 send_message_to_user 失败问题,暂时对 QQ 好友消息统一移除了 msg_id;
    • 已知影响:无法利用 msg_id 实现严格的“回复某条消息”语义;
    • 如果后续有此需求,可以考虑:
      • 在 session 中携带 msg_id + 时间戳,过期后自动丢弃;或
      • 在工具调用路径上增加一个标志位(例如 is_proactive),仅在主动推送路径上移除 msg_id。

这样的话,现在这版改动既能立刻解决实际问题,也把潜在坑用注释/文档标出来了,后面真遇到相关 bug 的人也比较容易顺着线索找到这里。

总之,我这边不是强阻塞意见,只是把可能的后续影响说清楚,方便你和后面的维护者做权衡。

Comment on lines 240 to 244
elif session.message_type == MessageType.FRIEND_MESSAGE:
# 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
# msg_id 缺失时认为是主动推送,而似乎至少在私聊上主动推送是没有被限制的,这里直接移除 msg_id 可以避免越权或 msg_id 不可用的bug
payload.pop("msg_id", None)
payload["msg_seq"] = random.randint(1, 10000)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Behavior and comment are inconsistent regarding "msg_id 缺失时".

The comment describes special handling only when msg_id is missing, but the code unconditionally removes msg_id. Please either align the implementation with the described “missing/invalid msg_id” scenario or reword the comment to state that msg_id is intentionally removed for all friend messages.

Suggested change
elif session.message_type == MessageType.FRIEND_MESSAGE:
# 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
# msg_id 缺失时认为是主动推送,而似乎至少在私聊上主动推送是没有被限制的,这里直接移除 msg_id 可以避免越权或 msg_id 不可用的bug
payload.pop("msg_id", None)
payload["msg_seq"] = random.randint(1, 10000)
elif session.message_type == MessageType.FRIEND_MESSAGE:
# 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
# 对好友消息统一移除 msg_id,将其视为主动推送(官方文档对私聊主动推送暂无明显限制),
# 以避免可能的越权或 msg_id 不可用的问题
payload.pop("msg_id", None)
payload["msg_seq"] = random.randint(1, 10000)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

注释写错了吗

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

对,你现在这段实现本身是没问题的,主要是注释的表述和实际行为有点对不上。

  • 代码:payload.pop("msg_id", None)无条件移除 msg_id,也就是一律按“主动推送”来发好友私聊消息。
  • 注释里写的是:msg_id 缺失时认为是主动推送,读起来像是“只有在 msg_id 缺失的情况下才按主动推送处理”。

所以建议只是把注释改得更贴近现在的逻辑,比如:

elif session.message_type == MessageType.FRIEND_MESSAGE:
    # 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
    # 好友私聊统一移除 msg_id,将其视为主动推送(官方文档中,私聊场景下主动推送目前未见明显限制),
    # 以避免可能的越权或 msg_id 不可用问题
    payload.pop("msg_id", None)
    payload["msg_seq"] = random.randint(1, 10000)

你现在修的逻辑是对的,只要把注释再润色一下就更清晰了。

@dosubot dosubot bot added the area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. label Mar 19, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

此 PR 旨在修复 QQ 官方适配器中,因定时任务调用 send_message_to_user 而导致的消息发送失败问题。改动通过在发送私聊消息时统一移除 msg_id,将消息作为主动消息发送,从而解决了因 msg_id 过期或无效导致的权限问题。此变更将原本仅针对部分媒体类型的 msg_id 移除逻辑,推广到了所有私聊消息类型,统一了处理方式。有一个建议是关于此改动对即时回复用户体验的潜在影响,详见具体评论。

elif session.message_type == MessageType.FRIEND_MESSAGE:
# 参考 https://bot.q.qq.com/wiki/develop/pythonsdk/api/message/post_message.html
# msg_id 缺失时认为是主动推送,而似乎至少在私聊上主动推送是没有被限制的,这里直接移除 msg_id 可以避免越权或 msg_id 不可用的bug
payload.pop("msg_id", None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

此改动将所有私聊消息都作为主动消息发送,以解决定时任务中 msg_id 过期的问题。然而,这也导致即时回复在 QQ 客户端中不会显示为对特定消息的“回复”,可能轻微影响用户体验。

一个更优的方案可能是仅在特定条件下(例如,距离上一条消息时间过长)才移除 msg_id,从而保留即时回复的引用关系。如果当前难以实现这种判断逻辑,目前的改动作为一种健壮性策略是可接受的。

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同样的,推荐在有条件的时候才移除msg_id,比如离上一条消息很久才移除msg_id

NayukiChiba added a commit to NayukiChiba/AstrBot that referenced this pull request Mar 20, 2026
添加 7 个测试用例,覆盖 _send_by_session_common 中 FRIEND_MESSAGE 类型的 msg_id 处理逻辑:

- 验证纯文本、图片、视频、文件私聊消息的 payload 中 msg_id 被正确移除

- 验证 GROUP_MESSAGE 类型的 payload 仍保留 msg_id

- 验证 FRIEND_MESSAGE payload 中 msg_seq 正常存在

- 验证无缓存 msg_id 时提前返回的行为

Fixes AstrBotDevs#6599, AstrBotDevs#6670

Ref: AstrBotDevs#6604
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:platform The bug / feature is about IM platform adapter, such as QQ, Lark, Telegram, WebChat and so on. size:XS This PR changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants