Skip to content

fix(qqofficial): keep markdown-rejected sends as passive replies - #10451

Open
he-yufeng wants to merge 1 commit into
AstrBotDevs:masterfrom
he-yufeng:fix/qqofficial-markdown-rejected-passive-reply
Open

he-yufeng wants to merge 1 commit into
AstrBotDevs:masterfrom
he-yufeng:fix/qqofficial-markdown-rejected-passive-reply

Conversation

@he-yufeng

@he-yufeng he-yufeng commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10420.

When the QQ official API refuses a native markdown payload, the current fallback in _send_with_markdown_fallback drops msg_id and resends through the active-message interface. Active messages are quota limited (304049), so a rejected markdown turns an ordinary reply into a rate-limit error and the message is lost.

This branch is stacked on #10401 because both PRs touch _send_with_markdown_fallback; only the tip commit is new.

Modifications / 改动点

qqofficial_message_event.py: reorder the fallback so a rejected markdown degrades to a plain content reply that keeps msg_id, staying a passive reply (msg_type reset from 2 to 0, stream trailing newline restored before the retry). The stream newline repair retry also keeps msg_id. Only errors that are not markdown rejections go to the active-send fallback, which keeps the message_reference two-stage retry from #10401. Fallback raises are chained so the original rejection stays visible in the logs.

test_qqofficial_group_message_create.py: three new tests covering the markdown-rejected passive path, the non-markdown active fallback, and the stream newline retry.

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

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

pytest tests/test_qqofficial_group_message_create.py gives 50 passed. Red/green verified: with the source change stashed, the two behavior tests fail while the control test passes; with the change restored, all 50 pass. ruff check and ruff format --check are clean on ruff 0.15.22, the same pin CI uses.

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc. (no new feature; the fix direction is tracked in [Bug] QQ频道消息无法回复:markdown 回退顺序有误,误用主动消息导致 304049 限频 #10420)
  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
  • 📚 No documentation update needed: this is an internal send path with no WebUI, navigation, or doc surface change.
  • 🤓 I have ensured that no new dependencies are introduced.
  • 😮 My changes do not introduce malicious code.

Summary by Sourcery

Keep QQ Official Markdown fallback replies passive and reliably deliver messages when Markdown payloads are rejected.

Bug Fixes:

  • Preserve passive reply behavior when QQ Official rejects native Markdown by retrying with plain content while retaining the message ID.
  • Keep stream newline correction retries passive and handle Markdown rejection during active fallback without losing the message.

Tests:

  • Add regression coverage for passive Markdown degradation, active fallback behavior, stream newline retries, and Markdown rejection during active retry.

@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_message_event.py" line_range="698-711" />
<code_context>
-                    ret = await send_func(fallback_payload)
-                    logger.info("[QQOfficial] 使用主动发送接口发送成功。")
-                    return ret
-                except _QQOFFICIAL_SEND_API_ERRORS as fallback_err:
-                    err = fallback_err
-                    payload = fallback_payload
-
-            if not isinstance(err, botpy.errors.ServerError):
-                raise
</code_context>
<issue_to_address>
**Markdown fallback is skipped**

When the passive send fails for a non-Markdown reason and the active retry is rejected for Markdown, `_send_with_markdown_fallback` propagates the active retry's Markdown error before attempting the plain-content downgrade, so the message is lost.

Route Markdown rejection from the active retry through the plain-content fallback.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if the fallback classification or reply-reference handling is wrong, the code can send an unintended proactive or duplicate message, or retry with an invalid reference; those messages are externally delivered and cannot be undone by reverting the change. The impact is bounded to affected sends, but correcting the code will not retract messages already sent.

Blocking findings: astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py:711


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

@he-yufeng

Copy link
Copy Markdown
Contributor Author

The sourcery finding was real. Traced it: a passive send failing for a non-markdown reason (expired msg_id) drops into the active retry with the markdown payload still intact, and when that retry is rejected for markdown the error propagated before any plain-content downgrade, so the message was lost.

Fixed in 0d456f6: the active retry's markdown rejection now routes through the same plain-content downgrade (markdown popped, msg_type 2 to 0, stream newline fix kept). The reference retry order is unchanged, so a valid message_reference still survives. Added a regression test that pins the three-call sequence (passive fail, active markdown rejection, downgraded send) and verified it goes red without the fix.

When the QQ official API refuses a native markdown payload, the
fallback used to drop msg_id and resend through the active-message
interface, which runs into the 304049 active-message rate limit and
loses the reply threading. Reorder the fallback so a rejected markdown
degrades to a plain content reply that keeps msg_id (passive reply,
msg_type reset from 2 to 0, stream trailing newline restored). The
stream newline repair retry also keeps msg_id. Only errors that are
not markdown rejections take the active-send fallback, and if the
active retry itself hits the markdown rejection (the passive failure
was unrelated, e.g. an expired msg_id) it downgrades to plain content
too instead of losing the message. Fallback raises are chained so the
original rejection stays visible in logs.
@he-yufeng
he-yufeng force-pushed the fix/qqofficial-markdown-rejected-passive-reply branch from 0d456f6 to c0321a2 Compare October 12, 2026 21:25
@he-yufeng

Copy link
Copy Markdown
Contributor Author

重建说明:这个 PR 原本叠在 #10401 的两个提交上。#10489 已把出站 message_reference 的填充合进 master(595f990b6),走的是 msg_idx 语义,比 #10401 的裸 id 透传更正确,所以叠放部分整个摘掉,改为在 master 上重放本修复,diff 现在只剩 fallback 重排和 4 个测试。此前那个 follow-up(主动重试也撞 markdown 拒绝时同样降级纯 content)也一并带上了。

验证:tests/test_qqofficial_group_message_create.py 41/41 过;拿 master 版 helper 跑 markdown-rejected 和 stream-newline 两个用例定败(负对照),本分支定过。

@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.

Sourcery assessment

Approved.

This branch has not been deployed

No deployments
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频道消息无法回复:markdown 回退顺序有误,误用主动消息导致 304049 限频

1 participant