fix(download): switch to api.curseforge.com.
This commit is contained in:
@@ -41,9 +41,11 @@ val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
|
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.5"
|
||||||
|
val versionType = System.getenv("VERSION_TYPE") ?: "nightly"
|
||||||
|
|
||||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||||
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||||
val versionType = System.getenv("VERSION_TYPE") ?: "nightly"
|
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
|
||||||
|
|
||||||
version = "$versionRoot.$buildNumber"
|
version = "$versionRoot.$buildNumber"
|
||||||
|
|
||||||
@@ -147,6 +149,7 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
|
|||||||
"Implementation-Version" to project.version,
|
"Implementation-Version" to project.version,
|
||||||
"Microsoft-Auth-Id" to microsoftAuthId,
|
"Microsoft-Auth-Id" to microsoftAuthId,
|
||||||
"Microsoft-Auth-Secret" to microsoftAuthSecret,
|
"Microsoft-Auth-Secret" to microsoftAuthSecret,
|
||||||
|
"CurseForge-Api-Key" to curseForgeApiKey,
|
||||||
"Build-Channel" to versionType,
|
"Build-Channel" to versionType,
|
||||||
"Class-Path" to "pack200.jar",
|
"Class-Path" to "pack200.jar",
|
||||||
"Add-Opens" to listOf(
|
"Add-Opens" to listOf(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
@FXML
|
@FXML
|
||||||
private StackPane center;
|
private StackPane center;
|
||||||
|
|
||||||
private VersionList<?> versionList;
|
private final VersionList<?> versionList;
|
||||||
private CompletableFuture<?> executor;
|
private CompletableFuture<?> executor;
|
||||||
|
|
||||||
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
public VersionsPage(Navigation navigation, String title, String gameVersion, DownloadProvider downloadProvider, String libraryId, Runnable callback) {
|
||||||
@@ -144,7 +144,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
|||||||
|
|
||||||
content.setTitle(remoteVersion.getSelfVersion());
|
content.setTitle(remoteVersion.getSelfVersion());
|
||||||
if (remoteVersion.getReleaseDate() != null) {
|
if (remoteVersion.getReleaseDate() != null) {
|
||||||
content.setSubtitle(Locales.DATE_TIME_FORMATTER.get().format(remoteVersion.getReleaseDate()));
|
content.setSubtitle(Locales.DATE_TIME_FORMATTER.get().format(remoteVersion.getReleaseDate().toInstant()));
|
||||||
} else {
|
} else {
|
||||||
content.setSubtitle("");
|
content.setSubtitle("");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
|||||||
}
|
}
|
||||||
return gameVersion;
|
return gameVersion;
|
||||||
}).thenApplyAsync(gameVersion -> {
|
}).thenApplyAsync(gameVersion -> {
|
||||||
return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort);
|
return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC);
|
||||||
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
}).whenComplete(Schedulers.javafx(), (result, exception) -> {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
if (exception == null) {
|
if (exception == null) {
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
setFailed(false);
|
setFailed(false);
|
||||||
|
|
||||||
Task.allOf(
|
Task.allOf(
|
||||||
Task.supplyAsync(() -> addon.getData().loadDependencies()),
|
Task.supplyAsync(() -> addon.getData().loadDependencies(repository)),
|
||||||
Task.supplyAsync(() -> {
|
Task.supplyAsync(() -> {
|
||||||
Stream<RemoteMod.Version> versions = addon.getData().loadVersions();
|
Stream<RemoteMod.Version> versions = addon.getData().loadVersions(repository);
|
||||||
// if (StringUtils.isNotBlank(version.getVersion())) {
|
// if (StringUtils.isNotBlank(version.getVersion())) {
|
||||||
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
// Optional<String> gameVersion = GameVersion.minecraftVersion(versionJar);
|
||||||
// if (gameVersion.isPresent()) {
|
// if (gameVersion.isPresent()) {
|
||||||
@@ -284,7 +284,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
|
|
||||||
Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies"));
|
Node title = ComponentList.createComponentListTitle(i18n("mods.dependencies"));
|
||||||
|
|
||||||
BooleanBinding show = Bindings.createBooleanBinding(() -> !control.dependencies.isEmpty(), control.loaded);
|
BooleanBinding show = Bindings.createBooleanBinding(() -> control.loaded.get() && !control.dependencies.isEmpty(), control.loaded);
|
||||||
title.managedProperty().bind(show);
|
title.managedProperty().bind(show);
|
||||||
title.visibleProperty().bind(show);
|
title.visibleProperty().bind(show);
|
||||||
dependencyPane.managedProperty().bind(show);
|
dependencyPane.managedProperty().bind(show);
|
||||||
@@ -385,7 +385,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
|||||||
pane.getChildren().setAll(graphicPane, content, saveAsButton);
|
pane.getChildren().setAll(graphicPane, content, saveAsButton);
|
||||||
|
|
||||||
content.setTitle(dataItem.getName());
|
content.setTitle(dataItem.getName());
|
||||||
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished()));
|
content.setSubtitle(FORMATTER.format(dataItem.getDatePublished().toInstant()));
|
||||||
saveAsButton.setOnMouseClicked(e -> selfPage.saveAs(dataItem));
|
saveAsButton.setOnMouseClicked(e -> selfPage.saveAs(dataItem));
|
||||||
|
|
||||||
switch (dataItem.getVersionType()) {
|
switch (dataItem.getVersionType()) {
|
||||||
|
|||||||
@@ -47,13 +47,21 @@ public class ModDownloadListPage extends DownloadListPage {
|
|||||||
|
|
||||||
private class Repository implements RemoteModRepository {
|
private class Repository implements RemoteModRepository {
|
||||||
|
|
||||||
|
private RemoteModRepository getBackedRemoteModRepository() {
|
||||||
|
if ("mods.modrinth".equals(downloadSource.get())) {
|
||||||
|
return ModrinthRemoteModRepository.INSTANCE;
|
||||||
|
} else {
|
||||||
|
return CurseForgeRemoteModRepository.MODS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type getType() {
|
public Type getType() {
|
||||||
return Type.MOD;
|
return Type.MOD;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort) throws IOException {
|
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
||||||
String newSearchFilter;
|
String newSearchFilter;
|
||||||
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
||||||
List<ModTranslations.Mod> mods = ModTranslations.MOD.searchMod(searchFilter);
|
List<ModTranslations.Mod> mods = ModTranslations.MOD.searchMod(searchFilter);
|
||||||
@@ -75,35 +83,32 @@ public class ModDownloadListPage extends DownloadListPage {
|
|||||||
newSearchFilter = searchFilter;
|
newSearchFilter = searchFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
return getBackedRemoteModRepository().search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder);
|
||||||
return ModrinthRemoteModRepository.INSTANCE.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort);
|
|
||||||
} else {
|
|
||||||
return CurseForgeRemoteModRepository.MODS.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<Category> getCategories() throws IOException {
|
public Stream<Category> getCategories() throws IOException {
|
||||||
if ("mods.modrinth".equals(downloadSource.get())) {
|
return getBackedRemoteModRepository().getCategories();
|
||||||
return ModrinthRemoteModRepository.INSTANCE.getCategories();
|
|
||||||
} else {
|
|
||||||
return CurseForgeRemoteModRepository.MODS.getCategories();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) {
|
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
return getBackedRemoteModRepository().getRemoteVersionByLocalFile(localModFile, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteMod getModById(String id) {
|
public RemoteMod getModById(String id) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
return getBackedRemoteModRepository().getModById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
||||||
|
return getBackedRemoteModRepository().getModFile(modId, fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
return getBackedRemoteModRepository().getRemoteVersionsById(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.mod.RemoteModRepository;
|
|||||||
import org.jackhuang.hmcl.util.Pair;
|
import org.jackhuang.hmcl.util.Pair;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -38,7 +39,9 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
|||||||
public final class ModTranslations {
|
public final class ModTranslations {
|
||||||
public static ModTranslations MOD = new ModTranslations("/assets/mod_data.txt");
|
public static ModTranslations MOD = new ModTranslations("/assets/mod_data.txt");
|
||||||
public static ModTranslations MODPACK = new ModTranslations("/assets/modpack_data.txt");
|
public static ModTranslations MODPACK = new ModTranslations("/assets/modpack_data.txt");
|
||||||
|
public static ModTranslations EMPTY = new ModTranslations("");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static ModTranslations getTranslationsByRepositoryType(RemoteModRepository.Type type) {
|
public static ModTranslations getTranslationsByRepositoryType(RemoteModRepository.Type type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MOD:
|
case MOD:
|
||||||
@@ -46,7 +49,7 @@ public final class ModTranslations {
|
|||||||
case MODPACK:
|
case MODPACK:
|
||||||
return MODPACK;
|
return MODPACK;
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,12 +64,14 @@ public final class ModTranslations {
|
|||||||
this.resourceName = resourceName;
|
this.resourceName = resourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Mod getModByCurseForgeId(String id) {
|
public Mod getModByCurseForgeId(String id) {
|
||||||
if (StringUtils.isBlank(id) || !loadCurseForgeMap()) return null;
|
if (StringUtils.isBlank(id) || !loadCurseForgeMap()) return null;
|
||||||
|
|
||||||
return curseForgeMap.get(id);
|
return curseForgeMap.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public Mod getModById(String id) {
|
public Mod getModById(String id) {
|
||||||
if (StringUtils.isBlank(id) || !loadModIdMap()) return null;
|
if (StringUtils.isBlank(id) || !loadModIdMap()) return null;
|
||||||
|
|
||||||
@@ -96,7 +101,7 @@ public final class ModTranslations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean loadFromResource() {
|
private boolean loadFromResource() {
|
||||||
if (mods != null) return true;
|
if (mods != null || StringUtils.isBlank(resourceName)) return true;
|
||||||
try {
|
try {
|
||||||
String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8);
|
String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName), StandardCharsets.UTF_8);
|
||||||
mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList());
|
mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList());
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class ModpackDownloadListPage extends DownloadListPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort) throws IOException {
|
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
||||||
String newSearchFilter;
|
String newSearchFilter;
|
||||||
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
if (StringUtils.CHINESE_PATTERN.matcher(searchFilter).find()) {
|
||||||
List<ModTranslations.Mod> mods = ModTranslations.MODPACK.searchMod(searchFilter);
|
List<ModTranslations.Mod> mods = ModTranslations.MODPACK.searchMod(searchFilter);
|
||||||
@@ -72,7 +72,7 @@ public class ModpackDownloadListPage extends DownloadListPage {
|
|||||||
newSearchFilter = searchFilter;
|
newSearchFilter = searchFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CurseForgeRemoteModRepository.MODPACKS.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort);
|
return CurseForgeRemoteModRepository.MODPACKS.search(gameVersion, category, pageOffset, pageSize, newSearchFilter, sort, sortOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,18 +81,23 @@ public class ModpackDownloadListPage extends DownloadListPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) {
|
public Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
return CurseForgeRemoteModRepository.MODPACKS.getRemoteVersionByLocalFile(localModFile, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteMod getModById(String id) {
|
public RemoteMod getModById(String id) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
return CurseForgeRemoteModRepository.MODPACKS.getModById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
||||||
|
return CurseForgeRemoteModRepository.MODPACKS.getModFile(modId, fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||||
throw new UnsupportedOperationException();
|
return CurseForgeRemoteModRepository.MODPACKS.getRemoteVersionsById(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.task.Task;
|
|||||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
|||||||
private final String libraryId;
|
private final String libraryId;
|
||||||
private final String gameVersion;
|
private final String gameVersion;
|
||||||
private final String selfVersion;
|
private final String selfVersion;
|
||||||
private final Instant releaseDate;
|
private final Date releaseDate;
|
||||||
private final List<String> urls;
|
private final List<String> urls;
|
||||||
private final Type type;
|
private final Type type;
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
|||||||
* @param selfVersion the version string of the remote version.
|
* @param selfVersion the version string of the remote version.
|
||||||
* @param urls the installer or universal jar original URL.
|
* @param urls the installer or universal jar original URL.
|
||||||
*/
|
*/
|
||||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, List<String> urls) {
|
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Date releaseDate, List<String> urls) {
|
||||||
this(libraryId, gameVersion, selfVersion, releaseDate, Type.UNCATEGORIZED, urls);
|
this(libraryId, gameVersion, selfVersion, releaseDate, Type.UNCATEGORIZED, urls);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
|||||||
* @param selfVersion the version string of the remote version.
|
* @param selfVersion the version string of the remote version.
|
||||||
* @param urls the installer or universal jar URL.
|
* @param urls the installer or universal jar URL.
|
||||||
*/
|
*/
|
||||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, Type type, List<String> urls) {
|
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Date releaseDate, Type type, List<String> urls) {
|
||||||
this.libraryId = Objects.requireNonNull(libraryId);
|
this.libraryId = Objects.requireNonNull(libraryId);
|
||||||
this.gameVersion = Objects.requireNonNull(gameVersion);
|
this.gameVersion = Objects.requireNonNull(gameVersion);
|
||||||
this.selfVersion = Objects.requireNonNull(selfVersion);
|
this.selfVersion = Objects.requireNonNull(selfVersion);
|
||||||
@@ -83,7 +83,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
|||||||
return getSelfVersion();
|
return getSelfVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getReleaseDate() {
|
public Date getReleaseDate() {
|
||||||
return releaseDate;
|
return releaseDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,12 +17,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.download.fabric;
|
package org.jackhuang.hmcl.download.fabric;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.download.*;
|
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||||
|
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||||
|
import org.jackhuang.hmcl.download.RemoteVersion;
|
||||||
import org.jackhuang.hmcl.game.Version;
|
import org.jackhuang.hmcl.game.Version;
|
||||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class FabricAPIRemoteVersion extends RemoteVersion {
|
public class FabricAPIRemoteVersion extends RemoteVersion {
|
||||||
@@ -36,7 +38,7 @@ public class FabricAPIRemoteVersion extends RemoteVersion {
|
|||||||
* @param selfVersion the version string of the remote version.
|
* @param selfVersion the version string of the remote version.
|
||||||
* @param urls the installer or universal jar original URL.
|
* @param urls the installer or universal jar original URL.
|
||||||
*/
|
*/
|
||||||
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {
|
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Date datePublished, RemoteMod.Version version, List<String> urls) {
|
||||||
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
||||||
|
|
||||||
this.fullVersion = fullVersion;
|
this.fullVersion = fullVersion;
|
||||||
|
|||||||
@@ -30,10 +30,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.format.DateTimeParseException;
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -112,7 +109,7 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
|||||||
}
|
}
|
||||||
|
|
||||||
versions.put(gameVersion, new ForgeRemoteVersion(
|
versions.put(gameVersion, new ForgeRemoteVersion(
|
||||||
version.getGameVersion(), version.getVersion(), releaseDate, urls));
|
version.getGameVersion(), version.getVersion(), releaseDate == null ? null : Date.from(releaseDate), urls));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
|
|||||||
import org.jackhuang.hmcl.game.Version;
|
import org.jackhuang.hmcl.game.Version;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ForgeRemoteVersion extends RemoteVersion {
|
public class ForgeRemoteVersion extends RemoteVersion {
|
||||||
@@ -34,8 +34,8 @@ public class ForgeRemoteVersion extends RemoteVersion {
|
|||||||
* @param selfVersion the version string of the remote version.
|
* @param selfVersion the version string of the remote version.
|
||||||
* @param url the installer or universal jar original URL.
|
* @param url the installer or universal jar original URL.
|
||||||
*/
|
*/
|
||||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, Instant instant, List<String> url) {
|
public ForgeRemoteVersion(String gameVersion, String selfVersion, Date releaseDate, List<String> url) {
|
||||||
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, instant, url);
|
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, releaseDate, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.game.Version;
|
|||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,7 +37,7 @@ public final class GameRemoteVersion extends RemoteVersion {
|
|||||||
|
|
||||||
private final ReleaseType type;
|
private final ReleaseType type;
|
||||||
|
|
||||||
public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Instant releaseDate) {
|
public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Date releaseDate) {
|
||||||
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, releaseDate, getReleaseType(type), url);
|
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, releaseDate, getReleaseType(type), url);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ public final class GameVersionList extends VersionList<GameRemoteVersion> {
|
|||||||
remoteVersion.getGameVersion(),
|
remoteVersion.getGameVersion(),
|
||||||
remoteVersion.getGameVersion(),
|
remoteVersion.getGameVersion(),
|
||||||
Collections.singletonList(remoteVersion.getUrl()),
|
Collections.singletonList(remoteVersion.getUrl()),
|
||||||
remoteVersion.getType(), remoteVersion.getReleaseTime().toInstant()));
|
remoteVersion.getType(), remoteVersion.getReleaseTime()));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.writeLock().unlock();
|
lock.writeLock().unlock();
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.mod;
|
|||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -90,9 +90,9 @@ public class RemoteMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface IMod {
|
public interface IMod {
|
||||||
List<RemoteMod> loadDependencies() throws IOException;
|
List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException;
|
||||||
|
|
||||||
Stream<Version> loadVersions() throws IOException;
|
Stream<Version> loadVersions(RemoteModRepository modRepository) throws IOException;
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IVersion {
|
public interface IVersion {
|
||||||
@@ -105,14 +105,14 @@ public class RemoteMod {
|
|||||||
private final String name;
|
private final String name;
|
||||||
private final String version;
|
private final String version;
|
||||||
private final String changelog;
|
private final String changelog;
|
||||||
private final Instant datePublished;
|
private final Date datePublished;
|
||||||
private final VersionType versionType;
|
private final VersionType versionType;
|
||||||
private final File file;
|
private final File file;
|
||||||
private final List<String> dependencies;
|
private final List<String> dependencies;
|
||||||
private final List<String> gameVersions;
|
private final List<String> gameVersions;
|
||||||
private final List<ModLoaderType> loaders;
|
private final List<ModLoaderType> loaders;
|
||||||
|
|
||||||
public Version(IVersion self, String modid, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List<String> dependencies, List<String> gameVersions, List<ModLoaderType> loaders) {
|
public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List<String> dependencies, List<String> gameVersions, List<ModLoaderType> loaders) {
|
||||||
this.self = self;
|
this.self = self;
|
||||||
this.modid = modid;
|
this.modid = modid;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@@ -146,7 +146,7 @@ public class RemoteMod {
|
|||||||
return changelog;
|
return changelog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getDatePublished() {
|
public Date getDatePublished() {
|
||||||
return datePublished;
|
return datePublished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,13 +44,20 @@ public interface RemoteModRepository {
|
|||||||
TOTAL_DOWNLOADS
|
TOTAL_DOWNLOADS
|
||||||
}
|
}
|
||||||
|
|
||||||
Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort)
|
enum SortOrder {
|
||||||
|
ASC,
|
||||||
|
DESC
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder)
|
||||||
throws IOException;
|
throws IOException;
|
||||||
|
|
||||||
Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException;
|
Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException;
|
||||||
|
|
||||||
RemoteMod getModById(String id) throws IOException;
|
RemoteMod getModById(String id) throws IOException;
|
||||||
|
|
||||||
|
RemoteMod.File getModFile(String modId, String fileId) throws IOException;
|
||||||
|
|
||||||
Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException;
|
Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException;
|
||||||
|
|
||||||
Stream<Category> getCategories() throws IOException;
|
Stream<Category> getCategories() throws IOException;
|
||||||
|
|||||||
@@ -19,157 +19,167 @@ package org.jackhuang.hmcl.mod.curse;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
|
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.Instant;
|
import java.util.*;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
public class CurseAddon implements RemoteMod.IMod {
|
public class CurseAddon implements RemoteMod.IMod {
|
||||||
private final int id;
|
private final int id;
|
||||||
private final String name;
|
|
||||||
private final List<Author> authors;
|
|
||||||
private final List<Attachment> attachments;
|
|
||||||
private final String websiteUrl;
|
|
||||||
private final int gameId;
|
private final int gameId;
|
||||||
private final String summary;
|
private final String name;
|
||||||
private final int defaultFileId;
|
|
||||||
private final LatestFile file;
|
|
||||||
private final List<LatestFile> latestFiles;
|
|
||||||
private final List<Category> categories;
|
|
||||||
private final int status;
|
|
||||||
private final int primaryCategoryId;
|
|
||||||
private final String slug;
|
private final String slug;
|
||||||
private final List<GameVersionLatestFile> gameVersionLatestFiles;
|
private final Links links;
|
||||||
|
private final String summary;
|
||||||
|
private final int status;
|
||||||
|
private final int downloadCount;
|
||||||
private final boolean isFeatured;
|
private final boolean isFeatured;
|
||||||
private final double popularityScore;
|
private final int primaryCategoryId;
|
||||||
|
private final List<Category> categories;
|
||||||
|
private final int classId;
|
||||||
|
private final List<Author> authors;
|
||||||
|
private final Logo logo;
|
||||||
|
private final int mainFileId;
|
||||||
|
private final List<LatestFile> latestFiles;
|
||||||
|
private final List<LatestFileIndex> latestFileIndices;
|
||||||
|
private final Date dateCreated;
|
||||||
|
private final Date dateModified;
|
||||||
|
private final Date dateReleased;
|
||||||
|
private final boolean allowModDistribution;
|
||||||
private final int gamePopularityRank;
|
private final int gamePopularityRank;
|
||||||
private final String primaryLanguage; // e.g. enUS
|
|
||||||
private final List<String> modLoaders;
|
|
||||||
private final boolean isAvailable;
|
private final boolean isAvailable;
|
||||||
private final boolean isExperimental;
|
private final int thumbsUpCount;
|
||||||
|
|
||||||
public CurseAddon(int id, String name, List<Author> authors, List<Attachment> attachments, String websiteUrl, int gameId, String summary, int defaultFileId, LatestFile file, List<LatestFile> latestFiles, List<Category> categories, int status, int primaryCategoryId, String slug, List<GameVersionLatestFile> gameVersionLatestFiles, boolean isFeatured, double popularityScore, int gamePopularityRank, String primaryLanguage, List<String> modLoaders, boolean isAvailable, boolean isExperimental) {
|
public CurseAddon(int id, int gameId, String name, String slug, Links links, String summary, int status, int downloadCount, boolean isFeatured, int primaryCategoryId, List<Category> categories, int classId, List<Author> authors, Logo logo, int mainFileId, List<LatestFile> latestFiles, List<LatestFileIndex> latestFileIndices, Date dateCreated, Date dateModified, Date dateReleased, boolean allowModDistribution, int gamePopularityRank, boolean isAvailable, int thumbsUpCount) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
|
||||||
this.authors = authors;
|
|
||||||
this.attachments = attachments;
|
|
||||||
this.websiteUrl = websiteUrl;
|
|
||||||
this.gameId = gameId;
|
this.gameId = gameId;
|
||||||
this.summary = summary;
|
this.name = name;
|
||||||
this.defaultFileId = defaultFileId;
|
|
||||||
this.file = file;
|
|
||||||
this.latestFiles = latestFiles;
|
|
||||||
this.categories = categories;
|
|
||||||
this.status = status;
|
|
||||||
this.primaryCategoryId = primaryCategoryId;
|
|
||||||
this.slug = slug;
|
this.slug = slug;
|
||||||
this.gameVersionLatestFiles = gameVersionLatestFiles;
|
this.links = links;
|
||||||
|
this.summary = summary;
|
||||||
|
this.status = status;
|
||||||
|
this.downloadCount = downloadCount;
|
||||||
this.isFeatured = isFeatured;
|
this.isFeatured = isFeatured;
|
||||||
this.popularityScore = popularityScore;
|
this.primaryCategoryId = primaryCategoryId;
|
||||||
|
this.categories = categories;
|
||||||
|
this.classId = classId;
|
||||||
|
this.authors = authors;
|
||||||
|
this.logo = logo;
|
||||||
|
this.mainFileId = mainFileId;
|
||||||
|
this.latestFiles = latestFiles;
|
||||||
|
this.latestFileIndices = latestFileIndices;
|
||||||
|
this.dateCreated = dateCreated;
|
||||||
|
this.dateModified = dateModified;
|
||||||
|
this.dateReleased = dateReleased;
|
||||||
|
this.allowModDistribution = allowModDistribution;
|
||||||
this.gamePopularityRank = gamePopularityRank;
|
this.gamePopularityRank = gamePopularityRank;
|
||||||
this.primaryLanguage = primaryLanguage;
|
|
||||||
this.modLoaders = modLoaders;
|
|
||||||
this.isAvailable = isAvailable;
|
this.isAvailable = isAvailable;
|
||||||
this.isExperimental = isExperimental;
|
this.thumbsUpCount = thumbsUpCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Author> getAuthors() {
|
|
||||||
return authors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Attachment> getAttachments() {
|
|
||||||
return attachments;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getWebsiteUrl() {
|
|
||||||
return websiteUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGameId() {
|
public int getGameId() {
|
||||||
return gameId;
|
return gameId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSummary() {
|
public String getName() {
|
||||||
return summary;
|
return name;
|
||||||
}
|
|
||||||
|
|
||||||
public int getDefaultFileId() {
|
|
||||||
return defaultFileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LatestFile getFile() {
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<LatestFile> getLatestFiles() {
|
|
||||||
return latestFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Category> getCategories() {
|
|
||||||
return categories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPrimaryCategoryId() {
|
|
||||||
return primaryCategoryId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSlug() {
|
public String getSlug() {
|
||||||
return slug;
|
return slug;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<GameVersionLatestFile> getGameVersionLatestFiles() {
|
public Links getLinks() {
|
||||||
return gameVersionLatestFiles;
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummary() {
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDownloadCount() {
|
||||||
|
return downloadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFeatured() {
|
public boolean isFeatured() {
|
||||||
return isFeatured;
|
return isFeatured;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getPopularityScore() {
|
public int getPrimaryCategoryId() {
|
||||||
return popularityScore;
|
return primaryCategoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Category> getCategories() {
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClassId() {
|
||||||
|
return classId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Author> getAuthors() {
|
||||||
|
return authors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Logo getLogo() {
|
||||||
|
return logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMainFileId() {
|
||||||
|
return mainFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LatestFile> getLatestFiles() {
|
||||||
|
return latestFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LatestFileIndex> getLatestFileIndices() {
|
||||||
|
return latestFileIndices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDateCreated() {
|
||||||
|
return dateCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDateModified() {
|
||||||
|
return dateModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDateReleased() {
|
||||||
|
return dateReleased;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAllowModDistribution() {
|
||||||
|
return allowModDistribution;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getGamePopularityRank() {
|
public int getGamePopularityRank() {
|
||||||
return gamePopularityRank;
|
return gamePopularityRank;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPrimaryLanguage() {
|
|
||||||
return primaryLanguage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getModLoaders() {
|
|
||||||
return modLoaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
return isAvailable;
|
return isAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isExperimental() {
|
public int getThumbsUpCount() {
|
||||||
return isExperimental;
|
return thumbsUpCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RemoteMod> loadDependencies() throws IOException {
|
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException {
|
||||||
Set<Integer> dependencies = latestFiles.stream()
|
Set<Integer> dependencies = latestFiles.stream()
|
||||||
.flatMap(latestFile -> latestFile.getDependencies().stream())
|
.flatMap(latestFile -> latestFile.getDependencies().stream())
|
||||||
.filter(dep -> dep.getType() == 3)
|
.filter(dep -> dep.getType() == 3)
|
||||||
@@ -177,52 +187,78 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
List<RemoteMod> mods = new ArrayList<>();
|
List<RemoteMod> mods = new ArrayList<>();
|
||||||
for (int dependencyId : dependencies) {
|
for (int dependencyId : dependencies) {
|
||||||
mods.add(CurseForgeRemoteModRepository.MODS.getModById(Integer.toString(dependencyId)));
|
mods.add(modRepository.getModById(Integer.toString(dependencyId)));
|
||||||
}
|
}
|
||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> loadVersions() throws IOException {
|
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
|
||||||
return CurseForgeRemoteModRepository.MODS.getRemoteVersionsById(Integer.toString(id));
|
return modRepository.getRemoteVersionsById(Integer.toString(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteMod toMod() {
|
public RemoteMod toMod() {
|
||||||
String iconUrl = null;
|
String iconUrl = Optional.ofNullable(logo).map(Logo::getThumbnailUrl).orElse("");
|
||||||
for (CurseAddon.Attachment attachment : attachments) {
|
|
||||||
if (attachment.isDefault()) {
|
|
||||||
iconUrl = attachment.getThumbnailUrl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new RemoteMod(
|
return new RemoteMod(
|
||||||
slug,
|
slug,
|
||||||
"",
|
"",
|
||||||
name,
|
name,
|
||||||
summary,
|
summary,
|
||||||
categories.stream().map(category -> Integer.toString(category.getCategoryId())).collect(Collectors.toList()),
|
categories.stream().map(category -> Integer.toString(category.getId())).collect(Collectors.toList()),
|
||||||
websiteUrl,
|
links.websiteUrl,
|
||||||
iconUrl,
|
iconUrl,
|
||||||
this
|
this
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
public static class Links {
|
||||||
|
private final String websiteUrl;
|
||||||
|
private final String wikiUrl;
|
||||||
|
private final String issuesUrl;
|
||||||
|
private final String sourceUrl;
|
||||||
|
|
||||||
|
public Links(String websiteUrl, String wikiUrl, String issuesUrl, String sourceUrl) {
|
||||||
|
this.websiteUrl = websiteUrl;
|
||||||
|
this.wikiUrl = wikiUrl;
|
||||||
|
this.issuesUrl = issuesUrl;
|
||||||
|
this.sourceUrl = sourceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWebsiteUrl() {
|
||||||
|
return websiteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWikiUrl() {
|
||||||
|
return wikiUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getIssuesUrl() {
|
||||||
|
return issuesUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getSourceUrl() {
|
||||||
|
return sourceUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
public static class Author {
|
public static class Author {
|
||||||
|
private final int id;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final String url;
|
private final String url;
|
||||||
private final int projectId;
|
|
||||||
private final int id;
|
|
||||||
private final int userId;
|
|
||||||
private final int twitchId;
|
|
||||||
|
|
||||||
public Author(String name, String url, int projectId, int id, int userId, int twitchId) {
|
public Author(int id, String name, String url) {
|
||||||
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.projectId = projectId;
|
}
|
||||||
this.id = id;
|
|
||||||
this.userId = userId;
|
public int getId() {
|
||||||
this.twitchId = twitchId;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@@ -232,21 +268,48 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
public String getUrl() {
|
public String getUrl() {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getProjectId() {
|
@Immutable
|
||||||
return projectId;
|
public static class Logo {
|
||||||
|
private final int id;
|
||||||
|
private final int modId;
|
||||||
|
private final String title;
|
||||||
|
private final String description;
|
||||||
|
private final String thumbnailUrl;
|
||||||
|
private final String url;
|
||||||
|
|
||||||
|
public Logo(int id, int modId, String title, String description, String thumbnailUrl, String url) {
|
||||||
|
this.id = id;
|
||||||
|
this.modId = modId;
|
||||||
|
this.title = title;
|
||||||
|
this.description = description;
|
||||||
|
this.thumbnailUrl = thumbnailUrl;
|
||||||
|
this.url = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUserId() {
|
public int getModId() {
|
||||||
return userId;
|
return modId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTwitchId() {
|
public String getTitle() {
|
||||||
return twitchId;
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbnailUrl() {
|
||||||
|
return thumbnailUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,60 +403,89 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://docs.curseforge.com/#schemafilehash">Schema</a>
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
public static class LatestFileHash {
|
||||||
|
private final String value;
|
||||||
|
private final int algo;
|
||||||
|
|
||||||
|
public LatestFileHash(String value, int algo) {
|
||||||
|
this.value = value;
|
||||||
|
this.algo = algo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAlgo() {
|
||||||
|
return algo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://docs.curseforge.com/#tocS_File">Schema</a>
|
||||||
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
public static class LatestFile implements RemoteMod.IVersion {
|
public static class LatestFile implements RemoteMod.IVersion {
|
||||||
private final int id;
|
private final int id;
|
||||||
|
private final int gameId;
|
||||||
|
private final int modId;
|
||||||
|
private final boolean isAvailable;
|
||||||
private final String displayName;
|
private final String displayName;
|
||||||
private final String fileName;
|
private final String fileName;
|
||||||
private final String fileDate;
|
|
||||||
private final int fileLength;
|
|
||||||
private final int releaseType;
|
private final int releaseType;
|
||||||
private final int fileStatus;
|
private final int fileStatus;
|
||||||
|
private final List<LatestFileHash> hashes;
|
||||||
|
private final Date fileDate;
|
||||||
|
private final int fileLength;
|
||||||
|
private final int downloadCount;
|
||||||
private final String downloadUrl;
|
private final String downloadUrl;
|
||||||
private final boolean isAlternate;
|
private final List<String> gameVersions;
|
||||||
private final int alternateFileId;
|
|
||||||
private final List<Dependency> dependencies;
|
private final List<Dependency> dependencies;
|
||||||
private final boolean isAvailable;
|
private final int alternateFileId;
|
||||||
private final List<String> gameVersion;
|
|
||||||
private final boolean hasInstallScript;
|
|
||||||
private final boolean isCompatibleWIthClient;
|
|
||||||
private final int categorySectionPackageType;
|
|
||||||
private final int restrictProjectFileAccess;
|
|
||||||
private final int projectStatus;
|
|
||||||
private final int projectId;
|
|
||||||
private final boolean isServerPack;
|
private final boolean isServerPack;
|
||||||
private final int serverPackFileId;
|
private final long fileFingerprint;
|
||||||
|
|
||||||
private transient Instant fileDataInstant;
|
public LatestFile(int id, int gameId, int modId, boolean isAvailable, String displayName, String fileName, int releaseType, int fileStatus, List<LatestFileHash> hashes, Date fileDate, int fileLength, int downloadCount, String downloadUrl, List<String> gameVersions, List<Dependency> dependencies, int alternateFileId, boolean isServerPack, long fileFingerprint) {
|
||||||
|
|
||||||
public LatestFile(int id, String displayName, String fileName, String fileDate, int fileLength, int releaseType, int fileStatus, String downloadUrl, boolean isAlternate, int alternateFileId, List<Dependency> dependencies, boolean isAvailable, List<String> gameVersion, boolean hasInstallScript, boolean isCompatibleWIthClient, int categorySectionPackageType, int restrictProjectFileAccess, int projectStatus, int projectId, boolean isServerPack, int serverPackFileId) {
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
this.gameId = gameId;
|
||||||
|
this.modId = modId;
|
||||||
|
this.isAvailable = isAvailable;
|
||||||
this.displayName = displayName;
|
this.displayName = displayName;
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
this.fileDate = fileDate;
|
|
||||||
this.fileLength = fileLength;
|
|
||||||
this.releaseType = releaseType;
|
this.releaseType = releaseType;
|
||||||
this.fileStatus = fileStatus;
|
this.fileStatus = fileStatus;
|
||||||
|
this.hashes = hashes;
|
||||||
|
this.fileDate = fileDate;
|
||||||
|
this.fileLength = fileLength;
|
||||||
|
this.downloadCount = downloadCount;
|
||||||
this.downloadUrl = downloadUrl;
|
this.downloadUrl = downloadUrl;
|
||||||
this.isAlternate = isAlternate;
|
this.gameVersions = gameVersions;
|
||||||
this.alternateFileId = alternateFileId;
|
|
||||||
this.dependencies = dependencies;
|
this.dependencies = dependencies;
|
||||||
this.isAvailable = isAvailable;
|
this.alternateFileId = alternateFileId;
|
||||||
this.gameVersion = gameVersion;
|
|
||||||
this.hasInstallScript = hasInstallScript;
|
|
||||||
this.isCompatibleWIthClient = isCompatibleWIthClient;
|
|
||||||
this.categorySectionPackageType = categorySectionPackageType;
|
|
||||||
this.restrictProjectFileAccess = restrictProjectFileAccess;
|
|
||||||
this.projectStatus = projectStatus;
|
|
||||||
this.projectId = projectId;
|
|
||||||
this.isServerPack = isServerPack;
|
this.isServerPack = isServerPack;
|
||||||
this.serverPackFileId = serverPackFileId;
|
this.fileFingerprint = fileFingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getGameId() {
|
||||||
|
return gameId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getModId() {
|
||||||
|
return modId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return isAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDisplayName() {
|
public String getDisplayName() {
|
||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
@@ -402,14 +494,6 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileDate() {
|
|
||||||
return fileDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getFileLength() {
|
|
||||||
return fileLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReleaseType() {
|
public int getReleaseType() {
|
||||||
return releaseType;
|
return releaseType;
|
||||||
}
|
}
|
||||||
@@ -418,67 +502,49 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
return fileStatus;
|
return fileStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<LatestFileHash> getHashes() {
|
||||||
|
return hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getFileDate() {
|
||||||
|
return fileDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFileLength() {
|
||||||
|
return fileLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDownloadCount() {
|
||||||
|
return downloadCount;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDownloadUrl() {
|
public String getDownloadUrl() {
|
||||||
|
if (downloadUrl == null) {
|
||||||
|
// This addon is not allowed for distribution, and downloadUrl will be null.
|
||||||
|
// We try to find its download url.
|
||||||
|
return String.format("https://edge.forgecdn.net/files/%d/%d/%s", id / 1000, id % 1000, fileName);
|
||||||
|
}
|
||||||
return downloadUrl;
|
return downloadUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAlternate() {
|
public List<String> getGameVersions() {
|
||||||
return isAlternate;
|
return gameVersions;
|
||||||
}
|
|
||||||
|
|
||||||
public int getAlternateFileId() {
|
|
||||||
return alternateFileId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Dependency> getDependencies() {
|
public List<Dependency> getDependencies() {
|
||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAvailable() {
|
public int getAlternateFileId() {
|
||||||
return isAvailable;
|
return alternateFileId;
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getGameVersion() {
|
|
||||||
return gameVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isHasInstallScript() {
|
|
||||||
return hasInstallScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCompatibleWIthClient() {
|
|
||||||
return isCompatibleWIthClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCategorySectionPackageType() {
|
|
||||||
return categorySectionPackageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRestrictProjectFileAccess() {
|
|
||||||
return restrictProjectFileAccess;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getProjectStatus() {
|
|
||||||
return projectStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getProjectId() {
|
|
||||||
return projectId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isServerPack() {
|
public boolean isServerPack() {
|
||||||
return isServerPack;
|
return isServerPack;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getServerPackFileId() {
|
public long getFileFingerprint() {
|
||||||
return serverPackFileId;
|
return fileFingerprint;
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getParsedFileDate() {
|
|
||||||
if (fileDataInstant == null) {
|
|
||||||
fileDataInstant = Instant.parse(fileDate);
|
|
||||||
}
|
|
||||||
return fileDataInstant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -504,9 +570,9 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ModLoaderType modLoaderType;
|
ModLoaderType modLoaderType;
|
||||||
if (gameVersion.contains("Forge")) {
|
if (gameVersions.contains("Forge")) {
|
||||||
modLoaderType = ModLoaderType.FORGE;
|
modLoaderType = ModLoaderType.FORGE;
|
||||||
} else if (gameVersion.contains("Fabric")) {
|
} else if (gameVersions.contains("Fabric")) {
|
||||||
modLoaderType = ModLoaderType.FABRIC;
|
modLoaderType = ModLoaderType.FABRIC;
|
||||||
} else {
|
} else {
|
||||||
modLoaderType = ModLoaderType.UNKNOWN;
|
modLoaderType = ModLoaderType.UNKNOWN;
|
||||||
@@ -514,94 +580,38 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
|
|
||||||
return new RemoteMod.Version(
|
return new RemoteMod.Version(
|
||||||
this,
|
this,
|
||||||
Integer.toString(projectId),
|
Integer.toString(modId),
|
||||||
getDisplayName(),
|
getDisplayName(),
|
||||||
getFileName(),
|
getFileName(),
|
||||||
null,
|
null,
|
||||||
getParsedFileDate(),
|
getFileDate(),
|
||||||
versionType,
|
versionType,
|
||||||
new RemoteMod.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),
|
new RemoteMod.File(Collections.emptyMap(), getDownloadUrl(), getFileName()),
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
gameVersion.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()),
|
gameVersions.stream().filter(ver -> ver.startsWith("1.") || ver.contains("w")).collect(Collectors.toList()),
|
||||||
Collections.singletonList(modLoaderType)
|
Collections.singletonList(modLoaderType)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://docs.curseforge.com/#tocS_FileIndex">Schema</a>
|
||||||
|
*/
|
||||||
@Immutable
|
@Immutable
|
||||||
public static class Category {
|
public static class LatestFileIndex {
|
||||||
private final int categoryId;
|
|
||||||
private final String name;
|
|
||||||
private final String url;
|
|
||||||
private final String avatarUrl;
|
|
||||||
private final int parentId;
|
|
||||||
private final int rootId;
|
|
||||||
private final int projectId;
|
|
||||||
private final int avatarId;
|
|
||||||
private final int gameId;
|
|
||||||
|
|
||||||
public Category(int categoryId, String name, String url, String avatarUrl, int parentId, int rootId, int projectId, int avatarId, int gameId) {
|
|
||||||
this.categoryId = categoryId;
|
|
||||||
this.name = name;
|
|
||||||
this.url = url;
|
|
||||||
this.avatarUrl = avatarUrl;
|
|
||||||
this.parentId = parentId;
|
|
||||||
this.rootId = rootId;
|
|
||||||
this.projectId = projectId;
|
|
||||||
this.avatarId = avatarId;
|
|
||||||
this.gameId = gameId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCategoryId() {
|
|
||||||
return categoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAvatarUrl() {
|
|
||||||
return avatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getParentId() {
|
|
||||||
return parentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRootId() {
|
|
||||||
return rootId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getProjectId() {
|
|
||||||
return projectId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAvatarId() {
|
|
||||||
return avatarId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGameId() {
|
|
||||||
return gameId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
public static class GameVersionLatestFile {
|
|
||||||
private final String gameVersion;
|
private final String gameVersion;
|
||||||
private final String projectFileId;
|
private final int fileId;
|
||||||
private final String projectFileName;
|
private final String filename;
|
||||||
private final int fileType;
|
private final int releaseType;
|
||||||
private final Integer modLoader; // optional
|
private final int gameVersionTypeId;
|
||||||
|
private final int modLoader;
|
||||||
|
|
||||||
public GameVersionLatestFile(String gameVersion, String projectFileId, String projectFileName, int fileType, Integer modLoader) {
|
public LatestFileIndex(String gameVersion, int fileId, String filename, int releaseType, int gameVersionTypeId, int modLoader) {
|
||||||
this.gameVersion = gameVersion;
|
this.gameVersion = gameVersion;
|
||||||
this.projectFileId = projectFileId;
|
this.fileId = fileId;
|
||||||
this.projectFileName = projectFileName;
|
this.filename = filename;
|
||||||
this.fileType = fileType;
|
this.releaseType = releaseType;
|
||||||
|
this.gameVersionTypeId = gameVersionTypeId;
|
||||||
this.modLoader = modLoader;
|
this.modLoader = modLoader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,20 +619,111 @@ public class CurseAddon implements RemoteMod.IMod {
|
|||||||
return gameVersion;
|
return gameVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProjectFileId() {
|
public int getFileId() {
|
||||||
return projectFileId;
|
return fileId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProjectFileName() {
|
public String getFilename() {
|
||||||
return projectFileName;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getFileType() {
|
public int getReleaseType() {
|
||||||
return fileType;
|
return releaseType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Integer getModLoader() {
|
@Nullable
|
||||||
|
public int getGameVersionTypeId() {
|
||||||
|
return gameVersionTypeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getModLoader() {
|
||||||
return modLoader;
|
return modLoader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
public static class Category {
|
||||||
|
private final int id;
|
||||||
|
private final int gameId;
|
||||||
|
private final String name;
|
||||||
|
private final String slug;
|
||||||
|
private final String url;
|
||||||
|
private final String iconUrl;
|
||||||
|
private final Date dateModified;
|
||||||
|
private final boolean isClass;
|
||||||
|
private final int classId;
|
||||||
|
private final int parentCategoryId;
|
||||||
|
|
||||||
|
private transient final List<Category> subcategories;
|
||||||
|
|
||||||
|
public Category() {
|
||||||
|
this(0, 0, "", "", "", "", new Date(), false, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Category(int id, int gameId, String name, String slug, String url, String iconUrl, Date dateModified, boolean isClass, int classId, int parentCategoryId) {
|
||||||
|
this.id = id;
|
||||||
|
this.gameId = gameId;
|
||||||
|
this.name = name;
|
||||||
|
this.slug = slug;
|
||||||
|
this.url = url;
|
||||||
|
this.iconUrl = iconUrl;
|
||||||
|
this.dateModified = dateModified;
|
||||||
|
this.isClass = isClass;
|
||||||
|
this.classId = classId;
|
||||||
|
this.parentCategoryId = parentCategoryId;
|
||||||
|
|
||||||
|
this.subcategories = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGameId() {
|
||||||
|
return gameId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSlug() {
|
||||||
|
return slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIconUrl() {
|
||||||
|
return iconUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Date getDateModified() {
|
||||||
|
return dateModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isClass() {
|
||||||
|
return isClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getClassId() {
|
||||||
|
return classId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getParentCategoryId() {
|
||||||
|
return parentCategoryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Category> getSubcategories() {
|
||||||
|
return subcategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RemoteModRepository.Category toCategory() {
|
||||||
|
return new RemoteModRepository.Category(
|
||||||
|
this,
|
||||||
|
Integer.toString(id),
|
||||||
|
getSubcategories().stream().map(Category::toCategory).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ import com.google.gson.JsonParseException;
|
|||||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||||
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
import org.jackhuang.hmcl.game.DefaultGameRepository;
|
||||||
import org.jackhuang.hmcl.mod.ModManager;
|
import org.jackhuang.hmcl.mod.ModManager;
|
||||||
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
import org.jackhuang.hmcl.util.StringUtils;
|
import org.jackhuang.hmcl.util.StringUtils;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -117,35 +117,18 @@ public final class CurseCompletionTask extends Task<Void> {
|
|||||||
manifest.getFiles().parallelStream()
|
manifest.getFiles().parallelStream()
|
||||||
.map(file -> {
|
.map(file -> {
|
||||||
updateProgress(finished.incrementAndGet(), manifest.getFiles().size());
|
updateProgress(finished.incrementAndGet(), manifest.getFiles().size());
|
||||||
if (StringUtils.isBlank(file.getFileName())) {
|
if (StringUtils.isBlank(file.getFileName()) || file.getUrl() == null) {
|
||||||
try {
|
try {
|
||||||
return file.withFileName(NetworkUtils.detectFileName(file.getUrl()));
|
RemoteMod.File remoteFile = CurseForgeRemoteModRepository.MODS.getModFile(Integer.toString(file.getProjectID()), Integer.toString(file.getFileID()));
|
||||||
} catch (IOException e) {
|
return file.withFileName(remoteFile.getFilename()).withURL(remoteFile.getUrl());
|
||||||
try {
|
} catch (FileNotFoundException fof) {
|
||||||
String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://cursemeta.dries007.net/%d/%d.json", file.getProjectID(), file.getFileID())));
|
Logging.LOG.log(Level.WARNING, "Could not query api.curseforge.com for deleted mods: " + file.getProjectID() + ", " +file.getFileID(), fof);
|
||||||
CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
notFound.set(true);
|
||||||
return file.withFileName(mod.getFileNameOnDisk()).withURL(mod.getDownloadURL());
|
return file;
|
||||||
} catch (FileNotFoundException fof) {
|
} catch (IOException | JsonParseException e) {
|
||||||
Logging.LOG.log(Level.WARNING, "Could not query cursemeta for deleted mods: " + file.getUrl(), fof);
|
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name projectID=" + file.getProjectID() + ", fileID=" +file.getFileID(), e);
|
||||||
notFound.set(true);
|
allNameKnown.set(false);
|
||||||
return file;
|
return file;
|
||||||
} catch (IOException | JsonParseException e2) {
|
|
||||||
try {
|
|
||||||
String result = NetworkUtils.doGet(NetworkUtils.toURL(String.format("https://addons-ecs.forgesvc.net/api/v2/addon/%d/file/%d", file.getProjectID(), file.getFileID())));
|
|
||||||
CurseMetaMod mod = JsonUtils.fromNonNullJson(result, CurseMetaMod.class);
|
|
||||||
return file.withFileName(mod.getFileName()).withURL(mod.getDownloadURL());
|
|
||||||
} catch (FileNotFoundException fof) {
|
|
||||||
Logging.LOG.log(Level.WARNING, "Could not query forgesvc for deleted mods: " + file.getUrl(), fof);
|
|
||||||
notFound.set(true);
|
|
||||||
return file;
|
|
||||||
} catch (IOException | JsonParseException e3) {
|
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e);
|
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e2);
|
|
||||||
Logging.LOG.log(Level.WARNING, "Unable to fetch the file name of URL: " + file.getUrl(), e3);
|
|
||||||
allNameKnown.set(false);
|
|
||||||
return file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return file;
|
return file;
|
||||||
|
|||||||
@@ -22,16 +22,15 @@ import org.jackhuang.hmcl.mod.LocalModFile;
|
|||||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||||
import org.jackhuang.hmcl.util.MurmurHash2;
|
import org.jackhuang.hmcl.util.MurmurHash2;
|
||||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
|
||||||
import org.jackhuang.hmcl.util.io.HttpRequest;
|
import org.jackhuang.hmcl.util.io.HttpRequest;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.net.URL;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||||
@@ -39,12 +38,19 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
|||||||
|
|
||||||
public final class CurseForgeRemoteModRepository implements RemoteModRepository {
|
public final class CurseForgeRemoteModRepository implements RemoteModRepository {
|
||||||
|
|
||||||
private static final String PREFIX = "https://addons-ecs.forgesvc.net";
|
private static final String PREFIX = "https://api.curseforge.com";
|
||||||
|
|
||||||
|
private static String apiKey;
|
||||||
|
|
||||||
|
static {
|
||||||
|
apiKey = System.getProperty("hmcl.curseforge.apikey",
|
||||||
|
JarUtils.thisJar().flatMap(JarUtils::getManifest).map(manifest -> manifest.getMainAttributes().getValue("CurseForge-Api-Key")).orElse(""));
|
||||||
|
}
|
||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private final int section;
|
private final int section;
|
||||||
|
|
||||||
private CurseForgeRemoteModRepository(Type type, int section) {
|
public CurseForgeRemoteModRepository(Type type, int section) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.section = section;
|
this.section = section;
|
||||||
}
|
}
|
||||||
@@ -54,27 +60,55 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CurseAddon> searchPaginated(String gameVersion, int category, int pageOffset, int pageSize, String searchFilter, int sort) throws IOException {
|
private int toModsSearchSortField(SortType sort) {
|
||||||
String response = NetworkUtils.doGet(new URL(NetworkUtils.withQuery(PREFIX + "/api/v2/addon/search", mapOf(
|
// https://docs.curseforge.com/#tocS_ModsSearchSortField
|
||||||
pair("categoryId", Integer.toString(category)),
|
switch (sort) {
|
||||||
pair("gameId", "432"),
|
case DATE_CREATED:
|
||||||
pair("gameVersion", gameVersion),
|
return 1;
|
||||||
pair("index", Integer.toString(pageOffset)),
|
case POPULARITY:
|
||||||
pair("pageSize", Integer.toString(pageSize)),
|
return 2;
|
||||||
pair("searchFilter", searchFilter),
|
case LAST_UPDATED:
|
||||||
pair("sectionId", Integer.toString(section)),
|
return 3;
|
||||||
pair("sort", Integer.toString(sort))
|
case NAME:
|
||||||
))));
|
return 4;
|
||||||
return JsonUtils.fromNonNullJson(response, new TypeToken<List<CurseAddon>>() {
|
case AUTHOR:
|
||||||
}.getType());
|
return 5;
|
||||||
|
case TOTAL_DOWNLOADS:
|
||||||
|
return 6;
|
||||||
|
default:
|
||||||
|
return 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String toSortOrder(SortOrder sortOrder) {
|
||||||
|
// https://docs.curseforge.com/#tocS_SortOrder
|
||||||
|
switch (sortOrder) {
|
||||||
|
case ASC:
|
||||||
|
return "asc";
|
||||||
|
case DESC:
|
||||||
|
return "desc";
|
||||||
|
}
|
||||||
|
return "asc";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod> search(String gameVersion, RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort) throws IOException {
|
public Stream<RemoteMod> search(String gameVersion, RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
|
||||||
int categoryId = 0;
|
int categoryId = 0;
|
||||||
if (category != null) categoryId = ((Category) category.getSelf()).getId();
|
if (category != null) categoryId = ((CurseAddon.Category) category.getSelf()).getId();
|
||||||
return searchPaginated(gameVersion, categoryId, pageOffset, pageSize, searchFilter, sort.ordinal()).stream()
|
Response<List<CurseAddon>> response = HttpRequest.GET(PREFIX + "/v1/mods/search",
|
||||||
.map(CurseAddon::toMod);
|
pair("gameId", "432"),
|
||||||
|
pair("classId", Integer.toString(section)),
|
||||||
|
pair("categoryId", Integer.toString(categoryId)),
|
||||||
|
pair("gameVersion", gameVersion),
|
||||||
|
pair("searchFilter", searchFilter),
|
||||||
|
pair("sortField", Integer.toString(toModsSearchSortField(sortType))),
|
||||||
|
pair("sortOrder", toSortOrder(sortOrder)),
|
||||||
|
pair("index", Integer.toString(pageOffset)),
|
||||||
|
pair("pageSize", Integer.toString(pageSize)))
|
||||||
|
.header("X-API-KEY", apiKey)
|
||||||
|
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
|
||||||
|
}.getType());
|
||||||
|
return response.getData().stream().map(CurseAddon::toMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,60 +129,74 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
|
|
||||||
long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));
|
long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));
|
||||||
|
|
||||||
FingerprintResponse response = HttpRequest.POST(PREFIX + "/api/v2/fingerprint")
|
Response<FingerprintMatchesResult> response = HttpRequest.POST(PREFIX + "/v1/fingerprints")
|
||||||
.json(Collections.singletonList(hash))
|
.json(mapOf(pair("fingerprints", Collections.singletonList(hash))))
|
||||||
.getJson(FingerprintResponse.class);
|
.header("X-API-KEY", apiKey)
|
||||||
|
.getJson(new TypeToken<Response<FingerprintMatchesResult>>() {
|
||||||
|
}.getType());
|
||||||
|
|
||||||
if (response.getExactMatches() == null || response.getExactMatches().isEmpty()) {
|
if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Optional.of(response.getExactMatches().get(0).getFile().toVersion());
|
return Optional.of(response.getData().getExactMatches().get(0).getFile().toVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RemoteMod getModById(String id) throws IOException {
|
public RemoteMod getModById(String id) throws IOException {
|
||||||
String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/api/v2/addon/" + id));
|
return HttpRequest.GET(PREFIX + "/v1/mods/" + id)
|
||||||
return JsonUtils.fromNonNullJson(response, CurseAddon.class).toMod();
|
.header("X-API-KEY", apiKey)
|
||||||
|
.getJson(CurseAddon.class).toMod();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
||||||
|
Response<CurseAddon.LatestFile> response = HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId))
|
||||||
|
.header("X-API-KEY", apiKey)
|
||||||
|
.getJson(new TypeToken<Response<CurseAddon.LatestFile>>() {
|
||||||
|
}.getType());
|
||||||
|
return response.getData().toVersion().getFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||||
String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/api/v2/addon/" + id + "/files"));
|
Response<List<CurseAddon.LatestFile>> response = HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files")
|
||||||
List<CurseAddon.LatestFile> files = JsonUtils.fromNonNullJson(response, new TypeToken<List<CurseAddon.LatestFile>>() {
|
.header("X-API-KEY", apiKey)
|
||||||
}.getType());
|
.getJson(new TypeToken<Response<List<CurseAddon.LatestFile>>>() {
|
||||||
return files.stream().map(CurseAddon.LatestFile::toVersion);
|
}.getType());
|
||||||
|
return response.getData().stream().map(CurseAddon.LatestFile::toVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Category> getCategoriesImpl() throws IOException {
|
public List<CurseAddon.Category> getCategoriesImpl() throws IOException {
|
||||||
String response = NetworkUtils.doGet(NetworkUtils.toURL(PREFIX + "/api/v2/category/section/" + section));
|
Response<List<CurseAddon.Category>> categories = HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432"))
|
||||||
List<Category> categories = JsonUtils.fromNonNullJson(response, new TypeToken<List<Category>>() {
|
.header("X-API-KEY", apiKey)
|
||||||
}.getType());
|
.getJson(new TypeToken<Response<List<CurseAddon.Category>>>() {
|
||||||
return reorganizeCategories(categories, section);
|
}.getType());
|
||||||
|
return reorganizeCategories(categories.getData(), section);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
public Stream<RemoteModRepository.Category> getCategories() throws IOException {
|
||||||
return getCategoriesImpl().stream().map(Category::toCategory);
|
return getCategoriesImpl().stream().map(CurseAddon.Category::toCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Category> reorganizeCategories(List<Category> categories, int rootId) {
|
private List<CurseAddon.Category> reorganizeCategories(List<CurseAddon.Category> categories, int rootId) {
|
||||||
List<Category> result = new ArrayList<>();
|
List<CurseAddon.Category> result = new ArrayList<>();
|
||||||
|
|
||||||
Map<Integer, Category> categoryMap = new HashMap<>();
|
Map<Integer, CurseAddon.Category> categoryMap = new HashMap<>();
|
||||||
for (Category category : categories) {
|
for (CurseAddon.Category category : categories) {
|
||||||
categoryMap.put(category.id, category);
|
categoryMap.put(category.getId(), category);
|
||||||
}
|
}
|
||||||
for (Category category : categories) {
|
for (CurseAddon.Category category : categories) {
|
||||||
if (category.parentGameCategoryId == rootId) {
|
if (category.getParentCategoryId() == rootId) {
|
||||||
result.add(category);
|
result.add(category);
|
||||||
} else {
|
} else {
|
||||||
Category parentCategory = categoryMap.get(category.parentGameCategoryId);
|
CurseAddon.Category parentCategory = categoryMap.get(category.getParentCategoryId());
|
||||||
if (parentCategory == null) {
|
if (parentCategory == null) {
|
||||||
// Category list is not correct, so we ignore this item.
|
// Category list is not correct, so we ignore this item.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
parentCategory.subcategories.add(category);
|
parentCategory.getSubcategories().add(category);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@@ -165,84 +213,69 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
public static final int SECTION_UNKNOWN2 = 4979;
|
public static final int SECTION_UNKNOWN2 = 4979;
|
||||||
public static final int SECTION_UNKNOWN3 = 4984;
|
public static final int SECTION_UNKNOWN3 = 4984;
|
||||||
|
|
||||||
public static final CurseForgeRemoteModRepository MODS = new CurseForgeRemoteModRepository(Type.MOD, SECTION_MOD);
|
public static final CurseForgeRemoteModRepository MODS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.MOD, SECTION_MOD);
|
||||||
public static final CurseForgeRemoteModRepository MODPACKS = new CurseForgeRemoteModRepository(Type.MODPACK, SECTION_MODPACK);
|
public static final CurseForgeRemoteModRepository MODPACKS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.MODPACK, SECTION_MODPACK);
|
||||||
public static final CurseForgeRemoteModRepository RESOURCE_PACKS = new CurseForgeRemoteModRepository(Type.RESOURCE_PACK, SECTION_RESOURCE_PACK);
|
public static final CurseForgeRemoteModRepository RESOURCE_PACKS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.RESOURCE_PACK, SECTION_RESOURCE_PACK);
|
||||||
public static final CurseForgeRemoteModRepository WORLDS = new CurseForgeRemoteModRepository(Type.WORLD, SECTION_WORLD);
|
public static final CurseForgeRemoteModRepository WORLDS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.WORLD, SECTION_WORLD);
|
||||||
public static final CurseForgeRemoteModRepository CUSTOMIZATIONS = new CurseForgeRemoteModRepository(Type.CUSTOMIZATION, SECTION_CUSTOMIZATION);
|
public static final CurseForgeRemoteModRepository CUSTOMIZATIONS = new CurseForgeRemoteModRepository(RemoteModRepository.Type.CUSTOMIZATION, SECTION_CUSTOMIZATION);
|
||||||
|
|
||||||
public static class Category {
|
public static class Pagination {
|
||||||
private final int id;
|
private final int index;
|
||||||
private final String name;
|
private final int pageSize;
|
||||||
private final String slug;
|
private final int resultCount;
|
||||||
private final String avatarUrl;
|
private final int totalCount;
|
||||||
private final int parentGameCategoryId;
|
|
||||||
private final int rootGameCategoryId;
|
|
||||||
private final int gameId;
|
|
||||||
|
|
||||||
private final List<Category> subcategories;
|
public Pagination(int index, int pageSize, int resultCount, int totalCount) {
|
||||||
|
this.index = index;
|
||||||
public Category() {
|
this.pageSize = pageSize;
|
||||||
this(0, "", "", "", 0, 0, 0, new ArrayList<>());
|
this.resultCount = resultCount;
|
||||||
|
this.totalCount = totalCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Category(int id, String name, String slug, String avatarUrl, int parentGameCategoryId, int rootGameCategoryId, int gameId, List<Category> subcategories) {
|
public int getIndex() {
|
||||||
this.id = id;
|
return index;
|
||||||
this.name = name;
|
|
||||||
this.slug = slug;
|
|
||||||
this.avatarUrl = avatarUrl;
|
|
||||||
this.parentGameCategoryId = parentGameCategoryId;
|
|
||||||
this.rootGameCategoryId = rootGameCategoryId;
|
|
||||||
this.gameId = gameId;
|
|
||||||
this.subcategories = subcategories;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getId() {
|
public int getPageSize() {
|
||||||
return id;
|
return pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public int getResultCount() {
|
||||||
return name;
|
return resultCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSlug() {
|
public int getTotalCount() {
|
||||||
return slug;
|
return totalCount;
|
||||||
}
|
|
||||||
|
|
||||||
public String getAvatarUrl() {
|
|
||||||
return avatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getParentGameCategoryId() {
|
|
||||||
return parentGameCategoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRootGameCategoryId() {
|
|
||||||
return rootGameCategoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getGameId() {
|
|
||||||
return gameId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Category> getSubcategories() {
|
|
||||||
return subcategories;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RemoteModRepository.Category toCategory() {
|
|
||||||
return new RemoteModRepository.Category(
|
|
||||||
this,
|
|
||||||
Integer.toString(id),
|
|
||||||
getSubcategories().stream().map(Category::toCategory).collect(Collectors.toList()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FingerprintResponse {
|
public static class Response<T> {
|
||||||
|
private final T data;
|
||||||
|
private final Pagination pagination;
|
||||||
|
|
||||||
|
public Response(T data, Pagination pagination) {
|
||||||
|
this.data = data;
|
||||||
|
this.pagination = pagination;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getData() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pagination getPagination() {
|
||||||
|
return pagination;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://docs.curseforge.com/#tocS_FingerprintsMatchesResult">Schema</a>
|
||||||
|
*/
|
||||||
|
private static class FingerprintMatchesResult {
|
||||||
private final boolean isCacheBuilt;
|
private final boolean isCacheBuilt;
|
||||||
private final List<CurseAddon> exactMatches;
|
private final List<FingerprintMatch> exactMatches;
|
||||||
private final List<Long> exactFingerprints;
|
private final List<Long> exactFingerprints;
|
||||||
|
|
||||||
public FingerprintResponse(boolean isCacheBuilt, List<CurseAddon> exactMatches, List<Long> exactFingerprints) {
|
public FingerprintMatchesResult(boolean isCacheBuilt, List<FingerprintMatch> exactMatches, List<Long> exactFingerprints) {
|
||||||
this.isCacheBuilt = isCacheBuilt;
|
this.isCacheBuilt = isCacheBuilt;
|
||||||
this.exactMatches = exactMatches;
|
this.exactMatches = exactMatches;
|
||||||
this.exactFingerprints = exactFingerprints;
|
this.exactFingerprints = exactFingerprints;
|
||||||
@@ -252,7 +285,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
return isCacheBuilt;
|
return isCacheBuilt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CurseAddon> getExactMatches() {
|
public List<FingerprintMatch> getExactMatches() {
|
||||||
return exactMatches;
|
return exactMatches;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,4 +293,31 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
|||||||
return exactFingerprints;
|
return exactFingerprints;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see <a href="https://docs.curseforge.com/#tocS_FingerprintMatch">Schema</a>
|
||||||
|
*/
|
||||||
|
private static class FingerprintMatch {
|
||||||
|
private final int id;
|
||||||
|
private final CurseAddon.LatestFile file;
|
||||||
|
private final List<CurseAddon.LatestFile> latestFiles;
|
||||||
|
|
||||||
|
public FingerprintMatch(int id, CurseAddon.LatestFile file, List<CurseAddon.LatestFile> latestFiles) {
|
||||||
|
this.id = id;
|
||||||
|
this.file = file;
|
||||||
|
this.latestFiles = latestFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CurseAddon.LatestFile getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<CurseAddon.LatestFile> getLatestFiles() {
|
||||||
|
return latestFiles;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
import org.jackhuang.hmcl.util.Immutable;
|
import org.jackhuang.hmcl.util.Immutable;
|
||||||
import org.jackhuang.hmcl.util.gson.Validation;
|
import org.jackhuang.hmcl.util.gson.Validation;
|
||||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -82,9 +83,17 @@ public final class CurseManifestFile implements Validation {
|
|||||||
throw new JsonParseException("Missing Project ID or File ID.");
|
throw new JsonParseException("Missing Project ID or File ID.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public URL getUrl() {
|
public URL getUrl() {
|
||||||
return url == null ? NetworkUtils.toURL("https://www.curseforge.com/minecraft/mc-mods/" + projectID + "/download/" + fileID + "/file")
|
if (url == null) {
|
||||||
: NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
if (fileName != null) {
|
||||||
|
return NetworkUtils.toURL(NetworkUtils.encodeLocation(String.format("https://edge.forgecdn.net/files/%d/%d/%s", fileID / 1000, fileID % 1000, fileName)));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NetworkUtils.toURL(NetworkUtils.encodeLocation(url));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CurseManifestFile withFileName(String fileName) {
|
public CurseManifestFile withFileName(String fileName) {
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ import org.jackhuang.hmcl.util.io.ResponseCodeException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -74,7 +71,8 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ModResult> searchPaginated(String gameVersion, int pageOffset, int pageSize, String searchFilter, SortType sort) throws IOException {
|
@Override
|
||||||
|
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
|
||||||
Map<String, String> query = mapOf(
|
Map<String, String> query = mapOf(
|
||||||
pair("query", searchFilter),
|
pair("query", searchFilter),
|
||||||
pair("offset", Integer.toString(pageOffset)),
|
pair("offset", Integer.toString(pageOffset)),
|
||||||
@@ -87,13 +85,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
Response<ModResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/api/v1/mod", query))
|
Response<ModResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/api/v1/mod", query))
|
||||||
.getJson(new TypeToken<Response<ModResult>>() {
|
.getJson(new TypeToken<Response<ModResult>>() {
|
||||||
}.getType());
|
}.getType());
|
||||||
return response.getHits();
|
return response.getHits().stream().map(ModResult::toMod);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Stream<RemoteMod> search(String gameVersion, Category category, int pageOffset, int pageSize, String searchFilter, SortType sort) throws IOException {
|
|
||||||
return searchPaginated(gameVersion, pageOffset, pageSize, searchFilter, sort).stream()
|
|
||||||
.map(ModResult::toMod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -119,6 +111,11 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RemoteMod.File getModFile(String modId, String fileId) throws IOException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
|
||||||
id = StringUtils.removePrefix(id, "local-");
|
id = StringUtils.removePrefix(id, "local-");
|
||||||
@@ -238,7 +235,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
private final String changelog;
|
private final String changelog;
|
||||||
|
|
||||||
@SerializedName("date_published")
|
@SerializedName("date_published")
|
||||||
private final Instant datePublished;
|
private final Date datePublished;
|
||||||
|
|
||||||
private final int downloads;
|
private final int downloads;
|
||||||
|
|
||||||
@@ -254,7 +251,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
|
|
||||||
private final List<String> loaders;
|
private final List<String> loaders;
|
||||||
|
|
||||||
public ModVersion(String id, String modId, String authorId, String name, String versionNumber, String changelog, Instant datePublished, int downloads, String versionType, List<ModVersionFile> files, List<String> dependencies, List<String> gameVersions, List<String> loaders) {
|
public ModVersion(String id, String modId, String authorId, String name, String versionNumber, String changelog, Date datePublished, int downloads, String versionType, List<ModVersionFile> files, List<String> dependencies, List<String> gameVersions, List<String> loaders) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.modId = modId;
|
this.modId = modId;
|
||||||
this.authorId = authorId;
|
this.authorId = authorId;
|
||||||
@@ -294,7 +291,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
return changelog;
|
return changelog;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Instant getDatePublished() {
|
public Date getDatePublished() {
|
||||||
return datePublished;
|
return datePublished;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -501,13 +498,13 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<RemoteMod> loadDependencies() throws IOException {
|
public List<RemoteMod> loadDependencies(RemoteModRepository modRepository) throws IOException {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Stream<RemoteMod.Version> loadVersions() throws IOException {
|
public Stream<RemoteMod.Version> loadVersions(RemoteModRepository modRepository) throws IOException {
|
||||||
return ModrinthRemoteModRepository.INSTANCE.getRemoteVersionsById(getModId());
|
return modRepository.getRemoteVersionsById(getModId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteMod toMod() {
|
public RemoteMod toMod() {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import java.util.*;
|
|||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||||
|
|
||||||
@@ -48,12 +49,14 @@ public abstract class FetchTask<T> extends Task<T> {
|
|||||||
protected CacheRepository repository = CacheRepository.getInstance();
|
protected CacheRepository repository = CacheRepository.getInstance();
|
||||||
|
|
||||||
public FetchTask(List<URL> urls, int retry) {
|
public FetchTask(List<URL> urls, int retry) {
|
||||||
if (urls == null || urls.isEmpty())
|
Objects.requireNonNull(urls);
|
||||||
throw new IllegalArgumentException("At least one URL is required");
|
|
||||||
|
|
||||||
this.urls = new ArrayList<>(urls);
|
this.urls = urls.stream().filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
this.retry = retry;
|
this.retry = retry;
|
||||||
|
|
||||||
|
if (this.urls.isEmpty())
|
||||||
|
throw new IllegalArgumentException("At least one URL is required");
|
||||||
|
|
||||||
setExecutor(download());
|
setExecutor(download());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import java.lang.reflect.Type;
|
|||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeParseException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
@@ -66,14 +71,14 @@ public final class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserial
|
|||||||
return EN_US_FORMAT.parse(string);
|
return EN_US_FORMAT.parse(string);
|
||||||
} catch (ParseException ex1) {
|
} catch (ParseException ex1) {
|
||||||
try {
|
try {
|
||||||
return ISO_8601_FORMAT.parse(string);
|
ZonedDateTime zonedDateTime = ZonedDateTime.parse(string, DateTimeFormatter.ISO_DATE_TIME);
|
||||||
} catch (ParseException ex2) {
|
return Date.from(zonedDateTime.toInstant());
|
||||||
|
} catch (DateTimeParseException e) {
|
||||||
try {
|
try {
|
||||||
String cleaned = string.replace("Z", "+00:00");
|
LocalDateTime localDateTime = LocalDateTime.parse(string, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
|
||||||
cleaned = cleaned.substring(0, 22) + cleaned.substring(23);
|
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
|
||||||
return ISO_8601_FORMAT.parse(cleaned);
|
} catch (DateTimeParseException e2) {
|
||||||
} catch (Exception e) {
|
throw new JsonParseException("Invalid date: " + string, e2);
|
||||||
throw new JsonParseException("Invalid date: " + string, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ public final class JavaVersion {
|
|||||||
|
|
||||||
JavaVersion javaVersion = new JavaVersion(executable, version, platform);
|
JavaVersion javaVersion = new JavaVersion(executable, version, platform);
|
||||||
if (javaVersion.getParsedVersion() == UNKNOWN)
|
if (javaVersion.getParsedVersion() == UNKNOWN)
|
||||||
throw new IOException("Unrecognized Java version " + version);
|
throw new IOException("Unrecognized Java version " + version + " at " + executable);
|
||||||
fromExecutableCache.put(executable, javaVersion);
|
fromExecutableCache.put(executable, javaVersion);
|
||||||
return javaVersion;
|
return javaVersion;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user