Skip to content

Instantly share code, notes, and snippets.

@Yoplitein
Last active June 16, 2024 07:13
Show Gist options
  • Save Yoplitein/e335e4f20775409af70d579f3dfa60da to your computer and use it in GitHub Desktop.
Save Yoplitein/e335e4f20775409af70d579f3dfa60da to your computer and use it in GitHub Desktop.
Script to download CurseForge modpacks
from urllib import request
import io
import json
import os
import sys
import time
import urllib
import zipfile
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <pack zip URL>")
raise SystemExit(1)
packZipUrl = sys.argv[1]
if "curseforge.com/api/v1/mods" not in packZipUrl:
print('Pack zip URL should look like "https://www.curseforge.com/api/v1/mods/..."')
print('(copy the manual download link, labeled "try again")')
raise SystemExit(1)
os.makedirs("mods", exist_ok = True)
print("Downloading pack zip")
with fetch(packZipUrl) as rawZip:
rawZip = io.BytesIO(rawZip.read())
with zipfile.ZipFile(rawZip) as zip:
print("Parsing manifest")
manifest = None
with zip.open("manifest.json", "r") as f:
manifest = json.load(f)
print("Downloading mods")
for file in manifest["files"]:
project = file["projectID"]
file = file["fileID"]
filename = downloadJar(project, file)
print(f"=> downloaded {filename}")
print("Unzipping config")
configs = (f for f in zip.namelist() if f.startswith("overrides/"))
for file in configs:
path = file.split("/", 1)[1]
dirname = os.path.dirname(path)
if dirname != "":
os.makedirs(os.path.dirname(path), exist_ok = True)
with zip.open(file, "r") as src:
with open(path, "wb") as dst:
pipeFile(src, dst)
print(f"=> unzipped {path}")
mcInfo = manifest["minecraft"]
print(f"\nMinecraft version {mcInfo['version']}")
print("Recommended modloader version(s):")
for loader in mcInfo["modLoaders"]:
print(f"* {loader['id']}")
print("\nDone!")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; rv:117.0) Gecko/20100101 Firefox/117.0"
}
def fetch(url):
attempts = 5
for _ in range(attempts):
try:
req = request.Request(url, headers = headers)
return request.urlopen(req, timeout = 10)
except urllib.error.URLError as err:
print(f"retrying... ({err})")
time.sleep(1)
pass
raise TimeoutError(f"Fetching `{url}` timed out after {attempts} attempts")
def downloadJar(project, file):
url = f"https://www.curseforge.com/api/v1/mods/{project}/files/{file}/download"
with fetch(url) as resp:
(_, filename) = resp.url.rsplit("/", 1)
path = f"mods/{filename}"
if os.path.exists(path):
print("...already downloaded")
return filename
with open(f"mods/{filename}", "wb") as f:
pipeFile(resp, f)
return filename
pipeBuf = bytearray(64 * 1024)
def pipeFile(src, dst):
while True:
len = src.readinto(pipeBuf)
if len > 0:
dst.write(pipeBuf[:len])
else:
break
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment