feat(notice): 实现全局notice消息管理系统
添加全局notice管理器,将notice消息与普通消息分离处理。主要功能包括: - 创建 GlobalNoticeManager 单例类,支持公共和特定聊天流作用域 - 在 message_manager 中集成notice检测和处理逻辑 - 扩展数据库模型和消息类,添加notice相关字段 - 在提示词生成器中添加notice信息块展示 - 配置系统支持notice相关参数设置 - 适配器插件增强notice类型识别和配置 notice消息特点: - 默认不触发聊天流程,只记录到全局管理器 - 可在提示词中展示最近的系统通知 - 支持按类型设置不同的生存时间 - 支持公共notice(所有聊天可见)和流特定notice BREAKING CHANGE: 数据库消息表结构变更,需要添加 is_public_notice 和 notice_type 字段
This commit is contained in:
400
src/chat/message_manager/global_notice_manager.py
Normal file
400
src/chat/message_manager/global_notice_manager.py
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
"""
|
||||||
|
全局Notice管理器
|
||||||
|
用于统一管理所有notice消息,将notice与正常消息分离
|
||||||
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from collections import defaultdict, deque
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, List, Optional, Any
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from src.common.data_models.database_data_model import DatabaseMessages
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger("global_notice_manager")
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeScope(Enum):
|
||||||
|
"""Notice作用域"""
|
||||||
|
PUBLIC = "public" # 公共notice,所有聊天流可见
|
||||||
|
STREAM = "stream" # 特定聊天流notice
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NoticeMessage:
|
||||||
|
"""Notice消息数据结构"""
|
||||||
|
message: DatabaseMessages
|
||||||
|
scope: NoticeScope
|
||||||
|
target_stream_id: Optional[str] = None # 如果是STREAM类型,指定目标流ID
|
||||||
|
timestamp: float = field(default_factory=time.time)
|
||||||
|
ttl: int = 3600 # 默认1小时过期
|
||||||
|
|
||||||
|
def is_expired(self) -> bool:
|
||||||
|
"""检查是否过期"""
|
||||||
|
return time.time() - self.timestamp > self.ttl
|
||||||
|
|
||||||
|
def is_accessible_by_stream(self, stream_id: str) -> bool:
|
||||||
|
"""检查聊天流是否可以访问此notice"""
|
||||||
|
if self.scope == NoticeScope.PUBLIC:
|
||||||
|
return True
|
||||||
|
return self.target_stream_id == stream_id
|
||||||
|
|
||||||
|
|
||||||
|
class GlobalNoticeManager:
|
||||||
|
"""全局Notice管理器"""
|
||||||
|
|
||||||
|
_instance = None
|
||||||
|
_lock = threading.Lock()
|
||||||
|
|
||||||
|
def __new__(cls):
|
||||||
|
if cls._instance is None:
|
||||||
|
with cls._lock:
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if hasattr(self, '_initialized'):
|
||||||
|
return
|
||||||
|
|
||||||
|
self._initialized = True
|
||||||
|
self._notices: Dict[str, deque[NoticeMessage]] = defaultdict(deque)
|
||||||
|
self._max_notices_per_type = 100 # 每种类型最大存储数量
|
||||||
|
self._cleanup_interval = 300 # 5分钟清理一次过期消息
|
||||||
|
self._last_cleanup_time = time.time()
|
||||||
|
|
||||||
|
# 统计信息
|
||||||
|
self.stats = {
|
||||||
|
"total_notices": 0,
|
||||||
|
"public_notices": 0,
|
||||||
|
"stream_notices": 0,
|
||||||
|
"expired_notices": 0,
|
||||||
|
"last_cleanup_time": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("全局Notice管理器初始化完成")
|
||||||
|
|
||||||
|
def add_notice(
|
||||||
|
self,
|
||||||
|
message: DatabaseMessages,
|
||||||
|
scope: NoticeScope = NoticeScope.STREAM,
|
||||||
|
target_stream_id: Optional[str] = None,
|
||||||
|
ttl: Optional[int] = None
|
||||||
|
) -> bool:
|
||||||
|
"""添加notice消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: 数据库消息对象
|
||||||
|
scope: notice作用域
|
||||||
|
target_stream_id: 目标聊天流ID(仅在STREAM模式下有效)
|
||||||
|
ttl: 生存时间(秒),默认为1小时
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否添加成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 验证消息是否为notice类型
|
||||||
|
if not self._is_notice_message(message):
|
||||||
|
logger.warning(f"尝试添加非notice消息: {message.message_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 验证参数
|
||||||
|
if scope == NoticeScope.STREAM and not target_stream_id:
|
||||||
|
logger.error("STREAM类型的notice必须指定target_stream_id")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 创建notice消息
|
||||||
|
notice = NoticeMessage(
|
||||||
|
message=message,
|
||||||
|
scope=scope,
|
||||||
|
target_stream_id=target_stream_id,
|
||||||
|
ttl=ttl or 3600 # 默认1小时
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确定存储键
|
||||||
|
storage_key = self._get_storage_key(scope, target_stream_id, message)
|
||||||
|
|
||||||
|
# 添加到存储
|
||||||
|
self._notices[storage_key].append(notice)
|
||||||
|
|
||||||
|
# 限制数量
|
||||||
|
if len(self._notices[storage_key]) > self._max_notices_per_type:
|
||||||
|
# 移除最旧的消息
|
||||||
|
removed = self._notices[storage_key].popleft()
|
||||||
|
logger.debug(f"移除过期notice: {removed.message.message_id}")
|
||||||
|
|
||||||
|
# 更新统计
|
||||||
|
self.stats["total_notices"] += 1
|
||||||
|
if scope == NoticeScope.PUBLIC:
|
||||||
|
self.stats["public_notices"] += 1
|
||||||
|
else:
|
||||||
|
self.stats["stream_notices"] += 1
|
||||||
|
|
||||||
|
# 定期清理过期消息
|
||||||
|
self._cleanup_expired_notices()
|
||||||
|
|
||||||
|
logger.info(f"✅ Notice已添加: id={message.message_id}, type={self._get_notice_type(message)}, scope={scope.value}, target={target_stream_id}, storage_key={storage_key}, ttl={ttl}s")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"添加notice消息失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_accessible_notices(self, stream_id: str, limit: int = 20) -> List[NoticeMessage]:
|
||||||
|
"""获取指定聊天流可访问的notice消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stream_id: 聊天流ID
|
||||||
|
limit: 最大返回数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[NoticeMessage]: 可访问的notice消息列表,按时间倒序排列
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
accessible_notices = []
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
# 清理过期消息
|
||||||
|
if current_time - self._last_cleanup_time > self._cleanup_interval:
|
||||||
|
self._cleanup_expired_notices()
|
||||||
|
|
||||||
|
# 收集可访问的notice
|
||||||
|
for storage_key, notices in self._notices.items():
|
||||||
|
for notice in notices:
|
||||||
|
if notice.is_expired():
|
||||||
|
continue
|
||||||
|
|
||||||
|
if notice.is_accessible_by_stream(stream_id):
|
||||||
|
accessible_notices.append(notice)
|
||||||
|
|
||||||
|
# 按时间倒序排列
|
||||||
|
accessible_notices.sort(key=lambda x: x.timestamp, reverse=True)
|
||||||
|
|
||||||
|
# 限制数量
|
||||||
|
return accessible_notices[:limit]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取可访问notice失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_notice_text(self, stream_id: str, limit: int = 10) -> str:
|
||||||
|
"""获取格式化的notice文本,用于构建提示词
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stream_id: 聊天流ID
|
||||||
|
limit: 最大notice数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 格式化的notice文本块(不包含标题,由调用方添加)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
notices = self.get_accessible_notices(stream_id, limit)
|
||||||
|
|
||||||
|
if not notices:
|
||||||
|
logger.debug(f"没有可访问的notice消息: stream_id={stream_id}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 构建notice文本块(不包含标题和结束线)
|
||||||
|
notice_lines = []
|
||||||
|
|
||||||
|
for notice in notices:
|
||||||
|
message = notice.message
|
||||||
|
notice_type = self._get_notice_type(message)
|
||||||
|
|
||||||
|
# 格式化notice消息
|
||||||
|
if notice_type:
|
||||||
|
notice_line = f"[{notice_type}] {message.processed_plain_text or message.raw_message}"
|
||||||
|
else:
|
||||||
|
notice_line = f"[通知] {message.processed_plain_text or message.raw_message}"
|
||||||
|
|
||||||
|
# 添加时间信息(相对时间)
|
||||||
|
time_diff = int(time.time() - notice.timestamp)
|
||||||
|
if time_diff < 60:
|
||||||
|
time_str = "刚刚"
|
||||||
|
elif time_diff < 3600:
|
||||||
|
time_str = f"{time_diff // 60}分钟前"
|
||||||
|
elif time_diff < 86400:
|
||||||
|
time_str = f"{time_diff // 3600}小时前"
|
||||||
|
else:
|
||||||
|
time_str = f"{time_diff // 86400}天前"
|
||||||
|
|
||||||
|
notice_line += f" ({time_str})"
|
||||||
|
notice_lines.append(notice_line)
|
||||||
|
|
||||||
|
result = "\n".join(notice_lines)
|
||||||
|
logger.debug(f"获取notice文本成功: stream_id={stream_id}, 数量={len(notices)}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取notice文本失败: {e}", exc_info=True)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def clear_notices(self, stream_id: Optional[str] = None, notice_type: Optional[str] = None) -> int:
|
||||||
|
"""清理notice消息
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stream_id: 聊天流ID,如果为None则清理所有流
|
||||||
|
notice_type: notice类型,如果为None则清理所有类型
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: 清理的消息数量
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
removed_count = 0
|
||||||
|
|
||||||
|
# 需要移除的键
|
||||||
|
keys_to_remove = []
|
||||||
|
|
||||||
|
for storage_key, notices in self._notices.items():
|
||||||
|
new_notices = deque()
|
||||||
|
|
||||||
|
for notice in notices:
|
||||||
|
should_remove = True
|
||||||
|
|
||||||
|
# 检查流ID过滤
|
||||||
|
if stream_id is not None:
|
||||||
|
if notice.scope == NoticeScope.STREAM:
|
||||||
|
if notice.target_stream_id != stream_id:
|
||||||
|
should_remove = False
|
||||||
|
else:
|
||||||
|
# 公共notice,只有当指定清理所有流时才清理
|
||||||
|
should_remove = False
|
||||||
|
|
||||||
|
# 检查notice类型过滤
|
||||||
|
if should_remove and notice_type is not None:
|
||||||
|
message_type = self._get_notice_type(notice.message)
|
||||||
|
if message_type != notice_type:
|
||||||
|
should_remove = False
|
||||||
|
|
||||||
|
if should_remove:
|
||||||
|
removed_count += 1
|
||||||
|
else:
|
||||||
|
new_notices.append(notice)
|
||||||
|
|
||||||
|
if new_notices:
|
||||||
|
self._notices[storage_key] = new_notices
|
||||||
|
else:
|
||||||
|
keys_to_remove.append(storage_key)
|
||||||
|
|
||||||
|
# 移除空的键
|
||||||
|
for key in keys_to_remove:
|
||||||
|
del self._notices[key]
|
||||||
|
|
||||||
|
logger.info(f"清理notice消息: {removed_count} 条")
|
||||||
|
return removed_count
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"清理notice消息失败: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_stats(self) -> Dict[str, Any]:
|
||||||
|
"""获取统计信息"""
|
||||||
|
# 更新实时统计
|
||||||
|
total_active_notices = sum(len(notices) for notices in self._notices.values())
|
||||||
|
self.stats["total_notices"] = total_active_notices
|
||||||
|
self.stats["active_keys"] = len(self._notices)
|
||||||
|
self.stats["last_cleanup_time"] = int(self._last_cleanup_time)
|
||||||
|
|
||||||
|
# 添加详细的存储键信息
|
||||||
|
storage_keys_info = {}
|
||||||
|
for key, notices in self._notices.items():
|
||||||
|
storage_keys_info[key] = {
|
||||||
|
"count": len(notices),
|
||||||
|
"oldest": min((n.timestamp for n in notices), default=0),
|
||||||
|
"newest": max((n.timestamp for n in notices), default=0),
|
||||||
|
}
|
||||||
|
self.stats["storage_keys"] = storage_keys_info
|
||||||
|
|
||||||
|
return self.stats.copy()
|
||||||
|
|
||||||
|
def _is_notice_message(self, message: DatabaseMessages) -> bool:
|
||||||
|
"""检查消息是否为notice类型"""
|
||||||
|
try:
|
||||||
|
# 首先检查消息的is_notify字段
|
||||||
|
if hasattr(message, 'is_notify') and message.is_notify:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 检查消息的附加配置
|
||||||
|
if hasattr(message, 'additional_config') and message.additional_config:
|
||||||
|
if isinstance(message.additional_config, dict):
|
||||||
|
return message.additional_config.get("is_notice", False)
|
||||||
|
elif isinstance(message.additional_config, str):
|
||||||
|
# 兼容JSON字符串格式
|
||||||
|
import json
|
||||||
|
config = json.loads(message.additional_config)
|
||||||
|
return config.get("is_notice", False)
|
||||||
|
|
||||||
|
# 检查消息类型或其他标识
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"检查notice类型失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _get_storage_key(self, scope: NoticeScope, target_stream_id: Optional[str], message: DatabaseMessages) -> str:
|
||||||
|
"""生成存储键"""
|
||||||
|
if scope == NoticeScope.PUBLIC:
|
||||||
|
return "public"
|
||||||
|
else:
|
||||||
|
notice_type = self._get_notice_type(message) or "default"
|
||||||
|
return f"stream_{target_stream_id}_{notice_type}"
|
||||||
|
|
||||||
|
def _get_notice_type(self, message: DatabaseMessages) -> Optional[str]:
|
||||||
|
"""获取notice类型"""
|
||||||
|
try:
|
||||||
|
if hasattr(message, 'additional_config') and message.additional_config:
|
||||||
|
if isinstance(message.additional_config, dict):
|
||||||
|
return message.additional_config.get("notice_type")
|
||||||
|
elif isinstance(message.additional_config, str):
|
||||||
|
import json
|
||||||
|
config = json.loads(message.additional_config)
|
||||||
|
return config.get("notice_type")
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _cleanup_expired_notices(self) -> int:
|
||||||
|
"""清理过期的notice消息"""
|
||||||
|
try:
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - self._last_cleanup_time < self._cleanup_interval:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
removed_count = 0
|
||||||
|
keys_to_remove = []
|
||||||
|
|
||||||
|
for storage_key, notices in self._notices.items():
|
||||||
|
new_notices = deque()
|
||||||
|
|
||||||
|
for notice in notices:
|
||||||
|
if notice.is_expired():
|
||||||
|
removed_count += 1
|
||||||
|
self.stats["expired_notices"] += 1
|
||||||
|
else:
|
||||||
|
new_notices.append(notice)
|
||||||
|
|
||||||
|
if new_notices:
|
||||||
|
self._notices[storage_key] = new_notices
|
||||||
|
else:
|
||||||
|
keys_to_remove.append(storage_key)
|
||||||
|
|
||||||
|
# 移除空的键
|
||||||
|
for key in keys_to_remove:
|
||||||
|
del self._notices[key]
|
||||||
|
|
||||||
|
self._last_cleanup_time = current_time
|
||||||
|
|
||||||
|
if removed_count > 0:
|
||||||
|
logger.debug(f"清理过期notice: {removed_count} 条")
|
||||||
|
|
||||||
|
return removed_count
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"清理过期notice失败: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
# 创建全局单例实例
|
||||||
|
global_notice_manager = GlobalNoticeManager()
|
||||||
@@ -7,7 +7,7 @@ import asyncio
|
|||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from typing import TYPE_CHECKING, Any, Dict
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
from src.chat.chatter_manager import ChatterManager
|
from src.chat.chatter_manager import ChatterManager
|
||||||
from src.chat.message_receive.chat_stream import ChatStream
|
from src.chat.message_receive.chat_stream import ChatStream
|
||||||
@@ -20,6 +20,7 @@ from src.plugin_system.apis.chat_api import get_chat_manager
|
|||||||
|
|
||||||
from .distribution_manager import stream_loop_manager
|
from .distribution_manager import stream_loop_manager
|
||||||
from .sleep_system.state_manager import SleepState, sleep_state_manager
|
from .sleep_system.state_manager import SleepState, sleep_state_manager
|
||||||
|
from .global_notice_manager import global_notice_manager, NoticeScope
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -53,6 +54,9 @@ class MessageManager:
|
|||||||
|
|
||||||
# 不再需要全局上下文管理器,直接通过 ChatManager 访问各个 ChatStream 的 context_manager
|
# 不再需要全局上下文管理器,直接通过 ChatManager 访问各个 ChatStream 的 context_manager
|
||||||
|
|
||||||
|
# 全局Notice管理器
|
||||||
|
self.notice_manager = global_notice_manager
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
"""启动消息管理器"""
|
"""启动消息管理器"""
|
||||||
if self.is_running:
|
if self.is_running:
|
||||||
@@ -154,6 +158,14 @@ class MessageManager:
|
|||||||
# TODO: 在这里为 WOKEN_UP_ANGRY 等未来状态添加特殊处理逻辑
|
# TODO: 在这里为 WOKEN_UP_ANGRY 等未来状态添加特殊处理逻辑
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# 检查是否为notice消息
|
||||||
|
if self._is_notice_message(message):
|
||||||
|
# Notice消息处理 - 不进入未读消息
|
||||||
|
logger.info(f"📢 检测到notice消息: message_id={message.message_id}, is_notify={message.is_notify}, notice_type={getattr(message, 'notice_type', None)}")
|
||||||
|
await self._handle_notice_message(stream_id, message)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 普通消息处理
|
||||||
chat_manager = get_chat_manager()
|
chat_manager = get_chat_manager()
|
||||||
chat_stream = await chat_manager.get_stream(stream_id)
|
chat_stream = await chat_manager.get_stream(stream_id)
|
||||||
if not chat_stream:
|
if not chat_stream:
|
||||||
@@ -618,6 +630,147 @@ class MessageManager:
|
|||||||
"processing_streams": len([s for s in self.stream_processing_status.keys() if self.stream_processing_status[s]]),
|
"processing_streams": len([s for s in self.stream_processing_status.keys() if self.stream_processing_status[s]]),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ===== Notice管理相关方法 =====
|
||||||
|
|
||||||
|
def _is_notice_message(self, message: DatabaseMessages) -> bool:
|
||||||
|
"""检查消息是否为notice类型"""
|
||||||
|
try:
|
||||||
|
# 首先检查消息的is_notify字段
|
||||||
|
if hasattr(message, 'is_notify') and message.is_notify:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 检查消息的附加配置
|
||||||
|
if hasattr(message, 'additional_config') and message.additional_config:
|
||||||
|
if isinstance(message.additional_config, dict):
|
||||||
|
return message.additional_config.get("is_notice", False)
|
||||||
|
elif isinstance(message.additional_config, str):
|
||||||
|
# 兼容JSON字符串格式
|
||||||
|
import json
|
||||||
|
config = json.loads(message.additional_config)
|
||||||
|
return config.get("is_notice", False)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"检查notice类型失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def _handle_notice_message(self, stream_id: str, message: DatabaseMessages) -> None:
|
||||||
|
"""处理notice消息,将其添加到全局notice管理器"""
|
||||||
|
try:
|
||||||
|
# 获取notice作用域
|
||||||
|
scope = self._determine_notice_scope(message, stream_id)
|
||||||
|
|
||||||
|
# 添加到全局notice管理器
|
||||||
|
success = self.notice_manager.add_notice(
|
||||||
|
message=message,
|
||||||
|
scope=scope,
|
||||||
|
target_stream_id=stream_id if scope == NoticeScope.STREAM else None,
|
||||||
|
ttl=self._get_notice_ttl(message)
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
logger.info(f"✅ Notice消息已添加到全局管理器: message_id={message.message_id}, scope={scope.value}, stream={stream_id}, ttl={self._get_notice_ttl(message)}s")
|
||||||
|
else:
|
||||||
|
logger.warning(f"❌ Notice消息添加失败: message_id={message.message_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理notice消息失败: {e}")
|
||||||
|
|
||||||
|
def _determine_notice_scope(self, message: DatabaseMessages, stream_id: str) -> NoticeScope:
|
||||||
|
"""确定notice的作用域"""
|
||||||
|
try:
|
||||||
|
# 检查附加配置中的公共notice标志
|
||||||
|
if hasattr(message, 'additional_config') and message.additional_config:
|
||||||
|
if isinstance(message.additional_config, dict):
|
||||||
|
is_public = message.additional_config.get("is_public_notice", False)
|
||||||
|
elif isinstance(message.additional_config, str):
|
||||||
|
import json
|
||||||
|
config = json.loads(message.additional_config)
|
||||||
|
is_public = config.get("is_public_notice", False)
|
||||||
|
else:
|
||||||
|
is_public = False
|
||||||
|
|
||||||
|
if is_public:
|
||||||
|
return NoticeScope.PUBLIC
|
||||||
|
|
||||||
|
# 检查notice类型来决定作用域
|
||||||
|
notice_type = self._get_notice_type(message)
|
||||||
|
|
||||||
|
# 某些类型的notice默认为公共notice
|
||||||
|
public_notice_types = {
|
||||||
|
"group_whole_ban", "group_whole_lift_ban",
|
||||||
|
"system_announcement", "platform_maintenance"
|
||||||
|
}
|
||||||
|
|
||||||
|
if notice_type in public_notice_types:
|
||||||
|
return NoticeScope.PUBLIC
|
||||||
|
|
||||||
|
# 默认为特定聊天流notice
|
||||||
|
return NoticeScope.STREAM
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"确定notice作用域失败: {e}")
|
||||||
|
return NoticeScope.STREAM
|
||||||
|
|
||||||
|
def _get_notice_type(self, message: DatabaseMessages) -> Optional[str]:
|
||||||
|
"""获取notice类型"""
|
||||||
|
try:
|
||||||
|
if hasattr(message, 'additional_config') and message.additional_config:
|
||||||
|
if isinstance(message.additional_config, dict):
|
||||||
|
return message.additional_config.get("notice_type")
|
||||||
|
elif isinstance(message.additional_config, str):
|
||||||
|
import json
|
||||||
|
config = json.loads(message.additional_config)
|
||||||
|
return config.get("notice_type")
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _get_notice_ttl(self, message: DatabaseMessages) -> int:
|
||||||
|
"""获取notice的生存时间"""
|
||||||
|
try:
|
||||||
|
# 根据notice类型设置不同的TTL
|
||||||
|
notice_type = self._get_notice_type(message)
|
||||||
|
|
||||||
|
ttl_mapping = {
|
||||||
|
"poke": 1800, # 戳一戳30分钟
|
||||||
|
"emoji_like": 3600, # 表情回复1小时
|
||||||
|
"group_ban": 7200, # 禁言2小时
|
||||||
|
"group_lift_ban": 7200, # 解禁2小时
|
||||||
|
"group_whole_ban": 3600, # 全体禁言1小时
|
||||||
|
"group_whole_lift_ban": 3600, # 解除全体禁言1小时
|
||||||
|
}
|
||||||
|
|
||||||
|
return ttl_mapping.get(notice_type, 3600) # 默认1小时
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return 3600
|
||||||
|
|
||||||
|
def get_notice_text(self, stream_id: str, limit: int = 10) -> str:
|
||||||
|
"""获取指定聊天流的notice文本,用于构建提示词"""
|
||||||
|
try:
|
||||||
|
return self.notice_manager.get_notice_text(stream_id, limit)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取notice文本失败: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def clear_notices(self, stream_id: Optional[str] = None, notice_type: Optional[str] = None) -> int:
|
||||||
|
"""清理notice消息"""
|
||||||
|
try:
|
||||||
|
return self.notice_manager.clear_notices(stream_id, notice_type)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"清理notice失败: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_notice_stats(self) -> Dict[str, Any]:
|
||||||
|
"""获取notice管理器统计信息"""
|
||||||
|
try:
|
||||||
|
return self.notice_manager.get_stats()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取notice统计失败: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
# 创建全局消息管理器实例
|
# 创建全局消息管理器实例
|
||||||
message_manager = MessageManager()
|
message_manager = MessageManager()
|
||||||
|
|||||||
@@ -301,12 +301,35 @@ class ChatBot:
|
|||||||
return False, None, True # 出错时继续处理消息
|
return False, None, True # 出错时继续处理消息
|
||||||
|
|
||||||
async def handle_notice_message(self, message: MessageRecv):
|
async def handle_notice_message(self, message: MessageRecv):
|
||||||
|
"""处理notice消息
|
||||||
|
|
||||||
|
notice消息是系统事件通知(如禁言、戳一戳等),具有以下特点:
|
||||||
|
1. 默认不触发聊天流程,只记录
|
||||||
|
2. 可通过配置开启触发聊天流程
|
||||||
|
3. 会在提示词中展示
|
||||||
|
"""
|
||||||
|
# 检查是否是notice消息
|
||||||
|
if message.is_notify:
|
||||||
|
logger.info(f"收到notice消息: {message.notice_type}")
|
||||||
|
|
||||||
|
# 根据配置决定是否触发聊天流程
|
||||||
|
if not global_config.notice.enable_notice_trigger_chat:
|
||||||
|
logger.debug("notice消息不触发聊天流程(配置已关闭)")
|
||||||
|
return True # 返回True表示已处理,不继续后续流程
|
||||||
|
else:
|
||||||
|
logger.debug("notice消息触发聊天流程(配置已开启)")
|
||||||
|
return False # 返回False表示继续处理,触发聊天流程
|
||||||
|
|
||||||
|
# 兼容旧的notice判断方式
|
||||||
if message.message_info.message_id == "notice":
|
if message.message_info.message_id == "notice":
|
||||||
message.is_notify = True
|
message.is_notify = True
|
||||||
logger.info("notice消息")
|
logger.info("旧格式notice消息")
|
||||||
# print(message)
|
|
||||||
|
# 同样根据配置决定
|
||||||
return True
|
if not global_config.notice.enable_notice_trigger_chat:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
# 处理适配器响应消息
|
# 处理适配器响应消息
|
||||||
if hasattr(message, "message_segment") and message.message_segment:
|
if hasattr(message, "message_segment") and message.message_segment:
|
||||||
@@ -425,6 +448,107 @@ class ChatBot:
|
|||||||
f"[{chat_name}]{message.message_info.user_info.user_nickname}:{message.processed_plain_text}\u001b[0m"
|
f"[{chat_name}]{message.message_info.user_info.user_nickname}:{message.processed_plain_text}\u001b[0m"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 处理notice消息
|
||||||
|
notice_handled = await self.handle_notice_message(message)
|
||||||
|
if notice_handled:
|
||||||
|
# notice消息已处理,需要先添加到message_manager再存储
|
||||||
|
try:
|
||||||
|
from src.common.data_models.database_data_model import DatabaseMessages
|
||||||
|
import time
|
||||||
|
|
||||||
|
message_info = message.message_info
|
||||||
|
msg_user_info = getattr(message_info, "user_info", None)
|
||||||
|
stream_user_info = getattr(message.chat_stream, "user_info", None)
|
||||||
|
group_info = getattr(message.chat_stream, "group_info", None)
|
||||||
|
|
||||||
|
message_id = message_info.message_id or ""
|
||||||
|
message_time = message_info.time if message_info.time is not None else time.time()
|
||||||
|
|
||||||
|
user_id = ""
|
||||||
|
user_nickname = ""
|
||||||
|
user_cardname = None
|
||||||
|
user_platform = ""
|
||||||
|
if msg_user_info:
|
||||||
|
user_id = str(getattr(msg_user_info, "user_id", "") or "")
|
||||||
|
user_nickname = getattr(msg_user_info, "user_nickname", "") or ""
|
||||||
|
user_cardname = getattr(msg_user_info, "user_cardname", None)
|
||||||
|
user_platform = getattr(msg_user_info, "platform", "") or ""
|
||||||
|
elif stream_user_info:
|
||||||
|
user_id = str(getattr(stream_user_info, "user_id", "") or "")
|
||||||
|
user_nickname = getattr(stream_user_info, "user_nickname", "") or ""
|
||||||
|
user_cardname = getattr(stream_user_info, "user_cardname", None)
|
||||||
|
user_platform = getattr(stream_user_info, "platform", "") or ""
|
||||||
|
|
||||||
|
chat_user_id = str(getattr(stream_user_info, "user_id", "") or "")
|
||||||
|
chat_user_nickname = getattr(stream_user_info, "user_nickname", "") or ""
|
||||||
|
chat_user_cardname = getattr(stream_user_info, "user_cardname", None)
|
||||||
|
chat_user_platform = getattr(stream_user_info, "platform", "") or ""
|
||||||
|
|
||||||
|
group_id = getattr(group_info, "group_id", None)
|
||||||
|
group_name = getattr(group_info, "group_name", None)
|
||||||
|
group_platform = getattr(group_info, "platform", None)
|
||||||
|
|
||||||
|
# 构建additional_config,确保包含is_notice标志
|
||||||
|
import json
|
||||||
|
additional_config_dict = {
|
||||||
|
"is_notice": True,
|
||||||
|
"notice_type": message.notice_type or "unknown",
|
||||||
|
"is_public_notice": bool(message.is_public_notice),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 如果message_info有additional_config,合并进来
|
||||||
|
if hasattr(message_info, 'additional_config') and message_info.additional_config:
|
||||||
|
if isinstance(message_info.additional_config, dict):
|
||||||
|
additional_config_dict.update(message_info.additional_config)
|
||||||
|
elif isinstance(message_info.additional_config, str):
|
||||||
|
try:
|
||||||
|
existing_config = json.loads(message_info.additional_config)
|
||||||
|
additional_config_dict.update(existing_config)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
additional_config_json = json.dumps(additional_config_dict)
|
||||||
|
|
||||||
|
# 创建数据库消息对象
|
||||||
|
db_message = DatabaseMessages(
|
||||||
|
message_id=message_id,
|
||||||
|
time=float(message_time),
|
||||||
|
chat_id=message.chat_stream.stream_id,
|
||||||
|
processed_plain_text=message.processed_plain_text,
|
||||||
|
display_message=message.processed_plain_text,
|
||||||
|
is_notify=bool(message.is_notify),
|
||||||
|
is_public_notice=bool(message.is_public_notice),
|
||||||
|
notice_type=message.notice_type,
|
||||||
|
additional_config=additional_config_json,
|
||||||
|
user_id=user_id,
|
||||||
|
user_nickname=user_nickname,
|
||||||
|
user_cardname=user_cardname,
|
||||||
|
user_platform=user_platform,
|
||||||
|
chat_info_stream_id=message.chat_stream.stream_id,
|
||||||
|
chat_info_platform=message.chat_stream.platform,
|
||||||
|
chat_info_create_time=float(message.chat_stream.create_time),
|
||||||
|
chat_info_last_active_time=float(message.chat_stream.last_active_time),
|
||||||
|
chat_info_user_id=chat_user_id,
|
||||||
|
chat_info_user_nickname=chat_user_nickname,
|
||||||
|
chat_info_user_cardname=chat_user_cardname,
|
||||||
|
chat_info_user_platform=chat_user_platform,
|
||||||
|
chat_info_group_id=group_id,
|
||||||
|
chat_info_group_name=group_name,
|
||||||
|
chat_info_group_platform=group_platform,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 添加到message_manager(这会将notice添加到全局notice管理器)
|
||||||
|
await message_manager.add_message(message.chat_stream.stream_id, db_message)
|
||||||
|
logger.info(f"✅ Notice消息已添加到message_manager: type={message.notice_type}, stream={message.chat_stream.stream_id}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Notice消息添加到message_manager失败: {e}", exc_info=True)
|
||||||
|
|
||||||
|
# 存储后直接返回
|
||||||
|
await MessageStorage.store_message(message, chat)
|
||||||
|
logger.debug("notice消息已存储,跳过后续处理")
|
||||||
|
return
|
||||||
|
|
||||||
# 过滤检查
|
# 过滤检查
|
||||||
if _check_ban_words(message.processed_plain_text, chat, user_info) or _check_ban_regex( # type: ignore
|
if _check_ban_words(message.processed_plain_text, chat, user_info) or _check_ban_regex( # type: ignore
|
||||||
message.raw_message, # type: ignore
|
message.raw_message, # type: ignore
|
||||||
@@ -522,6 +646,8 @@ class ChatBot:
|
|||||||
is_picid=bool(message.is_picid),
|
is_picid=bool(message.is_picid),
|
||||||
is_command=bool(message.is_command),
|
is_command=bool(message.is_command),
|
||||||
is_notify=bool(message.is_notify),
|
is_notify=bool(message.is_notify),
|
||||||
|
is_public_notice=bool(message.is_public_notice),
|
||||||
|
notice_type=message.notice_type,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
user_nickname=user_nickname,
|
user_nickname=user_nickname,
|
||||||
user_cardname=user_cardname,
|
user_cardname=user_cardname,
|
||||||
|
|||||||
@@ -203,6 +203,8 @@ class ChatStream:
|
|||||||
is_video=getattr(message, "is_video", False),
|
is_video=getattr(message, "is_video", False),
|
||||||
is_command=getattr(message, "is_command", False),
|
is_command=getattr(message, "is_command", False),
|
||||||
is_notify=getattr(message, "is_notify", False),
|
is_notify=getattr(message, "is_notify", False),
|
||||||
|
is_public_notice=getattr(message, "is_public_notice", False),
|
||||||
|
notice_type=getattr(message, "notice_type", None),
|
||||||
# 消息内容
|
# 消息内容
|
||||||
processed_plain_text=getattr(message, "processed_plain_text", ""),
|
processed_plain_text=getattr(message, "processed_plain_text", ""),
|
||||||
display_message=getattr(message, "processed_plain_text", ""), # 默认使用processed_plain_text
|
display_message=getattr(message, "processed_plain_text", ""), # 默认使用processed_plain_text
|
||||||
|
|||||||
@@ -121,7 +121,9 @@ class MessageRecv(Message):
|
|||||||
self.is_voice = False
|
self.is_voice = False
|
||||||
self.is_video = False
|
self.is_video = False
|
||||||
self.is_mentioned = None
|
self.is_mentioned = None
|
||||||
self.is_notify = False
|
self.is_notify = False # 是否为notice消息
|
||||||
|
self.is_public_notice = False # 是否为公共notice
|
||||||
|
self.notice_type = None # notice类型
|
||||||
self.is_at = False
|
self.is_at = False
|
||||||
self.is_command = False
|
self.is_command = False
|
||||||
|
|
||||||
@@ -131,6 +133,12 @@ class MessageRecv(Message):
|
|||||||
|
|
||||||
self.key_words = []
|
self.key_words = []
|
||||||
self.key_words_lite = []
|
self.key_words_lite = []
|
||||||
|
|
||||||
|
# 解析additional_config中的notice信息
|
||||||
|
if self.message_info.additional_config and isinstance(self.message_info.additional_config, dict):
|
||||||
|
self.is_notify = self.message_info.additional_config.get("is_notice", False)
|
||||||
|
self.is_public_notice = self.message_info.additional_config.get("is_public_notice", False)
|
||||||
|
self.notice_type = self.message_info.additional_config.get("notice_type")
|
||||||
|
|
||||||
def update_chat_stream(self, chat_stream: "ChatStream"):
|
def update_chat_stream(self, chat_stream: "ChatStream"):
|
||||||
self.chat_stream = chat_stream
|
self.chat_stream = chat_stream
|
||||||
|
|||||||
@@ -222,6 +222,8 @@ class OptimizedChatStream:
|
|||||||
is_video=getattr(message, "is_video", False),
|
is_video=getattr(message, "is_video", False),
|
||||||
is_command=getattr(message, "is_command", False),
|
is_command=getattr(message, "is_command", False),
|
||||||
is_notify=getattr(message, "is_notify", False),
|
is_notify=getattr(message, "is_notify", False),
|
||||||
|
is_public_notice=getattr(message, "is_public_notice", False),
|
||||||
|
notice_type=getattr(message, "notice_type", None),
|
||||||
processed_plain_text=getattr(message, "processed_plain_text", ""),
|
processed_plain_text=getattr(message, "processed_plain_text", ""),
|
||||||
display_message=getattr(message, "processed_plain_text", ""),
|
display_message=getattr(message, "processed_plain_text", ""),
|
||||||
priority_mode=getattr(message, "priority_mode", None),
|
priority_mode=getattr(message, "priority_mode", None),
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ def init_prompt():
|
|||||||
### 📬 未读历史消息(动作执行对象)
|
### 📬 未读历史消息(动作执行对象)
|
||||||
{unread_history_prompt}
|
{unread_history_prompt}
|
||||||
|
|
||||||
|
{notice_block}
|
||||||
|
|
||||||
## 表达方式
|
## 表达方式
|
||||||
- *你需要参考你的回复风格:*
|
- *你需要参考你的回复风格:*
|
||||||
{reply_style}
|
{reply_style}
|
||||||
@@ -179,6 +181,8 @@ If you need to use the search tool, please directly call the function "lpmm_sear
|
|||||||
{relation_info_block}
|
{relation_info_block}
|
||||||
{extra_info_block}
|
{extra_info_block}
|
||||||
|
|
||||||
|
{notice_block}
|
||||||
|
|
||||||
{cross_context_block}
|
{cross_context_block}
|
||||||
{identity}
|
{identity}
|
||||||
如果有人说你是人机,你可以用一种阴阳怪气的口吻来回应
|
如果有人说你是人机,你可以用一种阴阳怪气的口吻来回应
|
||||||
@@ -779,6 +783,55 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
return keywords_reaction_prompt
|
return keywords_reaction_prompt
|
||||||
|
|
||||||
|
async def build_notice_block(self, chat_id: str) -> str:
|
||||||
|
"""构建notice信息块
|
||||||
|
|
||||||
|
使用全局notice管理器获取notice消息并格式化展示
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 聊天ID(即stream_id)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 格式化的notice信息文本,如果没有notice或未启用则返回空字符串
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logger.debug(f"开始构建notice块,chat_id={chat_id}")
|
||||||
|
|
||||||
|
# 检查是否启用notice in prompt
|
||||||
|
if not hasattr(global_config, 'notice'):
|
||||||
|
logger.debug("notice配置不存在")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if not global_config.notice.notice_in_prompt:
|
||||||
|
logger.debug("notice_in_prompt配置未启用")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# 使用全局notice管理器获取notice文本
|
||||||
|
from src.chat.message_manager.message_manager import message_manager
|
||||||
|
|
||||||
|
limit = getattr(global_config.notice, 'notice_prompt_limit', 5)
|
||||||
|
logger.debug(f"获取notice文本,limit={limit}")
|
||||||
|
notice_text = message_manager.get_notice_text(chat_id, limit)
|
||||||
|
|
||||||
|
if notice_text and notice_text.strip():
|
||||||
|
# 添加标题和格式化
|
||||||
|
notice_lines = []
|
||||||
|
notice_lines.append("## 📢 最近的系统通知")
|
||||||
|
notice_lines.append("")
|
||||||
|
notice_lines.append(notice_text)
|
||||||
|
notice_lines.append("")
|
||||||
|
|
||||||
|
result = "\n".join(notice_lines)
|
||||||
|
logger.info(f"notice块构建成功,chat_id={chat_id}, 长度={len(result)}")
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
logger.debug(f"没有可用的notice文本,chat_id={chat_id}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"构建notice块失败,chat_id={chat_id}: {e}", exc_info=True)
|
||||||
|
return ""
|
||||||
|
|
||||||
async def _time_and_run_task(self, coroutine, name: str) -> tuple[str, Any, float]:
|
async def _time_and_run_task(self, coroutine, name: str) -> tuple[str, Any, float]:
|
||||||
"""计时并运行异步任务的辅助函数
|
"""计时并运行异步任务的辅助函数
|
||||||
|
|
||||||
@@ -1225,7 +1278,7 @@ class DefaultReplyer:
|
|||||||
|
|
||||||
from src.chat.utils.prompt import Prompt
|
from src.chat.utils.prompt import Prompt
|
||||||
|
|
||||||
# 并行执行六个构建任务
|
# 并行执行任务
|
||||||
tasks = {
|
tasks = {
|
||||||
"expression_habits": asyncio.create_task(
|
"expression_habits": asyncio.create_task(
|
||||||
self._time_and_run_task(
|
self._time_and_run_task(
|
||||||
@@ -1253,6 +1306,9 @@ class DefaultReplyer:
|
|||||||
"cross_context",
|
"cross_context",
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
"notice_block": asyncio.create_task(
|
||||||
|
self._time_and_run_task(self.build_notice_block(chat_id), "notice_block")
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
# 设置超时
|
# 设置超时
|
||||||
@@ -1271,6 +1327,7 @@ class DefaultReplyer:
|
|||||||
"tool_info": "",
|
"tool_info": "",
|
||||||
"prompt_info": "",
|
"prompt_info": "",
|
||||||
"cross_context": "",
|
"cross_context": "",
|
||||||
|
"notice_block": "",
|
||||||
}
|
}
|
||||||
logger.info(f"为超时任务 {task_name} 提供默认值")
|
logger.info(f"为超时任务 {task_name} 提供默认值")
|
||||||
return task_name, default_values[task_name], timeout
|
return task_name, default_values[task_name], timeout
|
||||||
@@ -1303,6 +1360,7 @@ class DefaultReplyer:
|
|||||||
tool_info = results_dict["tool_info"]
|
tool_info = results_dict["tool_info"]
|
||||||
prompt_info = results_dict["prompt_info"]
|
prompt_info = results_dict["prompt_info"]
|
||||||
cross_context_block = results_dict["cross_context"]
|
cross_context_block = results_dict["cross_context"]
|
||||||
|
notice_block = results_dict["notice_block"]
|
||||||
|
|
||||||
# 检查是否为视频分析结果,并注入引导语
|
# 检查是否为视频分析结果,并注入引导语
|
||||||
if target and ("[视频内容]" in target or "好的,我将根据您提供的" in target):
|
if target and ("[视频内容]" in target or "好的,我将根据您提供的" in target):
|
||||||
@@ -1443,6 +1501,7 @@ class DefaultReplyer:
|
|||||||
tool_info_block=tool_info,
|
tool_info_block=tool_info,
|
||||||
knowledge_prompt=prompt_info,
|
knowledge_prompt=prompt_info,
|
||||||
cross_context_block=cross_context_block,
|
cross_context_block=cross_context_block,
|
||||||
|
notice_block=notice_block,
|
||||||
keywords_reaction_prompt=keywords_reaction_prompt,
|
keywords_reaction_prompt=keywords_reaction_prompt,
|
||||||
extra_info_block=extra_info_block,
|
extra_info_block=extra_info_block,
|
||||||
time_block=time_block,
|
time_block=time_block,
|
||||||
@@ -1581,6 +1640,9 @@ class DefaultReplyer:
|
|||||||
else:
|
else:
|
||||||
reply_target_block = ""
|
reply_target_block = ""
|
||||||
|
|
||||||
|
# 构建notice_block
|
||||||
|
notice_block = await self.build_notice_block(chat_id)
|
||||||
|
|
||||||
if is_group_chat:
|
if is_group_chat:
|
||||||
await global_prompt_manager.get_prompt_async("chat_target_group1")
|
await global_prompt_manager.get_prompt_async("chat_target_group1")
|
||||||
await global_prompt_manager.get_prompt_async("chat_target_group2")
|
await global_prompt_manager.get_prompt_async("chat_target_group2")
|
||||||
@@ -1612,6 +1674,7 @@ class DefaultReplyer:
|
|||||||
# 添加已构建的表达习惯和关系信息
|
# 添加已构建的表达习惯和关系信息
|
||||||
expression_habits_block=expression_habits_block,
|
expression_habits_block=expression_habits_block,
|
||||||
relation_info_block=relation_info,
|
relation_info_block=relation_info,
|
||||||
|
notice_block=notice_block,
|
||||||
bot_name=global_config.bot.nickname,
|
bot_name=global_config.bot.nickname,
|
||||||
bot_nickname=",".join(global_config.bot.alias_names) if global_config.bot.alias_names else "",
|
bot_nickname=",".join(global_config.bot.alias_names) if global_config.bot.alias_names else "",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1008,6 +1008,9 @@ async def build_readable_messages(
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
copy_messages = [msg.copy() for msg in messages]
|
copy_messages = [msg.copy() for msg in messages]
|
||||||
|
|
||||||
|
if not copy_messages:
|
||||||
|
return ""
|
||||||
|
|
||||||
if show_actions and copy_messages:
|
if show_actions and copy_messages:
|
||||||
# 获取所有消息的时间范围
|
# 获取所有消息的时间范围
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class PromptParameters:
|
|||||||
tool_info_block: str = ""
|
tool_info_block: str = ""
|
||||||
knowledge_prompt: str = ""
|
knowledge_prompt: str = ""
|
||||||
cross_context_block: str = ""
|
cross_context_block: str = ""
|
||||||
|
notice_block: str = ""
|
||||||
|
|
||||||
# 其他内容块
|
# 其他内容块
|
||||||
keywords_reaction_prompt: str = ""
|
keywords_reaction_prompt: str = ""
|
||||||
@@ -347,6 +348,8 @@ class Prompt:
|
|||||||
pre_built_params["knowledge_prompt"] = self.parameters.knowledge_prompt
|
pre_built_params["knowledge_prompt"] = self.parameters.knowledge_prompt
|
||||||
if self.parameters.cross_context_block:
|
if self.parameters.cross_context_block:
|
||||||
pre_built_params["cross_context_block"] = self.parameters.cross_context_block
|
pre_built_params["cross_context_block"] = self.parameters.cross_context_block
|
||||||
|
if self.parameters.notice_block:
|
||||||
|
pre_built_params["notice_block"] = self.parameters.notice_block
|
||||||
|
|
||||||
# 根据参数确定要构建的项
|
# 根据参数确定要构建的项
|
||||||
if self.parameters.enable_expression and not pre_built_params.get("expression_habits_block"):
|
if self.parameters.enable_expression and not pre_built_params.get("expression_habits_block"):
|
||||||
@@ -836,6 +839,7 @@ class Prompt:
|
|||||||
"relation_info_block": context_data.get("relation_info_block", ""),
|
"relation_info_block": context_data.get("relation_info_block", ""),
|
||||||
"extra_info_block": self.parameters.extra_info_block or context_data.get("extra_info_block", ""),
|
"extra_info_block": self.parameters.extra_info_block or context_data.get("extra_info_block", ""),
|
||||||
"cross_context_block": context_data.get("cross_context_block", ""),
|
"cross_context_block": context_data.get("cross_context_block", ""),
|
||||||
|
"notice_block": self.parameters.notice_block or context_data.get("notice_block", ""),
|
||||||
"identity": self.parameters.identity_block or context_data.get("identity", ""),
|
"identity": self.parameters.identity_block or context_data.get("identity", ""),
|
||||||
"action_descriptions": self.parameters.action_descriptions or context_data.get("action_descriptions", ""),
|
"action_descriptions": self.parameters.action_descriptions or context_data.get("action_descriptions", ""),
|
||||||
"sender_name": self.parameters.sender or "未知用户",
|
"sender_name": self.parameters.sender or "未知用户",
|
||||||
@@ -865,6 +869,7 @@ class Prompt:
|
|||||||
"relation_info_block": context_data.get("relation_info_block", ""),
|
"relation_info_block": context_data.get("relation_info_block", ""),
|
||||||
"extra_info_block": self.parameters.extra_info_block or context_data.get("extra_info_block", ""),
|
"extra_info_block": self.parameters.extra_info_block or context_data.get("extra_info_block", ""),
|
||||||
"cross_context_block": context_data.get("cross_context_block", ""),
|
"cross_context_block": context_data.get("cross_context_block", ""),
|
||||||
|
"notice_block": self.parameters.notice_block or context_data.get("notice_block", ""),
|
||||||
"identity": self.parameters.identity_block or context_data.get("identity", ""),
|
"identity": self.parameters.identity_block or context_data.get("identity", ""),
|
||||||
"action_descriptions": self.parameters.action_descriptions or context_data.get("action_descriptions", ""),
|
"action_descriptions": self.parameters.action_descriptions or context_data.get("action_descriptions", ""),
|
||||||
"schedule_block": self.parameters.schedule_block or context_data.get("schedule_block", ""),
|
"schedule_block": self.parameters.schedule_block or context_data.get("schedule_block", ""),
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ class DatabaseMessages(BaseDataModel):
|
|||||||
is_emoji: bool = False, # 是否为表情消息
|
is_emoji: bool = False, # 是否为表情消息
|
||||||
is_picid: bool = False, # 是否为图片消息(包含图片 ID)
|
is_picid: bool = False, # 是否为图片消息(包含图片 ID)
|
||||||
is_command: bool = False, # 是否为命令消息(如 /help)
|
is_command: bool = False, # 是否为命令消息(如 /help)
|
||||||
is_notify: bool = False, # 是否为通知消息(如系统通知)
|
is_notify: bool = False, # 是否为notice消息(如禁言、戳一戳等系统事件)
|
||||||
|
is_public_notice: bool = False, # 是否为公共notice(所有聊天可见)
|
||||||
|
notice_type: str | None = None, # notice类型(由适配器指定,如 "group_ban", "poke" 等)
|
||||||
selected_expressions: str | None = None, # 选择的表情或响应模板
|
selected_expressions: str | None = None, # 选择的表情或响应模板
|
||||||
is_read: bool = False, # 是否已读
|
is_read: bool = False, # 是否已读
|
||||||
user_id: str = "", # 用户 ID
|
user_id: str = "", # 用户 ID
|
||||||
@@ -110,6 +112,8 @@ class DatabaseMessages(BaseDataModel):
|
|||||||
self.is_picid = is_picid
|
self.is_picid = is_picid
|
||||||
self.is_command = is_command
|
self.is_command = is_command
|
||||||
self.is_notify = is_notify
|
self.is_notify = is_notify
|
||||||
|
self.is_public_notice = is_public_notice
|
||||||
|
self.notice_type = notice_type
|
||||||
self.selected_expressions = selected_expressions
|
self.selected_expressions = selected_expressions
|
||||||
self.is_read = is_read
|
self.is_read = is_read
|
||||||
self.actions = actions
|
self.actions = actions
|
||||||
@@ -180,6 +184,8 @@ class DatabaseMessages(BaseDataModel):
|
|||||||
"is_picid": self.is_picid,
|
"is_picid": self.is_picid,
|
||||||
"is_command": self.is_command,
|
"is_command": self.is_command,
|
||||||
"is_notify": self.is_notify,
|
"is_notify": self.is_notify,
|
||||||
|
"is_public_notice": self.is_public_notice,
|
||||||
|
"notice_type": self.notice_type,
|
||||||
"selected_expressions": self.selected_expressions,
|
"selected_expressions": self.selected_expressions,
|
||||||
"is_read": self.is_read,
|
"is_read": self.is_read,
|
||||||
"actions": self.actions,
|
"actions": self.actions,
|
||||||
|
|||||||
@@ -248,6 +248,8 @@ class Messages(Base):
|
|||||||
is_picid: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
is_picid: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
is_command: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
is_command: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
is_notify: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
is_notify: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
|
is_public_notice: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||||
|
notice_type: Mapped[str | None] = mapped_column(String(50), nullable=True)
|
||||||
|
|
||||||
# 兴趣度系统字段
|
# 兴趣度系统字段
|
||||||
actions: Mapped[str | None] = mapped_column(Text, nullable=True)
|
actions: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ from src.config.official_configs import (
|
|||||||
MessageReceiveConfig,
|
MessageReceiveConfig,
|
||||||
MoodConfig,
|
MoodConfig,
|
||||||
NormalChatConfig,
|
NormalChatConfig,
|
||||||
|
NoticeConfig,
|
||||||
PermissionConfig,
|
PermissionConfig,
|
||||||
PersonalityConfig,
|
PersonalityConfig,
|
||||||
PlanningSystemConfig,
|
PlanningSystemConfig,
|
||||||
@@ -378,6 +379,7 @@ class Config(ValidatedConfigBase):
|
|||||||
personality: PersonalityConfig = Field(..., description="个性配置")
|
personality: PersonalityConfig = Field(..., description="个性配置")
|
||||||
chat: ChatConfig = Field(..., description="聊天配置")
|
chat: ChatConfig = Field(..., description="聊天配置")
|
||||||
message_receive: MessageReceiveConfig = Field(..., description="消息接收配置")
|
message_receive: MessageReceiveConfig = Field(..., description="消息接收配置")
|
||||||
|
notice: NoticeConfig = Field(..., description="Notice消息配置")
|
||||||
normal_chat: NormalChatConfig = Field(..., description="普通聊天配置")
|
normal_chat: NormalChatConfig = Field(..., description="普通聊天配置")
|
||||||
emoji: EmojiConfig = Field(..., description="表情配置")
|
emoji: EmojiConfig = Field(..., description="表情配置")
|
||||||
expression: ExpressionConfig = Field(..., description="表达配置")
|
expression: ExpressionConfig = Field(..., description="表达配置")
|
||||||
|
|||||||
@@ -150,6 +150,17 @@ class MessageReceiveConfig(ValidatedConfigBase):
|
|||||||
ban_msgs_regex: list[str] = Field(default_factory=lambda: [], description="禁用消息正则列表")
|
ban_msgs_regex: list[str] = Field(default_factory=lambda: [], description="禁用消息正则列表")
|
||||||
|
|
||||||
|
|
||||||
|
class NoticeConfig(ValidatedConfigBase):
|
||||||
|
"""Notice消息配置类"""
|
||||||
|
|
||||||
|
enable_notice_trigger_chat: bool = Field(default=False, description="是否允许notice消息触发聊天流程")
|
||||||
|
notice_in_prompt: bool = Field(default=True, description="是否在提示词中展示最近的notice消息")
|
||||||
|
notice_prompt_limit: int = Field(default=5, ge=1, le=20, description="在提示词中展示的最大notice数量")
|
||||||
|
notice_time_window: int = Field(default=3600, ge=60, le=86400, description="notice时间窗口(秒)")
|
||||||
|
max_notices_per_chat: int = Field(default=30, ge=10, le=100, description="每个聊天保留的notice数量上限")
|
||||||
|
notice_retention_time: int = Field(default=86400, ge=3600, le=604800, description="notice保留时间(秒)")
|
||||||
|
|
||||||
|
|
||||||
class NormalChatConfig(ValidatedConfigBase):
|
class NormalChatConfig(ValidatedConfigBase):
|
||||||
"""普通聊天配置类"""
|
"""普通聊天配置类"""
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,38 @@ class NoticeHandler:
|
|||||||
group_name=group_name,
|
group_name=group_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 准备additional_config,包含notice标志
|
||||||
|
notice_config = {
|
||||||
|
"is_notice": system_notice, # 禁言/解禁是系统通知
|
||||||
|
"is_public_notice": False, # 群内notice,非公共
|
||||||
|
"target_id": target_id, # 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁
|
||||||
|
}
|
||||||
|
|
||||||
|
# 根据notice_type设置notice_type字段
|
||||||
|
if system_notice:
|
||||||
|
sub_type = raw_message.get("sub_type")
|
||||||
|
if notice_type == NoticeType.group_ban:
|
||||||
|
if sub_type == NoticeType.GroupBan.ban:
|
||||||
|
user_id_in_ban = raw_message.get("user_id")
|
||||||
|
if user_id_in_ban == 0:
|
||||||
|
notice_config["notice_type"] = "group_whole_ban"
|
||||||
|
else:
|
||||||
|
notice_config["notice_type"] = "group_ban"
|
||||||
|
elif sub_type == NoticeType.GroupBan.lift_ban:
|
||||||
|
user_id_in_ban = raw_message.get("user_id")
|
||||||
|
if user_id_in_ban == 0:
|
||||||
|
notice_config["notice_type"] = "group_whole_lift_ban"
|
||||||
|
else:
|
||||||
|
notice_config["notice_type"] = "group_lift_ban"
|
||||||
|
elif notice_type == NoticeType.notify:
|
||||||
|
sub_type = raw_message.get("sub_type")
|
||||||
|
if sub_type == NoticeType.Notify.poke:
|
||||||
|
notice_config["notice_type"] = "poke"
|
||||||
|
notice_config["is_notice"] = True # 戳一戳也是notice
|
||||||
|
elif notice_type == NoticeType.group_msg_emoji_like:
|
||||||
|
notice_config["notice_type"] = "emoji_like"
|
||||||
|
notice_config["is_notice"] = True # 表情回复也是notice
|
||||||
|
|
||||||
message_info: BaseMessageInfo = BaseMessageInfo(
|
message_info: BaseMessageInfo = BaseMessageInfo(
|
||||||
platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
|
platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
|
||||||
message_id="notice",
|
message_id="notice",
|
||||||
@@ -191,7 +223,7 @@ class NoticeHandler:
|
|||||||
content_format=["text", "notify"],
|
content_format=["text", "notify"],
|
||||||
accept_format=ACCEPT_FORMAT,
|
accept_format=ACCEPT_FORMAT,
|
||||||
),
|
),
|
||||||
additional_config={"target_id": target_id}, # 在这里塞了一个target_id,方便mmc那边知道被戳的人是谁
|
additional_config=notice_config, # 字典而不是JSON字符串
|
||||||
)
|
)
|
||||||
|
|
||||||
message_base: MessageBase = MessageBase(
|
message_base: MessageBase = MessageBase(
|
||||||
@@ -504,6 +536,13 @@ class NoticeHandler:
|
|||||||
group_name=group_name,
|
group_name=group_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 准备notice标志
|
||||||
|
notice_config = {
|
||||||
|
"is_notice": True,
|
||||||
|
"is_public_notice": False,
|
||||||
|
"notice_type": "group_lift_ban" if user_id != 0 else "group_whole_lift_ban",
|
||||||
|
}
|
||||||
|
|
||||||
message_info: BaseMessageInfo = BaseMessageInfo(
|
message_info: BaseMessageInfo = BaseMessageInfo(
|
||||||
platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
|
platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
|
||||||
message_id="notice",
|
message_id="notice",
|
||||||
@@ -512,6 +551,7 @@ class NoticeHandler:
|
|||||||
group_info=group_info,
|
group_info=group_info,
|
||||||
template_info=None,
|
template_info=None,
|
||||||
format_info=None,
|
format_info=None,
|
||||||
|
additional_config=notice_config, # 字典而不是JSON字符串
|
||||||
)
|
)
|
||||||
|
|
||||||
message_base: MessageBase = MessageBase(
|
message_base: MessageBase = MessageBase(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[inner]
|
[inner]
|
||||||
version = "7.3.1"
|
version = "7.3.2"
|
||||||
|
|
||||||
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
||||||
#如果你想要修改配置文件,请递增version的值
|
#如果你想要修改配置文件,请递增version的值
|
||||||
@@ -149,6 +149,14 @@ ban_msgs_regex = [
|
|||||||
#"\\d{4}-\\d{2}-\\d{2}", # 匹配日期
|
#"\\d{4}-\\d{2}-\\d{2}", # 匹配日期
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[notice] # Notice消息配置
|
||||||
|
enable_notice_trigger_chat = false # 是否允许notice消息触发聊天流程(默认关闭,notice只会被记录但不会触发回复)
|
||||||
|
notice_in_prompt = true # 是否在提示词中展示最近的notice消息
|
||||||
|
notice_prompt_limit = 5 # 在提示词中展示的最大notice数量
|
||||||
|
notice_time_window = 3600 # notice时间窗口(秒),只有这个时间范围内的notice会在提示词中展示,默认1小时
|
||||||
|
max_notices_per_chat = 30 # 每个聊天保留的notice数量上限
|
||||||
|
notice_retention_time = 86400 # notice保留时间(秒),默认24小时
|
||||||
|
|
||||||
[anti_prompt_injection] # LLM反注入系统配置
|
[anti_prompt_injection] # LLM反注入系统配置
|
||||||
enabled = false # 是否启用反注入系统
|
enabled = false # 是否启用反注入系统
|
||||||
enabled_rules = false # 是否启用规则检测
|
enabled_rules = false # 是否启用规则检测
|
||||||
|
|||||||
Reference in New Issue
Block a user