From e15183a422c248d9a231890050fb8b5e3368345d Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Wed, 23 Jul 2025 15:53:59 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AE=A1=E7=90=86=E6=8F=92=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E4=BD=86=E6=98=AF=E5=8F=AA=E6=9C=89=E4=B8=80=E5=8D=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 2 +- plugins/hello_world_plugin/plugin.py | 12 +- .../apis/component_manage_api.py | 23 ++ src/plugin_system/base/base_command.py | 5 +- src/plugins/built_in/core_actions/plugin.py | 13 +- src/plugins/built_in/core_actions/reply.py | 13 +- .../built_in/plugin_management/_manifest.json | 39 +++ .../built_in/plugin_management/plugin.py | 246 ++++++++++++++++++ src/plugins/built_in/tts_plugin/plugin.py | 14 +- 9 files changed, 337 insertions(+), 30 deletions(-) create mode 100644 src/plugins/built_in/plugin_management/_manifest.json create mode 100644 src/plugins/built_in/plugin_management/plugin.py diff --git a/changes.md b/changes.md index 14dc39790..e0746da51 100644 --- a/changes.md +++ b/changes.md @@ -56,7 +56,7 @@ # 官方插件修改 1. `HelloWorld`插件现在有一个样例的`EventHandler`。 - +2. 内置插件增加了一个通过`Command`来管理插件的功能。 ### TODO 把这个看起来就很别扭的config获取方式改一下 diff --git a/plugins/hello_world_plugin/plugin.py b/plugins/hello_world_plugin/plugin.py index 14a9d16c5..55b9df82d 100644 --- a/plugins/hello_world_plugin/plugin.py +++ b/plugins/hello_world_plugin/plugin.py @@ -118,17 +118,17 @@ class HelloWorldPlugin(BasePlugin): """Hello World插件 - 你的第一个MaiCore插件""" # 插件基本信息 - plugin_name = "hello_world_plugin" # 内部标识符 - enable_plugin = True - dependencies = [] # 插件依赖列表 - python_dependencies = [] # Python包依赖列表 - config_file_name = "config.toml" # 配置文件名 + plugin_name: str = "hello_world_plugin" # 内部标识符 + enable_plugin: bool = True + dependencies: List[str] = [] # 插件依赖列表 + python_dependencies: List[str] = [] # Python包依赖列表 + config_file_name: str = "config.toml" # 配置文件名 # 配置节描述 config_section_descriptions = {"plugin": "插件基本信息", "greeting": "问候功能配置", "time": "时间查询配置"} # 配置Schema定义 - config_schema = { + config_schema: dict = { "plugin": { "name": ConfigField(type=str, default="hello_world_plugin", description="插件名称"), "version": ConfigField(type=str, default="1.0.0", description="插件版本"), diff --git a/src/plugin_system/apis/component_manage_api.py b/src/plugin_system/apis/component_manage_api.py index 545d4ba2b..d9ea051d9 100644 --- a/src/plugin_system/apis/component_manage_api.py +++ b/src/plugin_system/apis/component_manage_api.py @@ -220,3 +220,26 @@ def locally_disable_component(component_name: str, component_type: ComponentType return global_announcement_manager.disable_specific_chat_event_handler(stream_id, component_name) case _: raise ValueError(f"未知 component type: {component_type}") + +def get_locally_disabled_components(stream_id: str, component_type: ComponentType) -> list[str]: + """ + 获取指定消息流中禁用的组件列表。 + + Args: + stream_id (str): 消息流 ID。 + component_type (ComponentType): 组件类型。 + + Returns: + list[str]: 禁用的组件名称列表。 + """ + from src.plugin_system.core.global_announcement_manager import global_announcement_manager + + match component_type: + case ComponentType.ACTION: + return global_announcement_manager.get_disabled_chat_actions(stream_id) + case ComponentType.COMMAND: + return global_announcement_manager.get_disabled_chat_commands(stream_id) + case ComponentType.EVENT_HANDLER: + return global_announcement_manager.get_disabled_chat_event_handlers(stream_id) + case _: + raise ValueError(f"未知 component type: {component_type}") \ No newline at end of file diff --git a/src/plugin_system/base/base_command.py b/src/plugin_system/base/base_command.py index 813b40529..7909980cb 100644 --- a/src/plugin_system/base/base_command.py +++ b/src/plugin_system/base/base_command.py @@ -24,9 +24,8 @@ class BaseCommand(ABC): """Command组件的名称""" command_description: str = "" """Command组件的描述""" - - # 默认命令设置(子类可以覆盖) - command_pattern: str = "" + # 默认命令设置 + command_pattern: str = r"" """命令匹配的正则表达式""" command_help: str = "" """命令帮助信息""" diff --git a/src/plugins/built_in/core_actions/plugin.py b/src/plugins/built_in/core_actions/plugin.py index d01177a0a..99bff18aa 100644 --- a/src/plugins/built_in/core_actions/plugin.py +++ b/src/plugins/built_in/core_actions/plugin.py @@ -22,6 +22,7 @@ from src.plugins.built_in.core_actions.reply import ReplyAction logger = get_logger("core_actions") + @register_plugin class CoreActionsPlugin(BasePlugin): """核心动作插件 @@ -35,11 +36,11 @@ class CoreActionsPlugin(BasePlugin): """ # 插件基本信息 - plugin_name = "core_actions" # 内部标识符 - enable_plugin = True - dependencies = [] # 插件依赖列表 - python_dependencies = [] # Python包依赖列表 - config_file_name = "config.toml" + plugin_name: str = "core_actions" # 内部标识符 + enable_plugin: bool = True + dependencies: list[str] = [] # 插件依赖列表 + python_dependencies: list[str] = [] # Python包依赖列表 + config_file_name: str = "config.toml" # 配置节描述 config_section_descriptions = { @@ -48,7 +49,7 @@ class CoreActionsPlugin(BasePlugin): } # 配置Schema定义 - config_schema = { + config_schema: dict = { "plugin": { "enabled": ConfigField(type=bool, default=True, description="是否启用插件"), "config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"), diff --git a/src/plugins/built_in/core_actions/reply.py b/src/plugins/built_in/core_actions/reply.py index a5071c4c9..90aa4889d 100644 --- a/src/plugins/built_in/core_actions/reply.py +++ b/src/plugins/built_in/core_actions/reply.py @@ -1,4 +1,3 @@ - # 导入新插件系统 from src.plugin_system import BaseAction, ActionActivationType, ChatMode from src.config.config import global_config @@ -8,6 +7,7 @@ from typing import Tuple import asyncio import re import traceback + # 导入依赖的系统组件 from src.common.logger import get_logger @@ -20,6 +20,7 @@ from src.mais4u.constant_s4u import ENABLE_S4U logger = get_logger("reply_action") + class ReplyAction(BaseAction): """回复动作 - 参与聊天回复""" @@ -61,10 +62,10 @@ class ReplyAction(BaseAction): user_id = self.user_id platform = self.platform # logger.info(f"{self.log_prefix} 用户ID: {user_id}, 平台: {platform}") - person_id = get_person_info_manager().get_person_id(platform, user_id) + person_id = get_person_info_manager().get_person_id(platform, user_id) # type: ignore # logger.info(f"{self.log_prefix} 人物ID: {person_id}") person_name = get_person_info_manager().get_value_sync(person_id, "person_name") - reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}" + reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}" # type: ignore logger.info(f"{self.log_prefix} 回复目标: {reply_to}") try: @@ -118,11 +119,9 @@ class ReplyAction(BaseAction): # 存储动作记录 reply_text = f"你对{person_name}进行了回复:{reply_text}" - - + if ENABLE_S4U: await mai_thinking_manager.get_mai_think(self.chat_id).do_think_after_response(reply_text) - await self.store_action_info( action_build_into_prompt=False, @@ -138,4 +137,4 @@ class ReplyAction(BaseAction): except Exception as e: logger.error(f"{self.log_prefix} 回复动作执行失败: {e}") traceback.print_exc() - return False, f"回复失败: {str(e)}" \ No newline at end of file + return False, f"回复失败: {str(e)}" diff --git a/src/plugins/built_in/plugin_management/_manifest.json b/src/plugins/built_in/plugin_management/_manifest.json new file mode 100644 index 000000000..41b3cd9ce --- /dev/null +++ b/src/plugins/built_in/plugin_management/_manifest.json @@ -0,0 +1,39 @@ +{ + "manifest_version": 1, + "name": "插件和组件管理 (Plugin and Component Management)", + "version": "1.0.0", + "description": "通过系统API管理插件和组件的生命周期,包括加载、卸载、启用和禁用等操作。", + "author": { + "name": "MaiBot团队", + "url": "https://github.com/MaiM-with-u" + }, + "license": "GPL-v3.0-or-later", + "host_application": { + "min_version": "0.9.0" + }, + "homepage_url": "https://github.com/MaiM-with-u/maibot", + "repository_url": "https://github.com/MaiM-with-u/maibot", + "keywords": [ + "plugins", + "components", + "management", + "built-in" + ], + "categories": [ + "Core System", + "Plugin Management" + ], + "default_locale": "zh-CN", + "locales_path": "_locales", + "plugin_info": { + "is_built_in": true, + "plugin_type": "plugin_management", + "components": [ + { + "type": "command", + "name": "plugin_management", + "description": "管理插件和组件的生命周期,包括加载、卸载、启用和禁用等操作。" + } + ] + } +} \ No newline at end of file diff --git a/src/plugins/built_in/plugin_management/plugin.py b/src/plugins/built_in/plugin_management/plugin.py new file mode 100644 index 000000000..15c769db2 --- /dev/null +++ b/src/plugins/built_in/plugin_management/plugin.py @@ -0,0 +1,246 @@ +from typing import List, Tuple, Type +from src.plugin_system import ( + BasePlugin, + BaseCommand, + CommandInfo, + ConfigField, + register_plugin, + plugin_manage_api, + component_manage_api, + ComponentInfo, + ComponentType, +) +from src.plugin_system.base.base_action import BaseAction +from src.plugin_system.base.base_events_handler import BaseEventHandler +from src.plugin_system.base.component_types import ActionInfo, EventHandlerInfo + + +class ManagementCommand(BaseCommand): + command_name: str = "management" + description: str = "管理命令" + command_pattern: str = r"(?P^/p_m(\s[a-zA-Z0-9_]+)*\s*$)" + intercept_message: bool = True + + async def execute(self) -> Tuple[bool, str]: + command_list = self.matched_groups["manage_command"].strip().split(" ") + if len(command_list) == 1: + await self.show_help("all") + return True, "帮助已发送" + if len(command_list) == 2: + match command_list[1]: + case "plugin": + await self.show_help("plugin") + case "component": + await self.show_help("component") + case "help": + await self.show_help("all") + case _: + return False, "命令不合法" + if len(command_list) == 3: + if command_list[1] == "plugin": + match command_list[2]: + case "help": + await self.show_help("plugin") + case "list": + await self._list_registered_plugins() + case "list_enabled": + await self._list_loaded_plugins() + case "rescan": + await self._rescan_plugin_dirs() + case _: + return False, "命令不合法" + elif command_list[1] == "component": + match command_list[2]: + case "help": + await self.show_help("component") + return True, "帮助已发送" + case "list": + pass + case _: + return False, "命令不合法" + else: + return False, "命令不合法" + if len(command_list) == 4: + if command_list[1] == "plugin": + match command_list[2]: + case "load": + await self._load_plugin(command_list[3]) + case "unload": + await self._unload_plugin(command_list[3]) + case "reload": + await self._reload_plugin(command_list[3]) + case "add_dir": + await self._add_dir(command_list[3]) + case _: + return False, "命令不合法" + elif command_list[1] == "component": + pass + else: + return False, "命令不合法" + if len(command_list) == 5: + pass + if len(command_list) == 6: + pass + + return True, "命令执行完成" + + async def show_help(self, target: str): + help_msg = "" + match target: + case "all": + help_msg = ( + "管理命令帮助\n" + "/p_m help 管理命令提示\n" + "/p_m plugin 插件管理命令\n" + "/p_m component 组件管理命令\n" + "使用 /p_m plugin help 或 /p_m component help 获取具体帮助" + ) + case "plugin": + help_msg = ( + "插件管理命令帮助\n" + "/p_m plugin help 插件管理命令提示\n" + "/p_m plugin list 列出所有注册的插件\n" + "/p_m plugin list_enabled 列出所有加载(启用)的插件\n" + "/p_m plugin rescan 重新扫描所有目录\n" + "/p_m plugin load 加载指定插件\n" + "/p_m plugin unload 卸载指定插件\n" + "/p_m plugin reload 重新加载指定插件\n" + "/p_m plugin add_dir 添加插件目录\n" + ) + case "component": + help_msg = ( + "组件管理命令帮助\n" + "/p_m component help 组件管理命令提示\n" + "/p_m component list 列出所有注册的组件\n" + "/p_m component list enabled <可选: type> 列出所有启用的组件\n" + "/p_m component list disabled <可选: type> 列出所有禁用的组件\n" + " - 可选项: local,代表当前聊天中的;global,代表全局的\n" + " - 不填时为 global\n" + "/p_m component list type 列出指定类型的组件\n" + "/p_m component global enable <可选: component_type> 全局启用组件\n" + "/p_m component global disable <可选: component_type> 全局禁用组件\n" + "/p_m component local enable <可选: component_type> 本聊天启用组件\n" + "/p_m component local disable <可选: component_type> 本聊天禁用组件\n" + " - 可选项: action, command, event_handler\n" + ) + case _: + return + await self.send_text(help_msg) + + async def _list_loaded_plugins(self): + plugins = plugin_manage_api.list_loaded_plugins() + await self.send_text(f"已加载的插件: {', '.join(plugins)}") + + async def _list_registered_plugins(self): + plugins = plugin_manage_api.list_registered_plugins() + await self.send_text(f"已注册的插件: {', '.join(plugins)}") + + async def _rescan_plugin_dirs(self): + plugin_manage_api.rescan_plugin_directory() + await self.send_text("插件目录重新扫描执行中") + + async def _load_plugin(self, plugin_name: str): + await self.send_text(f"正在加载插件: {plugin_name}") + success, count = plugin_manage_api.load_plugin(plugin_name) + if success: + await self.send_text(f"插件加载成功: {plugin_name}") + else: + if count == 0: + await self.send_text(f"插件{plugin_name}为禁用状态") + await self.send_text(f"插件加载失败: {plugin_name}") + + async def _unload_plugin(self, plugin_name: str): + await self.send_text(f"正在卸载插件: {plugin_name}") + success = plugin_manage_api.remove_plugin(plugin_name) + if success: + await self.send_text(f"插件卸载成功: {plugin_name}") + else: + await self.send_text(f"插件卸载失败: {plugin_name}") + + async def _reload_plugin(self, plugin_name: str): + await self.send_text(f"正在重新加载插件: {plugin_name}") + success = plugin_manage_api.reload_plugin(plugin_name) + if success: + await self.send_text(f"插件重新加载成功: {plugin_name}") + else: + await self.send_text(f"插件重新加载失败: {plugin_name}") + + async def _add_dir(self, dir_path: str): + await self.send_text(f"正在添加插件目录: {dir_path}") + success = plugin_manage_api.add_plugin_directory(dir_path) + if success: + await self.send_text(f"插件目录添加成功: {dir_path}") + else: + await self.send_text(f"插件目录添加失败: {dir_path}") + + def _fetch_all_registered_components(self) -> List[ComponentInfo]: + all_plugin_info = component_manage_api.get_all_plugin_info() + if not all_plugin_info: + return [] + + components_info: List[ComponentInfo] = [] + for plugin_info in all_plugin_info.values(): + components_info.extend(plugin_info.components) + return components_info + + def _fetch_locally_disabled_components(self) -> List[str]: + locally_disabled_components_actions = component_manage_api.get_locally_disabled_components( + self.message.chat_stream.stream_id, ComponentType.ACTION + ) + locally_disabled_components_commands = component_manage_api.get_locally_disabled_components( + self.message.chat_stream.stream_id, ComponentType.COMMAND + ) + locally_disabled_components_event_handlers = component_manage_api.get_locally_disabled_components( + self.message.chat_stream.stream_id, ComponentType.EVENT_HANDLER + ) + return ( + locally_disabled_components_actions + + locally_disabled_components_commands + + locally_disabled_components_event_handlers + ) + + async def _list_all_registered_components(self): + components_info = self._fetch_all_registered_components() + if not components_info: + await self.send_text("没有注册的组件") + return + + all_components_str = ", ".join( + f"{component.name} ({component.component_type})" for component in components_info + ) + await self.send_text(f"已注册的组件: {all_components_str}") + + async def _list_enabled_components(self, target_type: str = "global"): + components_info = self._fetch_all_registered_components() + if not components_info: + await self.send_text("没有注册的组件") + return + + if target_type == "global": + enabled_components = [component for component in components_info if component.enabled] + if not enabled_components: + await self.send_text("没有启用的全局组件") + return + enabled_components_str = ", ".join( + f"{component.name} ({component.component_type})" for component in enabled_components + ) + await self.send_text(f"启用的全局组件: {enabled_components_str}") + elif target_type == "local": + locally_disabled_components = self._fetch_locally_disabled_components() + + + +@register_plugin +class PluginManagementPlugin(BasePlugin): + plugin_name: str = "plugin_management_plugin" + enable_plugin: bool = True + dependencies: list[str] = [] + python_dependencies: list[str] = [] + config_file_name: str = "config.toml" + config_schema: dict = {"plugin": {"enable": ConfigField(bool, default=True, description="是否启用插件")}} + + def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]: + components = [] + if self.get_config("plugin.enable", True): + components.append((ManagementCommand.get_command_info(), ManagementCommand)) + return components diff --git a/src/plugins/built_in/tts_plugin/plugin.py b/src/plugins/built_in/tts_plugin/plugin.py index 7d45f4d30..6683735e4 100644 --- a/src/plugins/built_in/tts_plugin/plugin.py +++ b/src/plugins/built_in/tts_plugin/plugin.py @@ -92,7 +92,7 @@ class TTSAction(BaseAction): # 确保句子结尾有合适的标点 if not any(processed_text.endswith(end) for end in [".", "?", "!", "。", "!", "?"]): - processed_text = processed_text + "。" + processed_text = f"{processed_text}。" return processed_text @@ -107,11 +107,11 @@ class TTSPlugin(BasePlugin): """ # 插件基本信息 - plugin_name = "tts_plugin" # 内部标识符 - enable_plugin = True - dependencies = [] # 插件依赖列表 - python_dependencies = [] # Python包依赖列表 - config_file_name = "config.toml" + plugin_name: str = "tts_plugin" # 内部标识符 + enable_plugin: bool = True + dependencies: list[str] = [] # 插件依赖列表 + python_dependencies: list[str] = [] # Python包依赖列表 + config_file_name: str = "config.toml" # 配置节描述 config_section_descriptions = { @@ -121,7 +121,7 @@ class TTSPlugin(BasePlugin): } # 配置Schema定义 - config_schema = { + config_schema: dict = { "plugin": { "name": ConfigField(type=str, default="tts_plugin", description="插件名称", required=True), "version": ConfigField(type=str, default="0.1.0", description="插件版本号"),