This commit is contained in:
SengokuCola
2025-07-23 23:55:22 +08:00
18 changed files with 809 additions and 140 deletions

View File

@@ -20,6 +20,7 @@
- `config_api.py`中的`get_global_config`和`get_plugin_config`方法现在支持嵌套访问的配置键名。
- `database_api.py`中的`db_query`方法调整了参数顺序以增强参数限制的同时保证了typing正确`db_get`方法增加了`single_result`参数,与`db_query`保持一致。
5. 增加了`logging_api`,可以用`get_logger`来获取日志记录器。
6. 增加了插件和组件管理的API。
# 插件系统修改
1. 现在所有的匹配模式不再是关键字了,而是枚举类。**(可能有遗漏)**
@@ -53,11 +54,13 @@
- 通过`disable_specific_chat_action``enable_specific_chat_action``disable_specific_chat_command``enable_specific_chat_command``disable_specific_chat_event_handler``enable_specific_chat_event_handler`来操作
- 同样不保存到配置文件~
# 官方插件修改
1. `HelloWorld`插件现在有一个样例的`EventHandler`。
2. 内置插件增加了一个通过`Command`来管理插件的功能。
### TODO
把这个看起来就很别扭的config获取方式改一下
来个API管理这些启用禁用
# 吐槽
```python

View File

@@ -118,17 +118,17 @@ class HelloWorldPlugin(BasePlugin):
"""Hello World插件 - 你的第一个MaiCore插件"""
# 插件基本信息
plugin_name = "hello_world_plugin" # 内部标识符
enable_plugin = True
dependencies = [] # 插件依赖列表
python_dependencies = [] # Python包依赖列表
config_file_name = "config.toml" # 配置文件名
plugin_name: str = "hello_world_plugin" # 内部标识符
enable_plugin: bool = True
dependencies: List[str] = [] # 插件依赖列表
python_dependencies: List[str] = [] # Python包依赖列表
config_file_name: str = "config.toml" # 配置文件名
# 配置节描述
config_section_descriptions = {"plugin": "插件基本信息", "greeting": "问候功能配置", "time": "时间查询配置"}
# 配置Schema定义
config_schema = {
config_schema: dict = {
"plugin": {
"name": ConfigField(type=str, default="hello_world_plugin", description="插件名称"),
"version": ConfigField(type=str, default="1.0.0", description="插件版本"),

View File

@@ -51,8 +51,6 @@ NO_ACTION = {
"action_prompt": "",
}
IS_MAI4U = False
install(extra_lines=3)
# 注释:原来的动作修改超时常量已移除,因为改为顺序执行

View File

@@ -1,7 +1,7 @@
import json
import time
import traceback
from typing import Dict, Any, Optional, Tuple, List
from typing import Dict, Any, Optional, Tuple
from rich.traceback import install
from datetime import datetime
from json_repair import repair_json
@@ -119,8 +119,8 @@ class ActionPlanner:
current_available_actions_dict = self.action_manager.get_using_actions()
# 获取完整的动作信息
all_registered_actions: List[ActionInfo] = list(
component_registry.get_components_by_type(ComponentType.ACTION).values() # type: ignore
all_registered_actions: Dict[str, ActionInfo] = component_registry.get_components_by_type( # type: ignore
ComponentType.ACTION
)
current_available_actions = {}
for action_name in current_available_actions_dict:

View File

@@ -48,7 +48,7 @@ TEMPLATE_DIR = os.path.join(PROJECT_ROOT, "template")
# 考虑到实际上配置文件中的mai_version是不会自动更新的,所以采用硬编码
# 对该字段的更新请严格参照语义化版本规范https://semver.org/lang/zh-CN/
MMC_VERSION = "0.9.0-snapshot.2"
MMC_VERSION = "0.9.0-snapshot.3"
def get_key_comment(toml_table, key):

View File

@@ -23,13 +23,6 @@ from .base import (
EventType,
MaiMessages,
)
from .core import (
plugin_manager,
component_registry,
dependency_manager,
events_manager,
global_announcement_manager,
)
# 导入工具模块
from .utils import (
@@ -39,12 +32,42 @@ from .utils import (
# generate_plugin_manifest,
)
from .apis import register_plugin, get_logger
from .apis import (
chat_api,
component_manage_api,
config_api,
database_api,
emoji_api,
generator_api,
llm_api,
message_api,
person_api,
plugin_manage_api,
send_api,
utils_api,
register_plugin,
get_logger,
)
__version__ = "1.0.0"
__all__ = [
# API 模块
"chat_api",
"component_manage_api",
"config_api",
"database_api",
"emoji_api",
"generator_api",
"llm_api",
"message_api",
"person_api",
"plugin_manage_api",
"send_api",
"utils_api",
"register_plugin",
"get_logger",
# 基础类
"BasePlugin",
"BaseAction",
@@ -63,12 +86,6 @@ __all__ = [
"EventType",
# 消息
"MaiMessages",
# 管理器
"plugin_manager",
"component_registry",
"dependency_manager",
"events_manager",
"global_announcement_manager",
# 装饰器
"register_plugin",
"ConfigField",

View File

@@ -7,6 +7,7 @@
# 导入所有API模块
from src.plugin_system.apis import (
chat_api,
component_manage_api,
config_api,
database_api,
emoji_api,
@@ -14,15 +15,17 @@ from src.plugin_system.apis import (
llm_api,
message_api,
person_api,
plugin_manage_api,
send_api,
utils_api,
plugin_register_api,
)
from .logging_api import get_logger
from .plugin_register_api import register_plugin
# 导出所有API模块使它们可以通过 apis.xxx 方式访问
__all__ = [
"chat_api",
"component_manage_api",
"config_api",
"database_api",
"emoji_api",
@@ -30,9 +33,9 @@ __all__ = [
"llm_api",
"message_api",
"person_api",
"plugin_manage_api",
"send_api",
"utils_api",
"plugin_register_api",
"get_logger",
"register_plugin",
]

View File

@@ -0,0 +1,245 @@
from typing import Optional, Union, Dict
from src.plugin_system.base.component_types import (
CommandInfo,
ActionInfo,
EventHandlerInfo,
PluginInfo,
ComponentType,
)
# === 插件信息查询 ===
def get_all_plugin_info() -> Dict[str, PluginInfo]:
"""
获取所有插件的信息。
Returns:
dict: 包含所有插件信息的字典,键为插件名称,值为 PluginInfo 对象。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_all_plugins()
def get_plugin_info(plugin_name: str) -> Optional[PluginInfo]:
"""
获取指定插件的信息。
Args:
plugin_name (str): 插件名称。
Returns:
PluginInfo: 插件信息对象,如果插件不存在则返回 None。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_plugin_info(plugin_name)
# === 组件查询方法 ===
def get_component_info(
component_name: str, component_type: ComponentType
) -> Optional[Union[CommandInfo, ActionInfo, EventHandlerInfo]]:
"""
获取指定组件的信息。
Args:
component_name (str): 组件名称。
component_type (ComponentType): 组件类型。
Returns:
Union[CommandInfo, ActionInfo, EventHandlerInfo]: 组件信息对象,如果组件不存在则返回 None。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_component_info(component_name, component_type) # type: ignore
def get_components_info_by_type(
component_type: ComponentType,
) -> Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]:
"""
获取指定类型的所有组件信息。
Args:
component_type (ComponentType): 组件类型。
Returns:
dict: 包含指定类型组件信息的字典,键为组件名称,值为对应的组件信息对象。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_components_by_type(component_type) # type: ignore
def get_enabled_components_info_by_type(
component_type: ComponentType,
) -> Dict[str, Union[CommandInfo, ActionInfo, EventHandlerInfo]]:
"""
获取指定类型的所有启用的组件信息。
Args:
component_type (ComponentType): 组件类型。
Returns:
dict: 包含指定类型启用组件信息的字典,键为组件名称,值为对应的组件信息对象。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_enabled_components_by_type(component_type) # type: ignore
# === Action 查询方法 ===
def get_registered_action_info(action_name: str) -> Optional[ActionInfo]:
"""
获取指定 Action 的注册信息。
Args:
action_name (str): Action 名称。
Returns:
ActionInfo: Action 信息对象,如果 Action 不存在则返回 None。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_registered_action_info(action_name)
def get_registered_command_info(command_name: str) -> Optional[CommandInfo]:
"""
获取指定 Command 的注册信息。
Args:
command_name (str): Command 名称。
Returns:
CommandInfo: Command 信息对象,如果 Command 不存在则返回 None。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_registered_command_info(command_name)
# === EventHandler 特定查询方法 ===
def get_registered_event_handler_info(
event_handler_name: str,
) -> Optional[EventHandlerInfo]:
"""
获取指定 EventHandler 的注册信息。
Args:
event_handler_name (str): EventHandler 名称。
Returns:
EventHandlerInfo: EventHandler 信息对象,如果 EventHandler 不存在则返回 None。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.get_registered_event_handler_info(event_handler_name)
# === 组件管理方法 ===
def globally_enable_component(component_name: str, component_type: ComponentType) -> bool:
"""
全局启用指定组件。
Args:
component_name (str): 组件名称。
component_type (ComponentType): 组件类型。
Returns:
bool: 启用成功返回 True否则返回 False。
"""
from src.plugin_system.core.component_registry import component_registry
return component_registry.enable_component(component_name, component_type)
async def globally_disable_component(component_name: str, component_type: ComponentType) -> bool:
"""
全局禁用指定组件。
**此函数是异步的,确保在异步环境中调用。**
Args:
component_name (str): 组件名称。
component_type (ComponentType): 组件类型。
Returns:
bool: 禁用成功返回 True否则返回 False。
"""
from src.plugin_system.core.component_registry import component_registry
return await component_registry.disable_component(component_name, component_type)
def locally_enable_component(component_name: str, component_type: ComponentType, stream_id: str) -> bool:
"""
局部启用指定组件。
Args:
component_name (str): 组件名称。
component_type (ComponentType): 组件类型。
stream_id (str): 消息流 ID。
Returns:
bool: 启用成功返回 True否则返回 False。
"""
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
match component_type:
case ComponentType.ACTION:
return global_announcement_manager.enable_specific_chat_action(stream_id, component_name)
case ComponentType.COMMAND:
return global_announcement_manager.enable_specific_chat_command(stream_id, component_name)
case ComponentType.EVENT_HANDLER:
return global_announcement_manager.enable_specific_chat_event_handler(stream_id, component_name)
case _:
raise ValueError(f"未知 component type: {component_type}")
def locally_disable_component(component_name: str, component_type: ComponentType, stream_id: str) -> bool:
"""
局部禁用指定组件。
Args:
component_name (str): 组件名称。
component_type (ComponentType): 组件类型。
stream_id (str): 消息流 ID。
Returns:
bool: 禁用成功返回 True否则返回 False。
"""
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
match component_type:
case ComponentType.ACTION:
return global_announcement_manager.disable_specific_chat_action(stream_id, component_name)
case ComponentType.COMMAND:
return global_announcement_manager.disable_specific_chat_command(stream_id, component_name)
case ComponentType.EVENT_HANDLER:
return global_announcement_manager.disable_specific_chat_event_handler(stream_id, component_name)
case _:
raise ValueError(f"未知 component type: {component_type}")
def get_locally_disabled_components(stream_id: str, component_type: ComponentType) -> list[str]:
"""
获取指定消息流中禁用的组件列表。
Args:
stream_id (str): 消息流 ID。
component_type (ComponentType): 组件类型。
Returns:
list[str]: 禁用的组件名称列表。
"""
from src.plugin_system.core.global_announcement_manager import global_announcement_manager
match component_type:
case ComponentType.ACTION:
return global_announcement_manager.get_disabled_chat_actions(stream_id)
case ComponentType.COMMAND:
return global_announcement_manager.get_disabled_chat_commands(stream_id)
case ComponentType.EVENT_HANDLER:
return global_announcement_manager.get_disabled_chat_event_handlers(stream_id)
case _:
raise ValueError(f"未知 component type: {component_type}")

View File

@@ -0,0 +1,95 @@
from typing import Tuple, List
def list_loaded_plugins() -> List[str]:
"""
列出所有当前加载的插件。
Returns:
list: 当前加载的插件名称列表。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return plugin_manager.list_loaded_plugins()
def list_registered_plugins() -> List[str]:
"""
列出所有已注册的插件。
Returns:
list: 已注册的插件名称列表。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return plugin_manager.list_registered_plugins()
async def remove_plugin(plugin_name: str) -> bool:
"""
卸载指定的插件。
**此函数是异步的,确保在异步环境中调用。**
Args:
plugin_name (str): 要卸载的插件名称。
Returns:
bool: 卸载是否成功。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return await plugin_manager.remove_registered_plugin(plugin_name)
async def reload_plugin(plugin_name: str) -> bool:
"""
重新加载指定的插件。
**此函数是异步的,确保在异步环境中调用。**
Args:
plugin_name (str): 要重新加载的插件名称。
Returns:
bool: 重新加载是否成功。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return await plugin_manager.reload_registered_plugin(plugin_name)
def load_plugin(plugin_name: str) -> Tuple[bool, int]:
"""
加载指定的插件。
Args:
plugin_name (str): 要加载的插件名称。
Returns:
Tuple[bool, int]: 加载是否成功,成功或失败个数。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return plugin_manager.load_registered_plugin_classes(plugin_name)
def add_plugin_directory(plugin_directory: str) -> bool:
"""
添加插件目录。
Args:
plugin_directory (str): 要添加的插件目录路径。
Returns:
bool: 添加是否成功。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return plugin_manager.add_plugin_directory(plugin_directory)
def rescan_plugin_directory() -> Tuple[int, int]:
"""
重新扫描插件目录,加载新插件。
Returns:
Tuple[int, int]: 成功加载的插件数量和失败的插件数量。
"""
from src.plugin_system.core.plugin_manager import plugin_manager
return plugin_manager.rescan_plugin_directory()

View File

@@ -24,9 +24,8 @@ class BaseCommand(ABC):
"""Command组件的名称"""
command_description: str = ""
"""Command组件的描述"""
# 默认命令设置(子类可以覆盖)
command_pattern: str = ""
# 默认命令设置
command_pattern: str = r""
"""命令匹配的正则表达式"""
command_help: str = ""
"""命令帮助信息"""

View File

@@ -442,7 +442,7 @@ class ComponentRegistry:
command_info,
)
# === 事件处理器特定查询方法 ===
# === EventHandler 特定查询方法 ===
def get_event_handler_registry(self) -> Dict[str, Type[BaseEventHandler]]:
"""获取事件处理器注册表"""
@@ -467,9 +467,9 @@ class ComponentRegistry:
"""获取所有插件"""
return self._plugins.copy()
def get_enabled_plugins(self) -> Dict[str, PluginInfo]:
"""获取所有启用的插件"""
return {name: info for name, info in self._plugins.items() if info.enabled}
# def get_enabled_plugins(self) -> Dict[str, PluginInfo]:
# """获取所有启用的插件"""
# return {name: info for name, info in self._plugins.items() if info.enabled}
def get_plugin_components(self, plugin_name: str) -> List[ComponentInfo]:
"""获取插件的所有组件"""

View File

@@ -31,6 +31,7 @@ class GlobalAnnouncementManager:
self._user_disabled_actions[chat_id].remove(action_name)
return True
except ValueError:
logger.warning(f"动作 {action_name} 不在禁用列表中")
return False
return False
@@ -51,6 +52,7 @@ class GlobalAnnouncementManager:
self._user_disabled_commands[chat_id].remove(command_name)
return True
except ValueError:
logger.warning(f"命令 {command_name} 不在禁用列表中")
return False
return False
@@ -71,6 +73,7 @@ class GlobalAnnouncementManager:
self._user_disabled_event_handlers[chat_id].remove(handler_name)
return True
except ValueError:
logger.warning(f"事件处理器 {handler_name} 不在禁用列表中")
return False
return False

View File

@@ -8,7 +8,7 @@ from pathlib import Path
from src.common.logger import get_logger
from src.plugin_system.base.plugin_base import PluginBase
from src.plugin_system.base.component_types import ComponentType, PluginInfo, PythonDependency
from src.plugin_system.base.component_types import ComponentType, PythonDependency
from src.plugin_system.utils.manifest_utils import VersionComparator
from .component_registry import component_registry
from .dependency_manager import dependency_manager
@@ -75,7 +75,7 @@ class PluginManager:
total_failed_registration = 0
for plugin_name in self.plugin_classes.keys():
load_status, count = self._load_registered_plugin_classes(plugin_name)
load_status, count = self.load_registered_plugin_classes(plugin_name)
if load_status:
total_registered += 1
else:
@@ -85,7 +85,73 @@ class PluginManager:
return total_registered, total_failed_registration
async def remove_registered_plugin(self, plugin_name: str) -> None:
def load_registered_plugin_classes(self, plugin_name: str) -> Tuple[bool, int]:
# sourcery skip: extract-duplicate-method, extract-method
"""
加载已经注册的插件类
"""
plugin_class = self.plugin_classes.get(plugin_name)
if not plugin_class:
logger.error(f"插件 {plugin_name} 的插件类未注册或不存在")
return False, 1
try:
# 使用记录的插件目录路径
plugin_dir = self.plugin_paths.get(plugin_name)
# 如果没有记录,直接返回失败
if not plugin_dir:
return False, 1
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件可能因为缺少manifest而失败
if not plugin_instance:
logger.error(f"插件 {plugin_name} 实例化失败")
return False, 1
# 检查插件是否启用
if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
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():
self.loaded_plugins[plugin_name] = plugin_instance
self._show_plugin_components(plugin_name)
return True, 1
else:
self.failed_plugins[plugin_name] = "插件注册失败"
logger.error(f"❌ 插件注册失败: {plugin_name}")
return False, 1
except FileNotFoundError as e:
# manifest文件缺失
error_msg = f"缺少manifest文件: {str(e)}"
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验证失败: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except Exception as e:
# 其他错误
error_msg = f"未知错误: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
logger.debug("详细错误信息: ", exc_info=True)
return False, 1
async def remove_registered_plugin(self, plugin_name: str) -> bool:
"""
禁用插件模块
"""
@@ -93,38 +159,40 @@ class PluginManager:
raise ValueError("插件名称不能为空")
if plugin_name not in self.loaded_plugins:
logger.warning(f"插件 {plugin_name} 未加载")
return
return False
plugin_instance = self.loaded_plugins[plugin_name]
plugin_info = plugin_instance.plugin_info
for component in plugin_info.components:
await component_registry.remove_component(component.name, component.component_type)
del self.loaded_plugins[plugin_name]
return True
async def reload_registered_plugin_module(self, plugin_name: str) -> None:
async def reload_registered_plugin(self, plugin_name: str) -> bool:
"""
重载插件模块
"""
await self.remove_registered_plugin(plugin_name)
self._load_registered_plugin_classes(plugin_name)
if not await self.remove_registered_plugin(plugin_name):
return False
if not self.load_registered_plugin_classes(plugin_name)[0]:
return False
logger.debug(f"插件 {plugin_name} 重载成功")
return True
def rescan_plugin_directory(self) -> None:
def rescan_plugin_directory(self) -> Tuple[int, int]:
"""
重新扫描插件根目录
"""
total_success = 0
total_fail = 0
for directory in self.plugin_directories:
if os.path.exists(directory):
logger.debug(f"重新扫描插件根目录: {directory}")
self._load_plugin_modules_from_directory(directory)
success, fail = self._load_plugin_modules_from_directory(directory)
total_success += success
total_fail += fail
else:
logger.warning(f"插件根目录不存在: {directory}")
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())
return total_success, total_fail
def get_plugin_instance(self, plugin_name: str) -> Optional["PluginBase"]:
"""获取插件实例
@@ -235,6 +303,25 @@ class PluginManager:
return dependency_manager.generate_requirements_file(all_dependencies, output_path)
# === 查询方法 ===
def list_loaded_plugins(self) -> List[str]:
"""
列出所有当前加载的插件。
Returns:
list: 当前加载的插件名称列表。
"""
return list(self.loaded_plugins.keys())
def list_registered_plugins(self) -> List[str]:
"""
列出所有已注册的插件类。
Returns:
list: 已注册的插件类名称列表。
"""
return list(self.plugin_classes.keys())
# === 私有方法 ===
# == 目录管理 ==
def _ensure_plugin_directories(self) -> None:
@@ -310,72 +397,6 @@ class PluginManager:
self.failed_plugins[module_name] = error_msg
return False
def _load_registered_plugin_classes(self, plugin_name: str) -> Tuple[bool, int]:
# sourcery skip: extract-duplicate-method, extract-method
"""
加载已经注册的插件类
"""
plugin_class = self.plugin_classes.get(plugin_name)
if not plugin_class:
logger.error(f"插件 {plugin_name} 的插件类未注册或不存在")
return False, 1
try:
# 使用记录的插件目录路径
plugin_dir = self.plugin_paths.get(plugin_name)
# 如果没有记录,直接返回失败
if not plugin_dir:
return False, 1
plugin_instance = plugin_class(plugin_dir=plugin_dir) # 实例化插件可能因为缺少manifest而失败
if not plugin_instance:
logger.error(f"插件 {plugin_name} 实例化失败")
return False, 1
# 检查插件是否启用
if not plugin_instance.enable_plugin:
logger.info(f"插件 {plugin_name} 已禁用,跳过加载")
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():
self.loaded_plugins[plugin_name] = plugin_instance
self._show_plugin_components(plugin_name)
return True, 1
else:
self.failed_plugins[plugin_name] = "插件注册失败"
logger.error(f"❌ 插件注册失败: {plugin_name}")
return False, 1
except FileNotFoundError as e:
# manifest文件缺失
error_msg = f"缺少manifest文件: {str(e)}"
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验证失败: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
return False, 1
except Exception as e:
# 其他错误
error_msg = f"未知错误: {str(e)}"
self.failed_plugins[plugin_name] = error_msg
logger.error(f"❌ 插件加载失败: {plugin_name} - {error_msg}")
logger.debug("详细错误信息: ", exc_info=True)
return False, 1
# == 兼容性检查 ==
def _check_plugin_version_compatibility(self, plugin_name: str, manifest_data: Dict[str, Any]) -> Tuple[bool, str]:

View File

@@ -22,6 +22,7 @@ from src.plugins.built_in.core_actions.reply import ReplyAction
logger = get_logger("core_actions")
@register_plugin
class CoreActionsPlugin(BasePlugin):
"""核心动作插件
@@ -35,11 +36,11 @@ class CoreActionsPlugin(BasePlugin):
"""
# 插件基本信息
plugin_name = "core_actions" # 内部标识符
enable_plugin = True
dependencies = [] # 插件依赖列表
python_dependencies = [] # Python包依赖列表
config_file_name = "config.toml"
plugin_name: str = "core_actions" # 内部标识符
enable_plugin: bool = True
dependencies: list[str] = [] # 插件依赖列表
python_dependencies: list[str] = [] # Python包依赖列表
config_file_name: str = "config.toml"
# 配置节描述
config_section_descriptions = {
@@ -48,7 +49,7 @@ class CoreActionsPlugin(BasePlugin):
}
# 配置Schema定义
config_schema = {
config_schema: dict = {
"plugin": {
"enabled": ConfigField(type=bool, default=True, description="是否启用插件"),
"config_version": ConfigField(type=str, default="0.4.0", description="配置文件版本"),

View File

@@ -1,4 +1,3 @@
# 导入新插件系统
from src.plugin_system import BaseAction, ActionActivationType, ChatMode
from src.config.config import global_config
@@ -8,6 +7,7 @@ from typing import Tuple
import asyncio
import re
import traceback
# 导入依赖的系统组件
from src.common.logger import get_logger
@@ -20,6 +20,7 @@ from src.mais4u.constant_s4u import ENABLE_S4U
logger = get_logger("reply_action")
class ReplyAction(BaseAction):
"""回复动作 - 参与聊天回复"""
@@ -61,10 +62,10 @@ class ReplyAction(BaseAction):
user_id = self.user_id
platform = self.platform
# logger.info(f"{self.log_prefix} 用户ID: {user_id}, 平台: {platform}")
person_id = get_person_info_manager().get_person_id(platform, user_id)
person_id = get_person_info_manager().get_person_id(platform, user_id) # type: ignore
# logger.info(f"{self.log_prefix} 人物ID: {person_id}")
person_name = get_person_info_manager().get_value_sync(person_id, "person_name")
reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}"
reply_to = f"{person_name}:{self.action_message.get('processed_plain_text', '')}" # type: ignore
logger.info(f"{self.log_prefix} 回复目标: {reply_to}")
try:
@@ -118,11 +119,9 @@ class ReplyAction(BaseAction):
# 存储动作记录
reply_text = f"你对{person_name}进行了回复:{reply_text}"
if ENABLE_S4U:
await mai_thinking_manager.get_mai_think(self.chat_id).do_think_after_response(reply_text)
await self.store_action_info(
action_build_into_prompt=False,
@@ -138,4 +137,4 @@ class ReplyAction(BaseAction):
except Exception as e:
logger.error(f"{self.log_prefix} 回复动作执行失败: {e}")
traceback.print_exc()
return False, f"回复失败: {str(e)}"
return False, f"回复失败: {str(e)}"

View File

@@ -0,0 +1,39 @@
{
"manifest_version": 1,
"name": "插件和组件管理 (Plugin and Component Management)",
"version": "1.0.0",
"description": "通过系统API管理插件和组件的生命周期包括加载、卸载、启用和禁用等操作。",
"author": {
"name": "MaiBot团队",
"url": "https://github.com/MaiM-with-u"
},
"license": "GPL-v3.0-or-later",
"host_application": {
"min_version": "0.9.0"
},
"homepage_url": "https://github.com/MaiM-with-u/maibot",
"repository_url": "https://github.com/MaiM-with-u/maibot",
"keywords": [
"plugins",
"components",
"management",
"built-in"
],
"categories": [
"Core System",
"Plugin Management"
],
"default_locale": "zh-CN",
"locales_path": "_locales",
"plugin_info": {
"is_built_in": true,
"plugin_type": "plugin_management",
"components": [
{
"type": "command",
"name": "plugin_management",
"description": "管理插件和组件的生命周期,包括加载、卸载、启用和禁用等操作。"
}
]
}
}

View File

@@ -0,0 +1,246 @@
from typing import List, Tuple, Type
from src.plugin_system import (
BasePlugin,
BaseCommand,
CommandInfo,
ConfigField,
register_plugin,
plugin_manage_api,
component_manage_api,
ComponentInfo,
ComponentType,
)
from src.plugin_system.base.base_action import BaseAction
from src.plugin_system.base.base_events_handler import BaseEventHandler
from src.plugin_system.base.component_types import ActionInfo, EventHandlerInfo
class ManagementCommand(BaseCommand):
command_name: str = "management"
description: str = "管理命令"
command_pattern: str = r"(?P<manage_command>^/p_m(\s[a-zA-Z0-9_]+)*\s*$)"
intercept_message: bool = True
async def execute(self) -> Tuple[bool, str]:
command_list = self.matched_groups["manage_command"].strip().split(" ")
if len(command_list) == 1:
await self.show_help("all")
return True, "帮助已发送"
if len(command_list) == 2:
match command_list[1]:
case "plugin":
await self.show_help("plugin")
case "component":
await self.show_help("component")
case "help":
await self.show_help("all")
case _:
return False, "命令不合法"
if len(command_list) == 3:
if command_list[1] == "plugin":
match command_list[2]:
case "help":
await self.show_help("plugin")
case "list":
await self._list_registered_plugins()
case "list_enabled":
await self._list_loaded_plugins()
case "rescan":
await self._rescan_plugin_dirs()
case _:
return False, "命令不合法"
elif command_list[1] == "component":
match command_list[2]:
case "help":
await self.show_help("component")
return True, "帮助已发送"
case "list":
pass
case _:
return False, "命令不合法"
else:
return False, "命令不合法"
if len(command_list) == 4:
if command_list[1] == "plugin":
match command_list[2]:
case "load":
await self._load_plugin(command_list[3])
case "unload":
await self._unload_plugin(command_list[3])
case "reload":
await self._reload_plugin(command_list[3])
case "add_dir":
await self._add_dir(command_list[3])
case _:
return False, "命令不合法"
elif command_list[1] == "component":
pass
else:
return False, "命令不合法"
if len(command_list) == 5:
pass
if len(command_list) == 6:
pass
return True, "命令执行完成"
async def show_help(self, target: str):
help_msg = ""
match target:
case "all":
help_msg = (
"管理命令帮助\n"
"/p_m help 管理命令提示\n"
"/p_m plugin 插件管理命令\n"
"/p_m component 组件管理命令\n"
"使用 /p_m plugin help 或 /p_m component help 获取具体帮助"
)
case "plugin":
help_msg = (
"插件管理命令帮助\n"
"/p_m plugin help 插件管理命令提示\n"
"/p_m plugin list 列出所有注册的插件\n"
"/p_m plugin list_enabled 列出所有加载(启用)的插件\n"
"/p_m plugin rescan 重新扫描所有目录\n"
"/p_m plugin load <plugin_name> 加载指定插件\n"
"/p_m plugin unload <plugin_name> 卸载指定插件\n"
"/p_m plugin reload <plugin_name> 重新加载指定插件\n"
"/p_m plugin add_dir <directory_path> 添加插件目录\n"
)
case "component":
help_msg = (
"组件管理命令帮助\n"
"/p_m component help 组件管理命令提示\n"
"/p_m component list 列出所有注册的组件\n"
"/p_m component list enabled <可选: type> 列出所有启用的组件\n"
"/p_m component list disabled <可选: type> 列出所有禁用的组件\n"
" - <type> 可选项: local代表当前聊天中的global代表全局的\n"
" - <type> 不填时为 global\n"
"/p_m component list type <component_type> 列出指定类型的组件\n"
"/p_m component global enable <component_name> <可选: component_type> 全局启用组件\n"
"/p_m component global disable <component_name> <可选: component_type> 全局禁用组件\n"
"/p_m component local enable <component_name> <可选: component_type> 本聊天启用组件\n"
"/p_m component local disable <component_name> <可选: component_type> 本聊天禁用组件\n"
" - <component_type> 可选项: action, command, event_handler\n"
)
case _:
return
await self.send_text(help_msg)
async def _list_loaded_plugins(self):
plugins = plugin_manage_api.list_loaded_plugins()
await self.send_text(f"已加载的插件: {', '.join(plugins)}")
async def _list_registered_plugins(self):
plugins = plugin_manage_api.list_registered_plugins()
await self.send_text(f"已注册的插件: {', '.join(plugins)}")
async def _rescan_plugin_dirs(self):
plugin_manage_api.rescan_plugin_directory()
await self.send_text("插件目录重新扫描执行中")
async def _load_plugin(self, plugin_name: str):
await self.send_text(f"正在加载插件: {plugin_name}")
success, count = plugin_manage_api.load_plugin(plugin_name)
if success:
await self.send_text(f"插件加载成功: {plugin_name}")
else:
if count == 0:
await self.send_text(f"插件{plugin_name}为禁用状态")
await self.send_text(f"插件加载失败: {plugin_name}")
async def _unload_plugin(self, plugin_name: str):
await self.send_text(f"正在卸载插件: {plugin_name}")
success = plugin_manage_api.remove_plugin(plugin_name)
if success:
await self.send_text(f"插件卸载成功: {plugin_name}")
else:
await self.send_text(f"插件卸载失败: {plugin_name}")
async def _reload_plugin(self, plugin_name: str):
await self.send_text(f"正在重新加载插件: {plugin_name}")
success = plugin_manage_api.reload_plugin(plugin_name)
if success:
await self.send_text(f"插件重新加载成功: {plugin_name}")
else:
await self.send_text(f"插件重新加载失败: {plugin_name}")
async def _add_dir(self, dir_path: str):
await self.send_text(f"正在添加插件目录: {dir_path}")
success = plugin_manage_api.add_plugin_directory(dir_path)
if success:
await self.send_text(f"插件目录添加成功: {dir_path}")
else:
await self.send_text(f"插件目录添加失败: {dir_path}")
def _fetch_all_registered_components(self) -> List[ComponentInfo]:
all_plugin_info = component_manage_api.get_all_plugin_info()
if not all_plugin_info:
return []
components_info: List[ComponentInfo] = []
for plugin_info in all_plugin_info.values():
components_info.extend(plugin_info.components)
return components_info
def _fetch_locally_disabled_components(self) -> List[str]:
locally_disabled_components_actions = component_manage_api.get_locally_disabled_components(
self.message.chat_stream.stream_id, ComponentType.ACTION
)
locally_disabled_components_commands = component_manage_api.get_locally_disabled_components(
self.message.chat_stream.stream_id, ComponentType.COMMAND
)
locally_disabled_components_event_handlers = component_manage_api.get_locally_disabled_components(
self.message.chat_stream.stream_id, ComponentType.EVENT_HANDLER
)
return (
locally_disabled_components_actions
+ locally_disabled_components_commands
+ locally_disabled_components_event_handlers
)
async def _list_all_registered_components(self):
components_info = self._fetch_all_registered_components()
if not components_info:
await self.send_text("没有注册的组件")
return
all_components_str = ", ".join(
f"{component.name} ({component.component_type})" for component in components_info
)
await self.send_text(f"已注册的组件: {all_components_str}")
async def _list_enabled_components(self, target_type: str = "global"):
components_info = self._fetch_all_registered_components()
if not components_info:
await self.send_text("没有注册的组件")
return
if target_type == "global":
enabled_components = [component for component in components_info if component.enabled]
if not enabled_components:
await self.send_text("没有启用的全局组件")
return
enabled_components_str = ", ".join(
f"{component.name} ({component.component_type})" for component in enabled_components
)
await self.send_text(f"启用的全局组件: {enabled_components_str}")
elif target_type == "local":
locally_disabled_components = self._fetch_locally_disabled_components()
@register_plugin
class PluginManagementPlugin(BasePlugin):
plugin_name: str = "plugin_management_plugin"
enable_plugin: bool = True
dependencies: list[str] = []
python_dependencies: list[str] = []
config_file_name: str = "config.toml"
config_schema: dict = {"plugin": {"enable": ConfigField(bool, default=True, description="是否启用插件")}}
def get_plugin_components(self) -> List[Tuple[CommandInfo, Type[BaseCommand]]]:
components = []
if self.get_config("plugin.enable", True):
components.append((ManagementCommand.get_command_info(), ManagementCommand))
return components

View File

@@ -92,7 +92,7 @@ class TTSAction(BaseAction):
# 确保句子结尾有合适的标点
if not any(processed_text.endswith(end) for end in [".", "?", "!", "", "", ""]):
processed_text = processed_text + ""
processed_text = f"{processed_text}"
return processed_text
@@ -107,11 +107,11 @@ class TTSPlugin(BasePlugin):
"""
# 插件基本信息
plugin_name = "tts_plugin" # 内部标识符
enable_plugin = True
dependencies = [] # 插件依赖列表
python_dependencies = [] # Python包依赖列表
config_file_name = "config.toml"
plugin_name: str = "tts_plugin" # 内部标识符
enable_plugin: bool = True
dependencies: list[str] = [] # 插件依赖列表
python_dependencies: list[str] = [] # Python包依赖列表
config_file_name: str = "config.toml"
# 配置节描述
config_section_descriptions = {
@@ -121,7 +121,7 @@ class TTSPlugin(BasePlugin):
}
# 配置Schema定义
config_schema = {
config_schema: dict = {
"plugin": {
"name": ConfigField(type=str, default="tts_plugin", description="插件名称", required=True),
"version": ConfigField(type=str, default="0.1.0", description="插件版本号"),