update-mods.py: Fix issues revealed by 1.17 updates
This commit is contained in:
parent
b9af400942
commit
d6e1cd3ffa
|
@ -97,6 +97,7 @@
|
||||||
# For the minecraft mod update script
|
# For the minecraft mod update script
|
||||||
(python3.withPackages (pypkgs:
|
(python3.withPackages (pypkgs:
|
||||||
with pypkgs; [
|
with pypkgs; [
|
||||||
|
dateutil
|
||||||
requests
|
requests
|
||||||
|
|
||||||
ipython
|
ipython
|
||||||
|
|
|
@ -3,13 +3,16 @@ import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import pathlib
|
import pathlib
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
from datetime import datetime
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, Generator, NamedTuple, Optional, Union
|
from typing import Any, Dict, Generator, List, NamedTuple, Optional, Union
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from dateutil import parser
|
||||||
|
|
||||||
|
|
||||||
API = "https://addons-ecs.forgesvc.net/api/v2"
|
API = "https://addons-ecs.forgesvc.net/api/v2"
|
||||||
|
JSON = Union[List[Dict[str, Any]], Dict[str, Any]]
|
||||||
|
|
||||||
|
|
||||||
class ModLoader(Enum):
|
class ModLoader(Enum):
|
||||||
|
@ -19,25 +22,34 @@ class ModLoader(Enum):
|
||||||
|
|
||||||
class File(NamedTuple):
|
class File(NamedTuple):
|
||||||
id: int
|
id: int
|
||||||
gameVersion: str
|
gameVersions: List[str]
|
||||||
name: str
|
name: str
|
||||||
modLoader: Optional[ModLoader]
|
modLoader: Optional[ModLoader]
|
||||||
|
date: datetime
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, f: Dict[str, Union[str, int]]):
|
def from_json(cls, f: JSON):
|
||||||
modLoader = f.get("modLoader", None)
|
assert isinstance(f, dict)
|
||||||
|
|
||||||
assert isinstance(f["gameVersion"], str)
|
assert isinstance(f.get("gameVersion"), list)
|
||||||
assert isinstance(f["projectFileId"], int)
|
assert isinstance(f.get("id"), int)
|
||||||
assert isinstance(f["projectFileName"], str)
|
assert isinstance(f.get("fileName"), str)
|
||||||
if modLoader is not None:
|
assert isinstance(f.get("fileDate"), str)
|
||||||
assert isinstance(modLoader, int)
|
|
||||||
|
modLoader = (
|
||||||
|
ModLoader.FORGE
|
||||||
|
if "Forge" in f["gameVersion"]
|
||||||
|
else ModLoader.FABRIC
|
||||||
|
if "Fabric" in f["gameVersion"]
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
f["projectFileId"],
|
f["id"],
|
||||||
f["gameVersion"],
|
f["gameVersion"],
|
||||||
f["projectFileName"],
|
f["fileName"],
|
||||||
ModLoader(modLoader) if modLoader is not None else None,
|
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"
|
] = "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]:
|
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()
|
res.raise_for_status()
|
||||||
|
|
||||||
latest_files = res.json().get("gameVersionLatestFiles", None)
|
latest_files = res.json()
|
||||||
|
|
||||||
if latest_files is None:
|
return (File.from_json(f) for f in latest_files)
|
||||||
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:
|
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")
|
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 []]
|
latest_files = [_ for _ in []]
|
||||||
|
|
||||||
def compatible(file_: File) -> bool:
|
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 (
|
return (
|
||||||
(file_.modLoader is None or file_.modLoader == mod_loader)
|
mod_loader is None
|
||||||
and file_version[0] == target_version[0]
|
or file_.modLoader is None
|
||||||
and file_version[1] == target_version[1]
|
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"WARNING: No compatible files found for {mod['project']}")
|
||||||
print(
|
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)
|
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:
|
with open("temp.json", "w") as out:
|
||||||
json.dump(new_mods, out, sort_keys=True, indent=2)
|
json.dump(new_mods, out, sort_keys=True, indent=2)
|
||||||
|
|
Loading…
Reference in a new issue