diff --git a/plugins/take_picture_plugin/plugin.py b/plugins/take_picture_plugin/plugin.py index 5be4bf438..15406ca16 100644 --- a/plugins/take_picture_plugin/plugin.py +++ b/plugins/take_picture_plugin/plugin.py @@ -36,11 +36,12 @@ import urllib.error import base64 import traceback -from src.plugin_system.base.base_plugin import BasePlugin, register_plugin +from src.plugin_system.base.base_plugin import BasePlugin from src.plugin_system.base.base_action import BaseAction from src.plugin_system.base.base_command import BaseCommand from src.plugin_system.base.component_types import ComponentInfo, ActionActivationType, ChatMode from src.plugin_system.base.config_types import ConfigField +from src.plugin_system import register_plugin from src.common.logger import get_logger logger = get_logger("take_picture_plugin") diff --git a/src/plugin_system/__init__.py b/src/plugin_system/__init__.py index 01b9a6125..213e86cac 100644 --- a/src/plugin_system/__init__.py +++ b/src/plugin_system/__init__.py @@ -5,11 +5,11 @@ MaiBot 插件系统 """ # 导出主要的公共接口 -from src.plugin_system.base.base_plugin import BasePlugin, register_plugin -from src.plugin_system.base.base_action import BaseAction -from src.plugin_system.base.base_command import BaseCommand -from src.plugin_system.base.config_types import ConfigField -from src.plugin_system.base.component_types import ( +from .base import ( + BasePlugin, + BaseAction, + BaseCommand, + ConfigField, ComponentType, ActionActivationType, ChatMode, @@ -19,18 +19,22 @@ from src.plugin_system.base.component_types import ( PluginInfo, PythonDependency, ) -from src.plugin_system.core.plugin_manager import plugin_manager -from src.plugin_system.core.component_registry import component_registry -from src.plugin_system.core.dependency_manager import dependency_manager +from .core.plugin_manager import ( + plugin_manager, + component_registry, + dependency_manager, +) # 导入工具模块 -from src.plugin_system.utils import ( +from .utils import ( ManifestValidator, ManifestGenerator, validate_plugin_manifest, generate_plugin_manifest, ) +from .apis.plugin_register_api import register_plugin + __version__ = "1.0.0" diff --git a/src/plugin_system/apis/__init__.py b/src/plugin_system/apis/__init__.py index cfcf9b7e7..15ef547ef 100644 --- a/src/plugin_system/apis/__init__.py +++ b/src/plugin_system/apis/__init__.py @@ -16,6 +16,7 @@ from src.plugin_system.apis import ( person_api, send_api, utils_api, + plugin_register_api, ) # 导出所有API模块,使它们可以通过 apis.xxx 方式访问 @@ -30,4 +31,5 @@ __all__ = [ "person_api", "send_api", "utils_api", + "plugin_register_api", ] diff --git a/src/plugin_system/apis/plugin_register_api.py b/src/plugin_system/apis/plugin_register_api.py new file mode 100644 index 000000000..d6e7f1f53 --- /dev/null +++ b/src/plugin_system/apis/plugin_register_api.py @@ -0,0 +1,29 @@ +from src.common.logger import get_logger + +logger = get_logger("plugin_register") + + +def register_plugin(cls): + from src.plugin_system.core.plugin_manager import plugin_manager + from src.plugin_system.base.base_plugin import BasePlugin + + """插件注册装饰器 + + 用法: + @register_plugin + class MyPlugin(BasePlugin): + plugin_name = "my_plugin" + plugin_description = "我的插件" + ... + """ + if not issubclass(cls, BasePlugin): + logger.error(f"类 {cls.__name__} 不是 BasePlugin 的子类") + return cls + + # 只是注册插件类,不立即实例化 + # 插件管理器会负责实例化和注册 + plugin_name = cls.plugin_name or cls.__name__ + plugin_manager.plugin_classes[plugin_name] = cls + logger.debug(f"插件类已注册: {plugin_name}") + + return cls diff --git a/src/plugin_system/base/__init__.py b/src/plugin_system/base/__init__.py index f22f5082d..bff325948 100644 --- a/src/plugin_system/base/__init__.py +++ b/src/plugin_system/base/__init__.py @@ -4,10 +4,10 @@ 提供插件开发的基础类和类型定义 """ -from src.plugin_system.base.base_plugin import BasePlugin, register_plugin -from src.plugin_system.base.base_action import BaseAction -from src.plugin_system.base.base_command import BaseCommand -from src.plugin_system.base.component_types import ( +from .base_plugin import BasePlugin +from .base_action import BaseAction +from .base_command import BaseCommand +from .component_types import ( ComponentType, ActionActivationType, ChatMode, @@ -15,13 +15,14 @@ from src.plugin_system.base.component_types import ( ActionInfo, CommandInfo, PluginInfo, + PythonDependency, ) +from .config_types import ConfigField __all__ = [ "BasePlugin", "BaseAction", "BaseCommand", - "register_plugin", "ComponentType", "ActionActivationType", "ChatMode", @@ -29,4 +30,6 @@ __all__ = [ "ActionInfo", "CommandInfo", "PluginInfo", + "PythonDependency", + "ConfigField", ] diff --git a/src/plugin_system/base/base_plugin.py b/src/plugin_system/base/base_plugin.py index 4044c12e9..5fdf20d2e 100644 --- a/src/plugin_system/base/base_plugin.py +++ b/src/plugin_system/base/base_plugin.py @@ -4,6 +4,9 @@ import os import inspect import toml import json +import shutil +import datetime + from src.common.logger import get_logger from src.plugin_system.base.component_types import ( PluginInfo, @@ -11,13 +14,10 @@ from src.plugin_system.base.component_types import ( PythonDependency, ) from src.plugin_system.base.config_types import ConfigField -from src.plugin_system.core.component_registry import component_registry +from src.plugin_system.utils.manifest_utils import ManifestValidator logger = get_logger("base_plugin") -# 全局插件类注册表 -_plugin_classes: Dict[str, Type["BasePlugin"]] = {} - class BasePlugin(ABC): """插件基类 @@ -29,7 +29,7 @@ class BasePlugin(ABC): """ # 插件基本信息(子类必须定义) - plugin_name: str = "" # 插件内部标识符(如 "doubao_pic_plugin") + plugin_name: str = "" # 插件内部标识符(如 "hello_world_plugin") enable_plugin: bool = False # 是否启用插件 dependencies: List[str] = [] # 依赖的其他插件 python_dependencies: List[PythonDependency] = [] # Python包依赖 @@ -103,7 +103,7 @@ class BasePlugin(ABC): if not self.get_manifest_info("description"): raise ValueError(f"插件 {self.plugin_name} 的manifest中缺少description字段") - def _load_manifest(self): + def _load_manifest(self): # sourcery skip: raise-from-previous-error """加载manifest文件(强制要求)""" if not self.plugin_dir: raise ValueError(f"{self.log_prefix} 没有插件目录路径,无法加载manifest") @@ -124,9 +124,6 @@ class BasePlugin(ABC): # 验证manifest格式 self._validate_manifest() - # 从manifest覆盖插件基本信息(如果插件类中未定义) - self._apply_manifest_overrides() - except json.JSONDecodeError as e: error_msg = f"{self.log_prefix} manifest文件格式错误: {e}" logger.error(error_msg) @@ -136,15 +133,6 @@ class BasePlugin(ABC): logger.error(error_msg) raise IOError(error_msg) # noqa - def _apply_manifest_overrides(self): - """从manifest文件覆盖插件信息(现在只处理内部标识符的fallback)""" - if not self.manifest_data: - return - - # 只有当插件类中没有定义plugin_name时,才从manifest中获取作为fallback - if not self.plugin_name: - self.plugin_name = self.manifest_data.get("name", "").replace(" ", "_").lower() - def _get_author_name(self) -> str: """从manifest获取作者名称""" author_info = self.get_manifest_info("author", {}) @@ -156,10 +144,7 @@ class BasePlugin(ABC): def _validate_manifest(self): """验证manifest文件格式(使用强化的验证器)""" if not self.manifest_data: - return - - # 导入验证器 - from src.plugin_system.utils.manifest_utils import ManifestValidator + raise ValueError(f"{self.log_prefix} manifest数据为空,验证失败") validator = ManifestValidator() is_valid = validator.validate_manifest(self.manifest_data) @@ -176,36 +161,6 @@ class BasePlugin(ABC): error_msg += f": {'; '.join(validator.validation_errors)}" raise ValueError(error_msg) - def _generate_default_manifest(self, manifest_path: str): - """生成默认的manifest文件""" - if not self.plugin_name: - logger.debug(f"{self.log_prefix} 插件名称未定义,无法生成默认manifest") - return - - # 从plugin_name生成友好的显示名称 - display_name = self.plugin_name.replace("_", " ").title() - - default_manifest = { - "manifest_version": 1, - "name": display_name, - "version": "1.0.0", - "description": "插件描述", - "author": {"name": "Unknown", "url": ""}, - "license": "MIT", - "host_application": {"min_version": "1.0.0", "max_version": "4.0.0"}, - "keywords": [], - "categories": [], - "default_locale": "zh-CN", - "locales_path": "_locales", - } - - try: - with open(manifest_path, "w", encoding="utf-8") as f: - json.dump(default_manifest, f, ensure_ascii=False, indent=2) - logger.info(f"{self.log_prefix} 已生成默认manifest文件: {manifest_path}") - except IOError as e: - logger.error(f"{self.log_prefix} 保存默认manifest文件失败: {e}") - def get_manifest_info(self, key: str, default: Any = None) -> Any: """获取manifest信息 @@ -304,9 +259,6 @@ class BasePlugin(ABC): def _backup_config_file(self, config_file_path: str) -> str: """备份配置文件""" - import shutil - import datetime - timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = f"{config_file_path}.backup_{timestamp}" @@ -377,13 +329,14 @@ class BasePlugin(ABC): logger.warning(f"{self.log_prefix} 配置节 {section_name} 结构已改变,使用默认值") # 检查旧配置中是否有新配置没有的节 - for section_name in old_config.keys(): + for section_name in old_config: if section_name not in migrated_config: logger.warning(f"{self.log_prefix} 配置节 {section_name} 在新版本中已被移除") return migrated_config def _generate_config_from_schema(self) -> Dict[str, Any]: + # sourcery skip: dict-comprehension """根据schema生成配置数据结构(不写入文件)""" if not self.config_schema: return {} @@ -473,7 +426,7 @@ class BasePlugin(ABC): except IOError as e: logger.error(f"{self.log_prefix} 保存配置文件失败: {e}", exc_info=True) - def _load_plugin_config(self): + def _load_plugin_config(self): # sourcery skip: extract-method """加载插件配置文件,支持版本检查和自动迁移""" if not self.config_file_name: logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载") @@ -568,7 +521,7 @@ class BasePlugin(ABC): def register_plugin(self) -> bool: """注册插件及其所有组件""" - + from src.plugin_system.core.component_registry import component_registry components = self.get_plugin_components() # 检查依赖 @@ -598,6 +551,7 @@ class BasePlugin(ABC): def _check_dependencies(self) -> bool: """检查插件依赖""" + from src.plugin_system.core.component_registry import component_registry if not self.dependencies: return True @@ -629,52 +583,3 @@ class BasePlugin(ABC): return default return current - - -def register_plugin(cls): - """插件注册装饰器 - - 用法: - @register_plugin - class MyPlugin(BasePlugin): - plugin_name = "my_plugin" - plugin_description = "我的插件" - ... - """ - if not issubclass(cls, BasePlugin): - logger.error(f"类 {cls.__name__} 不是 BasePlugin 的子类") - return cls - - # 只是注册插件类,不立即实例化 - # 插件管理器会负责实例化和注册 - plugin_name = cls.plugin_name or cls.__name__ - _plugin_classes[plugin_name] = cls - logger.debug(f"插件类已注册: {plugin_name}") - - return cls - - -def get_registered_plugin_classes() -> Dict[str, Type["BasePlugin"]]: - """获取所有已注册的插件类""" - return _plugin_classes.copy() - - -def instantiate_and_register_plugin(plugin_class: Type["BasePlugin"], plugin_dir: str = None) -> bool: - """实例化并注册插件 - - Args: - plugin_class: 插件类 - plugin_dir: 插件目录路径 - - Returns: - bool: 是否成功 - """ - try: - plugin_instance = plugin_class(plugin_dir=plugin_dir) - return plugin_instance.register_plugin() - except Exception as e: - logger.error(f"注册插件 {plugin_class.__name__} 时出错: {e}") - import traceback - - logger.error(traceback.format_exc()) - return False diff --git a/src/plugin_system/core/__init__.py b/src/plugin_system/core/__init__.py index d1377b477..6bd3d3935 100644 --- a/src/plugin_system/core/__init__.py +++ b/src/plugin_system/core/__init__.py @@ -6,8 +6,9 @@ from src.plugin_system.core.plugin_manager import plugin_manager from src.plugin_system.core.component_registry import component_registry - +from src.plugin_system.core.dependency_manager import dependency_manager __all__ = [ "plugin_manager", "component_registry", + "dependency_manager", ] diff --git a/src/plugin_system/core/component_registry.py b/src/plugin_system/core/component_registry.py index 9d2dea721..809319802 100644 --- a/src/plugin_system/core/component_registry.py +++ b/src/plugin_system/core/component_registry.py @@ -9,8 +9,8 @@ from src.plugin_system.base.component_types import ( ComponentType, ) -from ..base.base_command import BaseCommand -from ..base.base_action import BaseAction +from src.plugin_system.base.base_command import BaseCommand +from src.plugin_system.base.base_action import BaseAction logger = get_logger("component_registry") diff --git a/src/plugin_system/core/plugin_manager.py b/src/plugin_system/core/plugin_manager.py index 12c59dcf4..0de8f6eb6 100644 --- a/src/plugin_system/core/plugin_manager.py +++ b/src/plugin_system/core/plugin_manager.py @@ -89,6 +89,8 @@ class PluginManager: total_registered += 1 else: total_failed_registration += 1 + + return total_registered, total_failed_registration def load_registered_plugin_classes(self, plugin_name: str) -> bool: # sourcery skip: extract-duplicate-method, extract-method @@ -255,7 +257,7 @@ class PluginManager: all_optional_missing: List[PythonDependency] = [] plugin_status = {} - for plugin_name, _plugin_instance in self.loaded_plugins.items(): + for plugin_name in self.loaded_plugins: plugin_info = component_registry.get_plugin_info(plugin_name) if not plugin_info or not plugin_info.python_dependencies: plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []} @@ -327,7 +329,7 @@ class PluginManager: all_dependencies = [] - for plugin_name, _plugin_instance in self.loaded_plugins.items(): + for plugin_name in self.loaded_plugins: plugin_info = component_registry.get_plugin_info(plugin_name) if plugin_info and plugin_info.python_dependencies: all_dependencies.append(plugin_info.python_dependencies) @@ -563,3 +565,6 @@ class PluginManager: ) else: logger.info(f"✅ 插件加载成功: {plugin_name}") + +# 全局插件管理器实例 +plugin_manager = PluginManager() \ No newline at end of file diff --git a/src/plugin_system/core/plugin_manager_bak.py b/src/plugin_system/core/plugin_manager_bak.py deleted file mode 100644 index 7bb74b6ee..000000000 --- a/src/plugin_system/core/plugin_manager_bak.py +++ /dev/null @@ -1,570 +0,0 @@ -from typing import Dict, List, Optional, Any, TYPE_CHECKING, Tuple -import os -import importlib -import importlib.util -from pathlib import Path -import traceback -from src.plugin_system.base.component_types import PythonDependency - -if TYPE_CHECKING: - from src.plugin_system.base.base_plugin import BasePlugin - -from src.common.logger import get_logger -from src.plugin_system.core.component_registry import component_registry -from src.plugin_system.core.dependency_manager import dependency_manager -from src.plugin_system.base.component_types import ComponentType, PluginInfo - -logger = get_logger("plugin_manager") - - -class PluginManager: - """插件管理器 - - 负责加载、初始化和管理所有插件及其组件 - """ - - def __init__(self): - self.plugin_directories: List[str] = [] - self.loaded_plugins: Dict[str, "BasePlugin"] = {} - self.failed_plugins: Dict[str, str] = {} - self.plugin_paths: Dict[str, str] = {} # 记录插件名到目录路径的映射 - - # 确保插件目录存在 - self._ensure_plugin_directories() - logger.info("插件管理器初始化完成") - - def _ensure_plugin_directories(self): - """确保所有插件目录存在,如果不存在则创建""" - default_directories = ["src/plugins/built_in", "plugins"] - - for directory in default_directories: - if not os.path.exists(directory): - os.makedirs(directory, exist_ok=True) - logger.info(f"创建插件目录: {directory}") - if directory not in self.plugin_directories: - self.plugin_directories.append(directory) - logger.debug(f"已添加插件目录: {directory}") - else: - logger.warning(f"插件不可重复加载: {directory}") - - def add_plugin_directory(self, directory: str): - """添加插件目录""" - if os.path.exists(directory): - if directory not in self.plugin_directories: - self.plugin_directories.append(directory) - logger.debug(f"已添加插件目录: {directory}") - else: - logger.warning(f"插件不可重复加载: {directory}") - else: - logger.warning(f"插件目录不存在: {directory}") - - def load_all_plugins(self) -> tuple[int, int]: - """加载所有插件目录中的插件 - - Returns: - tuple[int, int]: (插件数量, 组件数量) - """ - logger.debug("开始加载所有插件...") - - # 第一阶段:加载所有插件模块(注册插件类) - total_loaded_modules = 0 - total_failed_modules = 0 - - for directory in self.plugin_directories: - loaded, failed = self._load_plugin_modules_from_directory(directory) - total_loaded_modules += loaded - total_failed_modules += failed - - logger.debug(f"插件模块加载完成 - 成功: {total_loaded_modules}, 失败: {total_failed_modules}") - - # 第二阶段:实例化所有已注册的插件类 - from src.plugin_system.base.base_plugin import get_registered_plugin_classes - - plugin_classes = get_registered_plugin_classes() - total_registered = 0 - total_failed_registration = 0 - - for plugin_name, plugin_class in plugin_classes.items(): - try: - # 使用记录的插件目录路径 - plugin_dir = self.plugin_paths.get(plugin_name) - - # 如果没有记录,则尝试查找(fallback) - if not plugin_dir: - plugin_dir = self._find_plugin_directory(plugin_class) - if plugin_dir: - self.plugin_paths[plugin_name] = plugin_dir # 实例化插件(可能因为缺少manifest而失败) - plugin_instance = plugin_class(plugin_dir=plugin_dir) - - # 检查插件是否启用 - if not plugin_instance.enable_plugin: - logger.info(f"插件 {plugin_name} 已禁用,跳过加载") - continue - - # 检查版本兼容性 - is_compatible, compatibility_error = self.check_plugin_version_compatibility( - plugin_name, plugin_instance.manifest_data - ) - if not is_compatible: - total_failed_registration += 1 - self.failed_plugins[plugin_name] = compatibility_error - logger.error(f"❌ 插件加载失败: {plugin_name} - {compatibility_error}") - continue - - if plugin_instance.register_plugin(): - total_registered += 1 - self.loaded_plugins[plugin_name] = plugin_instance - - # 📊 显示插件详细信息 - plugin_info = component_registry.get_plugin_info(plugin_name) - if plugin_info: - component_types = {} - for comp in plugin_info.components: - comp_type = comp.component_type.name - component_types[comp_type] = component_types.get(comp_type, 0) + 1 - - components_str = ", ".join([f"{count}个{ctype}" for ctype, count in component_types.items()]) - - # 显示manifest信息 - manifest_info = "" - if plugin_info.license: - manifest_info += f" [{plugin_info.license}]" - if plugin_info.keywords: - manifest_info += f" 关键词: {', '.join(plugin_info.keywords[:3])}" # 只显示前3个关键词 - if len(plugin_info.keywords) > 3: - manifest_info += "..." - - logger.info( - f"✅ 插件加载成功: {plugin_name} v{plugin_info.version} ({components_str}){manifest_info} - {plugin_info.description}" - ) - else: - logger.info(f"✅ 插件加载成功: {plugin_name}") - else: - total_failed_registration += 1 - self.failed_plugins[plugin_name] = "插件注册失败" - logger.error(f"❌ 插件注册失败: {plugin_name}") - - except FileNotFoundError as e: - # manifest文件缺失 - total_failed_registration += 1 - error_msg = f"缺少manifest文件: {str(e)}" - self.failed_plugins[plugin_name] = error_msg - logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") - - except ValueError as e: - # manifest文件格式错误或验证失败 - traceback.print_exc() - total_failed_registration += 1 - error_msg = f"manifest验证失败: {str(e)}" - self.failed_plugins[plugin_name] = error_msg - logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") - - except Exception as e: - # 其他错误 - total_failed_registration += 1 - error_msg = f"未知错误: {str(e)}" - self.failed_plugins[plugin_name] = error_msg - logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}") - logger.debug("详细错误信息: ", exc_info=True) - - # 获取组件统计信息 - stats = component_registry.get_registry_stats() - action_count = stats.get("action_components", 0) - command_count = stats.get("command_components", 0) - total_components = stats.get("total_components", 0) - - # 📋 显示插件加载总览 - if total_registered > 0: - logger.info("🎉 插件系统加载完成!") - logger.info( - f"📊 总览: {total_registered}个插件, {total_components}个组件 (Action: {action_count}, Command: {command_count})" - ) - - # 显示详细的插件列表 logger.info("📋 已加载插件详情:") - for plugin_name, _plugin_class in self.loaded_plugins.items(): - plugin_info = component_registry.get_plugin_info(plugin_name) - if plugin_info: - # 插件基本信息 - version_info = f"v{plugin_info.version}" if plugin_info.version else "" - author_info = f"by {plugin_info.author}" if plugin_info.author else "unknown" - license_info = f"[{plugin_info.license}]" if plugin_info.license else "" - info_parts = [part for part in [version_info, author_info, license_info] if part] - extra_info = f" ({', '.join(info_parts)})" if info_parts else "" - - logger.info(f" 📦 {plugin_name}{extra_info}") - - # Manifest信息 - if plugin_info.manifest_data: - if plugin_info.keywords: - logger.info(f" 🏷️ 关键词: {', '.join(plugin_info.keywords)}") - if plugin_info.categories: - logger.info(f" 📁 分类: {', '.join(plugin_info.categories)}") - if plugin_info.homepage_url: - logger.info(f" 🌐 主页: {plugin_info.homepage_url}") - - # 组件列表 - if plugin_info.components: - action_components = [c for c in plugin_info.components if c.component_type.name == "ACTION"] - command_components = [c for c in plugin_info.components if c.component_type.name == "COMMAND"] - - if action_components: - action_names = [c.name for c in action_components] - logger.info(f" 🎯 Action组件: {', '.join(action_names)}") - - if command_components: - command_names = [c.name for c in command_components] - logger.info(f" ⚡ Command组件: {', '.join(command_names)}") - - # 版本兼容性信息 - if plugin_info.min_host_version or plugin_info.max_host_version: - version_range = "" - if plugin_info.min_host_version: - version_range += f">={plugin_info.min_host_version}" - if plugin_info.max_host_version: - if version_range: - version_range += f", <={plugin_info.max_host_version}" - else: - version_range += f"<={plugin_info.max_host_version}" - logger.info(f" 📋 兼容版本: {version_range}") - - # 依赖信息 - if plugin_info.dependencies: - logger.info(f" 🔗 依赖: {', '.join(plugin_info.dependencies)}") - - # 配置文件信息 - if plugin_info.config_file: - config_status = "✅" if self.plugin_paths.get(plugin_name) else "❌" - logger.info(f" ⚙️ 配置: {plugin_info.config_file} {config_status}") - - # 显示目录统计 - logger.info("📂 加载目录统计:") - for directory in self.plugin_directories: - if os.path.exists(directory): - plugins_in_dir = [] - for plugin_name in self.loaded_plugins.keys(): - plugin_path = self.plugin_paths.get(plugin_name, "") - if plugin_path.startswith(directory): - plugins_in_dir.append(plugin_name) - - if plugins_in_dir: - logger.info(f" 📁 {directory}: {len(plugins_in_dir)}个插件 ({', '.join(plugins_in_dir)})") - else: - logger.info(f" 📁 {directory}: 0个插件") - - # 失败信息 - if total_failed_registration > 0: - logger.info(f"⚠️ 失败统计: {total_failed_registration}个插件加载失败") - for failed_plugin, error in self.failed_plugins.items(): - logger.info(f" ❌ {failed_plugin}: {error}") - else: - logger.warning("😕 没有成功加载任何插件") - - # 返回插件数量和组件数量 - return total_registered, total_components - - def _find_plugin_directory(self, plugin_class) -> Optional[str]: - """查找插件类对应的目录路径""" - try: - import inspect - - module = inspect.getmodule(plugin_class) - if module and hasattr(module, "__file__") and module.__file__: - return os.path.dirname(module.__file__) - except Exception as e: - logger.debug(f"通过inspect获取插件目录失败: {e}") - return None - - def _load_plugin_modules_from_directory(self, directory: str) -> tuple[int, int]: - """从指定目录加载插件模块""" - loaded_count = 0 - failed_count = 0 - - if not os.path.exists(directory): - logger.warning(f"插件目录不存在: {directory}") - return loaded_count, failed_count - - logger.debug(f"正在扫描插件目录: {directory}") - - # 遍历目录中的所有Python文件和包 - for item in os.listdir(directory): - item_path = os.path.join(directory, item) - - if os.path.isfile(item_path) and item.endswith(".py") and item != "__init__.py": - # 单文件插件 - plugin_name = Path(item_path).stem - if self._load_plugin_module_file(item_path, plugin_name, directory): - loaded_count += 1 - else: - failed_count += 1 - - elif os.path.isdir(item_path) and not item.startswith(".") and not item.startswith("__"): - # 插件包 - plugin_file = os.path.join(item_path, "plugin.py") - if os.path.exists(plugin_file): - plugin_name = item # 使用目录名作为插件名 - if self._load_plugin_module_file(plugin_file, plugin_name, item_path): - loaded_count += 1 - else: - failed_count += 1 - - return loaded_count, failed_count - - def _load_plugin_module_file(self, plugin_file: str, plugin_name: str, plugin_dir: str) -> bool: - """加载单个插件模块文件 - - Args: - plugin_file: 插件文件路径 - plugin_name: 插件名称 - plugin_dir: 插件目录路径 - """ - # 生成模块名 - plugin_path = Path(plugin_file) - if plugin_path.parent.name != "plugins": - # 插件包格式:parent_dir.plugin - module_name = f"plugins.{plugin_path.parent.name}.plugin" - else: - # 单文件格式:plugins.filename - module_name = f"plugins.{plugin_path.stem}" - - try: - # 动态导入插件模块 - spec = importlib.util.spec_from_file_location(module_name, plugin_file) - if spec is None or spec.loader is None: - logger.error(f"无法创建模块规范: {plugin_file}") - return False - - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - # 记录插件名和目录路径的映射 - self.plugin_paths[plugin_name] = plugin_dir - - logger.debug(f"插件模块加载成功: {plugin_file}") - return True - - except Exception as e: - error_msg = f"加载插件模块 {plugin_file} 失败: {e}" - logger.error(error_msg) - self.failed_plugins[plugin_name] = error_msg - return False - - def get_loaded_plugins(self) -> List[PluginInfo]: - """获取所有已加载的插件信息""" - return list(component_registry.get_all_plugins().values()) - - def get_enabled_plugins(self) -> List[PluginInfo]: - """获取所有启用的插件信息""" - return list(component_registry.get_enabled_plugins().values()) - - def enable_plugin(self, plugin_name: str) -> bool: - """启用插件""" - plugin_info = component_registry.get_plugin_info(plugin_name) - if plugin_info: - plugin_info.enabled = True - # 启用插件的所有组件 - for component in plugin_info.components: - component_registry.enable_component(component.name) - logger.debug(f"已启用插件: {plugin_name}") - return True - return False - - def disable_plugin(self, plugin_name: str) -> bool: - """禁用插件""" - plugin_info = component_registry.get_plugin_info(plugin_name) - if plugin_info: - plugin_info.enabled = False - # 禁用插件的所有组件 - for component in plugin_info.components: - component_registry.disable_component(component.name) - logger.debug(f"已禁用插件: {plugin_name}") - return True - return False - - def get_plugin_instance(self, plugin_name: str) -> Optional["BasePlugin"]: - """获取插件实例 - - Args: - plugin_name: 插件名称 - - Returns: - Optional[BasePlugin]: 插件实例或None - """ - return self.loaded_plugins.get(plugin_name) - - def get_plugin_stats(self) -> Dict[str, Any]: - """获取插件统计信息""" - all_plugins = component_registry.get_all_plugins() - enabled_plugins = component_registry.get_enabled_plugins() - - action_components = component_registry.get_components_by_type(ComponentType.ACTION) - command_components = component_registry.get_components_by_type(ComponentType.COMMAND) - - return { - "total_plugins": len(all_plugins), - "enabled_plugins": len(enabled_plugins), - "failed_plugins": len(self.failed_plugins), - "total_components": len(action_components) + len(command_components), - "action_components": len(action_components), - "command_components": len(command_components), - "loaded_plugin_files": len(self.loaded_plugins), - "failed_plugin_details": self.failed_plugins.copy(), - } - - def reload_plugin(self, plugin_name: str) -> bool: - """重新加载插件(高级功能,需要谨慎使用)""" - # TODO: 实现插件热重载功能 - logger.warning("插件热重载功能尚未实现") - return False - - def check_all_dependencies(self, auto_install: bool = False) -> Dict[str, any]: - """检查所有插件的Python依赖包 - - Args: - auto_install: 是否自动安装缺失的依赖包 - - Returns: - Dict[str, any]: 检查结果摘要 - """ - logger.info("开始检查所有插件的Python依赖包...") - - all_required_missing: List[PythonDependency] = [] - all_optional_missing: List[PythonDependency] = [] - plugin_status = {} - - for plugin_name, _plugin_instance in self.loaded_plugins.items(): - plugin_info = component_registry.get_plugin_info(plugin_name) - if not plugin_info or not plugin_info.python_dependencies: - plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []} - continue - - logger.info(f"检查插件 {plugin_name} 的依赖...") - - missing_required, missing_optional = dependency_manager.check_dependencies(plugin_info.python_dependencies) - - if missing_required: - all_required_missing.extend(missing_required) - plugin_status[plugin_name] = { - "status": "missing_required", - "missing": [dep.package_name for dep in missing_required], - "optional_missing": [dep.package_name for dep in missing_optional], - } - logger.error(f"插件 {plugin_name} 缺少必需依赖: {[dep.package_name for dep in missing_required]}") - elif missing_optional: - all_optional_missing.extend(missing_optional) - plugin_status[plugin_name] = { - "status": "missing_optional", - "missing": [], - "optional_missing": [dep.package_name for dep in missing_optional], - } - logger.warning(f"插件 {plugin_name} 缺少可选依赖: {[dep.package_name for dep in missing_optional]}") - else: - plugin_status[plugin_name] = {"status": "ok", "missing": []} - logger.info(f"插件 {plugin_name} 依赖检查通过") - - # 汇总结果 - total_missing = len({dep.package_name for dep in all_required_missing}) - total_optional_missing = len({dep.package_name for dep in all_optional_missing}) - - logger.info(f"依赖检查完成 - 缺少必需包: {total_missing}个, 缺少可选包: {total_optional_missing}个") - - # 如果需要自动安装 - install_success = True - if auto_install and all_required_missing: - # 去重 - unique_required = {} - for dep in all_required_missing: - unique_required[dep.package_name] = dep - - logger.info(f"开始自动安装 {len(unique_required)} 个必需依赖包...") - install_success = dependency_manager.install_dependencies(list(unique_required.values()), auto_install=True) - - return { - "total_plugins_checked": len(plugin_status), - "plugins_with_missing_required": len( - [p for p in plugin_status.values() if p["status"] == "missing_required"] - ), - "plugins_with_missing_optional": len( - [p for p in plugin_status.values() if p["status"] == "missing_optional"] - ), - "total_missing_required": total_missing, - "total_missing_optional": total_optional_missing, - "plugin_status": plugin_status, - "auto_install_attempted": auto_install and bool(all_required_missing), - "auto_install_success": install_success, - "install_summary": dependency_manager.get_install_summary(), - } - - def generate_plugin_requirements(self, output_path: str = "plugin_requirements.txt") -> bool: - """生成所有插件依赖的requirements文件 - - Args: - output_path: 输出文件路径 - - Returns: - bool: 生成是否成功 - """ - logger.info("开始生成插件依赖requirements文件...") - - all_dependencies = [] - - for plugin_name, _plugin_instance in self.loaded_plugins.items(): - plugin_info = component_registry.get_plugin_info(plugin_name) - if plugin_info and plugin_info.python_dependencies: - all_dependencies.append(plugin_info.python_dependencies) - - if not all_dependencies: - logger.info("没有找到任何插件依赖") - return False - - return dependency_manager.generate_requirements_file(all_dependencies, output_path) - - def check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]: - """检查插件版本兼容性 - - Args: - plugin_name: 插件名称 - manifest_data: manifest数据 - - Returns: - Tuple[bool, str]: (是否兼容, 错误信息) - """ - if "host_application" not in manifest_data: - # 没有版本要求,默认兼容 - return True, "" - - host_app = manifest_data["host_application"] - if not isinstance(host_app, dict): - return True, "" - - min_version = host_app.get("min_version", "") - max_version = host_app.get("max_version", "") - - if not min_version and not max_version: - return True, "" - - try: - from src.plugin_system.utils.manifest_utils import VersionComparator - - current_version = VersionComparator.get_current_host_version() - is_compatible, error_msg = VersionComparator.is_version_in_range(current_version, min_version, max_version) - - if not is_compatible: - return False, f"版本不兼容: {error_msg}" - else: - logger.debug(f"插件 {plugin_name} 版本兼容性检查通过") - return True, "" - - except Exception as e: - logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}") - return True, "" # 检查失败时默认允许加载 - - -# 全局插件管理器实例 -plugin_manager = PluginManager() - -# 注释掉以解决插件目录重复加载的情况 -# 默认插件目录 -# plugin_manager.add_plugin_directory("src/plugins/built_in") -# plugin_manager.add_plugin_directory("src/plugins/examples") -# 用户插件目录 -# plugin_manager.add_plugin_directory("plugins") diff --git a/src/plugin_system/utils/__init__.py b/src/plugin_system/utils/__init__.py index 10a4fef34..c64a34660 100644 --- a/src/plugin_system/utils/__init__.py +++ b/src/plugin_system/utils/__init__.py @@ -4,11 +4,16 @@ 提供插件开发和管理的实用工具 """ -from src.plugin_system.utils.manifest_utils import ( +from .manifest_utils import ( ManifestValidator, ManifestGenerator, validate_plugin_manifest, generate_plugin_manifest, ) -__all__ = ["ManifestValidator", "ManifestGenerator", "validate_plugin_manifest", "generate_plugin_manifest"] +__all__ = [ + "ManifestValidator", + "ManifestGenerator", + "validate_plugin_manifest", + "generate_plugin_manifest", +] diff --git a/src/plugin_system/utils/manifest_utils.py b/src/plugin_system/utils/manifest_utils.py index 7be7ba900..b6e5a1f30 100644 --- a/src/plugin_system/utils/manifest_utils.py +++ b/src/plugin_system/utils/manifest_utils.py @@ -305,7 +305,7 @@ class ManifestValidator: # 检查URL格式(可选字段) for url_field in ["homepage_url", "repository_url"]: if url_field in manifest_data and manifest_data[url_field]: - url = manifest_data[url_field] + url: str = manifest_data[url_field] if not (url.startswith("http://") or url.startswith("https://")): self.validation_warnings.append(f"{url_field}建议使用完整的URL格式") diff --git a/src/plugins/built_in/tts_plugin/plugin.py b/src/plugins/built_in/tts_plugin/plugin.py index d60186a13..b72106b0b 100644 --- a/src/plugins/built_in/tts_plugin/plugin.py +++ b/src/plugins/built_in/tts_plugin/plugin.py @@ -1,4 +1,5 @@ -from src.plugin_system.base.base_plugin import BasePlugin, register_plugin +from src.plugin_system.apis.plugin_register_api import register_plugin +from src.plugin_system.base.base_plugin import BasePlugin from src.plugin_system.base.component_types import ComponentInfo from src.common.logger import get_logger from src.plugin_system.base.base_action import BaseAction, ActionActivationType, ChatMode diff --git a/src/plugins/built_in/vtb_plugin/plugin.py b/src/plugins/built_in/vtb_plugin/plugin.py index a87071e63..2932205b5 100644 --- a/src/plugins/built_in/vtb_plugin/plugin.py +++ b/src/plugins/built_in/vtb_plugin/plugin.py @@ -1,4 +1,5 @@ -from src.plugin_system.base.base_plugin import BasePlugin, register_plugin +from src.plugin_system.apis.plugin_register_api import register_plugin +from src.plugin_system.base.base_plugin import BasePlugin from src.plugin_system.base.component_types import ComponentInfo from src.common.logger import get_logger from src.plugin_system.base.base_action import BaseAction, ActionActivationType, ChatMode