From 42b68624a971e29c0c13afe5b2212222104e7cb1 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Sun, 31 Aug 2025 01:45:52 +0200 Subject: [PATCH 1/6] feat: aggregate same mods from multiple repos in search --- mclib/src/query/IModQueryService.ts | 2 +- mclib/src/query/LocalModQueryService.ts | 28 +++++++++---- mclib/src/query/RemoteModQueryService.ts | 2 +- mclib/src/utils.ts | 14 +++++++ web/src/components/ModSearch.svelte | 6 +-- web/src/components/ModSearchList.svelte | 52 ++++++++++++++++++------ 6 files changed, 80 insertions(+), 24 deletions(-) diff --git a/mclib/src/query/IModQueryService.ts b/mclib/src/query/IModQueryService.ts index d9dbb87..613080a 100644 --- a/mclib/src/query/IModQueryService.ts +++ b/mclib/src/query/IModQueryService.ts @@ -6,7 +6,7 @@ export interface IModQueryService { query: string, specifiedRepos: ModRepositoryName[], maxResults: number, - ): Promise>; + ): Promise; getModReleasesFromMetadata(modMeta: ModMetadata): Promise; getModByDataHash(modData: Uint8Array): Promise; } diff --git a/mclib/src/query/LocalModQueryService.ts b/mclib/src/query/LocalModQueryService.ts index f42c255..f46a72c 100644 --- a/mclib/src/query/LocalModQueryService.ts +++ b/mclib/src/query/LocalModQueryService.ts @@ -1,5 +1,6 @@ import { type MCVersion, type ModRepositoryName, type ModRepoMetadata, type IRepository, type ModMetadata, type ModReleases, ModMetadataUtil, IModQueryService } from ".."; import { logger } from "../logger"; +import { isSameModAndRepo } from "../utils"; export class LocalModQueryService implements IModQueryService { @@ -21,9 +22,9 @@ export class LocalModQueryService implements IModQueryService { async searchMods( query: string, specifiedRepos: ModRepositoryName[], - maxResults: number = 10, - ): Promise> { - const allResults: Array<[ModRepositoryName, ModRepoMetadata]> = []; + maxResults: number, + ): Promise { + const allResults: Array = []; for (const repo of this.repositories) { try { @@ -32,9 +33,20 @@ export class LocalModQueryService implements IModQueryService { continue; // Skip repositories not in the specified list } - const results = await repo.searchMods(query, maxResults); - for (const mod of results) { - allResults.push([repoName, mod]); + // Loop over search results of this repository + for (const newModRepo of await repo.searchMods(query, maxResults)) { + + // Check if this mod already exists in the aggregated results + let wasMerged = false; + for (const existingMod of allResults) { + if (isSameModAndRepo(existingMod, newModRepo)) { + // Update existing mod entry with additional repository metadata + existingMod.push(newModRepo); + wasMerged = true; + break; + } + } + if (!wasMerged) allResults.push([newModRepo]); // Add as new entry } } catch (_) { // Ignore errors for individual repositories @@ -42,11 +54,13 @@ export class LocalModQueryService implements IModQueryService { } // Sort by download count descending - allResults.sort((a, b) => b[1].downloadCount - a[1].downloadCount); + allResults.sort((a, b) => b[0].downloadCount - a[0].downloadCount); + // Limit results if maxResults is specified if (maxResults !== undefined) { return allResults.slice(0, maxResults); } + return allResults; } diff --git a/mclib/src/query/RemoteModQueryService.ts b/mclib/src/query/RemoteModQueryService.ts index afd79d3..cd9a3d5 100644 --- a/mclib/src/query/RemoteModQueryService.ts +++ b/mclib/src/query/RemoteModQueryService.ts @@ -33,7 +33,7 @@ export class RemoteModQueryService implements IModQueryService { query: string, specifiedRepos: ModRepositoryName[], maxResults: number - ): Promise> { + ): Promise { return this.callEndpoint("searchMods", { query, specifiedRepos, maxResults }); } diff --git a/mclib/src/utils.ts b/mclib/src/utils.ts index caf3f3d..4659f8c 100644 --- a/mclib/src/utils.ts +++ b/mclib/src/utils.ts @@ -1,5 +1,19 @@ +import { ModMetadata, ModRepoMetadata } from "./types"; + export function validateParam(s: string) { if (!/^[a-zA-Z0-9]{0,64}$/.test(s)) { throw new Error('Invalid parameter: must be 0-64 alphanumeric characters'); } } + +export function isSameModAndRepo(modA: ModMetadata, modB: ModRepoMetadata): boolean { + for (const v of modA) { + if (isSameModRepo(v, modB)) return true; + } + return false; +} + + // Returns true if id OR slug matches id OR slug of the other mod +export function isSameModRepo(modA: ModRepoMetadata, modB: ModRepoMetadata): boolean { + return [modA.id, modA.slug].some(idA => [modB.id, modB.slug].includes(idA)); +} diff --git a/web/src/components/ModSearch.svelte b/web/src/components/ModSearch.svelte index 21015ae..312ef7e 100644 --- a/web/src/components/ModSearch.svelte +++ b/web/src/components/ModSearch.svelte @@ -1,7 +1,7 @@ - - - - {#each search_results.slice(0, 10) as meta (meta[0].id)} - {@const firstRepoMeta = meta[0]} - - - - - - - {/each} - -
{ - add_mod_to_list(firstRepoMeta); - }} - > - {firstRepoMeta.name} pic - { - add_mod_to_list(firstRepoMeta); - }} - > - {firstRepoMeta.name} - { - add_mod_to_list(firstRepoMeta); - }} - > - {humanize_number(firstRepoMeta.downloadCount) + - ' ' + - m['add_mods.search_results.downloads_count']()} -
+

{m['add_mods.search_results.repo_from']()} »

+ + + + + + {/each} +