From 5ab103df58d4dab9b310ac6dc5bcd851a2bcb72c Mon Sep 17 00:00:00 2001 From: "Casper V. Kristensen" Date: Sat, 6 Jan 2024 03:24:00 +0100 Subject: [PATCH] Add support for different game versions --- wau/addons.py | 11 +++++++++-- wau/cli.py | 12 +++++++++--- wau/providers/base.py | 4 ++-- wau/providers/curseforge.py | 23 ++++++++++++++++------- wau/providers/git.py | 4 ++-- wau/providers/github.py | 33 +++++++++++++++++++-------------- wau/providers/tukui.py | 6 +++--- wau/providers/web.py | 4 ++-- wau/providers/wowinterface.py | 6 +++--- 9 files changed, 65 insertions(+), 38 deletions(-) diff --git a/wau/addons.py b/wau/addons.py index fbc24d2..1cdb68a 100644 --- a/wau/addons.py +++ b/wau/addons.py @@ -5,6 +5,7 @@ import logging import re import shutil from dataclasses import dataclass, field +from enum import Enum from pathlib import Path from typing import Iterable, List, Dict @@ -13,6 +14,12 @@ from . import config, providers logger = logging.getLogger(__name__) +class Game(Enum): + WOTLK = "wotlk" + ERA = "era" + RETAIL = "retail" + + @dataclass class Addon: name: str = None @@ -64,12 +71,12 @@ class Addon: return provider raise ValueError(f"No AddOn provider for {self}") - def download(self) -> bool: + def download(self, game: Game) -> bool: try: logger.info("Downloading %s", self.url) self.create_download_dir() provider = self.get_provider() - changed = provider.download(self) + changed = provider.download(self, game) return changed except Exception as e: logger.exception(e) diff --git a/wau/cli.py b/wau/cli.py index 4db6ad5..1d6d35f 100644 --- a/wau/cli.py +++ b/wau/cli.py @@ -5,7 +5,7 @@ import shutil import textwrap from . import __url__, __author__, __version__, config, providers, addons -from .addons import Addon +from .addons import Addon, Game logger = logging.getLogger(__name__) @@ -28,6 +28,12 @@ class CLI: epilog=f"For more information, see <{__url__}>." ) parser.set_defaults(func=lambda a: parser.print_help()) + parser.add_argument( + "-g", "--game", + choices=[g.value for g in Game], + default=Game.WOTLK.value, + help="Game version. Used to select the optimal AddOn in case there are multiple available versions." + ) parser.add_argument( "-v", "--verbose", action="count", @@ -105,7 +111,7 @@ class CLI: continue print(f"Installing AddOn from {url}") addon = Addon(url=url) - addon.download() + addon.download(game=Game(args.game)) addon.install() self.installed_addons.append(addon) addons.save_installed_addons(self.installed_addons) @@ -122,7 +128,7 @@ class CLI: def update(self, args) -> None: for addon in self.installed_addons: print(f"Updating {addon.name}..", end=" ") - changed = addon.download() + changed = addon.download(game=Game(args.game)) if changed: addon.uninstall() addon.install() diff --git a/wau/providers/base.py b/wau/providers/base.py index a6b1f0a..69b2c72 100644 --- a/wau/providers/base.py +++ b/wau/providers/base.py @@ -1,4 +1,4 @@ -from ..addons import Addon +from ..addons import Addon, Game class Provider: @@ -10,7 +10,7 @@ class Provider: raise NotImplemented @classmethod - def download(cls, addon: Addon) -> bool: + def download(cls, addon: Addon, game: Game) -> bool: """ Download the provided AddOn. Returns True if the AddOn data was changed (i.e. downloaded/updated), False otherwise. diff --git a/wau/providers/curseforge.py b/wau/providers/curseforge.py index b20ac2c..8e76318 100644 --- a/wau/providers/curseforge.py +++ b/wau/providers/curseforge.py @@ -5,7 +5,7 @@ from typing import Dict from .web import Web from .. import http -from ..addons import Addon +from ..addons import Addon, Game logger = logging.getLogger(__name__) @@ -27,12 +27,12 @@ class CurseForge(Web): return "curseforge.com/wow/addons/" in url @classmethod - def download(cls, addon: Addon, url: str = None) -> bool: + def download(cls, addon: Addon, game: Game, url: str = None) -> bool: """ Twitch API from: https://github.com/Gaz492/TwitchAPI. Thanks Gareth! <3 """ - latest_file_url = cls._get_latest_file_url(addon) - return super().download(addon, url=latest_file_url) + latest_file_url = cls._get_latest_file_url(addon, game) + return super().download(addon, game, url=latest_file_url) @classmethod def _get_curse_id(cls, addon: Addon) -> int: @@ -71,14 +71,23 @@ class CurseForge(Web): raise ValueError("AddOn slug not found in CurseForge search results.") @classmethod - def _get_latest_file_url(cls, addon: Addon) -> str: + def _game_version_priorities(cls, game: Game) -> list[int]: + wotlk, tbc, era, retail = 73713, 73246, 67408, 517 # https://api.curseforge.com/v1/games/1/version-types + priorities = { + game.WOTLK: [wotlk, era, tbc, retail], + game.ERA: [era, wotlk, tbc, retail], + game.RETAIL: [retail, wotlk, era, tbc], + } + return priorities[game] + + @classmethod + def _get_latest_file_url(cls, addon: Addon, game: Game) -> str: curse_id = cls._get_curse_id(addon) logger.debug("Getting latest AddOn info from CurseForge") files = http.open(f"{cls.api_url}/v1/mods/{curse_id}/files", headers=cls.headers()).json()["data"] + version_priorities = cls._game_version_priorities(game) def key(index: int, file: dict) -> tuple: - # https://api.curseforge.com/v1/games/1/version-types: [WotLK, TBC, Classic, Retail] - version_priorities = [73713, 73246, 67408, 517] return ( min(version_priorities.index(v["gameVersionTypeId"]) for v in file["sortableGameVersions"]), file["releaseType"], # releaseType: 1: release, 2: beta, 3: alpha diff --git a/wau/providers/git.py b/wau/providers/git.py index f3284d2..fe8e630 100644 --- a/wau/providers/git.py +++ b/wau/providers/git.py @@ -4,7 +4,7 @@ from pathlib import Path from typing import List from .base import Provider -from ..addons import Addon +from ..addons import Addon, Game logger = logging.getLogger(__name__) @@ -26,7 +26,7 @@ class Git(Provider): return False @classmethod - def download(cls, addon: Addon) -> bool: + def download(cls, addon: Addon, game: Game) -> bool: if addon.is_cached(): return cls._pull(addon) cls._clone(addon) diff --git a/wau/providers/github.py b/wau/providers/github.py index 1f0fe4c..c9f668a 100644 --- a/wau/providers/github.py +++ b/wau/providers/github.py @@ -5,7 +5,7 @@ from typing import Tuple from .web import Web from .. import http -from ..addons import Addon +from ..addons import Addon, Game logger = logging.getLogger(__name__) @@ -22,24 +22,29 @@ class GitHub(Web): return False @classmethod - def download(cls, addon: Addon, url: str = None) -> bool: + def _asset_priorities(cls, game: Game) -> list[str]: + wotlk = ["wotlk", "wrath"] + era = ["era", "classic", "vanilla"] + retail = ["retail", "mainline"] + wildcard = [".*"] + priorities = { + game.WOTLK: wotlk + era + retail, + game.ERA: era + wotlk + retail, + game.RETAIL: retail, + } + priority = priorities[game] + priority_patterns = [f"[._-]{p}[._-]" for p in priority] + return priority_patterns + wildcard + + @classmethod + def download(cls, addon: Addon, game: Game, url: str = None) -> bool: repo_owner, repo_name = cls._parse_url(addon.url) addon.name = repo_name latest_release = http.open( url=f"{cls.api_url}/repos/{repo_owner}/{repo_name}/releases/latest" ).json() - asset_priorities = [ - r"[._-]wotlk[._-]", - r"[._-]wrath[._-]", - r"[._-]tbc[._-]", - r"[._-]tbcc[._-]", - r"[._-]bcc[._-]", - r"[._-]bc[._-]", - r"[._-]classic[._-]", - r"[._-]vanilla[._-]", - r".*", - ] + asset_priorities = cls._asset_priorities(game) for asset in sorted( latest_release["assets"], key=lambda a: next( @@ -49,7 +54,7 @@ class GitHub(Web): ) ): if Path(asset["name"]).suffix == ".zip": - return super().download(addon, asset["browser_download_url"]) + return super().download(addon, game, url=asset["browser_download_url"]) raise FileNotFoundError("No zip file found for latest release") @classmethod diff --git a/wau/providers/tukui.py b/wau/providers/tukui.py index 2f2ddef..a95c564 100755 --- a/wau/providers/tukui.py +++ b/wau/providers/tukui.py @@ -2,7 +2,7 @@ import logging from .web import Web from .. import http -from ..addons import Addon +from ..addons import Addon, Game logger = logging.getLogger(__name__) @@ -15,9 +15,9 @@ class TukUI(Web): return "tukui.org" in url @classmethod - def download(cls, addon: Addon, url: str = None) -> bool: + def download(cls, addon: Addon, game: Game, url: str = None) -> bool: file_url = cls._get_file_url(addon) - return super().download(addon, url=file_url) + return super().download(addon, game, url=file_url) @classmethod def _get_file_url(self, addon: Addon) -> str: diff --git a/wau/providers/web.py b/wau/providers/web.py index 724904d..0971b35 100644 --- a/wau/providers/web.py +++ b/wau/providers/web.py @@ -4,7 +4,7 @@ import urllib.request from .base import Provider from .. import http -from ..addons import Addon +from ..addons import Addon, Game logger = logging.getLogger(__name__) @@ -22,7 +22,7 @@ class Web(Provider): return False @classmethod - def download(cls, addon: Addon, url: str = None) -> bool: + def download(cls, addon: Addon, game: Game, url: str = None) -> bool: url = url or addon.url head = http.open(url).head() try: diff --git a/wau/providers/wowinterface.py b/wau/providers/wowinterface.py index 94e762a..4ae016c 100644 --- a/wau/providers/wowinterface.py +++ b/wau/providers/wowinterface.py @@ -1,7 +1,7 @@ import re from .web import Web -from ..addons import Addon +from ..addons import Addon, Game class WowInterface(Web): @@ -10,8 +10,8 @@ class WowInterface(Web): return "wowinterface.com/downloads/info" in url @classmethod - def download(cls, addon: Addon, url: str = None) -> bool: + def download(cls, addon: Addon, game: Game, url: str = None) -> bool: addon_id, addon_name = re.search(r"info(\d+)-?(.*)", addon.url).groups() if addon_name: addon.name = addon_name.replace(".html", "") - return super().download(addon, url=f"https://cdn.wowinterface.com/downloads/file{addon_id}/") + return super().download(addon, game, url=f"https://cdn.wowinterface.com/downloads/file{addon_id}/")