import { Injectable } from '@angular/core' ; import { chunk } from 'lodash-es' ; import SpotifyWebApi from 'spotify-web-api-js' ; import { TokenService } from '../spotify-auth' ; import { StateService } from './state.service' ; const spotifyApi = new SpotifyWebApi ( ) ; const MAX_LIMIT = 50 ; export interface ITrackWFeatures extends SpotifyApi . PlaylistTrackObject , SpotifyApi . AudioFeaturesObject { } function mergeTrackInfo ( tracks , trackFeatures ) { if ( tracks ) { return tracks . items . map ( ( track , index ) => ( { ... track , ... trackFeatures . audio_features [ index ] , } ) ) ; } else { return undefined ; } } @ Injectable ( { providedIn : 'root' , } ) export class SpotifyWebApiService { constructor ( private tokenSvc , private _stateService ) { spotifyApi . setAccessToken ( this . tokenSvc . oAuthToken ) ; this . _stateService . userProfile$ . subscribe ( ( userProfile ) => { this . userProfile = userProfile ; } ) ; } private userProfile : SpotifyApi . CurrentUsersProfileResponse ; async getPlaylists ( ) { const user = this . userProfile ; const playlistsResponse = await spotifyApi . getUserPlaylists ( user . id , { limit : MAX_LIMIT , } ) ; const remaining = playlistsResponse . total - MAX_LIMIT ; const numNecessaryCalls = Math . ceil ( remaining / MAX_LIMIT ) ; const necessaryCalls = Array . from ( Array ( numNecessaryCalls ) . keys ( ) ) . map ( ( index ) => { return spotifyApi . getUserPlaylists ( user . id , { offset : ( index + 1 ) * MAX_LIMIT , limit : MAX_LIMIT } ) ; } ) ; const remainingPlaylistsResponses = await Promise . all ( necessaryCalls ) ; return remainingPlaylistsResponses . reduce ( ( acc , curr ) => { acc . items = [ ... ( acc . items || [ ] ) , ... curr . items ] ; return acc ; } , playlistsResponse ) ; } async getPlaylist ( playlistId ) { return await spotifyApi . getPlaylist ( playlistId ) ; } async getTrack ( trackId ) { return await spotifyApi . getTrack ( trackId ) ; } async deletePlaylist ( playlistId ) { return await spotifyApi . unfollowPlaylist ( playlistId ) ; } async getPlaylistTracks ( playlistId ) { const tracksResponse = await spotifyApi . getPlaylistTracks ( playlistId , { limit : MAX_LIMIT , } ) ; const remaining = tracksResponse . total - MAX_LIMIT ; const numNecessaryCalls = Math . max ( 0 , Math . ceil ( remaining / MAX_LIMIT ) ) ; const necessaryCalls = Array . from ( Array ( numNecessaryCalls ) . keys ( ) ) . map ( ( index ) => { return spotifyApi . getPlaylistTracks ( playlistId , { offset : ( index + 1 ) * MAX_LIMIT , limit : MAX_LIMIT } ) ; } ) ; const remainingTracksResponses = await Promise . all ( necessaryCalls ) ; return remainingTracksResponses . reduce ( ( acc , curr ) => { acc . items = [ ... ( acc . items || [ ] ) , ... curr . items ] ; return acc ; } , tracksResponse ) ; } async updatePlaylist ( playlistId , trackIds ) { await spotifyApi . replaceTracksInPlaylist ( playlistId , [ ] ) ; const ADD_TRACKS_MAX = 100 ; const trackIdsChunked = chunk ( trackIds , ADD_TRACKS_MAX ) ; for ( const tracksChunk of trackIdsChunked ) { await spotifyApi . addTracksToPlaylist ( playlistId , tracksChunk ) ; } } async updatePlaylistDetails ( playlistId , details ) { return await spotifyApi . changePlaylistDetails ( playlistId , details ) ; } async createPlaylist ( playlistName , trackIds ) { const user = this . userProfile ; const playlist = await spotifyApi . createPlaylist ( user . id , { name : playlistName , public : false , } ) ; await this . updatePlaylist ( playlist . id , trackIds ) ; return playlist ; } async getTop ( ) { return await Promise . all ( [ spotifyApi . getMyTopArtists ( { time_range : 'long_term' } ) , spotifyApi . getMyTopTracks ( { time_range : 'long_term' } ) , ] ) ; } async getTopSongsPlaylists ( ) { const topSongsPlaylists = await spotifyApi . getCategoryPlaylists ( '2019' ) ; const playlistPromises = topSongsPlaylists . playlists . items . filter ( ( playlist ) => playlist . name . startsWith ( 'Your Top Songs' ) ) . map ( ( playlist ) => this . getPlaylistTracks ( playlist . id ) ) ; return await Promise . all ( playlistPromises ) ; } async getFeaturesOfTracks ( trackIds ) { const GET_AUDIO_FEATURES_MAX = 100 ; const artistResponsesChunkedUp = chunk ( trackIds , GET_AUDIO_FEATURES_MAX ) . map ( ( artistChunk ) => spotifyApi . getAudioFeaturesForTracks ( artistChunk ) ) ; const allPromises = await Promise . all ( artistResponsesChunkedUp ) ; return allPromises . reduce ( ( acc , curr ) => { acc . audio_features = [ ... ( acc . audio_features || [ ] ) , ... curr . audio_features ] ; return acc ; } , { audio_features : [ ] } ) ; } async getPlaylistTracksWithFeatures ( playlistId ) { const tracks = await this . getPlaylistTracks ( playlistId ) ; tracks . items = tracks . items . filter ( ( track ) => track . track ) ; const trackFeatures = await this . getFeaturesOfTracks ( tracks . items . map ( ( { track } ) => track . id ) ) ; return mergeTrackInfo ( tracks , trackFeatures ) ; } }