refactor(plugin_system): 引入 PluginMetadata 替代 manifest.json

将插件元数据定义从外部 `_manifest.json` 文件迁移到插件 `__init__.py` 文件中的 `__plugin_meta__` 变量。此举简化了插件加载流程,减少了文件I/O,并使元数据与插件代码更紧密地耦合。

主要变更:
- 引入 `PluginMetadata` 数据类来标准化插件元数据。
- 插件基类 `PluginBase` 现在直接接收 `PluginMetadata` 对象,不再负责解析 JSON 文件。
- 插件管理器 `PluginManager` 调整加载逻辑,从插件模块的 `__plugin_meta__` 属性获取元数据。
- 删除了 `manifest_utils.py` 及其相关的验证和版本比较逻辑,简化了依赖关系。
- 更新了所有内置插件,以采用新的元数据定义方式,并删除了它们各自的 `_manifest.json` 文件。

BREAKING CHANGE: 插件加载机制已改变。所有插件必须在其 `__init__.py` 中定义一个 `__plugin_meta__` 变量,该变量是 `PluginMetadata` 类的实例,并移除旧的 `_manifest.json` 文件。
This commit is contained in:
minecraft1024a
2025-10-04 16:17:03 +08:00
parent 46d6acfdcc
commit 3764b3a8a6
28 changed files with 281 additions and 1061 deletions

View File

@@ -1,7 +1,10 @@
#!/usr/bin/env python3 from src.plugin_system.base.plugin_metadata import PluginMetadata
"""
Bilibili 插件包
提供B站视频观看体验功能像真实用户一样浏览和评价视频
"""
# 插件会通过 @register_plugin 装饰器自动注册,这里不需要额外的导入 __plugin_meta__ = PluginMetadata(
name="Bilibili Plugin",
description="A plugin for interacting with Bilibili.",
usage="Usage details for Bilibili plugin.",
version="1.0.0",
author="Your Name",
license="MIT",
)

View File

@@ -0,0 +1,10 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="Echo Example Plugin",
description="An example plugin that echoes messages.",
usage="!echo [message]",
version="1.0.0",
author="Your Name",
license="MIT",
)

View File

@@ -0,0 +1,10 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="Hello World Plugin",
description="A simple hello world plugin.",
usage="!hello",
version="1.0.0",
author="Your Name",
license="MIT",
)

View File

@@ -50,13 +50,6 @@ from .base import (
create_plus_command_adapter, create_plus_command_adapter,
) )
# 导入工具模块
from .utils import (
ManifestValidator,
# ManifestGenerator,
# validate_plugin_manifest,
# generate_plugin_manifest,
)
from .utils.dependency_config import configure_dependency_settings, get_dependency_config from .utils.dependency_config import configure_dependency_settings, get_dependency_config
# 导入依赖管理模块 # 导入依赖管理模块

View File

@@ -5,7 +5,6 @@ from abc import ABC, abstractmethod
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
import orjson
import toml import toml
from src.common.logger import get_logger from src.common.logger import get_logger
@@ -15,7 +14,7 @@ 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.utils.manifest_utils import ManifestValidator from src.plugin_system.base.plugin_metadata import PluginMetadata
logger = get_logger("plugin_base") logger = get_logger("plugin_base")
@@ -33,39 +32,34 @@ class PluginBase(ABC):
dependencies: list[str] = [] dependencies: list[str] = []
python_dependencies: list[str | PythonDependency] = [] python_dependencies: list[str | PythonDependency] = []
# manifest文件相关
manifest_file_name: str = "_manifest.json" # manifest文件名
manifest_data: dict[str, Any] = {} # manifest数据
config_schema: dict[str, dict[str, ConfigField] | str] = {} config_schema: dict[str, dict[str, ConfigField] | str] = {}
config_section_descriptions: dict[str, str] = {} config_section_descriptions: dict[str, str] = {}
def __init__(self, plugin_dir: str): def __init__(self, plugin_dir: str, metadata: PluginMetadata):
"""初始化插件 """初始化插件
Args: Args:
plugin_dir: 插件目录路径,由插件管理器传递 plugin_dir: 插件目录路径,由插件管理器传递
metadata: 插件元数据对象
""" """
self.config: dict[str, Any] = {} # 插件配置 self.config: dict[str, Any] = {} # 插件配置
self.plugin_dir = plugin_dir # 插件目录路径 self.plugin_dir = plugin_dir # 插件目录路径
self.plugin_meta = metadata # 插件元数据
self.log_prefix = f"[Plugin:{self.plugin_name}]" self.log_prefix = f"[Plugin:{self.plugin_name}]"
self._is_enabled = self.enable_plugin # 从插件定义中获取默认启用状态 self._is_enabled = self.enable_plugin # 从插件定义中获取默认启用状态
# 加载manifest文件
self._load_manifest()
# 验证插件信息 # 验证插件信息
self._validate_plugin_info() self._validate_plugin_info()
# 加载插件配置 # 加载插件配置
self._load_plugin_config() self._load_plugin_config()
# 从manifest获取显示信息 # 从元数据获取显示信息
self.display_name = self.get_manifest_info("name", self.plugin_name) self.display_name = self.plugin_meta.name
self.plugin_version = self.get_manifest_info("version", "1.0.0") self.plugin_version = self.plugin_meta.version
self.plugin_description = self.get_manifest_info("description", "") self.plugin_description = self.plugin_meta.description
self.plugin_author = self._get_author_name() self.plugin_author = self.plugin_meta.author
# 标准化Python依赖为PythonDependency对象 # 标准化Python依赖为PythonDependency对象
normalized_python_deps = self._normalize_python_dependencies(self.python_dependencies) normalized_python_deps = self._normalize_python_dependencies(self.python_dependencies)
@@ -85,15 +79,6 @@ class PluginBase(ABC):
config_file=self.config_file_name or "", config_file=self.config_file_name or "",
dependencies=self.dependencies.copy(), dependencies=self.dependencies.copy(),
python_dependencies=normalized_python_deps, python_dependencies=normalized_python_deps,
# manifest相关信息
manifest_data=self.manifest_data.copy(),
license=self.get_manifest_info("license", ""),
homepage_url=self.get_manifest_info("homepage_url", ""),
repository_url=self.get_manifest_info("repository_url", ""),
keywords=self.get_manifest_info("keywords", []).copy() if self.get_manifest_info("keywords") else [],
categories=self.get_manifest_info("categories", []).copy() if self.get_manifest_info("categories") else [],
min_host_version=self.get_manifest_info("host_application.min_version", ""),
max_host_version=self.get_manifest_info("host_application.max_version", ""),
) )
logger.debug(f"{self.log_prefix} 插件基类初始化完成") logger.debug(f"{self.log_prefix} 插件基类初始化完成")
@@ -103,93 +88,10 @@ class PluginBase(ABC):
if not self.plugin_name: if not self.plugin_name:
raise ValueError(f"插件类 {self.__class__.__name__} 必须定义 plugin_name") raise ValueError(f"插件类 {self.__class__.__name__} 必须定义 plugin_name")
# 验证manifest中的必需信息 if not self.plugin_meta.name:
if not self.get_manifest_info("name"): raise ValueError(f"插件 {self.plugin_name} 的元数据中缺少 name 字段")
raise ValueError(f"插件 {self.plugin_name} 的manifest中缺少name字段") if not self.plugin_meta.description:
if not self.get_manifest_info("description"): raise ValueError(f"插件 {self.plugin_name} 的元数据中缺少 description 字段")
raise ValueError(f"插件 {self.plugin_name} 的manifest中缺少description字段")
def _load_manifest(self): # sourcery skip: raise-from-previous-error
"""加载manifest文件强制要求"""
if not self.plugin_dir:
raise ValueError(f"{self.log_prefix} 没有插件目录路径无法加载manifest")
manifest_path = os.path.join(self.plugin_dir, self.manifest_file_name)
if not os.path.exists(manifest_path):
error_msg = f"{self.log_prefix} 缺少必需的manifest文件: {manifest_path}"
logger.error(error_msg)
raise FileNotFoundError(error_msg)
try:
with open(manifest_path, encoding="utf-8") as f:
self.manifest_data = orjson.loads(f.read())
logger.debug(f"{self.log_prefix} 成功加载manifest文件: {manifest_path}")
# 验证manifest格式
self._validate_manifest()
except orjson.JSONDecodeError as e:
error_msg = f"{self.log_prefix} manifest文件格式错误: {e}"
logger.error(error_msg)
raise ValueError(error_msg)
except OSError as e:
error_msg = f"{self.log_prefix} 读取manifest文件失败: {e}"
logger.error(error_msg)
raise IOError(error_msg) # noqa
def _get_author_name(self) -> str:
"""从manifest获取作者名称"""
author_info = self.get_manifest_info("author", {})
if isinstance(author_info, dict):
return author_info.get("name", "")
else:
return str(author_info) if author_info else ""
def _validate_manifest(self):
"""验证manifest文件格式使用强化的验证器"""
if not self.manifest_data:
raise ValueError(f"{self.log_prefix} manifest数据为空验证失败")
validator = ManifestValidator()
is_valid = validator.validate_manifest(self.manifest_data)
# 记录验证结果
if validator.validation_errors or validator.validation_warnings:
report = validator.get_validation_report()
logger.info(f"{self.log_prefix} Manifest验证结果:\n{report}")
# 如果有验证错误,抛出异常
if not is_valid:
error_msg = f"{self.log_prefix} Manifest文件验证失败"
if validator.validation_errors:
error_msg += f": {'; '.join(validator.validation_errors)}"
raise ValueError(error_msg)
def get_manifest_info(self, key: str, default: Any = None) -> Any:
"""获取manifest信息
Args:
key: 信息键,支持点分割的嵌套键(如 "author.name"
default: 默认值
Returns:
Any: 对应的值
"""
if not self.manifest_data:
return default
keys = key.split(".")
value = self.manifest_data
for k in keys:
if isinstance(value, dict) and k in value:
value = value[k]
else:
return default
return value
def _generate_and_save_default_config(self, config_file_path: str): def _generate_and_save_default_config(self, config_file_path: str):
"""根据插件的Schema生成并保存默认配置文件""" """根据插件的Schema生成并保存默认配置文件"""
@@ -198,7 +100,7 @@ class PluginBase(ABC):
return return
toml_str = f"# {self.plugin_name} - 自动生成的配置文件\n" toml_str = f"# {self.plugin_name} - 自动生成的配置文件\n"
plugin_description = self.get_manifest_info("description", "插件配置文件") plugin_description = self.plugin_meta.description or "插件配置文件"
toml_str += f"# {plugin_description}\n\n" toml_str += f"# {plugin_description}\n\n"
# 遍历每个配置节 # 遍历每个配置节
@@ -333,7 +235,7 @@ class PluginBase(ABC):
return return
toml_str = f"# {self.plugin_name} - 配置文件\n" toml_str = f"# {self.plugin_name} - 配置文件\n"
plugin_description = self.get_manifest_info("description", "插件配置文件") plugin_description = self.plugin_meta.description or "插件配置文件"
toml_str += f"# {plugin_description}\n\n" toml_str += f"# {plugin_description}\n\n"
# 遍历每个配置节 # 遍历每个配置节
@@ -564,3 +466,11 @@ class PluginBase(ABC):
bool: 是否成功注册插件 bool: 是否成功注册插件
""" """
raise NotImplementedError("Subclasses must implement this method") raise NotImplementedError("Subclasses must implement this method")
async def on_plugin_loaded(self):
"""插件加载完成后的钩子函数"""
pass
def on_unload(self):
"""插件卸载时的钩子函数"""
pass

View File

@@ -0,0 +1,25 @@
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Set
@dataclass
class PluginMetadata:
"""
插件元数据,用于存储插件的开发者信息和用户帮助信息。
"""
name: str # 插件名称 (供用户查看)
description: str # 插件功能描述
usage: str # 插件使用方法
# 以下为可选字段,参考自 _manifest.json 和 NoneBot 设计
type: Optional[str] = None # 插件类别: "library", "application"
# 从原 _manifest.json 迁移的字段
version: str = "1.0.0" # 插件版本
author: str = "" # 作者名称
license: Optional[str] = None # 开源协议
repository_url: Optional[str] = None # 仓库地址
keywords: List[str] = field(default_factory=list) # 关键词
categories: List[str] = field(default_factory=list) # 分类
# 扩展字段
extra: Dict[str, Any] = field(default_factory=dict) # 其他任意信息

View File

@@ -9,7 +9,7 @@ from typing import Any, Optional
from src.common.logger import get_logger from src.common.logger import get_logger
from src.plugin_system.base.component_types import ComponentType from src.plugin_system.base.component_types import ComponentType
from src.plugin_system.base.plugin_base import PluginBase from src.plugin_system.base.plugin_base import PluginBase
from src.plugin_system.utils.manifest_utils import VersionComparator from src.plugin_system.base.plugin_metadata import PluginMetadata
from .component_registry import component_registry from .component_registry import component_registry
@@ -27,6 +27,7 @@ class PluginManager:
self.plugin_directories: list[str] = [] # 插件根目录列表 self.plugin_directories: list[str] = [] # 插件根目录列表
self.plugin_classes: dict[str, type[PluginBase]] = {} # 全局插件类注册表,插件名 -> 插件类 self.plugin_classes: dict[str, type[PluginBase]] = {} # 全局插件类注册表,插件名 -> 插件类
self.plugin_paths: dict[str, str] = {} # 记录插件名到目录路径的映射,插件名 -> 目录路径 self.plugin_paths: dict[str, str] = {} # 记录插件名到目录路径的映射,插件名 -> 目录路径
self.plugin_modules: dict[str, Any] = {} # 记录插件名到模块的映射
self.loaded_plugins: dict[str, PluginBase] = {} # 已加载的插件类实例注册表,插件名 -> 插件类实例 self.loaded_plugins: dict[str, PluginBase] = {} # 已加载的插件类实例注册表,插件名 -> 插件类实例
self.failed_plugins: dict[str, str] = {} # 记录加载失败的插件文件及其错误信息,插件名 -> 错误信息 self.failed_plugins: dict[str, str] = {} # 记录加载失败的插件文件及其错误信息,插件名 -> 错误信息
@@ -102,23 +103,25 @@ class PluginManager:
if not plugin_dir: if not plugin_dir:
return False, 1 return False, 1
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件可能因为缺少manifest而失败 module = self.plugin_modules.get(plugin_name)
if not module or not hasattr(module, "__plugin_meta__"):
self.failed_plugins[plugin_name] = "插件模块中缺少 __plugin_meta__"
logger.error(f"❌ 插件加载失败: {plugin_name} - 缺少 __plugin_meta__")
return False, 1
metadata: PluginMetadata = getattr(module, "__plugin_meta__")
plugin_instance = plugin_class(plugin_dir=plugin_dir, metadata=metadata)
if not plugin_instance: if not plugin_instance:
logger.error(f"插件 {plugin_name} 实例化失败") logger.error(f"插件 {plugin_name} 实例化失败")
return False, 1 return False, 1
# 检查插件是否启用 # 检查插件是否启用
if not plugin_instance.enable_plugin: if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载") logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
return False, 0 return False, 0
# 检查版本兼容性
is_compatible, compatibility_error = self._check_plugin_version_compatibility(
plugin_name, plugin_instance.manifest_data
)
if not is_compatible:
self.failed_plugins[plugin_name] = compatibility_error
logger.error(f"❌ 插件加载失败: {plugin_name} - {compatibility_error}")
return False, 1
if plugin_instance.register_plugin(): if plugin_instance.register_plugin():
self.loaded_plugins[plugin_name] = plugin_instance self.loaded_plugins[plugin_name] = plugin_instance
self._show_plugin_components(plugin_name) self._show_plugin_components(plugin_name)
@@ -138,21 +141,6 @@ class PluginManager:
logger.error(f"❌ 插件注册失败: {plugin_name}") logger.error(f"❌ 插件注册失败: {plugin_name}")
return False, 1 return False, 1
except FileNotFoundError as e:
# manifest文件缺失
error_msg = f"缺少manifest文件: {e!s}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except ValueError as e:
# manifest文件格式错误或验证失败
traceback.print_exc()
error_msg = f"manifest验证失败: {e!s}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except Exception as e: except Exception as e:
# 其他错误 # 其他错误
error_msg = f"未知错误: {e!s}" error_msg = f"未知错误: {e!s}"
@@ -284,14 +272,23 @@ class PluginManager:
if os.path.isdir(item_path) and not item.startswith(".") and not item.startswith("__"): if os.path.isdir(item_path) and not item.startswith(".") and not item.startswith("__"):
plugin_file = os.path.join(item_path, "plugin.py") plugin_file = os.path.join(item_path, "plugin.py")
if os.path.exists(plugin_file): if os.path.exists(plugin_file):
if self._load_plugin_module_file(plugin_file): module = self._load_plugin_module_file(plugin_file)
if module:
# 动态查找插件类并获取真实的 plugin_name
for attr_name in dir(module):
attr = getattr(module, attr_name)
if isinstance(attr, type) and issubclass(attr, PluginBase) and attr is not PluginBase:
plugin_name = getattr(attr, "plugin_name", None)
if plugin_name:
self.plugin_modules[plugin_name] = module
break
loaded_count += 1 loaded_count += 1
else: else:
failed_count += 1 failed_count += 1
return loaded_count, failed_count return loaded_count, failed_count
def _load_plugin_module_file(self, plugin_file: str) -> bool: def _load_plugin_module_file(self, plugin_file: str) -> Optional[Any]:
# sourcery skip: extract-method # sourcery skip: extract-method
"""加载单个插件模块文件 """加载单个插件模块文件
@@ -305,62 +302,36 @@ class PluginManager:
module_name = ".".join(plugin_path.parent.parts) module_name = ".".join(plugin_path.parent.parts)
try: try:
# 动态导入插件模块 # 首先加载 __init__.py 来获取元数据
init_file = os.path.join(plugin_dir, "__init__.py")
if os.path.exists(init_file):
init_spec = spec_from_file_location(f"{module_name}.__init__", init_file)
if init_spec and init_spec.loader:
init_module = module_from_spec(init_spec)
init_spec.loader.exec_module(init_module)
# 然后加载 plugin.py
spec = spec_from_file_location(module_name, plugin_file) spec = spec_from_file_location(module_name, plugin_file)
if spec is None or spec.loader is None: if spec is None or spec.loader is None:
logger.error(f"无法创建模块规范: {plugin_file}") logger.error(f"无法创建模块规范: {plugin_file}")
return False return None
module = module_from_spec(spec) module = module_from_spec(spec)
module.__package__ = module_name # 设置模块包名 module.__package__ = module_name
spec.loader.exec_module(module) spec.loader.exec_module(module)
# 将 __plugin_meta__ 从 init_module 附加到主模块
if init_module and hasattr(init_module, "__plugin_meta__"):
setattr(module, "__plugin_meta__", getattr(init_module, "__plugin_meta__"))
logger.debug(f"插件模块加载成功: {plugin_file} -> {plugin_name} ({plugin_dir})") logger.debug(f"插件模块加载成功: {plugin_file} -> {plugin_name} ({plugin_dir})")
return True return module
except Exception as e: except Exception as e:
error_msg = f"加载插件模块 {plugin_file} 失败: {e}" error_msg = f"加载插件模块 {plugin_file} 失败: {e}"
logger.error(error_msg) logger.error(error_msg)
self.failed_plugins[plugin_name if "plugin_name" in locals() else module_name] = error_msg self.failed_plugins[plugin_name if "plugin_name" in locals() else module_name] = error_msg
return False return None
# == 兼容性检查 ==
@staticmethod
def _check_plugin_version_compatibility(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:
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}"
logger.debug(f"插件 {plugin_name} 版本兼容性检查通过")
return True, ""
except Exception as e:
logger.warning(f"插件 {plugin_name} 版本兼容性检查失败: {e}")
return False, f"插件 {plugin_name} 版本兼容性检查失败: {e}" # 检查失败时默认不允许加载
# == 显示统计与插件信息 == # == 显示统计与插件信息 ==
@@ -396,17 +367,6 @@ class PluginManager:
logger.info(f" 📦 {plugin_info.display_name}{extra_info}") logger.info(f" 📦 {plugin_info.display_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: if plugin_info.components:
action_components = [ action_components = [

View File

@@ -2,18 +2,4 @@
插件系统工具模块 插件系统工具模块
提供插件开发和管理的实用工具 提供插件开发和管理的实用工具
""" """
from .manifest_utils import (
ManifestValidator,
# ManifestGenerator,
# validate_plugin_manifest,
# generate_plugin_manifest,
)
__all__ = [
"ManifestValidator",
# "ManifestGenerator",
# "validate_plugin_manifest",
# "generate_plugin_manifest",
]

View File

@@ -1,517 +0,0 @@
"""
插件Manifest工具模块
提供manifest文件的验证、生成和管理功能
"""
import re
from typing import Any
from src.common.logger import get_logger
from src.config.config import MMC_VERSION
# if TYPE_CHECKING:
# from src.plugin_system.base.base_plugin import BasePlugin
logger = get_logger("manifest_utils")
class VersionComparator:
"""版本号比较器
支持语义化版本号比较自动处理snapshot版本并支持向前兼容性检查
"""
# 版本兼容性映射表(硬编码)
# 格式: {插件最大支持版本: [实际兼容的版本列表]}
COMPATIBILITY_MAP = {
# 0.8.x 系列向前兼容规则
"0.8.0": ["0.8.1", "0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.1": ["0.8.2", "0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.2": ["0.8.3", "0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.3": ["0.8.4", "0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.4": ["0.8.5", "0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.5": ["0.8.6", "0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.6": ["0.8.7", "0.8.8", "0.8.9", "0.8.10"],
"0.8.7": ["0.8.8", "0.8.9", "0.8.10"],
"0.8.8": ["0.8.9", "0.8.10"],
"0.8.9": ["0.8.10"],
# 可以根据需要添加更多兼容映射
# "0.9.0": ["0.9.1", "0.9.2", "0.9.3"], # 示例0.9.x系列兼容
}
@staticmethod
def normalize_version(version: str) -> str:
"""标准化版本号移除snapshot标识
Args:
version: 原始版本号,如 "0.8.0-snapshot.1"
Returns:
str: 标准化后的版本号,如 "0.8.0"
"""
if not version:
return "0.0.0"
# 移除snapshot部分
normalized = re.sub(r"-snapshot\.\d+", "", version.strip())
normalized = re.sub(r"-alpha\-\d+", "", version.strip())
# 确保版本号格式正确
if not re.match(r"^\d+(\.\d+){0,2}$", normalized):
# 如果不是有效的版本号格式,返回默认版本
return "0.0.0"
# 尝试补全版本号
parts = normalized.split(".")
while len(parts) < 3:
parts.append("0")
normalized = ".".join(parts[:3])
return normalized
@staticmethod
def parse_version(version: str) -> tuple[int, int, int]:
"""解析版本号为元组
Args:
version: 版本号字符串
Returns:
Tuple[int, int, int]: (major, minor, patch)
"""
normalized = VersionComparator.normalize_version(version)
try:
parts = normalized.split(".")
return int(parts[0]), int(parts[1]), int(parts[2])
except (ValueError, IndexError):
logger.warning(f"无法解析版本号: {version},使用默认版本 0.0.0")
return 0, 0, 0
@staticmethod
def compare_versions(version1: str, version2: str) -> int:
"""比较两个版本号
Args:
version1: 第一个版本号
version2: 第二个版本号
Returns:
int: -1 if version1 < version2, 0 if equal, 1 if version1 > version2
"""
v1_tuple = VersionComparator.parse_version(version1)
v2_tuple = VersionComparator.parse_version(version2)
if v1_tuple < v2_tuple:
return -1
elif v1_tuple > v2_tuple:
return 1
else:
return 0
@staticmethod
def check_forward_compatibility(current_version: str, max_version: str) -> tuple[bool, str]:
"""检查向前兼容性(仅使用兼容性映射表)
Args:
current_version: 当前版本
max_version: 插件声明的最大支持版本
Returns:
Tuple[bool, str]: (是否兼容, 兼容信息)
"""
current_normalized = VersionComparator.normalize_version(current_version)
max_normalized = VersionComparator.normalize_version(max_version)
# 检查兼容性映射表
if max_normalized in VersionComparator.COMPATIBILITY_MAP:
compatible_versions = VersionComparator.COMPATIBILITY_MAP[max_normalized]
if current_normalized in compatible_versions:
return True, f"根据兼容性映射表,版本 {current_normalized}{max_normalized} 兼容"
return False, ""
@staticmethod
def is_version_in_range(version: str, min_version: str = "", max_version: str = "") -> tuple[bool, str]:
"""检查版本是否在指定范围内,支持兼容性检查
Args:
version: 要检查的版本号
min_version: 最小版本号(可选)
max_version: 最大版本号(可选)
Returns:
Tuple[bool, str]: (是否兼容, 错误信息或兼容信息)
"""
if not min_version and not max_version:
return True, ""
version_normalized = VersionComparator.normalize_version(version)
# 检查最小版本
if min_version:
min_normalized = VersionComparator.normalize_version(min_version)
if VersionComparator.compare_versions(version_normalized, min_normalized) < 0:
return False, f"版本 {version_normalized} 低于最小要求版本 {min_normalized}"
# 检查最大版本
if max_version:
max_normalized = VersionComparator.normalize_version(max_version)
comparison = VersionComparator.compare_versions(version_normalized, max_normalized)
if comparison > 0:
# 严格版本检查失败,尝试兼容性检查
is_compatible, compat_msg = VersionComparator.check_forward_compatibility(
version_normalized, max_normalized
)
if not is_compatible:
return False, f"版本 {version_normalized} 高于最大支持版本 {max_normalized},且无兼容性映射"
logger.info(f"版本兼容性检查:{compat_msg}")
return True, compat_msg
return True, ""
@staticmethod
def get_current_host_version() -> str:
"""获取当前主机应用版本
Returns:
str: 当前版本号
"""
return VersionComparator.normalize_version(MMC_VERSION)
@staticmethod
def add_compatibility_mapping(base_version: str, compatible_versions: list) -> None:
"""动态添加兼容性映射
Args:
base_version: 基础版本(插件声明的最大支持版本)
compatible_versions: 兼容的版本列表
"""
base_normalized = VersionComparator.normalize_version(base_version)
VersionComparator.COMPATIBILITY_MAP[base_normalized] = [
VersionComparator.normalize_version(v) for v in compatible_versions
]
logger.info(f"添加兼容性映射:{base_normalized} -> {compatible_versions}")
@staticmethod
def get_compatibility_info() -> dict[str, list]:
"""获取当前的兼容性映射表
Returns:
Dict[str, list]: 兼容性映射表的副本
"""
return VersionComparator.COMPATIBILITY_MAP.copy()
class ManifestValidator:
"""Manifest文件验证器"""
# 必需字段(必须存在且不能为空)
REQUIRED_FIELDS = ["manifest_version", "name", "version", "description", "author"]
# 可选字段(可以不存在或为空)
OPTIONAL_FIELDS = [
"license",
"host_application",
"homepage_url",
"repository_url",
"keywords",
"categories",
"default_locale",
"locales_path",
"plugin_info",
]
# 建议填写的字段(会给出警告但不会导致验证失败)
RECOMMENDED_FIELDS = ["license", "keywords", "categories"]
SUPPORTED_MANIFEST_VERSIONS = [1]
def __init__(self):
self.validation_errors = []
self.validation_warnings = []
def validate_manifest(self, manifest_data: dict[str, Any]) -> bool:
"""验证manifest数据
Args:
manifest_data: manifest数据字典
Returns:
bool: 是否验证通过(只有错误会导致验证失败,警告不会)
"""
self.validation_errors.clear()
self.validation_warnings.clear()
# 检查必需字段
for field in self.REQUIRED_FIELDS:
if field not in manifest_data:
self.validation_errors.append(f"缺少必需字段: {field}")
elif not manifest_data[field]:
self.validation_errors.append(f"必需字段不能为空: {field}")
# 检查manifest版本
if "manifest_version" in manifest_data:
version = manifest_data["manifest_version"]
if version not in self.SUPPORTED_MANIFEST_VERSIONS:
self.validation_errors.append(
f"不支持的manifest版本: {version},支持的版本: {self.SUPPORTED_MANIFEST_VERSIONS}"
)
# 检查作者信息格式
if "author" in manifest_data:
author = manifest_data["author"]
if isinstance(author, dict):
if "name" not in author or not author["name"]:
self.validation_errors.append("作者信息缺少name字段或为空")
# url字段是可选的
if author.get("url"):
url = author["url"]
if not (url.startswith("http://") or url.startswith("https://")):
self.validation_warnings.append("作者URL建议使用完整的URL格式")
elif isinstance(author, str):
if not author.strip():
self.validation_errors.append("作者信息不能为空")
else:
self.validation_errors.append("作者信息格式错误应为字符串或包含name字段的对象")
# 检查主机应用版本要求(可选)
if "host_application" in manifest_data:
host_app = manifest_data["host_application"]
if isinstance(host_app, dict):
min_version = host_app.get("min_version", "")
max_version = host_app.get("max_version", "")
# 验证版本字段格式
for version_field in ["min_version", "max_version"]:
if version_field in host_app and not host_app[version_field]:
self.validation_warnings.append(f"host_application.{version_field}为空")
# 检查当前主机版本兼容性
if min_version or max_version:
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:
self.validation_errors.append(f"版本兼容性检查失败: {error_msg} (当前版本: {current_version})")
else:
logger.debug(
f"版本兼容性检查通过: 当前版本 {current_version} 符合要求 [{min_version}, {max_version}]"
)
else:
self.validation_errors.append("host_application格式错误应为对象")
# 检查URL格式可选字段
for url_field in ["homepage_url", "repository_url"]:
if manifest_data.get(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格式")
# 检查数组字段格式(可选字段)
for list_field in ["keywords", "categories"]:
if list_field in manifest_data:
field_value = manifest_data[list_field]
if field_value is not None and not isinstance(field_value, list):
self.validation_errors.append(f"{list_field}应为数组格式")
elif isinstance(field_value, list):
# 检查数组元素是否为字符串
for i, item in enumerate(field_value):
if not isinstance(item, str):
self.validation_warnings.append(f"{list_field}[{i}]应为字符串")
# 检查建议字段(给出警告)
for field in self.RECOMMENDED_FIELDS:
if field not in manifest_data or not manifest_data[field]:
self.validation_warnings.append(f"建议填写字段: {field}")
# 检查plugin_info结构可选
if "plugin_info" in manifest_data:
plugin_info = manifest_data["plugin_info"]
if isinstance(plugin_info, dict):
# 检查components数组
if "components" in plugin_info:
components = plugin_info["components"]
if not isinstance(components, list):
self.validation_errors.append("plugin_info.components应为数组格式")
else:
for i, component in enumerate(components):
if not isinstance(component, dict):
self.validation_errors.append(f"plugin_info.components[{i}]应为对象")
else:
# 检查组件必需字段
for comp_field in ["type", "name", "description"]:
if comp_field not in component or not component[comp_field]:
self.validation_errors.append(
f"plugin_info.components[{i}]缺少必需字段: {comp_field}"
)
else:
self.validation_errors.append("plugin_info应为对象格式")
return len(self.validation_errors) == 0
def get_validation_report(self) -> str:
"""获取验证报告"""
report = []
if self.validation_errors:
report.append("❌ 验证错误:")
report.extend(f" - {error}" for error in self.validation_errors)
if self.validation_warnings:
report.append("⚠️ 验证警告:")
report.extend(f" - {warning}" for warning in self.validation_warnings)
if not self.validation_errors and not self.validation_warnings:
report.append("✅ Manifest文件验证通过")
return "\n".join(report)
# class ManifestGenerator:
# """Manifest文件生成器"""
# def __init__(self):
# self.template = {
# "manifest_version": 1,
# "name": "",
# "version": "1.0.0",
# "description": "",
# "author": {"name": "", "url": ""},
# "license": "MIT",
# "host_application": {"min_version": "1.0.0", "max_version": "4.0.0"},
# "homepage_url": "",
# "repository_url": "",
# "keywords": [],
# "categories": [],
# "default_locale": "zh-CN",
# "locales_path": "_locales",
# }
# def generate_from_plugin(self, plugin_instance: BasePlugin) -> Dict[str, Any]:
# """从插件实例生成manifest
# Args:
# plugin_instance: BasePlugin实例
# Returns:
# Dict[str, Any]: 生成的manifest数据
# """
# manifest = self.template.copy()
# # 基本信息
# manifest["name"] = plugin_instance.plugin_name
# manifest["version"] = plugin_instance.plugin_version
# manifest["description"] = plugin_instance.plugin_description
# # 作者信息
# if plugin_instance.plugin_author:
# manifest["author"]["name"] = plugin_instance.plugin_author
# # 组件信息
# components = []
# plugin_components = plugin_instance.get_plugin_components()
# for component_info, component_class in plugin_components:
# component_data: Dict[str, Any] = {
# "type": component_info.component_type.value,
# "name": component_info.name,
# "description": component_info.description,
# }
# # 添加激活模式信息对于Action组件
# if hasattr(component_class, "focus_activation_type"):
# activation_modes = []
# if hasattr(component_class, "focus_activation_type"):
# activation_modes.append(component_class.focus_activation_type.value)
# if hasattr(component_class, "normal_activation_type"):
# activation_modes.append(component_class.normal_activation_type.value)
# component_data["activation_modes"] = list(set(activation_modes))
# # 添加关键词信息
# if hasattr(component_class, "activation_keywords"):
# keywords = getattr(component_class, "activation_keywords", [])
# if keywords:
# component_data["keywords"] = keywords
# components.append(component_data)
# manifest["plugin_info"] = {"is_built_in": True, "plugin_type": "general", "components": components}
# return manifest
# def save_manifest(self, manifest_data: Dict[str, Any], plugin_dir: str) -> bool:
# """保存manifest文件
# Args:
# manifest_data: manifest数据
# plugin_dir: 插件目录
# Returns:
# bool: 是否保存成功
# """
# try:
# manifest_path = os.path.join(plugin_dir, "_manifest.json")
# with open(manifest_path, "w", encoding="utf-8") as f:
# orjson.dumps(manifest_data, f, ensure_ascii=False, indent=2)
# logger.info(f"Manifest文件已保存: {manifest_path}")
# return True
# except Exception as e:
# logger.error(f"保存manifest文件失败: {e}")
# return False
# def validate_plugin_manifest(plugin_dir: str) -> bool:
# """验证插件目录中的manifest文件
# Args:
# plugin_dir: 插件目录路径
# Returns:
# bool: 是否验证通过
# """
# manifest_path = os.path.join(plugin_dir, "_manifest.json")
# if not os.path.exists(manifest_path):
# logger.warning(f"未找到manifest文件: {manifest_path}")
# return False
# try:
# with open(manifest_path, "r", encoding="utf-8") as f:
# manifest_data = orjson.loads(f.read())
# validator = ManifestValidator()
# is_valid = validator.validate_manifest(manifest_data)
# logger.info(f"Manifest验证结果:\n{validator.get_validation_report()}")
# return is_valid
# except Exception as e:
# logger.error(f"读取或验证manifest文件失败: {e}")
# return False
# def generate_plugin_manifest(plugin_instance: BasePlugin, save_to_file: bool = True) -> Optional[Dict[str, Any]]:
# """为插件生成manifest文件
# Args:
# plugin_instance: BasePlugin实例
# save_to_file: 是否保存到文件
# Returns:
# Optional[Dict[str, Any]]: 生成的manifest数据
# """
# try:
# generator = ManifestGenerator()
# manifest_data = generator.generate_from_plugin(plugin_instance)
# if save_to_file and plugin_instance.plugin_dir:
# generator.save_manifest(manifest_data, plugin_instance.plugin_dir)
# return manifest_data
# except Exception as e:
# logger.error(f"生成manifest文件失败: {e}")
# return None

View File

@@ -1,7 +1,15 @@
""" from src.plugin_system.base.plugin_metadata import PluginMetadata
亲和力聊天处理器插件
"""
from .plugin import AffinityChatterPlugin __plugin_meta__ = PluginMetadata(
name="Affinity Flow Chatter",
description="Built-in chatter plugin for affinity flow with interest scoring and relationship building",
usage="This plugin is automatically triggered by the system.",
version="1.0.0",
author="MoFox",
keywords=["chatter", "affinity", "conversation"],
categories=["Chat", "AI"],
extra={
"is_built_in": True
}
)
__all__ = ["AffinityChatterPlugin"]

View File

@@ -1,23 +0,0 @@
{
"manifest_version": 1,
"name": "affinity_chatter",
"display_name": "Affinity Flow Chatter",
"description": "Built-in chatter plugin for affinity flow with interest scoring and relationship building",
"version": "1.0.0",
"author": "MoFox",
"plugin_class": "AffinityChatterPlugin",
"enabled": true,
"is_built_in": true,
"components": [
{
"name": "affinity_chatter",
"type": "chatter",
"description": "Affinity flow chatter with intelligent interest scoring and relationship building",
"enabled": true,
"chat_type_allow": ["all"]
}
],
"host_application": { "min_version": "0.8.0" },
"keywords": ["chatter", "affinity", "conversation"],
"categories": ["Chat", "AI"]
}

View File

@@ -0,0 +1,17 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="Emoji插件 (Emoji Actions)",
description="可以发送和管理Emoji",
usage="该插件提供 `emoji` action。",
version="1.0.0",
author="SengokuCola",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MaiM-with-u/maibot",
keywords=["emoji", "action", "built-in"],
categories=["Emoji"],
extra={
"is_built_in": True,
"plugin_type": "action_provider",
}
)

View File

@@ -1,34 +0,0 @@
{
"manifest_version": 1,
"name": "Emoji插件 (Emoji Actions)",
"version": "1.0.0",
"description": "可以发送和管理Emoji",
"author": {
"name": "SengokuCola",
"url": "https://github.com/MaiM-with-u"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.10.0"
},
"homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot",
"keywords": ["emoji", "action", "built-in"],
"categories": ["Emoji"],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": true,
"plugin_type": "action_provider",
"components": [
{
"type": "action",
"name": "emoji",
"description": "作为一条全新的消息,发送一个符合当前情景的表情包来生动地表达情绪。"
}
]
}
}

View File

@@ -1,8 +1,17 @@
""" from src.plugin_system.base.plugin_metadata import PluginMetadata
让框架能够发现并加载子目录中的组件。
"""
from .actions.read_feed_action import ReadFeedAction as ReadFeedAction __plugin_meta__ = PluginMetadata(
from .actions.send_feed_action import SendFeedAction as SendFeedAction name="MaiZone麦麦空间- 重构版",
from .commands.send_feed_command import SendFeedCommand as SendFeedCommand description="重构版让你的麦麦发QQ空间说说、评论、点赞支持AI配图、定时发送和自动监控功能",
from .plugin import MaiZoneRefactoredPlugin as MaiZoneRefactoredPlugin usage="该插件提供 `send_feed` 和 `read_feed` action以及 `send_feed` command。",
version="3.0.0",
author="MoFox-Studio",
license="GPL-v3.0",
repository_url="https://github.com/MoFox-Studio",
keywords=["QQ空间", "说说", "动态", "评论", "点赞", "自动化", "AI配图"],
categories=["社交", "自动化", "QQ空间"],
extra={
"is_built_in": False,
"plugin_type": "social",
}
)

View File

@@ -1,47 +0,0 @@
{
"manifest_version": 1,
"name": "MaiZone麦麦空间- 重构版",
"version": "3.0.0",
"description": "重构版让你的麦麦发QQ空间说说、评论、点赞支持AI配图、定时发送和自动监控功能",
"author": {
"name": "MoFox-Studio",
"url": "https://github.com/MoFox-Studio"
},
"license": "GPL-v3.0",
"host_application": {
"min_version": "0.10.0"
},
"keywords": ["QQ空间", "说说", "动态", "评论", "点赞", "自动化", "AI配图"],
"categories": ["社交", "自动化", "QQ空间"],
"plugin_info": {
"is_built_in": false,
"plugin_type": "social",
"components": [
{
"type": "action",
"name": "send_feed",
"description": "根据指定主题发送一条QQ空间说说"
},
{
"type": "action",
"name": "read_feed",
"description": "读取指定好友最近的说说,并评论点赞"
},
{
"type": "command",
"name": "send_feed",
"description": "通过命令发送QQ空间说说"
}
],
"features": [
"智能生成说说内容",
"AI自动配图硅基流动",
"自动点赞评论好友说说",
"定时发送说说",
"权限管理系统",
"历史记录避重"
]
}
}

View File

@@ -0,0 +1,16 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="napcat_plugin",
description="基于OneBot 11协议的NapCat QQ协议插件提供完整的QQ机器人API接口使用现有adapter连接",
usage="该插件提供 `napcat_tool` tool。",
version="1.0.0",
author="Windpicker_owo",
license="GPL-v3.0-or-later",
repository_url="https://github.com/Windpicker-owo/InternetSearchPlugin",
keywords=["qq", "bot", "napcat", "onebot", "api", "websocket"],
categories=["protocol"],
extra={
"is_built_in": False,
}
)

View File

@@ -1,42 +0,0 @@
{
"manifest_version": 1,
"name": "napcat_plugin",
"version": "1.0.0",
"description": "基于OneBot 11协议的NapCat QQ协议插件提供完整的QQ机器人API接口使用现有adapter连接",
"author": {
"name": "Windpicker_owo",
"url": "https://github.com/Windpicker-owo"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.10.0",
"max_version": "0.11.0"
},
"homepage_url": "https://github.com/Windpicker-owo/InternetSearchPlugin",
"repository_url": "https://github.com/Windpicker-owo/InternetSearchPlugin",
"keywords": ["qq", "bot", "napcat", "onebot", "api", "websocket"],
"categories": ["protocol"],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": false,
"components": [
{
"type": "tool",
"name": "napcat_tool",
"description": "NapCat QQ协议综合工具提供消息发送、群管理、好友管理、文件操作等完整功能"
}
],
"features": [
"消息发送与接收",
"群管理功能",
"好友管理功能",
"文件上传下载",
"AI语音功能",
"群签到与戳一戳",
"现有adapter连接"
]
}
}

View File

@@ -0,0 +1,16 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="权限管理插件Permission Management",
description="通过系统API管理权限",
usage="该插件提供 `permission_management` command。",
version="1.0.0",
author="MoFox-Studio",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MoFox-Studio",
keywords=["plugins", "permission", "management", "built-in"],
extra={
"is_built_in": True,
"plugin_type": "permission",
}
)

View File

@@ -1,33 +0,0 @@
{
"manifest_version": 1,
"name": "权限管理插件Permission Management",
"version": "1.0.0",
"description": "通过系统API管理权限",
"author": {
"name": "MoFox-Studio",
"url": "https://github.com/MoFox-Studio"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.10.0"
},
"keywords": [
"plugins",
"permission",
"management",
"built-in"
],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": true,
"plugin_type": "permission",
"components": [
{
"type": "command",
"name": "permission_management",
"description": "管理用户权限,包括添加、删除和修改权限等操作。"
}
]
}
}

View File

@@ -0,0 +1,17 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="插件和组件管理 (Plugin and Component Management)",
description="通过系统API管理插件和组件的生命周期包括加载、卸载、启用和禁用等操作。",
usage="该插件提供 `plugin_management` command。",
version="1.0.0",
author="MaiBot团队",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MaiM-with-u/maibot",
keywords=["plugins", "components", "management", "built-in"],
categories=["Core System", "Plugin Management"],
extra={
"is_built_in": True,
"plugin_type": "plugin_management",
}
)

View File

@@ -1,39 +0,0 @@
{
"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.1"
},
"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": "管理插件和组件的生命周期,包括加载、卸载、启用和禁用等操作。"
}
]
}
}

View File

@@ -0,0 +1,17 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="MoFox-Bot主动思考",
description="主动思考插件",
usage="该插件由系统自动触发。",
version="1.0.0",
author="MoFox-Studio",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MoFox-Studio",
keywords=["主动思考","自己发消息"],
categories=["Chat", "Integration"],
extra={
"is_built_in": True,
"plugin_type": "functional"
}
)

View File

@@ -1,25 +0,0 @@
{
"manifest_version": 1,
"name": "MoFox-Bot主动思考",
"version": "1.0.0",
"description": "主动思考插件",
"author": {
"name": "MoFox-Studio",
"url": "https://github.com/MoFox-Studio"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.10.0"
},
"keywords": ["主动思考","自己发消息"],
"categories": ["Chat", "Integration"],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": "true",
"plugin_type": "functional"
}
}

View File

@@ -0,0 +1,17 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="MoFox-Bot工具箱",
description="一个集合多种实用功能的插件,旨在提升聊天体验和效率。",
usage="该插件提供多种命令,详情请查阅文档。",
version="1.0.0",
author="MoFox-Studio",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MoFox-Studio",
keywords=["emoji", "reaction", "like", "表情", "回应", "点赞"],
categories=["Chat", "Integration"],
extra={
"is_built_in": "true",
"plugin_type": "functional"
}
)

View File

@@ -1,25 +0,0 @@
{
"manifest_version": 1,
"name": "MoFox-Bot工具箱",
"version": "1.0.0",
"description": "一个集合多种实用功能的插件,旨在提升聊天体验和效率。",
"author": {
"name": "MoFox-Studio",
"url": "https://github.com/MoFox-Studio"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.10.0"
},
"keywords": ["emoji", "reaction", "like", "表情", "回应", "点赞"],
"categories": ["Chat", "Integration"],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": "true",
"plugin_type": "functional"
}
}

View File

@@ -0,0 +1,17 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="文本转语音插件 (Text-to-Speech)",
description="将文本转换为语音进行播放的插件,支持多种语音模式和智能语音输出场景判断。",
usage="该插件提供 `tts_action` action。",
version="0.1.0",
author="MaiBot团队",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MaiM-with-u/maibot",
keywords=["tts", "voice", "audio", "speech", "accessibility"],
categories=["Audio Tools", "Accessibility", "Voice Assistant"],
extra={
"is_built_in": True,
"plugin_type": "audio_processor",
}
)

View File

@@ -1,42 +0,0 @@
{
"manifest_version": 1,
"name": "文本转语音插件 (Text-to-Speech)",
"version": "0.1.0",
"description": "将文本转换为语音进行播放的插件,支持多种语音模式和智能语音输出场景判断。",
"author": {
"name": "MaiBot团队",
"url": "https://github.com/MaiM-with-u"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.8.0"
},
"homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot",
"keywords": ["tts", "voice", "audio", "speech", "accessibility"],
"categories": ["Audio Tools", "Accessibility", "Voice Assistant"],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": true,
"plugin_type": "audio_processor",
"components": [
{
"type": "action",
"name": "tts_action",
"description": "将文本转换为语音进行播放",
"activation_modes": ["llm_judge", "keyword"],
"keywords": ["语音", "tts", "播报", "读出来", "语音播放", "听", "朗读"]
}
],
"features": [
"文本转语音播放",
"智能场景判断",
"关键词触发",
"支持多种语音模式"
]
}
}

View File

@@ -0,0 +1,16 @@
from src.plugin_system.base.plugin_metadata import PluginMetadata
__plugin_meta__ = PluginMetadata(
name="Web Search Tool",
description="A tool for searching the web.",
usage="This plugin provides a `web_search` tool.",
version="1.0.0",
author="MoFox-Studio",
license="GPL-v3.0-or-later",
repository_url="https://github.com/MoFox-Studio",
keywords=["web", "search", "tool"],
categories=["Tools"],
extra={
"is_built_in": True,
}
)