From 0e53c95c06d110bdfec486d2f1925c69404548d7 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Wed, 13 Dec 2023 18:35:50 +0800 Subject: [PATCH] feat: config --- addons/dashboard/helper.py | 318 ++++++++++++++++++++----------------- addons/dashboard/server.py | 29 ++-- cores/qqbot/core.py | 26 ++- main.py | 4 +- util/cmd_config.py | 19 +++ 5 files changed, 226 insertions(+), 170 deletions(-) diff --git a/addons/dashboard/helper.py b/addons/dashboard/helper.py index 80be607b5..0aeac5613 100644 --- a/addons/dashboard/helper.py +++ b/addons/dashboard/helper.py @@ -2,169 +2,187 @@ from addons.dashboard.server import AstrBotDashBoard, DashBoardData from pydantic import BaseModel from typing import Union, Optional import uuid +from util import general_utils as gu +from util.cmd_config import CmdConfig +from dataclasses import dataclass +import sys +import os -class DashBoardConfig(BaseModel): - type: str["group", "item"] - name: str - description: str - uuid: Optional[str] # 仅 item 才需要 - body: Optional[list['DashBoardConfig']] # 仅 group 才需要 - value: Optional[Union[list, dict, str, int, bool]] # 仅 item 才需要 - val_type: Optional[str] # 仅 item 才需要 +@dataclass +class DashBoardConfig(): + config_type: str + name: Optional[str] = None + description: Optional[str] = None + path: Optional[str] = None # 仅 item 才需要 + body: Optional[list['DashBoardConfig']] = None # 仅 group 才需要 + value: Optional[Union[list, dict, str, int, bool]] = None # 仅 item 才需要 + val_type: Optional[str] = None # 仅 item 才需要 class DashBoardHelper(): def __init__(self, dashboard_data: DashBoardData, config: dict): - self.parse_config(config) + dashboard_data.configs = { + "data": [] + } + self.parse_default_config(dashboard_data, config) self.dashboard_data: DashBoardData = dashboard_data self.dashboard = AstrBotDashBoard(self.dashboard_data) self.key_map = {} # key: uuid, value: config key name + self.cc = CmdConfig() @self.dashboard.register("post_configs") - def on_post_configs(configs: dict): - self.dashboard_data.configs = configs - - return True + def on_post_configs(post_configs: dict): + try: + gu.log(f"收到配置更新请求", gu.LEVEL_INFO, tag="可视化面板") + self.save_config(post_configs) + self.parse_default_config(self.dashboard_data, self.cc.get_all()) + # 重启 + py = sys.executable + os.execl(py, py, *sys.argv) + except Exception as e: + gu.log(f"在保存配置时发生错误:{e}", gu.LEVEL_ERROR, tag="可视化面板") + raise e + # 将 config.yaml、 中的配置解析到 dashboard_data.configs 中 - def parse_config(self, config: dict): - self.dashboard_data.configs = { - "data": [] - } - ''' - { - "data": [ - { - "type": "group", - "name": "机器人平台配置", - "description": "机器人平台配置描述", - "body": [ - { - "type": "item", - "val_type": "bool", - "name": "启用 QQ 频道平台", - "description": "机器人平台名称描述", - "value": true - }, - { - "type": "item", - "val_type": "string", - "name": "QQ机器人APPID", - "description": "机器人平台名称描述", - "value": "123456" - }, - { - "type": "item", - "val_type": "string", - "name": "QQ机器人令牌", - "description": "机器人平台名称描述", - "value": "123456" - }, - { - "type": "divider" - }, - { - "type": "item", - "val_type": "bool", - "name": "启用 GO-CQHTTP 平台", - "description": "机器人平台名称描述", - "value": false - } - ] - }, - { - "type": "group", - "name": "代理配置", - "description": "代理配置描述", - "body": [ - { - "type": "item", - "val_type": "string", - "name": "代理地址", - "description": "代理配置描述", - "value": "http://localhost:7890" - } - ] - }, - { - "type": "group", - "name": "其他配置", - "description": "其他配置描述", - "body": [ - { - "type": "item", - "val_type": "string", - "name": "回复前缀", - "description": "[xxxx] 你好! 其中xxxx是你可以填写的前缀。如果为空则不显示。", - "value": "GPT" - } - ] - } - - ] -} - ''' - for k in config: - if 'qqbot' in k and 'enable' in k['qqbot'] and 'gocqbot' in k and 'enable' in k['gocqbot']': - self.dashboard_data.configs['data'].append({ - "type": "group", - "name": "机器人平台配置", - "description": "机器人平台配置描述", - "body": [ - { - "type": "item", - "val_type": "bool", - "name": "启用 QQ 频道平台", - "description": "机器人平台名称描述", - "value": k['qqbot']['enable'], - "uuid": uuid.uuid4().hex - }, - { - "type": "item", - "val_type": "string", - "name": "QQ机器人APPID", - "description": "机器人平台名称描述", - "value": k['qqbot']['appid'], - "uuid": uuid.uuid4().hex - }, - { - "type": "item", - "val_type": "string", - "name": "QQ机器人令牌", - "description": "机器人平台名称描述", - "value": k['qqbot']['token'], - "uuid": uuid.uuid4().hex - }, - { - "type": "divider" - }, - { - "type": "item", - "val_type": "bool", - "name": "启用 GO-CQHTTP 平台", - "description": "机器人平台名称描述", - "value": k['gocqbot']['enable'], - "uuid": uuid.uuid4().hex - } - ] - }) + def parse_default_config(self, dashboard_data: DashBoardData, config: dict): + + try: + bot_platform_group = DashBoardConfig( + config_type="group", + name="机器人平台配置", + description="机器人平台配置描述", + body=[ + DashBoardConfig( + config_type="item", + val_type="bool", + name="启用 QQ 频道平台", + description="机器人平台名称描述", + value=config['qqbot']['enable'], + path="qqbot.enable", + ), + DashBoardConfig( + config_type="item", + val_type="string", + name="QQ机器人APPID", + description="机器人平台名称描述", + value=config['qqbot']['appid'], + path="qqbot.appid", + ), + DashBoardConfig( + config_type="item", + val_type="string", + name="QQ机器人令牌", + description="机器人平台名称描述", + value=config['qqbot']['token'], + path="qqbot.token", + ), + DashBoardConfig( + config_type="divider" + ), + DashBoardConfig( + config_type="item", + val_type="bool", + name="启用 GO-CQHTTP 平台", + description="机器人平台名称描述", + value=config['gocqbot']['enable'], + path="gocqbot.enable", + ) + ] + ) - if 'http_proxy' in k: - self.dashboard_data.configs['data'].append({ - "type": "group", - "name": "代理配置", - "description": "代理配置描述", - "body": [ - { - "type": "item", - "val_type": "string", - "name": "代理地址", - "description": "代理配置描述", - "value": k['proxy'], - "uuid": uuid.uuid4().hex - } - ] - }) + proxy_group = DashBoardConfig( + config_type="group", + name="代理配置", + description="代理配置描述", + body=[ + DashBoardConfig( + config_type="item", + val_type="string", + name="HTTP 代理地址", + description="代理配置描述", + value=config['http_proxy'], + path="proxy", + ), + DashBoardConfig( + config_type="item", + val_type="string", + name="HTTPS 代理地址", + description="代理配置描述", + value=config['https_proxy'], + path="proxy", + ) + ] + ) + other_group = DashBoardConfig( + config_type="group", + name="其他配置", + description="其他配置描述", + body=[ + DashBoardConfig( + config_type="item", + val_type="string", + name="回复前缀", + description="[xxxx] 你好! 其中xxxx是你可以填写的前缀。如果为空则不显示。", + value=config['reply_prefix'], + path="reply_prefix", + ) + ] + ) + + dashboard_data.configs['data'] = [ + bot_platform_group, + proxy_group, + other_group + ] + + except Exception as e: + gu.log(f"配置文件解析错误:{e}", gu.LEVEL_ERROR) + raise e + + def save_config(self, post_config: dict): + ''' + 根据 path 解析并保存配置 + ''' + # for config in dashboard_data.configs['data']: + # if config['config_type'] == "group": + # for item in config['body']: + # queue.append(item) + + queue = [] + for config in post_config['data']: + queue.append(config) + + while len(queue) > 0: + config = queue.pop(0) + if config['config_type'] == "group": + for item in config['body']: + queue.append(item) + elif config['config_type'] == "item": + if config['path'] is None or config['path'] == "": + continue + + path = config['path'].split('.') + if len(path) == 0: + continue + + if config['val_type'] == "bool": + self.cc.put_by_dot_str(config['path'], config['value']) + elif config['val_type'] == "string": + self.cc.put_by_dot_str(config['path'], config['value']) + elif config['val_type'] == "int": + try: + self.cc.put_by_dot_str(config['path'], int(config['value'])) + except: + raise ValueError(f"配置项 {config['name']} 的值必须是整数") + elif config['val_type'] == "float": + try: + self.cc.put_by_dot_str(config['path'], float(config['value'])) + except: + raise ValueError(f"配置项 {config['name']} 的值必须是浮点数") + else: + raise NotImplementedError(f"未知或者未实现的的配置项类型:{config['val_type']}") + def run(self): self.dashboard.run() \ No newline at end of file diff --git a/addons/dashboard/server.py b/addons/dashboard/server.py index fa85ab37d..8733c3374 100644 --- a/addons/dashboard/server.py +++ b/addons/dashboard/server.py @@ -1,14 +1,16 @@ -from flask import Flask +from flask import Flask, request import datetime -from pydantic import BaseModel from util import general_utils as gu +from dataclasses import dataclass -class DashBoardData(BaseModel): +@dataclass +class DashBoardData(): stats: dict configs: dict logs: dict - -class Response(BaseModel): + +@dataclass +class Response(): status: str message: str data: dict @@ -37,17 +39,20 @@ class AstrBotDashBoard(): @self.dashboard_be.post("/configs") def post_configs(): - if self.funcs["post_configs"](self.dashboard_data.configs): + post_configs = request.json + try: + self.funcs["post_configs"](post_configs) return Response( status="success", - message="", + message="保存成功~ 机器人正在重启以应用新的配置。", + data=None + ).__dict__ + except Exception as e: + return Response( + status="error", + message=e.__str__(), data=self.dashboard_data.configs ).__dict__ - return Response( - status="error", - message="", - data=self.dashboard_data.configs - ).__dict__ @self.dashboard_be.get("/logs") def get_logs(): diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py index ef1268ba5..ef0fa558b 100644 --- a/cores/qqbot/core.py +++ b/cores/qqbot/core.py @@ -40,6 +40,7 @@ import traceback from . global_object import GlobalObject from typing import Union, Callable from addons.dashboard.helper import DashBoardHelper +from addons.dashboard.server import DashBoardData # 缓存的会话 session_dict = {} @@ -122,6 +123,8 @@ cc.init_attributes("openai_image_generate", { "style": "vivid", "quality": "standard", }) +cc.init_attributes("http_proxy", "") +cc.init_attributes("https_proxy", "") # cc.init_attributes(["qq_forward_mode"], False) # QQ机器人 @@ -203,7 +206,9 @@ def initBot(cfg, prov): global keywords, _global_object # 迁移旧配置 - gu.try_migrate_config() + gu.try_migrate_config(cfg) + # 使用新配置 + cfg = cc.get_all() _event_loop = asyncio.new_event_loop() asyncio.set_event_loop(_event_loop) @@ -213,7 +218,13 @@ def initBot(cfg, prov): _global_object.base_config = cfg if 'reply_prefix' in cfg: - _global_object.reply_prefix = cfg['reply_prefix'] + # 适配旧版配置 + if isinstance(cfg['reply_prefix'], dict): + for k in cfg['reply_prefix']: + _global_object.reply_prefix = cfg['reply_prefix'][k] + break + else: + _global_object.reply_prefix = cfg['reply_prefix'] # 语言模型提供商 gu.log("--------加载语言模型--------", gu.LEVEL_INFO, fg=gu.FG_COLORS['yellow']) @@ -398,8 +409,12 @@ def initBot(cfg, prov): } # 初始化dashboard - dashboard_helper = DashBoardHelper(_global_object.dashboard_data) - dashboard_helper.parse_config(cfg) + _global_object.dashboard_data = DashBoardData( + stats={}, + configs={}, + logs={} + ) + dashboard_helper = DashBoardHelper(_global_object.dashboard_data, config=cc.get_all()) dashboard_thread = threading.Thread(target=dashboard_helper.run, daemon=True) dashboard_thread.start() @@ -736,8 +751,7 @@ async def oper_msg(message: Union[GroupMessage, FriendMessage, GuildMessage, Nak res = "" chatgpt_res = str(res) - if chosen_provider in _global_object.reply_prefix: - chatgpt_res = _global_object.reply_prefix[chosen_provider] + chatgpt_res + chatgpt_res = _global_object.reply_prefix + chatgpt_res except BaseException as e: gu.log(f"调用异常:{traceback.format_exc()}", gu.LEVEL_ERROR, max_len=100000) gu.log("调用语言模型例程时出现异常。原因: "+str(e), gu.LEVEL_ERROR) diff --git a/main.py b/main.py index 7b1b2af06..1169354d7 100644 --- a/main.py +++ b/main.py @@ -33,9 +33,9 @@ def main(): input("config.yaml 配置文件格式错误,请遵守 yaml 格式。") # 设置代理 - if 'http_proxy' in cfg: + if 'http_proxy' in cfg and cfg['http_proxy'] != '': os.environ['HTTP_PROXY'] = cfg['http_proxy'] - if 'https_proxy' in cfg: + if 'https_proxy' in cfg and cfg['https_proxy'] != '': os.environ['HTTPS_PROXY'] = cfg['https_proxy'] os.environ['NO_PROXY'] = 'cn.bing.com,https://api.sgroup.qq.com' diff --git a/util/cmd_config.py b/util/cmd_config.py index 9a138829a..047baed79 100644 --- a/util/cmd_config.py +++ b/util/cmd_config.py @@ -37,6 +37,25 @@ class CmdConfig(): with open(cpath, "w", encoding="utf-8-sig") as f: json.dump(d, f, indent=4, ensure_ascii=False) f.flush() + + @staticmethod + def put_by_dot_str(key: str, value): + ''' + 根据点分割的字符串,将值写入配置文件 + ''' + check_exist() + with open(cpath, "r", encoding="utf-8-sig") as f: + d = json.load(f) + _d = d + _ks = key.split(".") + for i in range(len(_ks)): + if i == len(_ks) - 1: + _d[_ks[i]] = value + else: + _d = _d[_ks[i]] + with open(cpath, "w", encoding="utf-8-sig") as f: + json.dump(d, f, indent=4, ensure_ascii=False) + f.flush() @staticmethod def init_attributes(key: Union[str, list], init_val = ""):