update-mods.py: Fix issues revealed by 1.17 updates #52

Manually merged
tlater merged 1 commit from tlater/fix-minecraft-update into master 2021-10-06 01:54:53 +01:00
2 changed files with 51 additions and 47 deletions

View file

@ -97,6 +97,7 @@
# For the minecraft mod update script
(python3.withPackages (pypkgs:
with pypkgs; [
dateutil
requests
ipython

View file

@ -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,14 +61,11 @@ 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)
def get_file_url(self, mod_id: int, file_id: int) -> str:
@ -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)
if latest is None:
print(f"WARNING: No compatible files found for {mod['project']}")
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"Versions available: {[(f.name, f.gameVersion) for f in latest_files]}"
f"Updating {mod['project']} {mod['filename']} -> {latest.name}..."
)
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_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.gameVersions) for f in latest_files]}"
)
new_mods.append(mod)
with open("temp.json", "w") as out:
json.dump(new_mods, out, sort_keys=True, indent=2)