UNPKG

enhanced-spotify-api

Version:

Object-oriented library to work with Spotify's API. Includes wrapper for regular endpoints and additional functionality and grouping of requests.

668 lines (582 loc) 19.7 kB
// Associated Models const Models = require('../../index'); /** * Creates a new Tracks container instance * @param {Array | Track | object | string} [data] (optional) Data to be preloaded, * Single or multiple tracks */ function Tracks(items) { this.name = 'Tracks'; this.type = 'Track'; this.uri_type = 'track'; Models.Container.call(this, items); } Tracks.prototype = { ...Models.Container.prototype, /** * Plays tracks on user's active device * * @param {object} [options] (Optional) Additional options * @param {object} [options.offset] Where from the tracks to play * @param {number} [options.offset.position=0] Index of item to start with in context * @param {string} [options.offset.uri] URI of item to start with in context * @returns {object} Response from request */ async play(options) { const _options = options || {}; _options.uris = []; for (let i = 0; i < this.order.length && i < 25; i += 1) { _options.uris.push(`spotify:track:${this.order[i]}`); } return Models.wrapperInstance.play(_options); }, /** * Returns array of booleans whether tracks are saved to the user's library * * @returns {Array} Array of booleans Whether track are saved to the user's library */ async areLiked() { const response = await Models.wrapperInstance.containsMySavedTracks(this.order); return response.body; }, /** * Adds tracks to the user's library * * @returns {object} Response from request */ async likeAll() { return Models.wrapperInstance.addToMySavedTracks(Object.keys(this.items)); }, /** * Removes tracks from the user's library * * @returns {object} Response from request. */ async unlikeAll() { return Models.wrapperInstance.removeFromMySavedTracks(Object.keys(this.items)); }, /** * Returns full track data for all tracks, * Retrieves from Spotify API if necessary * * @returns {Array} Array of track full objects */ async getFullObjects() { await this.retrieveFullObjects('full'); const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getFullObject()); } return result; }, /** * Returns simplified track data for all tracks, * Retrieves from Spotify API if necessary * * @returns {Array} Array of track simplified objects */ async getSimplifiedObjects() { await this.retrieveFullObjects('simplified'); const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getSimplifiedObject()); } return result; }, /** * Returns track link data for all tracks, * Retrieves from Spotify API if necessary * * @returns {Array} Array of track link objects */ async getLinkObjects() { await this.retrieveFullObjects('link'); const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getLinkObject()); } return result; }, /** * Returns audio feature data for all tracks, * Retrieves from Spotify API if necessary * * @returns {Array} Array of track audio features */ async getAudioFeatures() { await this.retrieveAudioFeatures(); const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getAudioFeatures()); } return result; }, /** * Returns audio analysis data for all tracks, * Retrieves from Spotify API if necessary * * @returns {Array} Array of track audio analysis data */ async getAudioAnalyses() { const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getAudioAnalysis()); } return result; }, /** * Returns all data, * Retrieves from Spotify API if necessary * * @returns {Array} Array of all track's data */ async getAllData() { await this.retrieveFullObjects('full'); await this.retrieveAudioFeatures(); await this.retrieveAudioAnalyses(); const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getAllData()); } return result; }, /** * Just returns whatever the track objects currently hold * * @returns {Array} Array of current track data */ async getCurrentData() { const result = []; for (let i = 0; i < this.order.length; i += 1) { await result.push(await this.items[this.order[i]].getCurrentData()); } return result; }, /** * Returns Artists instance with all track's artists * * @returns {Artists} Artists object of all track's artists */ async getArtists() { await this.retrieveFullObjects('simplified'); const artists = new Models.Artists(); const trackIDs = Object.keys(this.items); for (let i = 0; i < trackIDs.length; i += 1) { await artists.concat(await this.items[trackIDs[i]].getArtists()); } return artists; }, /** * Returns Albums instance with all track's albums * * @returns {Albums} Albums object of all track's albums */ async getAlbums() { await this.retrieveFullObjects('simplified'); const albums = new Models.Albums(); const trackIDs = Object.keys(this.items); for (let i = 0; i < trackIDs.length; i += 1) { await albums.push(await this.items[trackIDs[i]].getAlbum()); } return albums; }, /** * Returns averages for each audio feature * * @returns {object} With audio feature properties */ async getAudioFeatureAverages() { await this.retrieveAudioFeatures(); const addAudioFeatures = async (total, curr) => { const data = await curr.getAudioFeatures(); return { duration_ms: total.duration_ms + data.duration_ms, key: total.key + data.key, mode: total.mode + data.mode, time_signature: total.time_signature + data.time_signature, acousticness: total.acousticness + data.acousticness, danceability: total.danceability + data.danceability, energy: total.energy + data.energy, instrumentalness: total.instrumentalness + data.instrumentalness, liveness: total.liveness + data.liveness, loudness: total.loudness + data.loudness, speechiness: total.speechiness + data.speechiness, valence: total.valence + data.valence, tempo: total.tempo + data.tempo, }; }; const averages = await Object.values(this.items).reduce(addAudioFeatures, { duration_ms: 0, key: 0, mode: 0, time_signature: 0, acousticness: 0, danceability: 0, energy: 0, instrumentalness: 0, liveness: 0, loudness: 0, speechiness: 0, valence: 0, tempo: 0, }); const size = Object.keys(this.items).length; const properties = Object.keys(averages); for (let i = 0; i < properties.length; i += 1) { averages[properties[i]] /= size; } return averages; }, /** * Returns distributions for each audio feature * * @param {number} size Size of distributions * @returns {object} With audio feature properties */ async getAudioFeatureDistributions(size) { await this.retrieveAudioFeatures(); const properties = [ 'acousticness', 'danceability', 'energy', 'instrumentalness', 'liveness', 'loudness', 'speechiness', 'valence', 'tempo', ]; const distributeAudioFeatures = async (total, curr) => { const data = await curr.getAudioFeatures(); for (let i = 0; i < properties.length; i += 1) { const divisor = (properties[i] === 'tempo') ? 1 : 250; // eslint-disable-next-line no-param-reassign total[properties[i]][Math.round((data[properties[i]] / divisor) * size - 1)] += 1; } return total; }; const emptyDistribution = []; for (let i = 0; i < size.length; i += 1) { emptyDistribution.push(0); } const distributions = await Object.values(this.items).reduce(distributeAudioFeatures, { acousticness: emptyDistribution, danceability: emptyDistribution, energy: emptyDistribution, instrumentalness: emptyDistribution, liveness: emptyDistribution, loudness: emptyDistribution, speechiness: emptyDistribution, valence: emptyDistribution, tempo: emptyDistribution, }); return distributions; }, /** * Retrieves suggests for a random 5 of these tracks * * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks object with recommendations */ async getRecommendations(options) { if (options != null && typeof (options) !== 'object') { throw new Error('Tracks.search: Invalid Parameter "options"'); } const ids = Object.keys(this.items); const seeds = []; for (let i = 0; i < 5; i += 1) { if (!ids.length) { break; } const random = Math.round(Math.random() * (ids.length - 1)); seeds.push(ids.slice(random, random + 1)); } const _options = options || {}; if ('seed_artists' in _options) { delete _options.seed_artists; } if ('seed_genres' in _options) { delete _options.seed_artists; } _options.seed_tracks = seeds.join(','); const response = await Models.wrapperInstance.getRecommendations(_options); return new Models.Tracks(response.body.tracks); }, /** * Retrieves full track data for all tracks from Spotify API * * @param {string} objectType What to check if the track contains, * ('simplified', 'link', 'full') */ async retrieveFullObjects(objectType) { const ids = []; const trackIDs = Object.keys(this.items); for (let i = 0; i < trackIDs.length; i += 1) { if (objectType === 'simplified') { if (!(await this.items[trackIDs[i]].containsSimplifiedObject())) { ids.push(trackIDs[i]); } } else if (objectType === 'link') { if (!(await this.items[trackIDs[i]].containsLinkObject())) { ids.push(trackIDs[i]); } } else if (!(await this.items[trackIDs[i]].containsFullObject())) { ids.push(trackIDs[i]); } } if (ids.length) { let response; do { response = await Models.wrapperInstance.getTracks(ids.splice(0, 50)); for (let i = 0; i < response.body.tracks.length; i += 1) { if (response.body.tracks[i] !== null) { this.items[response.body.tracks[i].id].loadFullObject(response.body.tracks[i]); } } } while (ids.length > 0); } }, /** * Retrieves audio feature data for all tracks from Spotify API */ async retrieveAudioFeatures() { const ids = []; const trackIDs = Object.keys(this.items); for (let i = 0; i < trackIDs.length; i += 1) { if (!(await this.items[trackIDs[i]].containsAudioFeatures())) { ids.push(trackIDs[i]); } } if (ids.length) { let response; do { response = await Models.wrapperInstance.getAudioFeaturesForTracks(ids.splice(0, 100)); for (let i = 0; i < response.body.audio_features.length; i += 1) { if (response.body.audio_features[i] !== null) { const track = this.items[response.body.audio_features[i].id]; track.loadAudioFeatures(response.body.audio_features[i]); } } } while (ids.length > 0); } }, /** * Retrieves audio analysis data for all tracks from Spotify API */ async retrieveAudioAnalyses() { const trackIDs = Object.keys(this.items); let response; for (let i = 0; i < trackIDs.length; i += 1) { if (!(await this.items[trackIDs[i]].containsAudioAnalysis())) { response = await Models.wrapperInstance.getAudioAnalysisForTrack(trackIDs[i]); this.items[trackIDs[i]].loadAudioAnalysis(response.body); } } }, }; /** * Returns search results for a query * * @param {string} query String to search for * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks returned from search */ Tracks.search = async function search(query, options) { if (options != null && typeof (options) !== 'object') { throw new Error('Tracks.search: Invalid Parameter "options"'); } const response = await Models.wrapperInstance.searchTracks(query, options || {}); return new Models.Tracks(response.body.tracks.items); }; /** * Returns search results for a query based on seeds * * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks returned from Search */ Tracks.getRecommendations = async function getRecommendations(options) { if (options === null || typeof (options) !== 'object') { throw new Error('Tracks.getRecommendations: Invalid Parameter "options"'); } const response = await Models.wrapperInstance.getRecommendations(options || {}); return new Models.Tracks(response.body.tracks); }; /** * Returns users top played tracks * * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks returned request */ Tracks.getMyTopTracks = async function getMyTopTracks(options) { if (options != null && typeof (options) !== 'object') { throw new Error('Tracks.getMyTopTracks: Invalid Parameter "options"'); } const response = await Models.wrapperInstance.getMyTopTracks(options || {}); return new Models.Tracks(response.body.items); }; /** * Returns users recently played tracks * * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks returned request */ Tracks.getMyRecentlyPlayedTracks = async function getMyRecentlyPlayedTracks(options) { if (options != null && typeof (options) !== 'object') { throw new Error('Tracks.getMyRecentlyPlayedTracks: Invalid Parameter "options"'); } const response = await Models.wrapperInstance.getMyRecentlyPlayedTracks(options || {}); return new Models.Tracks(response.body.items); }; /** * Returns saved tracks * * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks returned request */ Tracks.getMySavedTracks = async function getMySavedTracks(options) { if (options != null && typeof (options) !== 'object') { throw new Error('Tracks.getMySavedTracks: Invalid Parameter "options"'); } const response = await Models.wrapperInstance.getMySavedTracks(options || {}); return new Models.Tracks(response.body.items); }; /** * Returns all saved tracks * * @param {object} [options] (Optional) Additional options * @returns {Tracks} Tracks returned request */ Tracks.getAllMySavedTracks = async function getAllMySavedTracks() { const _options = { limit: 50, offset: 0, }; const tracks = new Models.Tracks(); let response; do { response = await Models.wrapperInstance.getMySavedTracks(_options); await tracks.concat(response.body.items); _options.offset += 50; } while (!(response.items.length < 50)); return tracks; }; /** * Returns tracks from playlist * * @param {string} id ID for playlist * @returns {Tracks} Tracks from playlist */ Tracks.getPlaylistTracks = function getPlaylistTracks(id) { const playlist = new Models.Playlist(id); return playlist.getTracks(); }; /** * Returns tracks from album * * @param {string} id ID for album * @returns {Tracks} Tracks from album */ Tracks.getAlbumTracks = function getAlbumTracks(id) { const album = new Models.Album(id); return album.getTracks(); }; /** * Returns Tracks object of IDs * * @param {Array} trackIds Ids of tracks * @returns {Tracks} Tracks from ids */ Tracks.getTracks = async function getTracks(trackIds) { const tracks = new Models.Tracks(trackIds); await tracks.retrieveFullObjects(); return tracks; }; /** * Returns Artist's top tracks * * @param {string} id ID for artist * @returns {Tracks} Tracks from Artist top tracks */ Tracks.getArtistTopTracks = function (id) { const artist = new Models.Artist(id); return artist.getTopTracks(); }; /** * Adds functionality to Class * * @param {object} methods Object containing new methods to be added as properties */ Tracks.addMethods = function addMethods(methods) { const methodNames = Object.keys(methods); for (let i = 0; i < methodNames.length; i += 1) { this.prototype[methodNames[i]] = methods[methodNames[i]]; } }; /** * Replaces a method within the Class * * @param {string} name Name of the method to replace * @param {function} method Function to replace with */ Tracks.override = function override(name, method) { if (name in this.prototype) { this.prototype[name] = method; } else { throw new Error('Tracks.override: \'name\' does not exist.'); } }; Tracks.setCredentials = function setCredentials(credentials) { Models.wrapperInstance.setCredentials(credentials); }; Tracks.getCredentials = function getCredentials() { return Models.wrapperInstance.getCredentials(); }; Tracks.resetCredentials = function resetCredentials() { Models.wrapperInstance.resetCredentials(); }; Tracks.setClientId = function setClientId(clientId) { Models.wrapperInstance.setClientId(clientId); }; Tracks.setClientSecret = function setClientSecret(clientSecret) { Models.wrapperInstance.setClientSecret(clientSecret); }; Tracks.setAccessToken = function setAccessToken(accessToken) { Models.wrapperInstance.setAccessToken(accessToken); }; Tracks.setRefreshToken = function setRefreshToken(refreshToken) { Models.wrapperInstance.setRefreshToken(refreshToken); }; Tracks.setRedirectURI = function setRedirectURI(redirectUri) { Models.wrapperInstance.setRedirectURI(redirectUri); }; Tracks.getRedirectURI = function getRedirectURI() { return Models.wrapperInstance.getRedirectURI(); }; Tracks.getClientId = function getClientId() { return Models.wrapperInstance.getClientId(); }; Tracks.getClientSecret = function getClientSecret() { return Models.wrapperInstance.getClientSecret(); }; Tracks.getAccessToken = function getAccessToken() { return Models.wrapperInstance.getAccessToken(); }; Tracks.getRefreshToken = function getRefreshToken() { return Models.wrapperInstance.getRefreshToken(); }; Tracks.resetClientId = function resetClientId() { return Models.wrapperInstance.resetClientId(); }; Tracks.resetClientSecret = function resetClientSecret() { return Models.wrapperInstance.resetClientSecret(); }; Tracks.resetAccessToken = function resetAccessToken() { return Models.wrapperInstance.resetAccessToken(); }; Tracks.resetRefreshToken = function resetRefreshToken() { return Models.wrapperInstance.resetRefreshToken(); }; Tracks.resetRedirectURI = function resetRedirectURI() { return Models.wrapperInstance.resetRedirectURI(); }; module.exports = Tracks;