From f104d40d0a6a6dbcf4f7f7a8eab0e9b19438c266 Mon Sep 17 00:00:00 2001 From: Soulter <37870767+Soulter@users.noreply.github.com> Date: Sun, 14 May 2023 17:40:13 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E8=A7=84=E5=88=99=E3=80=81=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=8E=A5=E5=8F=A3=E8=A7=84=E8=8C=83=20fix:?= =?UTF-8?q?=20=E4=BF=AE=E5=A4=8D=E5=8F=91=E8=A8=80=E9=A2=91=E7=8E=87?= =?UTF-8?q?=E9=99=90=E5=88=B6=E6=8A=A5=E9=94=99=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/plugins/helloworld/README.md | 5 + addons/plugins/helloworld/helloworld.py | 21 +++- cores/qqbot/core.py | 27 ++++- model/command/command.py | 146 ++++++++++++++--------- model/command/command_openai_official.py | 5 +- model/command/command_rev_chatgpt.py | 5 +- model/command/command_rev_edgegpt.py | 5 +- 7 files changed, 147 insertions(+), 67 deletions(-) create mode 100644 addons/plugins/helloworld/README.md diff --git a/addons/plugins/helloworld/README.md b/addons/plugins/helloworld/README.md new file mode 100644 index 000000000..e7f48777b --- /dev/null +++ b/addons/plugins/helloworld/README.md @@ -0,0 +1,5 @@ +# helloworld + +QQChannelChatGPT项目的测试插件 + +A test plugin for QQChannelChatGPT plugin feature diff --git a/addons/plugins/helloworld/helloworld.py b/addons/plugins/helloworld/helloworld.py index 7a09e104f..d543c0829 100644 --- a/addons/plugins/helloworld/helloworld.py +++ b/addons/plugins/helloworld/helloworld.py @@ -27,8 +27,9 @@ class HelloWorldPlugin: """ QQ平台指令处理逻辑 """ + img_url = "https://gchat.qpic.cn/gchatpic_new/905617992/720871955-2246763964-C6EE1A52CC668EC982453065C4FA8747/0?term=2&is_origin=0" if message == "helloworld": - return True, tuple([True, [Plain("Hello World!!")], "helloworld"]) + return True, tuple([True, [Plain("Hello World!!"), Image.fromURL(url=img_url)], "helloworld"]) else: return False, None elif platform == "qqchan": @@ -39,6 +40,24 @@ class HelloWorldPlugin: return True, tuple([True, "Hello World!!", "helloworld"]) else: return False, None + """ + 帮助函数,当用户输入 plugin v 插件名称 时,会调用此函数,返回帮助信息 + 返回参数要求(必填):dict{ + "name": str, # 插件名称 + "desc": str, # 插件简短描述 + "help": str, # 插件帮助信息 + "version": str, # 插件版本 + "author": str, # 插件作者 + } + """ + def info(self): + return { + "name": "helloworld", + "desc": "测试插件", + "help": "测试插件, 回复helloworld即可触发", + "version": "v1.0.1 beta", + "author": "Soulter" + } # 热知识:检测消息开头指令,使用以下方法 diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py index 712e2c51a..20f3d00f1 100644 --- a/cores/qqbot/core.py +++ b/cores/qqbot/core.py @@ -21,6 +21,7 @@ from nakuru import ( FriendMessage ) from nakuru.entities.components import Plain,At +from model.command.command import Command # QQBotClient实例 client = '' @@ -98,6 +99,10 @@ nick_qq = "ai" bing_cache_loop = None +# 插件 +cached_plugins = {} + + def gocq_runner(): global gocq_app ok = False @@ -119,6 +124,7 @@ def new_sub_thread(func, args=()): thread = threading.Thread(target=func, args=args, daemon=True) thread.start() + # 写入统计信息 def toggle_count(at: bool, message): global stat_file @@ -181,7 +187,7 @@ def upload(): def initBot(cfg, prov): global chatgpt, provider, rev_chatgpt, baidu_judge, rev_edgegpt, chosen_provider global reply_prefix, gpt_config, config, uniqueSession, frequency_count, frequency_time,announcement, direct_message_mode, version - global command_openai_official, command_rev_chatgpt, command_rev_edgegpt,reply_prefix, keywords + global command_openai_official, command_rev_chatgpt, command_rev_edgegpt,reply_prefix, keywords, cached_plugins provider = prov config = cfg if 'reply_prefix' in cfg: @@ -304,6 +310,15 @@ def initBot(cfg, prov): thread_inst = None + print("--------------------加载插件--------------------") + # 加载插件 + _command = Command(None) + ok, err = _command.plugin_reload(cached_plugins) + if ok: + print("加载插件完成") + else: + print(err) + print("--------------------加载平台--------------------") # GOCQ if 'gocqbot' in cfg and cfg['gocqbot']['enable']: @@ -423,7 +438,7 @@ def oper_msg(message, role = "member" # 角色 hit = False # 是否命中指令 command_result = () # 调用指令返回的结果 - global admin_qq + global admin_qq, cached_plugins if platform == PLATFORM_QQCHAN: print("[QQCHAN-BOT] 接收到消息:"+ str(message.content)) @@ -445,7 +460,7 @@ def oper_msg(message, # 检查发言频率 if not check_frequency(user_id): - qqchannel_bot.send_qq_msg(message, f'{user_name}的发言超过频率限制(╯▔皿▔)╯。\n{frequency_time}秒内只能提问{frequency_count}次。') + send_message(platform, message, f'你的发言超过频率限制(╯▔皿▔)╯。\n管理员设置{frequency_time}秒内只能提问{frequency_count}次。', msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot) return if platform == PLATFORM_QQCHAN: @@ -542,7 +557,7 @@ def oper_msg(message, chatgpt_res = "" if chosen_provider == OPENAI_OFFICIAL: - hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name, role, platform=platform, message_obj=message) + hit, command_result = command_openai_official.check_command(qq_msg, session_id, user_name, role, platform=platform, message_obj=message, cached_plugins=cached_plugins) # hit: 是否触发了指令 if not hit: # 请求ChatGPT获得结果 @@ -555,7 +570,7 @@ def oper_msg(message, send_message(platform, message, f"OpenAI API错误, 原因: {str(e)}", msg_ref=msg_ref, gocq_loop=gocq_loop, qqchannel_bot=qqchannel_bot, gocq_bot=gocq_bot) elif chosen_provider == REV_CHATGPT: - hit, command_result = command_rev_chatgpt.check_command(qq_msg, role, platform=platform, message=message) + hit, command_result = command_rev_chatgpt.check_command(qq_msg, role, platform=platform, message=message, cached_plugins=cached_plugins) if not hit: try: chatgpt_res = str(rev_chatgpt.text_chat(qq_msg)) @@ -571,7 +586,7 @@ def oper_msg(message, bing_cache_loop = gocq_loop elif platform == PLATFORM_QQCHAN: bing_cache_loop = qqchan_loop - hit, command_result = command_rev_edgegpt.check_command(qq_msg, bing_cache_loop, role, platform=platform, message_obj=message) + hit, command_result = command_rev_edgegpt.check_command(qq_msg, bing_cache_loop, role, platform=platform, message_obj=message, cached_plugins=cached_plugins) if not hit: try: while rev_edgegpt.is_busy(): diff --git a/model/command/command.py b/model/command/command.py index d95f9d999..ed218e2f7 100644 --- a/model/command/command.py +++ b/model/command/command.py @@ -7,9 +7,8 @@ import requests from model.provider.provider import Provider import json import util.plugin_util as putil -import importlib import shutil - +import importlib PLATFORM_QQCHAN = 'qqchan' PLATFORM_GOCQ = 'gocq' @@ -33,49 +32,69 @@ class Command: except BaseException as e: raise e - def check_command(self, message, role, platform, message_obj): + def check_command(self, message, role, platform, message_obj, cached_plugins: dict): # 插件 - try: - plugins = self.get_plugin_modules() - if plugins != None: - # print(f"[DEBUG] 当前加载的插件:{plugins}") - for p in plugins: - # print(f"[Debug] 当前缓存的插件:{self.cached_plugins}") - try: - if p in self.cached_plugins: - module = self.cached_plugins[p]["module"] - obj = self.cached_plugins[p]["clsobj"] - else: - module = __import__("addons.plugins." + p + "." + p, fromlist=[p]) - cls = putil.get_classes(p, module) - obj = getattr(module, cls[0])() - self.cached_plugins[p] = { - "module": module, - "clsobj": obj - } - hit, res = obj.run(message, role, platform, message_obj) - if hit: - return True, res - except BaseException as e: - print(f"[Debug] 加载{p}插件出现问题,原因{str(e)}") - except BaseException as e: - print(f"[Debug] 插件加载出现问题,原因: {str(e)}\n已安装插件: {plugins}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。") + + for k, v in cached_plugins.items(): + try: + hit, res = v["clsobj"].run(message, role, platform, message_obj) + if hit: + return True, res + except BaseException as e: + print(f"[Debug] {k}插件加载出现问题,原因: {str(e)}\n已安装插件: {cached_plugins.keys}\n如果你没有相关装插件的想法, 请直接忽略此报错, 不影响其他功能的运行。") if self.command_start_with(message, "nick"): return True, self.set_nick(message, platform) if self.command_start_with(message, "plugin"): - return True, self.plugin_oper(message, role) + return True, self.plugin_oper(message, role, cached_plugins) return False, None + def plugin_reload(self, cached_plugins: dict, target: str = None, all: bool = False): + plugins = self.get_plugin_modules() + fail_rec = "" + if plugins != None: + for p in plugins: + try: + if p not in cached_plugins or p == target or all: + module = __import__("addons.plugins." + p + "." + p, fromlist=[p]) + if p in cached_plugins: + module = importlib.reload(module) + cls = putil.get_classes(p, module) + obj = getattr(module, cls[0])() + try: + info = obj.info() + if 'name' not in info or 'desc' not in info or 'version' not in info or 'author' not in info: + fail_rec += f"载入插件{p}失败,原因: 插件信息不完整\n" + continue + if isinstance(info, dict) == False: + fail_rec += f"载入插件{p}失败,原因: 插件信息格式不正确\n" + continue + except BaseException as e: + fail_rec += f"调用插件{p} info失败, 原因: {str(e)}\n" + continue + cached_plugins[p] = { + "module": module, + "clsobj": obj, + "info": info + } + except BaseException as e: + fail_rec += f"加载{p}插件出现问题,原因{str(e)}\n" + if fail_rec == "": + return True, None + else: + return False, fail_rec + else: + return False, "未找到任何插件模块" + ''' 插件指令 ''' - def plugin_oper(self, message: str, role: str): + def plugin_oper(self, message: str, role: str, cached_plugins: dict): l = message.split(" ") if len(l) < 2: - return True, "【插件指令】示例:\n安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin i 插件名 \n重载插件: \nplugin reload\n查看插件列表\nplugin l", "plugin" + return True, "\n=====插件指令面板=====\n安装插件: \nplugin i 插件Github地址\n卸载插件: \nplugin i 插件名 \n重载插件: \nplugin reload\n查看插件列表:\nplugin l\n更新插件: plugin u 插件名\n===============", "plugin" else: ppath = "" if os.path.exists("addons/plugins"): @@ -92,6 +111,8 @@ class Command: d = l[2].split("/")[-1] # 创建文件夹 plugin_path = os.path.join(ppath, d) + if os.path.exists(plugin_path): + shutil.rmtree(plugin_path) os.mkdir(plugin_path) Repo.clone_from(l[2],to_path=plugin_path,branch='master') @@ -102,8 +123,15 @@ class Command: mm = os.system(f"pip3 install {line.strip()}") if mm != 0: return False, "插件依赖安装失败,需要您手动pip安装对应插件的依赖。", "plugin" - - return True, "插件拉取成功~", "plugin" + + # 加载没缓存的插件 + ok, err = self.plugin_reload(cached_plugins) + if ok: + return True, "插件拉取并载入成功~", "plugin" + else: + # if os.path.exists(plugin_path): + # shutil.rmtree(plugin_path) + return False, f"插件拉取载入失败。\n跟踪: \n{err}", "plugin" except BaseException as e: return False, f"拉取插件失败,原因: {str(e)}", "plugin" elif l[1] == "d": @@ -112,41 +140,51 @@ class Command: try: # 删除文件夹 shutil.rmtree(os.path.join(ppath, l[2])) - if l[2] in self.cached_plugins: - del self.cached_plugins[l[2]] + if l[2] in cached_plugins: + del cached_plugins[l[2]] return True, "插件卸载成功~", "plugin" except BaseException as e: return False, f"卸载插件失败,原因: {str(e)}", "plugin" + elif l[1] == "u": + plugin_path = os.path.join(ppath, l[2]) + try: + repo = Repo(path = plugin_path) + repo.remotes.origin.pull() + ok, err = self.plugin_reload(cached_plugins, target=l[2]) + if ok: + return True, "\n更新插件成功!!", "plugin" + else: + return False, "更新插件成功,但是重载插件失败。\n问题跟踪: \n"+err, "plugin" + except BaseException as e: + return False, "更新插件失败, 请使用plugin i指令覆盖安装", "plugin" + elif l[1] == "l": try: - return True, "已安装的插件: \n" + "\n".join(os.listdir(ppath)) + "\n使用plugin v 插件名 查看插件帮助(如果有的话)", "plugin" + plugin_list_info = "\n".join([f"{k}: \n名称: {v['info']['name']}\n简介: {v['info']['desc']}\n版本: {v['info']['version']}\n作者: {v['info']['author']}\n" for k, v in cached_plugins.items()]) + return True, "\n=====已激活插件列表=====\n" + plugin_list_info + "\n使用plugin v 插件名 查看插件帮助\n=================", "plugin" except BaseException as e: return False, f"获取插件列表失败,原因: {str(e)}", "plugin" elif l[1] == "v": try: - if l[2] in os.listdir(ppath): - # 获取Readme - if os.path.exists(os.path.join(ppath, l[2], "README.md")): - with open(os.path.join(ppath, l[2], "README.md"), "r", encoding="utf-8") as f: - readme = f.read() - else: - readme = "暂无帮助(未找到此插件的README.md)" - return True, readme, "plugin" + if l[2] in cached_plugins: + info = cached_plugins[l[2]]["info"] + res = f"\n=====插件信息=====\n名称: {info['name']}\n{info['desc']}\n版本: {info['version']}作者: {info['author']}\n\n帮助:\n{info['help']}" + return True, res, "plugin" + else: + return False, "未找到该插件", "plugin" except BaseException as e: - return False, f"获取插件版本失败,原因: {str(e)}", "plugin" + return False, f"获取插件信息失败,原因: {str(e)}", "plugin" elif l[1] == "reload": if role != "admin": return False, f"你的身份组{role}没有权限重载插件", "plugin" try: - for pm in self.cached_plugins: - module = self.cached_plugins[pm]["module"] - cls = putil.get_classes(pm, module) - obj = getattr(module, cls[0])() - self.cached_plugins[pm] = { - "module": module, - "clsobj": obj - } - return True, "插件重载成功!", "plugin" + ok, err = self.plugin_reload(cached_plugins, all = True) + if ok: + return True, "\n重载插件成功~", "plugin" + else: + # if os.path.exists(plugin_path): + # shutil.rmtree(plugin_path) + return False, f"插件重载失败。\n跟踪: \n{err}", "plugin" except BaseException as e: return False, f"插件重载失败,原因: {str(e)}", "plugin" diff --git a/model/command/command_openai_official.py b/model/command/command_openai_official.py index b63f8f554..28c34d852 100644 --- a/model/command/command_openai_official.py +++ b/model/command/command_openai_official.py @@ -13,8 +13,9 @@ class CommandOpenAIOfficial(Command): user_name: str, role: str, platform: str, - message_obj): - hit, res = super().check_command(message, role, platform, message_obj=message_obj) + message_obj, + cached_plugins: dict): + hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins) if hit: return True, res if self.command_start_with(message, "reset", "重置"): diff --git a/model/command/command_rev_chatgpt.py b/model/command/command_rev_chatgpt.py index 35f1b4af1..7cdebd6ec 100644 --- a/model/command/command_rev_chatgpt.py +++ b/model/command/command_rev_chatgpt.py @@ -10,8 +10,9 @@ class CommandRevChatGPT(Command): message: str, role: str, platform: str, - message_obj): - hit, res = super().check_command(message, role, platform, message_obj=message_obj) + message_obj, + cached_plugins: dict): + hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins) if hit: return True, res if self.command_start_with(message, "help", "帮助"): diff --git a/model/command/command_rev_edgegpt.py b/model/command/command_rev_edgegpt.py index 29e63d6ba..838dfae92 100644 --- a/model/command/command_rev_edgegpt.py +++ b/model/command/command_rev_edgegpt.py @@ -12,8 +12,9 @@ class CommandRevEdgeGPT(Command): loop, role: str, platform: str, - message_obj): - hit, res = super().check_command(message, role, platform, message_obj=message_obj) + message_obj, + cached_plugins: dict): + hit, res = super().check_command(message, role, platform, message_obj=message_obj, cached_plugins=cached_plugins) if hit: return True, res if self.command_start_with(message, "reset"):