feat: 基本插件加载功能

This commit is contained in:
Rock Chin
2023-01-13 16:49:56 +08:00
parent 9962a6ebcc
commit 78c1ad16ce
7 changed files with 208 additions and 1 deletions

10
main.py
View File

@@ -117,9 +117,16 @@ def main(first_time_init=False):
timeout=config.process_message_timeout, retry=config.retry_times, timeout=config.process_message_timeout, retry=config.retry_times,
first_time_init=first_time_init) first_time_init=first_time_init)
# 加载插件
import pkg.plugin.host
pkg.plugin.host.load_plugins()
pkg.plugin.host.initialize_plugins()
if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程 if first_time_init: # 不是热重载之后的启动,则不启动新的bot线程
import mirai.exceptions import mirai.exceptions
def run_bot_wrapper(): def run_bot_wrapper():
global known_exception_caught global known_exception_caught
try: try:
@@ -201,6 +208,9 @@ def stop():
import pkg.qqbot.manager import pkg.qqbot.manager
import pkg.openai.session import pkg.openai.session
try: try:
import pkg.plugin.host
pkg.plugin.host.unload_plugins()
qqbot_inst = pkg.utils.context.get_qqbot_manager() qqbot_inst = pkg.utils.context.get_qqbot_manager()
assert isinstance(qqbot_inst, pkg.qqbot.manager.QQBotManager) assert isinstance(qqbot_inst, pkg.qqbot.manager.QQBotManager)

0
pkg/plugin/__init__.py Normal file
View File

93
pkg/plugin/host.py Normal file
View File

@@ -0,0 +1,93 @@
# 插件管理模块
import logging
import importlib
import pkgutil
import sys
import pkg.utils.context as context
__plugins__ = {}
"""{
"example": {
"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
}
}"""
def walk_plugin_path(module, prefix=''):
"""遍历插件路径"""
for item in pkgutil.iter_modules(module.__path__):
if item.ispkg:
walk_plugin_path(__import__(module.__name__ + '.' + item.name, fromlist=['']), prefix + item.name + '.')
else:
logging.info('加载模块: {}'.format(prefix + item.name))
importlib.import_module(module.__name__ + '.' + item.name)
def load_plugins():
""" 加载插件 """
logging.info("加载插件")
PluginHost()
walk_plugin_path(__import__('plugins'))
logging.debug(__plugins__)
def initialize_plugins():
""" 初始化插件 """
logging.info("初始化插件")
for plugin in __plugins__.values():
try:
plugin['instance'] = plugin["class"]()
except:
logging.error("插件{}初始化时发生错误: {}".format(plugin['name'], sys.exc_info()))
def unload_plugins():
""" 卸载插件 """
for plugin in __plugins__.values():
if 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']))
except:
logging.error("插件{}卸载时发生错误: {}".format(plugin['name'], sys.exc_info()))
def emit(event: str, **kwargs):
""" 触发事件 """
for plugin in __plugins__.values():
for hook in plugin['hooks'].get(event, []):
try:
kwargs['plugin_host'] = context.get_plugin_host()
hook(plugin['instance'], **kwargs)
except:
logging.error("插件{}触发事件{}时发生错误: {}".format(plugin['name'], event, sys.exc_info()))
class PluginHost:
"""插件宿主"""
def __init__(self):
context.set_plugin_host(self)
def prevent_default(self):
"""阻止默认行为"""
def get_runtime_context(self) -> context:
"""获取运行时上下文"""
return context

89
pkg/plugin/models.py Normal file
View File

@@ -0,0 +1,89 @@
import logging
import pkg.plugin.host as host
__current_registering_plugin__ = ""
import pkg.utils.context
PersonMessage = "person_message"
GroupMessage = "group_message"
PersonNormalMessage = "person_normal_message"
PersonCommand = "person_command"
GroupNormalMessage = "group_normal_message"
GroupCommand = "group_command"
SessionFirstMessage = "session_first_message"
SessionReset = "session_reset"
SessionExpired = "session_expired"
KeyExceeded = "key_exceeded"
KeySwitched = "key_switched"
class Plugin:
host: host.PluginHost
"""插件宿主,提供插件的一些基础功能"""
@classmethod
def on(cls, event):
"""事件处理器装饰器
:param
event: 事件类型
:return:
None
"""
def wrapper(func):
plugin_hooks = host.__plugins__[__current_registering_plugin__]["hooks"]
if event not in plugin_hooks:
plugin_hooks[event] = []
plugin_hooks[event].append(func)
host.__plugins__[__current_registering_plugin__]["hooks"] = plugin_hooks
return func
return wrapper
def register(name: str, description: str, version: str, author: str):
"""注册插件, 此函数作为装饰器使用
Args:
name (str): 插件名称
description (str): 插件描述
version (str): 插件版本
author (str): 插件作者
Returns:
None
"""
global __current_registering_plugin__
__current_registering_plugin__ = name
host.__plugins__[name] = {
"name": name,
"description": description,
"version": version,
"author": author,
"hooks": {}
}
def wrapper(cls: Plugin):
cls.name = name
cls.description = description
cls.version = version
cls.author = author
cls.host = pkg.utils.context.get_plugin_host()
# 存到插件列表
host.__plugins__[name]["class"] = cls
logging.info("插件注册完成: n='{}', d='{}', v={}, a='{}' ({})".format(name, description, version, author, cls))
return cls
return wrapper

View File

@@ -16,6 +16,9 @@ import pkg.qqbot.filter
import pkg.qqbot.process as processor import pkg.qqbot.process as processor
import pkg.utils.context import pkg.utils.context
import pkg.plugin.host as plugin_host
import pkg.plugin.models as plugin_models
# 并行运行 # 并行运行
def go(func, args=()): def go(func, args=()):
@@ -155,6 +158,8 @@ class QQBotManager:
# 私聊消息处理 # 私聊消息处理
def on_person_message(self, event: MessageEvent): def on_person_message(self, event: MessageEvent):
plugin_host.emit(plugin_models.PersonMessage)
reply = '' reply = ''
if event.sender.id == self.bot.qq: if event.sender.id == self.bot.qq:

View File

@@ -6,6 +6,7 @@ context = {
}, },
'logger_handler': None, 'logger_handler': None,
'config': None, 'config': None,
'plugin_host': None,
} }
@@ -38,4 +39,12 @@ def set_qqbot_manager(inst):
def get_qqbot_manager(): def get_qqbot_manager():
return context['inst']['qqbot.manager.QQBotManager'] return context['inst']['qqbot.manager.QQBotManager']
def set_plugin_host(inst):
context['plugin_host'] = inst
def get_plugin_host():
return context['plugin_host']

View File

@@ -32,6 +32,7 @@ def reload_all(notify=True):
importlib.reload(__import__('config')) importlib.reload(__import__('config'))
importlib.reload(__import__('main')) importlib.reload(__import__('main'))
importlib.reload(__import__('banlist')) importlib.reload(__import__('banlist'))
importlib.reload(__import__('plugins'))
pkg.utils.context.context = context pkg.utils.context.context = context
# 执行启动流程 # 执行启动流程