Skip to content

fix(aiocqhttp): route send_by_session by session self_id - #10136

Open
15515151 wants to merge 2 commits into
AstrBotDevs:masterfrom
15515151:master
Open

15515151 wants to merge 2 commits into
AstrBotDevs:masterfrom
15515151:master

Conversation

@15515151

@15515151 15515151 commented Sep 19, 2026

Copy link
Copy Markdown

同一个 aiocqhttp 适配器下挂多个反向 WS 客户端(多账号/多连接)时,
context.send_message / send_by_session 不携带 self_id,aiocqhttp 的 WebSocketReverseApi 无法选中连接,抛出 ApiNotAvailable,消息(如表情包) 静默发送失败,且 OneBot 侧(napcat等)收不到任何请求、无日志。

  • 适配器记录 session_id -> 该会话最近一次事件的 self_id
  • send_by_session 据此显式传入 self_id
  • send_message/_dispatch_send 支持显式 self_id(有 event 时仍以 event 为准), 合并转发与文件消息同样携带

验证:py_compile 通过;单元级用例确认显式 self_id 生效、event self_id 优先、无 self_id 时保持旧行为。

flowchart TB
    %% ============ 场景:1 个适配器,N 个 OneBot 连接 ============
    subgraph AstrBot["AstrBot"]
        ADAPTER["同一个 aiocqhttp 适配器<br/>只监听一个反向 WS 端口<br/>(如 6199)"]
    end

    subgraph OneBots["多个 OneBot 连接(N 条反向 WS 都连到这同一个适配器)"]
        OB1["OneBot 连接 #1<br/>self_id = 111"]
        OB2["OneBot 连接 #2<br/>self_id = 222"]
        OB3["OneBot 连接 #3<br/>self_id = 333"]
    end

    OB1 -- 反向 WS --> ADAPTER
    OB2 -- 反向 WS --> ADAPTER
    OB3 -- 反向 WS --> ADAPTER

    %% ============ 发送:修复前 / 修复后 ============
    PUSH["插件主动发送<br/>context.send_message()<br/>没有 event → 没有 self_id"]

    PUSH -- "修复前:适配器分不清该走哪条连接<br/>(连接数 > 1,没有 self_id 可依据)" --> ERR["❌ ApiNotAvailable<br/>请求根本没发出去<br/>OneBot 侧无任何日志"]

    PUSH -. "修复后:按会话查出 self_id<br/>session → self_id = 222" .-> SID["带上 self_id = 222"]
    SID -. "命中第 #2 条连接" .-> OB2
Loading

Modifications / 改动点

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

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

修改前的报错

[2026-09-19 13:45:30.357] [Plug]
[ERRO]
[v4.28.0] [meme_manager.main:1438]: 发送表情图片失败: 
[2026-09-19 13:45:30.371] [Plug]
[ERRO]
[v4.28.0] [meme_manager.main:1439]: Traceback (most recent call last):
  File "/root/AstrBot/data/plugins/meme_manager/main.py", line 1434, in after_message_sent
    await self.context.send_message(
  File "/root/AstrBot/astrbot/core/star/context.py", line 643, in send_message
    await platform.send_by_session(session, message_chain)
  File "/root/AstrBot/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py", line 119, in send_by_session
    await AiocqhttpMessageEvent.send_message(
  File "/root/AstrBot/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py", line 153, in send_message
    await cls._dispatch_send(bot, event, is_group, session_id, ret)
  File "/root/AstrBot/astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py", line 108, in _dispatch_send
    await bot.send_group_msg(
  File "/root/AstrBot/.venv/lib/python3.12/site-packages/aiocqhttp/__init__.py", line 236, in call_action
    return await self._api.call_action(action=action, **params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/root/AstrBot/.venv/lib/python3.12/site-packages/aiocqhttp/api_impl.py", line 191, in call_action
    raise ApiNotAvailable
aiocqhttp.exceptions.ApiNotAvailable

修改后的结果

[2026-09-19 14:02:26.597] [Core]
[INFO]
[core.event_bus:74]: [default] [xc(aiocqhttp)] Autumn_Wing/***: [At:***] 没问题,我再观察观察
[2026-09-19 14:02:27.188] [astrbot_plugin_LLMPerception]
[INFO]
[astrbot_plugin_llmperception.main:430]: 已添加感知信息: 发送时间: 2026-09-19 14:02:27 | 周六, 周末(法定节假日), 下午 | 农历丙午年(马年)八月初九 | 当前节气: 白露 | 平台: QQ, 群聊, 群名: 香菜
[2026-09-19 14:02:27.213] [Core]
[INFO]
[context.manager:105]: Compress completed. 165631 -> 100522 tokens, compression rate: 78.53%.
[2026-09-19 14:02:38.226] [Plug]
[INFO]
[meme_manager.main:1070]: [meme_manager] 去重后的最终表情列表: ['see']
[2026-09-19 14:02:38.233] [Core]
[INFO]
[respond.stage:212]: Prepare to send - Autumn_Wing/***: 行,那我先慢悠悠溜达出门去巷口买生煎包了,你盯着后台log慢慢看,记得把外卖点上别真硬扛着饿肚子
[2026-09-19 14:02:42.018] [Core]
[INFO]
[core.event_bus:74]: [default] [半夏(aiocqhttp)] 麦/***: 行,那我先慢悠悠溜达出门去巷口买生煎包了,你盯着后台log慢慢看,记得把外卖点上别真硬扛着饿肚子
[2026-09-19 14:02:43.118] [Core]
[INFO]
[core.event_bus:74]: [default] [半夏(aiocqhttp)] 麦/***: [图片]

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 checked the affected WebUI instructions and screenshots in docs/zh and docs/en against the changed navigation, page structure, and labels, and updated them in this PR (or explained why no documentation update is needed). For renamed, moved, or merged entry points, I included an old entry → new entry mapping in the documentation and changelog.
    / 我已对照变化后的 WebUI 入口、页面结构和术语,核对并在本 PR 中更新 docs/zhdocs/en 的相关操作说明与截图(或说明无需更新文档的原因)。入口改名、移动或合并时,已在文档和 changelog 中补充 旧入口 → 新入口 对照。

  • 🤓 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

按会话关联的 bot self_id 路由 aiocqhttp 主动发送请求,确保多连接场景下消息发送到正确的 OneBot 客户端。

Bug Fixes:

  • 修复同一 aiocqhttp 适配器连接多个 OneBot 账号时主动发送消息无法正确路由并静默失败的问题。

Enhancements:

  • 为会话记录最近使用的 bot self_id,并在无事件上下文的发送、文件消息和合并转发中支持显式路由,同时保持事件 self_id 优先及无 self_id 时的既有行为。

Tests:

  • 验证显式 self_id 路由、事件 self_id 优先级以及缺少 self_id 时的兼容行为。

同一个 aiocqhttp 适配器下挂多个反向 WS 客户端(多账号/多连接)时,
context.send_message / send_by_session 不携带 self_id,aiocqhttp 的
WebSocketReverseApi 无法选中连接,抛出 ApiNotAvailable,消息(如表情包)
静默发送失败,且 OneBot 侧(ws-plugin)收不到任何请求、无日志。

- 适配器记录 session_id -> 该会话最近一次事件的 self_id
- send_by_session 据此显式传入 self_id
- send_message/_dispatch_send 支持显式 self_id(有 event 时仍以 event 为准),
  合并转发与文件消息同样携带

验证:py_compile 通过;单元级用例确认显式 self_id 生效、event self_id
优先、无 self_id 时保持旧行为。

@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/aiocqhttp/aiocqhttp_platform_adapter.py" line_range="150-151" />
<code_context>
         elif event["post_type"] == "request":
             abm = await self._convert_handle_request_event(event)

+        if abm is not None and getattr(abm, "self_id", None):
+            self._session_self_id[abm.session_id] = str(abm.self_id)
+
         return abm
</code_context>
<issue_to_address>
**issue (broader_impact):** The cache key contains only the numeric session ID, so a group and a private conversation with the same numeric ID overwrite each other’s self_id. Subsequent `send_by_session` calls route through whichever event was processed most recently, sending messages to the wrong OneBot connection.

**Triggers:** When a group ID and a private user ID are equal and both conversations are active on different bot connections.

**Suggested fix:** Include the message type in the cache key and perform the lookup with both `session.message_type` and `session.session_id`.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if the cached session-to-self_id mapping is stale or incorrect, an active message could be sent through the wrong bot connection or to the wrong account. Reverting prevents future misrouting, but messages already sent cannot be recalled by the revert.

Blocking findings: astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py:151


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

Comment thread astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py Outdated
群号与私聊 QQ 号数值相同时,仅用 session_id 作为 key 会互相覆盖,
导致 send_by_session 把主动发送路由到错误的 self_id。改为
(message_type, session_id) 作为 key,查找时同样带上 message_type。
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