From 8674f50d901b2a37cf7cba4a84dd969f3848eb94 Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Sat, 21 Feb 2026 23:50:18 +0800 Subject: [PATCH] =?UTF-8?q?PersonInfo=E7=9B=B8=E5=85=B3=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A=EF=BC=8C=E9=87=8D?= =?UTF-8?q?=E5=91=BD=E5=90=8D=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_models/mai_message_data_model.py | 15 ++- ...del.py => message_component_data_model.py} | 31 +++++- .../data_models/person_info_data_model.py | 94 +++++++++++++++++++ src/common/database/database_model.py | 38 +++++--- src/common/utils/utils_message.py | 43 ++++++++- 5 files changed, 202 insertions(+), 19 deletions(-) rename src/common/data_models/{message_component_model.py => message_component_data_model.py} (91%) create mode 100644 src/common/data_models/person_info_data_model.py diff --git a/src/common/data_models/mai_message_data_model.py b/src/common/data_models/mai_message_data_model.py index 0e881508..8d4c277e 100644 --- a/src/common/data_models/mai_message_data_model.py +++ b/src/common/data_models/mai_message_data_model.py @@ -12,7 +12,7 @@ import json from datetime import datetime from src.common.database.database_model import Messages -from src.common.data_models.message_component_model import MessageSequence +from src.common.data_models.message_component_data_model import MessageSequence from src.common.utils.utils_message import MessageUtils from . import BaseDatabaseDataModel @@ -48,21 +48,30 @@ class MaiMessage(BaseDatabaseDataModel[Messages]): # 定义其他属性 self.message_info: MessageInfo # 初始化后赋值 self.is_mentioned: bool = False + """机器人被提及标记,若被at,则提及也被标记""" self.is_at: bool = False + """机器人被at标记""" self.is_emoji: bool = False + """消息为纯表情包,在计算打字时长时候会被特殊处理""" self.is_picture: bool = False + """消息为纯图片,在计算打字时长时候会被特殊处理""" self.is_command: bool = False + """消息为命令消息,打字时长必定为0""" self.is_notify: bool = False + """消息为通知消息""" self.session_id: str self.reply_to: Optional[str] = None self.processed_plain_text: Optional[str] = None + """处理过后的纯文本内容""" self.display_message: Optional[str] = None + """最后显示给大模型的消息内容""" self.raw_message: MessageSequence + """原始消息数据""" @classmethod - def from_db_instance(cls, db_record: "Messages") -> "MaiMessage": + def from_db_instance(cls, db_record: "Messages"): obj = cls(message_id=db_record.message_id, timestamp=db_record.timestamp) user_info = UserInfo(db_record.user_id, db_record.user_nickname, db_record.user_cardname) @@ -117,7 +126,7 @@ class MaiMessage(BaseDatabaseDataModel[Messages]): ) @classmethod - def from_maim_message(cls, message: MessageBase) -> "MaiMessage": + def from_maim_message(cls, message: MessageBase): """从 maim_message.MessageBase 创建 MaiMessage 实例,解析消息内容并提取相关信息""" msg_info = message.message_info assert msg_info, "MessageBase 的 message_info 不能为空" diff --git a/src/common/data_models/message_component_model.py b/src/common/data_models/message_component_data_model.py similarity index 91% rename from src/common/data_models/message_component_model.py rename to src/common/data_models/message_component_data_model.py index 1d7083d0..8290fdbe 100644 --- a/src/common/data_models/message_component_model.py +++ b/src/common/data_models/message_component_data_model.py @@ -134,9 +134,18 @@ class AtComponent(BaseMessageComponentModel): def format_name(self) -> str: return "at" - def __init__(self, target_user_id: str) -> None: + def __init__( + self, + target_user_id: str, + target_user_nickname: Optional[str] = None, + target_user_cardname: Optional[str] = None, + ) -> None: self.target_user_id = target_user_id """目标用户ID""" + self.target_user_nickname = target_user_nickname + """目标用户昵称""" + self.target_user_cardname = target_user_cardname + """目标用户备注名""" assert isinstance(target_user_id, str), "AtComponent 的 target_user_id 必须是字符串类型" async def to_seg(self) -> Seg: @@ -214,12 +223,15 @@ class ForwardComponent(BaseMessageComponentModel): def __init__( self, user_nickname: str, + message_id: str, content: List[StandardMessageComponents], user_id: Optional[str] = None, user_cardname: Optional[str] = None, ): self.user_nickname: str = user_nickname """转发节点的发送者昵称""" + self.message_id: str = message_id + """转发节点的消息ID""" self.content: List[StandardMessageComponents] = content """消息内容""" self.user_id: Optional[str] = user_id @@ -277,7 +289,14 @@ class MessageSequence: raise RuntimeError("VoiceComponent content 未初始化") return {"type": "voice", "data": item.content, "hash": item.binary_hash} elif isinstance(item, AtComponent): - return {"type": "at", "data": item.target_user_id} + return { + "type": "at", + "data": { + "target_user_id": item.target_user_id, + "target_user_nickname": item.target_user_nickname, + "target_user_cardname": item.target_user_cardname, + }, + } elif isinstance(item, ReplyComponent): return {"type": "reply", "data": item.target_message_id} elif isinstance(item, ForwardNodeComponent): @@ -288,6 +307,7 @@ class MessageSequence: "user_id": comp.user_id, "user_nickname": comp.user_nickname, "user_cardname": comp.user_cardname, + "message_id": comp.message_id, "content": [self._item_2_dict(c) for c in comp.content], } for comp in item.forward_components @@ -310,7 +330,11 @@ class MessageSequence: elif item_type == "voice": return VoiceComponent(binary_hash=item["hash"], content=item["data"]) elif item_type == "at": - return AtComponent(target_user_id=item["data"]) + return AtComponent( + target_user_id=item["data"]["target_user_id"], + target_user_nickname=item["data"].get("target_user_nickname"), + target_user_cardname=item["data"].get("target_user_cardname"), + ) elif item_type == "reply": return ReplyComponent(target_message_id=item["data"]) elif item_type == "forward": @@ -321,6 +345,7 @@ class MessageSequence: user_nickname=fc["user_nickname"], user_id=fc.get("user_id"), user_cardname=fc.get("user_cardname"), + message_id=fc.get("message_id"), content=content, ) forward_components.append(forward_component) diff --git a/src/common/data_models/person_info_data_model.py b/src/common/data_models/person_info_data_model.py new file mode 100644 index 00000000..ac15e9dd --- /dev/null +++ b/src/common/data_models/person_info_data_model.py @@ -0,0 +1,94 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional, List + +import json + +from src.common.database.database_model import PersonInfo + +from . import BaseDatabaseDataModel + + +@dataclass +class GroupCardnameInfo: + group_id: str + group_cardname: str + + +class MaiPersonInfo(BaseDatabaseDataModel[PersonInfo]): + def __init__( + self, + *, + is_known: bool, + person_id: str, + platform: str, + user_id: str, + user_nickname: str, + know_counts: int, + person_name: Optional[str] = None, + name_reason: Optional[str] = None, + group_cardname_list: Optional[List[GroupCardnameInfo]] = None, + memory_points: Optional[List[str]] = None, + first_known_time: Optional[datetime] = None, + last_known_time: Optional[datetime] = None, + ): + self.is_known = is_known + """标记是否为已知用户,已知用户指在数据库中存在记录的用户""" + self.person_id: str = person_id + """用户专有ID""" + self.person_name: Optional[str] = person_name + """用户名称""" + self.name_reason: Optional[str] = name_reason + """用户名称的来源或变更原因说明""" + self.platform: str = platform + """平台标识""" + self.user_id: str = user_id + """用户在平台上的ID""" + self.user_nickname: str = user_nickname + """用户在平台上的昵称""" + self.group_cardname_list: Optional[List[GroupCardnameInfo]] = group_cardname_list + """用户在不同群中的昵称列表""" + self.memory_points: Optional[List[str]] = memory_points + """与用户相关的记忆点列表""" + self.know_counts: int = know_counts + """已知用户被认识的次数""" + self.first_known_time: Optional[datetime] = first_known_time + """第一次被认识的时间""" + self.last_known_time: Optional[datetime] = last_known_time + """最后一次被认识的时间""" + + @classmethod + def from_db_instance(cls, db_record: "PersonInfo"): + nickname_json = json.loads(db_record.group_cardname) if db_record.group_cardname else None + group_cardname_list = [GroupCardnameInfo(**item) for item in nickname_json] if nickname_json else None + memory_points = json.loads(db_record.memory_points) if db_record.memory_points else None + return cls( + is_known=db_record.is_known, + person_id=db_record.person_id, + person_name=db_record.person_name, + name_reason=db_record.name_reason, + platform=db_record.platform, + user_id=db_record.user_id, + user_nickname=db_record.user_nickname, + group_cardname_list=group_cardname_list, + memory_points=memory_points, + know_counts=db_record.know_counts, + first_known_time=db_record.first_known_time, + last_known_time=db_record.last_known_time, + ) + + def to_db_instance(self) -> "PersonInfo": + return PersonInfo( + is_known=self.is_known, + person_id=self.person_id, + person_name=self.person_name, + name_reason=self.name_reason, + platform=self.platform, + user_id=self.user_id, + user_nickname=self.user_nickname, + group_cardname=json.dumps([gn.__dict__ for gn in self.group_cardname_list]) if self.group_cardname_list else None, + memory_points=json.dumps(self.memory_points) if self.memory_points else None, + know_counts=self.know_counts, + first_known_time=self.first_known_time, + last_known_time=self.last_known_time, + ) diff --git a/src/common/database/database_model.py b/src/common/database/database_model.py index 120e278a..48aafd79 100644 --- a/src/common/database/database_model.py +++ b/src/common/database/database_model.py @@ -93,11 +93,15 @@ class Images(SQLModel, table=True): query_count: int = Field(default=0) # 被查询次数 is_registered: bool = Field(default=False) # 是否已经注册 is_banned: bool = Field(default=False) # 被手动禁用 - + no_file_flag: bool = Field(default=False) # 文件不存在标记,如果为True表示文件已经不存在,仅保留描述字段 - record_time: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime, index=True)) # 记录时间(数据库记录被创建的时间) - register_time: Optional[datetime] = Field(default=None, sa_column=Column(DateTime, nullable=True)) # 注册时间(被注册为可用表情包的时间) + record_time: datetime = Field( + default_factory=datetime.now, sa_column=Column(DateTime, index=True) + ) # 记录时间(数据库记录被创建的时间) + register_time: Optional[datetime] = Field( + default=None, sa_column=Column(DateTime, nullable=True) + ) # 注册时间(被注册为可用表情包的时间) last_used_time: Optional[datetime] = Field(default=None, sa_column=Column(DateTime, nullable=True)) # 上次使用时间 vlm_processed: bool = Field(default=False) # 是否已经过VLM处理 @@ -171,7 +175,9 @@ class Expression(SQLModel, table=True): content_list: str # 内容列表,JSON格式存储 count: int = Field(default=0) # 使用次数 - last_active_time: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime, index=True)) # 上次使用时间 + last_active_time: datetime = Field( + default_factory=datetime.now, sa_column=Column(DateTime, index=True) + ) # 上次使用时间 create_time: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime)) # 创建时间 session_id: Optional[str] = Field(default=None, max_length=255, nullable=True) # 会话ID,区分是否为全局表达方式 @@ -232,8 +238,12 @@ class ThinkingQuestion(SQLModel, table=True): answer: Optional[str] = Field(default=None, nullable=True) # 问题答案 thinking_steps: Optional[str] = Field(default=None, nullable=True) # 思考步骤,JSON格式存储 - created_timestamp: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime, index=True)) # 创建时间 - updated_timestamp: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime, index=True)) # 最后更新时间 + created_timestamp: datetime = Field( + default_factory=datetime.now, sa_column=Column(DateTime, index=True) + ) # 创建时间 + updated_timestamp: datetime = Field( + default_factory=datetime.now, sa_column=Column(DateTime, index=True) + ) # 最后更新时间 class BinaryData(SQLModel, table=True): @@ -263,16 +273,18 @@ class PersonInfo(SQLModel, table=True): platform: str = Field(index=True, max_length=100) # 平台名称 user_id: str = Field(index=True, max_length=255) # 用户ID user_nickname: str = Field(index=True, max_length=255) # 用户昵称 - group_nickname: Optional[str] = Field( + group_cardname: Optional[str] = Field( default=None, nullable=True - ) # 群昵称 (JSON, [{"group_id": str, "group_nick_name": str}]) + ) # 群昵称 (JSON, [{"group_id": str, "group_cardname": str}]) # 印象 memory_points: Optional[str] = Field(default=None, nullable=True) # 记忆要点,JSON格式存储 # 认识次数和时间 know_counts: int = Field(default=0) # 认识次数 - first_known_time: Optional[datetime] = Field(default=None, sa_column=Column(DateTime, nullable=True)) # 首次认识时间 + first_known_time: Optional[datetime] = Field( + default=None, sa_column=Column(DateTime, nullable=True) + ) # 首次认识时间 last_known_time: Optional[datetime] = Field(default=None, sa_column=Column(DateTime, nullable=True)) # 最后认识时间 @@ -285,8 +297,12 @@ class ChatSession(SQLModel, table=True): session_id: str = Field(unique=True, index=True, max_length=255) # 聊天会话ID - created_timestamp: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime, index=True)) # 创建时间 - last_active_timestamp: datetime = Field(default_factory=datetime.now, sa_column=Column(DateTime, index=True)) # 最后活跃时间 + created_timestamp: datetime = Field( + default_factory=datetime.now, sa_column=Column(DateTime, index=True) + ) # 创建时间 + last_active_timestamp: datetime = Field( + default_factory=datetime.now, sa_column=Column(DateTime, index=True) + ) # 最后活跃时间 # 身份元数据 user_id: Optional[str] = Field(index=True, max_length=255, nullable=True) # 用户ID diff --git a/src/common/utils/utils_message.py b/src/common/utils/utils_message.py index aead8777..2c33a506 100644 --- a/src/common/utils/utils_message.py +++ b/src/common/utils/utils_message.py @@ -1,11 +1,12 @@ from maim_message import MessageBase, Seg -from typing import List +from typing import List, Tuple, Optional import base64 import hashlib import msgpack +import re -from src.common.data_models.message_component_model import ( +from src.common.data_models.message_component_data_model import ( MessageSequence, StandardMessageComponents, TextComponent, @@ -16,6 +17,7 @@ from src.common.data_models.message_component_model import ( ReplyComponent, DictComponent, ) +from src.config.config import global_config class MessageUtils: @@ -85,3 +87,40 @@ class MessageUtils: return ReplyComponent(target_message_id=seg.data) else: raise NotImplementedError(f"暂时不支持的消息片段类型: {seg.type}") + + @staticmethod + def check_ban_words(text: str) -> Tuple[bool, Optional[str]]: + """检查消息是否包含过滤词 + + Args: + text: 待检查的文本 + + Returns: + bool: 是否包含过滤词 + """ + if not text: + return False, None + return next( + ((True, word) for word in global_config.message_receive.ban_words if word in text), + (False, None), + ) + + @staticmethod + def check_ban_regex(text: str) -> Tuple[bool, Optional[str]]: + """检查消息是否匹配过滤正则表达式 + + Args: + text: 待检查的文本 + chat: 聊天对象 + userinfo: 用户信息 + + Returns: + bool: 是否匹配过滤正则 + """ + # 检查text是否为None或空字符串 + if not text: + return False, None + return next( + ((True, pattern) for pattern in global_config.message_receive.ban_msgs_regex if re.search(pattern, text)), + (False, None), + )