···11+#!/usr/bin/env nix-shell
22+#!nix-shell -i python3 -p python3Packages.requests python3Packages.dataclasses-json
33+44+import json
55+from dataclasses import dataclass, field
66+from datetime import datetime
77+from typing import Any, Dict, List, Optional
88+99+import requests
1010+from dataclasses_json import DataClassJsonMixin, LetterCase, config
1111+from marshmallow import fields
1212+1313+1414+@dataclass
1515+class Download(DataClassJsonMixin):
1616+ sha1: str
1717+ size: int
1818+ url: str
1919+2020+2121+@dataclass
2222+class Version(DataClassJsonMixin):
2323+ id: str
2424+ type: str
2525+ url: str
2626+ time: datetime = field(
2727+ metadata=config(
2828+ encoder=datetime.isoformat,
2929+ decoder=datetime.fromisoformat,
3030+ mm_field=fields.DateTime(format="iso"),
3131+ )
3232+ )
3333+ release_time: datetime = field(
3434+ metadata=config(
3535+ encoder=datetime.isoformat,
3636+ decoder=datetime.fromisoformat,
3737+ mm_field=fields.DateTime(format="iso"),
3838+ letter_case=LetterCase.CAMEL,
3939+ )
4040+ )
4141+4242+ def get_manifest(self) -> Any:
4343+ """Return the version's manifest."""
4444+ response = requests.get(self.url)
4545+ response.raise_for_status()
4646+ return response.json()
4747+4848+ def get_downloads(self) -> Dict[str, Download]:
4949+ """
5050+ Return all downloadable files from the version's manifest, in Download
5151+ objects.
5252+ """
5353+ return {
5454+ download_name: Download.from_dict(download_info)
5555+ for download_name, download_info in self.get_manifest()["downloads"].items()
5656+ }
5757+5858+ def get_server(self) -> Optional[Download]:
5959+ """
6060+ If the version has a server download available, return the Download
6161+ object for the server download. If the version does not have a server
6262+ download avilable, return None.
6363+ """
6464+ downloads = self.get_downloads()
6565+ if "server" in downloads:
6666+ return downloads["server"]
6767+ return None
6868+6969+7070+def get_versions() -> List[Version]:
7171+ """Return a list of Version objects for all available versions."""
7272+ response = requests.get(
7373+ "https://launchermeta.mojang.com/mc/game/version_manifest.json"
7474+ )
7575+ response.raise_for_status()
7676+ data = response.json()
7777+ return [Version.from_dict(version) for version in data["versions"]]
7878+7979+8080+def get_major_release(version_id: str) -> str:
8181+ """
8282+ Return the major release for a version. The major release for 1.17 and
8383+ 1.17.1 is 1.17.
8484+ """
8585+ if not len(version_id.split(".")) >= 2:
8686+ raise ValueError(f"version not in expected format: '{version_id}'")
8787+ return ".".join(version_id.split(".")[:2])
8888+8989+9090+def group_major_releases(releases: List[Version]) -> Dict[str, List[Version]]:
9191+ """
9292+ Return a dictionary containing each version grouped by each major release.
9393+ The key "1.17" contains a list with two Version objects, one for "1.17"
9494+ and another for "1.17.1".
9595+ """
9696+ groups: Dict[str, List[Version]] = {}
9797+ for release in releases:
9898+ major_release = get_major_release(release.id)
9999+ if major_release not in groups:
100100+ groups[major_release] = []
101101+ groups[major_release].append(release)
102102+ return groups
103103+104104+105105+def get_latest_major_releases(releases: List[Version]) -> Dict[str, Version]:
106106+ """
107107+ Return a dictionary containing the latest version for each major release.
108108+ The latest major release for 1.16 is 1.16.5, so the key "1.16" contains a
109109+ Version object for 1.16.5.
110110+ """
111111+ return {
112112+ major_release: sorted(releases, key=lambda x: x.id, reverse=True)[0]
113113+ for major_release, releases in group_major_releases(releases).items()
114114+ }
115115+116116+117117+def generate() -> Dict[str, Dict[str, str]]:
118118+ """
119119+ Return a dictionary containing the latest url, sha1 and version for each major
120120+ release.
121121+ """
122122+ versions = get_versions()
123123+ releases = list(
124124+ filter(lambda version: version.type == "release", versions)
125125+ ) # remove snapshots and betas
126126+ latest_major_releases = get_latest_major_releases(releases)
127127+128128+ servers = {
129129+ version: Download.schema().dump(download_info) # Download -> dict
130130+ for version, download_info in {
131131+ version: value.get_server()
132132+ for version, value in latest_major_releases.items()
133133+ }.items()
134134+ if download_info is not None # versions < 1.2 do not have a server
135135+ }
136136+ for server in servers.values():
137137+ del server["size"] # don't need it
138138+139139+ for version, server in servers.items():
140140+ server["version"] = latest_major_releases[version].id
141141+ return servers
142142+143143+144144+if __name__ == "__main__":
145145+ with open("versions.json", "w") as file:
146146+ json.dump(generate(), file, indent=2)
147147+ file.write("\n")