702 lines
29 KiB
Python
702 lines
29 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import logging
|
||
import asyncio
|
||
import importlib.util
|
||
import sys
|
||
import os
|
||
import inspect
|
||
import time
|
||
from pathlib import Path
|
||
from typing import Dict, List, Any, Optional, Callable
|
||
from dataclasses import dataclass
|
||
import yaml
|
||
import traceback
|
||
from sdk.plugin_command_decorator import plugin_command, command
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 加载失败记录 — 供统计 API 使用
|
||
LOAD_FAILURES: Dict[str, str] = {}
|
||
|
||
@dataclass
|
||
class PluginInfo:
|
||
"""插件信息数据类"""
|
||
name: str
|
||
version: str
|
||
description: str
|
||
author: str
|
||
enabled: bool
|
||
loaded: bool
|
||
error_count: int
|
||
permissions: List[str]
|
||
plugin_path: Path
|
||
commands: Dict[str, Dict] = None # 新增命令信息
|
||
|
||
class PluginService:
|
||
"""插件服务 - 管理插件的加载、卸载和运行"""
|
||
|
||
def __init__(self, config: Dict, permission_service, bridge_service, service_manager):
|
||
self.config = config
|
||
self.permission_service = permission_service
|
||
self.bridge_service = bridge_service
|
||
self.service_manager = service_manager # 新增服务管理器
|
||
self.bridge_service.service_manager = self.service_manager
|
||
self.plugins: Dict[str, Any] = {}
|
||
self.plugin_info: Dict[str, PluginInfo] = {}
|
||
self.plugins_dir = Path("plugins")
|
||
self.is_running = False
|
||
logger.debug("PluginService初始化开始")
|
||
|
||
|
||
async def start(self):
|
||
"""启动插件服务"""
|
||
try:
|
||
logger.info("启动插件服务")
|
||
|
||
# 创建插件目录
|
||
self.plugins_dir.mkdir(exist_ok=True)
|
||
|
||
# 自动加载插件
|
||
if self.config['plugins']['auto_load']:
|
||
await self.load_all_plugins()
|
||
|
||
await self.save_command_config()
|
||
|
||
# 启动 watchdog 热重载
|
||
if self.config.get('plugins', {}).get('hot_reload', False):
|
||
self._loop = asyncio.get_running_loop()
|
||
self._start_watchdog()
|
||
|
||
self.is_running = True
|
||
logger.info("插件服务启动完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"启动插件服务时出错: {str(e)}", exc_info=True)
|
||
raise
|
||
|
||
# ── watchdog 热重载 ──────────────────────────────────────
|
||
|
||
def _start_watchdog(self):
|
||
"""启动 watchdog 监听 plugins/ 目录实现热重载"""
|
||
try:
|
||
from watchdog.observers import Observer
|
||
from watchdog.events import FileSystemEventHandler
|
||
|
||
plugin_service = self # 闭包引用
|
||
|
||
class _PluginReloadHandler(FileSystemEventHandler):
|
||
"""防抖 + 插件级重载"""
|
||
|
||
def __init__(self):
|
||
self._debounce: Dict[str, float] = {}
|
||
self._debounce_sec = 1.0 # 1 秒内同插件只触发一次
|
||
|
||
def _plugin_name_from_path(self, event_path: str) -> Optional[str]:
|
||
"""从事件路径提取插件名(plugins/<name>/...)"""
|
||
try:
|
||
rel = os.path.relpath(event_path, str(plugin_service.plugins_dir))
|
||
parts = Path(rel).parts
|
||
if parts and not parts[0].startswith('.'):
|
||
return parts[0]
|
||
except ValueError:
|
||
pass
|
||
return None
|
||
|
||
def _should_handle(self, plugin_name: str) -> bool:
|
||
"""防抖 — 同插件在冷却时间内跳过"""
|
||
now = time.time()
|
||
last = self._debounce.get(plugin_name, 0)
|
||
if now - last < self._debounce_sec:
|
||
return False
|
||
self._debounce[plugin_name] = now
|
||
return True
|
||
|
||
def on_modified(self, event):
|
||
if event.is_directory:
|
||
return
|
||
path = event.src_path
|
||
if not path.endswith(('.py', '.yaml', '.yml')):
|
||
return
|
||
plugin_name = self._plugin_name_from_path(path)
|
||
if not plugin_name:
|
||
return
|
||
if not self._should_handle(plugin_name):
|
||
return
|
||
logger.info(
|
||
f"🔁 检测到插件文件变更: {plugin_name} ({os.path.basename(path)})"
|
||
)
|
||
asyncio.run_coroutine_threadsafe(
|
||
plugin_service._reload_plugin(plugin_name),
|
||
plugin_service._loop,
|
||
)
|
||
|
||
def on_created(self, event):
|
||
self.on_modified(event)
|
||
|
||
self._watchdog_observer = Observer()
|
||
self._watchdog_observer.schedule(
|
||
_PluginReloadHandler(),
|
||
str(self.plugins_dir),
|
||
recursive=True,
|
||
)
|
||
self._watchdog_observer.start()
|
||
logger.info("👁️ 插件热重载已启动 (watchdog)")
|
||
|
||
except ImportError:
|
||
logger.warning(
|
||
"watchdog 未安装,插件热重载不可用。pip install watchdog"
|
||
)
|
||
except Exception as e:
|
||
logger.warning(f"启动 watchdog 失败 (不影响框架): {e}")
|
||
|
||
async def _reload_plugin(self, plugin_name: str):
|
||
"""热重载单个插件 — 卸载后重新加载"""
|
||
plugin_dir = self.plugins_dir / plugin_name
|
||
if not plugin_dir.is_dir():
|
||
logger.debug(f"插件目录已消失,跳过重载: {plugin_name}")
|
||
return
|
||
|
||
# 路由器已冻结时跳过热重载 — 否则路由注册失败导致插件功能丢失
|
||
try:
|
||
internet = self.service_manager.get_service("internet")
|
||
if internet and internet.is_running:
|
||
logger.info(f" ⏭ 路由器已冻结,跳过热重载: {plugin_name} (需重启框架)")
|
||
return
|
||
except Exception:
|
||
pass
|
||
|
||
if plugin_name in self.plugins:
|
||
logger.info(f" ⏳ 卸载旧版本: {plugin_name}")
|
||
await self.unload_plugin(plugin_name)
|
||
|
||
await asyncio.sleep(0.2) # 给文件系统缓冲
|
||
|
||
success = await self.load_plugin(plugin_name)
|
||
if success:
|
||
# 重载后重新注册延迟路由
|
||
try:
|
||
internet = self.service_manager.get_service("internet")
|
||
if internet and internet.is_running:
|
||
plugin = self.plugins.get(plugin_name)
|
||
if plugin and hasattr(plugin, 'network_bridge'):
|
||
await self._reinitialize_plugin_network(plugin, internet)
|
||
except Exception:
|
||
pass
|
||
logger.info(f" ✅ 热重载完成: {plugin_name}")
|
||
else:
|
||
logger.warning(f" ❌ 热重载失败: {plugin_name}")
|
||
|
||
def _stop_watchdog(self):
|
||
"""停止 watchdog observer"""
|
||
obs = getattr(self, '_watchdog_observer', None)
|
||
if obs and obs.is_alive():
|
||
obs.stop()
|
||
obs.join(timeout=3)
|
||
logger.info("👁️ 插件热重载已停止")
|
||
|
||
async def stop(self):
|
||
"""停止插件服务"""
|
||
logger.info("关闭插件服务")
|
||
self._stop_watchdog()
|
||
for name in list(self.plugins.keys()):
|
||
await self.unload_plugin(name)
|
||
self.is_running = False
|
||
|
||
def _should_isolate(self, plugin_config: dict) -> bool:
|
||
"""检查插件是否应使用进程隔离模式"""
|
||
global_isolation = self.config.get('plugins', {}).get('isolation', False)
|
||
plugin_isolation = plugin_config.get('settings', {}).get('isolation', None)
|
||
if plugin_isolation is not None:
|
||
return bool(plugin_isolation)
|
||
return global_isolation
|
||
|
||
async def load_all_plugins(self):
|
||
"""加载所有插件"""
|
||
try:
|
||
logger.debug("开始加载所有插件")
|
||
|
||
if not self.plugins_dir.exists():
|
||
logger.warning("插件目录不存在,跳过加载")
|
||
return
|
||
|
||
loaded_count = 0
|
||
error_count = 0
|
||
isolated_count = 0
|
||
|
||
# 遍历插件目录
|
||
for plugin_dir in self.plugins_dir.iterdir():
|
||
if plugin_dir.is_dir():
|
||
try:
|
||
# 先读配置判断是否需要隔离
|
||
config_file = plugin_dir / "config.yaml"
|
||
plugin_config = {}
|
||
if config_file.exists():
|
||
with open(config_file, 'r') as f:
|
||
plugin_config = yaml.safe_load(f) or {}
|
||
|
||
if self._should_isolate(plugin_config):
|
||
success = await self._load_plugin_isolated(plugin_dir.name)
|
||
if success:
|
||
isolated_count += 1
|
||
loaded_count += 1
|
||
else:
|
||
error_count += 1
|
||
else:
|
||
success = await self.load_plugin(plugin_dir.name)
|
||
if success:
|
||
loaded_count += 1
|
||
else:
|
||
error_count += 1
|
||
except Exception as e:
|
||
logger.error(f"加载插件 {plugin_dir.name} 时出错: {str(e)}", exc_info=True)
|
||
error_count += 1
|
||
|
||
logger.info(
|
||
f"插件加载完成: 成功 {loaded_count} (其中隔离 {isolated_count}), 失败 {error_count}"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载所有插件时出错: {str(e)}", exc_info=True)
|
||
raise
|
||
|
||
async def _load_plugin_isolated(self, plugin_name: str) -> bool:
|
||
"""在独立子进程中加载插件(进程隔离模式)"""
|
||
try:
|
||
from services.process_isolated import IsolatedPlugin
|
||
|
||
plugin_path = self.plugins_dir / plugin_name
|
||
main_module = plugin_path / "__init__.py"
|
||
if not main_module.exists():
|
||
logger.error(f"隔离插件主模块不存在: {main_module}")
|
||
return False
|
||
|
||
config_file = plugin_path / "config.yaml"
|
||
plugin_config = {}
|
||
if config_file.exists():
|
||
with open(config_file) as f:
|
||
plugin_config = yaml.safe_load(f) or {}
|
||
|
||
iso = IsolatedPlugin(
|
||
plugin_name,
|
||
str(main_module),
|
||
plugin_config,
|
||
)
|
||
|
||
self.plugins[plugin_name] = iso
|
||
self.plugin_info[plugin_name] = PluginInfo(
|
||
name=plugin_config.get('name', plugin_name),
|
||
version=plugin_config.get('version', '0.1.0'),
|
||
description=plugin_config.get('description', ''),
|
||
author=plugin_config.get('author', ''),
|
||
enabled=True,
|
||
loaded=True,
|
||
error_count=0,
|
||
permissions=[],
|
||
plugin_path=plugin_path,
|
||
commands={},
|
||
)
|
||
logger.info(f"🔒 隔离插件加载成功: {plugin_name} (PID={iso.pid})")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载隔离插件 {plugin_name} 失败: {e}")
|
||
return False
|
||
|
||
async def load_plugin(self, plugin_name: str) -> bool:
|
||
"""加载单个插件 - 支持异步权限处理"""
|
||
try:
|
||
logger.debug(f"开始加载插件: {plugin_name}")
|
||
|
||
plugin_path = self.plugins_dir / plugin_name
|
||
|
||
if not plugin_path.exists():
|
||
LOAD_FAILURES[plugin_name] = "插件目录不存在"
|
||
logger.error(f"插件目录不存在: {plugin_path}")
|
||
return False
|
||
|
||
# 检查插件配置文件 (自动从 .example 模板创建)
|
||
config_file = plugin_path / "config.yaml"
|
||
if not config_file.exists():
|
||
template = plugin_path / "config.yaml.example"
|
||
if template.exists():
|
||
import shutil as _shutil
|
||
_shutil.copy(template, config_file)
|
||
logger.info(f"📋 从模板创建插件配置: {config_file}")
|
||
else:
|
||
logger.error(f"插件配置文件不存在: {config_file}")
|
||
return False
|
||
|
||
# 加载插件配置
|
||
with open(config_file, 'r', encoding='utf-8') as f:
|
||
plugin_config = yaml.safe_load(f)
|
||
|
||
# 检查权限文件
|
||
permission_file = plugin_path / "permissions.yaml"
|
||
if not permission_file.exists():
|
||
logger.error(f"插件权限文件不存在: {permission_file}")
|
||
return False
|
||
|
||
# 加载权限配置
|
||
with open(permission_file, 'r', encoding='utf-8') as f:
|
||
permission_config = yaml.safe_load(f)
|
||
|
||
# 验证插件信息
|
||
required_fields = ['name', 'version', 'description', 'author']
|
||
for field in required_fields:
|
||
if field not in plugin_config:
|
||
logger.error(f"插件配置缺少必要字段: {field}")
|
||
return False
|
||
|
||
# 检查主模块
|
||
main_module = plugin_path / "__init__.py"
|
||
if not main_module.exists():
|
||
logger.error(f"插件主模块不存在: {main_module}")
|
||
return False
|
||
|
||
# 动态加载插件模块
|
||
module_name = f"plugins.{plugin_name}"
|
||
spec = importlib.util.spec_from_file_location(module_name, main_module)
|
||
if not spec:
|
||
logger.error(f"无法创建模块规范: {module_name}")
|
||
return False
|
||
|
||
module = importlib.util.module_from_spec(spec)
|
||
sys.modules[module_name] = module
|
||
|
||
try:
|
||
spec.loader.exec_module(module)
|
||
logger.debug(f"插件模块加载成功: {module_name}")
|
||
except Exception as e:
|
||
logger.error(f"执行插件模块时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
# 获取插件类实例
|
||
if not hasattr(module, 'Plugin'):
|
||
logger.error(f"插件类 'Plugin' 不存在: {module_name}")
|
||
return False
|
||
|
||
# 权限申请和验证 - 非阻塞版本
|
||
permissions = permission_config.get('permissions', [])
|
||
if permissions:
|
||
# 非阻塞权限请求,立即返回True让插件继续加载
|
||
permission_result = await self.permission_service.request_permissions(plugin_name, permissions)
|
||
if not permission_result:
|
||
logger.warning(f"插件权限申请失败: {plugin_name}")
|
||
# 即使权限申请失败,也允许插件以受限模式运行
|
||
logger.info(f"插件 {plugin_name} 将以受限模式运行")
|
||
|
||
# 实例化插件
|
||
try:
|
||
plugin_instance = module.Plugin(
|
||
plugin_name=plugin_name,
|
||
config=plugin_config,
|
||
bridge=self.bridge_service
|
||
)
|
||
|
||
# 初始化插件
|
||
if hasattr(plugin_instance, 'initialize'):
|
||
if asyncio.iscoroutinefunction(plugin_instance.initialize):
|
||
await plugin_instance.initialize()
|
||
else:
|
||
plugin_instance.initialize()
|
||
|
||
# 扫描并注册 TUI 命令
|
||
plugin_commands = await self._scan_and_register_commands(plugin_name, plugin_instance, plugin_config)
|
||
|
||
# 自动暴露插件命令为 REST 端点
|
||
if hasattr(plugin_instance, 'network_bridge') and plugin_instance.network_bridge:
|
||
await plugin_instance.network_bridge.register_command_routes(
|
||
plugin_instance, require_auth=True
|
||
)
|
||
|
||
# 注册插件
|
||
self.plugins[plugin_name] = plugin_instance
|
||
|
||
# 保存插件信息
|
||
self.plugin_info[plugin_name] = PluginInfo(
|
||
name=plugin_config['name'],
|
||
version=plugin_config['version'],
|
||
description=plugin_config['description'],
|
||
author=plugin_config['author'],
|
||
enabled=True,
|
||
loaded=True,
|
||
error_count=0,
|
||
permissions=permissions,
|
||
plugin_path=plugin_path,
|
||
commands=plugin_commands
|
||
)
|
||
|
||
logger.info(f"插件加载成功: {plugin_name} v{plugin_config['version']}, 注册了 {len(plugin_commands)} 个命令")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"实例化插件时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"加载插件 {plugin_name} 时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
async def _scan_and_register_commands(self, plugin_name: str, plugin_instance: Any, plugin_config: Dict) -> Dict[str, Dict]:
|
||
"""扫描并注册插件命令 - 修正版本"""
|
||
try:
|
||
logger.debug(f"扫描插件命令: {plugin_name}")
|
||
|
||
command_service = self.service_manager.get_service("command")
|
||
if not command_service:
|
||
logger.error("命令服务不可用,无法注册插件命令")
|
||
return {}
|
||
|
||
# 扫描插件中的命令方法
|
||
command_methods = {}
|
||
for name, method in inspect.getmembers(plugin_instance, predicate=inspect.ismethod):
|
||
# 检查方法是否有命令装饰器或符合命名约定
|
||
if (hasattr(method, '_is_plugin_command') or
|
||
name.startswith('cmd_') or
|
||
name.startswith('command_')):
|
||
|
||
command_name = self._get_command_name(name, method, plugin_config)
|
||
command_description = self._get_command_description(name, method, plugin_config)
|
||
command_permissions = self._get_command_permissions(name, method, plugin_config)
|
||
|
||
# 修正:使用正确的source格式
|
||
command_service.register_command(
|
||
name=command_name,
|
||
handler=method,
|
||
description=command_description,
|
||
permissions=command_permissions,
|
||
source=f"plugin.{plugin_name}" # 使用 plugin.插件名 格式
|
||
)
|
||
|
||
command_methods[command_name] = {
|
||
'method_name': name,
|
||
'description': command_description,
|
||
'permissions': command_permissions
|
||
}
|
||
|
||
logger.debug(f"注册插件命令: {command_name} -> {name}")
|
||
|
||
return command_methods
|
||
|
||
except Exception as e:
|
||
logger.error(f"扫描插件命令时出错: {str(e)}", exc_info=True)
|
||
return {}
|
||
|
||
def _get_command_name(self, method_name: str, method: Callable, plugin_config: Dict) -> str:
|
||
"""获取命令名称"""
|
||
try:
|
||
# 如果方法有装饰器指定的名称
|
||
if hasattr(method, '_command_name'):
|
||
return getattr(method, '_command_name')
|
||
|
||
# 从方法名提取命令名
|
||
if method_name.startswith('cmd_'):
|
||
return method_name[4:]
|
||
elif method_name.startswith('command_'):
|
||
return method_name[8:]
|
||
else:
|
||
return method_name
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取命令名称时出错: {str(e)}")
|
||
return method_name
|
||
|
||
def _get_command_description(self, method_name: str, method: Callable, plugin_config: Dict) -> str:
|
||
"""获取命令描述"""
|
||
try:
|
||
# 如果方法有装饰器指定的描述
|
||
if hasattr(method, '_command_description'):
|
||
return getattr(method, '_command_description')
|
||
|
||
# 使用方法的文档字符串
|
||
if method.__doc__:
|
||
# 提取第一行作为描述
|
||
doc_lines = method.__doc__.strip().split('\n')
|
||
return doc_lines[0].strip()
|
||
|
||
# 默认描述
|
||
return f"插件命令: {method_name}"
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取命令描述时出错: {str(e)}")
|
||
return f"插件命令: {method_name}"
|
||
|
||
def _get_command_permissions(self, method_name: str, method: Callable, plugin_config: Dict) -> List[str]:
|
||
"""获取命令权限"""
|
||
try:
|
||
# 如果方法有装饰器指定的权限
|
||
if hasattr(method, '_command_permissions'):
|
||
return getattr(method, '_command_permissions')
|
||
|
||
# 从插件配置中获取默认权限
|
||
default_permissions = plugin_config.get('default_command_permissions', [])
|
||
return default_permissions.copy()
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取命令权限时出错: {str(e)}")
|
||
return []
|
||
|
||
async def unload_plugin(self, plugin_name: str) -> bool:
|
||
"""卸载插件"""
|
||
try:
|
||
logger.debug(f"开始卸载插件: {plugin_name}")
|
||
|
||
if plugin_name not in self.plugins:
|
||
logger.warning(f"插件未加载: {plugin_name}")
|
||
return False
|
||
|
||
plugin_instance = self.plugins[plugin_name]
|
||
plugin_info = self.plugin_info[plugin_name]
|
||
|
||
# 注销插件命令 (隔离插件可能没有注册命令)
|
||
if not self._is_isolated(plugin_instance):
|
||
await self._unregister_plugin_commands(plugin_name)
|
||
|
||
# 调用插件的清理方法
|
||
try:
|
||
if hasattr(plugin_instance, 'shutdown'):
|
||
if asyncio.iscoroutinefunction(plugin_instance.shutdown):
|
||
await plugin_instance.shutdown()
|
||
else:
|
||
plugin_instance.shutdown()
|
||
except Exception as e:
|
||
logger.error(f"插件清理时出错 {plugin_name}: {str(e)}", exc_info=True)
|
||
|
||
# 从模块缓存中移除
|
||
module_name = f"plugins.{plugin_name}"
|
||
if module_name in sys.modules:
|
||
del sys.modules[module_name]
|
||
|
||
# 移除插件实例和信息
|
||
del self.plugins[plugin_name]
|
||
plugin_info.loaded = False
|
||
plugin_info.enabled = False
|
||
|
||
logger.info(f"插件卸载成功: {plugin_name}")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"卸载插件 {plugin_name} 时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
@staticmethod
|
||
def _is_isolated(plugin_instance) -> bool:
|
||
from services.process_isolated import IsolatedPlugin
|
||
return isinstance(plugin_instance, IsolatedPlugin)
|
||
|
||
async def _unregister_plugin_commands(self, plugin_name: str):
|
||
"""注销插件命令"""
|
||
try:
|
||
command_service = self.service_manager.get_service("command")
|
||
if not command_service:
|
||
return
|
||
|
||
# 从命令服务中移除该插件的所有命令
|
||
commands_to_remove = []
|
||
for cmd_name, cmd_info in command_service.commands.items():
|
||
if cmd_info.source.startswith(f"plugin.{plugin_name}"):
|
||
commands_to_remove.append(cmd_name)
|
||
|
||
for cmd_name in commands_to_remove:
|
||
del command_service.commands[cmd_name]
|
||
logger.debug(f"注销插件命令: {cmd_name}")
|
||
|
||
logger.info(f"已注销插件 {plugin_name} 的 {len(commands_to_remove)} 个命令")
|
||
|
||
except Exception as e:
|
||
logger.error(f"注销插件命令时出错: {str(e)}", exc_info=True)
|
||
|
||
async def save_command_config(self):
|
||
"""保存命令配置到文件"""
|
||
try:
|
||
command_service = self.service_manager.get_service("command")
|
||
if not command_service:
|
||
logger.error("命令服务不可用")
|
||
return False
|
||
|
||
command_list = command_service.get_command_list()
|
||
config_path = Path("config") / "plugins" / "commands.yaml"
|
||
|
||
# 确保目录存在
|
||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||
|
||
config_data = {
|
||
"commands": {},
|
||
"plugin_commands": {},
|
||
"last_updated": asyncio.get_event_loop().time(),
|
||
"total_commands": len(command_list)
|
||
}
|
||
|
||
# 按来源分组命令
|
||
for cmd in command_list:
|
||
cmd_info = {
|
||
"description": cmd['description'],
|
||
"permissions": cmd['permissions'],
|
||
"source": cmd['source']
|
||
}
|
||
|
||
config_data["commands"][cmd['name']] = cmd_info
|
||
|
||
# 按插件分组
|
||
if cmd['source'].startswith("plugin."):
|
||
plugin_name = cmd['source'].split('.', 1)[1]
|
||
if plugin_name not in config_data["plugin_commands"]:
|
||
config_data["plugin_commands"][plugin_name] = {}
|
||
config_data["plugin_commands"][plugin_name][cmd['name']] = cmd_info
|
||
|
||
with open(config_path, 'w', encoding='utf-8') as f:
|
||
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True, indent=2)
|
||
|
||
logger.info(f"命令配置已保存: {config_path}, 共 {len(command_list)} 个命令")
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存命令配置时出错: {str(e)}", exc_info=True)
|
||
return False
|
||
|
||
async def register_delayed_routes(self, internet_service):
|
||
"""注册延迟的路由(在网络服务启动后)"""
|
||
try:
|
||
if not internet_service:
|
||
logger.warning("网络服务不可用,跳过延迟路由注册")
|
||
return
|
||
|
||
for plugin_name, plugin_instance in self.plugins.items():
|
||
try:
|
||
# 检查插件是否有延迟注册方法
|
||
if hasattr(plugin_instance, 'register_delayed_routes'):
|
||
await plugin_instance.register_delayed_routes(internet_service)
|
||
logger.info(f"延迟注册插件路由: {plugin_name}")
|
||
else:
|
||
# 如果插件没有延迟注册方法,尝试重新初始化网络功能
|
||
await self._reinitialize_plugin_network(plugin_instance, internet_service)
|
||
|
||
except Exception as e:
|
||
logger.error(f"延迟注册插件 {plugin_name} 路由时出错: {str(e)}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"注册延迟路由时出错: {str(e)}")
|
||
|
||
async def _reinitialize_plugin_network(self, plugin_instance, internet_service):
|
||
"""重新初始化插件的网络功能"""
|
||
try:
|
||
plugin_name = plugin_instance.plugin_name
|
||
|
||
# 检查插件是否有网络桥接
|
||
if hasattr(plugin_instance, 'network_bridge'):
|
||
# 重新创建网络桥接
|
||
plugin_instance.network_bridge = PluginNetworkBridge(
|
||
plugin_name, internet_service, plugin_instance.bridge
|
||
)
|
||
|
||
# 重新设置网络路由
|
||
if hasattr(plugin_instance, '_setup_network_routes'):
|
||
await plugin_instance._setup_network_routes()
|
||
|
||
logger.info(f"重新初始化插件网络功能: {plugin_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"重新初始化插件网络功能时出错: {str(e)}")
|
||
|