diff --git a/addons/dashboard/helper.py b/addons/dashboard/helper.py new file mode 100644 index 000000000..06dff7238 --- /dev/null +++ b/addons/dashboard/helper.py @@ -0,0 +1,31 @@ +from addons.dashboard.server import AstrBotDashBoard, DashBoardData +from pydantic import BaseModel +from typing import Union, Optional, + +class DashBoardConfig(BaseModel): + type: str["group", "item"] + name: str + description: str + body: Union[dict, list] + value: Optional[Union[list, dict, str, int, bool]] + val_type: Optional[str["list", "dict", "str", "int", "bool"]] + body: Optional['DashBoardConfig'] + + +class DashBoardHelper(): + def __init__(self, dashboard_data: DashBoardData): + self.dashboard_data = dashboard_data + self.dashboard = AstrBotDashBoard(self.dashboard_data) + + @self.dashboard.register("post_configs") + def on_post_configs(configs: dict): + self.dashboard_data.configs = configs + + return True + + # 将 config.yaml、 中的配置解析到 dashboard_data.configs 中 + def parse_config(self, config: dict): + pass + + def run(self): + self.dashboard.run() \ No newline at end of file diff --git a/addons/dashboard/server.py b/addons/dashboard/server.py new file mode 100644 index 000000000..fa85ab37d --- /dev/null +++ b/addons/dashboard/server.py @@ -0,0 +1,68 @@ +from flask import Flask +import datetime +from pydantic import BaseModel +from util import general_utils as gu + +class DashBoardData(BaseModel): + stats: dict + configs: dict + logs: dict + +class Response(BaseModel): + status: str + message: str + data: dict + +class AstrBotDashBoard(): + def __init__(self, dashboard_data: DashBoardData): + self.dashboard_data = dashboard_data + self.dashboard_be = Flask(__name__) + self.funcs = {} + + @self.dashboard_be.get("/stats") + def get_stats(): + return Response( + status="success", + message="", + data=self.dashboard_data.stats + ).dict() + + @self.dashboard_be.get("/configs") + def get_configs(): + return Response( + status="success", + message="", + data=self.dashboard_data.configs + ).__dict__ + + @self.dashboard_be.post("/configs") + def post_configs(): + if self.funcs["post_configs"](self.dashboard_data.configs): + return Response( + status="success", + message="", + 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(): + return Response( + status="success", + message="", + data=self.dashboard_data.logs + ).__dict__ + + def register(self, name: str): + def decorator(func): + self.funcs[name] = func + return func + return decorator + + def run(self): + gu.log(f"\n\n==================\n您可以访问:\n\thttp://localhost:6185/\n来登录可视化面板。\n==================\n\n", tag="可视化面板") + self.dashboard_be.run(host="0.0.0.0", port=6185) diff --git a/cores/qqbot/core.py b/cores/qqbot/core.py index 9cd34508e..ef1268ba5 100644 --- a/cores/qqbot/core.py +++ b/cores/qqbot/core.py @@ -39,7 +39,7 @@ import io import traceback from . global_object import GlobalObject from typing import Union, Callable - +from addons.dashboard.helper import DashBoardHelper # 缓存的会话 session_dict = {} @@ -48,9 +48,6 @@ count = {} # 统计信息 stat_file = '' -# 日志记录 -# logf = open('log.log', 'a+', encoding='utf-8') - # 用户发言频率 user_frequency = {} # 时间默认值 @@ -64,11 +61,8 @@ announcement = "" # 机器人私聊模式 direct_message_mode = True -# 适配pyinstaller -abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/' - # 版本 -version = '3.0.4' +version = '3.1.0' # 语言模型 REV_CHATGPT = 'rev_chatgpt' @@ -160,27 +154,6 @@ def _runner(func: Callable, args: tuple): loop.run_until_complete(func(*args)) loop.close() - -# [Deprecated] 写入统计信息 -def toggle_count(at: bool, message): - global stat_file - try: - if str(message.guild_id) not in count: - count[str(message.guild_id)] = { - 'count': 1, - 'direct_count': 1, - } - else: - count[str(message.guild_id)]['count'] += 1 - if not at: - count[str(message.guild_id)]['direct_count'] += 1 - stat_file = open(abs_path+"configs/stat", 'w', encoding='utf-8') - stat_file.write(json.dumps(count)) - stat_file.flush() - stat_file.close() - except BaseException: - pass - # 上传统计信息并检查更新 def upload(): global version, gocq_bot, qqchannel_bot @@ -228,6 +201,9 @@ def initBot(cfg, prov): global baidu_judge, chosen_provider global frequency_count, frequency_time, announcement, direct_message_mode, version global keywords, _global_object + + # 迁移旧配置 + gu.try_migrate_config() _event_loop = asyncio.new_event_loop() asyncio.set_event_loop(_event_loop) @@ -410,7 +386,7 @@ def initBot(cfg, prov): # thread.join() if thread_inst == None: - raise Exception("[System-Error] 没有启用/成功启用任何机器人,程序退出") + gu.log("没有启用/成功启用任何机器人平台", gu.LEVEL_CRITICAL) default_personality_str = cc.get("default_personality_str", "") if default_personality_str == "": @@ -420,12 +396,18 @@ def initBot(cfg, prov): "name": "default", "prompt": default_personality_str, } + + # 初始化dashboard + dashboard_helper = DashBoardHelper(_global_object.dashboard_data) + dashboard_helper.parse_config(cfg) + dashboard_thread = threading.Thread(target=dashboard_helper.run, daemon=True) + dashboard_thread.start() gu.log("🎉 项目启动完成。") + + # asyncio.get_event_loop().run_until_complete(cli()) - asyncio.get_event_loop().run_until_complete(cli()) - - thread_inst.join() + dashboard_thread.join() async def cli(): time.sleep(1) diff --git a/cores/qqbot/global_object.py b/cores/qqbot/global_object.py index d9871429e..8e268ff71 100644 --- a/cores/qqbot/global_object.py +++ b/cores/qqbot/global_object.py @@ -1,6 +1,7 @@ from model.platform.qqchan import QQChan, NakuruGuildMember, NakuruGuildMessage from model.platform.qq import QQ from model.provider.provider import Provider +from addons.dashboard.server import DashBoardData from nakuru import ( CQHTTP, GroupMessage, @@ -27,6 +28,7 @@ class GlobalObject: platform_qq: QQ platform_qqchan: QQChan default_personality: dict + dashboard_data: DashBoardData def __init__(self): self.nick = None # gocq 的昵称 @@ -41,6 +43,7 @@ class GlobalObject: self.platform_qq = None self.platform_qqchan = None self.default_personality = None + self.dashboard_data = None class AstrMessageEvent(): message_str: str # 纯消息字符串 diff --git a/main.py b/main.py index 4a209e82e..7b1b2af06 100644 --- a/main.py +++ b/main.py @@ -3,12 +3,17 @@ from pip._internal import main as pipmain import warnings import traceback import threading +import logging warnings.filterwarnings("ignore") abs_path = os.path.dirname(os.path.realpath(sys.argv[0])) + '/' def main(): - + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%H:%M:%S', + ) # config.yaml 配置文件加载和环境确认 try: import cores.qqbot.core as qqBot @@ -112,7 +117,6 @@ def get_platform(): print("other") if __name__ == "__main__": - args = sys.argv if '-cn' in args: @@ -123,7 +127,7 @@ if __name__ == "__main__": if '-replit' in args: print("[System] 启动Replit Web保活服务...") try: - from webapp_replit import keep_alive + from util.webapp_replit import keep_alive keep_alive() except BaseException as e: print(e) diff --git a/util/general_utils.py b/util/general_utils.py index 2525a15ec..6ec3c0eba 100644 --- a/util/general_utils.py +++ b/util/general_utils.py @@ -5,6 +5,7 @@ from PIL import Image, ImageDraw, ImageFont import os import re import requests +from util.cmd_config import CmdConfig PLATFORM_GOCQ = 'gocq' PLATFORM_QQCHAN = 'qqchan' @@ -79,7 +80,14 @@ def log( if len(msg) > max_len: msg = msg[:max_len] + "..." now = datetime.datetime.now().strftime("%m-%d %H:%M:%S") - pre = f"[{now}] [{level}] [{tag}]: {msg}" + + pres = [] + for line in msg.split("\n"): + if line == "\n": + pres.append("") + else: + pres.append(f"[{now}] [{level}] [{tag}]: {line}") + if level == "INFO": if fg is None: fg = FG_COLORS["green"] @@ -100,8 +108,11 @@ def log( fg = FG_COLORS["purple"] if bg is None: bg = BG_COLORS["default"] - - print(f"\033[{fg};{bg}m{pre}\033[0m") + + ret = "" + for line in pres: + ret += f"\033[{fg};{bg}m{line}\033[0m\n" + print(ret) def port_checker(port: int, host: str = "localhost"): @@ -501,14 +512,12 @@ def create_markdown_image(text: str): return p except Exception as e: raise e - -def test_markdown(): - # 示例使用 - markdown_text = """# Help Center - ! [] (https://soulter.top/helpme.jpg) - """ - image = render_markdown(markdown_text) - image.show() -# test_markdown() +# 迁移配置文件到 cmd_config.json +def try_migrate_config(old_config: dict): + cc = CmdConfig() + if cc.get("qqbot", None) is None: + # 未迁移过 + for k in old_config: + cc.put(k, old_config[k]) \ No newline at end of file diff --git a/webapp_replit.py b/util/webapp_replit.py similarity index 100% rename from webapp_replit.py rename to util/webapp_replit.py