feat: 优化整体项目

This commit is contained in:
2025-12-05 18:06:13 +08:00
parent b4df26f61d
commit 7d3ef70093
13 changed files with 2661 additions and 305 deletions

View File

@@ -2,8 +2,17 @@
插件管理插件
提供插件的热重载、启用、禁用等管理功能
支持的指令:
/插件列表 - 查看所有插件状态
/重载插件 <名称> - 重载指定插件
/重载所有插件 - 重载所有插件
/启用插件 <名称> - 启用指定插件
/禁用插件 <名称> - 禁用指定插件
/刷新插件 - 扫描并发现新插件
/插件帮助 - 显示帮助信息
"""
import sys
import tomllib
from pathlib import Path
from loguru import logger
@@ -18,7 +27,10 @@ class ManagePlugin(PluginBase):
# 插件元数据
description = "插件管理,支持热重载、启用、禁用"
author = "ShiHao"
version = "1.0.0"
version = "2.0.0"
# 最高加载优先级,确保最先加载
load_priority = 100
def __init__(self):
super().__init__()
@@ -34,37 +46,72 @@ class ManagePlugin(PluginBase):
self.admins = main_config.get("Bot", {}).get("admins", [])
logger.info(f"插件管理插件已加载,管理员: {self.admins}")
@on_text_message()
def _check_admin(self, message: dict) -> bool:
"""检查是否是管理员"""
sender_wxid = message.get("SenderWxid", "")
from_wxid = message.get("FromWxid", "")
is_group = message.get("IsGroup", False)
# 私聊时 sender_wxid 可能为空,使用 from_wxid
user_wxid = sender_wxid if is_group else from_wxid
return user_wxid in self.admins
@on_text_message(priority=99)
async def handle_command(self, bot, message: dict):
"""处理管理命令"""
content = message.get("Content", "").strip()
from_wxid = message.get("FromWxid", "")
sender_wxid = message.get("SenderWxid", "")
logger.debug(f"ManagePlugin: content={content}, from={from_wxid}, sender={sender_wxid}, admins={self.admins}")
# 检查权限
if not self.admins or sender_wxid not in self.admins:
return
if not self._check_admin(message):
return True # 继续传递给其他插件
# 插件帮助
if content == "/插件帮助" or content == "/plugin help":
await self._show_help(bot, from_wxid)
return False
# 插件列表
if content == "/插件列表" or content == "/plugins":
elif content == "/插件列表" or content == "/plugins":
await self._list_plugins(bot, from_wxid)
return False
# 重载所有插件
elif content == "/重载所有插件" or content == "/reload all":
await self._reload_all_plugins(bot, from_wxid)
return False
# 重载插件
elif content.startswith("/重载插件 ") or content.startswith("/reload "):
plugin_name = content.split(maxsplit=1)[1].strip()
await self._reload_plugin(bot, from_wxid, plugin_name)
return False
# 启用插件
elif content.startswith("/启用插件 ") or content.startswith("/enable "):
plugin_name = content.split(maxsplit=1)[1].strip()
await self._enable_plugin(bot, from_wxid, plugin_name)
return False
# 禁用插件
elif content.startswith("/禁用插件 ") or content.startswith("/disable "):
plugin_name = content.split(maxsplit=1)[1].strip()
await self._disable_plugin(bot, from_wxid, plugin_name)
return False
# 刷新插件(发现新插件)
elif content == "/刷新插件" or content == "/refresh":
await self._refresh_plugins(bot, from_wxid)
return False
# 加载新插件(从目录加载全新插件)
elif content.startswith("/加载插件 ") or content.startswith("/load "):
plugin_name = content.split(maxsplit=1)[1].strip()
await self._load_new_plugin(bot, from_wxid, plugin_name)
return False
return True # 不是管理命令,继续传递
async def _list_plugins(self, bot, to_wxid: str):
"""列出所有插件"""
@@ -159,3 +206,200 @@ class ManagePlugin(PluginBase):
logger.info(f"插件 {plugin_name} 已被禁用")
else:
await bot.send_text(to_wxid, f"❌ 插件 {plugin_name} 禁用失败")
async def _show_help(self, bot, to_wxid: str):
"""显示帮助信息"""
help_text = """📦 插件管理帮助
/插件列表 - 查看所有插件状态
/插件帮助 - 显示此帮助信息
/加载插件 <名称> - 加载新插件(无需重启)
/重载插件 <名称> - 热重载指定插件
/重载所有插件 - 热重载所有插件
/启用插件 <名称> - 启用已禁用的插件
/禁用插件 <名称> - 禁用指定插件
/刷新插件 - 扫描发现新插件
示例:
/加载插件 NewPlugin
/重载插件 AIChat
/禁用插件 Weather"""
await bot.send_text(to_wxid, help_text)
async def _reload_all_plugins(self, bot, to_wxid: str):
"""重载所有插件"""
pm = PluginManager()
await bot.send_text(to_wxid, "⏳ 正在重载所有插件...")
try:
# 清理插件相关的模块缓存
modules_to_remove = [
name for name in sys.modules.keys()
if name.startswith('plugins.') and 'ManagePlugin' not in name
]
for module_name in modules_to_remove:
del sys.modules[module_name]
# 重载所有插件
reloaded = await pm.reload_plugins()
if reloaded:
await bot.send_text(
to_wxid,
f"✅ 重载完成\n已加载 {len(reloaded)} 个插件:\n" +
"\n".join(f"{name}" for name in reloaded)
)
logger.success(f"已重载 {len(reloaded)} 个插件")
else:
await bot.send_text(to_wxid, "⚠️ 没有插件被重载")
except Exception as e:
logger.error(f"重载所有插件失败: {e}")
await bot.send_text(to_wxid, f"❌ 重载失败: {e}")
async def _refresh_plugins(self, bot, to_wxid: str):
"""刷新插件列表,发现新插件"""
pm = PluginManager()
try:
# 记录刷新前的插件数量
old_count = len(pm.plugin_info)
# 刷新插件列表
await pm.refresh_plugins()
# 计算新发现的插件
new_count = len(pm.plugin_info)
new_plugins = new_count - old_count
if new_plugins > 0:
# 获取新发现的插件名称
new_plugin_names = [
info["name"] for info in pm.plugin_info.values()
if not info.get("enabled", False)
][-new_plugins:]
await bot.send_text(
to_wxid,
f"✅ 发现 {new_plugins} 个新插件:\n" +
"\n".join(f"{name}" for name in new_plugin_names) +
"\n\n使用 /启用插件 <名称> 来启用"
)
logger.info(f"发现 {new_plugins} 个新插件: {new_plugin_names}")
else:
await bot.send_text(to_wxid, " 没有发现新插件")
except Exception as e:
logger.error(f"刷新插件失败: {e}")
await bot.send_text(to_wxid, f"❌ 刷新失败: {e}")
async def _load_new_plugin(self, bot, to_wxid: str, plugin_name: str):
"""加载全新的插件(支持插件类名或目录名)"""
import os
import importlib
import inspect
pm = PluginManager()
# 检查是否已加载
if plugin_name in pm.plugins:
await bot.send_text(to_wxid, f" 插件 {plugin_name} 已经加载,如需重载请使用 /重载插件")
return
try:
# 尝试查找插件
found = False
plugin_class = None
actual_plugin_name = None
for dirname in os.listdir("plugins"):
dirpath = f"plugins/{dirname}"
if not os.path.isdir(dirpath) or not os.path.exists(f"{dirpath}/main.py"):
continue
# 支持通过目录名或类名查找
if dirname == plugin_name or dirname.lower() == plugin_name.lower():
# 通过目录名匹配
module_name = f"plugins.{dirname}.main"
# 清理旧的模块缓存
if module_name in sys.modules:
del sys.modules[module_name]
# 导入模块
module = importlib.import_module(module_name)
# 查找插件类
for name, obj in inspect.getmembers(module):
if (inspect.isclass(obj) and
issubclass(obj, PluginBase) and
obj != PluginBase):
plugin_class = obj
actual_plugin_name = obj.__name__
found = True
break
if found:
break
# 尝试通过类名匹配
try:
module_name = f"plugins.{dirname}.main"
if module_name in sys.modules:
del sys.modules[module_name]
module = importlib.import_module(module_name)
for name, obj in inspect.getmembers(module):
if (inspect.isclass(obj) and
issubclass(obj, PluginBase) and
obj != PluginBase and
(obj.__name__ == plugin_name or obj.__name__.lower() == plugin_name.lower())):
plugin_class = obj
actual_plugin_name = obj.__name__
found = True
break
if found:
break
except:
continue
if not found or not plugin_class:
await bot.send_text(
to_wxid,
f"❌ 未找到插件 {plugin_name}\n"
f"请确认:\n"
f"1. plugins/{plugin_name}/main.py 存在\n"
f"2. main.py 中有继承 PluginBase 的类"
)
return
# 检查是否已加载(用实际类名再检查一次)
if actual_plugin_name in pm.plugins:
await bot.send_text(to_wxid, f" 插件 {actual_plugin_name} 已经加载")
return
# 加载插件
success = await pm._load_plugin_class(plugin_class)
if success:
await bot.send_text(
to_wxid,
f"✅ 插件加载成功\n"
f"名称: {actual_plugin_name}\n"
f"版本: {plugin_class.version}\n"
f"作者: {plugin_class.author}"
)
logger.success(f"新插件 {actual_plugin_name} 已加载")
else:
await bot.send_text(to_wxid, f"❌ 插件 {actual_plugin_name} 加载失败")
except Exception as e:
import traceback
logger.error(f"加载新插件失败: {e}\n{traceback.format_exc()}")
await bot.send_text(to_wxid, f"❌ 加载失败: {e}")