diff --git a/config/m4b-config.json b/config/m4b-config.json index 2730127..68746cb 100644 --- a/config/m4b-config.json +++ b/config/m4b-config.json @@ -1,6 +1,6 @@ { "project": { - "project_version": "4.3.5", + "project_version": "4.3.6", "compose_project_name": "money4band", "ds_project_server_url": "https://discord.com/invite/Fq8eeazBAD" }, diff --git a/utils/updater.py b/utils/updater.py index 823db60..aeb3484 100644 --- a/utils/updater.py +++ b/utils/updater.py @@ -5,6 +5,7 @@ import json import logging from datetime import datetime +import re from colorama import Fore, Back, Style, just_fix_windows_console script_dir = os.path.dirname(os.path.abspath(__file__)) @@ -14,29 +15,27 @@ from utils.loader import load_json_config -class Version(): +class Version: """ A class to represent a version number in the format major.minor.patch. Supports comparison operators (==, !=, <, >, <=, >=) and can be created from a string or individual components. """ + version_regex = re.compile( + r'(?:(?:v|version)?\s*)?(\d+)\.(\d+)\.(\d+)', re.IGNORECASE) + @staticmethod def from_string(version_str: str): """ - Create a Version object from a string in the format major.minor.patch. + Create a Version object by extracting version numbers from a string. """ - version_str = version_str.strip().lower().replace('v.', '').replace('v', '') - version_parts = version_str.split('.') - if len(version_parts) != 3: - raise ValueError("Invalid version string format") - try: - major = int(version_parts[0]) - minor = int(version_parts[1]) - patch = int(version_parts[2]) - except ValueError: - raise ValueError("Invalid version string format") - return Version(major, minor, patch) - + version_str = version_str.strip() + match = Version.version_regex.search(version_str) + if not match: + raise ValueError(f"Invalid version string format: '{version_str}'") + major, minor, patch = match.groups() + return Version(int(major), int(minor), int(patch)) + def __init__(self, major: int, minor: int, patch: int): self.major = major self.minor = minor @@ -49,6 +48,8 @@ def __repr__(self): return str(self) def __eq__(self, other): + if not isinstance(other, Version): + return False return ( self.major == other.major and self.minor == other.minor @@ -56,25 +57,33 @@ def __eq__(self, other): ) def __lt__(self, other): + if not isinstance(other, Version): + return NotImplemented if self.major < other.major: return True elif self.major > other.major: return False - else: # major is equal + else: # major is equal if self.minor < other.minor: return True elif self.minor > other.minor: return False - else: # minor is equal + else: # minor is equal return self.patch < other.patch def __gt__(self, other): + if not isinstance(other, Version): + return NotImplemented return not self.__lt__(other) and not self.__eq__(other) def __le__(self, other): + if not isinstance(other, Version): + return NotImplemented return self.__lt__(other) or self.__eq__(other) def __ge__(self, other): + if not isinstance(other, Version): + return NotImplemented return not self.__lt__(other) def __ne__(self, other): @@ -89,7 +98,6 @@ def get_latest_releases(count: int = 5) -> List[Dict]: with urllib.request.urlopen(url) as response: data = response.read().decode() releases = json.loads(data) - releases = releases[:count] stripped_releases = [] for release in releases: if release['prerelease']: @@ -97,11 +105,16 @@ def get_latest_releases(count: int = 5) -> List[Dict]: if release['draft']: continue name = release['name'] - if name == '': + if not name: name = release['tag_name'] - if name == '': + if not name: + continue + try: + version = Version.from_string(name) + except ValueError: + logging.warning( + f"Skipping release with unparseable version: '{name}'") continue - version = Version.from_string(name) url = release['html_url'] published_at = release['published_at'] published_at = datetime.strptime( @@ -112,7 +125,9 @@ def get_latest_releases(count: int = 5) -> List[Dict]: 'url': url, 'published_at': published_at }) - return stripped_releases + stripped_releases.sort( + key=lambda x: x['version'], reverse=True) + return stripped_releases[:count] except urllib.error.HTTPError as e: raise Exception(f"Failed to fetch releases. HTTP Error: {e.code}") except urllib.error.URLError as e: @@ -127,22 +142,28 @@ def check_update_available(m4b_config_path_or_dict: str | dict) -> None: just_fix_windows_console() m4b_config = load_json_config(m4b_config_path_or_dict) try: - current_version = m4b_config.get('project',{}).get('project_version', "0.0.0") - current_version = Version.from_string(current_version) - latest_release = get_latest_releases() - latest_release.sort(key=lambda x: x['version'], reverse=True) - latest_release = latest_release[0] + current_version_str = m4b_config.get('project', {}).get( + 'project_version', "0.0.0") + current_version = Version.from_string(current_version_str) + latest_releases = get_latest_releases() + if not latest_releases: + print(f"{Fore.YELLOW}No releases found.") + return + latest_release = latest_releases[0] if current_version < latest_release['version']: - print(f"{Fore.YELLOW}New version available: {latest_release['version']}, published at {latest_release['published_at']}") + print( + f"{Fore.YELLOW}New version available: {latest_release['version']}, published at {latest_release['published_at']}") print(f"Download URL: {Style.RESET_ALL}{latest_release['url']}") except Exception as e: logging.error(e) print(f"Error checking for updates: {e}") + def main(): releases = get_latest_releases() for release in releases: print(release) + if __name__ == '__main__': main()