Add support for different game versions

This commit is contained in:
Casper V. Kristensen 2024-01-06 03:24:00 +01:00
parent 0086cb798c
commit 5ab103df58
9 changed files with 65 additions and 38 deletions

View file

@ -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)

View file

@ -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()

View file

@ -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.

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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:

View file

@ -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:

View file

@ -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}/")