From 7cac138b6fb0c2579a6546d4058518e8e11da1c3 Mon Sep 17 00:00:00 2001 From: w1ndys Date: Mon, 21 Sep 2026 11:46:03 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(=E5=BC=95=E7=94=A8):=20=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=E5=BC=95=E7=94=A8=E6=96=87=E6=9C=AC=E6=97=B6=E8=B7=B3?= =?UTF-8?q?=E8=BF=87=E7=A9=BA=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +++ core/utils.py | 10 ++++++++++ main.py | 18 +++++++++++++----- tests/test_reply_text.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 tests/test_reply_text.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d5e5e2..301482e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - 新增引用解析开关 - 新增 allcpp 解析器 - 修复更新插件时字体占用导致无法更新的问题 +- 修复引用解析时 `"".join` 遇到 `None` 崩溃(引用合并转发 / 附件 / 空文本卡片时) +- 引用链抽不到文本时保留当前消息正文,不再用空串覆盖 +- `on_message` 异常只写日志,不再把堆栈发到群里 ## v1.5.6 diff --git a/core/utils.py b/core/utils.py index 759e9ee..b45013d 100644 --- a/core/utils.py +++ b/core/utils.py @@ -228,6 +228,16 @@ def _clean_embedded_url(value: str) -> str: 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) + + def extract_json_url(data: dict | str) -> str | None: """处理 JSON 类型消息段,提取可交给解析器处理的 URL。""" if isinstance(data, str): diff --git a/main.py b/main.py index 06a49ca..0cdd7a8 100644 --- a/main.py +++ b/main.py @@ -22,7 +22,7 @@ from .core.parsers import BaseParser, BilibiliParser from .core.render import Renderer from .core.sender import MessageSender -from .core.utils import extract_json_url +from .core.utils import extract_json_url, join_nonempty_texts class ParserPlugin(Star): @@ -113,7 +113,14 @@ def _get_parser_by_type(self, parser_type): @filter.event_message_type(filter.EventMessageType.ALL) async def on_message(self, event: AstrMessageEvent): - """消息的统一入口""" + """消息的统一入口。异常只写日志,避免堆栈发到群里。""" + try: + await self._handle_message(event) + except Exception: + logger.exception("解析消息失败,已跳过") + + async def _handle_message(self, event: AstrMessageEvent): + """实际解析流程。""" umo = event.unified_msg_origin # 白名单 @@ -138,7 +145,7 @@ async def on_message(self, event: AstrMessageEvent): for seg in chain: if isinstance(seg, At): mentioned_ids.add(str(seg.qq)) - elif isinstance(seg, Plain): + elif isinstance(seg, Plain) and seg.text: mentioned_ids.update(re.findall(r"<@!?([^>\s]+)>", seg.text)) if ( self.cfg.require_at_in_group @@ -169,8 +176,9 @@ async def on_message(self, event: AstrMessageEvent): reply_texts.append(seg.text) elif isinstance(seg, Json): reply_texts.append(extract_json_url(seg.data)) - if reply_texts: - text = "".join(reply_texts) + reply_text = join_nonempty_texts(reply_texts) + if reply_text: + text = reply_text if not text: return diff --git a/tests/test_reply_text.py b/tests/test_reply_text.py new file mode 100644 index 0000000..f7ceb8d --- /dev/null +++ b/tests/test_reply_text.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import importlib +import sys +import types +from types import SimpleNamespace + +import pytest + + +@pytest.fixture +def utils_module(monkeypatch: pytest.MonkeyPatch): + logger = SimpleNamespace(info=lambda *a, **k: None, warning=lambda *a, **k: None) + astrbot_pkg = types.ModuleType("astrbot") + astrbot_pkg.__path__ = [] + api_module = types.ModuleType("astrbot.api") + api_module.logger = logger + monkeypatch.setitem(sys.modules, "astrbot", astrbot_pkg) + monkeypatch.setitem(sys.modules, "astrbot.api", api_module) + monkeypatch.delitem(sys.modules, "core.utils", raising=False) + return importlib.import_module("core.utils") + + +def test_join_skips_none(utils_module): + assert utils_module.join_nonempty_texts([None]) == "" + assert utils_module.join_nonempty_texts([None, "https://b23.tv/x"]) == ( + "https://b23.tv/x" + ) + + +def test_join_skips_empty_and_keeps_order(utils_module): + assert utils_module.join_nonempty_texts(["a", None, "", "b"]) == "ab" From 88f6c19bfd0d3666e182ba555b9b4c5dabbee4dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=B7=E5=8D=B7?= Date: Mon, 21 Sep 2026 11:48:33 +0800 Subject: [PATCH 2/2] Update core/utils.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- core/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/utils.py b/core/utils.py index b45013d..656a413 100644 --- a/core/utils.py +++ b/core/utils.py @@ -232,7 +232,7 @@ def join_nonempty_texts(parts: list[object]) -> str: """拼接文本,跳过 None 和空串,避免 str.join 遇到空值崩溃。""" texts: list[str] = [] for part in parts: - if not part: + if part is None or part == "": continue texts.append(str(part)) return "".join(texts)