检查更新支持webui更新 (#1925)

*  检查更新支持webui跟新

* 🎨 移除无用导入
This commit is contained in:
HibiKier 2025-07-11 10:11:14 +08:00 committed by GitHub
parent 4bcc5aeea5
commit acfed0837a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 90 additions and 34 deletions

View File

@ -29,6 +29,7 @@
"unban", "unban",
"Uninfo", "Uninfo",
"userinfo", "userinfo",
"webui",
"zhenxun" "zhenxun"
], ],
"python.analysis.autoImportCompletions": true, "python.analysis.autoImportCompletions": true,

View File

@ -13,7 +13,11 @@ from pytest_mock import MockerFixture
from respx import MockRouter from respx import MockRouter
from tests.config import BotId, GroupId, MessageId, UserId 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 from tests.utils import get_response_json as _get_response_json
@ -311,6 +315,12 @@ async def test_check_update_release(
to_me=True, to_me=True,
) )
ctx.receive_event(bot, event) 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( ctx.should_call_api(
"send_msg", "send_msg",
_v11_private_message_send( _v11_private_message_send(
@ -401,6 +411,12 @@ async def test_check_update_main(
to_me=True, to_me=True,
) )
ctx.receive_event(bot, event) 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( ctx.should_call_api(
"send_msg", "send_msg",
_v11_private_message_send( _v11_private_message_send(

View File

@ -5,6 +5,10 @@ from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageSegme
from nonebot.adapters.onebot.v11.event import Sender 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: def get_response_json(base_path: Path, file: str) -> dict:
try: try:
return json.loads( return json.loads(

View File

@ -11,7 +11,7 @@ from nonebot_plugin_alconna import (
on_alconna, on_alconna,
store_true, store_true,
) )
from nonebot_plugin_session import EventSession from nonebot_plugin_uninfo import Uninfo
from zhenxun.configs.utils import PluginExtraData from zhenxun.configs.utils import PluginExtraData
from zhenxun.services.log import logger from zhenxun.services.log import logger
@ -22,7 +22,7 @@ from zhenxun.utils.manager.resource_manager import (
) )
from zhenxun.utils.message import MessageUtils from zhenxun.utils.message import MessageUtils
from ._data_source import UpdateManage from ._data_source import UpdateManager
__plugin_meta__ = PluginMetadata( __plugin_meta__ = PluginMetadata(
name="自动更新", name="自动更新",
@ -32,16 +32,18 @@ __plugin_meta__ = PluginMetadata(
检查更新真寻最新版本包括了自动更新 检查更新真寻最新版本包括了自动更新
资源文件大小一般在130mb左右除非必须更新一般仅更新代码文件 资源文件大小一般在130mb左右除非必须更新一般仅更新代码文件
指令 指令
检查更新 [main|release|resource] ?[-r] 检查更新 [main|release|resource|webui] ?[-r]
main: main分支 main: main分支
release: 最新release release: 最新release
resource: 资源文件 resource: 资源文件
webui: webui文件
-r: 下载资源文件一般在更新main或release时使用 -r: 下载资源文件一般在更新main或release时使用
示例: 示例:
检查更新 main 检查更新 main
检查更新 main -r 检查更新 main -r
检查更新 release -r 检查更新 release -r
检查更新 resource 检查更新 resource
检查更新 webui
""".strip(), """.strip(),
extra=PluginExtraData( extra=PluginExtraData(
author="HibiKier", author="HibiKier",
@ -53,7 +55,7 @@ __plugin_meta__ = PluginMetadata(
_matcher = on_alconna( _matcher = on_alconna(
Alconna( Alconna(
"检查更新", "检查更新",
Args["ver_type?", ["main", "release", "resource"]], Args["ver_type?", ["main", "release", "resource", "webui"]],
Option("-r|--resource", action=store_true, help_text="下载资源文件"), Option("-r|--resource", action=store_true, help_text="下载资源文件"),
), ),
priority=1, priority=1,
@ -66,23 +68,24 @@ _matcher = on_alconna(
@_matcher.handle() @_matcher.handle()
async def _( async def _(
bot: Bot, bot: Bot,
session: EventSession, session: Uninfo,
ver_type: Match[str], ver_type: Match[str],
resource: Query[bool] = Query("resource", False), resource: Query[bool] = Query("resource", False),
): ):
if not session.id1:
await MessageUtils.build_message("用户id为空...").finish()
result = "" result = ""
await MessageUtils.build_message("正在进行检查更新...").send(reply_to=True)
if ver_type.result in {"main", "release"}: if ver_type.result in {"main", "release"}:
if not ver_type.available: if not ver_type.available:
result = await UpdateManage.check_version() result = await UpdateManager.check_version()
logger.info("查看当前版本...", "检查更新", session=session) logger.info("查看当前版本...", "检查更新", session=session)
await MessageUtils.build_message(result).finish() await MessageUtils.build_message(result).finish()
try: 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: except Exception as e:
logger.error("版本更新失败...", "检查更新", session=session, e=e) logger.error("版本更新失败...", "检查更新", session=session, e=e)
await MessageUtils.build_message(f"更新版本失败...e: {e}").finish() 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": if resource.result or ver_type.result == "resource":
try: try:
await ResourceManager.init_resources(True) await ResourceManager.init_resources(True)

View File

@ -7,6 +7,7 @@ import zipfile
from nonebot.adapters import Bot from nonebot.adapters import Bot
from nonebot.utils import run_sync from nonebot.utils import run_sync
from zhenxun.configs.path_config import DATA_PATH
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.github_utils import GithubUtils from zhenxun.utils.github_utils import GithubUtils
from zhenxun.utils.github_utils.models import RepoInfo from zhenxun.utils.github_utils.models import RepoInfo
@ -17,6 +18,7 @@ from .config import (
BACKUP_PATH, BACKUP_PATH,
BASE_PATH, BASE_PATH,
BASE_PATH_STRING, BASE_PATH_STRING,
COMMAND,
DEFAULT_GITHUB_URL, DEFAULT_GITHUB_URL,
DOWNLOAD_GZ_FILE, DOWNLOAD_GZ_FILE,
DOWNLOAD_ZIP_FILE, DOWNLOAD_ZIP_FILE,
@ -38,7 +40,7 @@ def install_requirement():
if not requirement_path.exists(): if not requirement_path.exists():
logger.debug( logger.debug(
f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", "插件管理" f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", COMMAND
) )
return return
try: try:
@ -48,9 +50,9 @@ def install_requirement():
capture_output=True, capture_output=True,
text=True, text=True,
) )
logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", "插件管理") logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", COMMAND)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", "插件管理", e=e) logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", COMMAND, e=e)
@run_sync @run_sync
@ -61,7 +63,7 @@ def _file_handle(latest_version: str | None):
latest_version: 版本号 latest_version: 版本号
""" """
BACKUP_PATH.mkdir(exist_ok=True, parents=True) BACKUP_PATH.mkdir(exist_ok=True, parents=True)
logger.debug("开始解压文件压缩包...", "检查更新") logger.debug("开始解压文件压缩包...", COMMAND)
download_file = DOWNLOAD_GZ_FILE download_file = DOWNLOAD_GZ_FILE
if DOWNLOAD_GZ_FILE.exists(): if DOWNLOAD_GZ_FILE.exists():
tf = tarfile.open(DOWNLOAD_GZ_FILE) tf = tarfile.open(DOWNLOAD_GZ_FILE)
@ -69,7 +71,7 @@ def _file_handle(latest_version: str | None):
download_file = DOWNLOAD_ZIP_FILE download_file = DOWNLOAD_ZIP_FILE
tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE) tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE)
tf.extractall(TMP_PATH) tf.extractall(TMP_PATH)
logger.debug("解压文件压缩包完成...", "检查更新") logger.debug("解压文件压缩包完成...", COMMAND)
download_file_path = TMP_PATH / next( download_file_path = TMP_PATH / next(
x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir() 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 extract_path = download_file_path / BASE_PATH_STRING
target_path = BASE_PATH target_path = BASE_PATH
if PYPROJECT_FILE.exists(): if PYPROJECT_FILE.exists():
logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新") logger.debug(f"移除备份文件: {PYPROJECT_FILE}", COMMAND)
shutil.move(PYPROJECT_FILE, BACKUP_PATH / PYPROJECT_FILE_STRING) shutil.move(PYPROJECT_FILE, BACKUP_PATH / PYPROJECT_FILE_STRING)
if PYPROJECT_LOCK_FILE.exists(): 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) shutil.move(PYPROJECT_LOCK_FILE, BACKUP_PATH / PYPROJECT_LOCK_FILE_STRING)
if REQ_TXT_FILE.exists(): 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) shutil.move(REQ_TXT_FILE, BACKUP_PATH / REQ_TXT_FILE_STRING)
if _pyproject.exists(): if _pyproject.exists():
logger.debug("移动文件: pyproject.toml", "检查更新") logger.debug("移动文件: pyproject.toml", COMMAND)
shutil.move(_pyproject, PYPROJECT_FILE) shutil.move(_pyproject, PYPROJECT_FILE)
if _lock_file.exists(): if _lock_file.exists():
logger.debug("移动文件: poetry.lock", "检查更新") logger.debug("移动文件: poetry.lock", COMMAND)
shutil.move(_lock_file, PYPROJECT_LOCK_FILE) shutil.move(_lock_file, PYPROJECT_LOCK_FILE)
if _req_file.exists(): if _req_file.exists():
logger.debug("移动文件: requirements.txt", "检查更新") logger.debug("移动文件: requirements.txt", COMMAND)
shutil.move(_req_file, REQ_TXT_FILE) shutil.move(_req_file, REQ_TXT_FILE)
for folder in REPLACE_FOLDERS: for folder in REPLACE_FOLDERS:
"""移动指定文件夹""" """移动指定文件夹"""
_dir = BASE_PATH / folder _dir = BASE_PATH / folder
_backup_dir = BACKUP_PATH / folder _backup_dir = BACKUP_PATH / folder
if _backup_dir.exists(): if _backup_dir.exists():
logger.debug(f"删除备份文件夹 {_backup_dir}", "检查更新") logger.debug(f"删除备份文件夹 {_backup_dir}", COMMAND)
shutil.rmtree(_backup_dir) shutil.rmtree(_backup_dir)
if _dir.exists(): if _dir.exists():
logger.debug(f"移动旧文件夹 {_dir}", "检查更新") logger.debug(f"移动旧文件夹 {_dir}", COMMAND)
shutil.move(_dir, _backup_dir) shutil.move(_dir, _backup_dir)
else: else:
logger.warning(f"文件夹 {_dir} 不存在,跳过删除", "检查更新") logger.warning(f"文件夹 {_dir} 不存在,跳过删除", COMMAND)
for folder in REPLACE_FOLDERS: for folder in REPLACE_FOLDERS:
src_folder_path = extract_path / folder src_folder_path = extract_path / folder
dest_folder_path = target_path / folder dest_folder_path = target_path / folder
if src_folder_path.exists(): if src_folder_path.exists():
logger.debug( 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) shutil.move(src_folder_path, dest_folder_path)
else: else:
logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新") logger.debug(f"源文件夹不存在: {src_folder_path}", COMMAND)
if tf: if tf:
tf.close() tf.close()
if download_file.exists(): if download_file.exists():
logger.debug(f"删除下载文件: {download_file}", "检查更新") logger.debug(f"删除下载文件: {download_file}", COMMAND)
download_file.unlink() download_file.unlink()
if extract_path.exists(): if extract_path.exists():
logger.debug(f"删除解压文件夹: {extract_path}", "检查更新") logger.debug(f"删除解压文件夹: {extract_path}", COMMAND)
shutil.rmtree(extract_path) shutil.rmtree(extract_path)
if TMP_PATH.exists(): if TMP_PATH.exists():
shutil.rmtree(TMP_PATH) shutil.rmtree(TMP_PATH)
@ -134,7 +136,35 @@ def _file_handle(latest_version: str | None):
install_requirement() 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 @classmethod
async def check_version(cls) -> str: async def check_version(cls) -> str:
"""检查更新版本 """检查更新版本
@ -166,7 +196,7 @@ class UpdateManage:
返回: 返回:
str | None: 返回消息 str | None: 返回消息
""" """
logger.info("开始下载真寻最新版文件....", "检查更新") logger.info("开始下载真寻最新版文件....", COMMAND)
cur_version = cls.__get_version() cur_version = cls.__get_version()
url = None url = None
new_version = None new_version = None
@ -186,11 +216,11 @@ class UpdateManage:
if not url: if not url:
return "获取版本下载链接失败..." return "获取版本下载链接失败..."
if TMP_PATH.exists(): if TMP_PATH.exists():
logger.debug(f"删除临时文件夹 {TMP_PATH}", "检查更新") logger.debug(f"删除临时文件夹 {TMP_PATH}", COMMAND)
shutil.rmtree(TMP_PATH) shutil.rmtree(TMP_PATH)
logger.debug( logger.debug(
f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}", f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}",
"检查更新", COMMAND,
) )
await PlatformUtils.send_superuser( await PlatformUtils.send_superuser(
bot, bot,
@ -201,7 +231,7 @@ class UpdateManage:
DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE
) )
if await AsyncHttpx.download_file(url, download_file, stream=True): if await AsyncHttpx.download_file(url, download_file, stream=True):
logger.debug("下载真寻最新版文件完成...", "检查更新") logger.debug("下载真寻最新版文件完成...", COMMAND)
await _file_handle(new_version) await _file_handle(new_version)
result = "版本更新完成" result = "版本更新完成"
return ( return (
@ -210,7 +240,7 @@ class UpdateManage:
"请重新启动真寻以完成更新!" "请重新启动真寻以完成更新!"
) )
else: else:
logger.debug("下载真寻最新版文件失败...", "检查更新") logger.debug("下载真寻最新版文件失败...", COMMAND)
return "" return ""
@classmethod @classmethod

View File

@ -34,3 +34,5 @@ REPLACE_FOLDERS = [
"models", "models",
"configs", "configs",
] ]
COMMAND = "检查更新"