1#!/usr/bin/env nix-shell
2#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ps.requests ])"
3
4import os
5import hashlib
6import base64
7import json
8
9import requests
10
11
12class Version:
13 def __init__(self, name: str):
14 self.name: str = name
15 self.hash: str | None = None
16 self.build_number: int | None = None
17
18 @property
19 def full_name(self):
20 v_name = f"{self.name}-{self.build_number}"
21
22 # this will probably never happen because the download of a build with NoneType in URL would fail
23 if not self.name or not self.build_number:
24 print(f"Warning: version '{v_name}' contains NoneType!")
25
26 return v_name
27
28
29class VersionManager:
30 def __init__(self, base_url: str = "https://api.papermc.io/v2/projects/paper"):
31 self.versions: list[Version] = []
32 self.base_url: str = base_url
33
34 def fetch_versions(self, not_before_minor_version: int = 18):
35 """
36 Fetch all versions after given minor release
37 """
38
39 response = requests.get(self.base_url)
40
41 try:
42 response.raise_for_status()
43
44 except requests.exceptions.HTTPError as e:
45 print(e)
46 return
47
48 # we only want versions that are no pre-releases
49 release_versions = filter(
50 lambda v_name: 'pre' not in v_name, response.json()["versions"])
51
52 for version_name in release_versions:
53
54 # split version string, convert to list ot int
55 version_split = version_name.split(".")
56 version_split = list(map(int, version_split))
57
58 # check if version is higher than 1.<not_before_sub_version>
59 if (version_split[0] > 1) or (version_split[0] == 1 and version_split[1] >= not_before_minor_version):
60 self.versions.append(Version(version_name))
61
62 def fetch_latest_version_builds(self):
63 """
64 Set latest build number to each version
65 """
66
67 for version in self.versions:
68 url = f"{self.base_url}/versions/{version.name}"
69 response = requests.get(url)
70
71 # check that we've got a good response
72 try:
73 response.raise_for_status()
74
75 except requests.exceptions.HTTPError as e:
76 print(e)
77 return
78
79 # the highest build in response.json()['builds']:
80 latest_build = response.json()['builds'][-1]
81 version.build_number = latest_build
82
83 def generate_version_hashes(self):
84 """
85 Generate and set the hashes for all registered versions (versions will are downloaded to memory)
86 """
87
88 for version in self.versions:
89 url = f"{self.base_url}/versions/{version.name}/builds/{version.build_number}/downloads/paper-{version.full_name}.jar"
90 version.hash = self.download_and_generate_sha256_hash(url)
91
92 def versions_to_json(self):
93 return json.dumps(
94 {version.name: {'hash': version.hash, 'version': version.full_name}
95 for version in self.versions},
96 indent=4
97 )
98
99 def find_version_json() -> str:
100 """
101 Find the versions.json file in the same directory as this script
102 """
103 return os.path.join(os.path.dirname(os.path.realpath(__file__)), "versions.json")
104
105 def write_versions(self, file_name: str = find_version_json()):
106 """ write all processed versions to json """
107 # save json to versions.json
108 with open(file_name, 'w') as f:
109 f.write(self.versions_to_json() + "\n")
110
111 @staticmethod
112 def download_and_generate_sha256_hash(url: str) -> str | None:
113 """
114 Fetch the tarball from the given URL.
115 Then generate a sha256 hash of the tarball.
116 """
117
118 try:
119 # Download the file from the URL
120 response = requests.get(url)
121 response.raise_for_status()
122
123 except requests.exceptions.RequestException as e:
124 print(f"Error: {e}")
125 return None
126
127 # Create a new SHA-256 hash object
128 sha256_hash = hashlib.sha256()
129
130 # Update the hash object with chunks of the downloaded content
131 for byte_block in response.iter_content(4096):
132 sha256_hash.update(byte_block)
133
134 # Get the hexadecimal representation of the hash
135 hash_value = sha256_hash.digest()
136
137 # Encode the hash value in base64
138 base64_hash = base64.b64encode(hash_value).decode('utf-8')
139
140 # Format it as "sha256-{base64_hash}"
141 sri_representation = f"sha256-{base64_hash}"
142
143 return sri_representation
144
145
146if __name__ == '__main__':
147 version_manager = VersionManager()
148
149 version_manager.fetch_versions()
150 version_manager.fetch_latest_version_builds()
151 version_manager.generate_version_hashes()
152 version_manager.write_versions()