Skip to content

Instantly share code, notes, and snippets.

@RedstoneWizard08
Created January 20, 2024 01:53
Show Gist options
  • Save RedstoneWizard08/9a731c53178228fd49c141e2730b8e70 to your computer and use it in GitHub Desktop.
Save RedstoneWizard08/9a731c53178228fd49c141e2730b8e70 to your computer and use it in GitHub Desktop.
py2modman cli?
#!/usr/bin/env python3
"""
####################################################################
py2modman-cli.py v0.1.0
A simple program to download & install a profile from Thunderstore.
Designed for Lethal Company (and my modpack) by @RedstoneWizard08
Licensed under the MIT license.
==== Usage ====
./py2modman-cli.py [code]
^^^ Installs the profile from the code [code] to the "pack" folder
in the current directory.
==== Examples ====
bash $ ./py2modman-cli.py 018d1dcb-6035-3764-328f-fd149b33d6a2
[INFO] Fetching profile: 018d1dcb-6035-3764-328f-fd149b33d6a2
[INFO] Found profile: "Fresh"
[1 of 41] Downloading: BepInEx/BepInExPack v5.4.2100
# ...
[41 of 41] Downloading: Tyzeron/Minimap v1.0.5
bash $
==== Speed ====
Speed may vary based on your CPU, internet connection, disk speed,
and many other factors. This is my test on a cloud server with 5
cores and 26 GB of RAM (arm64), while running a Minecraft server in
the background (I did this quickly xD).
bash $ time ./py2modman-cli.py 018d1dcb-6035-3764-328f-fd149b33d6a2
# ...
[...] 4.03s user 1.08s system 29% cpu 17.410 total
bash $
####################################################################
"""
import io
import os
import glob
import yaml
import base64
import shutil
import zipfile
import requests
import argparse
def fix_paths(dir: str):
for item in glob.glob(dir):
if os.path.isdir(item):
continue
if "\\" in item:
real = item.replace("\\", "/")
parent = os.path.dirname(real)
if not os.path.exists(parent):
os.makedirs(parent)
os.rename(item, real)
def rm_or_not(file: str):
if os.path.exists(file):
os.remove(file)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("code", type=str)
args = parser.parse_args()
code = args.code
print(f"[INFO] Fetching profile: {code}")
url = f"https://thunderstore.io/api/experimental/legacyprofile/get/{code}/"
data = requests.get(url)
data = data.text.replace("#r2modman", "")
data = base64.b64decode(data)
buf = io.BytesIO(data)
if os.path.exists("pack"):
shutil.rmtree("pack")
os.makedirs("pack")
zipf = zipfile.ZipFile(buf)
zipf.extractall(path="pack")
zipf.close()
fix_paths("pack/**")
shutil.move("pack/config", "pack/BepInEx/config")
with open("pack/export.r2x") as file:
export_raw = file.read()
export = yaml.safe_load(export_raw)
mods = export["mods"]
name = export["profileName"]
print(f"[INFO] Found profile: \"{name}\"")
for (i, mod) in enumerate(mods):
ns = mod["name"].split("-")[0]
id = "-".join(mod["name"].split("-")[1:])
ver = ".".join(str(v) for v in mod["version"].values())
print(f"[{i + 1} of {len(mods)}] Downloading: {ns}/{id} v{ver}")
url = f"https://thunderstore.io/package/download/{ns}/{id}/{ver}/"
dir = f"pack/BepInEx/plugins/{ns}-{id}"
data = requests.get(url).content
buf = io.BytesIO(data)
zipf = zipfile.ZipFile(buf)
infos = [v.filename for v in zipf.filelist]
if id == "BepInExPack":
zipf.extractall("pack")
shutil.move("pack/BepInExPack/BepInEx/core", "pack/BepInEx/core")
os.rename("pack/BepInExPack/BepInEx/config/BepInEx.cfg", "pack/BepInEx/config/BepInEx.cfg")
os.rename("pack/BepInExPack/winhttp.dll", "pack/winhttp.dll")
os.rename("pack/BepInExPack/doorstop_config.ini", "pack/doorstop_config.ini")
shutil.rmtree("pack/BepInExPack")
rm_or_not("pack/icon.png")
rm_or_not("pack/LICENSE")
rm_or_not("pack/README.md")
rm_or_not("pack/CHANGELOG.md")
rm_or_not("pack/manifest.json")
else:
if "BepInEx/" in infos:
zipf.extractall("pack")
rm_or_not("pack/icon.png")
rm_or_not("pack/LICENSE")
rm_or_not("pack/README.md")
rm_or_not("pack/CHANGELOG.md")
rm_or_not("pack/manifest.json")
elif "plugins/" in infos or "patchers/" in infos:
zipf.extractall("pack/BepInEx")
rm_or_not("pack/BepInEx/icon.png")
rm_or_not("pack/BepInEx/LICENSE")
rm_or_not("pack/BepInEx/README.md")
rm_or_not("pack/BepInEx/CHANGELOG.md")
rm_or_not("pack/BepInEx/manifest.json")
elif ns + "-" + id + "/BepInEx" in infos:
zipf.extractall("pack")
rm_or_not("pack/icon.png")
rm_or_not("pack/LICENSE")
rm_or_not("pack/README.md")
rm_or_not("pack/CHANGELOG.md")
rm_or_not("pack/manifest.json")
for f in os.listdir("pack/" + ns + "-" + id):
src = "pack/" + ns + "-" + id + f
dst = "pack/" + f
shutil.move(src, dst)
elif id + "/BepInEx" in infos:
zipf.extractall("pack")
rm_or_not("pack/icon.png")
rm_or_not("pack/LICENSE")
rm_or_not("pack/README.md")
rm_or_not("pack/CHANGELOG.md")
rm_or_not("pack/manifest.json")
for f in os.listdir("pack/" + id):
src = "pack/" + id + f
dst = "pack/" + f
shutil.move(src, dst)
elif ns + "-" + id + "/plugins" in infos or ns + "-" + id + "/patchers" in infos:
zipf.extractall("pack")
rm_or_not("pack/icon.png")
rm_or_not("pack/LICENSE")
rm_or_not("pack/README.md")
rm_or_not("pack/CHANGELOG.md")
rm_or_not("pack/manifest.json")
for f in os.listdir("pack/" + ns + "-" + id):
src = "pack/" + ns + "-" + id + f
dst = "pack/BepInEx/" + f
shutil.move(src, dst)
elif id + "/plugins" in infos or id + "/patchers" in infos:
zipf.extractall("pack")
rm_or_not("pack/icon.png")
rm_or_not("pack/LICENSE")
rm_or_not("pack/README.md")
rm_or_not("pack/CHANGELOG.md")
rm_or_not("pack/manifest.json")
for f in os.listdir("pack/" + id):
src = "pack/" + id + f
dst = "pack/BepInEx/" + f
shutil.move(src, dst)
else:
zipf.extractall(dir)
rm_or_not(dir + "/icon.png")
rm_or_not(dir + "/LICENSE")
rm_or_not(dir + "/README.md")
rm_or_not(dir + "/CHANGELOG.md")
rm_or_not(dir + "/manifest.json")
zipf.close()
fix_paths("pack/**")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment