From acfed0837a794fc6414a89b335f6f95606cb93b2 Mon Sep 17 00:00:00 2001 From: HibiKier <45528451+HibiKier@users.noreply.github.com> Date: Fri, 11 Jul 2025 10:11:14 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20=E6=A3=80=E6=9F=A5=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E6=94=AF=E6=8C=81webui=E6=9B=B4=E6=96=B0=20(#1925)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :sparkles: 检查更新支持webui跟新 * :art: 移除无用导入 --- .vscode/settings.json | 1 + .../auto_update/test_check_update.py | 18 ++++- tests/utils.py | 4 + .../builtin_plugins/auto_update/__init__.py | 21 ++--- .../auto_update/_data_source.py | 78 +++++++++++++------ zhenxun/builtin_plugins/auto_update/config.py | 2 + 6 files changed, 90 insertions(+), 34 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e6830243..7f4310a9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -29,6 +29,7 @@ "unban", "Uninfo", "userinfo", + "webui", "zhenxun" ], "python.analysis.autoImportCompletions": true, diff --git a/tests/builtin_plugins/auto_update/test_check_update.py b/tests/builtin_plugins/auto_update/test_check_update.py index 0555806c..8a505401 100644 --- a/tests/builtin_plugins/auto_update/test_check_update.py +++ b/tests/builtin_plugins/auto_update/test_check_update.py @@ -13,7 +13,11 @@ from pytest_mock import MockerFixture from respx import MockRouter from tests.config import BotId, GroupId, MessageId, UserId -from tests.utils import _v11_group_message_event, _v11_private_message_send +from tests.utils import ( + _v11_group_message_event, + _v11_private_message_send, + get_reply_cq, +) from tests.utils import get_response_json as _get_response_json @@ -311,6 +315,12 @@ async def test_check_update_release( to_me=True, ) ctx.receive_event(bot, event) + ctx.should_call_send( + event=event, + message=Message(f"{get_reply_cq(MessageId.MESSAGE_ID)}正在进行检查更新..."), + result=None, + bot=bot, + ) ctx.should_call_api( "send_msg", _v11_private_message_send( @@ -401,6 +411,12 @@ async def test_check_update_main( to_me=True, ) ctx.receive_event(bot, event) + ctx.should_call_send( + event=event, + message=Message(f"{get_reply_cq(MessageId.MESSAGE_ID)}正在进行检查更新..."), + result=None, + bot=bot, + ) ctx.should_call_api( "send_msg", _v11_private_message_send( diff --git a/tests/utils.py b/tests/utils.py index f05aa8e9..9ec6af24 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -5,6 +5,10 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegme from nonebot.adapters.onebot.v11.event import Sender +def get_reply_cq(uid: int | str) -> str: + return f"[CQ:reply,id={uid}]" + + def get_response_json(base_path: Path, file: str) -> dict: try: return json.loads( diff --git a/zhenxun/builtin_plugins/auto_update/__init__.py b/zhenxun/builtin_plugins/auto_update/__init__.py index 3d906e1e..764fc39c 100644 --- a/zhenxun/builtin_plugins/auto_update/__init__.py +++ b/zhenxun/builtin_plugins/auto_update/__init__.py @@ -11,7 +11,7 @@ from nonebot_plugin_alconna import ( on_alconna, store_true, ) -from nonebot_plugin_session import EventSession +from nonebot_plugin_uninfo import Uninfo from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger @@ -22,7 +22,7 @@ from zhenxun.utils.manager.resource_manager import ( ) from zhenxun.utils.message import MessageUtils -from ._data_source import UpdateManage +from ._data_source import UpdateManager __plugin_meta__ = PluginMetadata( name="自动更新", @@ -32,16 +32,18 @@ __plugin_meta__ = PluginMetadata( 检查更新真寻最新版本,包括了自动更新 资源文件大小一般在130mb左右,除非必须更新一般仅更新代码文件 指令: - 检查更新 [main|release|resource] ?[-r] + 检查更新 [main|release|resource|webui] ?[-r] main: main分支 release: 最新release resource: 资源文件 + webui: webui文件 -r: 下载资源文件,一般在更新main或release时使用 示例: 检查更新 main 检查更新 main -r 检查更新 release -r 检查更新 resource + 检查更新 webui """.strip(), extra=PluginExtraData( author="HibiKier", @@ -53,7 +55,7 @@ __plugin_meta__ = PluginMetadata( _matcher = on_alconna( Alconna( "检查更新", - Args["ver_type?", ["main", "release", "resource"]], + Args["ver_type?", ["main", "release", "resource", "webui"]], Option("-r|--resource", action=store_true, help_text="下载资源文件"), ), priority=1, @@ -66,23 +68,24 @@ _matcher = on_alconna( @_matcher.handle() async def _( bot: Bot, - session: EventSession, + session: Uninfo, ver_type: Match[str], resource: Query[bool] = Query("resource", False), ): - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() result = "" + await MessageUtils.build_message("正在进行检查更新...").send(reply_to=True) if ver_type.result in {"main", "release"}: if not ver_type.available: - result = await UpdateManage.check_version() + result = await UpdateManager.check_version() logger.info("查看当前版本...", "检查更新", session=session) await MessageUtils.build_message(result).finish() try: - result = await UpdateManage.update(bot, session.id1, ver_type.result) + result = await UpdateManager.update(bot, session.user.id, ver_type.result) except Exception as e: logger.error("版本更新失败...", "检查更新", session=session, e=e) await MessageUtils.build_message(f"更新版本失败...e: {e}").finish() + elif ver_type.result == "webui": + result = await UpdateManager.update_webui() if resource.result or ver_type.result == "resource": try: await ResourceManager.init_resources(True) diff --git a/zhenxun/builtin_plugins/auto_update/_data_source.py b/zhenxun/builtin_plugins/auto_update/_data_source.py index 5c7a1df8..5fbeaa5d 100644 --- a/zhenxun/builtin_plugins/auto_update/_data_source.py +++ b/zhenxun/builtin_plugins/auto_update/_data_source.py @@ -7,6 +7,7 @@ import zipfile from nonebot.adapters import Bot from nonebot.utils import run_sync +from zhenxun.configs.path_config import DATA_PATH from zhenxun.services.log import logger from zhenxun.utils.github_utils import GithubUtils from zhenxun.utils.github_utils.models import RepoInfo @@ -17,6 +18,7 @@ from .config import ( BACKUP_PATH, BASE_PATH, BASE_PATH_STRING, + COMMAND, DEFAULT_GITHUB_URL, DOWNLOAD_GZ_FILE, DOWNLOAD_ZIP_FILE, @@ -38,7 +40,7 @@ def install_requirement(): if not requirement_path.exists(): logger.debug( - f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", "插件管理" + f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", COMMAND ) return try: @@ -48,9 +50,9 @@ def install_requirement(): capture_output=True, text=True, ) - logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", "插件管理") + logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", COMMAND) except subprocess.CalledProcessError as e: - logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", "插件管理", e=e) + logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", COMMAND, e=e) @run_sync @@ -61,7 +63,7 @@ def _file_handle(latest_version: str | None): latest_version: 版本号 """ BACKUP_PATH.mkdir(exist_ok=True, parents=True) - logger.debug("开始解压文件压缩包...", "检查更新") + logger.debug("开始解压文件压缩包...", COMMAND) download_file = DOWNLOAD_GZ_FILE if DOWNLOAD_GZ_FILE.exists(): tf = tarfile.open(DOWNLOAD_GZ_FILE) @@ -69,7 +71,7 @@ def _file_handle(latest_version: str | None): download_file = DOWNLOAD_ZIP_FILE tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE) tf.extractall(TMP_PATH) - logger.debug("解压文件压缩包完成...", "检查更新") + logger.debug("解压文件压缩包完成...", COMMAND) download_file_path = TMP_PATH / next( x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir() ) @@ -79,52 +81,52 @@ def _file_handle(latest_version: str | None): extract_path = download_file_path / BASE_PATH_STRING target_path = BASE_PATH if PYPROJECT_FILE.exists(): - logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新") + logger.debug(f"移除备份文件: {PYPROJECT_FILE}", COMMAND) shutil.move(PYPROJECT_FILE, BACKUP_PATH / PYPROJECT_FILE_STRING) if PYPROJECT_LOCK_FILE.exists(): - logger.debug(f"移除备份文件: {PYPROJECT_LOCK_FILE}", "检查更新") + logger.debug(f"移除备份文件: {PYPROJECT_LOCK_FILE}", COMMAND) shutil.move(PYPROJECT_LOCK_FILE, BACKUP_PATH / PYPROJECT_LOCK_FILE_STRING) if REQ_TXT_FILE.exists(): - logger.debug(f"移除备份文件: {REQ_TXT_FILE}", "检查更新") + logger.debug(f"移除备份文件: {REQ_TXT_FILE}", COMMAND) shutil.move(REQ_TXT_FILE, BACKUP_PATH / REQ_TXT_FILE_STRING) if _pyproject.exists(): - logger.debug("移动文件: pyproject.toml", "检查更新") + logger.debug("移动文件: pyproject.toml", COMMAND) shutil.move(_pyproject, PYPROJECT_FILE) if _lock_file.exists(): - logger.debug("移动文件: poetry.lock", "检查更新") + logger.debug("移动文件: poetry.lock", COMMAND) shutil.move(_lock_file, PYPROJECT_LOCK_FILE) if _req_file.exists(): - logger.debug("移动文件: requirements.txt", "检查更新") + logger.debug("移动文件: requirements.txt", COMMAND) shutil.move(_req_file, REQ_TXT_FILE) for folder in REPLACE_FOLDERS: """移动指定文件夹""" _dir = BASE_PATH / folder _backup_dir = BACKUP_PATH / folder if _backup_dir.exists(): - logger.debug(f"删除备份文件夹 {_backup_dir}", "检查更新") + logger.debug(f"删除备份文件夹 {_backup_dir}", COMMAND) shutil.rmtree(_backup_dir) if _dir.exists(): - logger.debug(f"移动旧文件夹 {_dir}", "检查更新") + logger.debug(f"移动旧文件夹 {_dir}", COMMAND) shutil.move(_dir, _backup_dir) else: - logger.warning(f"文件夹 {_dir} 不存在,跳过删除", "检查更新") + logger.warning(f"文件夹 {_dir} 不存在,跳过删除", COMMAND) for folder in REPLACE_FOLDERS: src_folder_path = extract_path / folder dest_folder_path = target_path / folder if src_folder_path.exists(): logger.debug( - f"移动文件夹: {src_folder_path} -> {dest_folder_path}", "检查更新" + f"移动文件夹: {src_folder_path} -> {dest_folder_path}", COMMAND ) shutil.move(src_folder_path, dest_folder_path) else: - logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新") + logger.debug(f"源文件夹不存在: {src_folder_path}", COMMAND) if tf: tf.close() if download_file.exists(): - logger.debug(f"删除下载文件: {download_file}", "检查更新") + logger.debug(f"删除下载文件: {download_file}", COMMAND) download_file.unlink() if extract_path.exists(): - logger.debug(f"删除解压文件夹: {extract_path}", "检查更新") + logger.debug(f"删除解压文件夹: {extract_path}", COMMAND) shutil.rmtree(extract_path) if TMP_PATH.exists(): shutil.rmtree(TMP_PATH) @@ -134,7 +136,35 @@ def _file_handle(latest_version: str | None): install_requirement() -class UpdateManage: +class UpdateManager: + @classmethod + async def update_webui(cls) -> str: + from zhenxun.builtin_plugins.web_ui.public.data_source import ( + update_webui_assets, + ) + + WEBUI_PATH = DATA_PATH / "web_ui" / "public" + BACKUP_PATH = DATA_PATH / "web_ui" / "backup_public" + if WEBUI_PATH.exists(): + if BACKUP_PATH.exists(): + logger.debug(f"删除旧的备份webui文件夹 {BACKUP_PATH}", COMMAND) + shutil.rmtree(BACKUP_PATH) + WEBUI_PATH.rename(BACKUP_PATH) + try: + await update_webui_assets() + logger.info("更新webui成功...", COMMAND) + if BACKUP_PATH.exists(): + logger.debug(f"删除旧的webui文件夹 {BACKUP_PATH}", COMMAND) + shutil.rmtree(BACKUP_PATH) + return "Webui更新成功!" + except Exception as e: + logger.error("更新webui失败...", COMMAND, e=e) + if BACKUP_PATH.exists(): + logger.debug(f"恢复旧的webui文件夹 {BACKUP_PATH}", COMMAND) + BACKUP_PATH.rename(WEBUI_PATH) + raise e + return "" + @classmethod async def check_version(cls) -> str: """检查更新版本 @@ -166,7 +196,7 @@ class UpdateManage: 返回: str | None: 返回消息 """ - logger.info("开始下载真寻最新版文件....", "检查更新") + logger.info("开始下载真寻最新版文件....", COMMAND) cur_version = cls.__get_version() url = None new_version = None @@ -186,11 +216,11 @@ class UpdateManage: if not url: return "获取版本下载链接失败..." if TMP_PATH.exists(): - logger.debug(f"删除临时文件夹 {TMP_PATH}", "检查更新") + logger.debug(f"删除临时文件夹 {TMP_PATH}", COMMAND) shutil.rmtree(TMP_PATH) logger.debug( f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}", - "检查更新", + COMMAND, ) await PlatformUtils.send_superuser( bot, @@ -201,7 +231,7 @@ class UpdateManage: DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE ) if await AsyncHttpx.download_file(url, download_file, stream=True): - logger.debug("下载真寻最新版文件完成...", "检查更新") + logger.debug("下载真寻最新版文件完成...", COMMAND) await _file_handle(new_version) result = "版本更新完成" return ( @@ -210,7 +240,7 @@ class UpdateManage: "请重新启动真寻以完成更新!" ) else: - logger.debug("下载真寻最新版文件失败...", "检查更新") + logger.debug("下载真寻最新版文件失败...", COMMAND) return "" @classmethod diff --git a/zhenxun/builtin_plugins/auto_update/config.py b/zhenxun/builtin_plugins/auto_update/config.py index b4c61345..1516b5e6 100644 --- a/zhenxun/builtin_plugins/auto_update/config.py +++ b/zhenxun/builtin_plugins/auto_update/config.py @@ -34,3 +34,5 @@ REPLACE_FOLDERS = [ "models", "configs", ] + +COMMAND = "检查更新"