feat: support global plugin config for docker envmaster
@@ -25,7 +25,7 @@ available_setting = { | |||||
"single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 | "single_chat_reply_suffix": "", # 私聊时自动回复的后缀,\n 可以换行 | ||||
"group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 | "group_chat_prefix": ["@bot"], # 群聊时包含该前缀则会触发机器人回复 | ||||
"group_chat_reply_prefix": "", # 群聊时自动回复的前缀 | "group_chat_reply_prefix": "", # 群聊时自动回复的前缀 | ||||
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 | |||||
"group_chat_reply_suffix": "", # 群聊时自动回复的后缀,\n 可以换行 | |||||
"group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 | "group_chat_keyword": [], # 群聊时包含该关键词则会触发机器人回复 | ||||
"group_at_off": False, # 是否关闭群聊时@bot的触发 | "group_at_off": False, # 是否关闭群聊时@bot的触发 | ||||
"group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 | "group_name_white_list": ["ChatGPT测试群", "ChatGPT测试群2"], # 开启自动回复的群名称列表 | ||||
@@ -37,7 +37,8 @@ available_setting = { | |||||
"image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 | "image_create_size": "256x256", # 图片大小,可选有 256x256, 512x512, 1024x1024 | ||||
# chatgpt会话参数 | # chatgpt会话参数 | ||||
"expires_in_seconds": 3600, # 无操作会话的过期时间 | "expires_in_seconds": 3600, # 无操作会话的过期时间 | ||||
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", # 人格描述 | |||||
# 人格描述 | |||||
"character_desc": "你是ChatGPT, 一个由OpenAI训练的大型语言模型, 你旨在回答并解决人们的任何问题,并且可以使用多种语言与人交流。", | |||||
"conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 | "conversation_max_tokens": 1000, # 支持上下文记忆的最多字符数 | ||||
# chatgpt限流配置 | # chatgpt限流配置 | ||||
"rate_limit_chatgpt": 20, # chatgpt的调用频率限制 | "rate_limit_chatgpt": 20, # chatgpt的调用频率限制 | ||||
@@ -228,3 +229,26 @@ def subscribe_msg(): | |||||
trigger_prefix = conf().get("single_chat_prefix", [""])[0] | trigger_prefix = conf().get("single_chat_prefix", [""])[0] | ||||
msg = conf().get("subscribe_msg", "") | msg = conf().get("subscribe_msg", "") | ||||
return msg.format(trigger_prefix=trigger_prefix) | 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()) |
@@ -24,16 +24,17 @@ class Banwords(Plugin): | |||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
try: | try: | ||||
# load config | |||||
conf = super().load_config() | |||||
curdir = os.path.dirname(__file__) | 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 = json.load(f) | |||||
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) | |||||
self.searchr = WordsSearch() | self.searchr = WordsSearch() | ||||
self.action = conf["action"] | self.action = conf["action"] | ||||
banwords_path = os.path.join(curdir, "banwords.txt") | banwords_path = os.path.join(curdir, "banwords.txt") | ||||
@@ -29,14 +29,9 @@ class BDunit(Plugin): | |||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
try: | try: | ||||
curdir = os.path.dirname(__file__) | |||||
config_path = os.path.join(curdir, "config.json") | |||||
conf = None | |||||
if not os.path.exists(config_path): | |||||
conf = super().load_config() | |||||
if not conf: | |||||
raise Exception("config.json not found") | 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.service_id = conf["service_id"] | ||||
self.api_key = conf["api_key"] | self.api_key = conf["api_key"] | ||||
self.secret_key = conf["secret_key"] | self.secret_key = conf["secret_key"] | ||||
@@ -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" | |||||
} | |||||
} | |||||
} |
@@ -178,16 +178,13 @@ class Godcmd(Plugin): | |||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
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 = json.load(f) | |||||
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) | |||||
if gconf["password"] == "": | if gconf["password"] == "": | ||||
self.temp_password = "".join(random.sample(string.digits, 4)) | self.temp_password = "".join(random.sample(string.digits, 4)) | ||||
logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password) | logger.info("[Godcmd] 因未设置口令,本次的临时口令为%s。" % self.temp_password) | ||||
@@ -1,6 +1,28 @@ | |||||
import os | |||||
import json | |||||
from config import pconf | |||||
from common.log import logger | |||||
class Plugin: | class Plugin: | ||||
def __init__(self): | def __init__(self): | ||||
self.handlers = {} | self.handlers = {} | ||||
def load_config(self) -> dict: | |||||
""" | |||||
加载当前插件配置 | |||||
:return: 插件配置字典 | |||||
""" | |||||
# 优先获取 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): | def get_help_text(self, **kwargs): | ||||
return "暂无帮助信息" | return "暂无帮助信息" |
@@ -9,7 +9,7 @@ import sys | |||||
from common.log import logger | from common.log import logger | ||||
from common.singleton import singleton | from common.singleton import singleton | ||||
from common.sorted_dict import SortedDict | from common.sorted_dict import SortedDict | ||||
from config import conf | |||||
from config import conf, write_plugin_config | |||||
from .event import * | from .event import * | ||||
@@ -62,6 +62,28 @@ class PluginManager: | |||||
self.save_config() | self.save_config() | ||||
return pconf | 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): | def scan_plugins(self): | ||||
logger.info("Scaning plugins ...") | logger.info("Scaning plugins ...") | ||||
plugins_dir = "./plugins" | plugins_dir = "./plugins" | ||||
@@ -88,7 +110,7 @@ class PluginManager: | |||||
self.loaded[plugin_path] = importlib.import_module(import_path) | self.loaded[plugin_path] = importlib.import_module(import_path) | ||||
self.current_plugin_path = None | self.current_plugin_path = None | ||||
except Exception as e: | 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 | continue | ||||
pconf = self.pconf | pconf = self.pconf | ||||
news = [self.plugins[name] for name in self.plugins] | news = [self.plugins[name] for name in self.plugins] | ||||
@@ -123,7 +145,7 @@ class PluginManager: | |||||
try: | try: | ||||
instance = plugincls() | instance = plugincls() | ||||
except Exception as e: | 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) | self.disable_plugin(name) | ||||
failed_plugins.append(name) | failed_plugins.append(name) | ||||
continue | continue | ||||
@@ -149,6 +171,8 @@ class PluginManager: | |||||
def load_plugins(self): | def load_plugins(self): | ||||
self.load_config() | self.load_config() | ||||
self.scan_plugins() | self.scan_plugins() | ||||
# 加载全量插件配置 | |||||
self._load_all_config() | |||||
pconf = self.pconf | pconf = self.pconf | ||||
logger.debug("plugins.json config={}".format(pconf)) | logger.debug("plugins.json config={}".format(pconf)) | ||||
for name, plugin in pconf["plugins"].items(): | for name, plugin in pconf["plugins"].items(): | ||||
@@ -10,7 +10,6 @@ from bridge.bridge import Bridge | |||||
from bridge.context import ContextType | from bridge.context import ContextType | ||||
from bridge.reply import Reply, ReplyType | from bridge.reply import Reply, ReplyType | ||||
from common import const | from common import const | ||||
from common.log import logger | |||||
from config import conf | from config import conf | ||||
from plugins import * | from plugins import * | ||||
@@ -119,15 +118,8 @@ class Tool(Plugin): | |||||
return | return | ||||
def _read_json(self) -> dict: | 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 = json.load(f) | |||||
return tool_config | |||||
default_config = {"tools": [], "kwargs": {}} | |||||
return super().load_config() or default_config | |||||
def _build_tool_kwargs(self, kwargs: dict): | def _build_tool_kwargs(self, kwargs: dict): | ||||
tool_model_name = kwargs.get("model_name") | tool_model_name = kwargs.get("model_name") | ||||