Skip to content

Commit ead3966

Browse files
iTroozZeKap
andauthored
feat: aggregate same mods from multiple repos in search (#60)
* feat: aggregate same mods from multiple repos in search * format + lint * remove debug console.log calls * search 20 mods instead of 10 on each repo * feat: repos links take less place in search results * chore: fix lint --------- Co-authored-by: ZeKap <marla.kap@proton.me>
1 parent 230e904 commit ead3966

File tree

8 files changed

+195
-85
lines changed

8 files changed

+195
-85
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { MCVersion, ModMetadata, ModReleases, ModRepoMetadata, ModRepositoryName } from "../types";
1+
import { MCVersion, ModMetadata, ModReleases, ModRepositoryName } from "../types";
22

33
export interface IModQueryService {
44
getMinecraftVersions(): Promise<MCVersion[]>;
55
searchMods(
66
query: string,
77
specifiedRepos: ModRepositoryName[],
88
maxResults: number,
9-
): Promise<Array<[ModRepositoryName, ModRepoMetadata]>>;
9+
): Promise<ModMetadata[]>;
1010
getModReleasesFromMetadata(modMeta: ModMetadata): Promise<ModReleases>;
1111
getModByDataHash(modData: Uint8Array): Promise<ModMetadata>;
1212
}

mclib/src/query/LocalModQueryService.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { type MCVersion, type ModRepositoryName, type ModRepoMetadata, type IRepository, type ModMetadata, type ModReleases, ModMetadataUtil, IModQueryService } from "..";
1+
import { type MCVersion, type ModRepositoryName, type IRepository, type ModMetadata, type ModReleases, ModMetadataUtil, IModQueryService } from "..";
22
import { logger } from "../logger";
3+
import { isSameModAndRepo } from "../utils";
34

45
export class LocalModQueryService implements IModQueryService {
56

@@ -21,9 +22,9 @@ export class LocalModQueryService implements IModQueryService {
2122
async searchMods(
2223
query: string,
2324
specifiedRepos: ModRepositoryName[],
24-
maxResults: number = 10,
25-
): Promise<Array<[ModRepositoryName, ModRepoMetadata]>> {
26-
const allResults: Array<[ModRepositoryName, ModRepoMetadata]> = [];
25+
maxResults: number,
26+
): Promise<ModMetadata[]> {
27+
const allResults: Array<ModMetadata> = [];
2728

2829
for (const repo of this.repositories) {
2930
try {
@@ -32,21 +33,34 @@ export class LocalModQueryService implements IModQueryService {
3233
continue; // Skip repositories not in the specified list
3334
}
3435

35-
const results = await repo.searchMods(query, maxResults);
36-
for (const mod of results) {
37-
allResults.push([repoName, mod]);
36+
// Loop over search results of this repository
37+
for (const newModRepo of await repo.searchMods(query, maxResults)) {
38+
39+
// Check if this mod already exists in the aggregated results
40+
let wasMerged = false;
41+
for (const existingMod of allResults) {
42+
if (isSameModAndRepo(existingMod, newModRepo)) {
43+
// Update existing mod entry with additional repository metadata
44+
existingMod.push(newModRepo);
45+
wasMerged = true;
46+
break;
47+
}
48+
}
49+
if (!wasMerged) allResults.push([newModRepo]); // Add as new entry
3850
}
3951
} catch (_) {
4052
// Ignore errors for individual repositories
4153
}
4254
}
4355

4456
// Sort by download count descending
45-
allResults.sort((a, b) => b[1].downloadCount - a[1].downloadCount);
57+
allResults.sort((a, b) => b[0].downloadCount - a[0].downloadCount);
4658

59+
// Limit results if maxResults is specified
4760
if (maxResults !== undefined) {
4861
return allResults.slice(0, maxResults);
4962
}
63+
5064
return allResults;
5165
}
5266

mclib/src/query/RemoteModQueryService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class RemoteModQueryService implements IModQueryService {
3333
query: string,
3434
specifiedRepos: ModRepositoryName[],
3535
maxResults: number
36-
): Promise<Array<[ModRepositoryName, ModRepoMetadata]>> {
36+
): Promise<ModMetadata[]> {
3737
return this.callEndpoint("searchMods", { query, specifiedRepos, maxResults });
3838
}
3939

mclib/src/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
1+
import { ModMetadata, ModRepoMetadata } from "./types";
2+
13
export function validateParam(s: string) {
24
if (!/^[a-zA-Z0-9]{0,64}$/.test(s)) {
35
throw new Error('Invalid parameter: must be 0-64 alphanumeric characters');
46
}
57
}
8+
9+
export function isSameModAndRepo(modA: ModMetadata, modB: ModRepoMetadata): boolean {
10+
for (const v of modA) {
11+
if (isSameModRepo(v, modB)) return true;
12+
}
13+
return false;
14+
}
15+
16+
// Returns true if id OR slug matches id OR slug of the other mod
17+
export function isSameModRepo(modA: ModRepoMetadata, modB: ModRepoMetadata): boolean {
18+
return [modA.id, modA.slug].some(idA => [modB.id, modB.slug].includes(idA));
19+
}

web/messages/en.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
"search_mod_by_name": "Search for a mod by its name",
1010
"search_mods_button": "Search",
1111
"search_results": {
12-
"loading_mod_search": "Please wait, we're searching your mod",
1312
"downloads_count": "downloads",
14-
"open_mod_repo_link": "See on {repo_name}",
15-
"add_mod": "Add",
13+
"repo_from": "From",
1614
"remove_mod": "Remove"
1715
},
1816
"drag_and_drop": {

web/messages/fr.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,8 @@
99
"search_mod_by_name": "Recherchez un mod par son nom",
1010
"search_mods_button": "Rechercher",
1111
"search_results": {
12-
"loading_mod_search": "Veuillez patienter, nous recherchons votre mod",
1312
"downloads_count": "téléchargements",
14-
"open_mod_repo_link": "Voir sur {repo_name}",
15-
"add_mod": "Ajouter",
13+
"repo_from": "Depuis",
1614
"remove_mod": "Enlever"
1715
},
1816
"drag_and_drop": {

web/src/components/ModSearch.svelte

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import * as m from '$msg';
33
import { ModRepositoryName } from 'mclib';
4-
import type { ModRepoMetadata } from 'mclib';
4+
import type { ModMetadata, ModRepoMetadata } from 'mclib';
55
import { ModSearchList, ToggleButtons } from '$cmpts';
66
import { slide } from 'svelte/transition';
77
import { modQueryService, repositories } from '../config';
@@ -13,7 +13,7 @@
1313
add_mod_to_list
1414
}: {
1515
search_name_input: string;
16-
search_results: [ModRepositoryName, ModRepoMetadata][];
16+
search_results: ModMetadata[];
1717
is_loading_search: boolean;
1818
add_mod_to_list: (mod: ModRepoMetadata) => void;
1919
} = $props();
@@ -35,7 +35,11 @@
3535
try {
3636
// set loading mode
3737
is_loading_search = true;
38-
const results = await modQueryService.searchMods(search_name_input, selected_mod_repo_names);
38+
const results = await modQueryService.searchMods(
39+
search_name_input,
40+
selected_mod_repo_names,
41+
20 // bigger number = more chance of having aggregated results between repositories (e.g. "jei" will not match JEI on modrinth for maxResults=10)
42+
);
3943
search_results = results;
4044
} catch (err) {
4145
console.log(err);

0 commit comments

Comments
 (0)