From 2a6cd17a3ceff6d2f5653607c111a4e0e941c0ff Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 24 Feb 2026 21:21:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E5=90=AF=E7=94=A8=20MCIM=20?= =?UTF-8?q?=E5=8A=A0=E9=80=9F=20(#5030)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../download/BMCLAPIDownloadProvider.java | 13 ++++--- .../curse/CurseForgeRemoteModRepository.java | 34 ++++++++++++++++--- .../modrinth/ModrinthRemoteModRepository.java | 29 ++++++++++++++-- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java index 7042d32279..ccafef0293 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.java @@ -93,13 +93,12 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider { pair("https://repo.maven.apache.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"), pair("https://hmcl.glavo.site/metadata/cleanroom", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/cleanroom"), pair("https://hmcl.glavo.site/metadata/fmllibs", "https://alist.8mi.tech/d/mirror/HMCL-Metadata/Auto/fmllibs"), - pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto") -// // https://github.com/mcmod-info-mirror/mcim-rust-api -// pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"), -// pair("https://cdn.modrinth.com", "https://mod.mcimirror.top"), -// pair("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"), -// pair("https://edge.forgecdn.net", "https://mod.mcimirror.top"), -// pair("https://mediafilez.forgecdn.net", "https://mod.mcimirror.top") + pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto"), + // https://github.com/mcmod-info-mirror/mcim-rust-api + pair("https://api.modrinth.com", "https://mod.mcimirror.top/modrinth"), + pair("https://cdn.modrinth.com", "https://mod.mcimirror.top"), + pair("https://api.curseforge.com", "https://mod.mcimirror.top/curseforge"), + pair("https://edge.forgecdn.net", "https://mod.mcimirror.top") ); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java index 62ed344418..9f70021795 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseForgeRemoteModRepository.java @@ -33,6 +33,7 @@ import org.jetbrains.annotations.Nullable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -42,6 +43,7 @@ import java.util.stream.Stream; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class CurseForgeRemoteModRepository implements RemoteModRepository { @@ -131,10 +133,34 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository query.put("index", Integer.toString(pageOffset * pageSize)); query.put("pageSize", Integer.toString(pageSize)); - Response> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", query)))) - .getJson(Response.typeOf(listTypeOf(CurseAddon.class))); - if (searchFilter.isEmpty()) { - return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize)); + Response> response = null; + + IOException exception = null; + List candidates = downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", query)); + for (URI candidate : candidates) { + LOG.info("Fetching " + candidate); + try { + response = withApiKey(HttpRequest.GET(candidate.toString())) + .getJson(Response.typeOf(listTypeOf(CurseAddon.class))); + if (searchFilter.isEmpty()) { + return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize)); + } + break; + } catch (IOException e) { + LOG.warning("Failed to search addons: " + candidate, e); + if (candidates.size() == 1) { + exception = e; + } else { + if (exception == null) { + exception = new IOException("Failed to search addons"); + } + exception.addSuppressed(e); + } + } + } + + if (response == null) { + throw exception != null ? exception : new IOException("No candidates found"); } // https://github.com/HMCL-dev/HMCL/issues/1549 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index a0dde282fc..3c0ff4318e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -37,6 +37,7 @@ import org.jetbrains.annotations.Nullable; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.URI; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.time.Instant; @@ -54,6 +55,7 @@ import java.util.stream.Stream; import static org.jackhuang.hmcl.util.Lang.mapOf; import static org.jackhuang.hmcl.util.Pair.pair; import static org.jackhuang.hmcl.util.gson.JsonUtils.listTypeOf; +import static org.jackhuang.hmcl.util.logging.Logger.LOG; public final class ModrinthRemoteModRepository implements RemoteModRepository { public static final ModrinthRemoteModRepository MODS = new ModrinthRemoteModRepository("mod"); @@ -112,9 +114,30 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository { pair("limit", Integer.toString(pageSize)), pair("index", convertSortType(sort)) ); - Response response = HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v2/search", query))) - .getJson(Response.typeOf(ProjectSearchResult.class)); - return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize)); + + + List candidates = downloadProvider.injectURLWithCandidates(NetworkUtils.withQuery(PREFIX + "/v2/search", query)); + IOException exception = null; + for (URI candidate : candidates) { + try { + LOG.info("Fetching " + candidate); + Response response = HttpRequest.GET(candidate.toString()) + .getJson(Response.typeOf(ProjectSearchResult.class)); + return new SearchResult(response.getHits().stream().map(ProjectSearchResult::toMod), (int) Math.ceil((double) response.totalHits / pageSize)); + } catch (IOException e) { + LOG.warning("Failed to search addons: " + candidate, e); + if (candidates.size() == 1) { + exception = e; + } else { + if (exception == null) { + exception = new IOException("Failed to search addons"); + } + exception.addSuppressed(e); + } + } + } + + throw exception != null ? exception : new IOException("No candidates found"); } finally { SEMAPHORE.release(); }