diff --git a/src/main/services/mods/beat-mods-api.service.ts b/src/main/services/mods/beat-mods-api.service.ts index 0bfeedee..b80b4030 100644 --- a/src/main/services/mods/beat-mods-api.service.ts +++ b/src/main/services/mods/beat-mods-api.service.ts @@ -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(); private readonly versionModsCache = new Map(); + private readonly modsHashCache = new Map(); 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> { - if (this.aliasesCache.size) { - return this.aliasesCache; - } - return this.requestService.getJSON>(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 { - 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 { if (this.versionModsCache.has(version.BSVersion)) { return this.versionModsCache.get(version.BSVersion); } - const alias = await this.getAliasOfVersion(version); - - return this.requestService.getJSON(this.getVersionModsUrl(alias)).then(mods => { + return this.requestService.getJSON(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 { - if (this.allModsCache) { - return this.allModsCache; + public getModByHash(hash: string): Promise { + if (this.modsHashCache.has(hash)) { + return Promise.resolve(this.modsHashCache.get(hash)); } - return this.requestService.getJSON(this.getAllModsUrl()).then(mods => { - this.allModsCache = mods; - return this.allModsCache; + + return this.requestService.getJSON(`${this.BEAT_MODS_API_URL}/hashlookup?hash=${hash}`).then(mods => { + this.updateModsHashCache(mods); + return mods.at(0); }); } } diff --git a/src/main/services/mods/bs-mods-manager.service.ts b/src/main/services/mods/bs-mods-manager.service.ts index c52787cc..6a7b3006 100644 --- a/src/main/services/mods/bs-mods-manager.service.ts +++ b/src/main/services/mods/bs-mods-manager.service.ts @@ -49,23 +49,14 @@ export class BsModsManagerService { } private async getModFromHash(hash: string): Promise { - 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 { - 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 { @@ -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 { @@ -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 { - 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 { 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)]); diff --git a/src/shared/models/mods/mod.interface.ts b/src/shared/models/mods/mod.interface.ts index 02bb21b2..5b7f221b 100644 --- a/src/shared/models/mods/mod.interface.ts +++ b/src/shared/models/mods/mod.interface.ts @@ -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; +}