diff --git a/changelog.md b/changelog.md index 76a5a3e..97e81af 100644 --- a/changelog.md +++ b/changelog.md @@ -1,11 +1,16 @@ +### Version 2.2.0 + +* [Exp] - Add experimental support for multi-sourceset projects +* [Feat] - Add support for new snapshot version formats for CurseForge +* [Feat] - Add option to not set GitHub release as latest +* [Feat] - Add Support for Modtale +* [Feat] - Add Support for uploading Hytale plugins to CurseForge and NightBloom + ### Version 2.1.8 * [Bug] - Fix commit `3562a62` that was wiped out by release 2.1.7 * [Chore] - Update dependencies * [Bug] - Fix NightBloom File ID extraction regex following API change -* [Exp] - Add experimental support for multi-sourceset projects -* [Feat] - Add support for new snapshot version formats for CurseForge -* [Feat] - Add option to not set GitHub release as latest ### Version 2.1.7 diff --git a/dummy.jar b/dummy.jar new file mode 100644 index 0000000..49f6b3a Binary files /dev/null and b/dummy.jar differ diff --git a/gradle.properties b/gradle.properties index e4ccbe5..8f865cb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,14 +1,14 @@ version_major=2 -version_minor=1 -version_patch=8 -version_build=4 +version_minor=2 +version_patch=0 +version_build=1 release_type=snapshot # Dependencies -curse4j=1.0.12 +curse4j=1.0.13 modrinth4j=2.2.0 -github=1.318 +github=1.330 mavenart=4.0.0-alpha-10 junit=5.10.1 lombok=1.18.38 -nightbloom=1.2.8 \ No newline at end of file +nightbloom=1.2.13 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd49..1b33c55 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e09..ca025c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a4..23d15a9 100644 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -112,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -203,7 +205,7 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. @@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..db3a6ac 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/license.txt b/license.txt index 4e88084..16b2dde 100644 --- a/license.txt +++ b/license.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023-2025 HypherionSA and Contributors +Copyright (c) 2023-2026 HypherionSA and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/readme.md b/readme.md index e88521d..e25b192 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,19 @@ ## ModPublisher -ModPublisher is a Gradle Plugin that allows modders to publish their mods to Modrinth, Curseforge and GitHub in one go. +ModPublisher is a Gradle Plugin that allows modders to publish their mods to Modrinth, CurseForge and GitHub in one go. No need for separate plugins, just one! --- +### Supported Platforms + +- [CurseForge](https://curseforge.com) +- [Modrinth](https://modrinth.com) +- [GitHub](https://github.com) +- [NightBloom](https://nightbloom.cc) +- [Modtale](https://modtale.net) + ### Setup For full documentation, checkout [ModPublisher Docs](https://modpublisher.fdd-docs.com/) @@ -60,8 +68,14 @@ publisher { curseforge System.getenv("CURSE_TOKEN") // GitHub Token github System.getenv("GITHUB_TOKEN") + // Modtale Token + modtale System.getenv("MODTALE_TOKEN") } + // Set the Game Type you are uploading for. This is Optional, but needed for Hytale Mods + // Current valid values are `hytale` and `minecraft`. minecraft is the default + setGameType("minecraft") + // Enable Debug mode. When enabled, no files will actually be uploaded setDebug(true) @@ -71,6 +85,9 @@ publisher { // Modrinth Project ID setModrinthID("dsgfhs79789") + // Modtale Project ID + setModtaleID("jfghg") + // Type of release. beta, alpha or release // You can also use VersionType.BETA, VersionType.ALPHA or VersionType.RELEASE setVersionType("beta") @@ -81,7 +98,7 @@ publisher { // Or https://gist.githubusercontent.com/hypherionmc/92f825d3c9337964cc77c9c8c9bf65e6/raw/ceeaaee5b98c688a23398864fe480b84796a1651/test_gist.md setChangelog("changelog.md") - // Required for Modrinth/GitHub + // Required for Modrinth/GitHub/Modtale setProjectVersion("1.20.2-${project.version}") // Fancy display name for the upload. @@ -94,15 +111,17 @@ publisher { // The modloaders your upload supports. // This can also be an Enum from ModLoader, // like setLoaders(ModLoader.FABRIC, ModLoader.FORGE) + // NOT NEEDED FOR HYTALE PLUGINS setLoaders("forge", "fabric") // The new Curseforge Environment tag. Optional // Valid values are "server", "client" or "both" // You can also use CurseEnvironment.BOTH, or CurseEnvironment.SERVER or CurseEnvironment.CLIENT + // NOT NEEDED FOR HYTALE PLUGINS setCurseEnvironment("both") // Upload the release, but keep it "private" until you are ready - // Unreleased feature on CurseForge, working on Modrinth + // Working CurseForge, not working on Modrinth setIsManualRelease(true) // The file to be uploaded. This can be a file, task, or string. @@ -117,16 +136,15 @@ publisher { // If this is a task, the task specified will be executed before publishing // Valid platforms are modrinth, curseforge and github setPlatformArtifact("modrinth", modrinthJar) - - // Disable the built in Fractureizer scanner - setDisableMalwareScanner(true) // Add supported java versions. Currently only used by CurseForge // Supports anything that can be parsed using JavaVersion.toVersion() + // NOT NEEDED FOR HYTALE PLUGINS setJavaVersions(JavaVersion.VERSION_1_8, 11) // Safety check to check if the artifact contains a valid mod metadata entry, // which could possibly mean that the jar is empty + // NOT NEEDED FOR HYTALE PLUGINS setDisableEmptyJarCheck(true) // Additional files to upload. Same as artifact, this can be a task, file or string @@ -268,8 +286,14 @@ publisher { curseforge(System.getenv("CURSE_TOKEN")) // GitHub Token github(System.getenv("GITHUB_TOKEN")) + // Modtale Token + modtale(System.getenv("MODTALE_TOKEN")) } + // Set the Game Type you are uploading for. This is Optional, but needed for Hytale Mods + // Current valid values are `hytale` and `minecraft`. minecraft is the default + gameType.set("minecraft") + // Enable Debug mode. When enabled, no files will actually be uploaded debug.set(true) @@ -279,6 +303,9 @@ publisher { // Modrinth Project ID modrinthID.set("sdjkg8867") + // Modtale Project ID + modtaleID.set("gjgndg") + // GitHub repo to publish to. Only required for GitHub githubRepo.set("OWNER/REPO") @@ -330,9 +357,6 @@ publisher { // If this is a task, the task specified will be executed before publishing // Valid platforms are modrinth, curseforge and github setPlatformArtifact("modrinth", modrinthJar) - - // Disable the built in Fractureizer scanner - disableMalwareScanner.set(true) // Safety check to check if the artifact contains a valid mod metadata entry, // which could possibly mean that the jar is empty diff --git a/src/main/java/com/hypherionmc/modpublisher/Constants.java b/src/main/java/com/hypherionmc/modpublisher/Constants.java index 713f71a..ceab201 100644 --- a/src/main/java/com/hypherionmc/modpublisher/Constants.java +++ b/src/main/java/com/hypherionmc/modpublisher/Constants.java @@ -17,6 +17,7 @@ public class Constants { public static final String GITHUB_TASK = "publishGitHub"; public static final String MODRINTH_TASK = "publishModrinth"; public static final String NIGHTBLOOM_TASK = "publishNightbloom"; + public static final String MODTALE_TASK = "publishModtale"; public static final String TASK_GROUP = "publishing"; public static final String EXTENSION_NAME = "publisher"; public static final String INTERNAL_TASK_GROUP = "modpublisher"; diff --git a/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherGradleExtension.java b/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherGradleExtension.java index 1a0c771..4c2b957 100644 --- a/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherGradleExtension.java +++ b/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherGradleExtension.java @@ -57,6 +57,9 @@ public class ModPublisherGradleExtension { // GitHub Repo. username/repo or URL @Getter private final Property githubRepo; + // Modtale Project ID + @Getter private final Property modtaleID; + // Type of release. Valid entries: release, beta, alpha @Getter private final Property versionType; @@ -117,6 +120,8 @@ public class ModPublisherGradleExtension { @Getter private final Property isManualRelease; + @Getter private final Property gameType; + // Proxy Config @Getter private final ProxyConfig proxyConfig; @@ -129,6 +134,7 @@ public ModPublisherGradleExtension(Project project) { this.modrinthID = project.getObjects().property(String.class); this.nightbloomID = project.getObjects().property(String.class); this.githubRepo = project.getObjects().property(String.class); + this.modtaleID = project.getObjects().property(String.class); this.versionType = project.getObjects().property(String.class).convention("release"); this.changelog = project.getObjects().property(Object.class); this.version = project.getObjects().property(String.class); @@ -140,6 +146,7 @@ public ModPublisherGradleExtension(Project project) { this.artifacts = new HashMap<>(); this.artifact = project.getObjects().property(Object.class); this.isManualRelease = project.getObjects().property(Boolean.class).convention(false); + this.gameType = project.getObjects().property(String.class).convention("minecraft"); // GitHub config this.github = new GithubConfig(); @@ -384,6 +391,7 @@ public void copyFrom(ModPublisherGradleExtension other, SourceSet sourceSet) { this.modrinthID.convention(other.modrinthID); this.nightbloomID.convention(other.nightbloomID); this.githubRepo.convention(other.githubRepo); + this.modtaleID.convention(other.modtaleID); this.versionType.convention(other.versionType); this.changelog.convention(other.changelog); this.version.convention(other.version); @@ -399,6 +407,7 @@ public void copyFrom(ModPublisherGradleExtension other, SourceSet sourceSet) { this.useModrinthStaging.convention(other.useModrinthStaging); this.additionalFiles.convention(other.additionalFiles); this.javaVersions.convention(other.javaVersions); + this.gameType.convention(other.gameType); if (other.artifacts != null && !other.artifacts.isEmpty()) { if (this.artifacts == null) this.artifacts = new HashMap<>(); @@ -457,6 +466,7 @@ public static class ApiKeys { private String modrinth = ""; private String github = ""; private String nightbloom = ""; + private String modtale = ""; /** * Mostly for Kotlin support @@ -493,6 +503,15 @@ public void github(String github) { public void nightbloom(String nightbloom) { this.nightbloom = nightbloom; } + + /** + * Mostly for Kotlin support + * Set the Modtale API key + * @param modtale The API Key + */ + public void modtale(String modtale) { + this.modtale = modtale; + } } /** diff --git a/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherPlugin.java b/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherPlugin.java index fc6bf38..36eb1fe 100644 --- a/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherPlugin.java +++ b/src/main/java/com/hypherionmc/modpublisher/plugin/ModPublisherPlugin.java @@ -57,6 +57,10 @@ public void apply(@Nonnull Project project) { nightbloomUploadTask.setDescription("Upload your mod to NightBloom"); nightbloomUploadTask.setGroup(TASK_GROUP); + final Task modtaleUploadTask = project.getTasks().create(MODTALE_TASK, ModtaleUploadTask.class, project, extension); + modtaleUploadTask.setDescription("Upload your mod to Modtale"); + modtaleUploadTask.setGroup(TASK_GROUP); + project.getPlugins().withId("java", p -> { Object maybeContainer = project.getExtensions().getByName("sourceSets"); @@ -91,6 +95,10 @@ public void apply(@Nonnull Project project) { ssNightbloom.setDescription("Upload '" + ssName + "' to NightBloom"); ssNightbloom.setGroup(INTERNAL_TASK_GROUP); + final Task ssmodtaleUploadTask = project.getTasks().create(MODTALE_TASK, ModtaleUploadTask.class, project, extension); + ssmodtaleUploadTask.setDescription("Upload '" + ssName + "' to Modtale"); + ssmodtaleUploadTask.setGroup(TASK_GROUP); + project.afterEvaluate(c -> { ssExt.copyFrom(extension, ss); @@ -100,10 +108,11 @@ public void apply(@Nonnull Project project) { ssGithub.setEnabled(false); ssModrinth.setEnabled(false); ssNightbloom.setEnabled(false); + ssmodtaleUploadTask.setEnabled(false); return; } - doPreChecks(project, ssExt, ssCurse, ssModrinth, ssGithub, ssNightbloom, ssUploadTask); + doPreChecks(project, ssExt, ssCurse, ssModrinth, ssGithub, ssNightbloom, ssUploadTask, ssmodtaleUploadTask); uploadTask.dependsOn(ssUploadTask); }); }); @@ -125,11 +134,11 @@ public void apply(@Nonnull Project project) { project.getLogger().lifecycle("Added Proxy Information"); } - doPreChecks(project, extension, curseUploadTask, modrinthUploadTask, gitHubUploadTask, nightbloomUploadTask, uploadTask); + doPreChecks(project, extension, curseUploadTask, modrinthUploadTask, gitHubUploadTask, nightbloomUploadTask, uploadTask, modtaleUploadTask); }); } - private void doPreChecks(Project project, ModPublisherGradleExtension extension, Task curseUploadTask, Task modrinthUploadTask, Task gitHubUploadTask, Task nightbloomUploadTask, Task uploadTask) { + private void doPreChecks(Project project, ModPublisherGradleExtension extension, Task curseUploadTask, Task modrinthUploadTask, Task gitHubUploadTask, Task nightbloomUploadTask, Task uploadTask, Task modtaleTask) { try { if (UploadPreChecks.canUploadCurse(project, extension)) { Object artifactObject = CommonUtil.getPlatformArtifact(Platform.CURSEFORGE, extension); @@ -160,6 +169,14 @@ private void doPreChecks(Project project, ModPublisherGradleExtension extension, resolveInputTask(project, artifactObject, nightbloomUploadTask); uploadTask.dependsOn(nightbloomUploadTask); } + + try { + if (UploadPreChecks.canUploadModtale(project, extension)) { + Object artifactObject = CommonUtil.getPlatformArtifact(Platform.MODTALE, extension); + resolveInputTask(project, artifactObject, modtaleTask); + uploadTask.dependsOn(modtaleTask); + } + } catch (Exception ignored) {} } catch (Exception ignored) {} } diff --git a/src/main/java/com/hypherionmc/modpublisher/properties/Platform.java b/src/main/java/com/hypherionmc/modpublisher/properties/Platform.java index 2ea2b3c..49fcfb5 100644 --- a/src/main/java/com/hypherionmc/modpublisher/properties/Platform.java +++ b/src/main/java/com/hypherionmc/modpublisher/properties/Platform.java @@ -14,5 +14,6 @@ public enum Platform { CURSEFORGE, MODRINTH, GITHUB, - NIGHTBLOOM + NIGHTBLOOM, + MODTALE } diff --git a/src/main/java/com/hypherionmc/modpublisher/tasks/CurseUploadTask.java b/src/main/java/com/hypherionmc/modpublisher/tasks/CurseUploadTask.java index 7b79f9f..e7f4157 100644 --- a/src/main/java/com/hypherionmc/modpublisher/tasks/CurseUploadTask.java +++ b/src/main/java/com/hypherionmc/modpublisher/tasks/CurseUploadTask.java @@ -13,6 +13,7 @@ import me.hypherionmc.curseupload.CurseUploadApi; import me.hypherionmc.curseupload.constants.CurseChangelogType; import me.hypherionmc.curseupload.constants.CurseReleaseType; +import me.hypherionmc.curseupload.constants.GameType; import me.hypherionmc.curseupload.requests.CurseArtifact; import org.apache.maven.artifact.versioning.DefaultArtifactVersion; import org.gradle.api.DefaultTask; @@ -64,6 +65,7 @@ public void upload() throws Exception { // Create the API Client and pass the Gradle logger as logger uploadApi = new CurseUploadApi(extension.getApiKeys().getCurseforge(), project.getLogger()); + uploadApi.setGameType(extension.getGameType().get().equalsIgnoreCase("minecraft") ? GameType.MINECRAFT : GameType.HYTALE); // Enable debug mode if required uploadApi.setDebug(extension.getDebug().get()); @@ -93,7 +95,7 @@ public void upload() throws Exception { continue; } - if (gameVersion.contains("-pre") || gameVersion.contains("-rc")) + if (gameVersion.contains("-pre") || gameVersion.contains("-rc") || gameVersion.matches("^\\d{4}\\.\\d{2}\\.\\d{2}-[a-zA-Z0-9]+$")) continue; DefaultArtifactVersion min = new DefaultArtifactVersion("b1.6.6"); @@ -112,26 +114,28 @@ public void upload() throws Exception { } } - for (String modLoader : extension.getLoaders().get()) { - // Replace `modloader` with `risugamis-modloader` - if (modLoader.equalsIgnoreCase("modloader")) { - artifact.modLoader("risugami's modloader"); - continue; - } - - // Replace `flint` with `flint loader` - if (modLoader.equalsIgnoreCase("flint")) { - artifact.modLoader("flint loader"); - continue; + if (uploadApi.getGameType() == GameType.MINECRAFT) { + for (String modLoader : extension.getLoaders().get()) { + // Replace `modloader` with `risugamis-modloader` + if (modLoader.equalsIgnoreCase("modloader")) { + artifact.modLoader("risugami's modloader"); + continue; + } + + // Replace `flint` with `flint loader` + if (modLoader.equalsIgnoreCase("flint")) { + artifact.modLoader("flint loader"); + continue; + } + + // No changes needed, pass the modloader along + artifact.modLoader(modLoader); } - - // No changes needed, pass the modloader along - artifact.modLoader(modLoader); } // Back to our regularly scheduled code // Add Curse Environment tags if they are specified - if (extension.getCurseEnvironment().isPresent() && !extension.getCurseEnvironment().get().isEmpty()) { + if (extension.getCurseEnvironment().isPresent() && !extension.getCurseEnvironment().get().isEmpty() && uploadApi.getGameType() == GameType.MINECRAFT) { String env = extension.getCurseEnvironment().get().toLowerCase(); switch (env) { @@ -142,14 +146,13 @@ public void upload() throws Exception { artifact.addGameVersion("server"); break; default: - case "both": artifact.addGameVersion("client"); artifact.addGameVersion("server"); break; } } - if (extension.getJavaVersions().isPresent() && !extension.getJavaVersions().get().isEmpty()) { + if (extension.getJavaVersions().isPresent() && !extension.getJavaVersions().get().isEmpty() && uploadApi.getGameType() == GameType.MINECRAFT) { for (JavaVersion javaVersion : extension.getJavaVersions().get()) { artifact.javaVersion("Java " + javaVersion.getMajorVersion()); } @@ -165,7 +168,7 @@ public void upload() throws Exception { artifact.manualRelease(); } - if (extension.getCurseDepends() != null) { + if (extension.getCurseDepends() != null && uploadApi.getGameType() == GameType.MINECRAFT) { extension.getCurseDepends().getRequired().get().forEach(artifact::requirement); extension.getCurseDepends().getOptional().get().forEach(artifact::optional); extension.getCurseDepends().getIncompatible().get().forEach(artifact::incompatibility); @@ -179,7 +182,9 @@ public void upload() throws Exception { } } - UploadPreChecks.checkEmptyJar(extension, uploadFile, extension.getLoaders().get()); + if (uploadApi.getGameType() == GameType.MINECRAFT) { + UploadPreChecks.checkEmptyJar(extension, uploadFile, extension.getLoaders().get()); + } // If debug mode is enabled, this will only log the JSON that will be sent and // will not actually upload the file diff --git a/src/main/java/com/hypherionmc/modpublisher/tasks/ModrinthPublishTask.java b/src/main/java/com/hypherionmc/modpublisher/tasks/ModrinthPublishTask.java index 230ae5f..1eb20e8 100644 --- a/src/main/java/com/hypherionmc/modpublisher/tasks/ModrinthPublishTask.java +++ b/src/main/java/com/hypherionmc/modpublisher/tasks/ModrinthPublishTask.java @@ -55,6 +55,11 @@ public ModrinthPublishTask(Project project, ModPublisherGradleExtension extensio */ @TaskAction public void upload() throws Exception { + if (extension.getGameType().get() == "hytale") { + project.getLogger().lifecycle("Hytale Plugins are not supported by Modrinth. Skipping..."); + return; + } + if (extension.getSourceSet() == null) { project.getLogger().lifecycle("Uploading to Modrinth"); } else { diff --git a/src/main/java/com/hypherionmc/modpublisher/tasks/ModtaleUploadTask.java b/src/main/java/com/hypherionmc/modpublisher/tasks/ModtaleUploadTask.java new file mode 100644 index 0000000..e278929 --- /dev/null +++ b/src/main/java/com/hypherionmc/modpublisher/tasks/ModtaleUploadTask.java @@ -0,0 +1,92 @@ +package com.hypherionmc.modpublisher.tasks; + +import com.hypherionmc.modpublisher.plugin.ModPublisherGradleExtension; +import com.hypherionmc.modpublisher.properties.Platform; +import com.hypherionmc.modpublisher.util.CommonUtil; +import com.hypherionmc.modpublisher.util.UploadPreChecks; +import com.hypherionmc.modpublisher.util.modtale.ModtaleApiClient; +import com.hypherionmc.modpublisher.util.modtale.meta.ModtaleMetadata; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.tasks.TaskAction; + +import javax.inject.Inject; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author HypherionSA + * Sub-Task to handle Modtale publishing. This task will only be executed if + * a Modtale API Key and Project ID is supplied + */ +public class ModtaleUploadTask extends DefaultTask { + + private final Project project; + private final ModPublisherGradleExtension extension; + + @Inject + public ModtaleUploadTask(Project project, ModPublisherGradleExtension extension) { + this.project = project; + this.extension = extension; + } + + @TaskAction + public void upload() throws Exception { + if (!extension.getGameType().get().equalsIgnoreCase("hytale")) + return; + + if (extension.getSourceSet() == null) { + project.getLogger().lifecycle("Uploading to Modtale"); + } else { + project.getLogger().lifecycle("Uploading {} to Modtale", extension.getProjectName()); + } + + UploadPreChecks.checkRequiredValues(project, Platform.MODTALE, extension); + boolean canUpload = UploadPreChecks.canUploadCurse(project, extension); + if (!canUpload) + return; + + // Modtale enforces x.x.x versioning + if (!extension.getProjectVersion().get().matches("^\\d+\\.\\d+\\.\\d+$")) { + throw new GradleException(extension.getProjectVersion().get() + " is not a valid file version for Modtale. Version needs to be in format X.Y.Z"); + } + + // Create the API Client and pass the Gradle logger as logger + ModtaleApiClient apiClient = ModtaleApiClient.of(extension.getApiKeys().getModtale(), getLogger()); + + // Enable debug mode if required + apiClient.setDebugMode(extension.getDebug().get()); + + Object artifactObject = CommonUtil.getPlatformArtifact(Platform.MODTALE, extension); + File uploadFile = CommonUtil.resolveFile(project, artifactObject); + + if (uploadFile == null || !uploadFile.exists()) + throw new FileNotFoundException("Cannot find file " + artifactObject); + + ModtaleMetadata metadata = ModtaleMetadata.builder() + .setChangelog(CommonUtil.resolveString(extension.getChangelog().get())) + .setChannel(extension.getVersionType().get().toUpperCase()) + .setVersionNumber(extension.getProjectVersion().get()) + .setGameVersions(getGameVersions(extension.getGameVersions().get())); + + // If debug mode is enabled, this will only log the JSON that will be sent and + // will not actually upload the file + apiClient.upload(extension.getModtaleID().get(), metadata, uploadFile); + } + + // TODO: This conversion needs to be automated! + private List getGameVersions(List inputVersions) { + List retVersions = new ArrayList<>(); + + for (String version : inputVersions) { + if (version.equalsIgnoreCase("early access")) continue; + retVersions.add(version); + } + + return retVersions; + } + +} diff --git a/src/main/java/com/hypherionmc/modpublisher/tasks/NightBloomUploadTask.java b/src/main/java/com/hypherionmc/modpublisher/tasks/NightBloomUploadTask.java index 167bec5..ae1d6cb 100644 --- a/src/main/java/com/hypherionmc/modpublisher/tasks/NightBloomUploadTask.java +++ b/src/main/java/com/hypherionmc/modpublisher/tasks/NightBloomUploadTask.java @@ -78,6 +78,7 @@ public void upload() throws Exception { metab.changelog(CommonUtil.resolveString(extension.getChangelog().get())); metab.type(extension.getVersionType().get().toLowerCase()); metab.version(extension.getProjectVersion().get()); + metab.game(extension.getGameType().get()); if (extension.getDisplayName().isPresent() && !extension.getDisplayName().get().isEmpty()) { metab.displayName(extension.getDisplayName().get()); @@ -87,7 +88,7 @@ public void upload() throws Exception { List finalGameVersions = new ArrayList<>(); for (String gameVersion : extension.getGameVersions().get()) { - if (gameVersion.endsWith("-snapshot")) + if (gameVersion.endsWith("-snapshot") || gameVersion.matches("^\\d{4}\\.\\d{2}\\.\\d{2}-[a-zA-Z0-9]+$")) continue; finalGameVersions.add(gameVersion.toLowerCase()); } diff --git a/src/main/java/com/hypherionmc/modpublisher/util/UploadPreChecks.java b/src/main/java/com/hypherionmc/modpublisher/util/UploadPreChecks.java index 7264d16..52d6ac1 100644 --- a/src/main/java/com/hypherionmc/modpublisher/util/UploadPreChecks.java +++ b/src/main/java/com/hypherionmc/modpublisher/util/UploadPreChecks.java @@ -43,7 +43,7 @@ public static boolean canUploadCurse(Project project, ModPublisherGradleExtensio // Check that both the Curseforge API key and Project ID is defined if (extension.getApiKeys() != null && !extension.getApiKeys().getCurseforge().isEmpty()) { if (!extension.getCurseID().isPresent() || extension.getCurseID().get().isEmpty()) { - throw new Exception("Found Curseforge API token, but curseID is not defined"); + throw new Exception("Found CurseForge API token, but curseID is not defined"); } else { return true; } @@ -56,7 +56,7 @@ public static boolean canUploadModrinth(Project project, ModPublisherGradleExten return false; if (StringUtils.isBlank(extension.getProjectVersion().getOrNull())) { - throw new Exception("Version is not defined. This is REQUIRED by modrinth"); + throw new Exception("Version is not defined. This is REQUIRED by Modrinth"); } // Check that both the Modrinth API key and Project ID is defined @@ -70,6 +70,25 @@ public static boolean canUploadModrinth(Project project, ModPublisherGradleExten return false; } + public static boolean canUploadModtale(Project project, ModPublisherGradleExtension extension) throws Exception { + if (extension == null) + return false; + + if (StringUtils.isBlank(extension.getProjectVersion().getOrNull())) { + throw new Exception("Version is not defined. This is REQUIRED by Modtale"); + } + + // Check that both the Modrinth API key and Project ID is defined + if (extension.getApiKeys() != null && !extension.getApiKeys().getModtale().isEmpty()) { + if (!extension.getModtaleID().isPresent() || extension.getModtaleID().get().isEmpty()) { + throw new Exception("Found Modtale API token, but modtaleID is not defined"); + } else { + return true; + } + } + return false; + } + public static boolean canUploadNightbloom(Project project, ModPublisherGradleExtension extension) throws Exception { if (extension == null) return false; diff --git a/src/main/java/com/hypherionmc/modpublisher/util/modtale/ModtaleApiClient.java b/src/main/java/com/hypherionmc/modpublisher/util/modtale/ModtaleApiClient.java new file mode 100644 index 0000000..fd3ada9 --- /dev/null +++ b/src/main/java/com/hypherionmc/modpublisher/util/modtale/ModtaleApiClient.java @@ -0,0 +1,104 @@ +package com.hypherionmc.modpublisher.util.modtale; + +import com.google.gson.JsonObject; +import com.hypherionmc.modpublisher.util.modtale.meta.ModtaleMetadata; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import me.hypherionmc.curseupload.CurseUploadApi; +import me.hypherionmc.curseupload.util.HTTPUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.CookieSpecs; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.gradle.api.logging.Logger; + +import java.io.File; + +@RequiredArgsConstructor(staticName = "of") +public class ModtaleApiClient { + + private final String apiKey; + private final Logger logger; + private static final String MODTALE_API = "https://api.modtale.net/api/v1/projects/%s/versions"; + + @Setter @Getter + private boolean debugMode = false; + + public void upload(String id, ModtaleMetadata metadata, File artifact) { + final HttpClient client = HttpClientBuilder.create() + .setDefaultRequestConfig(RequestConfig.custom().setCookieSpec(CookieSpecs.STANDARD).build()) + .setUserAgent("ModPublisher") + .build(); + + MultipartEntityBuilder requestBody = MultipartEntityBuilder.create().setMode(HttpMultipartMode.STRICT).setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + requestBody.addPart( + "file", + new FileBody(artifact, ContentType.APPLICATION_OCTET_STREAM, artifact.getName()) + ); + + requestBody.addTextBody( + "versionNumber", + metadata.getVersionNumber(), + ContentType.TEXT_PLAIN + ); + + requestBody.addTextBody( + "channel", + metadata.getChannel(), + ContentType.TEXT_PLAIN + ); + + for (String gv : metadata.getGameVersions()) { + requestBody.addTextBody("gameVersions", gv, ContentType.TEXT_PLAIN); + } + + requestBody.addTextBody( + "changelog", + metadata.getChangelog().isEmpty() ? "Coming Soon!" : metadata.getChangelog(), + ContentType.TEXT_PLAIN + ); + + final HttpPost request = new HttpPost(String.format(MODTALE_API, id)); + request.addHeader("X-MODTALE-KEY", apiKey); + request.setEntity(requestBody.build()); + + if (!debugMode) { + try { + final HttpResponse response = client.execute(request); + + int status = response.getStatusLine().getStatusCode(); + + String body = response.getEntity() != null + ? EntityUtils.toString(response.getEntity()) + : ""; + + if (status == 200) { + logger.lifecycle("Successfully uploaded artifact {}", artifact.getName()); + } else { + int errorCode = response.getStatusLine().getStatusCode(); + String errorMessage = response.getStatusLine().getReasonPhrase(); + logger.error("Failed to Upload artifact to Modtale. Code: {}, Error: {}, Output: {}", errorCode, errorMessage, body); + } + } catch (Exception e) { + CurseUploadApi.INSTANCE.getLogger().error("Failed to Upload artifact to Modtale.", e); + } + } else { + // Do not upload the file. Instead, write the JSON that will be sent to the console + JsonObject object = new JsonObject(); + object.add("metadata", HTTPUtils.gson.toJsonTree(metadata)); + object.addProperty("file", artifact.getName()); + + logger.lifecycle(HTTPUtils.gson.toJson(object)); + } + } + +} diff --git a/src/main/java/com/hypherionmc/modpublisher/util/modtale/meta/ModtaleMetadata.java b/src/main/java/com/hypherionmc/modpublisher/util/modtale/meta/ModtaleMetadata.java new file mode 100644 index 0000000..cadc63a --- /dev/null +++ b/src/main/java/com/hypherionmc/modpublisher/util/modtale/meta/ModtaleMetadata.java @@ -0,0 +1,59 @@ +package com.hypherionmc.modpublisher.util.modtale.meta; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ModtaleMetadata { + + private String versionNumber; + private List gameVersions; + private String changelog; + private String channel; + + public static ModtaleMetadata builder() { + return new ModtaleMetadata(); + } + + public ModtaleMetadata setVersionNumber(String version) { + this.versionNumber = version; + return this; + } + + public ModtaleMetadata addGameVersion(String gameVersion) { + if (this.gameVersions == null) + this.gameVersions = new ArrayList<>(); + + this.gameVersions.add(gameVersion); + return this; + } + + public ModtaleMetadata setGameVersions(List gameVersions) { + if (this.gameVersions == null) { + this.gameVersions = new ArrayList<>(); + } else { + this.gameVersions.clear(); + } + + this.gameVersions.addAll(gameVersions); + return this; + } + + public ModtaleMetadata setChangelog(String changelog) { + this.changelog = changelog; + return this; + } + + public ModtaleMetadata setChannel(String channel) { + this.channel = channel.toUpperCase(); + return this; + } + +} diff --git a/src/test/java/ModtaleTest.java b/src/test/java/ModtaleTest.java new file mode 100644 index 0000000..baca28d --- /dev/null +++ b/src/test/java/ModtaleTest.java @@ -0,0 +1,28 @@ +import com.hypherionmc.modpublisher.util.CommonUtil; +import com.hypherionmc.modpublisher.util.modtale.ModtaleApiClient; +import com.hypherionmc.modpublisher.util.modtale.meta.ModtaleMetadata; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; + +import static org.gradle.internal.cc.base.LoggingKt.getLogger; + +public class ModtaleTest { + + public static void main(String[] args) throws IOException { + ModtaleApiClient apiClient = ModtaleApiClient.of("", getLogger()); + //apiClient.setDebugMode(true); + + ModtaleMetadata metadata = ModtaleMetadata.builder() + .setChangelog("Testing") + .setChannel("RELEASE") + .setVersionNumber("1.0.0") + .setGameVersions(Collections.singletonList("2026.01.17-4b0f30090")); + + // If debug mode is enabled, this will only log the JSON that will be sent and + // will not actually upload the file + apiClient.upload("67a31f5e-255e-41b4-80ca-94eb58328695", metadata, new File("dummy.jar")); + } + +}