支持通过 MCIM 加速模组搜索 (#4225)

This commit is contained in:
Glavo
2025-08-11 16:06:59 +08:00
committed by GitHub
parent 41fd38d8d1
commit 0913c5e710
13 changed files with 68 additions and 33 deletions

View File

@@ -78,7 +78,14 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
pair("https://maven.fabricmc.net", apiRoot + "/maven"),
pair("https://authlib-injector.yushi.moe", apiRoot + "/mirrors/authlib-injector"),
pair("https://repo1.maven.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto")
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")
);
}

View File

@@ -17,6 +17,7 @@
*/
package org.jackhuang.hmcl.mod;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
@@ -83,7 +84,7 @@ public interface RemoteModRepository {
}
}
SearchResult search(String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder)
SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder)
throws IOException;
Optional<RemoteMod.Version> getRemoteVersionByLocalFile(LocalModFile localModFile, Path file) throws IOException;

View File

@@ -18,6 +18,7 @@
package org.jackhuang.hmcl.mod.curse;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.RemoteMod;
import org.jackhuang.hmcl.mod.RemoteModRepository;
@@ -26,6 +27,7 @@ import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.HttpRequest;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jetbrains.annotations.Nullable;
import java.io.ByteArrayOutputStream;
@@ -47,6 +49,13 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
private static final int WORD_PERFECT_MATCH_WEIGHT = 5;
private static <R extends HttpRequest> R withApiKey(R request) {
if (request.getUrl().startsWith(PREFIX) && !apiKey.isEmpty()) {
request.header("X-API-KEY", apiKey);
}
return request;
}
public static boolean isAvailable() {
return !apiKey.isEmpty();
}
@@ -100,29 +109,28 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
}
@Override
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sortType, SortOrder sortOrder) throws IOException {
int categoryId = 0;
if (category != null && category.getSelf() instanceof CurseAddon.Category) {
categoryId = ((CurseAddon.Category) category.getSelf()).getId();
}
Response<List<CurseAddon>> response = HttpRequest.GET(PREFIX + "/v1/mods/search",
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 * pageSize)),
pair("pageSize", Integer.toString(pageSize)))
.header("X-API-KEY", apiKey)
Response<List<CurseAddon>> response = withApiKey(HttpRequest.GET(downloadProvider.injectURL(NetworkUtils.withQuery(PREFIX + "/v1/mods/search", mapOf(
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 * pageSize)),
pair("pageSize", Integer.toString(pageSize)))))))
.getJson(Response.typeOf(listTypeOf(CurseAddon.class)));
if (searchFilter.isEmpty()) {
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), calculateTotalPages(response, pageSize));
}
// https://github.com/HMCL-dev/HMCL/issues/1549
String lowerCaseSearchFilter = searchFilter.toLowerCase();
String lowerCaseSearchFilter = searchFilter.toLowerCase(Locale.ROOT);
Map<String, Integer> searchFilterWords = new HashMap<>();
for (String s : StringUtils.tokenize(lowerCaseSearchFilter)) {
searchFilterWords.put(s, searchFilterWords.getOrDefault(s, 0) + 1);
@@ -162,9 +170,8 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
long hash = Integer.toUnsignedLong(MurmurHash2.hash32(baos.toByteArray(), baos.size(), 1));
Response<FingerprintMatchesResult> response = HttpRequest.POST(PREFIX + "/v1/fingerprints/432")
Response<FingerprintMatchesResult> response = withApiKey(HttpRequest.POST(PREFIX + "/v1/fingerprints/432"))
.json(mapOf(pair("fingerprints", Collections.singletonList(hash))))
.header("X-API-KEY", apiKey)
.getJson(Response.typeOf(FingerprintMatchesResult.class));
if (response.getData().getExactMatches() == null || response.getData().getExactMatches().isEmpty()) {
@@ -176,32 +183,28 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
@Override
public RemoteMod getModById(String id) throws IOException {
Response<CurseAddon> response = HttpRequest.GET(PREFIX + "/v1/mods/" + id)
.header("X-API-KEY", apiKey)
Response<CurseAddon> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id))
.getJson(Response.typeOf(CurseAddon.class));
return response.data.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)
Response<CurseAddon.LatestFile> response = withApiKey(HttpRequest.GET(String.format("%s/v1/mods/%s/files/%s", PREFIX, modId, fileId)))
.getJson(Response.typeOf(CurseAddon.LatestFile.class));
return response.getData().toVersion().getFile();
}
@Override
public Stream<RemoteMod.Version> getRemoteVersionsById(String id) throws IOException {
Response<List<CurseAddon.LatestFile>> response = HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files",
pair("pageSize", "10000"))
.header("X-API-KEY", apiKey)
Response<List<CurseAddon.LatestFile>> response = withApiKey(HttpRequest.GET(PREFIX + "/v1/mods/" + id + "/files",
pair("pageSize", "10000")))
.getJson(Response.typeOf(listTypeOf(CurseAddon.LatestFile.class)));
return response.getData().stream().map(CurseAddon.LatestFile::toVersion);
}
public List<CurseAddon.Category> getCategoriesImpl() throws IOException {
Response<List<CurseAddon.Category>> categories = HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432"))
.header("X-API-KEY", apiKey)
Response<List<CurseAddon.Category>> categories = withApiKey(HttpRequest.GET(PREFIX + "/v1/categories", pair("gameId", "432")))
.getJson(Response.typeOf(listTypeOf(CurseAddon.Category.class)));
return reorganizeCategories(categories.getData(), section);
}

View File

@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.mod.modrinth;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.mod.LocalModFile;
import org.jackhuang.hmcl.mod.ModLoaderType;
import org.jackhuang.hmcl.mod.RemoteMod;
@@ -79,7 +80,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
}
@Override
public SearchResult search(String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
public SearchResult search(DownloadProvider downloadProvider, String gameVersion, @Nullable RemoteModRepository.Category category, int pageOffset, int pageSize, String searchFilter, SortType sort, SortOrder sortOrder) throws IOException {
List<List<String>> facets = new ArrayList<>();
facets.add(Collections.singletonList("project_type:" + projectType));
if (StringUtils.isNotBlank(gameVersion)) {
@@ -95,7 +96,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
pair("limit", Integer.toString(pageSize)),
pair("index", convertSortType(sort))
);
Response<ProjectSearchResult> response = HttpRequest.GET(NetworkUtils.withQuery(PREFIX + "/v2/search", query))
Response<ProjectSearchResult> 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));
}

View File

@@ -38,11 +38,12 @@ import static org.jackhuang.hmcl.util.logging.Logger.LOG;
public final class CacheFileTask extends FetchTask<Path> {
public CacheFileTask(@NotNull String uri) {
super(List.of(NetworkUtils.toURI(uri)));
this(NetworkUtils.toURI(uri));
}
public CacheFileTask(@NotNull URI uri) {
super(List.of(uri));
setName(uri.toString());
}
@Override

View File

@@ -57,6 +57,10 @@ public abstract class HttpRequest {
this.method = method;
}
public String getUrl() {
return url;
}
public HttpRequest accept(String contentType) {
return header("Accept", contentType);
}