From d6e1cd3ffadfd1667ea18e978b539497ea869842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tristan=20Dani=C3=ABl=20Maat?= Date: Wed, 6 Oct 2021 01:13:31 +0100 Subject: [PATCH] update-mods.py: Fix issues revealed by 1.17 updates --- flake.nix | 1 + pkgs/minecraft/voor-kia/update-mods.py | 97 +++++++++++++------------- 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/flake.nix b/flake.nix index fba329c..a7b1ef7 100644 --- a/flake.nix +++ b/flake.nix @@ -97,6 +97,7 @@ # For the minecraft mod update script (python3.withPackages (pypkgs: with pypkgs; [ + dateutil requests ipython diff --git a/pkgs/minecraft/voor-kia/update-mods.py b/pkgs/minecraft/voor-kia/update-mods.py index df32d86..36722be 100644 --- a/pkgs/minecraft/voor-kia/update-mods.py +++ b/pkgs/minecraft/voor-kia/update-mods.py @@ -3,13 +3,16 @@ import json import hashlib import pathlib from copy import deepcopy +from datetime import datetime from enum import Enum -from typing import Dict, Generator, NamedTuple, Optional, Union +from typing import Any, Dict, Generator, List, NamedTuple, Optional, Union import requests +from dateutil import parser API = "https://addons-ecs.forgesvc.net/api/v2" +JSON = Union[List[Dict[str, Any]], Dict[str, Any]] class ModLoader(Enum): @@ -19,25 +22,34 @@ class ModLoader(Enum): class File(NamedTuple): id: int - gameVersion: str + gameVersions: List[str] name: str modLoader: Optional[ModLoader] + date: datetime @classmethod - def from_json(cls, f: Dict[str, Union[str, int]]): - modLoader = f.get("modLoader", None) + def from_json(cls, f: JSON): + assert isinstance(f, dict) - assert isinstance(f["gameVersion"], str) - assert isinstance(f["projectFileId"], int) - assert isinstance(f["projectFileName"], str) - if modLoader is not None: - assert isinstance(modLoader, int) + assert isinstance(f.get("gameVersion"), list) + assert isinstance(f.get("id"), int) + assert isinstance(f.get("fileName"), str) + assert isinstance(f.get("fileDate"), str) + + modLoader = ( + ModLoader.FORGE + if "Forge" in f["gameVersion"] + else ModLoader.FABRIC + if "Fabric" in f["gameVersion"] + else None + ) return cls( - f["projectFileId"], + f["id"], f["gameVersion"], - f["projectFileName"], - ModLoader(modLoader) if modLoader is not None else None, + f["fileName"], + modLoader, + parser.isoparse(f["fileDate"]), ) @@ -49,15 +61,12 @@ class CurseAPI: ] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0" def get_latest_files(self, mod_id: int) -> Generator[File, None, None]: - res = self._session.get(f"{API}/addon/{mod_id}") + res = self._session.get(f"{API}/addon/{mod_id}/files") res.raise_for_status() - latest_files = res.json().get("gameVersionLatestFiles", None) + latest_files = res.json() - if latest_files is None: - return (_ for _ in []) - else: - return (File.from_json(f) for f in latest_files) + return (File.from_json(f) for f in latest_files) def get_file_url(self, mod_id: int, file_id: int) -> str: res = self._session.get(f"{API}/addon/{mod_id}/file/{file_id}/download-url") @@ -107,43 +116,37 @@ def update(infile: pathlib.Path, version: str, mod_loader: ModLoader): latest_files = [_ for _ in []] def compatible(file_: File) -> bool: - file_version = file_.gameVersion.split(".") - target_version = version.split(".") - - # We assume that major + minor version are compatible; - # this seems to generally be true, but check the output - # for possible mistakes. - # - # The patch version is completely ignored since mod - # authors generally don't register versions properly - # enough to match this. - # - # Being more strict than this usually results in - # technically compatible mods with no available versions. return ( - (file_.modLoader is None or file_.modLoader == mod_loader) - and file_version[0] == target_version[0] - and file_version[1] == target_version[1] + mod_loader is None + or file_.modLoader is None + or file_.modLoader == mod_loader + ) and any( + file_version.startswith(version) for file_version in file_.gameVersions ) - latest = max(filter(compatible, latest_files), key=lambda f: f.gameVersion) + compatible_files = list(filter(compatible, latest_files)) + if compatible_files: + latest = max(compatible_files, key=lambda f: f.date) + if latest.id != mod["id"]: + print( + f"Updating {mod['project']} {mod['filename']} -> {latest.name}..." + ) + contents = curse.download_file(mod["project_id"], latest.id) + sha256 = hashlib.sha256(contents).hexdigest() - if latest is None: + new_mod = deepcopy(mod) + new_mod.update( + {"filename": latest.name, "id": latest.id, "sha256": sha256} + ) + new_mods.append(new_mod) + else: + new_mods.append(mod) + else: print(f"WARNING: No compatible files found for {mod['project']}") print( - f"Versions available: {[(f.name, f.gameVersion) for f in latest_files]}" + f"Versions available: {[(f.name, f.gameVersions) for f in latest_files]}" ) new_mods.append(mod) - elif latest.id != mod["id"]: - print(f"Updating {mod['project']}...") - contents = curse.download_file(mod["project_id"], latest.id) - sha256 = hashlib.sha256(contents).hexdigest() - - new_mod = deepcopy(mod) - new_mod.update({"filename": latest.name, "id": latest.id, "sha256": sha256}) - new_mods.append(new_mod) - else: - new_mods.append(mod) with open("temp.json", "w") as out: json.dump(new_mods, out, sort_keys=True, indent=2)