From 78060c99857a12a06f9eec09bfc999cfc7d6309c Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Tue, 17 Sep 2024 22:50:05 -0400 Subject: [PATCH] refactor: move`plugins` and `temp` folder to `data/` --- .github/workflows/coverage_test.yml | 1 + addons/plugins/helloworld/README.md | 10 ------ addons/plugins/helloworld/REPO | 1 - addons/plugins/helloworld/main.py | 36 -------------------- addons/plugins/helloworld/metadata.yaml | 6 ---- astrbot/bootstrap.py | 9 +++-- dashboard/server.py | 3 +- main.py | 3 +- model/command/internal_handler.py | 24 +++++++------- model/plugin/command.py | 1 - model/plugin/manager.py | 2 +- type/config.py | 2 +- util/cmd_config.py | 27 ++++++++++++++- util/config_utils.py | 15 --------- util/io.py | 10 +++--- util/updator/astrbot_updator.py | 41 ++--------------------- util/updator/plugin_updator.py | 44 ++++++++++++++++++++++--- 17 files changed, 94 insertions(+), 141 deletions(-) delete mode 100644 addons/plugins/helloworld/README.md delete mode 100644 addons/plugins/helloworld/REPO delete mode 100644 addons/plugins/helloworld/main.py delete mode 100644 addons/plugins/helloworld/metadata.yaml delete mode 100644 util/config_utils.py diff --git a/.github/workflows/coverage_test.yml b/.github/workflows/coverage_test.yml index 941084087..175e5f564 100644 --- a/.github/workflows/coverage_test.yml +++ b/.github/workflows/coverage_test.yml @@ -22,6 +22,7 @@ jobs: pip install -r requirements.txt pip install pytest pytest-cov pytest-asyncio mkdir data + mkdir data/plugins mkdir data/config mkdir temp diff --git a/addons/plugins/helloworld/README.md b/addons/plugins/helloworld/README.md deleted file mode 100644 index bd1098b98..000000000 --- a/addons/plugins/helloworld/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# helloworld - -AstrBot 插件模板 - -A template plugin for AstrBot plugin feature - -# 支持 - -[帮助文档](https://astrbot.soulter.top/center/docs/%E5%BC%80%E5%8F%91/%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/ -) diff --git a/addons/plugins/helloworld/REPO b/addons/plugins/helloworld/REPO deleted file mode 100644 index c158cc216..000000000 --- a/addons/plugins/helloworld/REPO +++ /dev/null @@ -1 +0,0 @@ -https://github.com/Soulter/helloworld \ No newline at end of file diff --git a/addons/plugins/helloworld/main.py b/addons/plugins/helloworld/main.py deleted file mode 100644 index 54586aa4e..000000000 --- a/addons/plugins/helloworld/main.py +++ /dev/null @@ -1,36 +0,0 @@ -flag_not_support = False -try: - from util.plugin_dev.api.v1 import ( - Context, - CommandResult, - AstrMessageEvent, - Middleware, - ) -except ImportError: - flag_not_support = True - print("导入接口失败。请升级到 AstrBot 最新版本。") - -''' -注意以格式 XXXPlugin 或 Main 来修改插件名。 -提示:把此模板仓库 fork 之后 clone 到机器人文件夹下的 addons/plugins/ 目录下,然后用 Pycharm/VSC 等工具打开可获更棒的编程体验(自动补全等) -''' -class HelloWorldPlugin: - """ - AstrBot 会传递 context 给插件。 - - - context.register_commands: 注册指令 - - context.register_task: 注册任务 - - context.message_handler: 消息处理器(平台类插件用) - """ - def __init__(self, context: Context) -> None: - self.context = context - self.context.register_commands("helloworld", "helloworld", "内置测试指令。", 1, self.helloworld) - - """ - 指令处理函数。 - - - 需要接收两个参数:message: AstrMessageEvent, context: Context - - 返回 CommandResult 对象 - """ - async def helloworld(self, message: AstrMessageEvent, context: Context): - return CommandResult().message("Hello, World!") \ No newline at end of file diff --git a/addons/plugins/helloworld/metadata.yaml b/addons/plugins/helloworld/metadata.yaml deleted file mode 100644 index 41e34ef3e..000000000 --- a/addons/plugins/helloworld/metadata.yaml +++ /dev/null @@ -1,6 +0,0 @@ -name: helloworld # 这是你的插件的唯一识别名。 -desc: 这是 AstrBot 的默认插件。 -help: -version: v1.3 # 插件版本号。格式:v1.1.1 或者 v1.1 -author: Soulter # 作者 -repo: https://github.com/Soulter/helloworld # 插件的仓库地址 diff --git a/astrbot/bootstrap.py b/astrbot/bootstrap.py index 300e07b8d..36e8a18a8 100644 --- a/astrbot/bootstrap.py +++ b/astrbot/bootstrap.py @@ -1,21 +1,20 @@ import asyncio import traceback +import os from astrbot.message.handler import MessageHandler from astrbot.persist.helper import dbConn from dashboard.server import AstrBotDashBoard -from model.provider.provider import Provider from model.command.manager import CommandManager from model.command.internal_handler import InternalCommandHandler from model.plugin.manager import PluginManager from model.platform.manager import PlatformManager -from typing import Dict, List, Union +from typing import Union from type.types import Context from type.config import VERSION from SparkleLogging.utils.core import LogManager from logging import Logger -from util.cmd_config import AstrBotConfig +from util.cmd_config import AstrBotConfig, try_migrate from util.metrics import MetricUploader -from util.config_utils import * from util.updator.astrbot_updator import AstrBotUpdator logger: Logger = LogManager.GetLogger(log_name='astrbot') @@ -26,7 +25,7 @@ class AstrBotBootstrap(): self.context = Context() # load configs and ensure the backward compatibility - try_migrate_config() + try_migrate() self.config_helper = AstrBotConfig() self.context.config_helper = self.config_helper logger.info("AstrBot v" + VERSION) diff --git a/dashboard/server.py b/dashboard/server.py index ee9090668..33b852c89 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -228,8 +228,7 @@ class AstrBotDashBoard(): file = request.files['file'] print(file.filename) logger.info(f"正在安装用户上传的插件 {file.filename}") - # save file to temp/ - file_path = f"temp/{uuid.uuid4()}.zip" + file_path = f"data/temp/{uuid.uuid4()}.zip" file.save(file_path) self.plugin_manager.install_plugin_from_file(file_path) logger.info(f"安装插件 {file.filename} 成功") diff --git a/main.py b/main.py index 3eead7548..7f408dec3 100644 --- a/main.py +++ b/main.py @@ -42,7 +42,8 @@ def check_env(): exit() os.makedirs("data/config", exist_ok=True) - os.makedirs("temp", exist_ok=True) + os.makedirs("data/plugins", exist_ok=True) + os.makedirs("data/temp", exist_ok=True) # workaround for issue #181 mimetypes.add_type("text/javascript", ".js") diff --git a/model/command/internal_handler.py b/model/command/internal_handler.py index c7e8e19bf..e05feccbe 100644 --- a/model/command/internal_handler.py +++ b/model/command/internal_handler.py @@ -19,23 +19,23 @@ class InternalCommandHandler: self.plugin_manager = plugin_manager self.manager.register("help", "查看帮助", 10, self.help) - self.manager.register("wake", "设置机器人唤醒词", 10, self.set_nick) - self.manager.register("update", "更新 AstrBot", 10, self.update) + self.manager.register("wake", "唤醒前缀", 10, self.set_nick) + self.manager.register("update", "更新管理", 10, self.update) self.manager.register("plugin", "插件管理", 10, self.plugin) self.manager.register("reboot", "重启 AstrBot", 10, self.reboot) - self.manager.register("websearch", "网页搜索开关", 10, self.web_search) - self.manager.register("t2i", "文本转图片开关", 10, self.t2i_toggle) - self.manager.register("myid", "获取你在此平台上的ID", 10, self.myid) - self.manager.register("provider", "查看和切换当前使用的 LLM 资源来源", 10, self.provider) + self.manager.register("websearch", "网页搜索", 10, self.web_search) + self.manager.register("t2i", "文转图", 10, self.t2i_toggle) + self.manager.register("myid", "用户ID", 10, self.myid) + self.manager.register("provider", "LLM 接入源", 10, self.provider) def provider(self, message: AstrMessageEvent, context: Context): if len(context.llms) == 0: - return CommandResult().message("当前没有加载任何 LLM 资源。") + return CommandResult().message("当前没有加载任何 LLM 接入源。") tokens = self.manager.command_parser.parse(message.message_str) if tokens.len == 1: - ret = "## 当前载入的 LLM 资源\n" + ret = "## 当前载入的 LLM 接入源\n" for idx, llm in enumerate(context.llms): ret += f"{idx}. {llm.llm_name}" if llm.origin: @@ -44,7 +44,7 @@ class InternalCommandHandler: ret += " (当前使用)" ret += "\n" - ret += "\n使用 provider <序号> 切换 LLM 资源。" + ret += "\n使用 provider <序号> 切换 LLM 接入源。" return CommandResult().message(ret) else: try: @@ -52,7 +52,7 @@ class InternalCommandHandler: if idx >= len(context.llms): return CommandResult().message("provider: 无效的序号。") context.message_handler.set_provider(context.llms[idx].llm_instance) - return CommandResult().message(f"已经成功切换到 LLM 资源 {context.llms[idx].llm_name}。") + return CommandResult().message(f"已经成功切换到 LLM 接入源 {context.llms[idx].llm_name}。") except BaseException as e: return CommandResult().message("provider: 参数错误。") @@ -71,7 +71,7 @@ class InternalCommandHandler: return CommandResult( hit=True, success=True, - message_chain=f"已经成功将唤醒词设定为 {nick}。", + message_chain=f"已经成功将唤醒前缀设定为 {nick}。", ) def update(self, message: AstrMessageEvent, context: Context): @@ -280,5 +280,5 @@ class InternalCommandHandler: return CommandResult( hit=True, success=False, - message_chain=f"在 {message.platform} 上获取你的ID失败,原因: {str(e)}", + message_chain=f"获取失败,原因: {str(e)}", ) diff --git a/model/plugin/command.py b/model/plugin/command.py index 1e4d8fab9..747d01eb7 100644 --- a/model/plugin/command.py +++ b/model/plugin/command.py @@ -24,4 +24,3 @@ class PluginCommandBridge(): def register_command(self, plugin_name, command_name, description, priority, handler, use_regex=False, ignore_prefix=False): self.plugin_commands_waitlist.append(CommandRegisterRequest(command_name, description, priority, handler, use_regex, plugin_name, ignore_prefix)) - \ No newline at end of file diff --git a/model/plugin/manager.py b/model/plugin/manager.py index 43e9cc30f..fb5275b5d 100644 --- a/model/plugin/manager.py +++ b/model/plugin/manager.py @@ -184,7 +184,7 @@ class PluginManager(): self.check_plugin_dept_update(target_plugin=root_dir_name) - module = __import__("addons.plugins." + + module = __import__("data.plugins." + root_dir_name + "." + p, fromlist=[p]) cls = self.get_classes(module) diff --git a/type/config.py b/type/config.py index c381f68e2..639e75ac0 100644 --- a/type/config.py +++ b/type/config.py @@ -1,4 +1,4 @@ -VERSION = '3.3.9' +VERSION = '3.3.12' DEFAULT_CONFIG = { "qqbot": { diff --git a/util/cmd_config.py b/util/cmd_config.py index 1b2a3d767..bdb2d239a 100644 --- a/util/cmd_config.py +++ b/util/cmd_config.py @@ -1,6 +1,8 @@ import os import json +import shutil import logging +from util.io import on_error from type.config import DEFAULT_CONFIG, DEFAULT_CONFIG_VERSION_2, MAPPINGS_1_2 from dataclasses import dataclass, field, asdict from typing import List, Dict, Optional @@ -295,4 +297,27 @@ class AstrBotConfig(): raise KeyError(f"Key {key} not found in config.") def check_exist(self) -> bool: - return os.path.exists(ASTRBOT_CONFIG_PATH) \ No newline at end of file + return os.path.exists(ASTRBOT_CONFIG_PATH) + +def try_migrate(): + ''' + - 将 cmd_config.json 迁移至 data/cmd_config.json (如果存在) + - 将 addons/plugins 迁移至 data/plugins (如果存在) + ''' + if os.path.exists("cmd_config.json") and not os.path.exists("data/cmd_config.json"): + try: + shutil.move("cmd_config.json", "data/cmd_config.json") + except: + logger.error("迁移 cmd_config.json 失败。") + + if os.path.exists("addons/plugins"): + if os.path.exists("data/plugins"): + try: + shutil.rmtree("data/plugins", onerror=on_error) + except: + logger.error("删除 data/plugins 失败。") + try: + shutil.move("addons/plugins", "data/") + shutil.rmtree("addons", onerror=on_error) + except: + logger.error("迁移 addons/plugins 失败。") \ No newline at end of file diff --git a/util/config_utils.py b/util/config_utils.py deleted file mode 100644 index 356bd6b90..000000000 --- a/util/config_utils.py +++ /dev/null @@ -1,15 +0,0 @@ -import json, os, shutil -import logging - -logger = logging.getLogger("astrbot") - -def try_migrate_config(): - ''' - 将 cmd_config.json 迁移至 data/cmd_config.json (如果存在的话) - ''' - if os.path.exists("cmd_config.json") and not os.path.exists("data/cmd_config.json"): - try: - shutil.move("cmd_config.json", "data/cmd_config.json") - except: - logger.error("迁移 cmd_config.json 失败。AstrBot 将不会读取配置文件,你可以手动将 cmd_config.json 迁移至 data/cmd_config.json。") - \ No newline at end of file diff --git a/util/io.py b/util/io.py index b6032d342..6125d620c 100644 --- a/util/io.py +++ b/util/io.py @@ -47,13 +47,11 @@ def port_checker(port: int, host: str = "localhost"): def save_temp_img(img: Image) -> str: - if not os.path.exists("temp"): - os.makedirs("temp") - + os.makedirs("data/temp", exist_ok=True) # 获得文件创建时间,清除超过1小时的 try: - for f in os.listdir("temp"): - path = os.path.join("temp", f) + for f in os.listdir("data/temp"): + path = os.path.join("data/temp", f) if os.path.isfile(path): ctime = os.path.getctime(path) if time.time() - ctime > 3600: @@ -63,7 +61,7 @@ def save_temp_img(img: Image) -> str: # 获得时间戳 timestamp = int(time.time()) - p = f"temp/{timestamp}.jpg" + p = f"data/temp/{timestamp}.jpg" if isinstance(img, Image.Image): img.save(p) diff --git a/util/updator/astrbot_updator.py b/util/updator/astrbot_updator.py index b6fbd9243..7db2705a9 100644 --- a/util/updator/astrbot_updator.py +++ b/util/updator/astrbot_updator.py @@ -1,4 +1,4 @@ -import os, psutil, sys, zipfile, shutil, time +import os, psutil, sys, time from util.updator.zip_updator import ReleaseInfo, RepoZipUpdator from SparkleLogging.utils.core import LogManager from logging import Logger @@ -65,7 +65,6 @@ class AstrBotUpdator(RepoZipUpdator): raise Exception(f"未找到版本号为 {version} 的更新文件。") try: - # self.download_from_repo_url("temp", file_url) download_file(file_url, "temp.zip") self.unzip_file("temp.zip", self.MAIN_PATH) except BaseException as e: @@ -78,40 +77,4 @@ class AstrBotUpdator(RepoZipUpdator): ''' 解压缩文件, 并将压缩包内**第一个**文件夹内的文件移动到 target_dir ''' - os.makedirs(target_dir, exist_ok=True) - update_dir = "" - logger.info(f"解压文件: {zip_path}") - with zipfile.ZipFile(zip_path, 'r') as z: - update_dir = z.namelist()[0] - z.extractall(target_dir) - - avoid_dirs = ["logs", "data", "configs", "temp_plugins", update_dir] - # copy addons/plugins to the target_dir temporarily - if os.path.exists(os.path.join(target_dir, "addons/plugins")): - logger.info("备份插件目录:从 addons/plugins 到 temp_plugins") - shutil.copytree(os.path.join(target_dir, "addons/plugins"), "temp_plugins") - - files = os.listdir(os.path.join(target_dir, update_dir)) - for f in files: - logger.info(f"移动更新文件/目录: {f}") - if os.path.isdir(os.path.join(target_dir, update_dir, f)): - if f in avoid_dirs: continue - if os.path.exists(os.path.join(target_dir, f)): - shutil.rmtree(os.path.join(target_dir, f), onerror=on_error) - else: - if os.path.exists(os.path.join(target_dir, f)): - os.remove(os.path.join(target_dir, f)) - shutil.move(os.path.join(target_dir, update_dir, f), target_dir) - - # move back - if os.path.exists("temp_plugins"): - logger.info("恢复插件目录:从 temp_plugins 到 addons/plugins") - shutil.rmtree(os.path.join(target_dir, "addons/plugins"), onerror=on_error) - shutil.move("temp_plugins", os.path.join(target_dir, "addons/plugins")) - - try: - logger.info(f"删除临时更新文件: {zip_path} 和 {os.path.join(target_dir, update_dir)}") - shutil.rmtree(os.path.join(target_dir, update_dir), onerror=on_error) - os.remove(zip_path) - except: - logger.warn(f"删除更新文件失败,可以手动删除 {zip_path} 和 {os.path.join(target_dir, update_dir)}") + pass \ No newline at end of file diff --git a/util/updator/plugin_updator.py b/util/updator/plugin_updator.py index 6303f2bed..97b14550b 100644 --- a/util/updator/plugin_updator.py +++ b/util/updator/plugin_updator.py @@ -1,19 +1,19 @@ -import os +import os, zipfile, shutil from util.updator.zip_updator import RepoZipUpdator from util.io import remove_dir -from type.plugin import PluginMetadata from type.register import RegisteredPlugin from typing import Union from SparkleLogging.utils.core import LogManager from logging import Logger +from util.io import on_error logger: Logger = LogManager.GetLogger(log_name='astrbot') class PluginUpdator(RepoZipUpdator): def __init__(self) -> None: - self.plugin_store_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../addons/plugins")) + self.plugin_store_path = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../data/plugins")) def get_plugin_store_path(self) -> str: return self.plugin_store_path @@ -44,5 +44,41 @@ class PluginUpdator(RepoZipUpdator): return plugin_path + def unzip_file(self, zip_path: str, target_dir: str): + os.makedirs(target_dir, exist_ok=True) + update_dir = "" + logger.info(f"解压文件: {zip_path}") + with zipfile.ZipFile(zip_path, 'r') as z: + update_dir = z.namelist()[0] + z.extractall(target_dir) - \ No newline at end of file + avoid_dirs = ["logs", "data", "configs", "temp_plugins", update_dir] + # copy addons/plugins to the target_dir temporarily + # if os.path.exists(os.path.join(target_dir, "addons/plugins")): + # logger.info("备份插件目录:从 addons/plugins 到 temp_plugins") + # shutil.copytree(os.path.join(target_dir, "addons/plugins"), "temp_plugins") + + files = os.listdir(os.path.join(target_dir, update_dir)) + for f in files: + logger.info(f"移动更新文件/目录: {f}") + if os.path.isdir(os.path.join(target_dir, update_dir, f)): + if f in avoid_dirs: continue + if os.path.exists(os.path.join(target_dir, f)): + shutil.rmtree(os.path.join(target_dir, f), onerror=on_error) + else: + if os.path.exists(os.path.join(target_dir, f)): + os.remove(os.path.join(target_dir, f)) + shutil.move(os.path.join(target_dir, update_dir, f), target_dir) + + # move back + # if os.path.exists("temp_plugins"): + # logger.info("恢复插件目录:从 temp_plugins 到 addons/plugins") + # shutil.rmtree(os.path.join(target_dir, "addons/plugins"), onerror=on_error) + # shutil.move("temp_plugins", os.path.join(target_dir, "addons/plugins")) + + try: + logger.info(f"删除临时更新文件: {zip_path} 和 {os.path.join(target_dir, update_dir)}") + shutil.rmtree(os.path.join(target_dir, update_dir), onerror=on_error) + os.remove(zip_path) + except: + logger.warn(f"删除更新文件失败,可以手动删除 {zip_path} 和 {os.path.join(target_dir, update_dir)}")