Skip to content

fix(qqofficial): render markdown for proactive send_by_session messages - #9914

Open
OMSociety wants to merge 3 commits into
AstrBotDevs:masterfrom
OMSociety:fix/qqofficial-proactive-markdown
Open

fix(qqofficial): render markdown for proactive send_by_session messages#9914
OMSociety wants to merge 3 commits into
AstrBotDevs:masterfrom
OMSociety:fix/qqofficial-proactive-markdown

Conversation

@OMSociety

@OMSociety OMSociety commented Sep 1, 2026

Copy link
Copy Markdown

Closes #7848
使通过 send_by_session() 发送的主动/定时消息,在 QQ 官方平台上能够正确渲染为 Markdown。

Modifications / 改动点

  • QQOfficialPlatformAdapter._send_by_session_common() 与正常回复路径(QQOfficialMessageEvent._post_send_one())对齐,使主动推送消息(定时任务、send_message_to_user 工具等)改用原生 Markdown payload(markdown + msg_type: 2)发送,而不是原来的纯 {"content": ...}
  • 尊重 MessageChain.use_markdown_:当它显式为 False 时,仍用纯文本 content 发送,保留关闭 Markdown 的用户的原有行为。
  • 媒体消息(msg_type: 7)会移除 markdown 字段并回退到 content,与 _post_send_one() 一致。
  • 频道(guild)文本频道发送移除 QQ v2 的 msg_type 字段(频道 API 不接受该字段)。
  • 新增 MarkdownPayload 导入。

动机:通过 send_by_session() 发送的主动/定时消息,在 QQ 官方平台上一直以纯文本渲染;而普通对话回复却能正确渲染 Markdown(见 #7848)。

  • Aligned QQOfficialPlatformAdapter._send_by_session_common() with the normal reply path (QQOfficialMessageEvent._post_send_one()), so proactive messages (cron jobs, the send_message_to_user tool, etc.) are now sent with the native markdown payload (markdown + msg_type: 2) instead of plain {"content": ...}.
  • Respects MessageChain.use_markdown_: when it is explicitly False, the message is still sent as plain content, preserving the previous behavior for users who disabled markdown.
  • Media messages (msg_type: 7) drop the markdown field and fall back to content, matching _post_send_one().
  • Guild text-channel sends strip the QQ v2 msg_type field (the guild API does not accept it).
  • Added the MarkdownPayload import.

Motivation: proactive/scheduled messages sent via send_by_session() were always rendered as plain text on QQ Official, while normal chat replies rendered markdown correctly (see #7848).

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

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

改动前测试对话:
image
改动后测试对话:
image
日志:
image


Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。这是 [Bug] QQ Official: cron/scheduled messages sent via send_by_session lack markdown rendering (msg_type=2) #7848 讨论过的 bug 修复。

  • 👀 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 QQ Official session-based messages so they render Markdown correctly while preserving compatible plain-text and media sending behavior.

Bug Fixes:

  • Render proactive and scheduled QQ Official messages sent via sessions as native Markdown when enabled.
  • Preserve plain-text sending when Markdown is explicitly disabled and when sending media messages.
  • Retry Markdown sends as plain content when the QQ API rejects native Markdown.

Enhancements:

  • Align session-based sending behavior with the standard QQ Official reply path across groups, friends, and guild channels.

Tests:

  • Update and expand QQ Official session-send tests for Markdown payloads, plain-text opt-out, media handling, guild payloads, and Markdown fallback behavior.

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 1 issue

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="400-404" />
<code_context>
             return

-        payload: dict[str, Any] = {"content": plain_text}
+        use_md = getattr(message_chain, "use_markdown_", None)
+        if use_md is False:
+            payload: dict[str, Any] = {"content": plain_text}
+        else:
+            payload = {
+                "markdown": MarkdownPayload(content=plain_text) if plain_text else None,
+                "msg_type": 2,
</code_context>
<issue_to_address>
**issue (bug_risk):** When a `MessageChain` explicitly sets `use_markdown_=False` and contains text separated by media, `_split_message_chain_by_media()` creates new `MessageChain` objects without copying `use_markdown_`. The recursive calls therefore see `use_md` as `None` and send the text-only chunks as Markdown instead of plain `content`.

**Triggers:** When `send_by_session()` receives a mixed text/media message with Markdown explicitly disabled.

**Suggested fix:** Preserve `use_markdown_` when constructing split message chains, or derive each split chain from the original chain so its metadata is retained.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and the change alters proactive outbound message payloads, replacing plain content with Markdown and msg_type=2 for text sends while changing media payload handling. If the API payload or Markdown interpretation is wrong, messages may fail or be delivered incorrectly; reverting prevents future occurrences but cannot undo messages already sent outside the team.

Blocking findings: astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py:404


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.

@OMSociety

Copy link
Copy Markdown
Author

@sourcery-ai 已按要求修复:split_message_chain_by_media() 改用 message.derive() 保留 use_markdown 等全部元数据,并新增回归测试 test_split_message_chain_by_media_preserves_use_markdown。验证:tests/test_qqofficial_*.py → 44 passed;ruff format / ruff check 通过。请复审。

@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Sure! I'm generating a new review now.

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've found 1 issue

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="404-406" />
<code_context>
+        if use_md is False:
+            payload: dict[str, Any] = {"content": plain_text}
+        else:
+            payload = {
+                "markdown": MarkdownPayload(content=plain_text) if plain_text else None,
+                "msg_type": 2,
+            }
         if msg_id and not allow_group_proactive_send:
</code_context>
<issue_to_address>
**issue (bug_risk):** The new default Markdown payload is sent directly through `post_group_message` and the C2C posting path without `_send_with_markdown_fallback`. When QQ rejects a Markdown payload because Markdown is unavailable or the payload is invalid for that destination, the exception propagates and the proactive message is not retried as plain `content`, whereas the normal reply path performs that fallback.

**Triggers:** When a proactive group or friend message receives a QQ API error for its Markdown payload.

**Suggested fix:** Route Markdown sends through the same `_send_with_markdown_fallback` helper used by `_post_send_one()`, including a retry with plain `content`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if the Markdown payload or message-type selection is wrong, proactive messages can be delivered with incorrect content or formatting to users, and those already-sent messages cannot be undone by reverting the change. The behavior is otherwise bounded and future sends return to the previous implementation after a rollback.

Blocking findings: astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py:406


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.

@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Hey @OMSociety, I've posted a new review for you!

@sourcery-ai sourcery-ai Bot left a comment

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.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Needs a human reviewer. The change makes proactive messages use QQ markdown payloads by default, so an incorrect API assumption or fallback could deliver malformed or unexpectedly formatted messages to users. Reverting stops future sends but cannot undo messages already delivered.


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.

@OMSociety

Copy link
Copy Markdown
Author

补充说明:此 PR 取代了早期的 #7849#7883#7895(三者均仍未合并、且基于已过期的基线)。它们的思路相同,但都未尊重 MessageChain.use_markdown_(会强制发送 Markdown),也不带 markdown 落回。本 PR 已补齐这两点。建议合并本 PR 后关闭那三个。

buyun14 pushed a commit to buyun14/AstrBot that referenced this pull request Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] QQ Official: cron/scheduled messages sent via send_by_session lack markdown rendering (msg_type=2)

1 participant