Use BMCLAPI mirror & update build

Route Minecraft/Fabric/Forge downloads through BMCLAPI mirror and update installer logic. Added mirror rewrite utility (rewriteToMirror), mirror-aware downloadFile, and mirrorFetch; switched version manifest and metadata endpoints to bmclapi2.bangbang93.com and wired maven/assets hosts into xmcl/installer calls. Cleaned up compiled electron JS files and consolidated runtime files under electron-dist; updated electron-builder.yml (compression, asar, NSIS settings, license/installer assets and languages). Minor env/path changes (VITE_APP_ICON paths), added .npmrc (electron_mirror), new Koring.yml, pnpm-workspace and public installer assets. Also added/updated multiple frontend pages, store components, and TypeScript handlers to integrate the changes.
This commit is contained in:
2026-08-04 01:28:16 +08:00
parent 06269698a6
commit cb0afd99c0
73 changed files with 3108 additions and 2898 deletions
+90 -28
View File
@@ -20,6 +20,19 @@ interface ModSearchResult {
source: 'modrinth' | 'curseforge';
}
interface ModSearchResponse {
hits: ModSearchResult[];
total: number;
limit: number;
offset: number;
}
interface ModCategory {
name: string;
label: string;
projectType: string;
}
interface ModVersionResult {
id: string;
name: string;
@@ -38,17 +51,21 @@ async function searchModrinth(
query?: string,
gameVersion?: string,
loader?: string,
category?: string,
projectType: string = 'mod',
limit: number = 20,
offset: number = 0
): Promise<ModSearchResult[]> {
): Promise<ModSearchResponse> {
const facets: string[][] = [];
if (gameVersion) facets.push([`versions:${gameVersion}`]);
if (loader) facets.push([`categories:${loader}`]);
if (category) facets.push([`categories:${category}`]);
const params = new URLSearchParams({
query: query || '',
limit: String(limit),
offset: String(offset),
project_type: projectType,
});
if (facets.length > 0) {
@@ -58,30 +75,38 @@ async function searchModrinth(
const response = await net.fetch(`${MODRINTH_API}/search?${params.toString()}`);
if (!response.ok) throw new Error(`Modrinth search failed: ${response.status}`);
const data = await response.json() as { hits: Array<{
project_id: string;
slug: string;
title: string;
description: string;
downloads: number;
icon_url?: string;
categories?: string[];
versions?: string[];
client_side?: string;
server_side?: string;
}> };
const data = await response.json() as {
total_hits: number;
hits: Array<{
project_id: string;
slug: string;
title: string;
description: string;
downloads: number;
icon_url?: string;
categories?: string[];
versions?: string[];
client_side?: string;
server_side?: string;
}>;
};
return data.hits.map((hit) => ({
id: hit.project_id,
slug: hit.slug,
name: hit.title,
description: hit.description,
downloads: hit.downloads,
iconUrl: hit.icon_url,
categories: hit.categories,
versions: hit.versions,
source: 'modrinth' as const,
}));
return {
hits: data.hits.map((hit) => ({
id: hit.project_id,
slug: hit.slug,
name: hit.title,
description: hit.description,
downloads: hit.downloads,
iconUrl: hit.icon_url,
categories: hit.categories,
versions: hit.versions,
source: 'modrinth' as const,
})),
total: data.total_hits,
limit,
offset,
};
}
async function searchCurseForge(
@@ -90,25 +115,62 @@ async function searchCurseForge(
loader?: string,
limit: number = 20,
offset: number = 0
): Promise<ModSearchResult[]> {
): Promise<ModSearchResponse> {
// CurseForge requires API key - return empty for now
return [];
return { hits: [], total: 0, limit, offset };
}
export async function searchMods(
query?: string,
gameVersion?: string,
loader?: string,
category?: string,
projectType: string = 'mod',
limit?: number,
offset?: number,
source: 'modrinth' | 'curseforge' = 'modrinth'
): Promise<ModSearchResult[]> {
): Promise<ModSearchResponse> {
if (source === 'modrinth') {
return searchModrinth(query, gameVersion, loader, limit, offset);
return searchModrinth(query, gameVersion, loader, category, projectType, limit, offset);
}
return searchCurseForge(query, gameVersion, loader, limit, offset);
}
export async function getCategories(projectType: string = 'mod'): Promise<ModCategory[]> {
const response = await net.fetch(`${MODRINTH_API}/tag/category`);
if (!response.ok) throw new Error(`Modrinth categories failed: ${response.status}`);
const data = await response.json() as Array<{
name: string;
project_type: string;
header: string;
}>;
// 只返回指定类型分类,label 优先取 header(国际化标题),无则用 name
return data
.filter((c) => c.project_type === projectType)
.map((c) => ({ name: c.name, label: c.header || c.name, projectType: c.project_type }));
}
export async function getGameVersions(): Promise<string[]> {
const response = await net.fetch(`${MODRINTH_API}/tag/game_version`);
if (!response.ok) throw new Error(`Modrinth game versions failed: ${response.status}`);
const data = await response.json() as string[];
// 只保留正式版(1.x 且非预览版),并按版本号倒序
return data
.filter((v) => /^\d+\.\d+(\.\d+)?$/.test(v))
.sort((a, b) => {
const pa = a.split('.').map(Number);
const pb = b.split('.').map(Number);
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
const diff = (pb[i] || 0) - (pa[i] || 0);
if (diff !== 0) return diff;
}
return 0;
});
}
export async function getModDetail(
projectId: string,
source: 'modrinth' | 'curseforge' = 'modrinth'