Merge pull request #508 from Zagrios/qol/mods-load-faster

[QOL] Mods load faster

(cherry picked from commit a6c1ba9fee)

+ init migration to BadBeatMods
This commit is contained in:
MathieuG-P
2024-12-28 22:54:32 +01:00
parent 992f41238c
commit 0f7c42a90c
3 changed files with 128 additions and 68 deletions
+42 -45
View File
@@ -7,14 +7,11 @@ export class BeatModsApiService {
private readonly requestService: RequestService;
private readonly BEAT_MODS_VERSIONS = "https://versions.beatmods.com/versions.json";
private readonly BEAT_MODS_ALIAS = "https://alias.beatmods.com/aliases.json";
public readonly MODS_REPO_URL = "https://bbm.saera.gay";
private readonly BEAT_MODS_API_URL = `${this.MODS_REPO_URL}/api`;
private readonly BEAT_MODS_API_URL = "https://beatmods.com/api/v1/";
public readonly BEAT_MODS_URL = "https://beatmods.com";
private readonly aliasesCache = new Map<string, BSVersion[]>();
private readonly versionModsCache = new Map<string, Mod[]>();
private readonly modsHashCache = new Map<string, Mod>();
private allModsCache: Mod[];
@@ -29,37 +26,12 @@ export class BeatModsApiService {
this.requestService = RequestService.getInstance();
}
private convertToBeatModsMod(mod: Mod): Mod {
}
private getVersionModsUrl(version: BSVersion): string {
return `${this.BEAT_MODS_API_URL}mod?status=approved&gameVersion=${version.BSVersion}&sort=&sortDirection=1`;
}
private getAllModsUrl(): string {
return `${this.BEAT_MODS_API_URL}mod`;
}
private async getVersionAlias(): Promise<Map<string, BSVersion[]>> {
if (this.aliasesCache.size) {
return this.aliasesCache;
}
return this.requestService.getJSON<Record<string, string[]>>(this.BEAT_MODS_ALIAS).then(rawAliases => {
Object.entries(rawAliases).forEach(([key, value]) => {
this.aliasesCache.set(
key,
value.map(s => ({ BSVersion: s } as BSVersion))
);
});
return this.aliasesCache;
});
}
private async getAliasOfVersion(version: BSVersion): Promise<BSVersion> {
return this.getVersionAlias().then(aliases => {
if (Array.from(aliases.keys()).some(k => k === version.BSVersion)) {
return version;
}
const alias = Array.from(aliases.entries()).find(([key, value]) => value.find(v => v.BSVersion === version.BSVersion))?.[0];
return { BSVersion: alias } as BSVersion;
});
return `${this.BEAT_MODS_API_URL}/mods?status=verified&gameVersion=${version.BSVersion}`;
}
private asignDependencies(mod: Mod, mods: Mod[]): Mod {
@@ -67,27 +39,52 @@ export class BeatModsApiService {
return mod;
}
private updateModsHashCache(mods: Mod[]): void {
if(!Array.isArray(mods)){
return;
}
for (const mod of mods) {
for (const downloads of (mod.downloads ?? [])) {
for (const hashMd5 of (downloads.hashMd5 ?? [])) {
this.modsHashCache.set(hashMd5.hash, mod);
}
}
for (const dep of (mod.dependencies ?? [])) {
for (const downloads of (dep.downloads ?? [])) {
for (const hashMd5 of (downloads.hashMd5 ?? [])) {
this.modsHashCache.set(hashMd5.hash, dep);
}
}
}
}
}
public async getVersionMods(version: BSVersion): Promise<Mod[]> {
if (this.versionModsCache.has(version.BSVersion)) {
return this.versionModsCache.get(version.BSVersion);
}
const alias = await this.getAliasOfVersion(version);
return this.requestService.getJSON<Mod[]>(this.getVersionModsUrl(alias)).then(mods => {
return this.requestService.getJSON<Mod[]>(this.getVersionModsUrl(version)).then(mods => {
mods = mods.map(mod => this.asignDependencies(mod, mods));
this.versionModsCache.set(version.BSVersion, mods);
this.updateModsHashCache(mods);
return mods;
});
}
public async getAllMods(): Promise<Mod[]> {
if (this.allModsCache) {
return this.allModsCache;
public getModByHash(hash: string): Promise<Mod> {
if (this.modsHashCache.has(hash)) {
return Promise.resolve(this.modsHashCache.get(hash));
}
return this.requestService.getJSON<Mod[]>(this.getAllModsUrl()).then(mods => {
this.allModsCache = mods;
return this.allModsCache;
return this.requestService.getJSON<Mod[]>(`${this.BEAT_MODS_API_URL}/hashlookup?hash=${hash}`).then(mods => {
this.updateModsHashCache(mods);
return mods.at(0);
});
}
}
@@ -49,23 +49,14 @@ export class BsModsManagerService {
}
private async getModFromHash(hash: string): Promise<Mod> {
const allMods = await this.beatModsApi.getAllMods();
return allMods.find(mod => {
if (mod.name.toLowerCase() === "bsipa") {
return false;
}
return mod.downloads.some(download => download.hashMd5.some(md5 => md5.hash === hash));
});
}
private async getIpaFromHash(hash: string): Promise<Mod> {
const allMods = await this.beatModsApi.getAllMods();
return allMods.find(mod => {
if (mod.name.toLowerCase() !== "bsipa") {
return false;
}
return mod.downloads.some(download => download.hashMd5.some(md5 => md5.hash === hash));
});
const mod = await this.beatModsApi.getModByHash(hash);
if(mod?.name?.toLowerCase() === "bsipa"){
return undefined;
}
return mod;
}
private async getModsInDir(version: BSVersion, modsDir: ModsInstallFolder): Promise<Mod[]> {
@@ -85,7 +76,6 @@ export class BsModsManagerService {
return undefined;
}
const hash = await md5File(filePath);
const mod = await this.getModFromHash(hash);
if (!mod) {
@@ -111,7 +101,7 @@ export class BsModsManagerService {
});
const mods = await Promise.all(promises);
return mods.filter(Boolean);
return mods.filter(Boolean);
}
private async getBsipaInstalled(version: BSVersion): Promise<Mod> {
@@ -121,19 +111,18 @@ export class BsModsManagerService {
return undefined;
}
const injectorMd5 = await md5File(injectorPath);
return this.getIpaFromHash(injectorMd5);
return this.beatModsApi.getModByHash(injectorMd5);
}
private async downloadZip(zipUrl: string): Promise<BsmZipExtractor> {
zipUrl = path.join(this.beatModsApi.BEAT_MODS_URL, zipUrl);
zipUrl = path.join(this.beatModsApi.MODS_REPO_URL, zipUrl);
log.info("Download mod zip", zipUrl);
const buffer = await lastValueFrom(this.requestService.downloadBuffer(zipUrl))
.then(progress => progress.data)
.catch(e => {
.catch((e: Error) => {
log.error("ZIP", "Error while downloading zip", e);
return undefined;
});
if (!buffer) {
@@ -325,7 +314,7 @@ export class BsModsManagerService {
public async getInstalledMods(version: BSVersion): Promise<Mod[]> {
this.manifestMatches = [];
await this.beatModsApi.getAllMods();
const bsipa = await this.getBsipaInstalled(version);
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS), this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING)]);
+74
View File
@@ -34,3 +34,77 @@ export interface FileHashes {
hash: string;
file: string;
}
// BBM MOD
export interface BbmModVersion {
id: number;
modId: number;
author: BbmUserAPIResponse;
modVersion: string;
platform: BbmPlatform;
zipHash: string;
status: BbmStatus;
dependencies: number[];
contentHashes: BbmContentHash[];
supportedGameVersions: BbmGameVersion[];
downloadCount: number;
lastApprovedById?: number;
lastUpdatedById?: number;
createdAt?: Date;
updatedAt?: Date;
}
export interface BbmContentHash {
path: string;
hash: string;
}
export enum BbmStatus {
Private = "private",
Removed = "removed",
Unverified = "unverified",
Verified = "verified",
}
export enum BbmPlatform {
SteamPC = `steampc`,
OculusPC = `oculuspc`,
UniversalPC = `universalpc`,
UniversalQuest = `universalquest`,
}
export interface BbmGameVersion {
readonly id: number;
gameName: "BeatSaber";
version: string; // semver
defaultVersion: boolean;
}
export enum BbmCategories {
Core = "core",
Essential = "essential",
Library = "library",
Cosmetic = "cosmetic",
PracticeTraining = "practice",
Gameplay = "gameplay",
StreamTools = "streamtools",
UIEnhancements = "ui",
Lighting = "lighting",
TweaksTools = "tweaks",
Multiplayer = "multiplayer",
TextChanges = "text",
Editor = "editor",
Other = "other",
}
export interface BbmUserAPIResponse {
id: number;
username: string;
githubId: string;
sponsorUrl: string;
displayName: string;
bio: string;
createdAt?: Date;
updatedAt?: Date;
}