diff --git a/astrbot/builtin_stars/astrbot/main.py b/astrbot/builtin_stars/astrbot/main.py index cc2c368a23..ee3ae13a48 100644 --- a/astrbot/builtin_stars/astrbot/main.py +++ b/astrbot/builtin_stars/astrbot/main.py @@ -58,6 +58,13 @@ async def handle_empty_mention(self, event: AstrMessageEvent): cfg = self.context.get_config(umo=event.unified_msg_origin) p_settings = cfg["platform_settings"] wake_prefix = cfg.get("wake_prefix", []) + provider_wake_prefix = cfg.get("provider_settings", {}).get( + "wake_prefix", + "", + ) + for bot_wake_prefix in wake_prefix: + if provider_wake_prefix.startswith(bot_wake_prefix): + provider_wake_prefix = provider_wake_prefix[len(bot_wake_prefix) :] if len(messages) != 1: return @@ -74,7 +81,10 @@ async def handle_empty_mention(self, event: AstrMessageEvent): if not (is_empty_mention or is_wake_prefix_only): return - if p_settings.get("empty_mention_waiting_need_reply", True): + if ( + p_settings.get("empty_mention_waiting_need_reply", True) + and not provider_wake_prefix + ): try: curr_cid = await self.context.conversation_manager.get_curr_conversation_id( event.unified_msg_origin, diff --git a/tests/unit/test_empty_mention_wake_prefix.py b/tests/unit/test_empty_mention_wake_prefix.py new file mode 100644 index 0000000000..99c537bed0 --- /dev/null +++ b/tests/unit/test_empty_mention_wake_prefix.py @@ -0,0 +1,79 @@ +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock + +import pytest + +import astrbot.builtin_stars.astrbot.main as main_module +from astrbot.api.message_components import At, Plain + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + ("provider_wake_prefix", "messages", "should_request_llm"), + [ + ("chat", [At(qq="bot")], False), + ("/chat", [At(qq="bot")], False), + ("chat", [Plain(text="/")], False), + ("", [At(qq="bot")], True), + ("/", [At(qq="bot")], True), + ], +) +async def test_empty_mention_reply_respects_provider_wake_prefix( + monkeypatch, + provider_wake_prefix, + messages, + should_request_llm, +): + """Verify that empty-mention replies respect the additional LLM wake prefix. + + Args: + monkeypatch: Pytest fixture used to replace the 60-second session waiter. + provider_wake_prefix: Additional LLM wake prefix configured for the provider. + messages: Message components passed to the empty-mention handler. + should_request_llm: Whether an immediate LLM request should be produced. + """ + + def skip_waiting(_timeout): + def decorator(_callback): + async def wait(*_args, **_kwargs): + raise TimeoutError + + return wait + + return decorator + + monkeypatch.setattr(main_module, "session_waiter", skip_waiting) + + conversation_manager = SimpleNamespace( + get_curr_conversation_id=AsyncMock(return_value="conversation-id"), + get_conversation=AsyncMock(return_value=None), + ) + main = main_module.Main.__new__(main_module.Main) + main.context = MagicMock() + main.context.conversation_manager = conversation_manager + main.context.get_config.return_value = { + "wake_prefix": ["/"], + "provider_settings": {"wake_prefix": provider_wake_prefix}, + "platform_settings": { + "empty_mention_waiting": True, + "empty_mention_waiting_need_reply": True, + }, + } + + event = MagicMock() + event.unified_msg_origin = "aiocqhttp:GroupMessage:group" + event.get_messages.return_value = messages + event.get_self_id.return_value = "bot" + event.get_platform_id.return_value = "aiocqhttp" + llm_request = object() + event.request_llm.return_value = llm_request + + results = [item async for item in main.handle_empty_mention(event)] + + assert results == ([llm_request] if should_request_llm else []) + if should_request_llm: + event.request_llm.assert_called_once() + conversation_manager.get_curr_conversation_id.assert_awaited_once() + else: + event.request_llm.assert_not_called() + conversation_manager.get_curr_conversation_id.assert_not_awaited()