diff --git a/astrbot/core/utils/version_comparator.py b/astrbot/core/utils/version_comparator.py new file mode 100644 index 000000000..f7ad65fcd --- /dev/null +++ b/astrbot/core/utils/version_comparator.py @@ -0,0 +1,88 @@ +import re + + +class VersionComparator: + @staticmethod + def compare_version(v1: str, v2: str) -> int: + """根据 Semver 语义版本规范来比较版本号的大小。支持不仅局限于 3 个数字的版本号,并处理预发布标签。 + + 参考: https://semver.org/lang/zh-CN/ + + 返回 1 表示 v1 > v2,返回 -1 表示 v1 < v2,返回 0 表示 v1 = v2。 + """ + v1 = v1.lower().replace("v", "") + v2 = v2.lower().replace("v", "") + + def split_version(version): + match = re.match( + r"^([0-9]+(?:\.[0-9]+)*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(.+))?$", + version, + ) + if not match: + return [], None + major_minor_patch = match.group(1).split(".") + prerelease = match.group(2) + # buildmetadata = match.group(3) # 构建元数据在比较时忽略 + parts = [int(x) for x in major_minor_patch] + prerelease = VersionComparator._split_prerelease(prerelease) + return parts, prerelease + + v1_parts, v1_prerelease = split_version(v1) + v2_parts, v2_prerelease = split_version(v2) + + # 比较数字部分 + length = max(len(v1_parts), len(v2_parts)) + v1_parts.extend([0] * (length - len(v1_parts))) + v2_parts.extend([0] * (length - len(v2_parts))) + + for i in range(length): + if v1_parts[i] > v2_parts[i]: + return 1 + elif v1_parts[i] < v2_parts[i]: + return -1 + + # 比较预发布标签 + if v1_prerelease is None and v2_prerelease is not None: + return 1 # 没有预发布标签的版本高于有预发布标签的版本 + elif v1_prerelease is not None and v2_prerelease is None: + return -1 # 有预发布标签的版本低于没有预发布标签的版本 + elif v1_prerelease is not None and v2_prerelease is not None: + len_pre = max(len(v1_prerelease), len(v2_prerelease)) + for i in range(len_pre): + p1 = v1_prerelease[i] if i < len(v1_prerelease) else None + p2 = v2_prerelease[i] if i < len(v2_prerelease) else None + + if p1 is None and p2 is not None: + return -1 + elif p1 is not None and p2 is None: + return 1 + elif isinstance(p1, int) and isinstance(p2, str): + return -1 + elif isinstance(p1, str) and isinstance(p2, int): + return 1 + elif isinstance(p1, int) and isinstance(p2, int): + if p1 > p2: + return 1 + elif p1 < p2: + return -1 + elif isinstance(p1, str) and isinstance(p2, str): + if p1 > p2: + return 1 + elif p1 < p2: + return -1 + return 0 # 预发布标签完全相同 + + return 0 # 数字部分和预发布标签都相同 + + @staticmethod + def _split_prerelease(prerelease): + if not prerelease: + return None + parts = prerelease.split(".") + result = [] + for part in parts: + if part.isdigit(): + result.append(int(part)) + else: + result.append(part) + return result diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py index 844de428e..137c7444a 100644 --- a/astrbot/core/zip_updator.py +++ b/astrbot/core/zip_updator.py @@ -8,6 +8,7 @@ import certifi from astrbot.core.utils.io import on_error, download_file from astrbot.core import logger +from astrbot.core.utils.version_comparator import VersionComparator class ReleaseInfo: @@ -102,31 +103,10 @@ class RepoZipUpdator: raise NotImplementedError() def compare_version(self, v1: str, v2: str) -> int: - """ - 比较两个版本号的大小。 - 返回 1 表示 v1 > v2,返回 -1 表示 v1 < v2,返回 0 表示 v1 = v2。 - 支持任意长度的版本号,如v1.2.3或v3.5.3.1。 - """ - v1 = v1.replace("v", "") - v2 = v2.replace("v", "") - v1_parts = v1.split(".") - v2_parts = v2.split(".") + """Semver 版本比较""" + return VersionComparator.compare_version(v1, v2) - # 获取最长的版本号长度 - length = max(len(v1_parts), len(v2_parts)) - - # 将短版本号补0以便比较 - v1_parts.extend(["0"] * (length - len(v1_parts))) - v2_parts.extend(["0"] * (length - len(v2_parts))) - - for i in range(length): - if int(v1_parts[i]) > int(v2_parts[i]): - return 1 - elif int(v1_parts[i]) < int(v2_parts[i]): - return -1 - return 0 - - async def check_update(self, url: str, current_version: str) -> ReleaseInfo: + async def check_update(self, url: str, current_version: str) -> ReleaseInfo | None: update_data = await self.fetch_release_info(url) tag_name = update_data[0]["tag_name"]