From 954e55f4b41b59b283ecd4f395279d1fa44c9869 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Thu, 20 Jul 2023 11:36:02 +0800 Subject: [PATCH 1/4] feat: add plugin global config to support docker volumes --- config.py | 28 ++++++++++++++++++++++++++-- plugins/banwords/banwords.py | 2 +- plugins/bdunit/bdunit.py | 2 +- plugins/config-template.json | 24 ++++++++++++++++++++++++ plugins/godcmd/godcmd.py | 2 +- plugins/plugin.py | 10 ++++++++++ plugins/plugin_manager.py | 28 ++++++++++++++++++++++++++-- plugins/tool/tool.py | 2 +- 8 files changed, 90 insertions(+), 8 deletions(-) create mode 100644 plugins/config-template.json diff --git a/config.py b/config.py index cdce26f..85c5436 100644 --- a/config.py +++ b/config.py @@ -25,7 +25,7 @@ available_setting = { "single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 "group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 "group_chat_reply_prefix": "", # 群聊时自动回复的前缀 - "group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 + "group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 "group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 "group_at_off": False, # 是否关闭群聊时@bot的触发 "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 @@ -37,7 +37,8 @@ available_setting = { "image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 # chatgpt会话参数 "expires_in_seconds": 3600, # 无操作会话的过期时间 - "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", # 人格描述 + # 人格描述 + "character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", "conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 # chatgpt限流配置 "rate_limit_chatgpt": 20, # chatgpt的调用频率限制 @@ -228,3 +229,26 @@ def subscribe_msg(): trigger_prefix = conf().get("single_chat_prefix", [""])[0] msg = conf().get("subscribe_msg", "") return msg.format(trigger_prefix=trigger_prefix) + + +# global plugin config +plugin_config = {} + + +def write_plugin_config(pconf: dict): + """ + 写入插件全局配置 + :param pconf: 全量插件配置 + """ + global plugin_config + for k in pconf: + plugin_config[k.lower()] = pconf[k] + + +def pconf(plugin_name: str) -> dict: + """ + 根据插件名称获取配置 + :param plugin_name: 插件名称 + :return: 该插件的配置项 + """ + return plugin_config.get(plugin_name.lower()) diff --git a/plugins/banwords/banwords.py b/plugins/banwords/banwords.py index 118b963..51cfc89 100644 --- a/plugins/banwords/banwords.py +++ b/plugins/banwords/banwords.py @@ -33,7 +33,7 @@ class Banwords(Plugin): json.dump(conf, f, indent=4) else: with open(config_path, "r") as f: - conf = json.load(f) + conf = super().load_config() or json.load(f) self.searchr = WordsSearch() self.action = conf["action"] banwords_path = os.path.join(curdir, "banwords.txt") diff --git a/plugins/bdunit/bdunit.py b/plugins/bdunit/bdunit.py index e41e8d2..778d33d 100644 --- a/plugins/bdunit/bdunit.py +++ b/plugins/bdunit/bdunit.py @@ -36,7 +36,7 @@ class BDunit(Plugin): raise Exception("config.json not found") else: with open(config_path, "r") as f: - conf = json.load(f) + conf = super().load_config() or json.load(f) self.service_id = conf["service_id"] self.api_key = conf["api_key"] self.secret_key = conf["secret_key"] diff --git a/plugins/config-template.json b/plugins/config-template.json new file mode 100644 index 0000000..5c2b19b --- /dev/null +++ b/plugins/config-template.json @@ -0,0 +1,24 @@ +{ + "godcmd": { + "password": "", + "admin_users": [] + }, + "banwords": { + "action": "replace", + "reply_filter": true, + "reply_action": "ignore" + }, + "tool": { + "tools": [ + "python", + "url-get", + "terminal", + "meteo-weather" + ], + "kwargs": { + "top_k_results": 2, + "no_default": false, + "model_name": "gpt-3.5-turbo" + } + } +} diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index 2ce370f..7131af0 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -187,7 +187,7 @@ class Godcmd(Plugin): json.dump(gconf, f, indent=4) else: with open(config_path, "r") as f: - gconf = json.load(f) + gconf = super().load_config() or json.load(f) if gconf["password"] == "": self.temp_password = "".join(random.sample(string.digits, 4)) logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password) diff --git a/plugins/plugin.py b/plugins/plugin.py index 6c82c8d..bd432b9 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -1,6 +1,16 @@ +import os +from config import pconf + class Plugin: def __init__(self): self.handlers = {} + def load_config(self) -> dict: + """ + 加载当前插件配置 + :return: 插件配置字典 + """ + return pconf(self.name) + def get_help_text(self, **kwargs): return "暂无帮助信息" diff --git a/plugins/plugin_manager.py b/plugins/plugin_manager.py index 2696954..1206902 100644 --- a/plugins/plugin_manager.py +++ b/plugins/plugin_manager.py @@ -9,7 +9,7 @@ import sys from common.log import logger from common.singleton import singleton from common.sorted_dict import SortedDict -from config import conf +from config import conf, write_plugin_config from .event import * @@ -62,6 +62,28 @@ class PluginManager: self.save_config() return pconf + @staticmethod + def _load_all_config(): + """ + 背景: 目前插件配置存放于每个插件目录的config.json下,docker运行时不方便进行映射,故增加统一管理的入口,优先 + 加载 plugins/config.json,原插件目录下的config.json 不受影响 + + 从 plugins/config.json 中加载所有插件的配置并写入 config.py 的全局配置中,供插件中使用 + 插件实例中通过 config.pconf(plugin_name) 即可获取该插件的配置 + """ + all_config_path = "./plugins/config.json" + try: + if os.path.exists(all_config_path): + # read from all plugins config + with open(all_config_path, "r", encoding="utf-8") as f: + all_conf = json.load(f) + logger.info(f"load all config from plugins/config.json: {all_conf}") + + # write to global config + write_plugin_config(all_conf) + except Exception as e: + logger.error(e) + def scan_plugins(self): logger.info("Scaning plugins ...") plugins_dir = "./plugins" @@ -88,7 +110,7 @@ class PluginManager: self.loaded[plugin_path] = importlib.import_module(import_path) self.current_plugin_path = None except Exception as e: - logger.exception("Failed to import plugin %s: %s" % (plugin_name, e)) + logger.warn("Failed to import plugin %s: %s" % (plugin_name, e)) continue pconf = self.pconf news = [self.plugins[name] for name in self.plugins] @@ -149,6 +171,8 @@ class PluginManager: def load_plugins(self): self.load_config() self.scan_plugins() + # 加载全量插件配置 + self._load_all_config() pconf = self.pconf logger.debug("plugins.json config={}".format(pconf)) for name, plugin in pconf["plugins"].items(): diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index 1c3b13d..b84d475 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -126,7 +126,7 @@ class Tool(Plugin): return tool_config else: with open(config_path, "r") as f: - tool_config = json.load(f) + tool_config = super().load_config() or json.load(f) return tool_config def _build_tool_kwargs(self, kwargs: dict): From 4bab4299f28f0660cb4c76a5b97e20e78f5a90ff Mon Sep 17 00:00:00 2001 From: zhayujie Date: Thu, 20 Jul 2023 14:24:40 +0800 Subject: [PATCH 2/4] fix: global plugin config read --- plugins/banwords/banwords.py | 18 ++++++++++-------- plugins/bdunit/bdunit.py | 13 +++++++------ plugins/godcmd/godcmd.py | 18 ++++++++++-------- plugins/plugin.py | 5 ++++- plugins/tool/tool.py | 13 +++++++------ 5 files changed, 38 insertions(+), 29 deletions(-) diff --git a/plugins/banwords/banwords.py b/plugins/banwords/banwords.py index 51cfc89..d8d4adb 100644 --- a/plugins/banwords/banwords.py +++ b/plugins/banwords/banwords.py @@ -26,14 +26,16 @@ class Banwords(Plugin): try: curdir = os.path.dirname(__file__) config_path = os.path.join(curdir, "config.json") - conf = None - if not os.path.exists(config_path): - conf = {"action": "ignore"} - with open(config_path, "w") as f: - json.dump(conf, f, indent=4) - else: - with open(config_path, "r") as f: - conf = super().load_config() or json.load(f) + # loading config from global plugin config + conf = super().load_config() + if not conf: + if not os.path.exists(config_path): + conf = {"action": "ignore"} + with open(config_path, "w") as f: + json.dump(conf, f, indent=4) + else: + with open(config_path, "r") as f: + conf = super().load_config() or json.load(f) self.searchr = WordsSearch() self.action = conf["action"] banwords_path = os.path.join(curdir, "banwords.txt") diff --git a/plugins/bdunit/bdunit.py b/plugins/bdunit/bdunit.py index 778d33d..212b4d7 100644 --- a/plugins/bdunit/bdunit.py +++ b/plugins/bdunit/bdunit.py @@ -31,12 +31,13 @@ class BDunit(Plugin): try: curdir = os.path.dirname(__file__) config_path = os.path.join(curdir, "config.json") - conf = None - if not os.path.exists(config_path): - raise Exception("config.json not found") - else: - with open(config_path, "r") as f: - conf = super().load_config() or json.load(f) + conf = super().load_config() + if not conf: + if not os.path.exists(config_path): + raise Exception("config.json not found") + else: + with open(config_path, "r") as f: + conf = json.load(f) self.service_id = conf["service_id"] self.api_key = conf["api_key"] self.secret_key = conf["secret_key"] diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index 7131af0..11def3f 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -180,14 +180,16 @@ class Godcmd(Plugin): curdir = os.path.dirname(__file__) config_path = os.path.join(curdir, "config.json") - gconf = None - if not os.path.exists(config_path): - gconf = {"password": "", "admin_users": []} - with open(config_path, "w") as f: - json.dump(gconf, f, indent=4) - else: - with open(config_path, "r") as f: - gconf = super().load_config() or json.load(f) + # loading config from global plugin config + gconf = super().load_config() + if not gconf: + if not os.path.exists(config_path): + gconf = {"password": "", "admin_users": []} + with open(config_path, "w") as f: + json.dump(gconf, f, indent=4) + else: + with open(config_path, "r") as f: + gconf = json.load(f) if gconf["password"] == "": self.temp_password = "".join(random.sample(string.digits, 4)) logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password) diff --git a/plugins/plugin.py b/plugins/plugin.py index bd432b9..2938b47 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -1,5 +1,6 @@ import os from config import pconf +from common.log import logger class Plugin: def __init__(self): @@ -10,7 +11,9 @@ class Plugin: 加载当前插件配置 :return: 插件配置字典 """ - return pconf(self.name) + conf = pconf(self.name) + logger.info(f"loading from global plugin config, plugin_name={self.name}, conf={conf}") + return conf def get_help_text(self, **kwargs): return "暂无帮助信息" diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index b84d475..eadd4d9 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -121,12 +121,13 @@ class Tool(Plugin): def _read_json(self) -> dict: curdir = os.path.dirname(__file__) config_path = os.path.join(curdir, "config.json") - tool_config = {"tools": [], "kwargs": {}} - if not os.path.exists(config_path): - return tool_config - else: - with open(config_path, "r") as f: - tool_config = super().load_config() or json.load(f) + tool_config = super().load_config() + if not tool_config: + if not os.path.exists(config_path): + return {"tools": [], "kwargs": {}} + else: + with open(config_path, "r") as f: + tool_config = json.load(f) return tool_config def _build_tool_kwargs(self, kwargs: dict): From aae9b648333af45616a5d3c9a0b591b3b321c783 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Thu, 20 Jul 2023 14:46:41 +0800 Subject: [PATCH 3/4] fix: reduce unnecessary error traceback logs --- plugins/plugin_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plugin_manager.py b/plugins/plugin_manager.py index 1206902..49c13ca 100644 --- a/plugins/plugin_manager.py +++ b/plugins/plugin_manager.py @@ -145,7 +145,7 @@ class PluginManager: try: instance = plugincls() except Exception as e: - logger.exception("Failed to init %s, diabled. %s" % (name, e)) + logger.warn("Failed to init %s, diabled. %s" % (name, e)) self.disable_plugin(name) failed_plugins.append(name) continue From 9ef8e1be3ff8ce1475faec9ef18a638d9085c6a3 Mon Sep 17 00:00:00 2001 From: zhayujie Date: Thu, 20 Jul 2023 16:08:19 +0800 Subject: [PATCH 4/4] feat: move loading config method to base class --- plugins/banwords/banwords.py | 11 +++++------ plugins/bdunit/bdunit.py | 8 +------- ...{config-template.json => config.json.template} | 0 plugins/godcmd/godcmd.py | 7 +------ plugins/plugin.py | 15 ++++++++++++--- plugins/tool/tool.py | 13 ++----------- 6 files changed, 21 insertions(+), 33 deletions(-) rename plugins/{config-template.json => config.json.template} (100%) diff --git a/plugins/banwords/banwords.py b/plugins/banwords/banwords.py index d8d4adb..2a33a5a 100644 --- a/plugins/banwords/banwords.py +++ b/plugins/banwords/banwords.py @@ -24,18 +24,17 @@ class Banwords(Plugin): def __init__(self): super().__init__() try: - curdir = os.path.dirname(__file__) - config_path = os.path.join(curdir, "config.json") - # loading config from global plugin config + # load config conf = super().load_config() + curdir = os.path.dirname(__file__) if not conf: + # 配置不存在则写入默认配置 + config_path = os.path.join(curdir, "config.json") if not os.path.exists(config_path): conf = {"action": "ignore"} with open(config_path, "w") as f: json.dump(conf, f, indent=4) - else: - with open(config_path, "r") as f: - conf = super().load_config() or json.load(f) + self.searchr = WordsSearch() self.action = conf["action"] banwords_path = os.path.join(curdir, "banwords.txt") diff --git a/plugins/bdunit/bdunit.py b/plugins/bdunit/bdunit.py index 212b4d7..33194e3 100644 --- a/plugins/bdunit/bdunit.py +++ b/plugins/bdunit/bdunit.py @@ -29,15 +29,9 @@ class BDunit(Plugin): def __init__(self): super().__init__() try: - curdir = os.path.dirname(__file__) - config_path = os.path.join(curdir, "config.json") conf = super().load_config() if not conf: - if not os.path.exists(config_path): - raise Exception("config.json not found") - else: - with open(config_path, "r") as f: - conf = json.load(f) + raise Exception("config.json not found") self.service_id = conf["service_id"] self.api_key = conf["api_key"] self.secret_key = conf["secret_key"] diff --git a/plugins/config-template.json b/plugins/config.json.template similarity index 100% rename from plugins/config-template.json rename to plugins/config.json.template diff --git a/plugins/godcmd/godcmd.py b/plugins/godcmd/godcmd.py index 11def3f..08bc09e 100644 --- a/plugins/godcmd/godcmd.py +++ b/plugins/godcmd/godcmd.py @@ -178,18 +178,13 @@ class Godcmd(Plugin): def __init__(self): super().__init__() - curdir = os.path.dirname(__file__) - config_path = os.path.join(curdir, "config.json") - # loading config from global plugin config + config_path = os.path.join(os.path.dirname(__file__), "config.json") gconf = super().load_config() if not gconf: if not os.path.exists(config_path): gconf = {"password": "", "admin_users": []} with open(config_path, "w") as f: json.dump(gconf, f, indent=4) - else: - with open(config_path, "r") as f: - gconf = json.load(f) if gconf["password"] == "": self.temp_password = "".join(random.sample(string.digits, 4)) logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password) diff --git a/plugins/plugin.py b/plugins/plugin.py index 2938b47..e7444d2 100644 --- a/plugins/plugin.py +++ b/plugins/plugin.py @@ -1,7 +1,9 @@ import os +import json from config import pconf from common.log import logger + class Plugin: def __init__(self): self.handlers = {} @@ -11,9 +13,16 @@ class Plugin: 加载当前插件配置 :return: 插件配置字典 """ - conf = pconf(self.name) - logger.info(f"loading from global plugin config, plugin_name={self.name}, conf={conf}") - return conf + # 优先获取 plugins/config.json 中的全局配置 + plugin_conf = pconf(self.name) + if not plugin_conf: + # 全局配置不存在,则获取插件目录下的配置 + plugin_config_path = os.path.join(self.path, "config.json") + if os.path.exists(plugin_config_path): + with open(plugin_config_path, "r") as f: + plugin_conf = json.load(f) + logger.debug(f"loading plugin config, plugin_name={self.name}, conf={plugin_conf}") + return plugin_conf def get_help_text(self, **kwargs): return "暂无帮助信息" diff --git a/plugins/tool/tool.py b/plugins/tool/tool.py index eadd4d9..b99eabb 100644 --- a/plugins/tool/tool.py +++ b/plugins/tool/tool.py @@ -10,7 +10,6 @@ from bridge.bridge import Bridge from bridge.context import ContextType from bridge.reply import Reply, ReplyType from common import const -from common.log import logger from config import conf from plugins import * @@ -119,16 +118,8 @@ class Tool(Plugin): return def _read_json(self) -> dict: - curdir = os.path.dirname(__file__) - config_path = os.path.join(curdir, "config.json") - tool_config = super().load_config() - if not tool_config: - if not os.path.exists(config_path): - return {"tools": [], "kwargs": {}} - else: - with open(config_path, "r") as f: - tool_config = json.load(f) - return tool_config + default_config = {"tools": [], "kwargs": {}} + return super().load_config() or default_config def _build_tool_kwargs(self, kwargs: dict): tool_model_name = kwargs.get("model_name")