Delete instances/forge-1.20.1-survival/download_mods.py
This commit is contained in:
parent
18b930f5cc
commit
e2c1d50a44
1 changed files with 0 additions and 231 deletions
|
|
@ -1,231 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
TARGET_DIR = "./data/mods"
|
|
||||||
MC_VERSION = "1.20.1"
|
|
||||||
LOADER = "forge"
|
|
||||||
MAX_WORKERS = 8
|
|
||||||
USER_AGENT = "MCServerModDownloader/2.0 (admin@server.local)"
|
|
||||||
|
|
||||||
# Explicit Slug Overrides for Modrinth
|
|
||||||
|
|
||||||
SLUG_OVERRIDES = {
|
|
||||||
"perspatium": "ad-astra-per-spatium-et-tempus",
|
|
||||||
"planets+": "ad-astra-planets",
|
|
||||||
"ad_astra_more_structures": "ad-astra-more-structures",
|
|
||||||
"alexsmobs": "alexs-mobs",
|
|
||||||
"architectury": "architectury-api",
|
|
||||||
"cloth-config": "cloth-config",
|
|
||||||
"create_ad_astra_recipes": "create-ad-astra-recipes",
|
|
||||||
"ferritecore": "ferrite-core",
|
|
||||||
"flan": "flan",
|
|
||||||
"improvedmobs": "improved-mobs",
|
|
||||||
"jei": "jei",
|
|
||||||
"patchouli": "patchouli",
|
|
||||||
"player-animation-lib": "playeranimator",
|
|
||||||
"resourcefulconfig": "resourceful-config",
|
|
||||||
"resourcefullib": "resourceful-lib",
|
|
||||||
"simplebackups": "simple-backups",
|
|
||||||
"sophisticatedcore": "sophisticated-core",
|
|
||||||
"tectonic": "tectonic",
|
|
||||||
"spark": "spark",
|
|
||||||
"tenshilib": "tenshilib",
|
|
||||||
"voicechat": "simple-voice-chat",
|
|
||||||
"yungsapi": "yungs-api",
|
|
||||||
"yungsbetterdungeons": "yungs-better-dungeons",
|
|
||||||
"yungsbettermineshafts": "yungs-better-mineshafts",
|
|
||||||
"yungsbetterstrongholds": "yungs-better-strongholds",
|
|
||||||
}
|
|
||||||
|
|
||||||
SERVER_MODS = [
|
|
||||||
"[forge]ctov-3.4.14.jar",
|
|
||||||
"ad_astra-forge-1.20.1-1.15.20.jar",
|
|
||||||
"ad_astra_extra_additions-1.20.1-1.1.1.jar",
|
|
||||||
"ad_astra_more_structures-1.0.1-forge.jar",
|
|
||||||
"alexsmobs-1.22.9.jar",
|
|
||||||
"architectury-9.2.14-forge.jar",
|
|
||||||
"bettercombat-forge-1.9.0+1.20.1.jar",
|
|
||||||
"Botania-1.20.1-454-FORGE.jar",
|
|
||||||
"botarium-forge-1.20.1-2.3.4.jar",
|
|
||||||
"citadel-2.6.3-1.20.1.jar",
|
|
||||||
"cloth-config-11.1.136-forge.jar",
|
|
||||||
"Clumps-forge-1.20.1-12.0.0.4.jar",
|
|
||||||
"connectivity-1.20.1-7.6.jar",
|
|
||||||
"corpse-forge-1.20.1-1.0.23.jar",
|
|
||||||
"create-1.20.1-6.0.8.jar",
|
|
||||||
"create_ad_astra_recipes-1.0.0-forge-1.20.1.jar",
|
|
||||||
"createaddition-1.20.1-1.3.3.jar",
|
|
||||||
"cupboard-1.20.1-3.7.jar",
|
|
||||||
"curios-forge-5.14.1+1.20.1.jar",
|
|
||||||
"decocraft-3.0.4-1.20.1-slim.jar",
|
|
||||||
"DistantHorizons-2.1.2-a-1.20.1.jar",
|
|
||||||
"FarmersDelight-1.20.1-1.3.2.jar",
|
|
||||||
"ferritecore-6.0.1-forge.jar",
|
|
||||||
"flan-1.20.1-1.11.16-forge.jar",
|
|
||||||
"geckolib-forge-1.20.1-4.8.4.jar",
|
|
||||||
"ImmersiveEngineering-1.20.1-10.2.0-183.jar",
|
|
||||||
"improvedmobs-1.20.1-1.13.7-forge.jar",
|
|
||||||
"irons_lib-1.20.1-2.1.0.jar",
|
|
||||||
"irons_spellbooks-1.20.1-3.16.2.jar",
|
|
||||||
"Jade-1.20.1-Forge-11.13.3.jar",
|
|
||||||
"jei-1.20.1-forge-15.48.0.180.jar",
|
|
||||||
"journeymap-forge-1.20.1-6.0.0.jar",
|
|
||||||
"Krypton Reno-forge-1.20.1-26.1.1-1.20.1.jar",
|
|
||||||
"lithostitched-forge-1.20.1-1.4.11.jar",
|
|
||||||
"locks_reforged-1.6.1.jar",
|
|
||||||
"lts_auth-1.0.1+mc1.20.1.jar",
|
|
||||||
"LuckPerms-Forge-5.4.102.jar",
|
|
||||||
"majrusz-library-forge-1.20.1-7.0.8.jar",
|
|
||||||
"majruszs-difficulty-forge-1.20.1-1.9.10.jar",
|
|
||||||
"mcda-5.0.2.jar",
|
|
||||||
"mcdw-9.0.4.jar",
|
|
||||||
"minecraft-comes-alive-7.6.28-beta.10+1.20.1-universal.jar",
|
|
||||||
"modernfix-forge-5.27.66+mc1.20.1.jar",
|
|
||||||
"Nullscape_1.20.x_v1.2.8.jar",
|
|
||||||
"Patchouli-1.20.1-85-FORGE.jar",
|
|
||||||
"perspatium-1.20.1-1.2.0.jar",
|
|
||||||
"planets+-bv1.7.5-1.20x.jar",
|
|
||||||
"player-animation-lib-forge-1.0.2-rc1+1.20.jar",
|
|
||||||
"resourcefulconfig-forge-1.20.1-2.1.3.jar",
|
|
||||||
"resourcefullib-forge-1.20.1-2.1.29.jar",
|
|
||||||
"SimpleBackups-1.20.1-3.1.24.jar",
|
|
||||||
"sophisticatedbackpacks-1.20.1-3.24.63.2049.jar",
|
|
||||||
"sophisticatedcore-1.20.1-1.3.75.2231.jar",
|
|
||||||
"spark-1.10.53-forge.jar",
|
|
||||||
"tectonic-3.0.17-forge-1.20.1.jar",
|
|
||||||
"tenshilib-1.20.1-1.7.6-forge.jar",
|
|
||||||
"Terralith_1.20.x_v2.5.4.jar",
|
|
||||||
"voicechat-forge-1.20.1-2.6.22.jar",
|
|
||||||
"YungsApi-1.20-Forge-4.0.6.jar",
|
|
||||||
"YungsBetterDungeons-1.20-Forge-4.0.4.jar",
|
|
||||||
"YungsBetterMineshafts-1.20-Forge-4.0.4.jar",
|
|
||||||
"YungsBetterStrongholds-1.20-Forge-4.0.3.jar",
|
|
||||||
]
|
|
||||||
|
|
||||||
def clean_mod_name(filename):
|
|
||||||
"""Strip prefixed metadata and digits to extract purely the mod identifier."""
|
|
||||||
name = re.sub(r"^\[.*?\]", "", filename)
|
|
||||||
name = re.sub(r"\.jar$", "", name)
|
|
||||||
|
|
||||||
# Split tokens by hyphen/underscore and keep tokens before version digits
|
|
||||||
tokens = re.split(r"[-_]", name)
|
|
||||||
clean_tokens = []
|
|
||||||
for token in tokens:
|
|
||||||
if re.match(r"^(v?\d|bv?\d|forge|fabric|quilt|mc1|1\.20|universal|slim)", token, re.I):
|
|
||||||
break
|
|
||||||
clean_tokens.append(token)
|
|
||||||
|
|
||||||
result = "-".join(clean_tokens).strip("-")
|
|
||||||
return result if result else name
|
|
||||||
|
|
||||||
def http_get_json(url):
|
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
|
||||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
||||||
return json.loads(resp.read().decode("utf-8"))
|
|
||||||
|
|
||||||
def download_file(url, target_path):
|
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
|
||||||
with urllib.request.urlopen(req, timeout=30) as resp, open(target_path, "wb") as f:
|
|
||||||
f.write(resp.read())
|
|
||||||
|
|
||||||
def process_mod(jar_name):
|
|
||||||
target_path = os.path.join(TARGET_DIR, jar_name)
|
|
||||||
|
|
||||||
if os.path.exists(target_path):
|
|
||||||
return ("EXISTS", jar_name, "File already in destination")
|
|
||||||
|
|
||||||
raw_clean = clean_mod_name(jar_name)
|
|
||||||
slug = SLUG_OVERRIDES.get(raw_clean.lower(), raw_clean.lower())
|
|
||||||
|
|
||||||
project_id = None
|
|
||||||
|
|
||||||
# Step 1: Try direct project lookup by slug
|
|
||||||
try:
|
|
||||||
proj_data = http_get_json(f"https://api.modrinth.com/v2/project/{slug}")
|
|
||||||
if proj_data and "id" in proj_data:
|
|
||||||
project_id = proj_data["id"]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Step 2: Fallback search if direct slug lookup fails
|
|
||||||
if not project_id:
|
|
||||||
try:
|
|
||||||
search_q = slug.replace("-", " ")
|
|
||||||
facets = json.dumps([["categories:" + LOADER], ["versions:" + MC_VERSION]])
|
|
||||||
query_url = f"https://api.modrinth.com/v2/search?query={urllib.parse.quote(search_q)}&facets={urllib.parse.quote(facets)}"
|
|
||||||
data = http_get_json(query_url)
|
|
||||||
hits = data.get("hits", [])
|
|
||||||
if hits:
|
|
||||||
project_id = hits[0]["project_id"]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if not project_id:
|
|
||||||
return ("NOT_FOUND", jar_name, f"Could not resolve Modrinth project for '{slug}'")
|
|
||||||
|
|
||||||
# Step 3: Fetch versions and download
|
|
||||||
try:
|
|
||||||
ver_url = f"https://api.modrinth.com/v2/project/{project_id}/version?loaders=[%22{LOADER}%22]&game_versions=[%22{MC_VERSION}%22]"
|
|
||||||
versions = http_get_json(ver_url)
|
|
||||||
|
|
||||||
if not versions:
|
|
||||||
return ("NOT_FOUND", jar_name, f"No {LOADER} {MC_VERSION} release on Modrinth")
|
|
||||||
|
|
||||||
download_url = None
|
|
||||||
for ver in versions:
|
|
||||||
for f in ver.get("files", []):
|
|
||||||
if f.get("filename") == jar_name:
|
|
||||||
download_url = f.get("url")
|
|
||||||
break
|
|
||||||
if download_url:
|
|
||||||
break
|
|
||||||
|
|
||||||
if not download_url and versions[0].get("files"):
|
|
||||||
download_url = versions[0]["files"][0].get("url")
|
|
||||||
|
|
||||||
if download_url:
|
|
||||||
download_file(download_url, target_path)
|
|
||||||
return ("SUCCESS", jar_name, "Downloaded from Modrinth")
|
|
||||||
else:
|
|
||||||
return ("NOT_FOUND", jar_name, "No download URL available")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return ("ERROR", jar_name, str(e))
|
|
||||||
|
|
||||||
def main():
|
|
||||||
os.makedirs(TARGET_DIR, exist_ok=True)
|
|
||||||
print(f"🚀 Starting download for {len(SERVER_MODS)} mods into '{TARGET_DIR}'...")
|
|
||||||
print(f"⚙️ Target: Minecraft {MC_VERSION} ({LOADER.upper()}) | Workers: {MAX_WORKERS}\n")
|
|
||||||
|
|
||||||
results = {"SUCCESS": [], "EXISTS": [], "NOT_FOUND": [], "ERROR": []}
|
|
||||||
|
|
||||||
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
|
||||||
futures = {executor.submit(process_mod, mod): mod for mod in SERVER_MODS}
|
|
||||||
for future in as_completed(futures):
|
|
||||||
status, jar_name, msg = future.result()
|
|
||||||
results[status].append((jar_name, msg))
|
|
||||||
|
|
||||||
symbol = "✅" if status == "SUCCESS" else "⏭️" if status == "EXISTS" else "❌"
|
|
||||||
print(f"{symbol} [{status}] {jar_name} -> {msg}")
|
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
|
||||||
print("📊 DOWNLOAD SUMMARY")
|
|
||||||
print("=" * 60)
|
|
||||||
print(f" ✅ Downloaded: {len(results['SUCCESS'])}")
|
|
||||||
print(f" ⏭️ Skipped: {len(results['EXISTS'])}")
|
|
||||||
print(f" ❌ Not Found: {len(results['NOT_FOUND']) + len(results['ERROR'])}")
|
|
||||||
|
|
||||||
missing = results['NOT_FOUND'] + results['ERROR']
|
|
||||||
if missing:
|
|
||||||
print("\n⚠️ The following mods were not found automatically:")
|
|
||||||
for jar_name, msg in missing:
|
|
||||||
print(f" • {jar_name} ({msg})")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Loading…
Reference in a new issue