From 58faea04a3797e912d929eca608080f0a9e2ec87 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Tue, 20 Dec 2022 03:00:51 +0100 Subject: [PATCH] start playlists management --- src/main/main.ts | 3 +- .../local-playlists-manager.service.ts | 81 +++++++++++++++++++ .../beat-saver/beat-saver-api.service.ts | 12 ++- .../beat-saver/beat-saver.service.ts | 10 ++- src/shared/models/maps/beat-saver.model.ts | 44 +++++++++- 5 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 src/main/services/additional-content/local-playlists-manager.service.ts diff --git a/src/main/main.ts b/src/main/main.ts index 23d7a27c..15425903 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -17,6 +17,7 @@ import { WindowManagerService } from './services/window-manager.service'; import { DeepLinkService } from './services/deep-link.service'; import { AppWindow } from 'shared/models/window-manager/app-window.model'; import { LocalMapsManagerService } from './services/additional-content/local-maps-manager.service'; +import { LocalPlaylistsManagerService } from './services/additional-content/local-playlists-manager.service'; export const PRELOAD_PATH = app.isPackaged ? path.join(__dirname, 'preload.js') : path.join(__dirname, '../../.erb/dll/preload.js') @@ -57,7 +58,7 @@ const createWindow = async (window: AppWindow = "launcher.html") => { const initServicesMustBeInitialized = () => { LocalMapsManagerService.getInstance(); - // Playlist + LocalPlaylistsManagerService.getInstance(); // Model } diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts new file mode 100644 index 00000000..0b190095 --- /dev/null +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -0,0 +1,81 @@ +import path from "path"; +import { BehaviorSubject } from "rxjs"; +import { Observable } from "rxjs"; +import { BSVersion } from "shared/bs-version.interface"; +import { BsvPlaylist, BsvPlaylistPage } from "shared/models/maps/beat-saver.model"; +import { BSLocalVersionService } from "../bs-local-version.service"; +import { RequestService } from "../request.service"; +import { UtilsService } from "../utils.service"; +import { LocalMapsManagerService } from "./local-maps-manager.service"; + +export class LocalPlaylistsManagerService { + + private static instance: LocalPlaylistsManagerService + + public static getInstance(): LocalPlaylistsManagerService { + if (!LocalPlaylistsManagerService.instance) { LocalPlaylistsManagerService.instance = new LocalPlaylistsManagerService(); } + return LocalPlaylistsManagerService.instance; + } + + private readonly PLAYLISTS_FOLDER = "Playlists"; + private readonly DEEP_LINKS = { + BeatSaver: "bsplaylist", + }; + + + private readonly versions: BSLocalVersionService; + private readonly maps: LocalMapsManagerService; + public readonly utils: UtilsService; + public readonly request: RequestService; + + private constructor(){ + this.maps = LocalMapsManagerService.getInstance(); + this.versions = BSLocalVersionService.getInstance(); + this.utils = UtilsService.getInstance(); + this.request = RequestService.getInstance(); + } + + private getPlaylistIdFromDownloadUrl(url: string): string{ + const splited = url.split("/") + const idIndex = splited.indexOf("id"); + return splited[idIndex + 1]; + } + + private async downloadPlaylistFile(playlist: BsvPlaylist, version: BSVersion): Promise{ + + const versionFolder = await this.versions.getVersionPath(version); + const bpListUrl = playlist.downloadURL; + + const bsListDest = path.join(versionFolder, this.PLAYLISTS_FOLDER, path.basename(bpListUrl)); + + return this.request.downloadFile(bpListUrl, bsListDest); + + } + + public downloadPlaylist(playlistPage: BsvPlaylistPage, version: BSVersion): Observable{ + + const res = new BehaviorSubject([]); + + const observer = async () => { + try{ + const bpListPath = await this.downloadPlaylistFile(playlistPage.playlist, version); + + for(const map of playlistPage.maps){ + const mapPath = await this.maps.downloadMap(map.map, version); + res.next([...res.value, mapPath]); + } + + res.next([...res.value, bpListPath]); + res.complete(); + } + catch(e){ + res.error(e); + } + } + + observer(); + return res.asObservable(); + + } + +} \ No newline at end of file diff --git a/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts b/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts index 9b9612fe..26560756 100644 --- a/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts +++ b/src/main/services/thrid-party/beat-saver/beat-saver-api.service.ts @@ -1,6 +1,6 @@ import { ApiResult } from "renderer/models/api/api.model"; import { BsvMapDetail } from "shared/models/maps"; -import { MapFilter, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model"; +import { BsvPlaylistPage, MapFilter, SearchParams, SearchResponse } from "shared/models/maps/beat-saver.model"; import fetch from "node-fetch" export class BeatSaverApiService { @@ -121,4 +121,14 @@ export class BeatSaverApiService { } + public async getPlaylistDetails(id: number): Promise>{ + + const res = await fetch(`${this.bsaverApiUrl}/playlists/id/${id}`); + + const data = await res.json() as BsvPlaylistPage; + + return {status: res.status, data}; + + } + } \ No newline at end of file diff --git a/src/main/services/thrid-party/beat-saver/beat-saver.service.ts b/src/main/services/thrid-party/beat-saver/beat-saver.service.ts index 4630f2dc..9cc3c5e4 100644 --- a/src/main/services/thrid-party/beat-saver/beat-saver.service.ts +++ b/src/main/services/thrid-party/beat-saver/beat-saver.service.ts @@ -1,7 +1,8 @@ import { splitIntoChunk } from "../../../helpers/array-tools"; import { BsvMapDetail } from "shared/models/maps"; -import { SearchParams } from "shared/models/maps/beat-saver.model"; +import { BsvPlaylist, BsvPlaylistPage, SearchParams } from "shared/models/maps/beat-saver.model"; import { BeatSaverApiService } from "./beat-saver-api.service"; +import { red } from "tailwindcss/colors"; export class BeatSaverService { @@ -65,6 +66,13 @@ export class BeatSaverService { } + public async getPlaylistPage(id: number): Promise{ + + const res = await this.bsaverApi.getPlaylistDetails(id); + return res.data; + + } + } \ No newline at end of file diff --git a/src/shared/models/maps/beat-saver.model.ts b/src/shared/models/maps/beat-saver.model.ts index 68429c6b..61e1734f 100644 --- a/src/shared/models/maps/beat-saver.model.ts +++ b/src/shared/models/maps/beat-saver.model.ts @@ -183,4 +183,46 @@ export interface SearchParams { includeEmpty?: boolean } -export type SearchOrder = "Latest"|"Relevance"|"Rating"|"Curated"; \ No newline at end of file +export type SearchOrder = "Latest"|"Relevance"|"Rating"|"Curated"; + +export interface BsvMapDetailWithOrder { + map: BsvMapDetail, + order: number +} + +export interface BsvPlaylist { + createdAt: BsvInstant, + curatedAt: BsvInstant, + curator: BsvUserDetail, + deletedAt: BsvInstant, + description: string, + downloadURL: string, + name: string, + owner: BsvUserDetail, + playlistId: number, + playlistImage: string, + playlistImage512: string, + public: boolean, + songsChangedAt: BsvInstant, + stats: BsvPlaylistStats, + updatedAt: BsvInstant, +} + +export interface BsvPlaylistStats { + avgScore: number, + downVotes: number, + mapperCount: number, + maxNps: number, + maxNpsTwoDP: number, + minNps: number, + minNpsTwoDP: number, + scoreOneDP: number, + totalDuration: number, + totalMaps: number, + upVotes: number +} + +export interface BsvPlaylistPage { + maps: BsvMapDetailWithOrder[], + playlist: BsvPlaylist +}