package io.gitlab.jfronny.inceptum.launcher.api; import gsoncompile.extensions.io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.FingerprintMatchesResponse.GC_FingerprintMatchesResponse; import gsoncompile.extensions.io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.GetModFileResponse.GC_GetModFileResponse; import gsoncompile.extensions.io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.GetModResponse.GC_GetModResponse; import gsoncompile.extensions.io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.SearchResponse.GC_SearchResponse; import io.gitlab.jfronny.commons.HttpUtils; import io.gitlab.jfronny.inceptum.common.Net; import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeFile; import io.gitlab.jfronny.inceptum.launcher.model.curseforge.CurseforgeMod; import io.gitlab.jfronny.inceptum.launcher.model.curseforge.response.*; import java.io.*; import java.net.URISyntaxException; import java.util.*; public class CurseforgeApi { // So you found the API key. // Please be aware that CurseForge requires you to change this if you make any kind of derivative work // Creating your own API key is relatively simple, so please don't abuse this private static final String API_KEY = new String(unsalt(new byte[]{ -30, 50, -60, -121, 62, -31, 35, 17, 16, -53, -53, -88, 21, -21, 15, -105, -115, -108, 114, -50, -49, -4, 56, -65, -70, 108, -65, -3, -55, -4, 36, -86, -40, 116, 71, -5, 75, -9, -43, 4, 91, -91, -29, 40, 66, 87, -80, -74, 71, 41, 76, -96, 108, -61, -113, 118, 7, -39, -116, -120 }, 1024)); private static final String API_URL = "https://api.curseforge.com/v1/"; private static final int pageSize = 20; public static List search(String gameVersion, String query, int page, String sort) throws IOException { return Net.downloadObject(Net.buildUrl(API_URL, "mods/search", Map.of( "gameId", "432", // minecraft "modLoaderType", "4", // fabric "classId", "6", // mods // "categoryId", "" "searchFilter", query, "sortField", sort, "sortOrder", "desc", "gameVersion", gameVersion, "pageSize", Integer.toString(pageSize), "index", Integer.toString(page * pageSize) )), GC_SearchResponse::read, API_KEY).data; } public static CurseforgeMod getMod(String slug) throws IOException { SearchResponse response = Net.downloadObject(Net.buildUrl(API_URL, "mods/search", Map.of( "gameId", "432", "classId", "6", "slug", slug )), GC_SearchResponse::read, API_KEY); if (response.pagination.totalCount != 1) { throw new FileNotFoundException("Could not find mod with slug \"" + slug + "\""); } return checkDistribution(response.data[0]); } public static CurseforgeMod getMod(int id) throws IOException { return checkDistribution(Net.downloadObject(API_URL + "mods/" + id, GC_GetModResponse::read, API_KEY).data); } private static CurseforgeMod checkDistribution(CurseforgeMod mod) { if (!mod.allowModDistribution) { throw new IllegalArgumentException("The author of the mod \"" + mod.slug + "\" has chosen to deliberately break your ability of downloading it.\n" + "Please let them know that disabling third party downloads does nothing but make the users life harder for no reason."); } return mod; } public static CurseforgeFile getFile(int modId, int fileId) throws IOException { return Net.downloadObject(API_URL + "mods/" + modId + "/files/" + fileId, GC_GetModFileResponse::read, API_KEY).data; } public static FingerprintMatchesResponse.Result checkFingerprint(long hash) throws IOException, URISyntaxException { try (Reader r = HttpUtils.post(API_URL + "fingerprints").bodyJson("{\"fingerprints\":[" + hash + "]}").sendReader()) { return GC_FingerprintMatchesResponse.read(r).data; } } private static byte[] unsalt(byte[] data, int salt) { byte[] result = new byte[data.length]; new Random(salt).nextBytes(result); for (int i = 0; i < data.length; i++) { result[i] ^= data[i]; } return result; } }