Skip to content

Instantly share code, notes, and snippets.

@Yoplitein
Created September 17, 2023 20:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • 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 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]
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):
req = request.Request(url, headers = headers)
return request.urlopen(req)
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)
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