#!/usr/bin/env nix-shell #!nix-shell -i python3 -p python3Packages.requests python3Packages.dataclasses-json import json from dataclasses import dataclass, field from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional import requests from dataclasses_json import DataClassJsonMixin, LetterCase, config from marshmallow import fields @dataclass class Download(DataClassJsonMixin): sha1: str size: int url: str @dataclass class Version(DataClassJsonMixin): id: str type: str url: str time: datetime = field( metadata=config( encoder=datetime.isoformat, decoder=datetime.fromisoformat, mm_field=fields.DateTime(format="iso"), ) ) release_time: datetime = field( metadata=config( encoder=datetime.isoformat, decoder=datetime.fromisoformat, mm_field=fields.DateTime(format="iso"), letter_case=LetterCase.CAMEL, ) ) def get_manifest(self) -> Any: """Return the version's manifest.""" response = requests.get(self.url) response.raise_for_status() return response.json() def get_downloads(self) -> Dict[str, Download]: """ Return all downloadable files from the version's manifest, in Download objects. """ return { download_name: Download.from_dict(download_info) for download_name, download_info in self.get_manifest()["downloads"].items() } def get_java_version(self) -> Any: """ Return the java version specified in a version's manifest, if it is present. Versions <= 1.6 do not specify this. """ return self.get_manifest().get("javaVersion", {}).get("majorVersion", None) def get_server(self) -> Optional[Download]: """ If the version has a server download available, return the Download object for the server download. If the version does not have a server download avilable, return None. """ downloads = self.get_downloads() if "server" in downloads: return downloads["server"] return None def get_versions() -> List[Version]: """Return a list of Version objects for all available versions.""" response = requests.get( "https://launchermeta.mojang.com/mc/game/version_manifest.json" ) response.raise_for_status() data = response.json() return [Version.from_dict(version) for version in data["versions"]] def get_major_release(version_id: str) -> str: """ Return the major release for a version. The major release for 1.17 and 1.17.1 is 1.17. """ if not len(version_id.split(".")) >= 2: raise ValueError(f"version not in expected format: '{version_id}'") return ".".join(version_id.split(".")[:2]) def group_major_releases(releases: List[Version]) -> Dict[str, List[Version]]: """ Return a dictionary containing each version grouped by each major release. The key "1.17" contains a list with two Version objects, one for "1.17" and another for "1.17.1". """ groups: Dict[str, List[Version]] = {} for release in releases: major_release = get_major_release(release.id) if major_release not in groups: groups[major_release] = [] groups[major_release].append(release) return groups def get_latest_major_releases(releases: List[Version]) -> Dict[str, Version]: """ Return a dictionary containing the latest version for each major release. The latest major release for 1.16 is 1.16.5, so the key "1.16" contains a Version object for 1.16.5. """ return { major_release: max( (release for release in releases if get_major_release(release.id) == major_release), key=lambda x: tuple(map(int, x.id.split('.'))), ) for major_release in group_major_releases(releases) } def generate() -> Dict[str, Dict[str, str]]: """ Return a dictionary containing the latest url, sha1 and version for each major release. """ versions = get_versions() releases = list( filter(lambda version: version.type == "release", versions) ) # remove snapshots and betas latest_major_releases = get_latest_major_releases(releases) servers = { version: Download.schema().dump(download_info) # Download -> dict for version, download_info in { version: value.get_server() for version, value in latest_major_releases.items() }.items() if download_info is not None # versions < 1.2 do not have a server } for server in servers.values(): del server["size"] # don't need it for version, server in servers.items(): server["version"] = latest_major_releases[version].id server["javaVersion"] = latest_major_releases[version].get_java_version() return servers if __name__ == "__main__": with open(Path(__file__).parent / "versions.json", "w") as file: json.dump(generate(), file, indent=2) file.write("\n")