UNPKG

cody-music

Version:

mac osx spotify and itunes music player controller, spotify audio features, itunes and spotify genre, and playlist control

566 lines 22.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PlaylistService = void 0; const client_1 = require("./client"); const models_1 = require("./models"); const store_1 = require("./store"); const profile_1 = require("./profile"); const util_1 = require("./util"); const musicClient = client_1.MusicClient.getInstance(); const musicStore = store_1.MusicStore.getInstance(); const userProfile = profile_1.UserProfile.getInstance(); const musicUtil = new util_1.MusicUtil(); class PlaylistService { constructor() { // } static getInstance() { if (!PlaylistService.instance) { PlaylistService.instance = new PlaylistService(); } return PlaylistService.instance; } async removeFromSpotifyLiked(trackIds) { trackIds = musicUtil.createTrackIdsFromUris(trackIds); const api = `/v1/me/tracks`; /** * ["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"] */ const qsOptions = { ids: trackIds.join(",") }; let codyResp = await musicClient.spotifyApiDelete(api, qsOptions); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiDelete(api, qsOptions); } return codyResp; } async saveToSpotifyLiked(trackIds) { trackIds = musicUtil.createTrackIdsFromUris(trackIds); const api = `/v1/me/tracks`; /** * {ids:["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"]} */ const qsOptions = {}; const payload = { ids: trackIds, }; let codyResp = await musicClient.spotifyApiPut(api, qsOptions, payload); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiPut(api, qsOptions, payload); } return codyResp; } async getSavedTracks(qsOptions = {}) { let tracks = []; const totalTracksToFetch = !qsOptions.limit || qsOptions.limit === -1 ? -1 : qsOptions.limit; if (!qsOptions.limit) { qsOptions["limit"] = 50; } else if (qsOptions.limit < 1) { qsOptions.limit = 1; } if (!qsOptions.offset) { qsOptions["offset"] = 0; } const api = `/v1/me/tracks`; let codyResp = await musicClient.spotifyApiGet(api, qsOptions); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiGet(api, qsOptions); } if (musicUtil.isResponseOkWithData(codyResp) && codyResp.data.items) { while (true) { let trackContainers = codyResp.data.items; // ensure the playerType is set let fetchedLimit = false; for (let x = 0; x < trackContainers.length; x++) { const item = trackContainers[x]; if (item.track) { const track = musicUtil.buildTrack(item.track); tracks.push(track); } if (totalTracksToFetch > 0 && tracks.length >= totalTracksToFetch) { fetchedLimit = true; break; } } if (fetchedLimit) { break; } if (codyResp.data.next) { // fetch the next set (remove the root) let nextApi = codyResp.data.next.substring(client_1.SPOTIFY_ROOT_API.length); codyResp = await musicClient.spotifyApiGet(nextApi, {}); } else { break; } } } return tracks; } async getPlaylists(qsOptions = {}) { let playlists = []; if (!musicStore.spotifyUserId) { await userProfile.getUserProfile(); } if (musicStore.spotifyUserId) { const spotifyUserId = musicStore.spotifyUserId; const fetchAll = qsOptions.all ? true : false; let limit = qsOptions.limit ? qsOptions.limit : 50; limit = limit < 1 ? 1 : limit; let offset = qsOptions.offset ? qsOptions.offset : 0; let codyResp = await this.getPlaylistsForUser(spotifyUserId, limit, offset); if (musicUtil.isItemsResponseOk(codyResp)) { let playlistItems = codyResp.data.items; // ensure the playerType is set playlistItems.forEach((playlist) => { playlist.playerType = models_1.PlayerType.WebSpotify; playlist.type = "playlist"; playlists.push(playlist); }); // check if we need to fetch every playlist if (fetchAll) { let threshold = codyResp.data.limit + codyResp.data.offset; let total = codyResp.data.total; while (total > threshold) { // update the next offset and fetch the next set offset = threshold; codyResp = await this.getPlaylistsForUser(musicStore.spotifyUserId, limit, offset); if (musicUtil.isItemsResponseOk(codyResp)) { playlistItems = codyResp.data.items; // ensure the playerType is set playlistItems.forEach((playlist) => { playlist.playerType = models_1.PlayerType.WebSpotify; playlist.type = "playlist"; playlists.push(playlist); }); } threshold = codyResp.data.limit + codyResp.data.offset; total = codyResp.data.total; } } } } return playlists; } async getPlaylistsForUser(spotifyUserId, limit, offset) { limit = limit || 50; offset = offset || 0; const qsOptions = { limit, offset, }; const api = `/v1/users/${spotifyUserId}/playlists`; let codyResp = await musicClient.spotifyApiGet(api, qsOptions); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiGet(api, qsOptions); } return codyResp; } async getSpotifyPlaylist(playlist_id) { let playlistItem = new models_1.PlaylistItem(); // make sure the ID is not the URI playlist_id = musicUtil.createSpotifyIdFromUri(playlist_id); const api = `/v1/playlists/${playlist_id}`; let codyResp = await musicClient.spotifyApiGet(api, {}); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiGet(api, {}); } if (musicUtil.isResponseOk(codyResp)) { playlistItem = { ...codyResp.data, }; } return playlistItem; } async getPlaylistTracks(playlist_id, qsOptions = {}) { if (!qsOptions.limit) { // maximum is 100 at a time qsOptions["limit"] = 100; } else if (qsOptions.limit < 1) { qsOptions.limit = 1; } if (!qsOptions.offset) { qsOptions["offset"] = 0; } // fields to return for the present moment // TODO: allow options to update this qsOptions["fields"] = "href,limit,next,offset,previous,total,items(track(name,id,album(id,name),artists,popularity))"; const api = `/v1/playlists/${playlist_id}/tracks`; let codyResp = await musicClient.spotifyApiGet(api, qsOptions); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiPost(api, qsOptions); } const paginationItem = new models_1.PaginationItem(); let tracks = []; while (true) { if (codyResp && codyResp.status === 200 && codyResp.data && codyResp.data.items) { let trackContainers = codyResp.data.items; // ensure the playerType is set trackContainers.forEach((item) => { if (item.track) { const track = musicUtil.buildTrack(item.track); tracks.push(track); } }); if (codyResp.data.next) { // fetch the next set (remove the root) let nextApi = codyResp.data.next.substring(client_1.SPOTIFY_ROOT_API.length); codyResp = await musicClient.spotifyApiGet(nextApi, {}); } else { break; } } else { break; } } delete codyResp.data; paginationItem.items = tracks; paginationItem.total = tracks.length; codyResp["data"] = paginationItem; return codyResp; } async getPlaylistNames(qsOptions = {}) { let names = []; let playlistNames = await this.getPlaylists(qsOptions); if (playlistNames) { names = playlistNames.map((playlistItem) => { return playlistItem.name; }); } return names; } /** * Create a new playlist * @param name * @param isPublic */ async createPlaylist(name, isPublic, description = "") { // get the profile if we don't have it if (!musicStore.spotifyUserId) { await userProfile.getUserProfile(); } const spotifyUserId = musicStore.spotifyUserId; let playlists = await this.getPlaylists(); // check if it's already in the playlist const existingPlaylist = playlists.length ? playlists.filter((n) => n.name === name) : []; if (existingPlaylist.length > 0) { // already exists, return it const failedCreate = new models_1.CodyResponse(); failedCreate.status = 500; failedCreate.state = models_1.CodyResponseType.Failed; failedCreate.message = `The playlist '${name}' already exists`; return failedCreate; } if (spotifyUserId) { /** * --data "{\"name\":\"A New Playlist\", \"public\":false} */ const payload = { name, public: isPublic, description, }; const api = `/v1/users/${spotifyUserId}/playlists`; const resp = await musicClient.spotifyApiPost(api, {}, JSON.stringify(payload)); if (resp && resp.state === models_1.CodyResponseType.Success) { // fetch this playlist to add it to "playlists" const playlistId = resp.data.id; const createdPlaylistItem = await this.getSpotifyPlaylist(playlistId); if (createdPlaylistItem) { playlists.push(createdPlaylistItem); } } return resp; } const failedCreate = new models_1.CodyResponse(); failedCreate.status = 500; failedCreate.state = models_1.CodyResponseType.Failed; failedCreate.message = "Unable to fetch the user ID"; return failedCreate; } async deletePlaylist(playlist_id) { playlist_id = musicUtil.createSpotifyIdFromUri(playlist_id); const api = `/v1/playlists/${playlist_id}/followers`; let codyResp = await musicClient.spotifyApiDelete(api, {}, {}); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiDelete(api, {}, {}); } return codyResp; } /** * type: Valid types are: album , artist, playlist, and track * q: can have a filter and keywords, or just keywords. You * can have a wildcard as well. The query will search against * the name and description if a specific filter isn't specified. * examples: * 1) search for a track by name "what a time to be alive" * query string: ?q=name:what%20a%20time&type=track * result: this should return tracks matching the track name * 2) search for a track using a wildcard in the name * query string: ?q=name:what*&type=track&limit=50 * result: will return all tracks with "what" in the name * 3) search for an artist in name or description * query string: ?tania%20bowra&type=artist * result: will return all artists where tania bowra is in * the name or description * limit: max of 50 * @param type * @param q */ async search(type, q, limit = 50) { limit = limit < 1 ? 1 : limit > 50 ? 50 : limit; q = q.trim(); let qryObj = { type, q, limit, }; // concat the key/value filterObjects const api = `/v1/search`; let codyResp = await musicClient.spotifyApiGet(api, qryObj); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiGet(api, qryObj); } let hasData = codyResp && codyResp.data && codyResp.data.tracks && codyResp.data.tracks.items && codyResp.data.tracks.items.length > 0 ? true : false; // empty result example (and the basic result structure) /** * {"status":200,"state":"success","statusText":"OK","message":"", * "data":{ * "tracks":{ * "href":"https://api.spotify.com/v1/search?query=track%3AEl+Perd%C3%B3n+artist%3ANicky+Jam+%26+Enrique+Iglesias&type=track&market=US&offset=0&limit=1", * "items":[], * "limit":1, * "next":null, * "offset":0, * "previous":null, * "total":0 * } * }, * "error":{} * } */ // If the search doesn't return anything for a track and the search // included "track:" and "artist:", try again with just the "track:" if (type === "track" && !hasData) { // create a new query with just the track if (q.includes("track:") && q.includes("artist:")) { const trackIdx = q.indexOf("track:"); const artistIdx = q.indexOf("artist:"); if (artistIdx > trackIdx) { // grab everything up until the artistIdx q = q.substring(0, artistIdx); } else { // grab everything start from the trackIdx q = q.substring(trackIdx); } q = q.trim(); qryObj = { type, q, limit, }; codyResp = await musicClient.spotifyApiGet(api, qryObj); } } hasData = codyResp?.data?.tracks?.items?.length; let emptyResult = {}; if (!hasData) { if (type === "track") { emptyResult["tracks"] = { items: [] }; } else if (type === "album") { emptyResult["albums"] = { items: [] }; } else if (type === "artist") { emptyResult["artists"] = { items: [] }; } else { emptyResult["playlists"] = { items: [] }; } } const searchResult = hasData ? codyResp.data : emptyResult; return searchResult; } /** * Add tracks to a given playlist * @param playlist_id * @param track_ids * @param position */ async addTracksToPlaylist(playlist_id, track_ids, position = 0) { let codyResp = new models_1.CodyResponse(); if (!track_ids) { codyResp.status = 500; codyResp.state = models_1.CodyResponseType.Failed; codyResp.message = "No track URIs provided to add to playlist"; return codyResp; } const tracks = musicUtil.createUrisFromTrackIds(track_ids); let payload = { uris: tracks, position, }; const api = `/v1/playlists/${playlist_id}/tracks`; codyResp = await musicClient.spotifyApiPost(api, {}, payload); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiPost(api, {}, payload); } return codyResp; } /** * Replace tracks of a given playlist. This will wipe out * the current set of tracks. * @param playlist_id * @param track_ids */ async replacePlaylistTracks(playlist_id, track_ids) { let codyResp = new models_1.CodyResponse(); if (!track_ids) { codyResp.status = 500; codyResp.state = models_1.CodyResponseType.Failed; codyResp.message = "No track URIs provided to remove from playlist"; return codyResp; } const tracks = musicUtil.createUrisFromTrackIds(track_ids); let payload = { uris: tracks, }; const api = `/v1/playlists/${playlist_id}/tracks`; codyResp = await musicClient.spotifyApiPut(api, {}, payload); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiPut(api, {}, payload); } return codyResp; } /** * Track IDs should be the uri (i.e. "spotify:track:4iV5W9uYEdYUVa79Axb7Rh") * but if it's only the id (i.e. "4iV5W9uYEdYUVa79Axb7Rh") this will add * the uri part "spotify:track:" * @param playlist_id * @param trackIds */ async removeTracksFromPlaylist(playlist_id, track_ids) { playlist_id = musicUtil.createSpotifyIdFromUri(playlist_id); let codyResp = new models_1.CodyResponse(); if (!track_ids) { codyResp.status = 500; codyResp.state = models_1.CodyResponseType.Failed; codyResp.message = "No track URIs provided to remove from playlist"; return codyResp; } // returns list of URIs let payload = {}; payload["tracks"] = musicUtil.createUrisFromTrackIds(track_ids, true /*addUriObj*/); codyResp = await musicClient.spotifyApiDelete(`/v1/playlists/${playlist_id}/tracks`, {}, payload); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiDelete(`/v1/playlists/${playlist_id}/tracks`, {}, payload); } return codyResp; } async getTopSpotifyTracks() { let tracks = []; const api = `/v1/me/top/tracks`; // add to the api to prevent the querystring from escaping the comma const qsOptions = { time_range: "medium_term", limit: 50, }; let response = await musicClient.spotifyApiGet(api, qsOptions); // check if the token needs to be refreshed if (response.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again response = await musicClient.spotifyApiGet(api, qsOptions); } if (musicUtil.isResponseOk(response)) { tracks = response.data.items; } if (tracks && tracks.length > 0) { tracks = tracks.map((track) => { return musicUtil.copySpotifyTrackToCodyTrack(track); }); } return tracks; } /** * follow a playlist * @param playlist_id */ async followPlaylist(playlist_id) { playlist_id = musicUtil.createSpotifyIdFromUri(playlist_id); const api = `/v1/playlists/${playlist_id}/followers`; let codyResp = await musicClient.spotifyApiPut(api, {}, {}); // check if the token needs to be refreshed if (codyResp.status === 401) { // refresh the token await musicClient.refreshSpotifyToken(); // try again codyResp = await musicClient.spotifyApiPut(api, {}, {}); } return codyResp; } } exports.PlaylistService = PlaylistService; //# sourceMappingURL=playlist.service.js.map