update-mods.py: Fix issues revealed by 1.17 updates
This commit is contained in:
		
							parent
							
								
									b9af400942
								
							
						
					
					
						commit
						d6e1cd3ffa
					
				
					 2 changed files with 51 additions and 47 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue