mod download script
This commit is contained in:
parent
4327d6a828
commit
7018f1d80f
63 changed files with 231 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
instances/forge-1.20.1-survival/data/mods/[forge]ctov-3.4.14.jar
Normal file
BIN
instances/forge-1.20.1-survival/data/mods/[forge]ctov-3.4.14.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
instances/forge-1.20.1-survival/data/mods/alexsmobs-1.22.9.jar
Normal file
BIN
instances/forge-1.20.1-survival/data/mods/alexsmobs-1.22.9.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
instances/forge-1.20.1-survival/data/mods/mcda-5.0.2.jar
Normal file
BIN
instances/forge-1.20.1-survival/data/mods/mcda-5.0.2.jar
Normal file
Binary file not shown.
BIN
instances/forge-1.20.1-survival/data/mods/mcdw-9.0.4.jar
Normal file
BIN
instances/forge-1.20.1-survival/data/mods/mcdw-9.0.4.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
231
instances/forge-1.20.1-survival/download_mods.py
Executable file
231
instances/forge-1.20.1-survival/download_mods.py
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
#!/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