This commit is contained in:
Rumio 2025-09-08 12:23:57 +08:00 committed by GitHub
commit f2d14568a5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 212 additions and 20 deletions

View File

@ -84,13 +84,16 @@ async def _(
): ):
result = "" result = ""
await MessageUtils.build_message("正在进行检查更新...").send(reply_to=True) 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 ver_type_str = ver_type.result
source_str = source.result source_str = source.result
if ver_type_str in {"main", "release"}: 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: try:
result += await UpdateManager.update_zhenxun( result += await UpdateManager.update_zhenxun(
bot, bot,

View File

@ -1,6 +1,9 @@
import asyncio
from typing import Literal from typing import Literal
from nonebot.adapters import Bot from nonebot.adapters import Bot
from packaging.specifiers import SpecifierSet
from packaging.version import InvalidVersion, Version
from zhenxun.services.log import logger from zhenxun.services.log import logger
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
@ -9,6 +12,7 @@ from zhenxun.utils.manager.zhenxun_repo_manager import (
ZhenxunRepoManager, ZhenxunRepoManager,
) )
from zhenxun.utils.platform import PlatformUtils from zhenxun.utils.platform import PlatformUtils
from zhenxun.utils.repo_utils import RepoFileManager
LOG_COMMAND = "AutoUpdate" LOG_COMMAND = "AutoUpdate"
@ -16,22 +20,101 @@ LOG_COMMAND = "AutoUpdate"
class UpdateManager: class UpdateManager:
@classmethod @classmethod
async def check_version(cls) -> str: async def check_version(cls) -> str:
"""检查更新版本 """检查真寻和资源的版本"""
bot_cur_version = cls.__get_version()
返回: release_task = ZhenxunRepoManager.zhenxun_get_latest_releases_data()
str: 更新信息 dev_version_task = RepoFileManager.get_file_content(
""" ZhenxunRepoConfig.ZHENXUN_BOT_GITHUB_URL, "__version__"
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')}"
) )
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 @classmethod
async def update_webui( async def update_webui(
@ -125,6 +208,7 @@ class UpdateManager:
f"检测真寻已更新,当前版本:{cur_version}\n开始更新...", f"检测真寻已更新,当前版本:{cur_version}\n开始更新...",
user_id, user_id,
) )
result_message = ""
if zip: if zip:
new_version = await ZhenxunRepoManager.zhenxun_zip_update(version_type) new_version = await ZhenxunRepoManager.zhenxun_zip_update(version_type)
await PlatformUtils.send_superuser( await PlatformUtils.send_superuser(
@ -133,7 +217,7 @@ class UpdateManager:
await VirtualEnvPackageManager.install_requirement( await VirtualEnvPackageManager.install_requirement(
ZhenxunRepoConfig.REQUIREMENTS_FILE ZhenxunRepoConfig.REQUIREMENTS_FILE
) )
return ( result_message = (
f"版本更新完成!\n版本: {cur_version} -> {new_version}\n" f"版本更新完成!\n版本: {cur_version} -> {new_version}\n"
"请重新启动真寻以完成更新!" "请重新启动真寻以完成更新!"
) )
@ -155,13 +239,54 @@ class UpdateManager:
await VirtualEnvPackageManager.install_requirement( await VirtualEnvPackageManager.install_requirement(
ZhenxunRepoConfig.REQUIREMENTS_FILE ZhenxunRepoConfig.REQUIREMENTS_FILE
) )
return ( result_message = (
f"版本更新完成!\n" f"版本更新完成!\n"
f"版本: {cur_version} -> {result.new_version}\n" f"版本: {cur_version} -> {result.new_version}\n"
f"变更文件个数: {len(result.changed_files)}" f"变更文件个数: {len(result.changed_files)}"
f"{'' if source == 'git' else '(阿里云更新不支持查看变更文件)'}\n" 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 @classmethod
def __get_version(cls) -> str: 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_FORMAT = "https://api.github.com/repos/{owner}/{repo}/commits/{branch}"
"""git api commit地址格式""" """git api commit地址格式"""
GIT_API_COMMIT_LIST_FORMAT = "https://api.github.com/repos/{owner}/{repo}/commits"
"""git api 列出commits的地址格式"""
GIT_API_PROXY_COMMIT_FORMAT = ( GIT_API_PROXY_COMMIT_FORMAT = (
"https://git-api.zhenxun.org/repos/{owner}/{repo}/commits/{branch}" "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: if not self.config.aliyun_codeup.organization_id:
raise AuthenticationError("阿里云CodeUp") 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: async def _get_newest_commit(self, repo_name: str, branch: str) -> str:
""" """
获取仓库最新提交ID 获取仓库最新提交ID

View File

@ -117,6 +117,20 @@ class BaseRepoManager(ABC):
""" """
pass 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: 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.services.log import logger
from zhenxun.utils.github_utils import GithubUtils 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.github_utils.models import AliyunTreeType, GitHubStrategy, TreeType
from zhenxun.utils.http_utils import AsyncHttpx from zhenxun.utils.http_utils import AsyncHttpx
from zhenxun.utils.utils import is_binary_file from zhenxun.utils.utils import is_binary_file
@ -633,3 +634,38 @@ class RepoFileManager:
result.success = False result.success = False
result.error_message = str(e) result.error_message = str(e)
return result 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) logger.error("获取提交信息失败", LOG_COMMAND, e=e)
return None 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: async def _get_newest_commit(self, owner: str, repo: str, branch: str) -> str:
""" """
获取仓库最新提交ID 获取仓库最新提交ID