From 1eb74aebfcda55c0e3476f21468797d1c3c6910d Mon Sep 17 00:00:00 2001 From: ACGaming <4818419+ACGaming@users.noreply.github.com> Date: Sat, 10 Aug 2024 12:58:24 +0200 Subject: [PATCH] Add Modrinth remote mod handling Documentation: https://github.com/TerraFirmaCraft-The-Final-Frontier/FileDirector/wiki/Remote-Mod-Type:-Modrinth Closes #28 --- build.gradle | 2 +- .../ConfigurationController.java | 14 +- .../configuration/type/ModrinthRemoteMod.java | 128 ++++++++++++++++++ 3 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 mod-director-core/src/main/java/net/jan/moddirector/core/configuration/type/ModrinthRemoteMod.java diff --git a/build.gradle b/build.gradle index 7c98c9e..261de8a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,2 +1,2 @@ group 'net.jan' -version '1.8.3' +version '1.9.0' diff --git a/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/ConfigurationController.java b/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/ConfigurationController.java index 12f3a34..f4bbde5 100644 --- a/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/ConfigurationController.java +++ b/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/ConfigurationController.java @@ -6,10 +6,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import net.jan.moddirector.core.ModDirector; import net.jan.moddirector.core.configuration.modpack.ModpackConfiguration; -import net.jan.moddirector.core.configuration.type.CurseRemoteMod; -import net.jan.moddirector.core.configuration.type.ModifyMod; -import net.jan.moddirector.core.configuration.type.RemoteConfig; -import net.jan.moddirector.core.configuration.type.UrlRemoteMod; +import net.jan.moddirector.core.configuration.type.*; import net.jan.moddirector.core.logging.ModDirectorSeverityLevel; import net.jan.moddirector.core.manage.ModDirectorError; import net.jan.moddirector.core.util.IOOperation; @@ -138,6 +135,13 @@ private void handleBundleConfig(Path configurationPath) { } } + jsonArray = jsonObject.getAsJsonArray("modrinth"); + if(jsonArray != null) { + for(JsonElement jsonElement : jsonArray) { + configurations.add(OBJECT_MAPPER.readValue(jsonElement.toString(), ModrinthRemoteMod.class)); + } + } + jsonArray = jsonObject.getAsJsonArray("url"); if(jsonArray != null) { for(JsonElement jsonElement : jsonArray) { @@ -244,6 +248,8 @@ private Class getTypeForFile(Path file) { String name = file.toString(); if(name.endsWith(".curse.json")) { return CurseRemoteMod.class; + } else if(name.endsWith(".modrinth.json")) { + return ModrinthRemoteMod.class; } else if(name.endsWith(".url.json")) { return UrlRemoteMod.class; } else { diff --git a/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/type/ModrinthRemoteMod.java b/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/type/ModrinthRemoteMod.java new file mode 100644 index 0000000..a244ca1 --- /dev/null +++ b/mod-director-core/src/main/java/net/jan/moddirector/core/configuration/type/ModrinthRemoteMod.java @@ -0,0 +1,128 @@ +package net.jan.moddirector.core.configuration.type; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import net.jan.moddirector.core.ModDirector; +import net.jan.moddirector.core.configuration.*; +import net.jan.moddirector.core.exception.ModDirectorException; +import net.jan.moddirector.core.manage.ProgressCallback; +import net.jan.moddirector.core.util.IOOperation; +import net.jan.moddirector.core.util.WebClient; +import net.jan.moddirector.core.util.WebGetResponse; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +public class ModrinthRemoteMod extends ModDirectorRemoteMod { + private final String addonId; + private final String fileId; + private final String fileName; + + private ModrinthAddonFileInformation fileInformation; + private String projectTitle; + + @JsonCreator + public ModrinthRemoteMod( + @JsonProperty(value = "addonId", required = true) String addonId, + @JsonProperty(value = "fileId", required = true) String fileId, + @JsonProperty(value = "metadata") RemoteModMetadata metadata, + @JsonProperty(value = "installationPolicy") InstallationPolicy installationPolicy, + @JsonProperty(value = "options") Map options, + @JsonProperty(value = "folder") String folder, + @JsonProperty(value = "inject") Boolean inject, + @JsonProperty(value = "fileName") String fileName, + @JsonProperty(value = "comment") String comment + ) { + super(metadata, installationPolicy, options, folder, inject); + this.addonId = addonId; + this.fileId = fileId; + this.fileName = fileName; + } + + @Override + public String remoteType() { + return "Modrinth"; + } + + @Override + public String offlineName() { + return "Project ID: " + addonId + ", File ID: " + fileId; + } + + @Override + public void performInstall(Path targetFile, ProgressCallback progressCallback, ModDirector director, RemoteModInformation information) throws ModDirectorException { + try (WebGetResponse response = WebClient.get(new URL(fileInformation.files.get(0).url))) { + progressCallback.setSteps(1); + IOOperation.copy(response.getInputStream(), Files.newOutputStream(targetFile), progressCallback, + response.getStreamSize()); + } catch (IOException e) { + throw new ModDirectorException("Failed to download file", e); + } + } + + @Override + public RemoteModInformation queryInformation() throws ModDirectorException { + queryTitle(); + try { + URL apiUrl = new URL(String.format("https://api.modrinth.com/v2/project/%s/version/%s", addonId, fileId)); + fileInformation = ConfigurationController.OBJECT_MAPPER.readValue(apiUrl, ModrinthAddonFileInformation.class); + } catch (MalformedURLException e) { + throw new ModDirectorException("Failed to create Modrinth API URL", e); + } catch (JsonParseException e) { + throw new ModDirectorException("Failed to parse JSON response from Modrinth", e); + } catch (JsonMappingException e) { + throw new ModDirectorException("Failed to map JSON response from Modrinth, did they change their API?", e); + } catch (IOException e) { + throw new ModDirectorException("Failed to open connection to Modrinth", e); + } + + String displayName = projectTitle != null ? projectTitle : (fileName != null ? fileName : fileInformation.files.get(0).filename); + + return new RemoteModInformation(displayName, fileInformation.files.get(0).filename); + } + + private void queryTitle() throws ModDirectorException { + try { + URL projectUrl = new URL(String.format("https://api.modrinth.com/v2/project/%s", addonId)); + ModrinthProjectInformation projectInformation = ConfigurationController.OBJECT_MAPPER.readValue(projectUrl, ModrinthProjectInformation.class); + projectTitle = projectInformation.title; + } catch (MalformedURLException e) { + throw new ModDirectorException("Failed to create Modrinth project URL", e); + } catch (JsonParseException e) { + throw new ModDirectorException("Failed to parse JSON response from Modrinth project", e); + } catch (JsonMappingException e) { + throw new ModDirectorException("Failed to map JSON response from Modrinth project", e); + } catch (IOException e) { + throw new ModDirectorException("Failed to open connection to Modrinth project", e); + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private static class ModrinthAddonFileInformation { + @JsonProperty("files") + private List files; + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class ModrinthFile { + @JsonProperty + private String url; + + @JsonProperty + private String filename; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + private static class ModrinthProjectInformation { + @JsonProperty + private String title; + } +}