暴露全部api,解决循环import问题

This commit is contained in:
UnCLAS-Prommer
2025-07-08 00:10:31 +08:00
parent 6633d5e273
commit 36974197a8
14 changed files with 89 additions and 702 deletions

View File

@@ -36,11 +36,12 @@ import urllib.error
import base64 import base64
import traceback 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_action import BaseAction
from src.plugin_system.base.base_command import BaseCommand 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.component_types import ComponentInfo, ActionActivationType, ChatMode
from src.plugin_system.base.config_types import ConfigField from src.plugin_system.base.config_types import ConfigField
from src.plugin_system import register_plugin
from src.common.logger import get_logger from src.common.logger import get_logger
logger = get_logger("take_picture_plugin") logger = get_logger("take_picture_plugin")

View File

@@ -5,11 +5,11 @@ MaiBot 插件系统
""" """
# 导出主要的公共接口 # 导出主要的公共接口
from src.plugin_system.base.base_plugin import BasePlugin, register_plugin from .base import (
from src.plugin_system.base.base_action import BaseAction BasePlugin,
from src.plugin_system.base.base_command import BaseCommand BaseAction,
from src.plugin_system.base.config_types import ConfigField BaseCommand,
from src.plugin_system.base.component_types import ( ConfigField,
ComponentType, ComponentType,
ActionActivationType, ActionActivationType,
ChatMode, ChatMode,
@@ -19,18 +19,22 @@ from src.plugin_system.base.component_types import (
PluginInfo, PluginInfo,
PythonDependency, PythonDependency,
) )
from src.plugin_system.core.plugin_manager import plugin_manager from .core.plugin_manager import (
from src.plugin_system.core.component_registry import component_registry plugin_manager,
from src.plugin_system.core.dependency_manager import dependency_manager component_registry,
dependency_manager,
)
# 导入工具模块 # 导入工具模块
from src.plugin_system.utils import ( from .utils import (
ManifestValidator, ManifestValidator,
ManifestGenerator, ManifestGenerator,
validate_plugin_manifest, validate_plugin_manifest,
generate_plugin_manifest, generate_plugin_manifest,
) )
from .apis.plugin_register_api import register_plugin
__version__ = "1.0.0" __version__ = "1.0.0"

View File

@@ -16,6 +16,7 @@ from src.plugin_system.apis import (
person_api, person_api,
send_api, send_api,
utils_api, utils_api,
plugin_register_api,
) )
# 导出所有API模块使它们可以通过 apis.xxx 方式访问 # 导出所有API模块使它们可以通过 apis.xxx 方式访问
@@ -30,4 +31,5 @@ __all__ = [
"person_api", "person_api",
"send_api", "send_api",
"utils_api", "utils_api",
"plugin_register_api",
] ]

View File

@@ -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

View File

@@ -4,10 +4,10 @@
提供插件开发的基础类和类型定义 提供插件开发的基础类和类型定义
""" """
from src.plugin_system.base.base_plugin import BasePlugin, register_plugin from .base_plugin import BasePlugin
from src.plugin_system.base.base_action import BaseAction from .base_action import BaseAction
from src.plugin_system.base.base_command import BaseCommand from .base_command import BaseCommand
from src.plugin_system.base.component_types import ( from .component_types import (
ComponentType, ComponentType,
ActionActivationType, ActionActivationType,
ChatMode, ChatMode,
@@ -15,13 +15,14 @@ from src.plugin_system.base.component_types import (
ActionInfo, ActionInfo,
CommandInfo, CommandInfo,
PluginInfo, PluginInfo,
PythonDependency,
) )
from .config_types import ConfigField
__all__ = [ __all__ = [
"BasePlugin", "BasePlugin",
"BaseAction", "BaseAction",
"BaseCommand", "BaseCommand",
"register_plugin",
"ComponentType", "ComponentType",
"ActionActivationType", "ActionActivationType",
"ChatMode", "ChatMode",
@@ -29,4 +30,6 @@ __all__ = [
"ActionInfo", "ActionInfo",
"CommandInfo", "CommandInfo",
"PluginInfo", "PluginInfo",
"PythonDependency",
"ConfigField",
] ]

View File

@@ -4,6 +4,9 @@ import os
import inspect import inspect
import toml import toml
import json import json
import shutil
import datetime
from src.common.logger import get_logger from src.common.logger import get_logger
from src.plugin_system.base.component_types import ( from src.plugin_system.base.component_types import (
PluginInfo, PluginInfo,
@@ -11,13 +14,10 @@ from src.plugin_system.base.component_types import (
PythonDependency, PythonDependency,
) )
from src.plugin_system.base.config_types import ConfigField 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") logger = get_logger("base_plugin")
# 全局插件类注册表
_plugin_classes: Dict[str, Type["BasePlugin"]] = {}
class BasePlugin(ABC): 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 # 是否启用插件 enable_plugin: bool = False # 是否启用插件
dependencies: List[str] = [] # 依赖的其他插件 dependencies: List[str] = [] # 依赖的其他插件
python_dependencies: List[PythonDependency] = [] # Python包依赖 python_dependencies: List[PythonDependency] = [] # Python包依赖
@@ -103,7 +103,7 @@ class BasePlugin(ABC):
if not self.get_manifest_info("description"): if not self.get_manifest_info("description"):
raise ValueError(f"插件 {self.plugin_name} 的manifest中缺少description字段") raise ValueError(f"插件 {self.plugin_name} 的manifest中缺少description字段")
def _load_manifest(self): def _load_manifest(self): # sourcery skip: raise-from-previous-error
"""加载manifest文件强制要求""" """加载manifest文件强制要求"""
if not self.plugin_dir: if not self.plugin_dir:
raise ValueError(f"{self.log_prefix} 没有插件目录路径无法加载manifest") raise ValueError(f"{self.log_prefix} 没有插件目录路径无法加载manifest")
@@ -124,9 +124,6 @@ class BasePlugin(ABC):
# 验证manifest格式 # 验证manifest格式
self._validate_manifest() self._validate_manifest()
# 从manifest覆盖插件基本信息如果插件类中未定义
self._apply_manifest_overrides()
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
error_msg = f"{self.log_prefix} manifest文件格式错误: {e}" error_msg = f"{self.log_prefix} manifest文件格式错误: {e}"
logger.error(error_msg) logger.error(error_msg)
@@ -136,15 +133,6 @@ class BasePlugin(ABC):
logger.error(error_msg) logger.error(error_msg)
raise IOError(error_msg) # noqa 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: def _get_author_name(self) -> str:
"""从manifest获取作者名称""" """从manifest获取作者名称"""
author_info = self.get_manifest_info("author", {}) author_info = self.get_manifest_info("author", {})
@@ -156,10 +144,7 @@ class BasePlugin(ABC):
def _validate_manifest(self): def _validate_manifest(self):
"""验证manifest文件格式使用强化的验证器""" """验证manifest文件格式使用强化的验证器"""
if not self.manifest_data: if not self.manifest_data:
return raise ValueError(f"{self.log_prefix} manifest数据为空验证失败")
# 导入验证器
from src.plugin_system.utils.manifest_utils import ManifestValidator
validator = ManifestValidator() validator = ManifestValidator()
is_valid = validator.validate_manifest(self.manifest_data) is_valid = validator.validate_manifest(self.manifest_data)
@@ -176,36 +161,6 @@ class BasePlugin(ABC):
error_msg += f": {'; '.join(validator.validation_errors)}" error_msg += f": {'; '.join(validator.validation_errors)}"
raise ValueError(error_msg) 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: def get_manifest_info(self, key: str, default: Any = None) -> Any:
"""获取manifest信息 """获取manifest信息
@@ -304,9 +259,6 @@ class BasePlugin(ABC):
def _backup_config_file(self, config_file_path: str) -> str: 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") timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{config_file_path}.backup_{timestamp}" backup_path = f"{config_file_path}.backup_{timestamp}"
@@ -377,13 +329,14 @@ class BasePlugin(ABC):
logger.warning(f"{self.log_prefix} 配置节 {section_name} 结构已改变,使用默认值") 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: if section_name not in migrated_config:
logger.warning(f"{self.log_prefix} 配置节 {section_name} 在新版本中已被移除") logger.warning(f"{self.log_prefix} 配置节 {section_name} 在新版本中已被移除")
return migrated_config return migrated_config
def _generate_config_from_schema(self) -> Dict[str, Any]: def _generate_config_from_schema(self) -> Dict[str, Any]:
# sourcery skip: dict-comprehension
"""根据schema生成配置数据结构不写入文件""" """根据schema生成配置数据结构不写入文件"""
if not self.config_schema: if not self.config_schema:
return {} return {}
@@ -473,7 +426,7 @@ class BasePlugin(ABC):
except IOError as e: except IOError as e:
logger.error(f"{self.log_prefix} 保存配置文件失败: {e}", exc_info=True) 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: if not self.config_file_name:
logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载") logger.debug(f"{self.log_prefix} 未指定配置文件,跳过加载")
@@ -568,7 +521,7 @@ class BasePlugin(ABC):
def register_plugin(self) -> bool: def register_plugin(self) -> bool:
"""注册插件及其所有组件""" """注册插件及其所有组件"""
from src.plugin_system.core.component_registry import component_registry
components = self.get_plugin_components() components = self.get_plugin_components()
# 检查依赖 # 检查依赖
@@ -598,6 +551,7 @@ class BasePlugin(ABC):
def _check_dependencies(self) -> bool: def _check_dependencies(self) -> bool:
"""检查插件依赖""" """检查插件依赖"""
from src.plugin_system.core.component_registry import component_registry
if not self.dependencies: if not self.dependencies:
return True return True
@@ -629,52 +583,3 @@ class BasePlugin(ABC):
return default return default
return current 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

View File

@@ -6,8 +6,9 @@
from src.plugin_system.core.plugin_manager import plugin_manager 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.component_registry import component_registry
from src.plugin_system.core.dependency_manager import dependency_manager
__all__ = [ __all__ = [
"plugin_manager", "plugin_manager",
"component_registry", "component_registry",
"dependency_manager",
] ]

View File

@@ -9,8 +9,8 @@ from src.plugin_system.base.component_types import (
ComponentType, ComponentType,
) )
from ..base.base_command import BaseCommand from src.plugin_system.base.base_command import BaseCommand
from ..base.base_action import BaseAction from src.plugin_system.base.base_action import BaseAction
logger = get_logger("component_registry") logger = get_logger("component_registry")

View File

@@ -90,6 +90,8 @@ class PluginManager:
else: else:
total_failed_registration += 1 total_failed_registration += 1
return total_registered, total_failed_registration
def load_registered_plugin_classes(self, plugin_name: str) -> bool: def load_registered_plugin_classes(self, plugin_name: str) -> bool:
# sourcery skip: extract-duplicate-method, extract-method # sourcery skip: extract-duplicate-method, extract-method
""" """
@@ -255,7 +257,7 @@ class PluginManager:
all_optional_missing: List[PythonDependency] = [] all_optional_missing: List[PythonDependency] = []
plugin_status = {} 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) plugin_info = component_registry.get_plugin_info(plugin_name)
if not plugin_info or not plugin_info.python_dependencies: if not plugin_info or not plugin_info.python_dependencies:
plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []} plugin_status[plugin_name] = {"status": "no_dependencies", "missing": []}
@@ -327,7 +329,7 @@ class PluginManager:
all_dependencies = [] 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) plugin_info = component_registry.get_plugin_info(plugin_name)
if plugin_info and plugin_info.python_dependencies: if plugin_info and plugin_info.python_dependencies:
all_dependencies.append(plugin_info.python_dependencies) all_dependencies.append(plugin_info.python_dependencies)
@@ -563,3 +565,6 @@ class PluginManager:
) )
else: else:
logger.info(f"✅ 插件加载成功: {plugin_name}") logger.info(f"✅ 插件加载成功: {plugin_name}")
# 全局插件管理器实例
plugin_manager = PluginManager()

View File

@@ -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")

View File

@@ -4,11 +4,16 @@
提供插件开发和管理的实用工具 提供插件开发和管理的实用工具
""" """
from src.plugin_system.utils.manifest_utils import ( from .manifest_utils import (
ManifestValidator, ManifestValidator,
ManifestGenerator, ManifestGenerator,
validate_plugin_manifest, validate_plugin_manifest,
generate_plugin_manifest, generate_plugin_manifest,
) )
__all__ = ["ManifestValidator", "ManifestGenerator", "validate_plugin_manifest", "generate_plugin_manifest"] __all__ = [
"ManifestValidator",
"ManifestGenerator",
"validate_plugin_manifest",
"generate_plugin_manifest",
]

View File

@@ -305,7 +305,7 @@ class ManifestValidator:
# 检查URL格式可选字段 # 检查URL格式可选字段
for url_field in ["homepage_url", "repository_url"]: for url_field in ["homepage_url", "repository_url"]:
if url_field in manifest_data and manifest_data[url_field]: 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://")): if not (url.startswith("http://") or url.startswith("https://")):
self.validation_warnings.append(f"{url_field}建议使用完整的URL格式") self.validation_warnings.append(f"{url_field}建议使用完整的URL格式")

View File

@@ -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.plugin_system.base.component_types import ComponentInfo
from src.common.logger import get_logger from src.common.logger import get_logger
from src.plugin_system.base.base_action import BaseAction, ActionActivationType, ChatMode from src.plugin_system.base.base_action import BaseAction, ActionActivationType, ChatMode

View File

@@ -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.plugin_system.base.component_types import ComponentInfo
from src.common.logger import get_logger from src.common.logger import get_logger
from src.plugin_system.base.base_action import BaseAction, ActionActivationType, ChatMode from src.plugin_system.base.base_action import BaseAction, ActionActivationType, ChatMode