message_builder重构完成

pull/1201/head
UnCLAS-Prommer 2025-08-20 22:48:52 +08:00
parent 9253c0ad77
commit 924983e6de
No known key found for this signature in database
6 changed files with 123 additions and 107 deletions

View File

@ -1,8 +1,8 @@
import time # 导入 time 模块以获取当前时间 import time
import random import random
import re import re
from typing import List, Dict, Any, Tuple, Optional, Callable, Union from typing import List, Dict, Any, Tuple, Optional, Callable
from rich.traceback import install from rich.traceback import install
from src.config.config import global_config from src.config.config import global_config
@ -648,7 +648,7 @@ def build_readable_actions(actions: List[Dict[str, Any]]) -> str:
async def build_readable_messages_with_list( async def build_readable_messages_with_list(
messages: List[Dict[str, Any]], messages: List[DatabaseMessages],
replace_bot_name: bool = True, replace_bot_name: bool = True,
timestamp_mode: str = "relative", timestamp_mode: str = "relative",
truncate: bool = False, truncate: bool = False,
@ -658,7 +658,7 @@ async def build_readable_messages_with_list(
允许通过参数控制格式化行为 允许通过参数控制格式化行为
""" """
formatted_string, details_list, pic_id_mapping, _ = _build_readable_messages_internal( formatted_string, details_list, pic_id_mapping, _ = _build_readable_messages_internal(
messages, replace_bot_name, timestamp_mode, truncate convert_DatabaseMessages_to_MessageAndActionModel(messages), replace_bot_name, timestamp_mode, truncate
) )
if pic_mapping_info := build_pic_mapping_info(pic_id_mapping): if pic_mapping_info := build_pic_mapping_info(pic_id_mapping):
@ -675,7 +675,7 @@ def build_readable_messages_with_id(
truncate: bool = False, truncate: bool = False,
show_actions: bool = False, show_actions: bool = False,
show_pic: bool = True, show_pic: bool = True,
) -> Tuple[str, List[Dict[str, Any]]]: ) -> Tuple[str, List[DatabaseMessages]]:
""" """
将消息列表转换为可读的文本格式并返回原始(时间戳, 昵称, 内容)列表 将消息列表转换为可读的文本格式并返回原始(时间戳, 昵称, 内容)列表
允许通过参数控制格式化行为 允许通过参数控制格式化行为
@ -818,7 +818,6 @@ def build_readable_messages(
formatted_before, _, pic_id_mapping, pic_counter = _build_readable_messages_internal( formatted_before, _, pic_id_mapping, pic_counter = _build_readable_messages_internal(
messages_before_mark, messages_before_mark,
replace_bot_name, replace_bot_name,
merge_messages,
timestamp_mode, timestamp_mode,
truncate, truncate,
pic_id_mapping, pic_id_mapping,
@ -829,7 +828,6 @@ def build_readable_messages(
formatted_after, _, pic_id_mapping, _ = _build_readable_messages_internal( formatted_after, _, pic_id_mapping, _ = _build_readable_messages_internal(
messages_after_mark, messages_after_mark,
replace_bot_name, replace_bot_name,
merge_messages,
timestamp_mode, timestamp_mode,
False, False,
pic_id_mapping, pic_id_mapping,
@ -998,3 +996,22 @@ async def get_person_id_list(messages: List[Dict[str, Any]]) -> List[str]:
person_ids_set.add(person_id) person_ids_set.add(person_id)
return list(person_ids_set) # 将集合转换为列表返回 return list(person_ids_set) # 将集合转换为列表返回
def convert_DatabaseMessages_to_MessageAndActionModel(message: List[DatabaseMessages]) -> List[MessageAndActionModel]:
"""
DatabaseMessages 列表转换为 MessageAndActionModel 列表
"""
return [
MessageAndActionModel(
time=msg.time,
user_id=msg.user_info.user_id,
user_platform=msg.user_info.platform,
user_nickname=msg.user_info.user_nickname,
user_cardname=msg.user_info.user_cardname,
processed_plain_text=msg.processed_plain_text,
display_message=msg.display_message,
chat_info_platform=msg.chat_info.platform,
)
for msg in message
]

View File

@ -12,6 +12,7 @@ from typing import Optional, Tuple, Dict, List, Any
from src.common.logger import get_logger from src.common.logger import get_logger
from src.common.data_models.info_data_model import TargetPersonInfo from src.common.data_models.info_data_model import TargetPersonInfo
from src.common.data_models.database_data_model import DatabaseMessages
from src.common.message_repository import find_messages, count_messages from src.common.message_repository import find_messages, count_messages
from src.config.config import global_config, model_config from src.config.config import global_config, model_config
from src.chat.message_receive.message import MessageRecv from src.chat.message_receive.message import MessageRecv
@ -152,10 +153,13 @@ def get_recent_group_speaker(chat_stream_id: str, sender, limit: int = 12) -> li
if ( if (
(db_msg.user_info.platform, db_msg.user_info.user_id) != sender (db_msg.user_info.platform, db_msg.user_info.user_id) != sender
and db_msg.user_info.user_id != global_config.bot.qq_account and db_msg.user_info.user_id != global_config.bot.qq_account
and (db_msg.user_info.platform, db_msg.user_info.user_id, db_msg.user_info.user_nickname) not in who_chat_in_group and (db_msg.user_info.platform, db_msg.user_info.user_id, db_msg.user_info.user_nickname)
not in who_chat_in_group
and len(who_chat_in_group) < 5 and len(who_chat_in_group) < 5
): # 排除重复排除消息发送者排除bot限制加载的关系数目 ): # 排除重复排除消息发送者排除bot限制加载的关系数目
who_chat_in_group.append((db_msg.user_info.platform, db_msg.user_info.user_id, db_msg.user_info.user_nickname)) who_chat_in_group.append(
(db_msg.user_info.platform, db_msg.user_info.user_id, db_msg.user_info.user_nickname)
)
return who_chat_in_group return who_chat_in_group
@ -641,9 +645,9 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]:
target_info = TargetPersonInfo( target_info = TargetPersonInfo(
platform=platform, platform=platform,
user_id=user_id, user_id=user_id,
user_nickname=user_info.user_nickname, # type: ignore user_nickname=user_info.user_nickname, # type: ignore
person_id=None, person_id=None,
person_name=None person_name=None,
) )
# Try to fetch person info # Try to fetch person info
@ -670,7 +674,7 @@ def get_chat_type_and_target_info(chat_id: str) -> Tuple[bool, Optional[Dict]]:
return is_group_chat, chat_target_info return is_group_chat, chat_target_info
def assign_message_ids(messages: List[Any]) -> List[Dict[str, Any]]: def assign_message_ids(messages: List[DatabaseMessages]) -> List[DatabaseMessages]:
""" """
为消息列表中的每个消息分配唯一的简短随机ID 为消息列表中的每个消息分配唯一的简短随机ID
@ -678,9 +682,9 @@ def assign_message_ids(messages: List[Any]) -> List[Dict[str, Any]]:
messages: 消息列表 messages: 消息列表
Returns: Returns:
包含 {'id': str, 'message': any} 格式的字典列表 List[DatabaseMessages]: 分配了唯一ID的消息列表(写入message_id属性)
""" """
result = [] result: List[DatabaseMessages] = list(messages) # 复制原始消息列表
used_ids = set() used_ids = set()
len_i = len(messages) len_i = len(messages)
if len_i > 100: if len_i > 100:
@ -690,72 +694,62 @@ def assign_message_ids(messages: List[Any]) -> List[Dict[str, Any]]:
a = 1 a = 1
b = 9 b = 9
for i, message in enumerate(messages): for i, _ in enumerate(result):
# 生成唯一的简短ID # 生成唯一的简短ID
while True: while True:
# 使用索引+随机数生成简短ID # 使用索引+随机数生成简短ID
random_suffix = random.randint(a, b) random_suffix = random.randint(a, b)
message_id = f"m{i+1}{random_suffix}" message_id = f"m{i + 1}{random_suffix}"
if message_id not in used_ids: if message_id not in used_ids:
used_ids.add(message_id) used_ids.add(message_id)
break break
result[i].message_id = message_id
result.append({
'id': message_id,
'message': message
})
return result return result
def assign_message_ids_flexible( # def assign_message_ids_flexible(
messages: list, # messages: list, prefix: str = "msg", id_length: int = 6, use_timestamp: bool = False
prefix: str = "msg", # ) -> list:
id_length: int = 6, # """
use_timestamp: bool = False # 为消息列表中的每个消息分配唯一的简短随机ID增强版
) -> list:
"""
为消息列表中的每个消息分配唯一的简短随机ID增强版
Args: # Args:
messages: 消息列表 # messages: 消息列表
prefix: ID前缀默认为"msg" # prefix: ID前缀默认为"msg"
id_length: ID的总长度不包括前缀默认为6 # id_length: ID的总长度不包括前缀默认为6
use_timestamp: 是否在ID中包含时间戳默认为False # use_timestamp: 是否在ID中包含时间戳默认为False
Returns: # Returns:
包含 {'id': str, 'message': any} 格式的字典列表 # 包含 {'id': str, 'message': any} 格式的字典列表
""" # """
result = [] # result = []
used_ids = set() # used_ids = set()
for i, message in enumerate(messages): # for i, message in enumerate(messages):
# 生成唯一的ID # # 生成唯一的ID
while True: # while True:
if use_timestamp: # if use_timestamp:
# 使用时间戳的后几位 + 随机字符 # # 使用时间戳的后几位 + 随机字符
timestamp_suffix = str(int(time.time() * 1000))[-3:] # timestamp_suffix = str(int(time.time() * 1000))[-3:]
remaining_length = id_length - 3 # remaining_length = id_length - 3
random_chars = ''.join(random.choices(string.ascii_lowercase + string.digits, k=remaining_length)) # random_chars = "".join(random.choices(string.ascii_lowercase + string.digits, k=remaining_length))
message_id = f"{prefix}{timestamp_suffix}{random_chars}" # message_id = f"{prefix}{timestamp_suffix}{random_chars}"
else: # else:
# 使用索引 + 随机字符 # # 使用索引 + 随机字符
index_str = str(i + 1) # index_str = str(i + 1)
remaining_length = max(1, id_length - len(index_str)) # remaining_length = max(1, id_length - len(index_str))
random_chars = ''.join(random.choices(string.ascii_lowercase + string.digits, k=remaining_length)) # random_chars = "".join(random.choices(string.ascii_lowercase + string.digits, k=remaining_length))
message_id = f"{prefix}{index_str}{random_chars}" # message_id = f"{prefix}{index_str}{random_chars}"
if message_id not in used_ids: # if message_id not in used_ids:
used_ids.add(message_id) # used_ids.add(message_id)
break # break
result.append({ # result.append({"id": message_id, "message": message})
'id': message_id,
'message': message
})
return result # return result
# 使用示例: # 使用示例:
@ -773,6 +767,7 @@ def assign_message_ids_flexible(
# result3 = assign_message_ids_flexible(messages, prefix="ts", use_timestamp=True) # result3 = assign_message_ids_flexible(messages, prefix="ts", use_timestamp=True)
# # 结果: [{'id': 'ts123a1b', 'message': 'Hello'}, {'id': 'ts123c2d', 'message': 'World'}, {'id': 'ts123e3f', 'message': 'Test message'}] # # 结果: [{'id': 'ts123a1b', 'message': 'Hello'}, {'id': 'ts123c2d', 'message': 'World'}, {'id': 'ts123e3f', 'message': 'Test message'}]
def parse_keywords_string(keywords_input) -> list[str]: def parse_keywords_string(keywords_input) -> list[str]:
# sourcery skip: use-contextlib-suppress # sourcery skip: use-contextlib-suppress
""" """
@ -826,7 +821,7 @@ def parse_keywords_string(keywords_input) -> list[str]:
pass pass
# 尝试不同的分隔符 # 尝试不同的分隔符
separators = ['/', ',', ' ', '|', ';'] separators = ["/", ",", " ", "|", ";"]
for separator in separators: for separator in separators:
if separator in keywords_str: if separator in keywords_str:

View File

@ -1,26 +1,27 @@
import copy
from typing import Dict, Any from typing import Dict, Any
class AbstractClassFlag: class BaseDataModel:
pass def deepcopy(self):
return copy.deepcopy(self)
def temporarily_transform_class_to_dict(obj: Any) -> Any: def temporarily_transform_class_to_dict(obj: Any) -> Any:
""" """
将对象或容器中的 AbstractClassFlag 子类类对象 AbstractClassFlag 实例 将对象或容器中的 BaseDataModel 子类类对象 BaseDataModel 实例
递归转换为普通 dict不修改原对象 递归转换为普通 dict不修改原对象
- 对于类对象isinstance(value, type) issubclass(..., AbstractClassFlag) - 对于类对象isinstance(value, type) issubclass(..., BaseDataModel)
读取类的 __dict__ 中非 dunder 项并递归转换 读取类的 __dict__ 中非 dunder 项并递归转换
- 对于实例isinstance(value, AbstractClassFlag)读取 vars(instance) 并递归转换 - 对于实例isinstance(value, BaseDataModel)读取 vars(instance) 并递归转换
""" """
def _transform(value: Any) -> Any: def _transform(value: Any) -> Any:
# 值是类对象且为 AbstractClassFlag 的子类 # 值是类对象且为 BaseDataModel 的子类
if isinstance(value, type) and issubclass(value, AbstractClassFlag): if isinstance(value, type) and issubclass(value, BaseDataModel):
return {k: _transform(v) for k, v in value.__dict__.items() if not k.startswith("__") and not callable(v)} return {k: _transform(v) for k, v in value.__dict__.items() if not k.startswith("__") and not callable(v)}
# 值是 AbstractClassFlag 的实例 # 值是 BaseDataModel 的实例
if isinstance(value, AbstractClassFlag): if isinstance(value, BaseDataModel):
return {k: _transform(v) for k, v in vars(value).items()} return {k: _transform(v) for k, v in vars(value).items()}
# 常见容器类型,递归处理 # 常见容器类型,递归处理

View File

@ -1,11 +1,11 @@
from typing import Optional, Dict, Any from typing import Optional, Any
from dataclasses import dataclass, field, fields, MISSING from dataclasses import dataclass, field
from . import AbstractClassFlag from . import BaseDataModel
@dataclass @dataclass
class DatabaseUserInfo(AbstractClassFlag): class DatabaseUserInfo(BaseDataModel):
platform: str = field(default_factory=str) platform: str = field(default_factory=str)
user_id: str = field(default_factory=str) user_id: str = field(default_factory=str)
user_nickname: str = field(default_factory=str) user_nickname: str = field(default_factory=str)
@ -21,7 +21,7 @@ class DatabaseUserInfo(AbstractClassFlag):
@dataclass @dataclass
class DatabaseGroupInfo(AbstractClassFlag): class DatabaseGroupInfo(BaseDataModel):
group_id: str = field(default_factory=str) group_id: str = field(default_factory=str)
group_name: str = field(default_factory=str) group_name: str = field(default_factory=str)
group_platform: Optional[str] = None group_platform: Optional[str] = None
@ -35,7 +35,7 @@ class DatabaseGroupInfo(AbstractClassFlag):
@dataclass @dataclass
class DatabaseChatInfo(AbstractClassFlag): class DatabaseChatInfo(BaseDataModel):
stream_id: str = field(default_factory=str) stream_id: str = field(default_factory=str)
platform: str = field(default_factory=str) platform: str = field(default_factory=str)
create_time: float = field(default_factory=float) create_time: float = field(default_factory=float)
@ -55,7 +55,7 @@ class DatabaseChatInfo(AbstractClassFlag):
@dataclass(init=False) @dataclass(init=False)
class DatabaseMessages(AbstractClassFlag): class DatabaseMessages(BaseDataModel):
def __init__( def __init__(
self, self,
message_id: str = "", message_id: str = "",

View File

@ -1,8 +1,10 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Optional from typing import Optional
from . import BaseDataModel
@dataclass @dataclass
class TargetPersonInfo: class TargetPersonInfo(BaseDataModel):
platform: str = field(default_factory=str) platform: str = field(default_factory=str)
user_id: str = field(default_factory=str) user_id: str = field(default_factory=str)
user_nickname: str = field(default_factory=str) user_nickname: str = field(default_factory=str)

View File

@ -1,9 +1,10 @@
from typing import Optional from typing import Optional
from dataclasses import dataclass, field from dataclasses import dataclass, field
from . import BaseDataModel
@dataclass @dataclass
class MessageAndActionModel: class MessageAndActionModel(BaseDataModel):
time: float = field(default_factory=float) time: float = field(default_factory=float)
user_id: str = field(default_factory=str) user_id: str = field(default_factory=str)
user_platform: str = field(default_factory=str) user_platform: str = field(default_factory=str)