update-mods.py: Fix issues revealed by 1.17 updates #52
					 2 changed files with 51 additions and 47 deletions
				
			
		|  | @ -97,6 +97,7 @@ | |||
|               # For the minecraft mod update script | ||||
|               (python3.withPackages (pypkgs: | ||||
|                 with pypkgs; [ | ||||
|                   dateutil | ||||
|                   requests | ||||
| 
 | ||||
|                   ipython | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue