Compare commits

...

4 Commits

Author SHA1 Message Date
Rumio
6f7e9fc839
Merge b7864e535d into 74ce848127 2025-09-07 16:00:27 +00:00
pre-commit-ci[bot]
b7864e535d 🚨 auto fix by pre-commit hooks 2025-09-07 16:00:24 +00:00
webjoin111
c495c5d9b5 ♻️ refactor(repo): 统一仓库最新提交获取接口并迁移文件提交日期逻辑 2025-09-07 23:58:31 +08:00
webjoin111
e3d49c7105 feat(auto_update): 增强自动更新与版本检查
- 优化 `检查更新` 默认行为,未指定类型时直接显示版本信息
- 扩展版本详情显示:当前版本、最新开发版/正式版(含日期)、资源版本及更新提示
- 新增更新后资源兼容性检查,自动读取 `resources.spec` 并提示更新
- 使用 `asyncio.gather` 并发获取版本信息,引入 `packaging` 库提高比较准确性
- 优化错误处理与日志记录
2025-09-01 15:10:25 +08:00
7 changed files with 212 additions and 20 deletions

View File

@ -84,13 +84,16 @@ async def _(
):
result = ""
await MessageUtils.build_message("正在进行检查更新...").send(reply_to=True)
if not ver_type.available:
result += await UpdateManager.check_version()
logger.info("查看当前版本...", "检查更新", session=session)
await MessageUtils.build_message(result).finish()
return
ver_type_str = ver_type.result
source_str = source.result
if ver_type_str in {"main", "release"}:
if not ver_type.available:
result += await UpdateManager.check_version()
logger.info("查看当前版本...", "检查更新", session=session)
await MessageUtils.build_message(result).finish()
try:
result += await UpdateManager.update_zhenxun(
bot,

View File

@ -1,6 +1,9 @@
import asyncio
from typing import Literal
from nonebot.adapters import Bot
from packaging.specifiers import SpecifierSet
from packaging.version import InvalidVersion, Version
from zhenxun.services.log import logger
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
@ -9,6 +12,7 @@ from zhenxun.utils.manager.zhenxun_repo_manager import (
ZhenxunRepoManager,
)
from zhenxun.utils.platform import PlatformUtils
from zhenxun.utils.repo_utils import RepoFileManager
LOG_COMMAND = "AutoUpdate"
@ -16,22 +20,101 @@ LOG_COMMAND = "AutoUpdate"
class UpdateManager:
@classmethod
async def check_version(cls) -> str:
"""检查更新版本
"""检查真寻和资源的版本"""
bot_cur_version = cls.__get_version()
返回:
str: 更新信息
"""
cur_version = cls.__get_version()
release_data = await ZhenxunRepoManager.zhenxun_get_latest_releases_data()
if not release_data:
return "检查更新获取版本失败..."
return (
"检测到当前版本更新\n"
f"当前版本:{cur_version}\n"
f"最新版本:{release_data.get('name')}\n"
f"创建日期:{release_data.get('created_at')}\n"
f"更新内容:\n{release_data.get('body')}"
release_task = ZhenxunRepoManager.zhenxun_get_latest_releases_data()
dev_version_task = RepoFileManager.get_file_content(
ZhenxunRepoConfig.ZHENXUN_BOT_GITHUB_URL, "__version__"
)
bot_commit_date_task = RepoFileManager.get_file_last_commit_date(
ZhenxunRepoConfig.ZHENXUN_BOT_GITHUB_URL, "__version__"
)
res_commit_date_task = RepoFileManager.get_file_last_commit_date(
ZhenxunRepoConfig.RESOURCE_GITHUB_URL, "__version__"
)
(
release_data,
dev_version_text,
bot_commit_date,
res_commit_date,
) = await asyncio.gather(
release_task,
dev_version_task,
bot_commit_date_task,
res_commit_date_task,
return_exceptions=True,
)
if isinstance(release_data, dict):
bot_release_version = release_data.get("name", "获取失败")
bot_release_date = release_data.get("created_at", "").split("T")[0]
else:
bot_release_version = "获取失败"
bot_release_date = "获取失败"
logger.warning(f"获取 Bot release 信息失败: {release_data}")
if isinstance(dev_version_text, str):
bot_dev_version = dev_version_text.split(":")[-1].strip()
else:
bot_dev_version = "获取失败"
bot_commit_date = "获取失败"
logger.warning(f"获取 Bot dev 版本信息失败: {dev_version_text}")
bot_update_hint = ""
try:
cur_base_v = bot_cur_version.split("-")[0].lstrip("v")
dev_base_v = bot_dev_version.split("-")[0].lstrip("v")
if Version(cur_base_v) < Version(dev_base_v):
bot_update_hint = "\n-> 发现新开发版本, 可用 `检查更新 main` 更新"
elif (
Version(cur_base_v) == Version(dev_base_v)
and bot_cur_version != bot_dev_version
):
bot_update_hint = "\n-> 发现新开发版本, 可用 `检查更新 main` 更新"
except (InvalidVersion, TypeError, IndexError):
if bot_cur_version != bot_dev_version and bot_dev_version != "获取失败":
bot_update_hint = "\n-> 发现新开发版本, 可用 `检查更新 main` 更新"
bot_update_info = (
f"当前版本: {bot_cur_version}\n"
f"最新开发版: {bot_dev_version} (更新于: {bot_commit_date})\n"
f"最新正式版: {bot_release_version} (发布于: {bot_release_date})"
f"{bot_update_hint}"
)
res_version_file = ZhenxunRepoConfig.RESOURCE_PATH / "__version__"
res_cur_version = "未找到"
if res_version_file.exists():
if text := res_version_file.open(encoding="utf8").readline():
res_cur_version = text.split(":")[-1].strip()
res_latest_version = "获取失败"
try:
res_latest_version_text = await RepoFileManager.get_file_content(
ZhenxunRepoConfig.RESOURCE_GITHUB_URL, "__version__"
)
res_latest_version = res_latest_version_text.split(":")[-1].strip()
except Exception as e:
res_commit_date = "获取失败"
logger.warning(f"获取资源版本信息失败: {e}")
res_update_hint = ""
try:
if Version(res_cur_version) < Version(res_latest_version):
res_update_hint = "\n-> 发现新资源版本, 可用 `检查更新 resource` 更新"
except (InvalidVersion, TypeError):
pass
res_update_info = (
f"当前版本: {res_cur_version}\n"
f"最新版本: {res_latest_version} (更新于: {res_commit_date})"
f"{res_update_hint}"
)
return f"『绪山真寻 Bot』\n{bot_update_info}\n\n『真寻资源』\n{res_update_info}"
@classmethod
async def update_webui(
@ -125,6 +208,7 @@ class UpdateManager:
f"检测真寻已更新,当前版本:{cur_version}\n开始更新...",
user_id,
)
result_message = ""
if zip:
new_version = await ZhenxunRepoManager.zhenxun_zip_update(version_type)
await PlatformUtils.send_superuser(
@ -133,7 +217,7 @@ class UpdateManager:
await VirtualEnvPackageManager.install_requirement(
ZhenxunRepoConfig.REQUIREMENTS_FILE
)
return (
result_message = (
f"版本更新完成!\n版本: {cur_version} -> {new_version}\n"
"请重新启动真寻以完成更新!"
)
@ -155,13 +239,54 @@ class UpdateManager:
await VirtualEnvPackageManager.install_requirement(
ZhenxunRepoConfig.REQUIREMENTS_FILE
)
return (
result_message = (
f"版本更新完成!\n"
f"版本: {cur_version} -> {result.new_version}\n"
f"变更文件个数: {len(result.changed_files)}"
f"{'' if source == 'git' else '(阿里云更新不支持查看变更文件)'}\n"
"请重新启动真寻以完成更新!"
)
resource_warning = ""
if version_type == "main":
try:
spec_content = await RepoFileManager.get_file_content(
ZhenxunRepoConfig.ZHENXUN_BOT_GITHUB_URL, "resources.spec"
)
required_spec_str = None
for line in spec_content.splitlines():
if line.startswith("require_resources_version:"):
required_spec_str = line.split(":", 1)[1].strip().strip("\"'")
break
if required_spec_str:
res_version_file = ZhenxunRepoConfig.RESOURCE_PATH / "__version__"
local_res_version_str = "0.0.0"
if res_version_file.exists():
if text := res_version_file.open(encoding="utf8").readline():
local_res_version_str = text.split(":")[-1].strip()
spec = SpecifierSet(required_spec_str)
local_ver = Version(local_res_version_str)
if not spec.contains(local_ver):
warning_header = (
f"⚠️ **资源版本不兼容!**\n"
f"当前代码需要资源版本: `{required_spec_str}`\n"
f"您当前的资源版本是: `{local_res_version_str}`\n"
"**将自动为您更新资源文件...**"
)
await PlatformUtils.send_superuser(bot, warning_header, user_id)
resource_update_source = None if zip else source
resource_update_result = await cls.update_resources(
source=resource_update_source, force=force
)
resource_warning = (
f"\n\n{warning_header}\n{resource_update_result}"
)
except Exception as e:
logger.warning(f"检查资源版本兼容性时出错: {e}", LOG_COMMAND, e=e)
resource_warning = (
"\n\n⚠️ 检查资源版本兼容性时出错,建议手动运行 `检查更新 resource`"
)
return result_message + resource_warning
@classmethod
def __get_version(cls) -> str:

View File

@ -40,6 +40,9 @@ RELEASE_SOURCE_FORMAT = (
GIT_API_COMMIT_FORMAT = "https://api.github.com/repos/{owner}/{repo}/commits/{branch}"
"""git api commit地址格式"""
GIT_API_COMMIT_LIST_FORMAT = "https://api.github.com/repos/{owner}/{repo}/commits"
"""git api 列出commits的地址格式"""
GIT_API_PROXY_COMMIT_FORMAT = (
"https://git-api.zhenxun.org/repos/{owner}/{repo}/commits/{branch}"
)

View File

@ -348,6 +348,11 @@ class AliyunCodeupManager(BaseRepoManager):
if not self.config.aliyun_codeup.organization_id:
raise AuthenticationError("阿里云CodeUp")
async def get_latest_commit(self, repo_url: str, branch: str = "main") -> str:
"""获取阿里云CodeUp仓库指定分支的最新提交哈希值。"""
repo_name = repo_url.split("/tree/")[0].split("/")[-1].replace(".git", "")
return await self._get_newest_commit(repo_name, branch)
async def _get_newest_commit(self, repo_name: str, branch: str) -> str:
"""
获取仓库最新提交ID

View File

@ -117,6 +117,20 @@ class BaseRepoManager(ABC):
"""
pass
@abstractmethod
async def get_latest_commit(self, repo_url: str, branch: str = "main") -> str:
"""
获取仓库指定分支的最新提交哈希值
参数:
repo_url: 仓库URL或名称
branch: 分支名称
返回:
str: 最新的提交哈希值
"""
pass
async def save_file_content(self, content: bytes, local_path: Path) -> int:
"""
保存文件内容

View File

@ -11,6 +11,7 @@ from httpx import Response
from zhenxun.services.log import logger
from zhenxun.utils.github_utils import GithubUtils
from zhenxun.utils.github_utils.const import GIT_API_COMMIT_LIST_FORMAT
from zhenxun.utils.github_utils.models import AliyunTreeType, GitHubStrategy, TreeType
from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.utils import is_binary_file
@ -633,3 +634,38 @@ class RepoFileManager:
result.success = False
result.error_message = str(e)
return result
async def get_file_last_commit_date(
self, repo_url: str, file_path: str
) -> str | None:
"""
获取 GitHub 仓库中指定文件的最新提交日期
参数:
repo_url: 仓库的URL
file_path: 文件在仓库中的路径
返回:
str | None: "YYYY-MM-DD" 格式的日期字符串如果失败则返回 None
"""
try:
repo_info = GithubUtils.parse_github_url(repo_url)
api_url = GIT_API_COMMIT_LIST_FORMAT.format(
owner=repo_info.owner, repo=repo_info.repo
)
params = {
"sha": repo_info.branch,
"path": file_path,
"page": 1,
"per_page": 1,
}
data = await AsyncHttpx.get_json(api_url, params=params)
if data and isinstance(data, list) and data[0]:
date_str = data[0]["commit"]["committer"]["date"]
return date_str.split("T")[0]
except Exception as e:
logger.warning(
f"获取 {repo_url}{file_path} 的 commit 日期失败", LOG_COMMAND, e=e
)
return None

View File

@ -320,6 +320,12 @@ class GithubManager(BaseRepoManager):
logger.error("获取提交信息失败", LOG_COMMAND, e=e)
return None
async def get_latest_commit(self, repo_url: str, branch: str = "main") -> str:
"""获取GitHub仓库指定分支的最新提交哈希值。"""
repo_info = GithubUtils.parse_github_url(repo_url)
repo_name = repo_info.repo.replace(".git", "")
return await self._get_newest_commit(repo_info.owner, repo_name, branch)
async def _get_newest_commit(self, owner: str, repo: str, branch: str) -> str:
"""
获取仓库最新提交ID