From 78c1ad16ced859bd7fb9d90ad5d8d5db5f577554 Mon Sep 17 00:00:00 2001 From: Rock Chin <1010553892@qq.com> Date: Fri, 13 Jan 2023 16:49:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9F=BA=E6=9C=AC=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 10 +++++ pkg/plugin/__init__.py | 0 pkg/plugin/host.py | 93 ++++++++++++++++++++++++++++++++++++++++++ pkg/plugin/models.py | 89 ++++++++++++++++++++++++++++++++++++++++ pkg/qqbot/manager.py | 5 +++ pkg/utils/context.py | 11 ++++- pkg/utils/reloader.py | 1 + 7 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 pkg/plugin/__init__.py create mode 100644 pkg/plugin/host.py create mode 100644 pkg/plugin/models.py diff --git a/main.py b/main.py index a1d99bf4..4dbe6d3b 100644 --- a/main.py +++ b/main.py @@ -117,9 +117,16 @@ def main(first_time_init=False): timeout=config.process_message_timeout, retry=config.retry_times, 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线程 import mirai.exceptions + def run_bot_wrapper(): global known_exception_caught try: @@ -201,6 +208,9 @@ def stop(): import pkg.qqbot.manager import pkg.openai.session try: + import pkg.plugin.host + pkg.plugin.host.unload_plugins() + qqbot_inst = pkg.utils.context.get_qqbot_manager() assert isinstance(qqbot_inst, pkg.qqbot.manager.QQBotManager) diff --git a/pkg/plugin/__init__.py b/pkg/plugin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg/plugin/host.py b/pkg/plugin/host.py new file mode 100644 index 00000000..f0542029 --- /dev/null +++ b/pkg/plugin/host.py @@ -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": , + "hooks": { + "person_message": [ + + ] + }, + "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 diff --git a/pkg/plugin/models.py b/pkg/plugin/models.py new file mode 100644 index 00000000..e7202a14 --- /dev/null +++ b/pkg/plugin/models.py @@ -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 diff --git a/pkg/qqbot/manager.py b/pkg/qqbot/manager.py index 95dd382b..e13f01c3 100644 --- a/pkg/qqbot/manager.py +++ b/pkg/qqbot/manager.py @@ -16,6 +16,9 @@ import pkg.qqbot.filter import pkg.qqbot.process as processor import pkg.utils.context +import pkg.plugin.host as plugin_host +import pkg.plugin.models as plugin_models + # 并行运行 def go(func, args=()): @@ -155,6 +158,8 @@ class QQBotManager: # 私聊消息处理 def on_person_message(self, event: MessageEvent): + plugin_host.emit(plugin_models.PersonMessage) + reply = '' if event.sender.id == self.bot.qq: diff --git a/pkg/utils/context.py b/pkg/utils/context.py index f599e246..30f81770 100644 --- a/pkg/utils/context.py +++ b/pkg/utils/context.py @@ -6,6 +6,7 @@ context = { }, 'logger_handler': None, 'config': None, + 'plugin_host': None, } @@ -38,4 +39,12 @@ def set_qqbot_manager(inst): def get_qqbot_manager(): - return context['inst']['qqbot.manager.QQBotManager'] \ No newline at end of file + return context['inst']['qqbot.manager.QQBotManager'] + + +def set_plugin_host(inst): + context['plugin_host'] = inst + + +def get_plugin_host(): + return context['plugin_host'] diff --git a/pkg/utils/reloader.py b/pkg/utils/reloader.py index 639c03da..fc9f80d8 100644 --- a/pkg/utils/reloader.py +++ b/pkg/utils/reloader.py @@ -32,6 +32,7 @@ def reload_all(notify=True): importlib.reload(__import__('config')) importlib.reload(__import__('main')) importlib.reload(__import__('banlist')) + importlib.reload(__import__('plugins')) pkg.utils.context.context = context # 执行启动流程