Commit of existing code.
This commit is contained in:
+291
@@ -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
|
||||||
Generated
+8
@@ -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
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (solderPacker) (2)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/solderPacker.iml" filepath="$PROJECT_DIR$/.idea/solderPacker.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
+147
@@ -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()
|
||||||
@@ -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())
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Reference in New Issue
Block a user