diff --git a/pytests/utils_test/test_bot_identity_utils.py b/pytests/utils_test/test_bot_identity_utils.py index 55502a89..2ba8f35a 100644 --- a/pytests/utils_test/test_bot_identity_utils.py +++ b/pytests/utils_test/test_bot_identity_utils.py @@ -157,6 +157,14 @@ def test_unknown_platform_no_longer_falls_back_to_qq(monkeypatch): assert "unknown_platform" in logger.warning_messages[-1] +def test_unknown_platform_warns_only_once(monkeypatch): + utils_module, logger = load_utils_module(monkeypatch, qq_account=123456, platforms=[]) + + assert utils_module.is_bot_self("unknown_platform", "first") is False + assert utils_module.is_bot_self(" unknown_platform ", "second") is False + assert len(logger.warning_messages) == 1 + + def test_unconfigured_qq_account_disables_qq_and_webui_identity(monkeypatch): utils_module, _logger = load_utils_module(monkeypatch, qq_account=0, platforms=["telegram:tg_bot"]) @@ -185,3 +193,24 @@ def test_is_mentioned_bot_in_message_uses_platform_account(monkeypatch): assert is_mentioned is True assert is_at is True assert reply_probability == 1.0 + + +def test_is_mentioned_bot_in_message_normalizes_qq_platform(monkeypatch): + utils_module, _logger = load_utils_module(monkeypatch, qq_account=123456, platforms=[]) + + message = SimpleNamespace( + processed_plain_text="@ 你好", + platform=" QQ ", + is_mentioned=False, + message_segment=None, + message_info=SimpleNamespace( + additional_config={}, + user_info=SimpleNamespace(user_id="user_1"), + ), + ) + + is_mentioned, is_at, reply_probability = utils_module.is_mentioned_bot_in_message(message) + + assert is_mentioned is True + assert is_at is True + assert reply_probability == 1.0 diff --git a/src/chat/brain_chat/PFC/message_sender.py b/src/chat/brain_chat/PFC/message_sender.py index 483a03c8..a927c413 100644 --- a/src/chat/brain_chat/PFC/message_sender.py +++ b/src/chat/brain_chat/PFC/message_sender.py @@ -42,8 +42,12 @@ class DirectMessageSender: segments = Seg(type="seglist", data=[Seg(type="text", data=content)]) # 获取麦麦的信息 + bot_user_id = get_bot_account(chat_stream.platform) + if not bot_user_id: + logger.warning(f"[私聊][{self.private_name}]平台 {chat_stream.platform} 未配置机器人账号,发送消息时回退到 QQ 账号") + bot_user_id = str(getattr(global_config.bot, "qq_account", "")).strip() bot_user_info = UserInfo( - user_id=get_bot_account(chat_stream.platform), + user_id=bot_user_id, user_nickname=global_config.bot.nickname, ) diff --git a/src/chat/replyer/group_generator.py b/src/chat/replyer/group_generator.py index 760cb9ea..3c41ae2f 100644 --- a/src/chat/replyer/group_generator.py +++ b/src/chat/replyer/group_generator.py @@ -1115,6 +1115,10 @@ class DefaultReplyer: anchor_message: Optional[MaiMessage] = None, ) -> SessionMessage: """构建单个发送消息""" + bot_user_id = get_bot_account(self.chat_stream.platform) + if not bot_user_id: + logger.warning(f"平台 {self.chat_stream.platform} 未配置机器人账号,发送消息时回退到 QQ 账号") + bot_user_id = str(getattr(global_config.bot, "qq_account", "")).strip() maim_message = MessageBase( message_info=BaseMessageInfo( @@ -1122,7 +1126,7 @@ class DefaultReplyer: message_id=message_id, time=thinking_start_time, user_info=MaimUserInfo( - user_id=get_bot_account(self.chat_stream.platform), + user_id=bot_user_id, user_nickname=global_config.bot.nickname, ), additional_config={}, diff --git a/src/chat/replyer/private_generator.py b/src/chat/replyer/private_generator.py index 74049226..008124d4 100644 --- a/src/chat/replyer/private_generator.py +++ b/src/chat/replyer/private_generator.py @@ -955,6 +955,10 @@ class PrivateReplyer: anchor_message: Optional[MaiMessage] = None, ) -> SessionMessage: """构建单个发送消息""" + bot_user_id = get_bot_account(self.chat_stream.platform) + if not bot_user_id: + logger.warning(f"平台 {self.chat_stream.platform} 未配置机器人账号,发送消息时回退到 QQ 账号") + bot_user_id = str(getattr(global_config.bot, "qq_account", "")).strip() maim_message = MessageBase( message_info=BaseMessageInfo( @@ -962,7 +966,7 @@ class PrivateReplyer: message_id=message_id, time=thinking_start_time, user_info=MaimUserInfo( - user_id=get_bot_account(self.chat_stream.platform), + user_id=bot_user_id, user_nickname=global_config.bot.nickname, ), group_info=None, diff --git a/src/chat/utils/utils.py b/src/chat/utils/utils.py index 947d9fc5..44eae4c0 100644 --- a/src/chat/utils/utils.py +++ b/src/chat/utils/utils.py @@ -1,11 +1,12 @@ +from datetime import datetime +from typing import TYPE_CHECKING, List, Optional, Tuple + import ast import json import os import random import re import time -from datetime import datetime -from typing import Optional, Tuple, List, TYPE_CHECKING import jieba @@ -15,12 +16,14 @@ from src.common.logger import get_logger from src.config.config import global_config, model_config from src.llm_models.utils_model import LLMRequest from src.person_info.person_info import Person + from .typo_generator import ChineseTypoGenerator if TYPE_CHECKING: from src.common.data_models.info_data_model import TargetPersonInfo logger = get_logger("chat_utils") +_warned_unconfigured_platforms: set[str] = set() def is_english_letter(char: str) -> bool: @@ -122,14 +125,16 @@ def is_bot_self(platform: str, user_id: str) -> bool: if bot_account: return user_id_str == bot_account - logger.warning(f"平台 {normalized_platform} 未配置机器人账号,无法判断用户 {user_id_str} 是否为机器人自己") + if normalized_platform not in _warned_unconfigured_platforms: + _warned_unconfigured_platforms.add(normalized_platform) + logger.warning(f"平台 {normalized_platform} 未配置机器人账号,无法判断用户 {user_id_str} 是否为机器人自己") return False def is_mentioned_bot_in_message(message: SessionMessage) -> tuple[bool, bool, float]: """检查消息是否提到了机器人(统一多平台实现)""" text = message.processed_plain_text or "" - platform = message.platform or "" + platform = str(message.platform or "").strip().lower() # 获取当前平台对应的账号 current_account = get_bot_account(platform) diff --git a/src/common/message_repository.py b/src/common/message_repository.py index 79353a9a..81ae6360 100644 --- a/src/common/message_repository.py +++ b/src/common/message_repository.py @@ -7,9 +7,9 @@ import traceback from sqlalchemy import and_, func, not_, or_ from sqlmodel import col, select +from src.chat.message_receive.message import SessionMessage from src.common.database.database import get_db_session from src.common.database.database_model import Messages -from src.chat.message_receive.message import SessionMessage from src.common.logger import get_logger logger = get_logger(__name__) @@ -162,17 +162,26 @@ def find_messages( after_time=after_time, ) if filter_bot: - from src.chat.utils.utils import get_all_bot_accounts + from src.chat.utils.utils import _get_configured_qq_account, get_all_bot_accounts bot_accounts = get_all_bot_accounts() + exclusion_conditions: list[Any] = [] if bot_accounts: - bot_identity_predicate = or_( - *[ - and_(Messages.platform == platform_name, Messages.user_id == account) - for platform_name, account in bot_accounts.items() - ] + exclusion_conditions.append( + or_( + *[ + and_(Messages.platform == platform_name, Messages.user_id == account) + for platform_name, account in bot_accounts.items() + ] + ) ) - conditions.append(not_(bot_identity_predicate)) + + # 兼容旧数据:历史机器人消息在所有平台上都使用 QQ 账号进行存储。 + if qq_fallback := _get_configured_qq_account(): + exclusion_conditions.append(Messages.user_id == qq_fallback) + + if exclusion_conditions: + conditions.append(not_(or_(*exclusion_conditions))) if filter_command: conditions.append(Messages.is_command == False) # noqa: E712 diff --git a/src/common/utils/system_utils.py b/src/common/utils/system_utils.py index 6518db42..a5085f19 100644 --- a/src/common/utils/system_utils.py +++ b/src/common/utils/system_utils.py @@ -1,4 +1,5 @@ -# TODO: 这个兼容包装层后续可以删除,统一直接使用 src.chat.utils.utils.is_bot_self +# TODO: 这个包装层后续可以删除,统一直接使用 src.chat.utils.utils.is_bot_self +# 注意:参数顺序已从旧版 (user_id, platform) 变更为 (platform, user_id),与统一接口一致 def is_bot_self(platform: str, user_id: str) -> bool: """ 判断用户 ID 是否是机器人自己。 diff --git a/src/services/send_service.py b/src/services/send_service.py index ee2a8993..6749adfc 100644 --- a/src/services/send_service.py +++ b/src/services/send_service.py @@ -83,6 +83,10 @@ async def _send_to_target( additional_config: dict[str, object] = {} if selected_expressions is not None: additional_config["selected_expressions"] = selected_expressions + bot_user_id = get_bot_account(target_stream.platform) + if not bot_user_id: + logger.warning(f"[SendService] 平台 {target_stream.platform} 未配置机器人账号,发送消息时回退到 QQ 账号") + bot_user_id = str(getattr(global_config.bot, "qq_account", "")).strip() maim_message = MessageBase( message_info=BaseMessageInfo( @@ -90,7 +94,7 @@ async def _send_to_target( message_id=message_id, time=current_time, user_info=MaimUserInfo( - user_id=get_bot_account(target_stream.platform), + user_id=bot_user_id, user_nickname=global_config.bot.nickname, platform=target_stream.platform, ),