From d6284b6b4cab6cfe7ed48d88dfdce0aa8dcb534e Mon Sep 17 00:00:00 2001 From: UnCLAS-Prommer Date: Thu, 24 Jul 2025 00:31:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=92=E4=BB=B6=E7=AE=A1=E7=90=86API?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=B8=8E=E4=BF=AE=E6=94=B9=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changes.md | 2 +- src/chat/message_receive/bot.py | 6 +- src/plugin_system/core/component_registry.py | 98 ++++--- src/plugin_system/core/events_manager.py | 2 + src/plugin_system/core/plugin_manager.py | 6 +- .../built_in/plugin_management/plugin.py | 270 +++++++++++++++--- 6 files changed, 302 insertions(+), 82 deletions(-) diff --git a/changes.md b/changes.md index e0746da51..70b9dfbf2 100644 --- a/changes.md +++ b/changes.md @@ -56,7 +56,7 @@ # 官方插件修改 1. `HelloWorld`插件现在有一个样例的`EventHandler`。 -2. 内置插件增加了一个通过`Command`来管理插件的功能。 +2. 内置插件增加了一个通过`Command`来管理插件的功能。具体是使用`/pm`命令唤起。 ### TODO 把这个看起来就很别扭的config获取方式改一下 diff --git a/src/chat/message_receive/bot.py b/src/chat/message_receive/bot.py index b58377e27..cade4f145 100644 --- a/src/chat/message_receive/bot.py +++ b/src/chat/message_receive/bot.py @@ -220,9 +220,6 @@ class ChatBot: await MessageStorage.update_message(message) return - if not await events_manager.handle_mai_events(EventType.ON_MESSAGE, message): - return - get_chat_manager().register_message(message) chat = await get_chat_manager().get_or_create_stream( @@ -257,6 +254,9 @@ class ChatBot: logger.info(f"命令处理完成,跳过后续消息处理: {cmd_result}") return + if not await events_manager.handle_mai_events(EventType.ON_MESSAGE, message): + return + # 确认从接口发来的message是否有自定义的prompt模板信息 if message.message_info.template_info and not message.message_info.template_info.template_default: template_group_name: Optional[str] = message.message_info.template_info.template_name # type: ignore diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index 772fc8bd5..2ea89b880 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -25,27 +25,35 @@ class ComponentRegistry: """ def __init__(self): - # 组件注册表 - self._components: Dict[str, ComponentInfo] = {} # 命名空间式组件名 -> 组件信息 - # 类型 -> 组件原名称 -> 组件信息 + # 命名空间式组件名构成法 f"{component_type}.{component_name}" + self._components: Dict[str, ComponentInfo] = {} + """组件注册表 命名空间式组件名 -> 组件信息""" self._components_by_type: Dict[ComponentType, Dict[str, ComponentInfo]] = {types: {} for types in ComponentType} - # 命名空间式组件名 -> 组件类 + """类型 -> 组件原名称 -> 组件信息""" self._components_classes: Dict[str, Type[Union[BaseCommand, BaseAction, BaseEventHandler]]] = {} + """命名空间式组件名 -> 组件类""" # 插件注册表 - self._plugins: Dict[str, PluginInfo] = {} # 插件名 -> 插件信息 + self._plugins: Dict[str, PluginInfo] = {} + """插件名 -> 插件信息""" # Action特定注册表 - self._action_registry: Dict[str, Type[BaseAction]] = {} # action名 -> action类 - self._default_actions: Dict[str, ActionInfo] = {} # 默认动作集,即启用的Action集,用于重置ActionManager状态 + self._action_registry: Dict[str, Type[BaseAction]] = {} + """Action注册表 action名 -> action类""" + self._default_actions: Dict[str, ActionInfo] = {} + """默认动作集,即启用的Action集,用于重置ActionManager状态""" # Command特定注册表 - self._command_registry: Dict[str, Type[BaseCommand]] = {} # command名 -> command类 - self._command_patterns: Dict[Pattern, str] = {} # 编译后的正则 -> command名 + self._command_registry: Dict[str, Type[BaseCommand]] = {} + """Command类注册表 command名 -> command类""" + self._command_patterns: Dict[Pattern, str] = {} + """编译后的正则 -> command名""" # EventHandler特定注册表 - self._event_handler_registry: Dict[str, Type[BaseEventHandler]] = {} # event_handler名 -> event_handler类 - self._enabled_event_handlers: Dict[str, Type[BaseEventHandler]] = {} # 启用的事件处理器 + self._event_handler_registry: Dict[str, Type[BaseEventHandler]] = {} + """event_handler名 -> event_handler类""" + self._enabled_event_handlers: Dict[str, Type[BaseEventHandler]] = {} + """启用的事件处理器 event_handler名 -> event_handler类""" logger.info("组件注册中心初始化完成") @@ -199,30 +207,55 @@ class ComponentRegistry: # === 组件移除相关 === - async def remove_component(self, component_name: str, component_type: ComponentType): + async def remove_component(self, component_name: str, component_type: ComponentType, plugin_name: str) -> bool: target_component_class = self.get_component_class(component_name, component_type) if not target_component_class: logger.warning(f"组件 {component_name} 未注册,无法移除") - return - match component_type: - case ComponentType.ACTION: - self._action_registry.pop(component_name, None) - self._default_actions.pop(component_name, None) - case ComponentType.COMMAND: - self._command_registry.pop(component_name, None) - keys_to_remove = [k for k, v in self._command_patterns.items() if v == component_name] - for key in keys_to_remove: - self._command_patterns.pop(key, None) - case ComponentType.EVENT_HANDLER: - from .events_manager import events_manager # 延迟导入防止循环导入问题 + return False + try: + match component_type: + case ComponentType.ACTION: + self._action_registry.pop(component_name) + self._default_actions.pop(component_name) + case ComponentType.COMMAND: + self._command_registry.pop(component_name) + keys_to_remove = [k for k, v in self._command_patterns.items() if v == component_name] + for key in keys_to_remove: + self._command_patterns.pop(key) + case ComponentType.EVENT_HANDLER: + from .events_manager import events_manager # 延迟导入防止循环导入问题 - self._event_handler_registry.pop(component_name, None) - self._enabled_event_handlers.pop(component_name, None) - await events_manager.unregister_event_subscriber(component_name) - self._components.pop(component_name, None) - self._components_by_type[component_type].pop(component_name, None) - self._components_classes.pop(component_name, None) - logger.info(f"组件 {component_name} 已移除") + self._event_handler_registry.pop(component_name) + self._enabled_event_handlers.pop(component_name) + await events_manager.unregister_event_subscriber(component_name) + namespaced_name = f"{component_type}.{component_name}" + self._components.pop(namespaced_name) + self._components_by_type[component_type].pop(component_name) + self._components_classes.pop(namespaced_name) + logger.info(f"组件 {component_name} 已移除") + return True + except KeyError: + logger.warning(f"移除组件时未找到组件: {component_name}") + return False + except Exception as e: + logger.error(f"移除组件 {component_name} 时发生错误: {e}") + return False + + def remove_plugin_registry(self, plugin_name: str) -> bool: + """移除插件注册信息 + + Args: + plugin_name: 插件名称 + + Returns: + bool: 是否成功移除 + """ + if plugin_name not in self._plugins: + logger.warning(f"插件 {plugin_name} 未注册,无法移除") + return False + del self._plugins[plugin_name] + logger.info(f"插件 {plugin_name} 已移除") + return True # === 组件全局启用/禁用方法 === @@ -255,7 +288,8 @@ class ComponentRegistry: from .events_manager import events_manager # 延迟导入防止循环导入问题 events_manager.register_event_subscriber(target_component_info, target_component_class) - self._components[component_name].enabled = True + namespaced_name = f"{component_type}.{component_name}" + self._components[namespaced_name].enabled = True self._components_by_type[component_type][component_name].enabled = True logger.info(f"组件 {component_name} 已启用") return True diff --git a/src/plugin_system/core/events_manager.py b/src/plugin_system/core/events_manager.py index 0182409cd..1f01b4ab4 100644 --- a/src/plugin_system/core/events_manager.py +++ b/src/plugin_system/core/events_manager.py @@ -75,6 +75,8 @@ class EventsManager: handler_task = asyncio.create_task(handler.execute(transformed_message)) handler_task.add_done_callback(self._task_done_callback) handler_task.set_name(f"{handler.plugin_name}-{handler.handler_name}") + if handler.handler_name not in self._handler_tasks: + self._handler_tasks[handler.handler_name] = [] self._handler_tasks[handler.handler_name].append(handler_task) except Exception as e: logger.error(f"创建事件处理器任务 {handler.handler_name} 时发生异常: {e}") diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 90ba16f4e..8bb005a94 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -162,10 +162,12 @@ class PluginManager: return False plugin_instance = self.loaded_plugins[plugin_name] plugin_info = plugin_instance.plugin_info + success = True for component in plugin_info.components: - await component_registry.remove_component(component.name, component.component_type) + success &= await component_registry.remove_component(component.name, component.component_type, plugin_name) + success &= component_registry.remove_plugin_registry(plugin_name) del self.loaded_plugins[plugin_name] - return True + return success async def reload_registered_plugin(self, plugin_name: str) -> bool: """ diff --git a/src/plugins/built_in/plugin_management/plugin.py b/src/plugins/built_in/plugin_management/plugin.py index 15c769db2..353afe7f1 100644 --- a/src/plugins/built_in/plugin_management/plugin.py +++ b/src/plugins/built_in/plugin_management/plugin.py @@ -1,3 +1,5 @@ +import asyncio + from typing import List, Tuple, Type from src.plugin_system import ( BasePlugin, @@ -10,18 +12,16 @@ from src.plugin_system import ( 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*$)" + command_pattern: str = r"(?P^/pm(\s[a-zA-Z0-9_]+)*\s*$)" intercept_message: bool = True async def execute(self) -> Tuple[bool, str]: + # sourcery skip: merge-duplicate-blocks command_list = self.matched_groups["manage_command"].strip().split(" ") if len(command_list) == 1: await self.show_help("all") @@ -35,6 +35,7 @@ class ManagementCommand(BaseCommand): case "help": await self.show_help("all") case _: + await self.send_text("插件管理命令不合法") return False, "命令不合法" if len(command_list) == 3: if command_list[1] == "plugin": @@ -48,17 +49,18 @@ class ManagementCommand(BaseCommand): case "rescan": await self._rescan_plugin_dirs() case _: + await self.send_text("插件管理命令不合法") 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, "命令不合法" + if command_list[2] == "list": + await self._list_all_registered_components() + elif command_list[2] == "help": + await self.show_help("component") + else: + await self.send_text("插件管理命令不合法") + return False, "命令不合法" else: + await self.send_text("插件管理命令不合法") return False, "命令不合法" if len(command_list) == 4: if command_list[1] == "plugin": @@ -72,15 +74,61 @@ class ManagementCommand(BaseCommand): case "add_dir": await self._add_dir(command_list[3]) case _: + await self.send_text("插件管理命令不合法") return False, "命令不合法" elif command_list[1] == "component": - pass + if command_list[2] != "list": + await self.send_text("插件管理命令不合法") + return False, "命令不合法" + if command_list[3] == "enabled": + await self._list_enabled_components() + elif command_list[3] == "disabled": + await self._list_disabled_components() + else: + await self.send_text("插件管理命令不合法") + return False, "命令不合法" else: + await self.send_text("插件管理命令不合法") return False, "命令不合法" if len(command_list) == 5: - pass + if command_list[1] != "component": + await self.send_text("插件管理命令不合法") + return False, "命令不合法" + if command_list[2] != "list": + await self.send_text("插件管理命令不合法") + return False, "命令不合法" + if command_list[3] == "enabled": + await self._list_enabled_components(target_type=command_list[4]) + elif command_list[3] == "disabled": + await self._list_disabled_components(target_type=command_list[4]) + elif command_list[3] == "type": + await self._list_registered_components_by_type(command_list[4]) + else: + await self.send_text("插件管理命令不合法") + return False, "命令不合法" if len(command_list) == 6: - pass + if command_list[1] != "component": + await self.send_text("插件管理命令不合法") + return False, "命令不合法" + if command_list[2] == "enable": + if command_list[3] == "global": + await self._globally_enable_component(command_list[4], command_list[5]) + elif command_list[3] == "local": + await self._locally_enable_component(command_list[4], command_list[5]) + else: + await self.send_text("插件管理命令不合法") + return False, "命令不合法" + elif command_list[2] == "disable": + if command_list[3] == "global": + await self._globally_disable_component(command_list[4], command_list[5]) + elif command_list[3] == "local": + await self._locally_disable_component(command_list[4], command_list[5]) + else: + await self.send_text("插件管理命令不合法") + return False, "命令不合法" + else: + await self.send_text("插件管理命令不合法") + return False, "命令不合法" return True, "命令执行完成" @@ -90,37 +138,37 @@ class ManagementCommand(BaseCommand): 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 获取具体帮助" + "/pm help 管理命令提示\n" + "/pm plugin 插件管理命令\n" + "/pm component 组件管理命令\n" + "使用 /pm plugin help 或 /pm 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" + "/pm plugin help 插件管理命令提示\n" + "/pm plugin list 列出所有注册的插件\n" + "/pm plugin list_enabled 列出所有加载(启用)的插件\n" + "/pm plugin rescan 重新扫描所有目录\n" + "/pm plugin load 加载指定插件\n" + "/pm plugin unload 卸载指定插件\n" + "/pm plugin reload 重新加载指定插件\n" + "/pm 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" + "/pm component help 组件管理命令提示\n" + "/pm component list 列出所有注册的组件\n" + "/pm component list enabled <可选: type> 列出所有启用的组件\n" + "/pm 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" + "/pm component list type 列出已经注册的指定类型的组件\n" + "/pm component enable global 全局启用组件\n" + "/pm component enable local 本聊天启用组件\n" + "/pm component disable global 全局禁用组件\n" + "/pm component disable local 本聊天禁用组件\n" " - 可选项: action, command, event_handler\n" ) case _: @@ -140,7 +188,6 @@ class ManagementCommand(BaseCommand): 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}") @@ -150,16 +197,14 @@ class ManagementCommand(BaseCommand): 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) + success = await 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) + success = await plugin_manage_api.reload_plugin(plugin_name) if success: await self.send_text(f"插件重新加载成功: {plugin_name}") else: @@ -168,6 +213,7 @@ class ManagementCommand(BaseCommand): async def _add_dir(self, dir_path: str): await self.send_text(f"正在添加插件目录: {dir_path}") success = plugin_manage_api.add_plugin_directory(dir_path) + await asyncio.sleep(0.5) # 防止乱序发送 if success: await self.send_text(f"插件目录添加成功: {dir_path}") else: @@ -219,15 +265,151 @@ class ManagementCommand(BaseCommand): if target_type == "global": enabled_components = [component for component in components_info if component.enabled] if not enabled_components: - await self.send_text("没有启用的全局组件") + 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}") + await self.send_text(f"满足条件的已启用全局组件: {enabled_components_str}") elif target_type == "local": locally_disabled_components = self._fetch_locally_disabled_components() - + enabled_components = [ + component + for component in components_info + if (component.name not in locally_disabled_components and 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}") + + async def _list_disabled_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": + disabled_components = [component for component in components_info if not component.enabled] + if not disabled_components: + await self.send_text("没有满足条件的已禁用全局组件") + return + disabled_components_str = ", ".join( + f"{component.name} ({component.component_type})" for component in disabled_components + ) + await self.send_text(f"满足条件的已禁用全局组件: {disabled_components_str}") + elif target_type == "local": + locally_disabled_components = self._fetch_locally_disabled_components() + disabled_components = [ + component + for component in components_info + if (component.name in locally_disabled_components or not component.enabled) + ] + if not disabled_components: + await self.send_text("本聊天没有满足条件的已禁用组件") + return + disabled_components_str = ", ".join( + f"{component.name} ({component.component_type})" for component in disabled_components + ) + await self.send_text(f"本聊天满足条件的已禁用组件: {disabled_components_str}") + + async def _list_registered_components_by_type(self, target_type: str): + match target_type: + case "action": + component_type = ComponentType.ACTION + case "command": + component_type = ComponentType.COMMAND + case "event_handler": + component_type = ComponentType.EVENT_HANDLER + case _: + await self.send_text(f"未知组件类型: {target_type}") + return + + components_info = component_manage_api.get_components_info_by_type(component_type) + if not components_info: + await self.send_text(f"没有注册的 {target_type} 组件") + return + + components_str = ", ".join( + f"{name} ({component.component_type})" for name, component in components_info.items() + ) + await self.send_text(f"注册的 {target_type} 组件: {components_str}") + + async def _globally_enable_component(self, component_name: str, component_type: str): + match component_type: + case "action": + target_component_type = ComponentType.ACTION + case "command": + target_component_type = ComponentType.COMMAND + case "event_handler": + target_component_type = ComponentType.EVENT_HANDLER + case _: + await self.send_text(f"未知组件类型: {component_type}") + return + if component_manage_api.globally_enable_component(component_name, target_component_type): + await self.send_text(f"全局启用组件成功: {component_name}") + else: + await self.send_text(f"全局启用组件失败: {component_name}") + + async def _globally_disable_component(self, component_name: str, component_type: str): + match component_type: + case "action": + target_component_type = ComponentType.ACTION + case "command": + target_component_type = ComponentType.COMMAND + case "event_handler": + target_component_type = ComponentType.EVENT_HANDLER + case _: + await self.send_text(f"未知组件类型: {component_type}") + return + success = await component_manage_api.globally_disable_component(component_name, target_component_type) + if success: + await self.send_text(f"全局禁用组件成功: {component_name}") + else: + await self.send_text(f"全局禁用组件失败: {component_name}") + + async def _locally_enable_component(self, component_name: str, component_type: str): + match component_type: + case "action": + target_component_type = ComponentType.ACTION + case "command": + target_component_type = ComponentType.COMMAND + case "event_handler": + target_component_type = ComponentType.EVENT_HANDLER + case _: + await self.send_text(f"未知组件类型: {component_type}") + return + if component_manage_api.locally_enable_component( + component_name, + target_component_type, + self.message.chat_stream.stream_id, + ): + await self.send_text(f"本地启用组件成功: {component_name}") + else: + await self.send_text(f"本地启用组件失败: {component_name}") + + async def _locally_disable_component(self, component_name: str, component_type: str): + match component_type: + case "action": + target_component_type = ComponentType.ACTION + case "command": + target_component_type = ComponentType.COMMAND + case "event_handler": + target_component_type = ComponentType.EVENT_HANDLER + case _: + await self.send_text(f"未知组件类型: {component_type}") + return + if component_manage_api.locally_disable_component( + component_name, + target_component_type, + self.message.chat_stream.stream_id, + ): + await self.send_text(f"本地禁用组件成功: {component_name}") + else: + await self.send_text(f"本地禁用组件失败: {component_name}") @register_plugin