Skip to content

fix: 引用解析拼接空值崩溃,异常不再发到群里 - #197

Open
w1ndys wants to merge 2 commits into
Zhalslar:mainfrom
w1ndys:fix/reply-join-none
Open

w1ndys wants to merge 2 commits into
Zhalslar:mainfrom
w1ndys:fix/reply-join-none

Conversation

@w1ndys

@w1ndys w1ndys commented Sep 21, 2026

Copy link
Copy Markdown

问题

开启 enable_reply_parse 后,引用合并转发、附件或空文本卡片时,on_message 会崩溃,堆栈被 AstrBot 发到群里。

引用链里的 Json 段会走 extract_json_url(),抽不到 URL 时返回 None。随后:

text = "".join(reply_texts)  # reply_texts 里有 None

触发:

TypeError: sequence item 0: expected str instance, NoneType found

异常从 on_message 冒泡出去,群里就会看到完整 traceback。入站合并消息本身本来就不在解析范围内,但也不该因此把机器人打崩。

修复

  1. 新增 join_nonempty_texts(),拼接时跳过 None 和空串
  2. 引用链抽不到文本时保留当前消息正文,不再用空串覆盖
  3. on_message 捕获异常只写日志,不再把堆栈发到群里
  4. 扫描 @ 时要求 Plain.text 存在,避免空文本段再踩一次

复现

  1. 开启引用解析
  2. 引用一条合并转发、无文本附件或空卡片
  3. @ 机器人

Fork

同一修复也在 fork 里:https://github.com/w1ndys/astrbot_plugin_parser(`v1.5.7-fix1`)。本 PR 不含 fork 的仓库地址 / 版本号改动。

Sourcery 总结

修复引用解析对空消息内容的处理,避免异常导致机器人崩溃或向群聊泄露堆栈。

错误修复:

  • 防止引用解析在消息片段缺少文本时因拼接空值而崩溃。
  • 在无法从引用内容中提取文本时保留当前消息正文,避免误清空待解析内容。
  • 将消息处理异常限制为日志记录,避免将完整堆栈发送到群聊。

改进:

  • 增强 @ 提及扫描对空文本消息片段的兼容性。

测试:

  • 为非空文本拼接逻辑增加空值、空字符串和顺序保持测试。
Original summary in English

Sourcery 摘要

增强回复解析功能,使其能够处理引用内容为空的情况,并防止处理失败时在群聊中暴露 traceback。

Bug 修复:

  • 防止在引用消息片段缺少文本或文本为空时,回复解析发生崩溃。
  • 当无法提取引用内容时,保留当前消息正文。
  • 将消息处理异常保留在日志中,而不是将 traceback 发送到群聊。

改进:

  • 使提及扫描能够容忍空的纯文本片段。

测试:

  • 增加测试覆盖:跳过 null 和空文本,同时保留拼接顺序。
Original summary in English

Summary by Sourcery

Harden reply parsing against empty referenced content and prevent processing failures from exposing tracebacks in group chats.

Bug Fixes:

  • Prevent reply parsing from crashing when referenced message segments contain missing or empty text.
  • Preserve the current message body when referenced content cannot be extracted.
  • Keep message-processing exceptions in logs instead of sending tracebacks to group chats.

Enhancements:

  • Make mention scanning tolerant of empty plain-text segments.

Tests:

  • Add coverage for skipping null and empty text while preserving concatenation order.

Copilot AI lite review requested due to automatic review settings September 21, 2026 03:46
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@sourcery-ai

sourcery-ai Bot commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

审查者指南

通过安全的非空文本拼接、引用正文回退和消息入口异常隔离,修复引用合并转发、附件及空卡片触发的崩溃与 traceback 外发问题,并补充 @ 扫描兼容性及对应单元测试。

安全消息解析与异常隔离时序图

sequenceDiagram
    participant Event as AstrMessageEvent
    participant Plugin as ParserPlugin
    participant Utils as core.utils
    participant Logger as Logger

    Event->>Plugin: on_message(event)
    Plugin->>Plugin: _handle_message(event)
    Plugin->>Plugin: scan Plain and At segments
    Plugin->>Utils: join_nonempty_texts(reply_texts)
    Utils-->>Plugin: reply_text
    alt reply_text exists
        Plugin->>Plugin: use reply_text
    else reply_text is empty
        Plugin->>Plugin: keep current message text
    end
    alt parsing raises Exception
        Plugin->>Logger: logger.exception(解析消息失败,已跳过)
        Logger-->>Plugin: traceback stays in logs
    else parsing succeeds
        Plugin-->>Event: continue processing without traceback message
    end
Loading

安全引用文本回退流程图

flowchart TD
    A[引用链包含消息段] --> B[收集 Plain.text 和 extract_json_url 结果]
    B --> C["join_nonempty_texts(reply_texts)"]
    C --> D{存在非空引用正文?}
    D -->|是| E[使用引用正文]
    D -->|否| F[保留当前消息正文]
    E --> G[继续解析]
    F --> G
    G --> H{发生异常?}
    H -->|是| I[记录日志并跳过消息]
    H -->|否| J[正常完成]
Loading

文件级变更

变更 详情 文件
防止引用文本拼接因空值崩溃,并在无法提取引用内容时保留当前消息正文。
  • 新增统一文本拼接辅助函数,跳过 None 和空串并保留其他值顺序。
  • 引用链中的 JSON 段抽取不到 URL 时不再触发 join 崩溃;仅在得到非空引用文本时覆盖当前正文。
  • 增加辅助函数对 None、空串及混合文本的单元测试。
core/utils.py
main.py
tests/test_reply_text.py
隔离消息处理异常,避免完整 traceback 外发到群聊。
  • 将 on_message 拆分为异常保护入口和实际处理流程。
  • 捕获处理异常并仅记录带堆栈的日志后跳过消息。
main.py
增强 @ 扫描对空文本消息段的兼容性。
  • 仅对存在文本内容的 Plain 段执行正则匹配。
main.py
补充变更日志,记录引用解析崩溃和异常外发修复。
  • 记录空值拼接、正文保留及异常不再发群的修复内容。
CHANGELOG.md

提示与命令

与 Sourcery 互动

  • 触发新的审查: 在拉取请求中评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 回复审查评论,请 Sourcery 根据该评论创建 issue。你也可以回复审查评论并使用 @sourcery-ai issue,从该评论创建 issue。
  • 生成拉取请求标题: 在拉取请求标题的任意位置写入 @sourcery-ai,即可随时生成标题。你也可以在拉取请求中评论 @sourcery-ai title,以随时(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文的任意位置写入 @sourcery-ai summary,即可在指定位置随时生成 PR 摘要。你也可以在拉取请求中评论 @sourcery-ai summary,以随时(重新)生成摘要。
  • 生成审查者指南: 在拉取请求中评论 @sourcery-ai guide,即可随时(重新)生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,此功能会很有用。
  • 忽略所有 Sourcery 审查: 在拉取请求中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 审查。如果你想从头开始新的审查,此功能尤其有用——别忘了评论 @sourcery-ai review 以触发新的审查!

自定义使用体验

访问你的控制面板以:

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

Original review guide in English

Reviewer's Guide

通过安全的非空文本拼接、引用正文回退和消息入口异常隔离,修复引用合并转发、附件及空卡片触发的崩溃与 traceback 外发问题,并补充 @ 扫描兼容性及对应单元测试。

Sequence diagram for safe message parsing and error isolation

sequenceDiagram
    participant Event as AstrMessageEvent
    participant Plugin as ParserPlugin
    participant Utils as core.utils
    participant Logger as Logger

    Event->>Plugin: on_message(event)
    Plugin->>Plugin: _handle_message(event)
    Plugin->>Plugin: scan Plain and At segments
    Plugin->>Utils: join_nonempty_texts(reply_texts)
    Utils-->>Plugin: reply_text
    alt reply_text exists
        Plugin->>Plugin: use reply_text
    else reply_text is empty
        Plugin->>Plugin: keep current message text
    end
    alt parsing raises Exception
        Plugin->>Logger: logger.exception(解析消息失败,已跳过)
        Logger-->>Plugin: traceback stays in logs
    else parsing succeeds
        Plugin-->>Event: continue processing without traceback message
    end
Loading

Flow diagram for safe quoted-text fallback

flowchart TD
    A[引用链包含消息段] --> B[收集 Plain.text 和 extract_json_url 结果]
    B --> C["join_nonempty_texts(reply_texts)"]
    C --> D{存在非空引用正文?}
    D -->|是| E[使用引用正文]
    D -->|否| F[保留当前消息正文]
    E --> G[继续解析]
    F --> G
    G --> H{发生异常?}
    H -->|是| I[记录日志并跳过消息]
    H -->|否| J[正常完成]
Loading

File-Level Changes

Change Details Files
防止引用文本拼接因空值崩溃,并在无法提取引用内容时保留当前消息正文。
  • 新增统一文本拼接辅助函数,跳过 None 和空串并保留其他值顺序。
  • 引用链中的 JSON 段抽取不到 URL 时不再触发 join 崩溃;仅在得到非空引用文本时覆盖当前正文。
  • 增加辅助函数对 None、空串及混合文本的单元测试。
core/utils.py
main.py
tests/test_reply_text.py
隔离消息处理异常,避免完整 traceback 外发到群聊。
  • 将 on_message 拆分为异常保护入口和实际处理流程。
  • 捕获处理异常并仅记录带堆栈的日志后跳过消息。
main.py
增强 @ 扫描对空文本消息段的兼容性。
  • 仅对存在文本内容的 Plain 段执行正则匹配。
main.py
补充变更日志,记录引用解析崩溃和异常外发修复。
  • 记录空值拼接、正文保留及异常不再发群的修复内容。
CHANGELOG.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

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

你好——我发现了 1 个问题

面向 AI 代理的提示
请处理本次代码审查中的评论:

## 单独评论

### 评论 1
<location path="core/utils.py" line_range="235-236" />
<code_context>
     return unquote(value.strip().replace("\\/", "/"))


+def join_nonempty_texts(parts: list[object]) -> str:
+    """拼接文本,跳过 None 和空串,避免 str.join 遇到空值崩溃。"""
+    texts: list[str] = []
+    for part in parts:
+        if not part:
+            continue
+        texts.append(str(part))
+    return "".join(texts)
+
</code_context>
<issue_to_address>
**小问题(潜在错误):** join_nonempty_texts 会丢弃所有假值,而不仅仅是 None 和空字符串;parts 中有效的 0、False 或其他类似文本的假值会被静默省略。

**触发条件:** 调用方在 parts 中传入假值的非字符串值时。

**建议修复:** 在使用 str(part) 转换其余值之前,显式检查 `part is None or part == ""````suggestion
        if part is None or part == "":
            continue
```
</issue_to_address>

Sourcery 对开源项目免费——如果您喜欢我们的审查结果,请考虑分享 ✨
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="core/utils.py" line_range="235-236" />
<code_context>
     return unquote(value.strip().replace("\\/", "/"))


+def join_nonempty_texts(parts: list[object]) -> str:
+    """拼接文本,跳过 None 和空串,避免 str.join 遇到空值崩溃。"""
+    texts: list[str] = []
+    for part in parts:
+        if not part:
+            continue
+        texts.append(str(part))
+    return "".join(texts)
+
</code_context>
<issue_to_address>
**nitpick (bug_risk):** join_nonempty_texts drops every falsey value, not only None and the empty string; a valid 0, False, or other falsey text-like value in parts is silently omitted.

**Triggers:** When a caller passes a falsey non-string value in parts.

**Suggested fix:** Check explicitly for `part is None or part == ""` before converting the remaining values with `str(part)`.

```suggestion
        if part is None or part == "":
            continue
```
</issue_to_address>

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

Comment thread core/utils.py Outdated

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

Reviewed changes have no unresolved blocking issues.

Review effort: Lite
Findings: None

What changed in this PR

修复引用消息包含空内容时的解析崩溃,并避免异常堆栈发送到群聊。

Changes:

  • 新增安全文本拼接,跳过空值。
  • 引用无可解析文本时保留当前正文。
  • 捕获消息处理异常并仅记录日志。
  • 增加相关单元测试。
File Description
tests/​test_reply_text.py 覆盖空值过滤与顺序保持
main.py 调整消息处理、引用解析和 @ 扫描逻辑
core/​utils.py 新增非空文本拼接工具
CHANGELOG.md 记录修复内容

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 21, 2026 03:48

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

已完成审查,未发现阻碍批准的未解决问题。

Review effort: Lite
Findings: None

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.

2 participants