Skip to content

feat(qqofficial): inline public images in markdown messages (#10019) - #10031

Open
nina-ysml wants to merge 2 commits into
AstrBotDevs:masterfrom
nina-ysml:feat/10019-qqofficial-markdown-images
Open

nina-ysml wants to merge 2 commits into
AstrBotDevs:masterfrom
nina-ysml:feat/10019-qqofficial-markdown-images

Conversation

@nina-ysml

@nina-ysml nina-ysml commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Problem

QQ renders rich-media messages (msg_type=7) with the image above the text, so a [Plain, Image] MessageChain arrives visually reversed (#10019). Markdown keeps the original component order, but the adapter dropped markdown whenever the chain carried an image and fell back to msg_type=7.

Fix

When a chain carries only publicly hosted images (plus text), build the markdown body with the image inlined and keep msg_type=2:

  • _build_markdown_with_public_images() renders text + ![image](url) in the original component order.
  • Callers detect this case before parsing media, so the image is neither downloaded to base64 nor uploaded as rich media — the QQ platform downloads and re-hosts the URL itself.
  • Components markdown cannot express (At / Record / Video / File), and chains with local or base64 images, keep the existing rich-media path, because markdown images must reference a publicly reachable URL.
  • When the platform rejects markdown, the fallback message uses the text-only content instead of the markdown source.

Both send paths are covered: _send_by_session_common (session / proactive send) and _post_send_one (reply).

Scope note: ARK cards

The issue also asks to evaluate encapsulating ARK structured cards as a MergeMessage substitute. Per the official docs (消息类型 scene support table), structured cards are receive-only for bots in both 单聊 and 群聊 (发 ❌ / 收 ✅), so ARK cannot replace MergeMessage here. That part is therefore not implemented in this PR.

Tests

  • test_ws_group_markdown_with_public_image_embeds_image — session send path
  • test_group_reply_markdown_with_public_image_embeds_image — reply path

Both assert msg_type == 2, no media field, the image URL present in markdown.content, and the text ordered before the image. Both fail before this change and pass after it. The other qqofficial tests are unaffected.

Refs #10019

Summary by Sourcery

Preserve QQ message ordering by embedding publicly hosted images in markdown messages instead of falling back to rich media.

New Features:

  • Inline publicly hosted images in QQ Official markdown messages while preserving their original text-and-image order.
  • Support multiple public images in a single markdown message without splitting the message chain.

Bug Fixes:

  • Prevent QQ rich-media rendering from reversing text-before-image message chains.
  • Use text-only content when markdown delivery is rejected instead of sending the markdown source as fallback.

Enhancements:

  • Retain the existing rich-media handling for local, base64, or otherwise unsupported media and components.
  • Apply public-image markdown handling to both session/proactive sends and replies.

Tests:

  • Add coverage for public-image markdown in session sends and group replies.
  • Add coverage confirming multiple public images remain in one message and preserve component order.

…evs#10019)

QQ renders rich-media messages (msg_type=7) with the image above the text, so a text-then-image MessageChain arrives visually reversed. Markdown keeps the original component order, but the adapter dropped markdown whenever the chain carried an image and fell back to msg_type=7.

When the chain carries only publicly hosted images plus text, build the markdown body with the image inlined and keep msg_type=2, skipping both the image download and the rich-media upload. Chains with local or base64 images keep using the rich-media path, because markdown images must reference a publicly reachable URL that the platform fetches itself.

Both send paths are updated: the session send path and the reply path.

@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="375-377" />
<code_context>
+        markdown_with_images = (
+            None
+            if use_md is False or stream is not None
+            else QQOfficialMessageEvent._build_markdown_with_public_images(
+                message_to_send
+            )
+        )
+
</code_context>
<issue_to_address>
**issue (broader_impact):** Multiple public images are split into separate MessageChains before `_build_markdown_with_public_images()` runs, because `_split_message_chain_by_media()` treats every second image as a new message. A chain such as `[Plain, Image1, Plain, Image2]` is therefore sent as multiple QQ messages instead of one markdown body preserving the original component order.

**Triggers:** When a message chain contains two or more image components.

**Suggested fix:** Detect an all-public-image chain before media splitting, or change the splitter to keep public images together for markdown rendering.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a public image URL is now passed directly to QQ in markdown, changing who fetches the resource and allowing malformed or unexpected URLs to affect the delivered message. Reverting stops future occurrences, but messages already sent and any external fetches or URL exposure cannot be undone.

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


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

… intact

Testing the markdown path against a real QQ bot surfaced two problems.

1. The generated markdown concatenated text and image with no line break, so QQ rendered the image inline and it overlapped the surrounding text. Each image now sits on its own block.

2. A chain carrying several public images was still split by media, producing one message per image. Markdown can carry several images in a single message, so markdown chains are no longer split.

Tests assert the block separation and cover the multi-image case.
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.

1 participant