import traceback from threading import Thread from typing import Tuple, Optional, List, Any from app.core import EventManager from app.db.systemconfigs import SystemConfigs from app.helper import ModuleHelper from app.log import logger from app.utils.singleton import Singleton class PluginManager(metaclass=Singleton): """ 插件管理器 """ systemconfigs: SystemConfigs = None eventmanager: EventManager = None # 插件列表 _plugins: dict = {} # 运行态插件列表 _running_plugins: dict = {} # 配置Key _config_key: str = "plugin.%s" # 事件处理线程 _thread: Thread = None # 开关 _active: bool = False def __init__(self): self.init_config() def init_config(self): self.systemconfigs = SystemConfigs() self.eventmanager = EventManager() # 停止已有插件 self.stop() # 启动插件 self.start() def __run(self): """ 事件处理线程 """ while self._active: event, handlers = self.eventmanager.get_event() if event: logger.info(f"处理事件:{event.event_type} - {handlers}") for handler in handlers: try: names = handler.__qualname__.split(".") self.run_plugin_method(names[0], names[1], event) except Exception as e: logger.error(f"事件处理出错:{str(e)} - {traceback.format_exc()}") def start(self): """ 启动 """ # 加载插件 self.__load_plugins() # 将事件管理器设为启动 self._active = True self._thread = Thread(target=self.__run) # 启动事件处理线程 self._thread.start() def stop(self): """ 停止 """ # 将事件管理器设为停止 self._active = False # 等待事件处理线程退出 if self._thread: self._thread.join() # 停止所有插件 self.__stop_plugins() def __load_plugins(self): """ 加载所有插件 """ # 扫描插件目录 plugins = ModuleHelper.load( "app.plugins", filter_func=lambda _, obj: hasattr(obj, 'init_plugin') ) # 排序 plugins.sort(key=lambda x: x.plugin_order if hasattr(x, "plugin_order") else 0) self._running_plugins = {} self._plugins = {} for plugin in plugins: plugin_id = plugin.__name__ self._plugins[plugin_id] = plugin # 生成实例 self._running_plugins[plugin_id] = plugin() # 初始化配置 self.reload_plugin(plugin_id) logger.info(f"Plugin Loaded:{plugin.__name__}") def reload_plugin(self, pid: str): """ 生效插件配置 """ if not pid: return if not self._running_plugins.get(pid): return if hasattr(self._running_plugins[pid], "init_plugin"): try: self._running_plugins[pid].init_plugin(self.get_plugin_config(pid)) logger.debug(f"生效插件配置:{pid}") except Exception as err: logger.error(f"加载插件 {pid} 出错:{err} - {traceback.format_exc()}") def __stop_plugins(self): """ 停止所有插件 """ for plugin in self._running_plugins.values(): if hasattr(plugin, "stop"): plugin.stop() def get_plugin_config(self, pid: str) -> dict: """ 获取插件配置 """ if not self._plugins.get(pid): return {} return self.systemconfigs.get(self._config_key % pid) or {} def get_plugin_page(self, pid: str) -> Tuple[Optional[str], Optional[str], Optional[str]]: """ 获取插件额外页面数据 :return: 标题,页面内容,确定按钮响应函数 """ if not self._running_plugins.get(pid): return None, None, None if not hasattr(self._running_plugins[pid], "get_page"): return None, None, None return self._running_plugins[pid].get_page() def get_plugin_script(self, pid: str) -> Optional[str]: """ 获取插件额外脚本 """ if not self._running_plugins.get(pid): return None if not hasattr(self._running_plugins[pid], "get_script"): return None return self._running_plugins[pid].get_script() def get_plugin_state(self, pid: str) -> Optional[bool]: """ 获取插件状态 """ if not self._running_plugins.get(pid): return None if not hasattr(self._running_plugins[pid], "get_state"): return None return self._running_plugins[pid].get_state() def save_plugin_config(self, pid: str, conf: dict) -> bool: """ 保存插件配置 """ if not self._plugins.get(pid): return False return self.systemconfigs.set(self._config_key % pid, conf) @staticmethod def __get_plugin_color(plugin: str) -> str: """ 获取插件的主题色 """ if hasattr(plugin, "plugin_color") and plugin.plugin_color: return plugin.plugin_color return "" def get_plugins_conf(self, auth_level: int) -> dict: """ 获取所有插件配置 """ all_confs = {} for pid, plugin in self._running_plugins.items(): # 基本属性 conf = {} # 权限 if hasattr(plugin, "auth_level") \ and plugin.auth_level > auth_level: continue # 名称 if hasattr(plugin, "plugin_name"): conf.update({"name": plugin.plugin_name}) # 描述 if hasattr(plugin, "plugin_desc"): conf.update({"desc": plugin.plugin_desc}) # 版本号 if hasattr(plugin, "plugin_version"): conf.update({"version": plugin.plugin_version}) # 图标 if hasattr(plugin, "plugin_icon"): conf.update({"icon": plugin.plugin_icon}) # ID前缀 if hasattr(plugin, "plugin_config_prefix"): conf.update({"prefix": plugin.plugin_config_prefix}) # 插件额外的页面 if hasattr(plugin, "get_page"): title, _, _ = plugin.get_page() conf.update({"page": title}) # 插件额外的脚本 if hasattr(plugin, "get_script"): conf.update({"script": plugin.get_script()}) # 主题色 conf.update({"color": self.__get_plugin_color(plugin)}) # 配置项 conf.update({"fields": plugin.get_fields() or {}}) # 配置值 conf.update({"config": self.get_plugin_config(pid)}) # 状态 conf.update({"state": plugin.get_state()}) # 汇总 all_confs[pid] = conf return all_confs def get_plugin_apps(self, auth_level: int) -> dict: """ 获取所有插件 """ all_confs = {} for pid, plugin in self._plugins.items(): # 基本属性 conf = {} # 权限 if hasattr(plugin, "auth_level") \ and plugin.auth_level > auth_level: continue # ID conf.update({"id": pid}) # 名称 if hasattr(plugin, "plugin_name"): conf.update({"name": plugin.plugin_name}) # 描述 if hasattr(plugin, "plugin_desc"): conf.update({"desc": plugin.plugin_desc}) # 版本 if hasattr(plugin, "plugin_version"): conf.update({"version": plugin.plugin_version}) # 图标 if hasattr(plugin, "plugin_icon"): conf.update({"icon": plugin.plugin_icon}) # 主题色 conf.update({"color": self.__get_plugin_color(plugin)}) if hasattr(plugin, "plugin_author"): conf.update({"author": plugin.plugin_author}) # 作者链接 if hasattr(plugin, "author_url"): conf.update({"author_url": plugin.author_url}) # 汇总 all_confs[pid] = conf return all_confs def get_plugin_commands(self) -> List[dict]: """ 获取插件命令 [{ "cmd": "/xx", "event": EventType.xx, "desc": "xxxx", "data": {} }] """ ret_commands = [] for _, plugin in self._running_plugins.items(): if hasattr(plugin, "get_command"): ret_commands.append(plugin.get_command()) return ret_commands def run_plugin_method(self, pid: str, method: str, *args, **kwargs) -> Any: """ 运行插件方法 """ if not self._running_plugins.get(pid): return None if not hasattr(self._running_plugins[pid], method): return None return getattr(self._running_plugins[pid], method)(*args, **kwargs)