Skip to content

fix(message): 修复 Reply 组件 toDict 方法输出多余字段导致私聊消息发送失败的问题#8477

Merged
RC-CHN merged 1 commit into
AstrBotDevs:masterfrom
NayukiChiba:fix/8476-qq-reply
Jun 2, 2026
Merged

fix(message): 修复 Reply 组件 toDict 方法输出多余字段导致私聊消息发送失败的问题#8477
RC-CHN merged 1 commit into
AstrBotDevs:masterfrom
NayukiChiba:fix/8476-qq-reply

Conversation

@NayukiChiba
Copy link
Copy Markdown
Contributor

@NayukiChiba NayukiChiba commented Jun 1, 2026

  • Reply.toDict() 继承 BaseMessageComponent.toDict() 会将所有非 None 默认字段序列化,导致 OneBot V11 reply 段包含多余字段,引起私聊引用回复失败(message not found)
  • 重写 Reply.toDict() 方法,仅返回 {"type": "reply", "data": {"id": str(self.id)}},符合协议标准
  • 新增 tests/unit/test_aiocqhttp_reply.py,覆盖 Reply.toDict() 输出格式、_parse_onebot_json 路径及私聊发送场景的验证

ref #8476

Modifications / 改动点

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

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


platform win32 -- Python 3.12.12, pytest-9.0.3, pluggy-1.6.0
rootdir: D:\Nayey\Code\NayukiChiba\Astrbot
configfile: pyproject.toml
plugins: anyio-4.13.0, asyncio-1.4.0, cov-7.1.0
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 36 items                                                                                   

tests\unit\test_aiocqhttp_reply.py ......                                                      [ 16%]
tests\unit\test_aiocqhttp_poke.py ....                                                         [ 27%]
tests\test_quoted_message_parser.py ..........................                                 [100%]

36 passed in 2.20s 

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

Fix OneBot V11 reply segment serialization for the Reply component to prevent private message reply failures and add regression tests for the aiocqhttp path.

Bug Fixes:

  • Ensure Reply.toDict() outputs only the id field in the OneBot V11 reply segment to avoid message not found errors in private chats.

Tests:

  • Add unit tests for Reply.toDict() output, OneBot JSON parsing, and private/group sending flows on the aiocqhttp platform to guard against regressions in reply handling.

- Reply.toDict() 继承 BaseMessageComponent.toDict() 会将所有非 None 默认字段序列化,导致 OneBot V11 reply 段包含多余字段,引起私聊引用回复失败(message not found)
- 重写 Reply.toDict() 方法,仅返回 {"type": "reply", "data": {"id": str(self.id)}},符合协议标准
- 新增 tests/unit/test_aiocqhttp_reply.py,覆盖 Reply.toDict() 输出格式、_parse_onebot_json 路径及私聊发送场景的验证
@dosubot dosubot Bot added size:XS This PR changes 0-9 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend labels Jun 1, 2026
Copy link
Copy Markdown
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

This pull request introduces a toDict method to the Reply message component to ensure only the id field is serialized, conforming to the OneBot V11 reply format and resolving a message not found issue in private chats. It also adds a comprehensive suite of unit tests to verify this behavior. The reviewer feedback highlights that several test cases and their docstrings are misleadingly named and described as if they expect the buggy behavior (e.g., asserting that extra fields are included) when they actually assert the correct, fixed behavior (excluding extra fields). It is recommended to rename these tests and update their docstrings to accurately reflect the expected correct behavior.

Comment on lines +30 to +36
def test_reply_to_dict_contains_only_id_in_data():
"""Reply.toDict() 应当只输出 id 字段,不含 chain、sender_id 等多余字段。

当前实际行为:继承了 BaseMessageComponent.toDict(),会将所有
非 None 的默认值(chain: [], sender_id: 0, qq: 0, seq: 0 等)
一起序列化,违反了 OneBot V11 的 reply 段格式约定。
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

文档字符串中写着“当前实际行为”并描述了多余字段被序列化的 buggy 行为。由于此 PR 已经修复了该 bug,这不再是当前的实际行为。为了避免混淆未来的维护者,请将文档字符串更新为说明这是“此前行为”,或者直接删除该过时的描述。

Suggested change
def test_reply_to_dict_contains_only_id_in_data():
"""Reply.toDict() 应当只输出 id 字段不含 chainsender_id 等多余字段
当前实际行为继承了 BaseMessageComponent.toDict(),会将所有
None 的默认值chain: [], sender_id: 0, qq: 0, seq: 0
一起序列化违反了 OneBot V11 reply 段格式约定
"""
def test_reply_to_dict_contains_only_id_in_data():
"""Reply.toDict() 应当只输出 id 字段不含 chainsender_id 等多余字段
此前行为继承了 BaseMessageComponent.toDict(),会将所有
None 的默认值chain: [], sender_id: 0, qq: 0, seq: 0
一起序列化违反了 OneBot V11 reply 段格式约定
"""

Comment on lines +72 to +78
@pytest.mark.asyncio
async def test_parse_onebot_json_reply_produces_extra_fields():
"""_parse_onebot_json 处理 Reply 时会输出多余字段。

这验证了 bug 的链路:从 Reply 组件 → _parse_onebot_json →
OneBot 协议 payload,多余字段一直传递到 send_private_msg。
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

测试名称 test_parse_onebot_json_reply_produces_extra_fields 及其文档字符串声称它会“输出多余字段”,但测试中的断言实际上是在验证它不会输出多余字段(即修复后的正确行为)。请重命名该测试并更新文档字符串,以准确反映预期的正确行为。

@pytest.mark.asyncio
async def test_parse_onebot_json_reply_does_not_produce_extra_fields():
    """验证 _parse_onebot_json 处理 Reply 时不会输出多余字段。

    这确保了从 Reply 组件到 OneBot 协议 payload 的链路中,多余字段已被过滤。
    """

Comment on lines +103 to +110
@pytest.mark.asyncio
async def test_send_private_msg_with_reply_includes_extra_fields():
"""验证私聊发送带 Reply 的消息时,实际传给 bot.send_private_msg 的
payload 包含多余字段。

这是导致私聊下 'message not found' 的直接原因:
OneBot 协议端收到的 reply 段数据不符合标准格式。
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

测试名称 test_send_private_msg_with_reply_includes_extra_fields 及其文档字符串声称 payload “包含多余字段”,但测试实际上断言它不包含多余字段。请重命名该测试并更新文档字符串,以反映正确的行为。

@pytest.mark.asyncio
async def test_send_private_msg_with_reply_excludes_extra_fields():
    """验证私聊发送带 Reply 的消息时,实际传给 bot.send_private_msg 的
    payload 不包含多余字段。
    """

Comment on lines +144 to +151
@pytest.mark.asyncio
async def test_send_group_msg_with_reply_also_includes_extra_fields():
"""对比:群聊发送带 Reply 的消息同样包含多余字段。

如果群聊引用回复正常而私聊失败,可能的原因是不同协议端
对多余字段的容忍度不同(例如 napcat 在 send_group_msg 中
忽略了多余字段,但在 send_private_msg 中严格校验)。
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

测试名称 test_send_group_msg_with_reply_also_includes_extra_fields 及其文档字符串声称群聊消息 payload “同样包含多余字段”,但测试实际上断言它不包含多余字段。请重命名该测试并更新文档字符串,以反映正确的行为。

@pytest.mark.asyncio
async def test_send_group_msg_with_reply_also_excludes_extra_fields():
    """验证群聊发送带 Reply 的消息同样不包含多余字段。"""

Copy link
Copy Markdown
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 1 issue, and left some high level feedback:

  • In Reply.toDict, consider using the component’s declared type (e.g., self.type or ComponentType.Reply) instead of hardcoding the string 'reply' to keep behavior aligned if the enum or type name ever changes.
  • Several tests in test_aiocqhttp_reply.py (e.g. test_send_private_msg_with_reply_includes_extra_fields, test_send_group_msg_with_reply_also_includes_extra_fields) now assert that extra fields do not exist while their names/docstrings still describe the old buggy behavior; renaming or rewording them to describe the expected fixed behavior would make the intent clearer.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `Reply.toDict`, consider using the component’s declared `type` (e.g., `self.type` or `ComponentType.Reply`) instead of hardcoding the string `'reply'` to keep behavior aligned if the enum or type name ever changes.
- Several tests in `test_aiocqhttp_reply.py` (e.g. `test_send_private_msg_with_reply_includes_extra_fields`, `test_send_group_msg_with_reply_also_includes_extra_fields`) now assert that extra fields do *not* exist while their names/docstrings still describe the old buggy behavior; renaming or rewording them to describe the expected fixed behavior would make the intent clearer.

## Individual Comments

### Comment 1
<location path="tests/unit/test_aiocqhttp_reply.py" line_range="183" />
<code_context>
+# ============================================================
+
+
+def test_reply_to_dict_matches_onebot_v11_format():
+    """OneBot V11 标准 reply 段格式:
+    {"type": "reply", "data": {"id": "..."}}
</code_context>
<issue_to_address>
**suggestion (testing):** 建议增加一个用例覆盖非字符串 id(例如整数)自动转换为字符串的行为。

当前 `Reply.toDict()` 内部对 `self.id` 调用了 `str(self.id)`,但现有用例里 `id` 都是字符串,无法验证对非字符串 ID 的处理行为。建议补充类似:

```python
def test_reply_to_dict_casts_non_string_id_to_string():
    reply = Comp.Reply(id=123456)
    result = reply.toDict()
    assert result["data"]["id"] == "123456"
```

以锁定“自动转为字符串”的序列化约定,避免后续重构误删 `str()` 而导致与 OneBot V11 不兼容。
</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.

# ============================================================


def test_reply_to_dict_matches_onebot_v11_format():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): 建议增加一个用例覆盖非字符串 id(例如整数)自动转换为字符串的行为。

当前 Reply.toDict() 内部对 self.id 调用了 str(self.id),但现有用例里 id 都是字符串,无法验证对非字符串 ID 的处理行为。建议补充类似:

def test_reply_to_dict_casts_non_string_id_to_string():
    reply = Comp.Reply(id=123456)
    result = reply.toDict()
    assert result["data"]["id"] == "123456"

以锁定“自动转为字符串”的序列化约定,避免后续重构误删 str() 而导致与 OneBot V11 不兼容。

@NayukiChiba NayukiChiba marked this pull request as draft June 1, 2026 02:57
@NayukiChiba NayukiChiba marked this pull request as ready for review June 1, 2026 03:22
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 2, 2026
@RC-CHN RC-CHN merged commit 7d45a24 into AstrBotDevs:master Jun 2, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend lgtm This PR has been approved by a maintainer 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