perf: 优化插件加载规则、更新插件接口规范
fix: 修复发言频率限制报错的问题
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# helloworld
|
||||
|
||||
QQChannelChatGPT项目的测试插件
|
||||
|
||||
A test plugin for QQChannelChatGPT plugin feature
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
|
||||
# 热知识:检测消息开头指令,使用以下方法
|
||||
|
||||
+21
-6
@@ -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():
|
||||
|
||||
+92
-54
@@ -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"
|
||||
|
||||
|
||||
@@ -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", "重置"):
|
||||
|
||||
@@ -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", "帮助"):
|
||||
|
||||
@@ -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"):
|
||||
|
||||
Reference in New Issue
Block a user