+
## 🏁 快速开始
-### 📦 安装部署
+### 📦 安装与部署
-```bash
-# 克隆项目
-git clone https://github.com/MoFox-Studio/MoFox_Bot.git
-cd MoFox_Bot
+
-# 安装依赖
-pip install -r requirements.txt
+> [!NOTE]
+> 详细的安装和配置步骤,请务必参考我们的官方文档:
+> * **Windows 用户部署指南**: [https://mofox-studio.github.io/MoFox-Bot-Docs/docs/deployment_guide.html](https://mofox-studio.github.io/MoFox-Bot-Docs/docs/deployment_guide.html)
+> * **`bot_config.toml` 究极详细教程**: [https://mofox-studio.github.io/MoFox-Bot-Docs/docs/guides/bot_config_guide.html](https://mofox-studio.github.io/MoFox-Bot-Docs/docs/guides/bot_config_guide.html)
-# 配置机器人
-cp config/bot_config.toml.example config/bot_config.toml
-# 编辑配置文件...
+
-# 启动机器人
-python bot.py
-```
+### ⚙️ 配置要点
-### ⚙️ 配置说明
+1. 📝 **核心配置**: 修改 `config/bot_config.toml` 中的基础设置,如 LLM API Key 等。
+2. 🤖 **协议端配置**: 设置 NapCatQQ 或其他兼容的 QQ 协议端,确保通信正常。
+3. 🗃️ **数据库配置**: 根据需求选择 SQLite 或配置你的 MySQL 服务器。
+4. 🔌 **插件配置**: 在 `config/plugins/` 目录下按需配置插件。
-1. 📝 **编辑配置文件** - 修改 `config/bot_config.toml` 中的基本设置
-2. 🤖 **配置协议端** - 设置 NapCat 或其他兼容的 QQ 协议端
-3. 🗃️ **数据库配置** - 选择 SQLite 或 MySQL 作为数据存储
-4. 🔌 **插件配置** - 在 `config/plugins/` 目录下配置所需插件
+
## 🙏 致谢
-我们衷心感谢以下优秀的开源项目:
+我们衷心感谢以下优秀的开源项目,没有它们,就没有 MoFox_Bot。
-
-
-| 项目 | 描述 | 贡献 |
-|------|------|------|
-| 🎯 [MaiM-with-u](https://github.com/MaiM-with-u/MaiBot) | 原版 MaiBot 项目 | 提供优秀的基础框架 |
-| 🐱 [NapCat](https://github.com/NapNeko/NapCatQQ) | 基于 NTQQ 的 Bot 协议端 | 现代化的 QQ 协议实现 |
-| 🌌 [Maizone](https://github.com/internetsb/Maizone) | 魔改空间插件 | 插件部分功能借鉴 |
+| 项目 | 描述 | 贡献 |
+| ------------------------------------------ | -------------------- | ---------------- |
+| 🎯 [MaiM-with-u/MaiBot](https://github.com/MaiM-with-u/MaiBot) | 原版 MaiBot 项目 | 提供优秀的基础框架 |
+| 🐱 [NapNeko/NapCatQQ](https://github.com/NapNeko/NapCatQQ) | 基于 NTQQ 的 Bot 协议端 | 现代化的 QQ 协议实现 |
+| 🌌 [internetsb/Maizone](https://github.com/internetsb/Maizone) | 魔改空间插件 | 插件部分功能借鉴 |
---
+
+
## ⚠️ 注意事项
-
-
> [!CAUTION]
> **重要提醒**
->
-> 使用本项目前必须阅读和同意 [📋 用户协议](EULA.md) 和 [🔒 隐私协议](PRIVACY.md)
->
-> 本应用生成内容来自人工智能模型,由 AI 生成,请仔细甄别,请勿用于违反法律的用途
->
-> AI 生成内容不代表本项目团队的观点和立场
+>
+> - 使用本项目前,你必须阅读并同意 [**📋 用户协议 (EULA.md)**](EULA.md)。
+> - 本应用生成的内容来自人工智能大模型,请仔细甄别其准确性,并请勿用于任何违反法律法规的用途。
+> - AI 生成的所有内容不代表本项目团队的任何观点和立场。
---
-## 📄 开源协议
-
-**本项目基于 [GPL-3.0](LICENSE) 协议开源**
+## 📄 开源协议
+
+本项目基于 **[GPL-3.0](LICENSE)** 协议开源。
[](LICENSE)
```
-Copyright © 2024 MoFox Studio
-Licensed under the GNU General Public License v3.0
+ Copyright © 2025 MoFox Studio
+ Licensed under the GNU General Public License v3.0
```
@@ -192,8 +194,8 @@ Licensed under the GNU General Public License v3.0
**🌟 如果这个项目对你有帮助,请给我们一个 Star!**
-**💬 有问题或建议?欢迎提 Issue 或 PR!**
+**💬 有任何问题或建议?欢迎提交 Issue 或 Pull Request!**
-Made with ❤️ by [MoFox Studio](https://github.com/MoFox-Studio)
+*Made with ❤️ by [MoFox Studio](https://github.com/MoFox-Studio)*
diff --git a/plugins/bilibli/bilibli_base.py b/plugins/bilibli/bilibli_base.py
index 66dc2697e..2fcd87934 100644
--- a/plugins/bilibli/bilibli_base.py
+++ b/plugins/bilibli/bilibli_base.py
@@ -8,8 +8,6 @@ Bilibili 工具基础模块
import re
import aiohttp
import asyncio
-import tempfile
-import os
from typing import Optional, Dict, Any
from src.common.logger import get_logger
from src.chat.utils.utils_video import get_video_analyzer
diff --git a/src/chat/planner_actions/planner.py b/src/chat/planner_actions/planner.py
index 6ea19c2be..6115b73bd 100644
--- a/src/chat/planner_actions/planner.py
+++ b/src/chat/planner_actions/planner.py
@@ -429,7 +429,7 @@ class ActionPlanner:
schedule_block = ""
if global_config.schedule.enable:
if current_activity := schedule_manager.get_current_activity():
- schedule_block = f"你当前正在:{current_activity}。"
+ schedule_block = f"你当前正在:{current_activity},但注意它与群聊的聊天无关。"
mood_block = ""
if global_config.mood.enable_mood:
diff --git a/src/common/vector_db/chromadb_impl.py b/src/common/vector_db/chromadb_impl.py
index 8e9313b3b..363f5fcbe 100644
--- a/src/common/vector_db/chromadb_impl.py
+++ b/src/common/vector_db/chromadb_impl.py
@@ -92,12 +92,14 @@ class ChromaDBImpl(VectorDBBase):
collection = self.get_or_create_collection(collection_name)
if collection:
try:
- return collection.query(
- query_embeddings=query_embeddings,
- n_results=n_results,
- where=where or {},
+ query_params = {
+ "query_embeddings": query_embeddings,
+ "n_results": n_results,
**kwargs,
- )
+ }
+ if where:
+ query_params["where"] = where
+ return collection.query(**query_params)
except Exception as e:
logger.error(f"查询集合 '{collection_name}' 失败: {e}")
return {}
diff --git a/src/config/config.py b/src/config/config.py
index 9aa25cc15..70c0edd0e 100644
--- a/src/config/config.py
+++ b/src/config/config.py
@@ -40,7 +40,6 @@ from src.config.official_configs import (
DependencyManagementConfig,
WebSearchConfig,
AntiPromptInjectionConfig,
- PluginsConfig,
SleepSystemConfig,
MonthlyPlanSystemConfig,
CrossContextConfig,
@@ -389,7 +388,6 @@ class Config(ValidatedConfigBase):
video_analysis: VideoAnalysisConfig = Field(default_factory=lambda: VideoAnalysisConfig(), description="视频分析配置")
dependency_management: DependencyManagementConfig = Field(default_factory=lambda: DependencyManagementConfig(), description="依赖管理配置")
web_search: WebSearchConfig = Field(default_factory=lambda: WebSearchConfig(), description="网络搜索配置")
- plugins: PluginsConfig = Field(default_factory=lambda: PluginsConfig(), description="插件配置")
sleep_system: SleepSystemConfig = Field(default_factory=lambda: SleepSystemConfig(), description="睡眠系统配置")
monthly_plan_system: MonthlyPlanSystemConfig = Field(default_factory=lambda: MonthlyPlanSystemConfig(), description="月层计划系统配置")
cross_context: CrossContextConfig = Field(default_factory=lambda: CrossContextConfig(), description="跨群聊上下文共享配置")
diff --git a/src/config/official_configs.py b/src/config/official_configs.py
index 36e9159ec..f8bb37d95 100644
--- a/src/config/official_configs.py
+++ b/src/config/official_configs.py
@@ -600,13 +600,6 @@ class AntiPromptInjectionConfig(ValidatedConfigBase):
shield_suffix: str = Field(default=" 🛡️", description="保护后缀")
-
-class PluginsConfig(ValidatedConfigBase):
- """插件配置"""
-
- centralized_config: bool = Field(default=True, description="是否启用插件配置集中化管理")
-
-
class SleepSystemConfig(ValidatedConfigBase):
"""睡眠系统配置类"""
diff --git a/src/plugin_system/base/plugin_base.py b/src/plugin_system/base/plugin_base.py
index dfe1ec94d..8b6ad84c9 100644
--- a/src/plugin_system/base/plugin_base.py
+++ b/src/plugin_system/base/plugin_base.py
@@ -458,54 +458,35 @@ class PluginBase(ABC):
加载插件配置文件,实现集中化管理和自动迁移。
处理逻辑:
- 1. 确定插件模板配置文件路径(位于插件目录内)。
- 2. 如果模板不存在,则在插件目录内生成一份默认配置。
- 3. 确定用户配置文件路径(位于 `config/plugins/` 目录下)。
- 4. 如果用户配置文件不存在,则从插件目录复制模板文件过去。
- 5. 加载用户配置文件,并进行版本检查和自动迁移(如果需要)。
- 6. 最终加载的配置是用户配置文件。
+ 1. 确定用户配置文件路径(位于 `config/plugins/` 目录下)。
+ 2. 如果用户配置文件不存在,则根据 config_schema 直接在中央目录生成一份。
+ 3. 加载用户配置文件,并进行版本检查和自动迁移(如果需要)。
+ 4. 最终加载的配置是用户配置文件。
"""
if not self.config_file_name:
logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载")
return
- # 1. 确定插件模板配置文件路径
- template_config_path = os.path.join(self.plugin_dir, self.config_file_name)
+ # 1. 确定并确保用户配置文件路径存在
+ user_config_path = os.path.join(CONFIG_DIR, "plugins", self.plugin_name, self.config_file_name)
+ os.makedirs(os.path.dirname(user_config_path), exist_ok=True)
- # 2. 如果模板不存在,则在插件目录内生成
- if not os.path.exists(template_config_path):
- logger.info(f"{self.log_prefix} 插件目录缺少配置文件 {template_config_path},将生成默认配置。")
- self._generate_and_save_default_config(template_config_path)
-
- # 3. 确定用户配置文件路径
- plugin_config_dir = os.path.join(CONFIG_DIR, "plugins", self.plugin_name)
- user_config_path = os.path.join(plugin_config_dir, self.config_file_name)
-
- # 确保用户插件配置目录存在
- os.makedirs(plugin_config_dir, exist_ok=True)
-
- # 4. 如果用户配置文件不存在,从模板复制
+ # 2. 如果用户配置文件不存在,直接在中央目录生成
if not os.path.exists(user_config_path):
- try:
- shutil.copy2(template_config_path, user_config_path)
- logger.info(f"{self.log_prefix} 已从模板创建用户配置文件: {user_config_path}")
- except IOError as e:
- logger.error(f"{self.log_prefix} 复制配置文件失败: {e}", exc_info=True)
- # 如果复制失败,后续将无法加载,直接返回
- return
+ logger.info(f"{self.log_prefix} 用户配置文件 {user_config_path} 不存在,将生成默认配置。")
+ self._generate_and_save_default_config(user_config_path)
# 检查最终的用户配置文件是否存在
if not os.path.exists(user_config_path):
logger.warning(f"{self.log_prefix} 用户配置文件 {user_config_path} 不存在且无法创建。")
return
- # 5. 加载、检查和迁移用户配置文件
+ # 3. 加载、检查和迁移用户配置文件
_, file_ext = os.path.splitext(self.config_file_name)
if file_ext.lower() != ".toml":
logger.warning(f"{self.log_prefix} 不支持的配置文件格式: {file_ext},仅支持 .toml")
self.config = {}
return
-
try:
with open(user_config_path, "r", encoding="utf-8") as f:
existing_config = toml.load(f) or {}
diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py
index b16c80f61..754df34d9 100644
--- a/src/plugin_system/core/plugin_manager.py
+++ b/src/plugin_system/core/plugin_manager.py
@@ -1,5 +1,7 @@
import asyncio
import os
+import shutil
+import hashlib
import traceback
import importlib
@@ -37,6 +39,48 @@ class PluginManager:
self._ensure_plugin_directories()
logger.info("插件管理器初始化完成")
+ def _synchronize_plugin_config(self, plugin_name: str, plugin_dir: str):
+ """
+ 同步单个插件的配置。
+ """
+ central_config_dir = os.path.join("config", "plugins", plugin_name)
+ plugin_config_dir = os.path.join(plugin_dir, "config")
+
+ # 确保中央配置目录存在
+ os.makedirs(central_config_dir, exist_ok=True)
+
+ # 1. 从插件目录同步到中央目录(如果中央配置不存在)
+ if os.path.exists(plugin_config_dir) and os.path.isdir(plugin_config_dir):
+ for filename in os.listdir(plugin_config_dir):
+ central_config_file = os.path.join(central_config_dir, filename)
+ plugin_config_file = os.path.join(plugin_config_dir, filename)
+
+ if not os.path.exists(central_config_file) and os.path.isfile(plugin_config_file):
+ shutil.copy2(plugin_config_file, central_config_file)
+ logger.info(f"从 {plugin_name} 复制默认配置到中央目录: {filename}")
+
+ # 2. 从中央目录同步到插件目录(覆盖)
+ if os.path.isdir(central_config_dir):
+ for filename in os.listdir(central_config_dir):
+ central_config_file = os.path.join(central_config_dir, filename)
+ plugin_config_file = os.path.join(plugin_config_dir, filename)
+
+ if not os.path.isfile(central_config_file):
+ continue
+
+ # 确保插件的 config 目录存在
+ os.makedirs(plugin_config_dir, exist_ok=True)
+
+ should_copy = True
+ if os.path.exists(plugin_config_file):
+ with open(central_config_file, 'rb') as f1, open(plugin_config_file, 'rb') as f2:
+ if hashlib.md5(f1.read()).hexdigest() == hashlib.md5(f2.read()).hexdigest():
+ should_copy = False
+
+ if should_copy:
+ shutil.copy2(central_config_file, plugin_config_file)
+ logger.info(f"同步中央配置到 {plugin_name}: {filename}")
+
# === 插件目录管理 ===
def add_plugin_directory(self, directory: str) -> bool:
@@ -104,6 +148,9 @@ class PluginManager:
if not plugin_dir:
return False, 1
+ # 同步插件配置
+ self._synchronize_plugin_config(plugin_name, plugin_dir)
+
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件(可能因为缺少manifest而失败)
if not plugin_instance:
logger.error(f"插件 {plugin_name} 实例化失败")
diff --git a/src/plugins/built_in/set_typing_status/_manifest.json b/src/plugins/built_in/set_typing_status/_manifest.json
new file mode 100644
index 000000000..0ba1ff237
--- /dev/null
+++ b/src/plugins/built_in/set_typing_status/_manifest.json
@@ -0,0 +1,16 @@
+{
+ "name": "Set Typing Status",
+ "description": "一个在LLM生成回复时设置私聊输入状态的插件。",
+ "version": "1.0.0",
+ "author": {
+ "name": "MoFox-Studio"
+ },
+ "license": "MIT",
+ "homepage_url": "",
+ "repository_url": "",
+ "keywords": ["typing", "status", "private chat"],
+ "categories": ["utility"],
+ "host_application": {
+ "min_version": "0.10.0"
+ }
+}
\ No newline at end of file
diff --git a/src/plugins/built_in/set_typing_status/plugin.py b/src/plugins/built_in/set_typing_status/plugin.py
new file mode 100644
index 000000000..a88fe1390
--- /dev/null
+++ b/src/plugins/built_in/set_typing_status/plugin.py
@@ -0,0 +1,64 @@
+from typing import List, Tuple, Type
+import logging
+
+from src.plugin_system import (
+ BasePlugin,
+ register_plugin,
+ ComponentInfo,
+ BaseEventHandler,
+ EventType,
+)
+from src.plugin_system.base.base_event import HandlerResult
+from src.plugin_system.apis import send_api
+
+logger = logging.getLogger(__name__)
+
+
+class SetTypingStatusHandler(BaseEventHandler):
+ """在LLM处理私聊消息后设置“正在输入”状态的事件处理器。"""
+
+ handler_name = "set_typing_status_handler"
+ handler_description = "在LLM生成回复后,将用户的聊天状态设置为“正在输入”。"
+ init_subscribe = [EventType.POST_LLM]
+
+ async def execute(self, params: dict) -> HandlerResult:
+ message = params.get("message")
+ if not message or not message.is_private_message:
+ return HandlerResult(success=True, continue_process=True)
+
+ user_id = message.message_info.user_info.user_id
+ if not user_id:
+ return HandlerResult(success=False, continue_process=True, message="无法获取用户ID")
+
+ try:
+ params = {"user_id": user_id,"event_type": 1}
+ await send_api.adapter_command_to_stream(
+ action="set_input_status",
+ params=params,
+ stream_id=message.stream_id,
+ )
+ logger.debug(f"成功为用户 {user_id} 设置“正在输入”状态。")
+ return HandlerResult(success=True, continue_process=True)
+ except Exception as e:
+ logger.error(f"为用户 {user_id} 设置“正在输入”状态时出错: {e}")
+ return HandlerResult(success=False, continue_process=True, message=str(e))
+
+
+@register_plugin
+class SetTypingStatusPlugin(BasePlugin):
+ """一个在LLM生成回复时设置私聊输入状态的插件。"""
+
+ plugin_name = "set_typing_status"
+ enable_plugin = True
+ dependencies = []
+ python_dependencies = []
+ config_file_name = ""
+
+ config_schema = {}
+
+ def get_plugin_components(self) -> List[Tuple[ComponentInfo, Type]]:
+ """注册插件的功能组件。"""
+ return [(SetTypingStatusHandler.get_handler_info(), SetTypingStatusHandler)]
+
+ def register_plugin(self) -> bool:
+ return True
diff --git a/src/schedule/monthly_plan_manager.py b/src/schedule/monthly_plan_manager.py
index 1e25cdf11..bc55544d2 100644
--- a/src/schedule/monthly_plan_manager.py
+++ b/src/schedule/monthly_plan_manager.py
@@ -77,7 +77,7 @@ class MonthlyPlanManager:
if len(plans) > max_plans:
logger.warning(f"当前月度计划数量 ({len(plans)}) 超出上限 ({max_plans}),将自动删除多余的计划。")
# 按创建时间升序排序(旧的在前),然后删除超出上限的部分(新的)
- plans_to_delete = sorted(plans, key=lambda p: p.created_at)[max_plans:]
+ plans_to_delete = sorted(plans, key=lambda p: p.created_at, reverse=True)[:len(plans)-max_plans]
delete_ids = [p.id for p in plans_to_delete]
delete_plans_by_ids(delete_ids)
# 重新获取计划列表
@@ -138,6 +138,14 @@ class MonthlyPlanManager:
finally:
self.generation_running = False
+ def trigger_generate_monthly_plans(self, target_month: Optional[str] = None):
+ """
+ 以非阻塞的方式启动月度计划生成任务。
+ 这允许其他模块(如ScheduleManager)触发计划生成,而无需等待其完成。
+ """
+ logger.info(f"已触发 {target_month or '当前月份'} 的非阻塞月度计划生成任务。")
+ asyncio.create_task(self.generate_monthly_plans(target_month))
+
def _get_previous_month(self, current_month: str) -> str:
"""获取上个月的月份字符串"""
try:
diff --git a/src/schedule/schedule_manager.py b/src/schedule/schedule_manager.py
index 2834d159a..f8685e161 100644
--- a/src/schedule/schedule_manager.py
+++ b/src/schedule/schedule_manager.py
@@ -231,17 +231,15 @@ class ScheduleManager:
# 如果计划耗尽,则触发补充生成
if not sampled_plans:
- logger.info("可用的月度计划已耗尽或不足,尝试进行补充生成...")
+ logger.info("可用的月度计划已耗尽或不足,触发后台补充生成...")
from mmc.src.schedule.monthly_plan_manager import monthly_plan_manager
-
- success = await monthly_plan_manager.generate_monthly_plans(current_month_str)
- if success:
- logger.info("补充生成完成,重新抽取月度计划...")
- sampled_plans = get_smart_plans_for_daily_schedule(
- current_month_str, max_count=3, avoid_days=avoid_days
- )
- else:
- logger.warning("月度计划补充生成失败。")
+
+ # 以非阻塞方式触发月度计划生成
+ monthly_plan_manager.trigger_generate_monthly_plans(current_month_str)
+
+ # 注意:这里不再等待生成结果,因此后续代码不会立即获得新计划。
+ # 日程将基于当前可用的信息生成,新计划将在下一次日程生成时可用。
+ logger.info("月度计划的后台生成任务已启动,本次日程将不包含新计划。")
if sampled_plans:
plan_texts = "\n".join([f"- {plan.plan_text}" for plan in sampled_plans])
diff --git a/template/bot_config_template.toml b/template/bot_config_template.toml
index d5b4f0da7..6a5f8baa8 100644
--- a/template/bot_config_template.toml
+++ b/template/bot_config_template.toml
@@ -1,5 +1,5 @@
[inner]
-version = "6.5.8"
+version = "6.6.0"
#----以下是给开发人员阅读的,如果你只是部署了MoFox-Bot,不需要阅读----
#如果你想要修改配置文件,请递增version的值
@@ -411,9 +411,6 @@ exa_api_keys = ["None"]# EXA API密钥列表,支持轮询机制
enabled_engines = ["ddg"] # 启用的搜索引擎列表,可选: "exa", "tavily", "ddg","bing"
search_strategy = "single" # 搜索策略: "single"(使用第一个可用引擎), "parallel"(并行使用所有启用的引擎), "fallback"(按顺序尝试,失败则尝试下一个)
-[plugins] # 插件配置
-centralized_config = true # 是否启用插件配置集中化管理
-
# ----------------------------------------------------------------
# 月度计划系统设置 (Monthly Plan System Settings)
# ----------------------------------------------------------------