Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- 新增引用解析开关
- 新增 allcpp 解析器
- 修复更新插件时字体占用导致无法更新的问题
- 修复引用解析时 `"".join` 遇到 `None` 崩溃(引用合并转发 / 附件 / 空文本卡片时)
- 引用链抽不到文本时保留当前消息正文,不再用空串覆盖
- `on_message` 异常只写日志,不再把堆栈发到群里

## v1.5.6

Expand Down
10 changes: 10 additions & 0 deletions core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 part is None or part == "":
continue
texts.append(str(part))
return "".join(texts)


def extract_json_url(data: dict | str) -> str | None:
"""处理 JSON 类型消息段,提取可交给解析器处理的 URL。"""
if isinstance(data, str):
Expand Down
18 changes: 13 additions & 5 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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

# 白名单
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions tests/test_reply_text.py
Original file line number Diff line number Diff line change
@@ -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"