Skip to content

Instantly share code, notes, and snippets.

@Bluscream
Created April 24, 2023 00:05
Show Gist options
  • Save Bluscream/8404c5cb8899625ec93f7d2e7ccb7c45 to your computer and use it in GitHub Desktop.
Save Bluscream/8404c5cb8899625ec93f7d2e7ccb7c45 to your computer and use it in GitHub Desktop.
Minecraft Mod Sorter
import hashlib
import os
from pathlib import Path
from pprint import pformat
import json
import urllib3
import logging
import shutil
from packaging.version import parse as _parse_version
from packaging.version import InvalidVersion
from re import compile
from sanitize_filename import sanitize
from minecraft_mod_manager.gateways.jar_parser import JarParser
from minecraft_mod_manager.core.entities.mod import Mod, ModLoaders
logging.basicConfig(filename=os.path.splitext(__file__)[0]+'.log', encoding='utf-8', level=logging.DEBUG, format = '%(asctime)s:%(levelname)s:%(name)s:%(message)s')
log = logging.getLogger(__name__)
log.addHandler(logging.StreamHandler())
modlog = open(os.path.dirname(__file__)+"/mods.csv", mode="w")
modlog.write("hash;path;id;version;name;mod loader;mod loader version;mc version\n") # [';'.join(mod.__members())]
seenmd5s = set()
def dispose(error = False):
log.info("END; ERROR=%s", error)
modlog.close()
input("done")
exit(1 if error else 0)
def hash_file(path, hash_type='md5', size=65536):
func = getattr(hashlib, hash_type)()
log.debug("Hashing file %s with algorithm %s", path, hash_type)
with open(path, 'rb') as f:
b = f.read(size)
while len(b) > 0:
func.update(b)
b = f.read(size)
return func.hexdigest()
versionPattern = compile(r'\d+(=?\.(\d+(=?\.(\d+)*)*)*)*')
def extract_version(v):
result = versionPattern.search(v)
if result: return result.group(0)
return str(v)
def parse_version(v:str):
if not v: return v
if isinstance(v, list): v = v[0]
try: v = _parse_version(extract_version(v).strip("><=~").removesuffix(".x").removesuffix(".*").split("-",1)[0]) # "v"+version
except InvalidVersion as ex:
log.error("_parse_version(%s) failed:", v, ex.args[0])
return v
log.debug("Parsed version %s to %s",v,_v)
return v
def parse_forge_version(v:str):
return int(v.strip('[]()').split(",")[0])
# _t = mod.loader_version.split(",")[0]
# _t = ''.join(ch for ch in _t if ch.isalnum())
# Get Forge versions so we can resolve mc_version
forge_version_api_url = "https://meta.prismlauncher.org/v1/net.minecraftforge/index.json"
def fetch_list(location=forge_version_api_url, cache_file="forge.versions.json"):
"""Goes to the default location and returns a python list.
"""
try:
http = urllib3.PoolManager()
request = http.request("GET", location)
raw_data = request.data.decode("utf-8")
data = json.loads(raw_data)
with open(cache_file, "w") as cache: cache.write(raw_data)
return data
except (urllib3.exceptions.HTTPError, json.JSONDecodeError) as exc:
log.exception("Cannot access Intranet List Location - fetching cache")
try:
data = json.loads(open(cache_file).readlines())
return data
except (IOError, json.JSONDecodeError):
log.exception("Cache File not found or broken")
raise
forge_versions_data = fetch_list()
forge_versions = {}
for version in forge_versions_data["versions"]:
for req in version['requires']:
if req['uid'] == 'net.minecraft':
forge_versions[version['version']] = req['equals']
log.info("Got %i forge versions from %s", len(forge_versions), forge_version_api_url)
# The directory to move the mods to
mod_dir = "D:\\OneDrive\\Games\\Minecraft\\mods\\"
# Search for .jar files on the system
output = os.popen('es ext:jar').read()+"\n"+os.popen('es ext:jar.disabled').read()
# log.debug("output:",output)
jar_files = [path.strip() for path in output.split('\n') if path]
jar_folders: set[str] = set()
for jar_file in jar_files:
jar_folder = os.path.dirname(jar_file.strip())
if jar_folder: jar_folders.add(jar_folder)
log.info(f"Found {len(jar_files)} JAR files in {len(jar_folders)} folders.")
# Loop through each .jar folder
for jar_folder in sorted(jar_folders):
try:
# log.debug("Processing folder %s", jar_folder)
# Ignore folders within target folder
if mod_dir in jar_folder: continue
folder_path = Path(jar_folder)
try: is_dir = folder_path.is_dir()
except PermissionError as ex:
log.error("Not enough permissions to scan folder %s",folder_path)
continue
if not is_dir: continue
log.debug("Processing folder %s", jar_folder)
# Create a new instance of the jar parser
jar_parser = JarParser(folder_path)
# Parse the metadata of the .jar file
for mod in jar_parser.mods:
try:
mod.path = folder_path / mod.file
mod.hash = hash_file(mod.path)
if mod.hash in seenmd5s: continue
seenmd5s.add(mod.hash)
if not mod.mc_version and mod.loader_version:
if mod.mod_loader == ModLoaders.Forge:
forge_version = parse_forge_version(mod.loader_version)
mod.mc_version = forge_versions[parse_forge_version(mod.loader_version)] if forge_version in forge_versions else None
mod.loader_version = parse_version(mod.loader_version) or ""
mod.mc_version = parse_version(mod.mc_version) or ""
modlog.write(f"{mod.hash};{mod.path};{mod.id};{mod.version};{mod.name};{mod.mod_loader};{mod.loader_version};{mod.mc_version}\n") # [';'.join(mod.__members())]
# Create the directory for the Minecraft version if it doesn't already exist
version_dir = os.path.join(mod_dir, sanitize(str(mod.mc_version) or "Unknown Version"))
loader_dir = os.path.join(version_dir, sanitize(mod.mod_loader.value.title() or "Unknown Loader"))
if not os.path.isdir(loader_dir):
log.info("Creating %s", loader_dir)
os.makedirs(loader_dir, exist_ok=True)
# Move the .jar file to the appropriate directory
dest_file = os.path.join(loader_dir, mod.file)
log.info("Copying %s to %s", mod.path, dest_file)
shutil.copyfile(mod.path, dest_file)
except Exception as ex:
log.error("GOT ERROR LOL")
log.exception(ex)
input("error in mods for loop")
# input("folder done")
# # Skip files that are not recognized as Minecraft mods
# if not metadata or not metadata["minecraft_version"]:
# continue
except Exception as ex:
log.error("GOT ERROR LOL")
log.exception(ex)
input("error in jar_folders loop")
log.info("Got %s unique md5 hashes",len(seenmd5s))
dispose()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment