Files
ServerJar/utils/common.py
T

349 lines
12 KiB
Python
Raw Normal View History

2026-05-17 17:16:58 +08:00
from pathlib import Path
import requests
import click
import os
from jproperties import Properties
2026-05-17 17:16:58 +08:00
PAPER_VERSION_API = "https://api.papermc.io/v2/projects/paper/versions/{}"
PAPER_SERVER_JAR_API = "https://api.papermc.io/v2/projects/paper/versions/{}/builds/{}/downloads/paper-{}-{}.jar"
MOJANG_VERSION_MANIFEST_V2 = "https://piston-meta.mojang.com/mc/game/version_manifest_v2.json"
def read_server_properties(server_dir: Path) -> Properties:
if not server_dir.exists():
raise FileNotFoundError("Server directory does not exist")
props = Properties()
with open(server_dir / 'server.properties', 'rb') as config_file:
props.load(config_file, encoding='utf-8')
return props
def save_server_properties(properties: Properties, server_dir: Path) -> None:
path = server_dir / 'server.properties'
with path.open("wb") as config_file:
properties.store(config_file, encoding='utf-8')
def activate_eula_file(server_dir: Path) -> bool:
path = server_dir / 'eula.txt'
if not path.exists():
raise FileNotFoundError("EULA file does not exist. Did you run the server?")
with open(path, 'r', encoding='utf-8') as file:
lines = file.readlines()
for line in lines:
print(f"{line}")
print("To activate eula file please read above text. If you agree this license, enter Y [not agree enter N]")
result = str(input("Agree? :"))
if result.strip().lower() != "y":
return False
with open(path, 'w', encoding='utf-8') as file:
for line in lines:
if line.strip() == "eula=false":
file.write("eula=true\n")
continue
file.write(line)
return True
2026-05-17 17:16:58 +08:00
2026-05-17 21:14:40 +08:00
def jar_filename(filename: str | None, default: str) -> str:
if filename:
name = filename
if not name.endswith(".jar"):
name += ".jar"
return name
return default
2026-05-17 17:16:58 +08:00
def download_file(url: str, destination: Path, chunk_size: int = 1024 * 512):
2026-05-17 19:16:00 +08:00
destination.parent.mkdir(parents=True, exist_ok=True)
2026-05-17 17:16:58 +08:00
with requests.get(url, stream=True, timeout=30) as r:
if r.status_code != 200:
raise Exception(f"Download failed: {r.status_code}\nResponse: {r.text}")
total = int(r.headers.get("content-length", 0))
2026-05-17 19:16:00 +08:00
with destination.open(mode="wb", buffering=chunk_size) as f:
2026-05-17 17:16:58 +08:00
if total > 0:
with click.progressbar(length=total, label=f"Downloading {os.path.basename(destination)}") as bar:
for chunk in r.iter_content(chunk_size=chunk_size):
if chunk:
f.write(chunk)
bar.update(len(chunk))
else:
click.echo(f"Downloading {os.path.basename(destination)} (unknown size)")
for chunk in r.iter_content(chunk_size=chunk_size):
if chunk:
f.write(chunk)
def get_specific_version_paper_builds(minecraft_version: str) -> list[dict[str, str]]:
"""
Get specific version of Paper builds
:param minecraft_version:
:return:
"""
url = PAPER_VERSION_API.format(minecraft_version)
try:
r = requests.get(url)
if r.status_code == 200:
return r.json().get("builds", [])
else:
raise Exception("Unable to fetch build version for {}\n"
"Response: {}".format(minecraft_version, r.text))
except requests.exceptions.RequestException as e:
raise Exception("Unable to get paper version from server.\n"
"URL: {}\n"
"Error: {}".format(url, e))
def get_version_list(release=True):
try:
r = requests.get(MOJANG_VERSION_MANIFEST_V2)
if r.status_code == 200:
if release:
return [version.get('id') for version in r.json().get("versions", [])
if version.get("type") == "release" if version.get('id') is not None]
return r.json()["versions"]
else:
raise Exception("Unable to fetch version list.\n"
"Response: {}".format(r.text))
except requests.exceptions.RequestException as e:
raise Exception("Unable to get version list from server.\n"
"URL: {}\n"
"Error: {}".format(MOJANG_VERSION_MANIFEST_V2, e))
2026-05-17 21:14:40 +08:00
def get_version_manifest():
try:
r = requests.get(MOJANG_VERSION_MANIFEST_V2)
if r.status_code == 200:
return r.json()
raise Exception("Unable to fetch version manifest.\n"
"Response: {}".format(r.text))
except requests.exceptions.RequestException as e:
raise Exception("Unable to get version manifest from server.\n"
"URL: {}\n"
"Error: {}".format(MOJANG_VERSION_MANIFEST_V2, e))
def get_minecraft_version_metadata(minecraft_version: str):
manifest = get_version_manifest()
version_info = next(
(version for version in manifest.get("versions", []) if version.get("id") == minecraft_version),
None,
)
if version_info is None:
raise Exception(f"Minecraft version {minecraft_version} was not found in Mojang version manifest.")
try:
r = requests.get(version_info["url"])
if r.status_code == 200:
return r.json()
raise Exception("Unable to fetch Minecraft version metadata for {}.\n"
"Response: {}".format(minecraft_version, r.text))
except requests.exceptions.RequestException as e:
raise Exception("Unable to get Minecraft version metadata from server.\n"
"URL: {}\n"
"Error: {}".format(version_info.get("url"), e))
2026-05-17 17:16:58 +08:00
def get_latest_version_minecraft(release=True):
version_list = get_version_list(release=release)
2026-05-17 21:14:40 +08:00
if not version_list:
ver = None
elif release:
ver = version_list[0]
else:
ver = version_list[0].get("id")
2026-05-17 17:16:58 +08:00
if ver is None:
raise Exception("Unable to find latest version in version list.\n")
return ver
def get_specific_version_minecraft_require_java_version(minecraft_version, release=True):
version_list = get_version_list(release=release)
if release:
version_exists = minecraft_version in version_list
else:
version_exists = any(version.get("id") == minecraft_version for version in version_list)
if not version_exists:
raise Exception("Specified Minecraft version does not exist.")
metadata = get_minecraft_version_metadata(minecraft_version)
if not metadata:
raise Exception("Unable to get Minecraft version metadata for {}.\n".format(minecraft_version))
return metadata.get("javaVersion", {}).get("majorVersion", None)
2026-05-17 17:16:58 +08:00
def download_server_jar(minecraft_version: str, build_version: str, destination: Path, filename: str | None = None):
"""
Download server jar (paper server only)
"""
url = PAPER_SERVER_JAR_API.format(minecraft_version, build_version, minecraft_version, build_version)
2026-05-17 21:14:40 +08:00
jar_name = jar_filename(filename, os.path.basename(url))
2026-05-17 17:16:58 +08:00
destination.parent.mkdir(parents=True, exist_ok=True)
destination = Path(destination, jar_name)
try:
download_file(url, destination)
return destination
except Exception as e:
raise Exception(
"Unable to download server jar for version {}\nURL: {}\nError: {}".format(minecraft_version, url, e))
2026-05-17 17:16:58 +08:00
def get_latest_build_of_version(minecraft_version: str) -> str:
builds = get_specific_version_paper_builds(minecraft_version)
if not builds:
raise Exception(f"No builds found for Paper {minecraft_version}")
# Paper API usually lists builds ascending; latest is the last one
return str(builds[-1])
def download_latest_build_paper_jar(minecraft_version: str, destination_dir: Path, filename: str | None = None):
build = get_latest_build_of_version(minecraft_version)
return download_server_jar(minecraft_version, build, destination_dir, filename=filename)
2026-05-17 19:16:00 +08:00
def version_exist_from_paper(minecraft_version: str) -> bool:
try:
get_specific_version_paper_builds(minecraft_version)
return True
except Exception:
return False
def get_latest_paper_version(release) -> str:
vers = get_version_list(release=release)
index = 0
latest_paper_support_ver = None
while latest_paper_support_ver is None:
if len(vers) < index + 1:
2026-05-17 19:16:00 +08:00
raise Exception("No supported Minecraft version available for Paper support.")
if version_exist_from_paper(minecraft_version=vers[index]):
latest_paper_support_ver = vers[index]
break
index += 1
2026-05-17 19:16:00 +08:00
return latest_paper_support_ver
2026-05-17 17:16:58 +08:00
def download_latest_paper_jar(destination_dir: Path, filename: str | None = None, release: bool = True):
"""
Download latest Minecraft version (release) paper jar
"""
vers = get_version_list(release=release)
if len(vers) == 0:
raise Exception("No versions available for Minecraft (Did the server return wrong response ?)")
latest_mc = vers[0]
2026-05-17 19:16:00 +08:00
2026-05-17 21:14:40 +08:00
return download_latest_build_paper_jar(latest_mc, destination_dir, filename=filename)
def download_vanilla_server_jar(minecraft_version: str, destination: Path, filename: str | None = None):
"""
Download a vanilla Minecraft server jar from Mojang's version manifest.
"""
metadata = get_minecraft_version_metadata(minecraft_version)
server_download = metadata.get("downloads", {}).get("server")
if not server_download or not server_download.get("url"):
raise Exception(f"Minecraft version {minecraft_version} does not provide a vanilla server jar.")
url = server_download["url"]
jar_name = jar_filename(filename, f"minecraft_server.{minecraft_version}.jar")
destination.parent.mkdir(parents=True, exist_ok=True)
destination = Path(destination, jar_name)
try:
download_file(url, destination)
return destination
except Exception as e:
raise Exception("Unable to download vanilla server jar for version {}\nURL: {}\nError: {}".format(
minecraft_version,
url,
e,
))
def find_system_java_executables(java_names):
"""
Find system Java executables
:param java_names:
:return:
"""
roots = []
if os.name == "nt":
for env_name in ("ProgramFiles", "ProgramFiles(x86)"):
root = os.environ.get(env_name)
if root:
roots.append(os.path.join(root, "Java"))
elif os.name == "posix":
roots.extend(("/Library/Java/JavaVirtualMachines", "/opt/java", "/usr/lib/jvm", "/usr/local/java"))
found = []
for root in roots:
if not os.path.isdir(root):
continue
for current_root, _, files in os.walk(root):
for java_name in java_names:
if java_name in files and os.path.basename(current_root).lower() == "bin":
found.append(os.path.join(current_root, java_name))
break
return found
def major_version_from_runtime_dir(runtime_dir):
"""
Get major version from target runtime directory
(Only works if this runtime is created by launcher)
:param runtime_dir:
:return:
"""
name = runtime_dir.name
if name.lower().startswith("java_"):
return name.split("_", 1)[1]
info_path = runtime_dir / "java.version.info"
if info_path.exists():
try:
for line in info_path.read_text(encoding="utf-8").splitlines():
if line.strip().startswith("JavaMajorVersion") and "=" in line:
return line.split("=", 1)[1].strip().strip('"').strip("'")
except Exception:
return ""
return ""