Merge branch 'dev' of https://github.com/MoFox-Studio/MoFox_Bot into dev
This commit is contained in:
@@ -206,7 +206,7 @@ class CycleProcessor:
|
|||||||
|
|
||||||
# 触发规划前事件
|
# 触发规划前事件
|
||||||
result = await event_manager.trigger_event(
|
result = await event_manager.trigger_event(
|
||||||
EventType.ON_PLAN, plugin_name="SYSTEM", stream_id=self.context.chat_stream
|
EventType.ON_PLAN, permission_group="SYSTEM", stream_id=self.context.chat_stream
|
||||||
)
|
)
|
||||||
if not result.all_continue_process():
|
if not result.all_continue_process():
|
||||||
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
|
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于规划前中断了内容生成")
|
||||||
|
|||||||
144
src/chat/frequency_analyzer/analyzer.py
Normal file
144
src/chat/frequency_analyzer/analyzer.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
"""
|
||||||
|
Chat Frequency Analyzer
|
||||||
|
=======================
|
||||||
|
|
||||||
|
本模块负责分析用户的聊天时间戳,以识别出他们最活跃的聊天时段(高峰时段)。
|
||||||
|
|
||||||
|
核心功能:
|
||||||
|
- 使用滑动窗口算法来检测时间戳集中的区域。
|
||||||
|
- 提供接口查询指定用户当前是否处于其聊天高峰时段内。
|
||||||
|
- 结果会被缓存以提高性能。
|
||||||
|
|
||||||
|
可配置参数:
|
||||||
|
- ANALYSIS_WINDOW_HOURS: 用于分析的时间窗口大小(小时)。
|
||||||
|
- MIN_CHATS_FOR_PEAK: 在一个窗口内需要多少次聊天才能被认为是高峰时段。
|
||||||
|
- MIN_GAP_BETWEEN_PEAKS_HOURS: 两个独立高峰时段之间的最小间隔(小时)。
|
||||||
|
"""
|
||||||
|
import time as time_module
|
||||||
|
from datetime import datetime, timedelta, time
|
||||||
|
from typing import List, Tuple, Optional
|
||||||
|
|
||||||
|
from .tracker import chat_frequency_tracker
|
||||||
|
|
||||||
|
# --- 可配置参数 ---
|
||||||
|
# 用于分析的时间窗口大小(小时)
|
||||||
|
ANALYSIS_WINDOW_HOURS = 2
|
||||||
|
# 触发高峰时段所需的最小聊天次数
|
||||||
|
MIN_CHATS_FOR_PEAK = 4
|
||||||
|
# 两个独立高峰时段之间的最小间隔(小时)
|
||||||
|
MIN_GAP_BETWEEN_PEAKS_HOURS = 1
|
||||||
|
|
||||||
|
|
||||||
|
class ChatFrequencyAnalyzer:
|
||||||
|
"""
|
||||||
|
分析聊天时间戳,以识别用户的高频聊天时段。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 缓存分析结果,避免重复计算
|
||||||
|
# 格式: { "chat_id": (timestamp_of_analysis, [peak_windows]) }
|
||||||
|
self._analysis_cache: dict[str, tuple[float, list[tuple[time, time]]]] = {}
|
||||||
|
self._cache_ttl_seconds = 60 * 30 # 缓存30分钟
|
||||||
|
|
||||||
|
def _find_peak_windows(self, timestamps: List[float]) -> List[Tuple[datetime, datetime]]:
|
||||||
|
"""
|
||||||
|
使用滑动窗口算法来识别时间戳列表中的高峰时段。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timestamps (List[float]): 按时间排序的聊天时间戳。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[datetime, datetime]]: 识别出的高峰时段列表,每个元组代表一个时间窗口的开始和结束。
|
||||||
|
"""
|
||||||
|
if len(timestamps) < MIN_CHATS_FOR_PEAK:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 将时间戳转换为 datetime 对象
|
||||||
|
datetimes = [datetime.fromtimestamp(ts) for ts in timestamps]
|
||||||
|
datetimes.sort()
|
||||||
|
|
||||||
|
peak_windows: List[Tuple[datetime, datetime]] = []
|
||||||
|
window_start_idx = 0
|
||||||
|
|
||||||
|
for i in range(len(datetimes)):
|
||||||
|
# 移动窗口的起始点
|
||||||
|
while datetimes[i] - datetimes[window_start_idx] > timedelta(hours=ANALYSIS_WINDOW_HOURS):
|
||||||
|
window_start_idx += 1
|
||||||
|
|
||||||
|
# 检查当前窗口是否满足高峰条件
|
||||||
|
if i - window_start_idx + 1 >= MIN_CHATS_FOR_PEAK:
|
||||||
|
current_window_start = datetimes[window_start_idx]
|
||||||
|
current_window_end = datetimes[i]
|
||||||
|
|
||||||
|
# 合并重叠或相邻的高峰时段
|
||||||
|
if peak_windows and current_window_start - peak_windows[-1][1] < timedelta(hours=MIN_GAP_BETWEEN_PEAKS_HOURS):
|
||||||
|
# 扩展上一个窗口的结束时间
|
||||||
|
peak_windows[-1] = (peak_windows[-1][0], current_window_end)
|
||||||
|
else:
|
||||||
|
peak_windows.append((current_window_start, current_window_end))
|
||||||
|
|
||||||
|
return peak_windows
|
||||||
|
|
||||||
|
def get_peak_chat_times(self, chat_id: str) -> List[Tuple[time, time]]:
|
||||||
|
"""
|
||||||
|
获取指定用户的高峰聊天时间段。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (str): 聊天标识符。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Tuple[time, time]]: 高峰时段的列表,每个元组包含开始和结束时间 (time 对象)。
|
||||||
|
"""
|
||||||
|
# 检查缓存
|
||||||
|
cached_timestamp, cached_windows = self._analysis_cache.get(chat_id, (0, []))
|
||||||
|
if time_module.time() - cached_timestamp < self._cache_ttl_seconds:
|
||||||
|
return cached_windows
|
||||||
|
|
||||||
|
timestamps = chat_frequency_tracker.get_timestamps_for_chat(chat_id)
|
||||||
|
if not timestamps:
|
||||||
|
return []
|
||||||
|
|
||||||
|
peak_datetime_windows = self._find_peak_windows(timestamps)
|
||||||
|
|
||||||
|
# 将 datetime 窗口转换为 time 窗口,并进行归一化处理
|
||||||
|
peak_time_windows = []
|
||||||
|
for start_dt, end_dt in peak_datetime_windows:
|
||||||
|
# TODO:这里可以添加更复杂的逻辑来处理跨天的平均时间
|
||||||
|
# 为简化,我们直接使用窗口的起止时间
|
||||||
|
peak_time_windows.append((start_dt.time(), end_dt.time()))
|
||||||
|
|
||||||
|
# 更新缓存
|
||||||
|
self._analysis_cache[chat_id] = (time_module.time(), peak_time_windows)
|
||||||
|
|
||||||
|
return peak_time_windows
|
||||||
|
|
||||||
|
def is_in_peak_time(self, chat_id: str, now: Optional[datetime] = None) -> bool:
|
||||||
|
"""
|
||||||
|
检查当前时间是否处于用户的高峰聊天时段内。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (str): 聊天标识符。
|
||||||
|
now (Optional[datetime]): 要检查的时间,默认为当前时间。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 如果处于高峰时段则返回 True,否则返回 False。
|
||||||
|
"""
|
||||||
|
if now is None:
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
now_time = now.time()
|
||||||
|
peak_times = self.get_peak_chat_times(chat_id)
|
||||||
|
|
||||||
|
for start_time, end_time in peak_times:
|
||||||
|
if start_time <= end_time: # 同一天
|
||||||
|
if start_time <= now_time <= end_time:
|
||||||
|
return True
|
||||||
|
else: # 跨天
|
||||||
|
if now_time >= start_time or now_time <= end_time:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# 创建一个全局单例
|
||||||
|
chat_frequency_analyzer = ChatFrequencyAnalyzer()
|
||||||
77
src/chat/frequency_analyzer/tracker.py
Normal file
77
src/chat/frequency_analyzer/tracker.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import orjson
|
||||||
|
import time
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
|
||||||
|
# 数据存储路径
|
||||||
|
DATA_DIR = Path("data/frequency_analyzer")
|
||||||
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
TRACKER_FILE = DATA_DIR / "chat_timestamps.json"
|
||||||
|
|
||||||
|
logger = get_logger("ChatFrequencyTracker")
|
||||||
|
|
||||||
|
|
||||||
|
class ChatFrequencyTracker:
|
||||||
|
"""
|
||||||
|
负责跟踪和存储用户聊天启动时间戳。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._timestamps: Dict[str, List[float]] = self._load_timestamps()
|
||||||
|
|
||||||
|
def _load_timestamps(self) -> Dict[str, List[float]]:
|
||||||
|
"""从本地文件加载时间戳数据。"""
|
||||||
|
if not TRACKER_FILE.exists():
|
||||||
|
return {}
|
||||||
|
try:
|
||||||
|
with open(TRACKER_FILE, "rb") as f:
|
||||||
|
data = orjson.loads(f.read())
|
||||||
|
logger.info(f"成功从 {TRACKER_FILE} 加载了聊天时间戳数据。")
|
||||||
|
return data
|
||||||
|
except orjson.JSONDecodeError:
|
||||||
|
logger.warning(f"无法解析 {TRACKER_FILE},将创建一个新的空数据文件。")
|
||||||
|
return {}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"加载聊天时间戳数据时发生未知错误: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _save_timestamps(self):
|
||||||
|
"""将当前的时间戳数据保存到本地文件。"""
|
||||||
|
try:
|
||||||
|
with open(TRACKER_FILE, "wb") as f:
|
||||||
|
f.write(orjson.dumps(self._timestamps))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存聊天时间戳数据到 {TRACKER_FILE} 时失败: {e}")
|
||||||
|
|
||||||
|
def record_chat_start(self, chat_id: str):
|
||||||
|
"""
|
||||||
|
记录一次聊天会话的开始。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (str): 唯一的聊天标识符 (例如,用户ID)。
|
||||||
|
"""
|
||||||
|
now = time.time()
|
||||||
|
if chat_id not in self._timestamps:
|
||||||
|
self._timestamps[chat_id] = []
|
||||||
|
|
||||||
|
self._timestamps[chat_id].append(now)
|
||||||
|
logger.debug(f"为 chat_id '{chat_id}' 记录了新的聊天时间: {now}")
|
||||||
|
self._save_timestamps()
|
||||||
|
|
||||||
|
def get_timestamps_for_chat(self, chat_id: str) -> Optional[List[float]]:
|
||||||
|
"""
|
||||||
|
获取指定聊天的所有时间戳记录。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (str): 聊天标识符。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[List[float]]: 时间戳列表,如果不存在则返回 None。
|
||||||
|
"""
|
||||||
|
return self._timestamps.get(chat_id)
|
||||||
|
|
||||||
|
|
||||||
|
# 创建一个全局单例
|
||||||
|
chat_frequency_tracker = ChatFrequencyTracker()
|
||||||
119
src/chat/frequency_analyzer/trigger.py
Normal file
119
src/chat/frequency_analyzer/trigger.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
"""
|
||||||
|
Frequency-Based Proactive Trigger
|
||||||
|
=================================
|
||||||
|
|
||||||
|
本模块实现了一个周期性任务,用于根据用户的聊天频率来智能地触发主动思考。
|
||||||
|
|
||||||
|
核心功能:
|
||||||
|
- 定期运行,检查所有已知的私聊用户。
|
||||||
|
- 调用 ChatFrequencyAnalyzer 判断当前是否处于用户的高峰聊天时段。
|
||||||
|
- 如果满足条件(高峰时段、角色清醒、聊天循环空闲),则触发一次主动思考。
|
||||||
|
- 包含冷却机制,以避免在同一个高峰时段内重复打扰用户。
|
||||||
|
|
||||||
|
可配置参数:
|
||||||
|
- TRIGGER_CHECK_INTERVAL_SECONDS: 触发器检查的周期(秒)。
|
||||||
|
- COOLDOWN_HOURS: 在同一个高峰时段内触发一次后的冷却时间(小时)。
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
from src.common.logger import get_logger
|
||||||
|
from src.chat.chat_loop.proactive.events import ProactiveTriggerEvent
|
||||||
|
from src.chat.heart_flow.heartflow import heartflow
|
||||||
|
from src.chat.chat_loop.sleep_manager.sleep_manager import SleepManager
|
||||||
|
from .analyzer import chat_frequency_analyzer
|
||||||
|
|
||||||
|
logger = get_logger("FrequencyBasedTrigger")
|
||||||
|
|
||||||
|
# --- 可配置参数 ---
|
||||||
|
# 触发器检查周期(秒)
|
||||||
|
TRIGGER_CHECK_INTERVAL_SECONDS = 60 * 5 # 5分钟
|
||||||
|
# 冷却时间(小时),确保在一个高峰时段只触发一次
|
||||||
|
COOLDOWN_HOURS = 3
|
||||||
|
|
||||||
|
|
||||||
|
class FrequencyBasedTrigger:
|
||||||
|
"""
|
||||||
|
一个周期性任务,根据聊天频率分析结果来触发主动思考。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, sleep_manager: SleepManager):
|
||||||
|
self._sleep_manager = sleep_manager
|
||||||
|
self._task: Optional[asyncio.Task] = None
|
||||||
|
# 记录上次为用户触发的时间,用于冷却控制
|
||||||
|
# 格式: { "chat_id": timestamp }
|
||||||
|
self._last_triggered: Dict[str, float] = {}
|
||||||
|
|
||||||
|
async def _run_trigger_cycle(self):
|
||||||
|
"""触发器的主要循环逻辑。"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(TRIGGER_CHECK_INTERVAL_SECONDS)
|
||||||
|
logger.debug("开始执行频率触发器检查...")
|
||||||
|
|
||||||
|
# 1. 检查角色是否清醒
|
||||||
|
if self._sleep_manager.is_sleeping():
|
||||||
|
logger.debug("角色正在睡眠,跳过本次频率触发检查。")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 2. 获取所有已知的聊天ID
|
||||||
|
# 【注意】这里我们假设所有 subheartflow 的 ID 就是 chat_id
|
||||||
|
all_chat_ids = list(heartflow.subheartflows.keys())
|
||||||
|
if not all_chat_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
for chat_id in all_chat_ids:
|
||||||
|
# 3. 检查是否处于冷却时间内
|
||||||
|
last_triggered_time = self._last_triggered.get(chat_id, 0)
|
||||||
|
if time.time() - last_triggered_time < COOLDOWN_HOURS * 3600:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 4. 检查当前是否是该用户的高峰聊天时间
|
||||||
|
if chat_frequency_analyzer.is_in_peak_time(chat_id, now):
|
||||||
|
|
||||||
|
sub_heartflow = await heartflow.get_or_create_subheartflow(chat_id)
|
||||||
|
if not sub_heartflow:
|
||||||
|
logger.warning(f"无法为 {chat_id} 获取或创建 sub_heartflow。")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 5. 检查用户当前是否已有活跃的思考或回复任务
|
||||||
|
cycle_detail = sub_heartflow.heart_fc_instance.context.current_cycle_detail
|
||||||
|
if cycle_detail and not cycle_detail.end_time:
|
||||||
|
logger.debug(f"用户 {chat_id} 的聊天循环正忙(仍在周期 {cycle_detail.cycle_id} 中),本次不触发。")
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"检测到用户 {chat_id} 处于聊天高峰期,且聊天循环空闲,准备触发主动思考。")
|
||||||
|
|
||||||
|
# 6. 直接调用 proactive_thinker
|
||||||
|
event = ProactiveTriggerEvent(
|
||||||
|
source="frequency_analyzer",
|
||||||
|
reason=f"User is in a high-frequency chat period."
|
||||||
|
)
|
||||||
|
await sub_heartflow.heart_fc_instance.proactive_thinker.think(event)
|
||||||
|
|
||||||
|
# 7. 更新触发时间,进入冷却
|
||||||
|
self._last_triggered[chat_id] = time.time()
|
||||||
|
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
logger.info("频率触发器任务被取消。")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"频率触发器循环发生未知错误: {e}", exc_info=True)
|
||||||
|
# 发生错误后,等待更长时间再重试,避免刷屏
|
||||||
|
await asyncio.sleep(TRIGGER_CHECK_INTERVAL_SECONDS * 2)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""启动触发器任务。"""
|
||||||
|
if self._task is None or self._task.done():
|
||||||
|
self._task = asyncio.create_task(self._run_trigger_cycle())
|
||||||
|
logger.info("基于聊天频率的主动思考触发器已启动。")
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""停止触发器任务。"""
|
||||||
|
if self._task and not self._task.done():
|
||||||
|
self._task.cancel()
|
||||||
|
logger.info("基于聊天频率的主动思考触发器已停止。")
|
||||||
@@ -450,7 +450,7 @@ class ChatBot:
|
|||||||
logger.info(f"命令处理完成,跳过后续消息处理: {cmd_result}")
|
logger.info(f"命令处理完成,跳过后续消息处理: {cmd_result}")
|
||||||
return
|
return
|
||||||
|
|
||||||
result = await event_manager.trigger_event(EventType.ON_MESSAGE, plugin_name="SYSTEM", message=message)
|
result = await event_manager.trigger_event(EventType.ON_MESSAGE, permission_group="SYSTEM", message=message)
|
||||||
if not result.all_continue_process():
|
if not result.all_continue_process():
|
||||||
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于消息到达时取消了消息处理")
|
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于消息到达时取消了消息处理")
|
||||||
|
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ def init_prompt():
|
|||||||
Prompt(
|
Prompt(
|
||||||
"""
|
"""
|
||||||
{name_block}
|
{name_block}
|
||||||
|
{personality_block}
|
||||||
|
|
||||||
{chat_context_description},{time_block},现在请你根据以下聊天内容,选择一个或多个合适的action。如果没有合适的action,请选择no_action。,
|
{chat_context_description},{time_block},现在请你根据以下聊天内容,选择一个或多个合适的action。如果没有合适的action,请选择no_action。,
|
||||||
{chat_content_block}
|
{chat_content_block}
|
||||||
@@ -423,6 +424,19 @@ class ActionPlanner:
|
|||||||
bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else ""
|
bot_nickname = f",也有人叫你{','.join(global_config.bot.alias_names)}" if global_config.bot.alias_names else ""
|
||||||
name_block = f"你的名字是{bot_name}{bot_nickname},请注意哪些是你自己的发言。"
|
name_block = f"你的名字是{bot_name}{bot_nickname},请注意哪些是你自己的发言。"
|
||||||
|
|
||||||
|
# 构建人格信息块(仅在启用时)
|
||||||
|
personality_block = ""
|
||||||
|
if global_config.chat.include_personality:
|
||||||
|
personality_core = global_config.personality.personality_core
|
||||||
|
personality_side = global_config.personality.personality_side
|
||||||
|
if personality_core or personality_side:
|
||||||
|
personality_parts = []
|
||||||
|
if personality_core:
|
||||||
|
personality_parts.append(f"核心人格:{personality_core}")
|
||||||
|
if personality_side:
|
||||||
|
personality_parts.append(f"人格侧面:{personality_side}")
|
||||||
|
personality_block = "你的人格特征是:" + ";".join(personality_parts)
|
||||||
|
|
||||||
planner_prompt_template = await global_prompt_manager.get_prompt_async("sub_planner_prompt")
|
planner_prompt_template = await global_prompt_manager.get_prompt_async("sub_planner_prompt")
|
||||||
prompt = planner_prompt_template.format(
|
prompt = planner_prompt_template.format(
|
||||||
time_block=time_block,
|
time_block=time_block,
|
||||||
@@ -432,6 +446,7 @@ class ActionPlanner:
|
|||||||
action_options_text=action_options_block,
|
action_options_text=action_options_block,
|
||||||
moderation_prompt=moderation_prompt_block,
|
moderation_prompt=moderation_prompt_block,
|
||||||
name_block=name_block,
|
name_block=name_block,
|
||||||
|
personality_block=personality_block,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"构建小脑提示词时出错: {e}\n{traceback.format_exc()}")
|
logger.error(f"构建小脑提示词时出错: {e}\n{traceback.format_exc()}")
|
||||||
|
|||||||
@@ -286,7 +286,7 @@ class DefaultReplyer:
|
|||||||
# 触发 POST_LLM 事件(请求 LLM 之前)
|
# 触发 POST_LLM 事件(请求 LLM 之前)
|
||||||
if not from_plugin:
|
if not from_plugin:
|
||||||
result = await event_manager.trigger_event(
|
result = await event_manager.trigger_event(
|
||||||
EventType.POST_LLM, plugin_name="SYSTEM", prompt=prompt, stream_id=stream_id
|
EventType.POST_LLM, permission_group="SYSTEM", prompt=prompt, stream_id=stream_id
|
||||||
)
|
)
|
||||||
if not result.all_continue_process():
|
if not result.all_continue_process():
|
||||||
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于请求前中断了内容生成")
|
raise UserWarning(f"插件{result.get_summary().get('stopped_handlers', '')}于请求前中断了内容生成")
|
||||||
@@ -310,7 +310,7 @@ class DefaultReplyer:
|
|||||||
if not from_plugin:
|
if not from_plugin:
|
||||||
result = await event_manager.trigger_event(
|
result = await event_manager.trigger_event(
|
||||||
EventType.AFTER_LLM,
|
EventType.AFTER_LLM,
|
||||||
plugin_name="SYSTEM",
|
permission_group="SYSTEM",
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
llm_response=llm_response,
|
llm_response=llm_response,
|
||||||
stream_id=stream_id,
|
stream_id=stream_id,
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class ChatConfig(ValidatedConfigBase):
|
|||||||
)
|
)
|
||||||
delta_sigma: int = Field(default=120, description="采用正态分布随机时间间隔")
|
delta_sigma: int = Field(default=120, description="采用正态分布随机时间间隔")
|
||||||
planner_size: float = Field(default=5.0, ge=1.0, description="小脑(sub-planner)的尺寸,决定每个小脑处理多少个action")
|
planner_size: float = Field(default=5.0, ge=1.0, description="小脑(sub-planner)的尺寸,决定每个小脑处理多少个action")
|
||||||
|
include_personality: bool = Field(default=False, description="是否在小脑决策中包含角色人设信息")
|
||||||
|
|
||||||
def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float:
|
def get_current_talk_frequency(self, chat_stream_id: Optional[str] = None) -> float:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class MainSystem:
|
|||||||
# 停止消息重组器
|
# 停止消息重组器
|
||||||
from src.plugin_system.core.event_manager import event_manager
|
from src.plugin_system.core.event_manager import event_manager
|
||||||
from src.plugin_system import EventType
|
from src.plugin_system import EventType
|
||||||
asyncio.run(event_manager.trigger_event(EventType.ON_STOP,plugin_name="SYSTEM"))
|
asyncio.run(event_manager.trigger_event(EventType.ON_STOP,permission_group="SYSTEM"))
|
||||||
from src.utils.message_chunker import reassembler
|
from src.utils.message_chunker import reassembler
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ MoFox_Bot(第三方修改版)
|
|||||||
logger.info("日程表管理器初始化成功。")
|
logger.info("日程表管理器初始化成功。")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await event_manager.trigger_event(EventType.ON_START, plugin_name="SYSTEM")
|
await event_manager.trigger_event(EventType.ON_START, permission_group="SYSTEM")
|
||||||
init_time = int(1000 * (time.time() - init_start_time))
|
init_time = int(1000 * (time.time() - init_start_time))
|
||||||
logger.info(f"初始化完成,神经元放电{init_time}次")
|
logger.info(f"初始化完成,神经元放电{init_time}次")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ class PlusCommand(ABC):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
# 检查是否为群聊消息
|
# 检查是否为群聊消息
|
||||||
is_group = hasattr(self.message, "is_group_message") and self.message.is_group_message
|
is_group = hasattr(self.message.message_info, "group_info") and self.message.message_info.group_info
|
||||||
|
|
||||||
if self.chat_type_allow == ChatType.GROUP and is_group:
|
if self.chat_type_allow == ChatType.GROUP and is_group:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ class EventManager:
|
|||||||
return {handler.handler_name: handler for handler in event.subscribers}
|
return {handler.handler_name: handler for handler in event.subscribers}
|
||||||
|
|
||||||
async def trigger_event(
|
async def trigger_event(
|
||||||
self, event_name: Union[EventType, str], plugin_name: Optional[str] = "", **kwargs
|
self, event_name: Union[EventType, str], permission_group: Optional[str] = "", **kwargs
|
||||||
) -> Optional[HandlerResultsCollection]:
|
) -> Optional[HandlerResultsCollection]:
|
||||||
"""触发指定事件
|
"""触发指定事件
|
||||||
|
|
||||||
@@ -309,11 +309,11 @@ class EventManager:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# 插件白名单检查
|
# 插件白名单检查
|
||||||
if event.allowed_triggers and not plugin_name:
|
if event.allowed_triggers and not permission_group:
|
||||||
logger.warning(f"事件 {event_name} 存在触发者白名单,缺少plugin_name无法验证权限,已拒绝触发!")
|
logger.warning(f"事件 {event_name} 存在触发者白名单,缺少plugin_name无法验证权限,已拒绝触发!")
|
||||||
return None
|
return None
|
||||||
elif event.allowed_triggers and plugin_name not in event.allowed_triggers:
|
elif event.allowed_triggers and permission_group not in event.allowed_triggers:
|
||||||
logger.warning(f"插件 {plugin_name} 没有权限触发事件 {event_name},已拒绝触发!")
|
logger.warning(f"插件 {permission_group} 没有权限触发事件 {event_name},已拒绝触发!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await event.activate(params)
|
return await event.activate(params)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class AtAction(BaseAction):
|
|||||||
reply_to=reply_to,
|
reply_to=reply_to,
|
||||||
extra_info=extra_info,
|
extra_info=extra_info,
|
||||||
enable_tool=False, # 艾特回复通常不需要工具调用
|
enable_tool=False, # 艾特回复通常不需要工具调用
|
||||||
from_plugin=True # 标识来自插件
|
from_plugin=False
|
||||||
)
|
)
|
||||||
|
|
||||||
if success and llm_response:
|
if success and llm_response:
|
||||||
|
|||||||
@@ -1 +1,254 @@
|
|||||||
PLUGIN_NAME = "napcat_adapter"
|
PLUGIN_NAME = "napcat_adapter"
|
||||||
|
|
||||||
|
QQ_FACE: dict = {
|
||||||
|
"0": "[表情:惊讶]",
|
||||||
|
"1": "[表情:撇嘴]",
|
||||||
|
"2": "[表情:色]",
|
||||||
|
"3": "[表情:发呆]",
|
||||||
|
"4": "[表情:得意]",
|
||||||
|
"5": "[表情:流泪]",
|
||||||
|
"6": "[表情:害羞]",
|
||||||
|
"7": "[表情:闭嘴]",
|
||||||
|
"8": "[表情:睡]",
|
||||||
|
"9": "[表情:大哭]",
|
||||||
|
"10": "[表情:尴尬]",
|
||||||
|
"11": "[表情:发怒]",
|
||||||
|
"12": "[表情:调皮]",
|
||||||
|
"13": "[表情:呲牙]",
|
||||||
|
"14": "[表情:微笑]",
|
||||||
|
"15": "[表情:难过]",
|
||||||
|
"16": "[表情:酷]",
|
||||||
|
"18": "[表情:抓狂]",
|
||||||
|
"19": "[表情:吐]",
|
||||||
|
"20": "[表情:偷笑]",
|
||||||
|
"21": "[表情:可爱]",
|
||||||
|
"22": "[表情:白眼]",
|
||||||
|
"23": "[表情:傲慢]",
|
||||||
|
"24": "[表情:饥饿]",
|
||||||
|
"25": "[表情:困]",
|
||||||
|
"26": "[表情:惊恐]",
|
||||||
|
"27": "[表情:流汗]",
|
||||||
|
"28": "[表情:憨笑]",
|
||||||
|
"29": "[表情:悠闲]",
|
||||||
|
"30": "[表情:奋斗]",
|
||||||
|
"31": "[表情:咒骂]",
|
||||||
|
"32": "[表情:疑问]",
|
||||||
|
"33": "[表情: 嘘]",
|
||||||
|
"34": "[表情:晕]",
|
||||||
|
"35": "[表情:折磨]",
|
||||||
|
"36": "[表情:衰]",
|
||||||
|
"37": "[表情:骷髅]",
|
||||||
|
"38": "[表情:敲打]",
|
||||||
|
"39": "[表情:再见]",
|
||||||
|
"41": "[表情:发抖]",
|
||||||
|
"42": "[表情:爱情]",
|
||||||
|
"43": "[表情:跳跳]",
|
||||||
|
"46": "[表情:猪头]",
|
||||||
|
"49": "[表情:拥抱]",
|
||||||
|
"53": "[表情:蛋糕]",
|
||||||
|
"56": "[表情:刀]",
|
||||||
|
"59": "[表情:便便]",
|
||||||
|
"60": "[表情:咖啡]",
|
||||||
|
"63": "[表情:玫瑰]",
|
||||||
|
"64": "[表情:凋谢]",
|
||||||
|
"66": "[表情:爱心]",
|
||||||
|
"67": "[表情:心碎]",
|
||||||
|
"74": "[表情:太阳]",
|
||||||
|
"75": "[表情:月亮]",
|
||||||
|
"76": "[表情:赞]",
|
||||||
|
"77": "[表情:踩]",
|
||||||
|
"78": "[表情:握手]",
|
||||||
|
"79": "[表情:胜利]",
|
||||||
|
"85": "[表情:飞吻]",
|
||||||
|
"86": "[表情:怄火]",
|
||||||
|
"89": "[表情:西瓜]",
|
||||||
|
"96": "[表情:冷汗]",
|
||||||
|
"97": "[表情:擦汗]",
|
||||||
|
"98": "[表情:抠鼻]",
|
||||||
|
"99": "[表情:鼓掌]",
|
||||||
|
"100": "[表情:糗大了]",
|
||||||
|
"101": "[表情:坏笑]",
|
||||||
|
"102": "[表情:左哼哼]",
|
||||||
|
"103": "[表情:右哼哼]",
|
||||||
|
"104": "[表情:哈欠]",
|
||||||
|
"105": "[表情:鄙视]",
|
||||||
|
"106": "[表情:委屈]",
|
||||||
|
"107": "[表情:快哭了]",
|
||||||
|
"108": "[表情:阴险]",
|
||||||
|
"109": "[表情:左亲亲]",
|
||||||
|
"110": "[表情:吓]",
|
||||||
|
"111": "[表情:可怜]",
|
||||||
|
"112": "[表情:菜刀]",
|
||||||
|
"114": "[表情:篮球]",
|
||||||
|
"116": "[表情:示爱]",
|
||||||
|
"118": "[表情:抱拳]",
|
||||||
|
"119": "[表情:勾引]",
|
||||||
|
"120": "[表情:拳头]",
|
||||||
|
"121": "[表情:差劲]",
|
||||||
|
"123": "[表情:NO]",
|
||||||
|
"124": "[表情:OK]",
|
||||||
|
"125": "[表情:转圈]",
|
||||||
|
"129": "[表情:挥手]",
|
||||||
|
"137": "[表情:鞭炮]",
|
||||||
|
"144": "[表情:喝彩]",
|
||||||
|
"146": "[表情:爆筋]",
|
||||||
|
"147": "[表情:棒棒糖]",
|
||||||
|
"169": "[表情:手枪]",
|
||||||
|
"171": "[表情:茶]",
|
||||||
|
"172": "[表情:眨眼睛]",
|
||||||
|
"173": "[表情:泪奔]",
|
||||||
|
"174": "[表情:无奈]",
|
||||||
|
"175": "[表情:卖萌]",
|
||||||
|
"176": "[表情:小纠结]",
|
||||||
|
"177": "[表情:喷血]",
|
||||||
|
"178": "[表情:斜眼笑]",
|
||||||
|
"179": "[表情:doge]",
|
||||||
|
"181": "[表情:戳一戳]",
|
||||||
|
"182": "[表情:笑哭]",
|
||||||
|
"183": "[表情:我最美]",
|
||||||
|
"185": "[表情:羊驼]",
|
||||||
|
"187": "[表情:幽灵]",
|
||||||
|
"201": "[表情:点赞]",
|
||||||
|
"212": "[表情:托腮]",
|
||||||
|
"262": "[表情:脑阔疼]",
|
||||||
|
"263": "[表情:沧桑]",
|
||||||
|
"264": "[表情:捂脸]",
|
||||||
|
"265": "[表情:辣眼睛]",
|
||||||
|
"266": "[表情:哦哟]",
|
||||||
|
"267": "[表情:头秃]",
|
||||||
|
"268": "[表情:问号脸]",
|
||||||
|
"269": "[表情:暗中观察]",
|
||||||
|
"270": "[表情:emm]",
|
||||||
|
"271": "[表情:吃 瓜]",
|
||||||
|
"272": "[表情:呵呵哒]",
|
||||||
|
"273": "[表情:我酸了]",
|
||||||
|
"277": "[表情:汪汪]",
|
||||||
|
"281": "[表情:无眼笑]",
|
||||||
|
"282": "[表情:敬礼]",
|
||||||
|
"283": "[表情:狂笑]",
|
||||||
|
"284": "[表情:面无表情]",
|
||||||
|
"285": "[表情:摸鱼]",
|
||||||
|
"286": "[表情:魔鬼笑]",
|
||||||
|
"287": "[表情:哦]",
|
||||||
|
"289": "[表情:睁眼]",
|
||||||
|
"293": "[表情:摸锦鲤]",
|
||||||
|
"294": "[表情:期待]",
|
||||||
|
"295": "[表情:拿到红包]",
|
||||||
|
"297": "[表情:拜谢]",
|
||||||
|
"298": "[表情:元宝]",
|
||||||
|
"299": "[表情:牛啊]",
|
||||||
|
"300": "[表情:胖三斤]",
|
||||||
|
"302": "[表情:左拜年]",
|
||||||
|
"303": "[表情:右拜年]",
|
||||||
|
"305": "[表情:右亲亲]",
|
||||||
|
"306": "[表情:牛气冲天]",
|
||||||
|
"307": "[表情:喵喵]",
|
||||||
|
"311": "[表情:打call]",
|
||||||
|
"312": "[表情:变形]",
|
||||||
|
"314": "[表情:仔细分析]",
|
||||||
|
"317": "[表情:菜汪]",
|
||||||
|
"318": "[表情:崇拜]",
|
||||||
|
"319": "[表情: 比心]",
|
||||||
|
"320": "[表情:庆祝]",
|
||||||
|
"323": "[表情:嫌弃]",
|
||||||
|
"324": "[表情:吃糖]",
|
||||||
|
"325": "[表情:惊吓]",
|
||||||
|
"326": "[表情:生气]",
|
||||||
|
"332": "[表情:举牌牌]",
|
||||||
|
"333": "[表情:烟花]",
|
||||||
|
"334": "[表情:虎虎生威]",
|
||||||
|
"336": "[表情:豹富]",
|
||||||
|
"337": "[表情:花朵脸]",
|
||||||
|
"338": "[表情:我想开了]",
|
||||||
|
"339": "[表情:舔屏]",
|
||||||
|
"341": "[表情:打招呼]",
|
||||||
|
"342": "[表情:酸Q]",
|
||||||
|
"343": "[表情:我方了]",
|
||||||
|
"344": "[表情:大怨种]",
|
||||||
|
"345": "[表情:红包多多]",
|
||||||
|
"346": "[表情:你真棒棒]",
|
||||||
|
"347": "[表情:大展宏兔]",
|
||||||
|
"349": "[表情:坚强]",
|
||||||
|
"350": "[表情:贴贴]",
|
||||||
|
"351": "[表情:敲敲]",
|
||||||
|
"352": "[表情:咦]",
|
||||||
|
"353": "[表情:拜托]",
|
||||||
|
"354": "[表情:尊嘟假嘟]",
|
||||||
|
"355": "[表情:耶]",
|
||||||
|
"356": "[表情:666]",
|
||||||
|
"357": "[表情:裂开]",
|
||||||
|
"392": "[表情:龙年 快乐]",
|
||||||
|
"393": "[表情:新年中龙]",
|
||||||
|
"394": "[表情:新年大龙]",
|
||||||
|
"395": "[表情:略略略]",
|
||||||
|
"396": "[表情:龙年快乐]",
|
||||||
|
"424": "[表情:按钮]",
|
||||||
|
"😊": "[表情:嘿嘿]",
|
||||||
|
"😌": "[表情:羞涩]",
|
||||||
|
"😚": "[ 表情:亲亲]",
|
||||||
|
"😓": "[表情:汗]",
|
||||||
|
"😰": "[表情:紧张]",
|
||||||
|
"😝": "[表情:吐舌]",
|
||||||
|
"😁": "[表情:呲牙]",
|
||||||
|
"😜": "[表情:淘气]",
|
||||||
|
"☺": "[表情:可爱]",
|
||||||
|
"😍": "[表情:花痴]",
|
||||||
|
"😔": "[表情:失落]",
|
||||||
|
"😄": "[表情:高兴]",
|
||||||
|
"😏": "[表情:哼哼]",
|
||||||
|
"😒": "[表情:不屑]",
|
||||||
|
"😳": "[表情:瞪眼]",
|
||||||
|
"😘": "[表情:飞吻]",
|
||||||
|
"😭": "[表情:大哭]",
|
||||||
|
"😱": "[表情:害怕]",
|
||||||
|
"😂": "[表情:激动]",
|
||||||
|
"💪": "[表情:肌肉]",
|
||||||
|
"👊": "[表情:拳头]",
|
||||||
|
"👍": "[表情 :厉害]",
|
||||||
|
"👏": "[表情:鼓掌]",
|
||||||
|
"👎": "[表情:鄙视]",
|
||||||
|
"🙏": "[表情:合十]",
|
||||||
|
"👌": "[表情:好的]",
|
||||||
|
"👆": "[表情:向上]",
|
||||||
|
"👀": "[表情:眼睛]",
|
||||||
|
"🍜": "[表情:拉面]",
|
||||||
|
"🍧": "[表情:刨冰]",
|
||||||
|
"🍞": "[表情:面包]",
|
||||||
|
"🍺": "[表情:啤酒]",
|
||||||
|
"🍻": "[表情:干杯]",
|
||||||
|
"☕": "[表情:咖啡]",
|
||||||
|
"🍎": "[表情:苹果]",
|
||||||
|
"🍓": "[表情:草莓]",
|
||||||
|
"🍉": "[表情:西瓜]",
|
||||||
|
"🚬": "[表情:吸烟]",
|
||||||
|
"🌹": "[表情:玫瑰]",
|
||||||
|
"🎉": "[表情:庆祝]",
|
||||||
|
"💝": "[表情:礼物]",
|
||||||
|
"💣": "[表情:炸弹]",
|
||||||
|
"✨": "[表情:闪光]",
|
||||||
|
"💨": "[表情:吹气]",
|
||||||
|
"💦": "[表情:水]",
|
||||||
|
"🔥": "[表情:火]",
|
||||||
|
"💤": "[表情:睡觉]",
|
||||||
|
"💩": "[表情:便便]",
|
||||||
|
"💉": "[表情:打针]",
|
||||||
|
"📫": "[表情:邮箱]",
|
||||||
|
"🐎": "[表情:骑马]",
|
||||||
|
"👧": "[表情:女孩]",
|
||||||
|
"👦": "[表情:男孩]",
|
||||||
|
"🐵": "[表情:猴]",
|
||||||
|
"🐷": "[表情:猪]",
|
||||||
|
"🐮": "[表情:牛]",
|
||||||
|
"🐔": "[表情:公鸡]",
|
||||||
|
"🐸": "[表情:青蛙]",
|
||||||
|
"👻": "[表情:幽灵]",
|
||||||
|
"🐛": "[表情:虫]",
|
||||||
|
"🐶": "[表情:狗]",
|
||||||
|
"🐳": "[表情:鲸鱼]",
|
||||||
|
"👢": "[表情:靴子]",
|
||||||
|
"☀": "[表情:晴天]",
|
||||||
|
"❔": "[表情:问号]",
|
||||||
|
"🔫": "[表情:手枪]",
|
||||||
|
"💓": "[表情:爱 心]",
|
||||||
|
"🏪": "[表情:便利店]",
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import orjson
|
||||||
|
|
||||||
from src.plugin_system import BaseEventHandler
|
from src.plugin_system import BaseEventHandler
|
||||||
from src.plugin_system.base.base_event import HandlerResult
|
from src.plugin_system.base.base_event import HandlerResult
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ class NapcatEvent:
|
|||||||
"""接收到魔法猜拳消息"""
|
"""接收到魔法猜拳消息"""
|
||||||
FRIEND_INPUT = "napcat_on_friend_input"
|
FRIEND_INPUT = "napcat_on_friend_input"
|
||||||
"""好友正在输入"""
|
"""好友正在输入"""
|
||||||
|
EMOJI_LIEK = "napcat_on_received_emoji_like"
|
||||||
|
"""接收到群聊表情回复"""
|
||||||
|
|
||||||
class ACCOUNT(Enum):
|
class ACCOUNT(Enum):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -297,8 +297,8 @@ class NapcatAdapterPlugin(BasePlugin):
|
|||||||
config_schema: dict = {
|
config_schema: dict = {
|
||||||
"plugin": {
|
"plugin": {
|
||||||
"name": ConfigField(type=str, default="napcat_adapter_plugin", description="插件名称"),
|
"name": ConfigField(type=str, default="napcat_adapter_plugin", description="插件名称"),
|
||||||
"version": ConfigField(type=str, default="1.0.0", description="插件版本"),
|
"version": ConfigField(type=str, default="1.1.0", description="插件版本"),
|
||||||
"config_version": ConfigField(type=str, default="1.3.0", description="配置文件版本"),
|
"config_version": ConfigField(type=str, default="1.3.1", description="配置文件版本"),
|
||||||
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
"enabled": ConfigField(type=bool, default=False, description="是否启用插件"),
|
||||||
},
|
},
|
||||||
"inner": {
|
"inner": {
|
||||||
@@ -345,6 +345,7 @@ class NapcatAdapterPlugin(BasePlugin):
|
|||||||
"poke_debounce_seconds": ConfigField(type=int, default=3, description="戳一戳防抖时间(秒),在指定时间内第二次针对机器人的戳一戳将被忽略"),
|
"poke_debounce_seconds": ConfigField(type=int, default=3, description="戳一戳防抖时间(秒),在指定时间内第二次针对机器人的戳一戳将被忽略"),
|
||||||
"enable_reply_at": ConfigField(type=bool, default=True, description="是否启用引用回复时艾特用户的功能"),
|
"enable_reply_at": ConfigField(type=bool, default=True, description="是否启用引用回复时艾特用户的功能"),
|
||||||
"reply_at_rate": ConfigField(type=float, default=0.5, description="引用回复时艾特用户的几率 (0.0 ~ 1.0)"),
|
"reply_at_rate": ConfigField(type=float, default=0.5, description="引用回复时艾特用户的几率 (0.0 ~ 1.0)"),
|
||||||
|
"enable_emoji_like": ConfigField(type=bool, default=True, description="是否启用群聊表情回复功能"),
|
||||||
|
|
||||||
# 视频处理设置
|
# 视频处理设置
|
||||||
"enable_video_analysis": ConfigField(type=bool, default=True, description="是否启用视频识别功能"),
|
"enable_video_analysis": ConfigField(type=bool, default=True, description="是否启用视频识别功能"),
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class NoticeType: # 通知事件
|
|||||||
group_recall = "group_recall" # 群聊消息撤回
|
group_recall = "group_recall" # 群聊消息撤回
|
||||||
notify = "notify"
|
notify = "notify"
|
||||||
group_ban = "group_ban" # 群禁言
|
group_ban = "group_ban" # 群禁言
|
||||||
|
group_msg_emoji_like = "group_msg_emoji_like" # 群聊表情回复
|
||||||
|
|
||||||
class Notify:
|
class Notify:
|
||||||
poke = "poke" # 戳一戳
|
poke = "poke" # 戳一戳
|
||||||
|
|||||||
@@ -385,7 +385,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_text_message(sub_message)
|
ret_seg = await self.handle_text_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.TEXT, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.TEXT, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -394,7 +394,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_face_message(sub_message)
|
ret_seg = await self.handle_face_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.FACE, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.FACE, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -404,7 +404,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_reply_message(sub_message)
|
ret_seg = await self.handle_reply_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.REPLY, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.REPLY, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message += ret_seg
|
seg_message += ret_seg
|
||||||
else:
|
else:
|
||||||
@@ -414,7 +414,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_image_message(sub_message)
|
ret_seg = await self.handle_image_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.IMAGE, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.IMAGE, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
logger.debug("图片处理成功,添加到消息段")
|
logger.debug("图片处理成功,添加到消息段")
|
||||||
@@ -425,7 +425,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_record_message(sub_message)
|
ret_seg = await self.handle_record_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.RECORD, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.RECORD, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.clear()
|
seg_message.clear()
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
@@ -437,7 +437,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_video_message(sub_message)
|
ret_seg = await self.handle_video_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.VIDEO, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.VIDEO, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -451,7 +451,7 @@ class MessageHandler:
|
|||||||
)
|
)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.AT, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.AT, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -460,7 +460,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_rps_message(sub_message)
|
ret_seg = await self.handle_rps_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.RPS, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.RPS, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -469,7 +469,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_dice_message(sub_message)
|
ret_seg = await self.handle_dice_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.DICE, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.DICE, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -478,7 +478,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_shake_message(sub_message)
|
ret_seg = await self.handle_shake_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.SHAKE, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.SHAKE, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
@@ -507,7 +507,7 @@ class MessageHandler:
|
|||||||
ret_seg = await self.handle_json_message(sub_message)
|
ret_seg = await self.handle_json_message(sub_message)
|
||||||
if ret_seg:
|
if ret_seg:
|
||||||
await event_manager.trigger_event(
|
await event_manager.trigger_event(
|
||||||
NapcatEvent.ON_RECEIVED.JSON, plugin_name=PLUGIN_NAME, message_seg=ret_seg
|
NapcatEvent.ON_RECEIVED.JSON, permission_group=PLUGIN_NAME, message_seg=ret_seg
|
||||||
)
|
)
|
||||||
seg_message.append(ret_seg)
|
seg_message.append(ret_seg)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from ..utils import (
|
|||||||
read_ban_list,
|
read_ban_list,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...CONSTS import PLUGIN_NAME
|
from ...CONSTS import PLUGIN_NAME, QQ_FACE
|
||||||
|
|
||||||
notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=100)
|
notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=100)
|
||||||
unsuccessful_notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=3)
|
unsuccessful_notice_queue: asyncio.Queue[MessageBase] = asyncio.Queue(maxsize=3)
|
||||||
@@ -127,9 +127,16 @@ class NoticeHandler:
|
|||||||
from src.plugin_system.core.event_manager import event_manager
|
from src.plugin_system.core.event_manager import event_manager
|
||||||
from ...event_types import NapcatEvent
|
from ...event_types import NapcatEvent
|
||||||
|
|
||||||
await event_manager.trigger_event(NapcatEvent.ON_RECEIVED.FRIEND_INPUT, plugin_name=PLUGIN_NAME)
|
await event_manager.trigger_event(NapcatEvent.ON_RECEIVED.FRIEND_INPUT, permission_group=PLUGIN_NAME)
|
||||||
case _:
|
case _:
|
||||||
logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}")
|
logger.warning(f"不支持的notify类型: {notice_type}.{sub_type}")
|
||||||
|
case NoticeType.group_msg_emoji_like:
|
||||||
|
# 该事件转移到 handle_group_emoji_like_notify函数内触发
|
||||||
|
if config_api.get_plugin_config(self.plugin_config, "features.enable_emoji_like", True):
|
||||||
|
logger.debug("处理群聊表情回复")
|
||||||
|
handled_message, user_info = await self.handle_group_emoji_like_notify(raw_message,group_id,user_id)
|
||||||
|
else:
|
||||||
|
logger.warning("群聊表情回复被禁用,取消群聊表情回复处理")
|
||||||
case NoticeType.group_ban:
|
case NoticeType.group_ban:
|
||||||
sub_type = raw_message.get("sub_type")
|
sub_type = raw_message.get("sub_type")
|
||||||
match sub_type:
|
match sub_type:
|
||||||
@@ -284,6 +291,50 @@ class NoticeHandler:
|
|||||||
)
|
)
|
||||||
return seg_data, user_info
|
return seg_data, user_info
|
||||||
|
|
||||||
|
async def handle_group_emoji_like_notify(self, raw_message: dict, group_id: int, user_id: int):
|
||||||
|
if not group_id:
|
||||||
|
logger.error("群ID不能为空,无法处理群聊表情回复通知")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
user_qq_info: dict = await get_member_info(self.get_server_connection(), group_id, user_id)
|
||||||
|
if user_qq_info:
|
||||||
|
user_name = user_qq_info.get("nickname")
|
||||||
|
user_cardname = user_qq_info.get("card")
|
||||||
|
else:
|
||||||
|
user_name = "QQ用户"
|
||||||
|
user_cardname = "QQ用户"
|
||||||
|
logger.debug("无法获取表情回复对方的用户昵称")
|
||||||
|
|
||||||
|
from src.plugin_system.core.event_manager import event_manager
|
||||||
|
from ...event_types import NapcatEvent
|
||||||
|
|
||||||
|
target_message = await event_manager.trigger_event(NapcatEvent.MESSAGE.GET_MSG,message_id=raw_message.get("message_id",""))
|
||||||
|
target_message_text = target_message.get_message_result().get("data",{}).get("raw_message","")
|
||||||
|
if not target_message:
|
||||||
|
logger.error("未找到对应消息")
|
||||||
|
return None, None
|
||||||
|
if len(target_message_text) > 15:
|
||||||
|
target_message_text = target_message_text[:15] + "..."
|
||||||
|
|
||||||
|
user_info: UserInfo = UserInfo(
|
||||||
|
platform=config_api.get_plugin_config(self.plugin_config, "maibot_server.platform_name", "qq"),
|
||||||
|
user_id=user_id,
|
||||||
|
user_nickname=user_name,
|
||||||
|
user_cardname=user_cardname,
|
||||||
|
)
|
||||||
|
|
||||||
|
like_emoji_id = raw_message.get("likes")[0].get("emoji_id")
|
||||||
|
await event_manager.trigger_event(
|
||||||
|
NapcatEvent.ON_RECEIVED.EMOJI_LIEK,
|
||||||
|
permission_group=PLUGIN_NAME,
|
||||||
|
group_id=group_id,
|
||||||
|
user_id=user_id,
|
||||||
|
message_id=raw_message.get("message_id",""),
|
||||||
|
emoji_id=like_emoji_id
|
||||||
|
)
|
||||||
|
seg_data = Seg(type="text",data=f"{user_name}使用Emoji表情{QQ_FACE.get(like_emoji_id,"")}回复了你的消息[{target_message_text}]")
|
||||||
|
return seg_data, user_info
|
||||||
|
|
||||||
async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg, UserInfo] | Tuple[None, None]:
|
async def handle_ban_notify(self, raw_message: dict, group_id: int) -> Tuple[Seg, UserInfo] | Tuple[None, None]:
|
||||||
if not group_id:
|
if not group_id:
|
||||||
logger.error("群ID不能为空,无法处理禁言通知")
|
logger.error("群ID不能为空,无法处理禁言通知")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[inner]
|
[inner]
|
||||||
version = "6.7.7"
|
version = "6.7.9"
|
||||||
|
|
||||||
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
|
||||||
#如果你想要修改配置文件,请递增version的值
|
#如果你想要修改配置文件,请递增version的值
|
||||||
@@ -175,6 +175,8 @@ delta_sigma = 120 # 正态分布的标准差,控制时间间隔的随机程度
|
|||||||
|
|
||||||
# --- 大脑/小脑 Planner 配置 ---
|
# --- 大脑/小脑 Planner 配置 ---
|
||||||
planner_size = 5.0 # 小脑(sub-planner)的尺寸,决定每个小脑处理多少个action。数值越小,并行度越高,但单个小脑的上下文越少。建议范围:3.0-8.0
|
planner_size = 5.0 # 小脑(sub-planner)的尺寸,决定每个小脑处理多少个action。数值越小,并行度越高,但单个小脑的上下文越少。建议范围:3.0-8.0
|
||||||
|
include_personality = false # 是否在小脑决策中包含角色人设信息(personality_core、personality_side)
|
||||||
|
|
||||||
|
|
||||||
[relationship]
|
[relationship]
|
||||||
enable_relationship = true # 是否启用关系系统
|
enable_relationship = true # 是否启用关系系统
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[inner]
|
[inner]
|
||||||
version = "1.3.1"
|
version = "1.3.3"
|
||||||
|
|
||||||
# 配置文件版本号迭代规则同bot_config.toml
|
# 配置文件版本号迭代规则同bot_config.toml
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ temperature = 0.7
|
|||||||
max_tokens = 800
|
max_tokens = 800
|
||||||
|
|
||||||
[model_task_config.schedule_generator]#日程表生成模型
|
[model_task_config.schedule_generator]#日程表生成模型
|
||||||
model_list = ["deepseek-v3"]
|
model_list = ["siliconflow-deepseek-v3"]
|
||||||
temperature = 0.7
|
temperature = 0.7
|
||||||
max_tokens = 1000
|
max_tokens = 1000
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ temperature = 0.1 # 低温度确保检测结果稳定
|
|||||||
max_tokens = 200 # 检测结果不需要太长的输出
|
max_tokens = 200 # 检测结果不需要太长的输出
|
||||||
|
|
||||||
[model_task_config.monthly_plan_generator] # 月层计划生成模型
|
[model_task_config.monthly_plan_generator] # 月层计划生成模型
|
||||||
model_list = ["deepseek-v3"]
|
model_list = ["siliconflow-deepseek-v3"]
|
||||||
temperature = 0.7
|
temperature = 0.7
|
||||||
max_tokens = 1000
|
max_tokens = 1000
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
测试脚本用于验证LauchNapcatAdapterHandler的plugin_config修复
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# 添加项目根目录到Python路径
|
|
||||||
project_root = Path(__file__).parent
|
|
||||||
sys.path.insert(0, str(project_root))
|
|
||||||
|
|
||||||
from src.plugins.built_in.napcat_adapter_plugin.plugin import LauchNapcatAdapterHandler
|
|
||||||
|
|
||||||
def test_plugin_config_fix():
|
|
||||||
"""测试plugin_config修复"""
|
|
||||||
print("测试LauchNapcatAdapterHandler的plugin_config修复...")
|
|
||||||
|
|
||||||
# 创建测试配置
|
|
||||||
test_config = {
|
|
||||||
"napcat_server": {
|
|
||||||
"mode": "reverse",
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 8095
|
|
||||||
},
|
|
||||||
"maibot_server": {
|
|
||||||
"host": "localhost",
|
|
||||||
"port": 8000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 创建处理器实例
|
|
||||||
handler = LauchNapcatAdapterHandler()
|
|
||||||
|
|
||||||
# 设置插件配置(模拟事件管理器注册时的行为)
|
|
||||||
handler.plugin_config = test_config
|
|
||||||
|
|
||||||
print(f"设置的plugin_config: {handler.plugin_config}")
|
|
||||||
|
|
||||||
# 测试配置访问
|
|
||||||
if handler.plugin_config is not None and handler.plugin_config == test_config:
|
|
||||||
print("✅ plugin_config修复成功!")
|
|
||||||
print(f"✅ 可以正常访问配置: napcat_server.mode = {handler.plugin_config.get('napcat_server', {}).get('mode')}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print("❌ plugin_config修复失败!")
|
|
||||||
print(f"❌ 当前plugin_config: {handler.plugin_config}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
success = test_plugin_config_fix()
|
|
||||||
sys.exit(0 if success else 1)
|
|
||||||
Reference in New Issue
Block a user