Add support for different game versions
This commit is contained in:
parent
0086cb798c
commit
5ab103df58
9 changed files with 65 additions and 38 deletions
|
@ -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)
|
||||
|
|
12
wau/cli.py
12
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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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}/")
|
||||
|
|
Loading…
Reference in a new issue