2023-01-13 16:49:56 +08:00
|
|
|
|
# 插件管理模块
|
2023-01-15 00:04:47 +08:00
|
|
|
|
import asyncio
|
2023-01-13 16:49:56 +08:00
|
|
|
|
import logging
|
|
|
|
|
import importlib
|
2023-01-17 11:54:33 +08:00
|
|
|
|
import os
|
2023-01-13 16:49:56 +08:00
|
|
|
|
import pkgutil
|
|
|
|
|
import sys
|
2023-03-20 20:50:25 +08:00
|
|
|
|
import shutil
|
2023-01-15 00:04:47 +08:00
|
|
|
|
import traceback
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
|
|
|
|
import pkg.utils.context as context
|
2023-01-16 23:40:59 +08:00
|
|
|
|
import pkg.plugin.switch as switch
|
2023-02-14 17:57:38 +08:00
|
|
|
|
import pkg.plugin.settings as settings
|
2023-04-21 17:51:58 +08:00
|
|
|
|
import pkg.qqbot.adapter as msadapter
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
2023-01-15 00:04:47 +08:00
|
|
|
|
from mirai import Mirai
|
|
|
|
|
|
2023-01-13 16:49:56 +08:00
|
|
|
|
__plugins__ = {}
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""插件列表
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
{
|
2023-01-13 16:49:56 +08:00
|
|
|
|
"example": {
|
2023-01-16 23:40:59 +08:00
|
|
|
|
"path": "plugins/example/main.py",
|
|
|
|
|
"enabled: True,
|
2023-01-13 16:49:56 +08:00
|
|
|
|
"name": "example",
|
|
|
|
|
"description": "example",
|
|
|
|
|
"version": "0.0.1",
|
|
|
|
|
"author": "RockChinQ",
|
|
|
|
|
"class": <class 'plugins.example.ExamplePlugin'>,
|
|
|
|
|
"hooks": {
|
|
|
|
|
"person_message": [
|
|
|
|
|
<function ExamplePlugin.person_message at 0x0000020E1D1B8D38>
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
"instance": None
|
|
|
|
|
}
|
2023-04-03 00:19:28 +08:00
|
|
|
|
}
|
|
|
|
|
"""
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
2023-02-14 17:57:38 +08:00
|
|
|
|
__plugins_order__ = []
|
|
|
|
|
"""插件顺序"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_plugin_order():
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""根据__plugin__生成插件初始顺序,无视是否启用"""
|
2023-02-14 17:57:38 +08:00
|
|
|
|
global __plugins_order__
|
|
|
|
|
__plugins_order__ = []
|
|
|
|
|
for plugin_name in __plugins__:
|
|
|
|
|
__plugins_order__.append(plugin_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def iter_plugins():
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""按照顺序迭代插件"""
|
2023-02-14 17:57:38 +08:00
|
|
|
|
for plugin_name in __plugins_order__:
|
|
|
|
|
yield __plugins__[plugin_name]
|
|
|
|
|
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
2023-02-15 13:32:15 +08:00
|
|
|
|
def iter_plugins_name():
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""迭代插件名"""
|
2023-02-15 13:32:15 +08:00
|
|
|
|
for plugin_name in __plugins_order__:
|
|
|
|
|
yield plugin_name
|
|
|
|
|
|
|
|
|
|
|
2023-01-16 23:47:57 +08:00
|
|
|
|
__current_module_path__ = ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def walk_plugin_path(module, prefix='', path_prefix=''):
|
|
|
|
|
global __current_module_path__
|
2023-01-13 16:49:56 +08:00
|
|
|
|
"""遍历插件路径"""
|
|
|
|
|
for item in pkgutil.iter_modules(module.__path__):
|
|
|
|
|
if item.ispkg:
|
2023-01-16 23:47:57 +08:00
|
|
|
|
logging.debug("扫描插件包: plugins/{}".format(path_prefix + item.name))
|
|
|
|
|
walk_plugin_path(__import__(module.__name__ + '.' + item.name, fromlist=['']),
|
|
|
|
|
prefix + item.name + '.', path_prefix + item.name + '/')
|
2023-01-13 16:49:56 +08:00
|
|
|
|
else:
|
2023-02-12 13:15:33 +08:00
|
|
|
|
try:
|
|
|
|
|
logging.debug("扫描插件模块: plugins/{}".format(path_prefix + item.name + '.py'))
|
|
|
|
|
__current_module_path__ = "plugins/"+path_prefix + item.name + '.py'
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
2023-02-12 13:15:33 +08:00
|
|
|
|
importlib.import_module(module.__name__ + '.' + item.name)
|
|
|
|
|
logging.info('加载模块: plugins/{} 成功'.format(path_prefix + item.name + '.py'))
|
|
|
|
|
except:
|
|
|
|
|
logging.error('加载模块: plugins/{} 失败: {}'.format(path_prefix + item.name + '.py', sys.exc_info()))
|
|
|
|
|
traceback.print_exc()
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load_plugins():
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""加载插件"""
|
2023-01-13 16:49:56 +08:00
|
|
|
|
logging.info("加载插件")
|
|
|
|
|
PluginHost()
|
|
|
|
|
walk_plugin_path(__import__('plugins'))
|
|
|
|
|
|
|
|
|
|
logging.debug(__plugins__)
|
|
|
|
|
|
2023-01-16 23:47:57 +08:00
|
|
|
|
# 加载开关数据
|
|
|
|
|
switch.load_switch()
|
|
|
|
|
|
2023-02-14 17:57:38 +08:00
|
|
|
|
# 生成初始顺序
|
|
|
|
|
generate_plugin_order()
|
|
|
|
|
# 加载插件顺序
|
|
|
|
|
settings.load_settings()
|
|
|
|
|
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
|
|
|
|
def initialize_plugins():
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""初始化插件"""
|
2023-01-13 16:49:56 +08:00
|
|
|
|
logging.info("初始化插件")
|
2023-02-25 20:29:21 +08:00
|
|
|
|
import pkg.plugin.models as models
|
2023-02-14 17:57:38 +08:00
|
|
|
|
for plugin in iter_plugins():
|
2023-04-05 16:50:35 +08:00
|
|
|
|
# if not plugin['enabled']:
|
|
|
|
|
# continue
|
2023-01-13 16:49:56 +08:00
|
|
|
|
try:
|
2023-02-25 20:29:21 +08:00
|
|
|
|
models.__current_registering_plugin__ = plugin['name']
|
2023-01-17 12:07:08 +08:00
|
|
|
|
plugin['instance'] = plugin["class"](plugin_host=context.get_plugin_host())
|
2023-01-17 15:43:28 +08:00
|
|
|
|
logging.info("插件 {} 已初始化".format(plugin['name']))
|
2023-01-13 16:49:56 +08:00
|
|
|
|
except:
|
|
|
|
|
logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unload_plugins():
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""卸载插件"""
|
2023-03-05 15:39:13 +08:00
|
|
|
|
# 不再显式卸载插件,因为当程序结束时,插件的析构函数会被系统执行
|
2023-01-17 15:43:28 +08:00
|
|
|
|
# for plugin in __plugins__.values():
|
|
|
|
|
# if plugin['enabled'] and plugin['instance'] is not None:
|
|
|
|
|
# if not hasattr(plugin['instance'], '__del__'):
|
|
|
|
|
# logging.warning("插件{}没有定义析构函数".format(plugin['name']))
|
|
|
|
|
# else:
|
|
|
|
|
# try:
|
|
|
|
|
# plugin['instance'].__del__()
|
|
|
|
|
# logging.info("卸载插件: {}".format(plugin['name']))
|
|
|
|
|
# plugin['instance'] = None
|
|
|
|
|
# except:
|
|
|
|
|
# logging.error("插件{}卸载时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
|
|
|
|
|
2023-01-17 00:11:07 +08:00
|
|
|
|
def install_plugin(repo_url: str):
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""安装插件,从git储存库获取并解决依赖"""
|
2023-01-17 00:11:07 +08:00
|
|
|
|
try:
|
|
|
|
|
import pkg.utils.pkgmgr
|
|
|
|
|
pkg.utils.pkgmgr.ensure_dulwich()
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
import dulwich
|
|
|
|
|
except ModuleNotFoundError:
|
|
|
|
|
raise Exception("dulwich模块未安装,请查看 https://github.com/RockChinQ/QChatGPT/issues/77")
|
|
|
|
|
|
|
|
|
|
from dulwich import porcelain
|
|
|
|
|
|
|
|
|
|
logging.info("克隆插件储存库: {}".format(repo_url))
|
2023-01-17 11:54:33 +08:00
|
|
|
|
repo = porcelain.clone(repo_url, "plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/", checkout=True)
|
|
|
|
|
|
|
|
|
|
# 检查此目录是否包含requirements.txt
|
|
|
|
|
if os.path.exists("plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/requirements.txt"):
|
|
|
|
|
logging.info("检测到requirements.txt,正在安装依赖")
|
|
|
|
|
import pkg.utils.pkgmgr
|
|
|
|
|
pkg.utils.pkgmgr.install_requirements("plugins/"+repo_url.split(".git")[0].split("/")[-1]+"/requirements.txt")
|
|
|
|
|
|
2023-04-14 18:42:09 +08:00
|
|
|
|
import pkg.utils.log as log
|
|
|
|
|
log.reset_logging()
|
2023-01-17 00:11:07 +08:00
|
|
|
|
|
|
|
|
|
|
2023-03-20 20:50:25 +08:00
|
|
|
|
def uninstall_plugin(plugin_name: str) -> str:
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""卸载插件"""
|
2023-03-20 20:50:25 +08:00
|
|
|
|
if plugin_name not in __plugins__:
|
|
|
|
|
raise Exception("插件不存在")
|
|
|
|
|
|
|
|
|
|
# 获取文件夹路径
|
|
|
|
|
plugin_path = __plugins__[plugin_name]['path'].replace("\\", "/")
|
|
|
|
|
|
|
|
|
|
# 剪切路径为plugins/插件名
|
|
|
|
|
plugin_path = plugin_path.split("plugins/")[1].split("/")[0]
|
|
|
|
|
|
|
|
|
|
# 删除文件夹
|
|
|
|
|
shutil.rmtree("plugins/"+plugin_path)
|
|
|
|
|
return "plugins/"+plugin_path
|
|
|
|
|
|
|
|
|
|
|
2023-01-14 19:59:51 +08:00
|
|
|
|
class EventContext:
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""事件上下文"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
eid = 0
|
2023-01-16 18:00:30 +08:00
|
|
|
|
"""事件编号"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
|
|
|
|
name = ""
|
|
|
|
|
|
|
|
|
|
__prevent_default__ = False
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""是否阻止默认行为"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
|
|
|
|
__prevent_postorder__ = False
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""是否阻止后续插件的执行"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
2023-01-14 22:53:28 +08:00
|
|
|
|
__return_value__ = {}
|
|
|
|
|
""" 返回值
|
|
|
|
|
示例:
|
|
|
|
|
{
|
|
|
|
|
"example": [
|
|
|
|
|
'value1',
|
|
|
|
|
'value2',
|
|
|
|
|
3,
|
|
|
|
|
4,
|
|
|
|
|
{
|
|
|
|
|
'key1': 'value1',
|
|
|
|
|
},
|
|
|
|
|
['value1', 'value2']
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def add_return(self, key: str, ret):
|
|
|
|
|
"""添加返回值"""
|
|
|
|
|
if key not in self.__return_value__:
|
|
|
|
|
self.__return_value__[key] = []
|
|
|
|
|
self.__return_value__[key].append(ret)
|
|
|
|
|
|
|
|
|
|
def get_return(self, key: str):
|
|
|
|
|
"""获取key的所有返回值"""
|
|
|
|
|
if key in self.__return_value__:
|
|
|
|
|
return self.__return_value__[key]
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def get_return_value(self, key: str):
|
|
|
|
|
"""获取key的首个返回值"""
|
|
|
|
|
if key in self.__return_value__:
|
|
|
|
|
return self.__return_value__[key][0]
|
|
|
|
|
return None
|
|
|
|
|
|
2023-01-14 19:59:51 +08:00
|
|
|
|
def prevent_default(self):
|
|
|
|
|
"""阻止默认行为"""
|
|
|
|
|
self.__prevent_default__ = True
|
|
|
|
|
|
|
|
|
|
def prevent_postorder(self):
|
|
|
|
|
"""阻止后续插件执行"""
|
|
|
|
|
self.__prevent_postorder__ = True
|
|
|
|
|
|
|
|
|
|
def is_prevented_default(self):
|
2023-01-14 22:36:48 +08:00
|
|
|
|
"""是否阻止默认行为"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
return self.__prevent_default__
|
|
|
|
|
|
|
|
|
|
def is_prevented_postorder(self):
|
2023-01-14 22:36:48 +08:00
|
|
|
|
"""是否阻止后序插件执行"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
return self.__prevent_postorder__
|
|
|
|
|
|
|
|
|
|
def __init__(self, name: str):
|
|
|
|
|
self.name = name
|
|
|
|
|
self.eid = EventContext.eid
|
2023-01-15 22:23:18 +08:00
|
|
|
|
self.__prevent_default__ = False
|
|
|
|
|
self.__prevent_postorder__ = False
|
|
|
|
|
self.__return_value__ = {}
|
2023-01-14 19:59:51 +08:00
|
|
|
|
EventContext.eid += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def emit(event_name: str, **kwargs) -> EventContext:
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""触发事件"""
|
2023-01-14 19:59:51 +08:00
|
|
|
|
import pkg.utils.context as context
|
2023-01-14 22:36:48 +08:00
|
|
|
|
if context.get_plugin_host() is None:
|
|
|
|
|
return None
|
2023-01-14 19:59:51 +08:00
|
|
|
|
return context.get_plugin_host().emit(event_name, **kwargs)
|
2023-01-13 16:49:56 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PluginHost:
|
|
|
|
|
"""插件宿主"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
context.set_plugin_host(self)
|
|
|
|
|
|
|
|
|
|
def get_runtime_context(self) -> context:
|
2023-01-16 19:15:54 +08:00
|
|
|
|
"""获取运行时上下文(pkg.utils.context模块的对象)
|
|
|
|
|
|
|
|
|
|
此上下文用于和主程序其他模块交互(数据库、QQ机器人、OpenAI接口等)
|
|
|
|
|
详见pkg.utils.context模块
|
|
|
|
|
其中的context变量保存了其他重要模块的类对象,可以使用这些对象进行交互
|
|
|
|
|
"""
|
2023-01-13 16:49:56 +08:00
|
|
|
|
return context
|
2023-01-13 23:13:54 +08:00
|
|
|
|
|
2023-01-15 00:04:47 +08:00
|
|
|
|
def get_bot(self) -> Mirai:
|
2023-01-13 23:13:54 +08:00
|
|
|
|
"""获取机器人对象"""
|
|
|
|
|
return context.get_qqbot_manager().bot
|
|
|
|
|
|
2023-04-21 17:51:58 +08:00
|
|
|
|
def get_bot_adapter(self) -> msadapter.MessageSourceAdapter:
|
|
|
|
|
"""获取消息源适配器"""
|
|
|
|
|
return context.get_qqbot_manager().adapter
|
|
|
|
|
|
2023-01-15 00:04:47 +08:00
|
|
|
|
def send_person_message(self, person, message):
|
|
|
|
|
"""发送私聊消息"""
|
2023-04-25 10:26:03 +08:00
|
|
|
|
asyncio.run(self.get_bot_adapter().send_message("person", person, message))
|
2023-01-15 00:04:47 +08:00
|
|
|
|
|
|
|
|
|
def send_group_message(self, group, message):
|
|
|
|
|
"""发送群消息"""
|
2023-04-25 10:26:03 +08:00
|
|
|
|
asyncio.run(self.get_bot_adapter().send_message("group", group, message))
|
2023-01-15 00:04:47 +08:00
|
|
|
|
|
2023-01-13 23:13:54 +08:00
|
|
|
|
def notify_admin(self, message):
|
|
|
|
|
"""通知管理员"""
|
|
|
|
|
context.get_qqbot_manager().notify_admin(message)
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
|
|
|
|
def emit(self, event_name: str, **kwargs) -> EventContext:
|
2023-04-03 00:19:28 +08:00
|
|
|
|
"""触发事件"""
|
2023-02-25 20:29:21 +08:00
|
|
|
|
import json
|
|
|
|
|
|
2023-01-14 19:59:51 +08:00
|
|
|
|
event_context = EventContext(event_name)
|
2023-01-15 22:23:18 +08:00
|
|
|
|
logging.debug("触发事件: {} ({})".format(event_name, event_context.eid))
|
2023-02-14 17:57:38 +08:00
|
|
|
|
for plugin in iter_plugins():
|
2023-02-25 20:29:21 +08:00
|
|
|
|
|
2023-01-16 23:40:59 +08:00
|
|
|
|
if not plugin['enabled']:
|
|
|
|
|
continue
|
|
|
|
|
|
2023-01-17 15:43:28 +08:00
|
|
|
|
# if plugin['instance'] is None:
|
|
|
|
|
# # 从关闭状态切到开启状态之后,重新加载插件
|
|
|
|
|
# try:
|
|
|
|
|
# plugin['instance'] = plugin["class"](plugin_host=self)
|
|
|
|
|
# logging.info("插件 {} 已初始化".format(plugin['name']))
|
|
|
|
|
# except:
|
|
|
|
|
# logging.error("插件 {} 初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
|
|
|
|
|
# continue
|
2023-01-16 23:40:59 +08:00
|
|
|
|
|
2023-02-25 20:29:21 +08:00
|
|
|
|
if 'hooks' not in plugin or event_name not in plugin['hooks']:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
hooks = []
|
|
|
|
|
if event_name in plugin["hooks"]:
|
|
|
|
|
hooks = plugin["hooks"][event_name]
|
|
|
|
|
for hook in hooks:
|
2023-01-14 19:59:51 +08:00
|
|
|
|
try:
|
2023-01-16 19:15:54 +08:00
|
|
|
|
already_prevented_default = event_context.is_prevented_default()
|
|
|
|
|
|
2023-01-14 19:59:51 +08:00
|
|
|
|
kwargs['host'] = context.get_plugin_host()
|
|
|
|
|
kwargs['event'] = event_context
|
2023-01-15 22:41:47 +08:00
|
|
|
|
|
2023-01-14 19:59:51 +08:00
|
|
|
|
hook(plugin['instance'], **kwargs)
|
|
|
|
|
|
2023-01-16 19:15:54 +08:00
|
|
|
|
if event_context.is_prevented_default() and not already_prevented_default:
|
2023-01-14 20:34:33 +08:00
|
|
|
|
logging.debug("插件 {} 已要求阻止事件 {} 的默认行为".format(plugin['name'], event_name))
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
2023-01-15 00:04:47 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
logging.error("插件{}触发事件{}时发生错误".format(plugin['name'], event_name))
|
|
|
|
|
logging.error(traceback.format_exc())
|
2023-01-14 19:59:51 +08:00
|
|
|
|
|
2023-02-25 20:29:21 +08:00
|
|
|
|
# print("done:{}".format(plugin['name']))
|
|
|
|
|
if event_context.is_prevented_postorder():
|
|
|
|
|
logging.debug("插件 {} 阻止了后序插件的执行".format(plugin['name']))
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
logging.debug("事件 {} ({}) 处理完毕,返回值: {}".format(event_name, event_context.eid,
|
|
|
|
|
event_context.__return_value__))
|
2023-01-17 21:21:35 +08:00
|
|
|
|
|
2023-01-14 19:59:51 +08:00
|
|
|
|
return event_context
|