commit 57e7ceaf0e9e2d421a4a0a9fbfddb57ed650477f Author: Big Duckie Date: Fri Nov 15 14:22:16 2024 -0700 Commit of existing code. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f406b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,291 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,pycharm +# Edit at https://www.toptal.com/developers/gitignore?templates=python,pycharm + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python,pycharm + +config.toml \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..01de0e2 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..92e05cf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/solderPacker.iml b/.idea/solderPacker.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/.idea/solderPacker.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/requirements-cli.txt b/requirements-cli.txt new file mode 100644 index 0000000..be635ea --- /dev/null +++ b/requirements-cli.txt @@ -0,0 +1,6 @@ +certifi==2024.8.30 +charset-normalizer==3.4.0 +idna==3.10 +requests==2.32.3 +text-unidecode==1.3 +urllib3==2.2.3 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..42c96e2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,13 @@ +certifi==2024.8.30 +charset-normalizer==3.4.0 +idna==3.10 +PySide6==6.8.0.2 +PySide6_Addons==6.8.0.2 +PySide6_Essentials==6.8.0.2 +python-slugify==8.0.4 +requests==2.32.3 +shiboken6==6.8.0.2 +text-unidecode==1.3 +toml==0.10.2 +urllib3==2.2.3 +validators==0.34.0 diff --git a/solderPacker.py b/solderPacker.py new file mode 100644 index 0000000..78c2147 --- /dev/null +++ b/solderPacker.py @@ -0,0 +1,147 @@ +import argparse +import logging +import os +import shutil +import zipfile + +import requests + + +class SolderPacker: + def __init__(self, solder_dir: str, solder_url: str, solder_key: str): + self.solder_dir = solder_dir + self.solder_url = solder_url + self.solder_key = solder_key + + def compress_jar(self, mod_slug: str, minecraft_version: str, mod_loader: str, mod_version: str, jar_path: str): + export_path = f"{self.solder_dir}{os.sep}{mod_slug}" + if not os.path.exists(export_path): + os.makedirs(export_path) + zip_path = f"{export_path}{os.sep}{mod_slug}-{minecraft_version}-{mod_loader}-{mod_version}.zip" + if os.path.exists(zip_path): + os.remove(zip_path) + + mod_zip = zipfile.ZipFile( + file=zip_path, + mode="w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) + mod_zip.write(jar_path, f"mods/{mod_slug}-{minecraft_version}-{mod_loader}-{mod_version}.jar") + mod_zip.close() + return zip_path + + def copy_zip(self, mod_slug: str, minecraft_version: str, mod_loader: str, mod_version: str, zip_src): + export_path = f"{self.solder_dir}{os.sep}{mod_slug}" + if not os.path.exists(export_path): + os.makedirs(export_path) + zip_path = f"{export_path}{os.sep}{mod_slug}-{minecraft_version}-{mod_loader}-{mod_version}.zip" + shutil.copy(zip_src, zip_path) + return zip_path + + def register_mod(self, mod_slug: str, mod_name: str, mod_author: str, mod_description: str, mod_url: str): + resp = requests.get(f"{self.solder_url}/api/mod/{mod_slug}") + if resp.status_code == 404: + mod_data = { + "name": mod_slug, + "pretty_name": mod_name, + "author": mod_author, + "description": mod_description, + "link": mod_url + } + requests.post(f"{self.solder_url}/mod/create", cookies={"technic_solder_session": self.solder_key}, + data=mod_data, headers={"Origin": self.solder_url, "X-Requested-With": "XMLHttpRequest"}) + + resp = requests.get(f"{self.solder_url}/api/mod/{mod_slug}") + if resp.status_code == 404: + logging.debug("failed to register mod") + return resp.json() + else: + logging.debug("mod already registered") + + def register_mod_version(self, mod_slug: str, minecraft_version: str, mod_loader: str, mod_version: str): + resp = requests.get(f"{self.solder_url}/api/mod/{mod_slug}") + if resp.status_code != 200: + logging.debug("mod doesn't exist") + return + mod_id = resp.json()["id"] + + resp = requests.get(f"{self.solder_url}/api/mod/{mod_slug}/{mod_version}") + if resp.status_code != 200: + logging.debug("creating mod version") + version_data = { + "mod-id": mod_id, + "add-version": f"{minecraft_version}-{mod_loader}-{mod_version}", + "add-md5": "" + } + resp = requests.post(f"{self.solder_url}/mod/add-version", + cookies={"technic_solder_session": self.solder_key}, + data=version_data, + headers={"Origin": self.solder_url, "X-Requested-With": "XMLHttpRequest"}) + if resp.status_code != 200: + logging.debug("failed to create mod version") + else: + logging.debug("created mod version") + else: + logging.debug("mod version already registered. updating hash") + version_data = { + "version-id": resp.json()["id"], + "md5": "" + } + resp = requests.post(f"{self.solder_url}/mod/rehash", + cookies={"technic_solder_session": self.solder_key}, + data=version_data, + headers={"Origin": self.solder_url, "X-Requested-With": "XMLHttpRequest"}) + if resp.status_code != 200: + logging.debug("failed to rehash mod version") + else: + logging.debug("rehashed mod version") + + +def main(): + try: + argp = argparse.ArgumentParser() + mod_data = argp.add_argument_group(title="Mod Data") + mod_data.add_argument("-s", "--slug", help="Mod Slug") + mod_data.add_argument("-n", "--name", help="Mod Name") + mod_data.add_argument("-v", "--version", help="Mod Version") + mod_data.add_argument("-a", "--author", help="Mod Author") + mod_data.add_argument("-d", "--description", help="Mod Description") + mod_data.add_argument("-u", "--url", help="Mod URL") + + mod_data.add_argument("-m", "--minecraft", type=str, help="Minecraft Version") + mod_data.add_argument("-l", "--loader", type=str, choices=["forge", "fabric", "quilt"], default="forge", + help="Mod Loader") + + argp.add_argument("-p", "--path", required=True, default=os.getcwd(), help="Export Path") + + solder_api = argp.add_argument_group(title="Solder API") + solder_api.add_argument("-sa", "--api", default=os.environ.get("SOLDER_URL"), type=str, + help="Solder instance URL") + solder_api.add_argument("-sk", "--key", default=os.environ.get("SOLDER_KEY"), type=str, help="Solder API Key") + + input_type = argp.add_mutually_exclusive_group(required=True) + input_type.add_argument("-j", "--jar", type=str, help="The jar file to package and upload as a mod.") + input_type.add_argument("-z", "--zip", type=str, help="The zip file to upload.") + + argp.set_defaults() + args = argp.parse_args() + + sp = SolderPacker(args.path, args.api, args.key) + if args.jar: + if not os.path.isfile(args.jar): + print("Malformed JAR path") + exit() + sp.compress_jar(args.slug, args.minecraft, args.loader, args.version, args.jar) + elif args.zip: + if not os.path.isfile(args.zip): + print("Malformed ZIP path") + exit() + sp.copy_zip(args.slug, args.minecraft, args.loader, args.version, args.zip) + + sp.register_mod(args.slug, args.name, args.author, args.description, args.url) + sp.register_mod_version(args.slug, args.minecraft, args.loader, args.version) + + except KeyboardInterrupt: + print("Packing cancelled") + + +if __name__ == '__main__': + main() diff --git a/solderPackerGUI.py b/solderPackerGUI.py new file mode 100644 index 0000000..b798fa3 --- /dev/null +++ b/solderPackerGUI.py @@ -0,0 +1,337 @@ +import json +import logging +import os +import re +import sys +import traceback +import zipfile + +import PySide6.QtCore as QtC +import PySide6.QtGui as QtG +import PySide6.QtWidgets as QtW +import toml +from slugify import slugify + +from solderPacker import SolderPacker + +# Globals +config = {} +base_path = os.path.dirname(os.path.realpath(__file__)) + + +class Form(QtW.QMainWindow): + def __init__(self): + QtW.QMainWindow.__init__(self) + self.setMinimumSize(400, 500) + self.setWindowIcon(QtG.QIcon(f"{base_path}{os.sep}wrenchIcon.ico")) + self.setWindowTitle("Solder Packer") + + self.thread_pool = QtC.QThreadPool() + self.solder_packer = SolderPacker(config["path"], config["url"], config["token"]) + self.jar_path = "" + + menubar = self.menuBar() + options_action = menubar.addAction("Options") + options_action.triggered.connect(self.open_options) + + main_widget = QtW.QWidget() + layout = QtW.QGridLayout() + + mod_title_label = QtW.QLabel("Mod Name") + self.mod_title_edit = QtW.QLineEdit("") + layout.addWidget(mod_title_label, 1, 1) + layout.addWidget(self.mod_title_edit, 1, 2) + + mod_slug_label = QtW.QLabel("Mod Slug") + self.mod_slug_edit = QtW.QLineEdit("") + layout.addWidget(mod_slug_label, 2, 1) + layout.addWidget(self.mod_slug_edit, 2, 2) + + mod_version_label = QtW.QLabel("Mod Version") + self.mod_version_edit = QtW.QLineEdit("") + layout.addWidget(mod_version_label, 3, 1) + layout.addWidget(self.mod_version_edit, 3, 2) + + mod_mc_version_label = QtW.QLabel("MC Version") + self.mod_mc_version_edit = QtW.QLineEdit("") + layout.addWidget(mod_mc_version_label, 4, 1) + layout.addWidget(self.mod_mc_version_edit, 4, 2) + + mod_loader_label = QtW.QLabel("Mod Loader") + self.mod_loader_edit = QtW.QLineEdit("") + layout.addWidget(mod_loader_label, 5, 1) + layout.addWidget(self.mod_loader_edit, 5, 2) + + mod_author_label = QtW.QLabel("Mod Author") + self.mod_author_edit = QtW.QLineEdit("") + layout.addWidget(mod_author_label, 6, 1) + layout.addWidget(self.mod_author_edit, 6, 2) + + mod_url_label = QtW.QLabel("Mod URL") + self.mod_url_edit = QtW.QLineEdit("") + layout.addWidget(mod_url_label, 7, 1) + layout.addWidget(self.mod_url_edit, 7, 2) + + mod_desc_label = QtW.QLabel("Mod Description") + mod_desc_label.setAlignment(QtC.Qt.AlignmentFlag.AlignCenter) + self.mod_desc_edit = QtW.QPlainTextEdit("") + self.mod_desc_edit.setMinimumSize(200, 50) + layout.addWidget(mod_desc_label, 8, 1, 1, 2) + layout.addWidget(self.mod_desc_edit, 9, 1, 1, 2) + + horizontal_line = QtW.QFrame() + horizontal_line.setFrameShape(QtW.QFrame.Shape.HLine) + horizontal_line.setFrameShadow(QtW.QFrame.Shadow.Sunken) + layout.addWidget(horizontal_line, 10, 1, 1, 2) + + self.drop_label = FileDropArea("Drop JAR Here", self) + self.drop_label.setMinimumSize(200, 50) + self.drop_label.setAlignment(QtC.Qt.AlignmentFlag.AlignCenter) + self.drop_label.setFrameStyle(QtW.QFrame.Shadow.Sunken | QtW.QFrame.Shape.StyledPanel) + self.drop_label.dropped.connect(self.file_dropped) + layout.addWidget(self.drop_label, 11, 1, 1, 2) + + self.btn_process = QtW.QPushButton("Process") + self.btn_process.clicked.connect(self.process_click) + layout.addWidget(self.btn_process, 12, 1, 1, 2) + + self.setCentralWidget(main_widget) + main_widget.setLayout(layout) + + def open_options(self): + options_dialog = OptionsDialog(parent=self) + options_dialog.closed.connect(self.options_closed) + options_dialog.show() + + def options_closed(self): + self.solder_packer.solder_dir = config["path"] + self.solder_packer.solder_url = config["url"] + self.solder_packer.solder_key = config["token"] + + def file_dropped(self, file_path: str): + self.jar_path = file_path + if config["extract"]: + jar = zipfile.ZipFile(file_path, "r") + if "META-INF/mods.toml" in jar.namelist(): + # This jar file uses FML + logging.debug("identified mod as forge mod") + mod_toml = toml.loads(jar.read("META-INF/mods.toml").decode("utf-8")) + # This mess is to catch if the mod is using the Jar version for versioning. It extracts it from the + # MANIFEST.MF and places it where it should be in the toml. + if mod_toml["mods"][0]["version"] == "${file.jarVersion}": + mod_toml["mods"][0]["version"] = re.findall( + r"Implementation-Version: (.*)", jar.read("META-INF/MANIFEST.MF").decode("utf-8"))[0] + + # Update form with data + self.mod_title_edit.setText(mod_toml.get("mods", [{}])[0].get("displayName", "")) + self.mod_slug_edit.setText(slugify(mod_toml.get("mods", [{}])[0].get("displayName", ""), + replacements=[["&", "and"], ["$", "s"], ["'", ""]])) + self.mod_version_edit.setText(mod_toml.get("mods", [{}])[0].get("version", "")) + self.mod_loader_edit.setText("forge") + self.mod_author_edit.setText(mod_toml.get("mods", [{}])[0].get("authors", "")) + self.mod_url_edit.setText(mod_toml.get("mods", [{}])[0].get("displayURL", "")) + self.mod_desc_edit.setPlainText(mod_toml.get("mods", [{}])[0].get("description", "")) + elif "fabric.mod.json" in jar.namelist(): + # This jar file uses Fabric + logging.debug("identified mod as fabric mod") + mod_json = json.loads(jar.read("fabric.mod.json").decode("utf-8")) + + # Update form with data + self.mod_title_edit.setText(mod_json.get("name", "")) + self.mod_slug_edit.setText(slugify(mod_json.get("name", ""), + replacements=[["&", "and"], ["$", "s"], ["'", ""]])) + self.mod_version_edit.setText(mod_json.get("version", "")) + self.mod_loader_edit.setText("fabric") + + # Some spaghetti code for dealing with the possibility of authors being a []string or a dict. + authors = mod_json.get("authors", []) + author_list = [] + if len(authors) > 0 and type(authors[0]) == dict: + for author in authors: + author_list.append(author.get("name")) + elif len(authors) > 0 and type(authors[0]) == str: + author_list = authors + self.mod_author_edit.setText(", ".join(author_list)) + self.mod_url_edit.setText(mod_json.get("contact", {}).get("homepage", "")) + self.mod_desc_edit.setPlainText(mod_json.get("description", "")) + elif "quilt.mod.json" in jar.namelist(): + # This jar file uses Quilt + logging.debug("identified mod as quilt mod") + mod_json = json.loads(jar.read("quilt.mod.json").decode("utf-8")) + + # Update form with data + self.mod_title_edit.setText(mod_json.get("quilt_loader", {}).get("metadata", {}).get("name", "")) + self.mod_slug_edit.setText(slugify(mod_json.get("quilt_loader", {}).get("metadata", {}).get("name", ""), + replacements=[["&", "and"], ["$", "s"], ["'", ""]])) + self.mod_version_edit.setText(mod_json.get("quilt_loader", {}).get("version", "")) + self.mod_loader_edit.setText("quilt") + self.mod_author_edit.setText( + ", ".join(mod_json.get("quilt_loader", {}).get("metadata", {}).get("contributors", {}).keys())) + self.mod_url_edit.setText( + mod_json.get("quilt_loader", {}).get("metadata", {}).get("contact", {}).get("homepage", "")) + self.mod_desc_edit.setPlainText( + mod_json.get("quilt_loader", {}).get("metadata", {}).get("description", "")) + else: + logging.debug("failed to identify mod loader") + jar.close() + + def process_click(self): + self.btn_process.setEnabled(False) + self.btn_process.setText("Processing...") + + worker = Worker(self.process, self.mod_slug_edit.text(), self.mod_title_edit.text(), + self.mod_version_edit.text(), self.mod_mc_version_edit.text(), self.mod_loader_edit.text(), + self.mod_author_edit.text(), self.mod_url_edit.text(), self.mod_desc_edit.toPlainText(), + self.jar_path) + + worker.signals.finished.connect(self.finished_processing) + worker.signals.error.connect(self.failed_processing) + self.thread_pool.start(worker) + + def process(self, mod_slug: str, mod_title: str, mod_version: str, mod_mc_version: str, mod_loader: str, + mod_author: str, mod_url: str, mod_desc: str, jar_path: str): + logging.debug("attempting jar compression") + self.solder_packer.compress_jar(mod_slug, mod_mc_version, mod_loader, mod_version, jar_path) + logging.debug("jar compression done") + if config["upload"]: + logging.debug("attempting to register mod with solder") + self.solder_packer.register_mod(mod_slug, mod_title, mod_author, mod_desc, mod_url) + logging.debug("registration complete") + logging.debug("attempting to register mod version with solder") + self.solder_packer.register_mod_version(mod_slug, mod_mc_version, mod_loader, mod_version) + logging.debug("registration complete") + + def finished_processing(self): + self.btn_process.setEnabled(True) + self.btn_process.setText("Process") + + def failed_processing(self): + dialog = QtW.QMessageBox(self) + dialog.setWindowTitle("Error") + dialog.setText("There was an error processing this JAR.") + dialog.exec() + + self.finished_processing() + + +class FileDropArea(QtW.QLabel): + dropped = QtC.Signal(str) + + def __init__(self, title, parent): + super(FileDropArea, self).__init__(title, parent) + self.parent = parent + self.setAcceptDrops(True) + + def dragEnterEvent(self, e): + e.accept() + + @QtC.Slot() + def dropEvent(self, e): + file_path = e.mimeData().urls()[0].toLocalFile() + self.dropped.emit(file_path) + self.setText(os.path.basename(file_path)) + + +class OptionsDialog(QtW.QDialog): + closed = QtC.Signal() + + def __init__(self, parent=None): + QtW.QDialog.__init__(self, parent) + + self.setMinimumSize(300, 200) + + self.setWindowIcon(QtG.QIcon(f"{base_path}{os.sep}wrenchIcon.ico")) + self.setWindowTitle("Options") + + layout = QtW.QGridLayout() + self.setLayout(layout) + + layout.addWidget(QtW.QLabel("Export Folder"), 1, 1) + layout.addWidget(QtW.QLabel("Technic URL"), 2, 1) + layout.addWidget(QtW.QLabel("Technic Cookie"), 3, 1) + + self.input_export_folder = QtW.QLineEdit(config["path"]) + layout.addWidget(self.input_export_folder, 1, 2) + self.input_technic_url = QtW.QLineEdit(config["url"]) + layout.addWidget(self.input_technic_url, 2, 2) + self.input_technic_cookie = QtW.QLineEdit(config["token"]) + layout.addWidget(self.input_technic_cookie, 3, 2) + + self.checkbox_extract_data = QtW.QCheckBox("Extract Mod Info") + self.checkbox_extract_data.setChecked(config["extract"]) + layout.addWidget(self.checkbox_extract_data, 4, 1) + + self.checkbox_upload_data = QtW.QCheckBox("Upload Data") + self.checkbox_upload_data.setChecked(config["upload"]) + layout.addWidget(self.checkbox_upload_data, 4, 2) + + button_save = QtW.QPushButton("Save") + layout.addWidget(button_save, 5, 1, 1, 2) + button_save.clicked.connect(self.save_options) + + def save_options(self): + config["path"] = self.input_export_folder.text() + config["url"] = self.input_technic_url.text() + config["token"] = self.input_technic_cookie.text() + config["extract"] = (self.checkbox_extract_data.checkState() == QtC.Qt.CheckState.Checked) + config["upload"] = (self.checkbox_upload_data.checkState() == QtC.Qt.CheckState.Checked) + + with open(f"{base_path}{os.sep}config.toml", "w") as f: + toml.dump(config, f) + f.close() + self.close() + + @QtC.Slot() + def closeEvent(self, event): + self.closed.emit() + + +class WorkerSignals(QtC.QObject): + finished = QtC.Signal() + error = QtC.Signal(tuple) + + +class Worker(QtC.QRunnable): + def __init__(self, fn, *args, **kwargs): + super().__init__() + self.fn = fn + self.args = args + self.kwargs = kwargs + self.signals = WorkerSignals() + + @QtC.Slot() + def run(self): + try: + self.fn(*self.args, **self.kwargs) + except: + exec_type, value = sys.exc_info()[:2] + self.signals.error.emit((exec_type, value, traceback.format_exc())) + finally: + self.signals.finished.emit() + + +def get_default(struct: dict, key: str, default: any): + return default if key not in struct else struct[key] + + +if __name__ == '__main__': + logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) + + try: + config = toml.load(f"{base_path}{os.sep}config.toml") + except FileNotFoundError: + config = {} + + config["path"] = get_default(config, "path", os.getcwd()) + config["url"] = get_default(config, "url", os.environ.get("SOLDER_URL") or "http://localhost") + config["token"] = get_default(config, "token", os.environ.get("SOLDER_KEY") or "") + config["extract"] = get_default(config, "extract", True) + config["upload"] = get_default(config, "upload", False) + with open(f"{base_path}{os.sep}config.toml", "w") as f: + toml.dump(config, f) + f.close() + + app = QtW.QApplication(sys.argv) + form = Form() + form.show() + sys.exit(app.exec()) diff --git a/wrenchIcon.ico b/wrenchIcon.ico new file mode 100644 index 0000000..58f56f0 Binary files /dev/null and b/wrenchIcon.ico differ