diff --git a/deemix/deemix/plugins/spotify.js b/deemix/deemix/plugins/spotify.js index e3617a4..86e53e8 100644 --- a/deemix/deemix/plugins/spotify.js +++ b/deemix/deemix/plugins/spotify.js @@ -10,7 +10,7 @@ const { const { Convertable, Collection } = require('../types/DownloadObjects.js') const { sep } = require('path') const fs = require('fs') -const SpotifyWebApi = require('spotify-web-api-node-plus') +const SpotifyWebApi = require('spotify-web-api-node') const got = require('got') const { queue } = require('async') diff --git a/deemix/package.json b/deemix/package.json index 1dfbfdb..515e6d8 100644 --- a/deemix/package.json +++ b/deemix/package.json @@ -20,7 +20,7 @@ "got": "^11.8.2", "html-entities": "^2.3.3", "metaflac-js2": "^1.0.8", - "spotify-web-api-node-plus": "../spotify-web-api-node-plus", + "spotify-web-api-node": "../spotify-web-api-node", "tough-cookie": "^4.0.0" }, "devDependencies": { diff --git a/latest.txt b/latest.txt index 5c8dc42..4ead027 100644 --- a/latest.txt +++ b/latest.txt @@ -1 +1 @@ -2024.7.26-r256.c452e2e47d \ No newline at end of file +2024.7.31-r258.711fcec286 \ No newline at end of file diff --git a/spotify-web-api-node-plus/src/http-manager.js b/spotify-web-api-node-plus/src/http-manager.js deleted file mode 100644 index a5c983f..0000000 --- a/spotify-web-api-node-plus/src/http-manager.js +++ /dev/null @@ -1,175 +0,0 @@ -'use strict' - -const { - TimeoutError, - WebapiError, - WebapiRegularError, - WebapiAuthenticationError, - WebapiPlayerError -} = require('./response-error') - -const HttpManager = {} - -/* Create superagent options from the base request */ -const _getParametersFromRequest = function (request) { - const options = {} - - if (request.getQueryParameters()) { - options.query = new URLSearchParams( - request.getQueryParameters() - ).toString() - } - - if ( - request.getHeaders() && - request.getHeaders()['Content-Type'] === 'application/json' - ) { - options.data = JSON.stringify(request.getBodyParameters()) - } else if (request.getBodyParameters()) { - options.data = request.getBodyParameters() - } - - if (request.getHeaders()) { - options.headers = request.getHeaders() - } - - if (request.getTimeout()) { - options.timeout = request.getTimeout() - } - - return options -} - -/** - * @param {Response} response - */ -const _toError = async function (response) { - const body = await response.json() - - if ( - typeof body === 'object' && - typeof body.error === 'object' && - typeof body.error.reason === 'string' - ) { - return new WebapiPlayerError(body, response.headers, response.status) - } - - if (typeof body === 'object' && typeof body.error === 'object') { - return new WebapiRegularError(body, response.headers, response.status) - } - - if (typeof body === 'object' && typeof body.error === 'string') { - return new WebapiAuthenticationError( - body, - response.headers, - response.status - ) - } - - /* Other type of error, or unhandled Web API error format */ - return new WebapiError(body, response.headers, response.status, body) -} - -/* Make the request to the Web API */ -HttpManager._makeRequest = function (method, options, uri, callback) { - const headers = new Headers(options.headers || {}) - let serializationMethod = JSON.stringify - - if (headers.get('Content-Type') === 'application/x-www-form-urlencoded') { - serializationMethod = d => new URLSearchParams(d) - } - - const controller = new AbortController() - let timeoutId - - if (options.timeout) { - setTimeout(() => { - controller.abort() - }, options.timeout) - } - - let body = options.data - - if (body && typeof body !== 'string') { - body = serializationMethod(body) - } - - fetch(uri + (options.query ? '?' + options.query : ''), { - method, - headers, - body, - signal: controller.signal - }) - .then(async resp => { - clearTimeout(timeoutId) - - if (!resp.ok) { - return callback(await _toError(resp)) - } - - return callback(null, { - body: await resp.json().catch(() => null), - headers: resp.headers, - statusCode: resp.status - }) - }) - .catch(err => { - if (controller.signal.aborted) { - return callback( - new TimeoutError(`request took longer than ${options.timeout}ms`) - ) - } - - return callback(err) - }) -} - -/** - * Make a HTTP GET request. - * @param {BaseRequest} The request. - * @param {Function} The callback function. - */ -HttpManager.get = function (request, callback) { - const options = _getParametersFromRequest(request) - const method = 'GET' - - HttpManager._makeRequest(method, options, request.getURI(), callback) -} - -/** - * Make a HTTP POST request. - * @param {BaseRequest} The request. - * @param {Function} The callback function. - */ -HttpManager.post = function (request, callback) { - const options = _getParametersFromRequest(request) - const method = 'POST' - - HttpManager._makeRequest(method, options, request.getURI(), callback) -} - -/** - * Make a HTTP DELETE request. - * @param {BaseRequest} The request. - * @param {Function} The callback function. - */ -HttpManager.del = function (request, callback) { - const options = _getParametersFromRequest(request) - const method = 'DELETE' - - HttpManager._makeRequest(method, options, request.getURI(), callback) -} - -/** - * Make a HTTP PUT request. - * @param {BaseRequest} The request. - * @param {Function} The callback function. - */ -HttpManager.put = function (request, callback) { - const options = _getParametersFromRequest(request) - const method = 'PUT' - - HttpManager._makeRequest(method, options, request.getURI(), callback) -} - -module.exports = HttpManager diff --git a/spotify-web-api-node/.travis.yml b/spotify-web-api-node/.travis.yml new file mode 100644 index 0000000..aca4270 --- /dev/null +++ b/spotify-web-api-node/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - 14 +after_success: + - npm run travis diff --git a/spotify-web-api-node-plus/CHANGELOG.md b/spotify-web-api-node/CHANGELOG.md similarity index 71% rename from spotify-web-api-node-plus/CHANGELOG.md rename to spotify-web-api-node/CHANGELOG.md index a39264d..0a31100 100644 --- a/spotify-web-api-node-plus/CHANGELOG.md +++ b/spotify-web-api-node/CHANGELOG.md @@ -1,62 +1,47 @@ ## Change log -#### 7.0.0 (Jan 2024) - -* Added [getQueue](https://developer.spotify.com/documentation/web-api/reference/get-queue) endpoint -* Added [getPlaylistCoverImage](https://developer.spotify.com/documentation/web-api/reference/get-playlist-cover) endpoint -* Added [addToQueue](https://developer.spotify.com/documentation/web-api/reference/add-to-queue) endpoint -* Added [getAvailableMarkets](https://developer.spotify.com/documentation/web-api/reference/get-available-markets) endpoint -* Updated Readme with new endpoints - -#### 6.0.0 - -* **BREAKING CHANGES** - * This release replaces superagent with `fetch` when making HTTP calls. This should result in no changes for consumers - of this library except that the `headers` returned in the responses are now `Headers` objects instead of plain objects. - Headers are case insensitive and must be treated as such. - #### 5.0.2 (Jan 2021) -* Fix: Make `transferMyPlayback` not require the `options` object, since it should be optional. Thanks for the heads-up [@Simber1](https://github.com/Simber1)! +* Fix: Make `transferMyPlayback` not require the `options` object, since it should be optional. Thanks for the heads-up [@Simber1](https://github.com/Simber1)! #### 5.0.1 (Jan 2021) * Fix error handling in the HTTP client. Thanks [@yamadapc](https://github.com/yamadapc)! -* This package can currently not be built on **Node 15 on Linux**, due to a dependency not being available yet. Issue can be followed on the [node-canvas](https://github.com/Automattic/node-canvas/issues/1688) issue tracker. In the mean time, Travis CI will run on earlier versions of Node. +* This package can currently not be built on **Node 15 on Linux**, due to a dependency not being available yet. Issue can be followed on the [node-canvas](https://github.com/Automattic/node-canvas/issues/1688) issue tracker. In the mean time, Travis CI will run on earlier versions of Node. #### 5.0.0 (Oct 2020) -* **BREAKING CHANGES**. +* **BREAKING CHANGES**. * Arguments for some API methods have changed, causing incorrect behaviour using argument order from version 4.x. See the `README.md` for examples of how the methods can be used. - * Create Playlist (`createPlaylist`) method no longer accepts a `userId` string as its first argument. - * Transfer A User's Playback (`transferMyPlayback`) method takes a `deviceIds` array as its first argument. + * Create Playlist (`createPlaylist`) method no longer accepts a `userId` string as its first argument. + * Transfer A User's Playback (`transferMyPlayback`) method takes a `deviceIds` array as its first argument. * Skip to Previous (`skipToPrevious`) method takes an `options` object as its first argument. * Skip to Next (`skipToNext`) method takes an `options` object as its first argument. * Set Repeat Mode on the Current User's Playback (`setRepeat`) method takes a `state` string as its first argument. * Set Shuffle Mode on the Current User's Playback (`setShuffle`) method takes a `state` string as its first argument. - + Cheers [@marinacrachi](https://github.com/marinacrachi) for the createPlaylist update. * Removed legacy support for not passing an `options` object while providing a callback method. This was only supported on a few of the older endpoints, and could lead to tricky bugs. The affected endpoints are `getTrack`, `getTracks`, `getAlbum`, `getAlbums`, and `createPlaylist`. Again, check the `README.md` for examples on how these methods can be used if needed. * Removed `options` argument for retrieving an access token using the Client Credentials flow, `clientCredentialsGrant`. * API errors come in five different flavours. * WebapiRegularError - For errors returned by most API endpoints. - * WebapiPlayerError - For errors returned by the Player API. These contain a bit more information. + * WebapiPlayerError - For errors returned by the Player API. These contain a bit more information. * WebapiAuthenticationError - For errors related to authentication. * WebapiError - For errors that come from the Web API that didn't fit into one of the above. * TimeoutError - For network timeout errors. - + More importantly, errors now contain the response body, headers, and status code. One side-effect of this is that rate limited requests can be handled by checking the `Retry-After` header. Thanks for the PRs [@kauffecup](https://github.com/kauffecup), [@lantelyes](https://github.com/lantelyes), [@dkliemsch](https://github.com/dkliemsch), and [@erezny](https://github.com/erezny). - + Much appreciated [@konstantinjdobler](https://github.com/konstantinjdobler) for updates to the Player API errors. * Added support for [Implicit Grant flow](https://developer.spotify.com/documentation/general/guides/authorization-guide/#implicit-grant-flow) - Thanks [@gaganza](https://github.com/gaganza), [@reblws](https://github.com/reblws) and [@noahp78](https://github.com/noahp78)! * Starts or Resumes the Current User's Playback (`play`) method now supports the `position_ms` option. Thanks [@alqubo](https://github.com/alqubo), [@koflin](https://github.com/koflin), [@DoctorFishy](https://github.com/DoctorFishy). Thanks [@carmilso](https://github.com/carmilso) for general improvements to the Player API methods. * Binding for [Add an Item to the User's Playback Queue](https://developer.spotify.com/documentation/web-api/reference/player/add-to-queue/) endpoint added. Thanks [@thattomperson](https://github.com/thattomperson) and [@AriciducaZagaria](https://github.com/AriciducaZagaria)! -* Binding for all [Shows and Episodes endpoints](https://developer.spotify.com/console/shows/). Thanks a _lot_ [@andyruwruw](https://github.com/andyruwruw)! +* Binding for all [Shows and Episodes endpoints](https://developer.spotify.com/console/shows/). Thanks a _lot_ [@andyruwruw](https://github.com/andyruwruw)! * Documentation updates to keep up to date with ES6, thanks [@dandv](https://github.com/dandv)! Other documentation improvements by [@terensu-desu](https://github.com/terensu-desu), and examples by [@dersimn](https://github.com/dersimn). Thanks! -* Bumped dependencies to resolve critical security issues. -* Finally, hat off to [@dersimn](https://github.com/dersimn). Thanks for collecting all of the lingering PRs and merging them into a working and up-to-date fork. You really stepped up. +* Bumped dependencies to resolve critical security issues. +* Finally, hat off to [@dersimn](https://github.com/dersimn). Thanks for collecting all of the lingering PRs and merging them into a working and up-to-date fork. You really stepped up. -Likely more changes coming before release to npm, which will happen shortly. +Likely more changes coming before release to npm, which will happen shortly. #### 4.0.0 (14 Sep 2018) @@ -91,11 +76,11 @@ Likely more changes coming before release to npm, which will happen shortly. #### 2.4.0 (2 May 2017) * Change `addTracksToPlaylist` to pass the data in the body, preventing an issue with a long URL when passing many tracks. Thanks [@dolcalmi](https://github.com/dolcalmi) for [the PR](https://github.com/thelinmichael/spotify-web-api-node/pull/117) -* Add support for fetching [recently played tracks](https://developer.spotify.com/documentation/web-api/reference/get-recently-played/). Thanks [@jeremyboles](https://github.com/jeremyboles) for [the PR](https://github.com/thelinmichael/spotify-web-api-node/pull/111). +* Add support for fetching [recently played tracks](https://developer.spotify.com/web-api/console/get-recently-played/). Thanks [@jeremyboles](https://github.com/jeremyboles) for [the PR](https://github.com/thelinmichael/spotify-web-api-node/pull/111). #### 2.3.6 (15 October 2016) -* Add language bindings for the **[Get Audio Analysis for a Track](https://developer.spotify.com/documentation/web-api/reference/get-audio-analysis/)** endpoint. +* Add language bindings for the **[Get Audio Analysis for a Track](https://developer.spotify.com/web-api/get-audio-analysis/)** endpoint. #### 2.3.5 (20 July 2016) @@ -111,7 +96,7 @@ Likely more changes coming before release to npm, which will happen shortly. #### 2.3.2 (10 July 2016) -* Add language bindings for **[Get a List of Current User's Playlists](https://developer.spotify.com/documentation/web-api/reference/get-a-list-of-current-users-playlists/)**. Thanks [@JMPerez](https://github.com/JMPerez) and [@vinialbano](https://github.com/vinialbano). +* Add language bindings for **[Get a List of Current User's Playlists](https://developer.spotify.com/web-api/get-a-list-of-current-users-playlists/)**. Thanks [@JMPerez](https://github.com/JMPerez) and [@vinialbano](https://github.com/vinialbano). #### 2.3.1 (3 July 2016) @@ -119,12 +104,12 @@ Likely more changes coming before release to npm, which will happen shortly. #### 2.3.0 (2 April 2016) -* Add language bindings for **[Get Recommendations Based on Seeds](https://developer.spotify.com/documentation/web-api/reference/get-recommendations/)**, **[Get a User's Top Artists and Tracks](https://developer.spotify.com/documentation/web-api/reference/get-users-top-artists-and-tracks/)**, **[Get Audio Features for a Track](https://developer.spotify.com/documentation/web-api/reference/get-audio-features/)**, and **[Get Audio Features for Several Tracks](https://developer.spotify.com/documentation/web-api/reference/get-several-audio-features/)**. Read more about the endpoints in the links above or in this [blog post](https://developer.spotify.com/news-stories/2016/03/29/api-improvements-update/). +* Add language bindings for **[Get Recommendations Based on Seeds](https://developer.spotify.com/web-api/get-recommendations/)**, **[Get a User's Top Artists and Tracks](https://developer.spotify.com/web-api/get-users-top-artists-and-tracks/)**, **[Get Audio Features for a Track](https://developer.spotify.com/web-api/get-audio-features/)**, and **[Get Audio Features for Several Tracks](https://developer.spotify.com/web-api/get-several-audio-features/)**. Read more about the endpoints in the links above or in this [blog post](https://developer.spotify.com/news-stories/2016/03/29/api-improvements-update/). * Add generic search method enabling searches for several types at once, e.g. search for both tracks and albums in a single request, instead of one request for track results and one request for album results. #### 2.2.0 (23 November 2015) -* Add language bindings for **[Get User's Saved Albums](https://developer.spotify.com/documentation/web-api/reference/get-users-saved-albums/)** and other endpoints related to the user's saved albums. +* Add language bindings for **[Get User's Saved Albums](https://developer.spotify.com/web-api/get-users-saved-albums/)** and other endpoints related to the user's saved albums. #### 2.1.1 (23 November 2015) @@ -132,7 +117,7 @@ Likely more changes coming before release to npm, which will happen shortly. #### 2.1.0 (16 July 2015) -* Add language binding for **[Get Followed Artists](https://developer.spotify.com/documentation/web-api/reference/get-followed-artists/)** +* Add language binding for **[Get Followed Artists](https://developer.spotify.com/web-api/get-followed-artists/)** #### 2.0.2 (11 May 2015) @@ -149,28 +134,28 @@ Likely more changes coming before release to npm, which will happen shortly. #### 1.3.13 (26 Feb 2015) -* Add language binding for **[Reorder tracks in a Playlist](https://developer.spotify.com/documentation/web-api/reference/reorder-playlists-tracks/)** +* Add language binding for **[Reorder tracks in a Playlist](https://developer.spotify.com/web-api/reorder-playlists-tracks/)** #### 1.3.12 (22 Feb 2015) -* Add language binding for **[Remove tracks in a Playlist by Position](https://developer.spotify.com/documentation/web-api/reference/remove-tracks-playlist/)** +* Add language binding for **[Remove tracks in a Playlist by Position](https://developer.spotify.com/web-api/remove-tracks-playlist/)** #### 1.3.11 -* Add **[Search for Playlists](https://developer.spotify.com/documentation/web-api/reference/search-item/)** endpoint. +* Add **[Search for Playlists](https://developer.spotify.com/web-api/search-item/)** endpoint. #### 1.3.10 -* Add market parameter to endpoints supporting **[Track Relinking](https://developer.spotify.com/documentation/web-api/reference/track-relinking-guide/)**. +* Add market parameter to endpoints supporting **[Track Relinking](https://developer.spotify.com/web-api/track-relinking-guide/)**. * Improve SEO by adding keywords to the package.json file. ;-) #### 1.3.8 -* Add **[Get a List of Categories](https://developer.spotify.com/documentation/web-api/reference/get-list-categories/)**, **[Get a Category](https://developer.spotify.com/documentation/web-api/reference/get-category/)**, and **[Get A Category's Playlists](https://developer.spotify.com/documentation/web-api/reference/get-categorys-playlists/)** endpoints. +* Add **[Get a List of Categories](https://developer.spotify.com/web-api/get-list-categories/)**, **[Get a Category](https://developer.spotify.com/web-api/get-category/)**, and **[Get A Category's Playlists](https://developer.spotify.com/web-api/get-categorys-playlists/)** endpoints. #### 1.3.7 -* Add **[Check if Users are Following Playlist](https://developer.spotify.com/documentation/web-api/reference/check-user-following-playlist/)** endpoint. +* Add **[Check if Users are Following Playlist](https://developer.spotify.com/web-api/check-user-following-playlist/)** endpoint. #### 1.3.5 @@ -178,7 +163,7 @@ Likely more changes coming before release to npm, which will happen shortly. #### 1.3.4 -* Add **[Follow Playlist](https://developer.spotify.com/documentation/web-api/reference/follow-playlist/)** and **[Unfollow Playlist](https://developer.spotify.com/documentation/web-api/reference/unfollow-playlist/)** endpoints. +* Add **[Follow Playlist](https://developer.spotify.com/web-api/follow-playlist/)** and **[Unfollow Playlist](https://developer.spotify.com/web-api/unfollow-playlist/)** endpoints. #### 1.3.3 @@ -194,11 +179,11 @@ Likely more changes coming before release to npm, which will happen shortly. #### 1.2.1 -* Add **[Follow endpoints](https://developer.spotify.com/documentation/web-api/reference/web-api-follow-endpoints/)**. Great work [JMPerez](https://github.com/JMPerez). +* Add **[Follow endpoints](https://developer.spotify.com/web-api/web-api-follow-endpoints/)**. Great work [JMPerez](https://github.com/JMPerez). #### 1.1.0 -* Add **[Browse endpoints](https://developer.spotify.com/documentation/web-api/reference/browse-endpoints/)**. Thanks [fsahin](https://github.com/fsahin). +* Add **[Browse endpoints](https://developer.spotify.com/web-api/browse-endpoints/)**. Thanks [fsahin](https://github.com/fsahin). #### 1.0.2 @@ -210,19 +195,19 @@ Likely more changes coming before release to npm, which will happen shortly. #### 1.0.0 -* Add **[Replace tracks in a Playlist](https://developer.spotify.com/documentation/web-api/reference/replace-playlists-tracks/)** endpoint -* Add **[Remove tracks in a Playlist](https://developer.spotify.com/documentation/web-api/reference/remove-tracks-playlist/)** endpoint +* Add **[Replace tracks in a Playlist](https://developer.spotify.com/web-api/replace-playlists-tracks/)** endpoint +* Add **[Remove tracks in a Playlist](https://developer.spotify.com/web-api/remove-tracks-playlist/)** endpoint * Return errors as Error objects instead of unparsed JSON. Thanks [niftylettuce](https://github.com/niftylettuce). #### 0.0.11 -* Add **[Change Playlist details](https://developer.spotify.com/documentation/web-api/reference/change-playlist-details/)** endpoint (change published status and name). Gracias [JMPerez](https://github.com/JMPerez). +* Add **[Change Playlist details](https://developer.spotify.com/web-api/change-playlist-details/)** endpoint (change published status and name). Gracias [JMPerez](https://github.com/JMPerez). #### 0.0.10 -* Add Your Music Endpoints (**[Add tracks](https://developer.spotify.com/documentation/web-api/reference/save-tracks-user/)**, **[Remove tracks](https://developer.spotify.com/documentation/web-api/reference/remove-tracks-user/)**, **[Contains tracks](https://developer.spotify.com/documentation/web-api/reference/check-users-saved-tracks/)**, **[Get tracks](https://developer.spotify.com/documentation/web-api/reference/get-users-saved-tracks/)**). +* Add Your Music Endpoints (**[Add tracks](https://developer.spotify.com/web-api/save-tracks-user/)**, **[Remove tracks](https://developer.spotify.com/web-api/remove-tracks-user/)**, **[Contains tracks](https://developer.spotify.com/web-api/check-users-saved-tracks/)**, **[Get tracks](https://developer.spotify.com/web-api/get-users-saved-tracks/)**). * Documentation updates (change scope name of playlist-modify to playlist-modify-public, and a fix to a parameter type). Thanks [JMPerez](https://github.com/JMPerez) and [matiassingers](https://github.com/matiassingers). #### 0.0.9 -* Add **[Related artists](https://developer.spotify.com/documentation/web-api/reference/get-related-artists/)** endpoint \ No newline at end of file +* Add **[Related artists](https://developer.spotify.com/web-api/get-related-artists/)** endpoint diff --git a/spotify-web-api-node-plus/LICENSE b/spotify-web-api-node/LICENSE similarity index 100% rename from spotify-web-api-node-plus/LICENSE rename to spotify-web-api-node/LICENSE diff --git a/spotify-web-api-node-plus/README.md b/spotify-web-api-node/README.md similarity index 95% rename from spotify-web-api-node-plus/README.md rename to spotify-web-api-node/README.md index 97de171..1e7a9cd 100644 --- a/spotify-web-api-node-plus/README.md +++ b/spotify-web-api-node/README.md @@ -1,9 +1,16 @@ # Spotify Web API Node +[![Tests](https://travis-ci.org/thelinmichael/spotify-web-api-node.svg?branch=master)](https://travis-ci.org/thelinmichael/spotify-web-api-node) +[![Coverage Status](https://coveralls.io/repos/thelinmichael/spotify-web-api-node/badge.svg)](https://coveralls.io/r/thelinmichael/spotify-web-api-node) +[![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/spotify-web-api-node.svg)](https://bundlephobia.com/result?p=spotify-web-api-node) + This is a universal wrapper/client for the [Spotify Web API](https://developer.spotify.com/web-api/) that runs on Node.JS and the browser, using [browserify](http://browserify.org/)/[webpack](https://webpack.github.io/)/[rollup](http://rollupjs.org/). A list of selected wrappers for different languages and environments is available at the Developer site's [Libraries page](https://developer.spotify.com/documentation/web-api/libraries/). -This library is a fork of [spotify-web-api-node](https://github.com/thelinmichael/spotify-web-api-node) with the goal of -the library becoming dependency free and actively maintained. +Project owners are [thelinmichael](https://github.com/thelinmichael) and [JMPerez](https://github.com/JMPerez), with help from [a lot of awesome contributors](https://github.com/thelinmichael/spotify-web-api-node/network/members). + +## Version 5 + +> :warning: Since the last release (4.0.0, released over year ago) a lot of new functionality has been added by a lot of different contributors. **Thank you.** In order to implement some of the feature requests, some **breaking changes** had to be made. A list of them, along with a list of the new functionality, can be found in the [CHANGELOG](https://github.com/thelinmichael/spotify-web-api-node/blob/master/CHANGELOG.md). ## Table of contents @@ -45,7 +52,7 @@ The library includes helper functions to do the following: #### Your Music library * Add, remove, and get tracks and albums that are in the signed in user's Your Music library -* Check if a track or album is in the signed in user's Your Music library +* Check if a track or album is in the signed in user's Your Music library #### Personalization @@ -66,14 +73,14 @@ The library includes helper functions to do the following: * Get a User's Available Devices * Get Information About The User's Current Playback State * Get Current User's Recently Played Tracks -* Get the User's Currently Playing Track +* Get the User's Currently Playing Track * Pause a User's Playback * Seek To Position In Currently Playing Track * Set Repeat Mode On User’s Playback * Set Volume For User's Playback * Skip User’s Playback To Next Track -* Skip User’s Playback To Previous Track -* Start/Resume a User's Playback +* Skip User’s Playback To Previous Track +* Start/Resume a User's Playback * Toggle Shuffle For User’s Playback * Transfer a User's Playback @@ -120,12 +127,7 @@ This project depends on [superagent](https://github.com/visionmedia/superagent) ## Installation -```bash -$ npm install spotify-web-api-node --save -# If you are using this library in a Node version that does not have the fetch function (Node < v17), you will need -# to install and configure a fetch polyfill -$ npm install node-fetch --save -``` + $ npm install spotify-web-api-node --save ## Usage @@ -199,7 +201,7 @@ spotifyApi ### Responses and errors -This exposes the response headers, status code and body. +This exposes the response headers, status code and body. ```json { @@ -213,7 +215,7 @@ This exposes the response headers, status code and body. } ``` -Errors have same fields, as well as a human readable `message`. This is especially useful since +Errors have same fields, as well as a human readable `message`. This is especially useful since Spotify's Web API returns different types of error objects depending on the endpoint being called. #### Example of a response @@ -242,7 +244,7 @@ Retrieving a track's metadata in `spotify-web-api-node` version 1.4.0 and later: ### More examples -Below are examples for all helper functions. Longer examples of some requests can be found in the [examples folder](examples/). +Below are examples for all helper functions. Longer examples of some requests can be found in the [examples folder](examples/). ```javascript var SpotifyWebApi = require('spotify-web-api-node'); @@ -253,22 +255,6 @@ var spotifyApi = new SpotifyWebApi(); * Get metadata of tracks, albums, artists, shows, and episodes */ -// Get track -spotifyApi.getTrack("28s5iRbX62GZG2ReZTLKDa") - .then(function (data) { - console.log("Track information", data.body); - }, function (err) { - console.error(err); - }); - -// Get multiple tracks -spotifyApi.getTracks(["6Rb4xFCy3Hh5MJ88ehLXdc", "5oOoQn1bhg6nbuvd54URPF"]) - .then(function (data) { - console.log("Tracks information", track); - },function (err) { - console.error(err); - }); - // Get album spotifyApi.getAlbum('5U4W9E5WsYb2jUQWePT8Xm') .then(function(data) { @@ -834,7 +820,7 @@ spotifyApi.getMyRecentlyPlayedTracks({ console.log('Something went wrong!', err); }); -// Get the User's Currently Playing Track +// Get the User's Currently Playing Track spotifyApi.getMyCurrentPlayingTrack() .then(function(data) { console.log('Now playing: ' + data.body.item.name); @@ -887,7 +873,7 @@ spotifyApi.skipToNext() console.log('Something went wrong!', err); }); -// Skip User’s Playback To Previous Track +// Skip User’s Playback To Previous Track spotifyApi.skipToPrevious() .then(function() { console.log('Skip to previous'); @@ -896,7 +882,7 @@ spotifyApi.skipToPrevious() console.log('Something went wrong!', err); }); -// Start/Resume a User's Playback +// Start/Resume a User's Playback spotifyApi.play() .then(function() { console.log('Playback started'); @@ -1252,7 +1238,7 @@ api ## Development -See something you think can be improved? [Open an issue](https://github.com/eligundry/spotify-web-api-fetch/issues/new) or clone the project and send a pull request with your changes. +See something you think can be improved? [Open an issue](https://github.com/thelinmichael/spotify-web-api-node/issues/new) or clone the project and send a pull request with your changes. ### Running tests diff --git a/spotify-web-api-node/examples/access-token-refresh.js b/spotify-web-api-node/examples/access-token-refresh.js new file mode 100644 index 0000000..8ddd324 --- /dev/null +++ b/spotify-web-api-node/examples/access-token-refresh.js @@ -0,0 +1,86 @@ +const SpotifyWebApi = require('../'); + +/** + * This example refreshes an access token. Refreshing access tokens is only possible access tokens received using the + * Authorization Code flow, documented here: https://developer.spotify.com/spotify-web-api/authorization-guide/#authorization_code_flow + */ + +/* Retrieve an authorization code as documented here: + * https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + * or in the Authorization section of the README. + * + * Codes are given for a set of scopes. For this example, the scopes are user-read-private and user-read-email. + * Scopes are documented here: + * https://developer.spotify.com/documentation/general/guides/scopes/ + */ +const authorizationCode = + ''; + +/** + * Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '', + redirectUri: '' +}); + +// When our access token will expire +let tokenExpirationEpoch; + +// First retrieve an access token +spotifyApi.authorizationCodeGrant(authorizationCode).then( + function(data) { + // Set the access token and refresh token + spotifyApi.setAccessToken(data.body['access_token']); + spotifyApi.setRefreshToken(data.body['refresh_token']); + + // Save the amount of seconds until the access token expired + tokenExpirationEpoch = + new Date().getTime() / 1000 + data.body['expires_in']; + console.log( + 'Retrieved token. It expires in ' + + Math.floor(tokenExpirationEpoch - new Date().getTime() / 1000) + + ' seconds!' + ); + }, + function(err) { + console.log( + 'Something went wrong when retrieving the access token!', + err.message + ); + } +); + +// Continually print out the time left until the token expires.. +let numberOfTimesUpdated = 0; + +setInterval(function() { + console.log( + 'Time left: ' + + Math.floor(tokenExpirationEpoch - new Date().getTime() / 1000) + + ' seconds left!' + ); + + // OK, we need to refresh the token. Stop printing and refresh. + if (++numberOfTimesUpdated > 5) { + clearInterval(this); + + // Refresh token and print the new time to expiration. + spotifyApi.refreshAccessToken().then( + function(data) { + tokenExpirationEpoch = + new Date().getTime() / 1000 + data.body['expires_in']; + console.log( + 'Refreshed token. It now expires in ' + + Math.floor(tokenExpirationEpoch - new Date().getTime() / 1000) + + ' seconds!' + ); + }, + function(err) { + console.log('Could not refresh the token!', err.message); + } + ); + } +}, 1000); diff --git a/spotify-web-api-node/examples/access-token-using-client-credentials.js b/spotify-web-api-node/examples/access-token-using-client-credentials.js new file mode 100644 index 0000000..ff7cf3b --- /dev/null +++ b/spotify-web-api-node/examples/access-token-using-client-credentials.js @@ -0,0 +1,32 @@ +const SpotifyWebApi = require('../'); + +/** + * This example retrieves an access token using the Client Credentials Flow, documented at: + * https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow + */ + +/** + * Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '' +}); + +// Retrieve an access token +spotifyApi.clientCredentialsGrant().then( + function(data) { + console.log('The access token expires in ' + data.body['expires_in']); + console.log('The access token is ' + data.body['access_token']); + + // Save the access token so that it's used in future calls + spotifyApi.setAccessToken(data.body['access_token']); + }, + function(err) { + console.log( + 'Something went wrong when retrieving an access token', + err.message + ); + } +); diff --git a/spotify-web-api-node/examples/add-remove-replace-tracks-in-a-playlist.js b/spotify-web-api-node/examples/add-remove-replace-tracks-in-a-playlist.js new file mode 100644 index 0000000..d444d28 --- /dev/null +++ b/spotify-web-api-node/examples/add-remove-replace-tracks-in-a-playlist.js @@ -0,0 +1,84 @@ +const SpotifyWebApi = require('../'); + +/** + * This example demonstrates adding tracks, removing tracks, and replacing tracks in a playlist. At the time of writing this + * documentation, this is the available playlist track modification feature in the Spotify Web API. + * + * Since authorization is required, this example retrieves an access token using the Authorization Code Grant flow, + * documented here: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + * + * Codes are given for a set of scopes. For this example, the scopes are playlist-modify-public. + * Scopes are documented here: + * https://developer.spotify.com/documentation/general/guides/scopes/ + */ + +/* Obtain the `authorizationCode` below as described in the Authorization section of the README. + */ +const authorizationCode = + ''; + +/** + * Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '', + redirectUri: '' +}); + +let playlistId; + +// First retrieve an access token +spotifyApi + .authorizationCodeGrant(authorizationCode) + .then(function(data) { + // Save the access token so that it's used in future requests + spotifyApi.setAccessToken(data['access_token']); + + // Create a playlist + return spotifyApi.createPlaylist( + 'thelinmichael', + 'My New Awesome Playlist' + ); + }) + .then(function(data) { + console.log('Ok. Playlist created!'); + playlistId = data.body['id']; + + // Add tracks to the playlist + return spotifyApi.addTracksToPlaylist(playlistId, [ + 'spotify:track:4iV5W9uYEdYUVa79Axb7Rh', + 'spotify:track:6tcfwoGcDjxnSc6etAkDRR', + 'spotify:track:4iV5W9uYEdYUVa79Axb7Rh' + ]); + }) + .then(function(data) { + console.log('Ok. Tracks added!'); + + // Woops! Made a duplicate. Remove one of the duplicates from the playlist + return spotifyApi.removeTracksFromPlaylist('thelinmichael', playlistId, [ + { + uri: 'spotify:track:4iV5W9uYEdYUVa79Axb7Rh', + positions: [0] + } + ]); + }) + .then(function(data) { + console.log('Ok. Tracks removed!'); + + // Actually, lets just replace all tracks in the playlist with something completely different + return spotifyApi.replaceTracksInPlaylist('thelinmichael', playlistId, [ + 'spotify:track:5Wd2bfQ7wc6GgSa32OmQU3', + 'spotify:track:4r8lRYnoOGdEi6YyI5OC1o', + 'spotify:track:4TZZvblv2yzLIBk2JwJ6Un', + 'spotify:track:2IA4WEsWAYpV9eKkwR2UYv', + 'spotify:track:6hDH3YWFdcUNQjubYztIsG' + ]); + }) + .then(function(data) { + console.log('Ok. Tracks replaced!'); + }) + .catch(function(err) { + console.log('Something went wrong:', err.message); + }); diff --git a/spotify-web-api-node/examples/add-tracks-to-a-playlist.js b/spotify-web-api-node/examples/add-tracks-to-a-playlist.js new file mode 100644 index 0000000..4ab8da1 --- /dev/null +++ b/spotify-web-api-node/examples/add-tracks-to-a-playlist.js @@ -0,0 +1,49 @@ +const SpotifyWebApi = require('../'); + +/** + * This example demonstrates adding tracks to a specified position in a playlist. + * + * Since authorization is required, this example retrieves an access token using the Authorization Code Grant flow, + * documented here: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + * + * Codes are given for a set of scopes. For this example, the scopes are playlist-modify-public. + * Scopes are documented here: + * https://developer.spotify.com/documentation/general/guides/scopes/ + */ + +/* Obtain the `authorizationCode` below as described in the Authorization section of the README. + */ +const authorizationCode = ''; + +/** + * Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '', + redirectUri: '' +}); + +// First retrieve an access token +spotifyApi + .authorizationCodeGrant(authorizationCode) + .then(function(data) { + spotifyApi.setAccessToken(data.body['access_token']); + return spotifyApi.addTracksToPlaylist( + '5ieJqeLJjjI8iJWaxeBLuK', + [ + 'spotify:track:4iV5W9uYEdYUVa79Axb7Rh', + 'spotify:track:1301WleyT98MSxVHPZCA6M' + ], + { + position: 10 + } + ); + }) + .then(function(data) { + console.log('Added tracks to the playlist!'); + }) + .catch(function(err) { + console.log('Something went wrong:', err.message); + }); diff --git a/spotify-web-api-node/examples/client-credentials.js b/spotify-web-api-node/examples/client-credentials.js new file mode 100644 index 0000000..b6a75ab --- /dev/null +++ b/spotify-web-api-node/examples/client-credentials.js @@ -0,0 +1,26 @@ +const { util } = require('prettier'); +var SpotifyWebApi = require('../'); + +/** + * This example uses the Client Credentials authorization flow. + */ + +/** + * Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '' +}); + +// Retrieve an access token using your credentials +spotifyApi.clientCredentialsGrant(). + then(function(result) { + console.log('It worked! Your access token is: ' + result.body.access_token); + }).catch(function(err) { + console.log('If this is printed, it probably means that you used invalid ' + + 'clientId and clientSecret values. Please check!'); + console.log('Hint: '); + console.log(err); + }); diff --git a/spotify-web-api-node/examples/get-info-about-current-user.js b/spotify-web-api-node/examples/get-info-about-current-user.js new file mode 100644 index 0000000..d389b13 --- /dev/null +++ b/spotify-web-api-node/examples/get-info-about-current-user.js @@ -0,0 +1,54 @@ +const SpotifyWebApi = require('../'); + +/** + * This example retrieves information about the 'current' user. The current user is the user that has + * authorized the application to access its data. + */ + +/* Retrieve a code as documented here: + * https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + * + * Codes are given for a set of scopes. For this example, the scopes are user-read-private and user-read-email. + * Scopes are documented here: + * https://developer.spotify.com/documentation/general/guides/scopes/ + */ +const authorizationCode = + ''; + +/* Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '', + redirectUri: '' +}); + +// First retrieve an access token +spotifyApi + .authorizationCodeGrant(authorizationCode) + .then(function(data) { + console.log('Retrieved access token', data.body['access_token']); + + // Set the access token + spotifyApi.setAccessToken(data.body['access_token']); + + // Use the access token to retrieve information about the user connected to it + return spotifyApi.getMe(); + }) + .then(function(data) { + // "Retrieved data for Faruk Sahin" + console.log('Retrieved data for ' + data.body['display_name']); + + // "Email is farukemresahin@gmail.com" + console.log('Email is ' + data.body.email); + + // "Image URL is http://media.giphy.com/media/Aab07O5PYOmQ/giphy.gif" + console.log('Image URL is ' + data.body.images[0].url); + + // "This user has a premium account" + console.log('This user has a ' + data.body.product + ' account'); + }) + .catch(function(err) { + console.log('Something went wrong:', err.message); + }); diff --git a/spotify-web-api-node/examples/get-related-artists.js b/spotify-web-api-node/examples/get-related-artists.js new file mode 100644 index 0000000..f21ed7f --- /dev/null +++ b/spotify-web-api-node/examples/get-related-artists.js @@ -0,0 +1,49 @@ +var SpotifyWebApi = require('../'); + +/* + * This example shows how to get artists related to another artists. The endpoint is documented here: + * https://developer.spotify.com/web-api/get-related-artists/ + + * Please note that authorization is now required and so this example retrieves an access token using the Authorization Code Flow, + * documented here: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + */ + +var authorizationCode = + 'AQAgjS78s64u1axMCBCRA0cViW_ZDDU0pbgENJ_-WpZr3cEO7V5O-JELcEPU6pGLPp08SfO3dnHmu6XJikKqrU8LX9W6J11NyoaetrXtZFW-Y58UGeV69tuyybcNUS2u6eyup1EgzbTEx4LqrP_eCHsc9xHJ0JUzEhi7xcqzQG70roE4WKM_YrlDZO-e7GDRMqunS9RMoSwF_ov-gOMpvy9OMb7O58nZoc3LSEdEwoZPCLU4N4TTJ-IF6YsQRhQkEOJK'; + +/* Set the credentials given on Spotify's My Applications page. + * https://developer.spotify.com/my-applications + */ +var spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '', + redirectUri: '' +}); + +var artistId = '0qeei9KQnptjwb8MgkqEoy'; + +spotifyApi +.authorizationCodeGrant(authorizationCode) + .then(function(data) { + console.log('Retrieved access token', data.body['access_token']); + + // Set the access token + spotifyApi.setAccessToken(data.body['access_token']); + + // Use the access token to retrieve information about the user connected to it + return spotifyApi.getArtistRelatedArtists(artistId); + }) + .then(function(data) { + if (data.body.artists.length) { + // Print the number of similar artists + console.log('I got ' + data.body.artists.length + ' similar artists!'); + + console.log('The most similar one is ' + data.body.artists[0].name); + } else { + console.log("I didn't find any similar artists.. Sorry."); + } + }, + function(err) { + console.log('Something went wrong:', err.message); + } +); diff --git a/spotify-web-api-node/examples/get-top-tracks-for-artist.js b/spotify-web-api-node/examples/get-top-tracks-for-artist.js new file mode 100644 index 0000000..4eebd13 --- /dev/null +++ b/spotify-web-api-node/examples/get-top-tracks-for-artist.js @@ -0,0 +1,56 @@ +const SpotifyWebApi = require('../'); + +/** + * This example retrieves the top tracks for an artist. + * https://developer.spotify.com/documentation/web-api/reference/artists/get-artists-top-tracks/ + */ + +/** + * This endpoint doesn't require an access token, but it's beneficial to use one as it + * gives the application a higher rate limit. + * + * Since it's not necessary to get an access token connected to a specific user, this example + * uses the Client Credentials flow. This flow uses only the client ID and the client secret. + * https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '' +}); + +// Retrieve an access token +spotifyApi + .clientCredentialsGrant() + .then(function(data) { + // Set the access token on the API object so that it's used in all future requests + spotifyApi.setAccessToken(data.body['access_token']); + + // Get the most popular tracks by David Bowie in Great Britain + return spotifyApi.getArtistTopTracks('0oSGxfWSnnOXhD2fKuz2Gy', 'GB'); + }) + .then(function(data) { + console.log('The most popular tracks for David Bowie is..'); + console.log('Drum roll..'); + console.log('...'); + + /* + * 1. Space Oddity - 2009 Digital Remaster (popularity is 51) + * 2. Heroes - 1999 Digital Remaster (popularity is 33) + * 3. Let's Dance - 1999 Digital Remaster (popularity is 20) + * 4. ... + */ + data.body.tracks.forEach(function(track, index) { + console.log( + index + + 1 + + '. ' + + track.name + + ' (popularity is ' + + track.popularity + + ')' + ); + }); + }) + .catch(function(err) { + console.log('Unfortunately, something has gone wrong.', err.message); + }); diff --git a/spotify-web-api-node/examples/search-for-tracks.js b/spotify-web-api-node/examples/search-for-tracks.js new file mode 100644 index 0000000..dc1f0b3 --- /dev/null +++ b/spotify-web-api-node/examples/search-for-tracks.js @@ -0,0 +1,55 @@ +const SpotifyWebApi = require('../'); + +/* + * This example shows how to search for a track. The endpoint is documented here: + * https://developer.spotify.com/documentation/web-api/reference/search/ + + * Since authorization is now required, this example retrieves an access token using the Authorization Code Grant flow, + * documented here: https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow + * + * Obtain the `authorizationCode` below as described in the Authorization section of the README. + */ + +const authorizationCode = ''; + +/** + * Get the credentials from Spotify's Dashboard page. + * https://developer.spotify.com/dashboard/applications + */ +const spotifyApi = new SpotifyWebApi({ + clientId: '', + clientSecret: '', + redirectUri: '' +}); + +spotifyApi + .authorizationCodeGrant(authorizationCode) + .then(function(data) { + console.log('Retrieved access token', data.body['access_token']); + + // Set the access token + spotifyApi.setAccessToken(data.body['access_token']); + + // Use the access token to retrieve information about the user connected to it + return spotifyApi.searchTracks('Love'); + }) + .then(function(data) { + // Print some information about the results + console.log('I got ' + data.body.tracks.total + ' results!'); + + // Go through the first page of results + var firstPage = data.body.tracks.items; + console.log('The tracks in the first page are (popularity in parentheses):'); + + /* + * 0: All of Me (97) + * 1: My Love (91) + * 2: I Love This Life (78) + * ... + */ + firstPage.forEach(function(track, index) { + console.log(index + ': ' + track.name + ' (' + track.popularity + ')'); + }); + }).catch(function(err) { + console.log('Something went wrong:', err.message); + }); diff --git a/spotify-web-api-node/examples/tutorial/00-get-access-token.js b/spotify-web-api-node/examples/tutorial/00-get-access-token.js new file mode 100644 index 0000000..9b51720 --- /dev/null +++ b/spotify-web-api-node/examples/tutorial/00-get-access-token.js @@ -0,0 +1,101 @@ +/** + * This example is using the Authorization Code flow. + * + * In root directory run + * + * npm install express + * + * then run with the followinng command. If you don't have a client_id and client_secret yet, + * create an application on Create an application here: https://developer.spotify.com/my-applications to get them. + * Make sure you whitelist the correct redirectUri in line 26. + * + * node access-token-server.js "" "" + * + * and visit in your Browser. + */ +const SpotifyWebApi = require('../../'); +const express = require('../../node_modules/express'); + +const scopes = [ + 'ugc-image-upload', + 'user-read-playback-state', + 'user-modify-playback-state', + 'user-read-currently-playing', + 'streaming', + 'app-remote-control', + 'user-read-email', + 'user-read-private', + 'playlist-read-collaborative', + 'playlist-modify-public', + 'playlist-read-private', + 'playlist-modify-private', + 'user-library-modify', + 'user-library-read', + 'user-top-read', + 'user-read-playback-position', + 'user-read-recently-played', + 'user-follow-read', + 'user-follow-modify' +]; + +const spotifyApi = new SpotifyWebApi({ + redirectUri: 'http://localhost:8888/callback', + clientId: process.argv.slice(2)[0], + clientSecret: process.argv.slice(2)[1] +}); + +const app = express(); + +app.get('/login', (req, res) => { + res.redirect(spotifyApi.createAuthorizeURL(scopes)); +}); + +app.get('/callback', (req, res) => { + const error = req.query.error; + const code = req.query.code; + const state = req.query.state; + + if (error) { + console.error('Callback Error:', error); + res.send(`Callback Error: ${error}`); + return; + } + + spotifyApi + .authorizationCodeGrant(code) + .then(data => { + const access_token = data.body['access_token']; + const refresh_token = data.body['refresh_token']; + const expires_in = data.body['expires_in']; + + spotifyApi.setAccessToken(access_token); + spotifyApi.setRefreshToken(refresh_token); + + console.log('access_token:', access_token); + console.log('refresh_token:', refresh_token); + + console.log( + `Sucessfully retreived access token. Expires in ${expires_in} s.` + ); + res.send('Success! You can now close the window.'); + + setInterval(async () => { + const data = await spotifyApi.refreshAccessToken(); + const access_token = data.body['access_token']; + + console.log('The access token has been refreshed!'); + console.log('access_token:', access_token); + spotifyApi.setAccessToken(access_token); + }, expires_in / 2 * 1000); + }) + .catch(error => { + console.error('Error getting Tokens:', error); + res.send(`Error getting Tokens: ${error}`); + }); +}); + +app.listen(8888, () => + console.log( + 'HTTP Server up. Now go to http://localhost:8888/login in your browser.' + ) +); diff --git a/spotify-web-api-node/examples/tutorial/01-basics/01-get-info-about-current-user.js b/spotify-web-api-node/examples/tutorial/01-basics/01-get-info-about-current-user.js new file mode 100644 index 0000000..97535b1 --- /dev/null +++ b/spotify-web-api-node/examples/tutorial/01-basics/01-get-info-about-current-user.js @@ -0,0 +1,11 @@ +const SpotifyWebApi = require('../../../'); + +const spotifyApi = new SpotifyWebApi(); +spotifyApi.setAccessToken(process.env.SPOTIFY_ACCESS_TOKEN); + +(async () => { + const me = await spotifyApi.getMe(); + console.log(me); +})().catch(e => { + console.error(e); +}); diff --git a/spotify-web-api-node/examples/tutorial/README.md b/spotify-web-api-node/examples/tutorial/README.md new file mode 100644 index 0000000..72f763d --- /dev/null +++ b/spotify-web-api-node/examples/tutorial/README.md @@ -0,0 +1,17 @@ +Execute all commands from the root folder of this repository. + +Start with + + git clone + cd spotify-web-api-node + npm install + npm install express + node examples/tutorial/00-get-access-token.js "" "" + +and visit in your browser to get an `access_token`. +If you don't have a `client_id` and `client_secret` yet, create an application here: to get them. Make sure you whitelist the correct redirectUri when creating your application, which is `http://localhost:8888/callback`. + +After you got the `access_token`, call all other examples with this token in ENV variable `SPOTIFY_ACCESS_TOKEN`. The easiest way is to call: + + export SPOTIFY_ACCESS_TOKEN="" + node examples/tutorial/01-basics/01-get-info-about-current-user.js diff --git a/spotify-web-api-node-plus/package.json b/spotify-web-api-node/package.json similarity index 54% rename from spotify-web-api-node-plus/package.json rename to spotify-web-api-node/package.json index 87a15fb..8900249 100644 --- a/spotify-web-api-node-plus/package.json +++ b/spotify-web-api-node/package.json @@ -1,14 +1,14 @@ { - "name": "spotify-web-api-node-plus", - "version": "7.0.1", - "homepage": "https://github.com/manhbi18112005/spotify-web-api-node-plus", + "name": "spotify-web-api-node", + "version": "5.0.3", + "homepage": "https://github.com/thelinmichael/spotify-web-api-node", "description": "A Node.js wrapper for Spotify's Web API", "main": "./src/server.js", "author": "Michael Thelin", "contributors": [ { - "name": "Le Ngo Duc Manh", - "url": "https://github.com/manhbi18112005" + "name": "José M. Perez", + "url": "https://github.com/JMPerez" }, { "name": "Deeplydrumming", @@ -18,20 +18,16 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/manhbi18112005/spotify-web-api-node-plus.git" + "url": "https://github.com/thelinmichael/spotify-web-api-node.git" }, "scripts": { "test": "jest", "travis": "npm test -- --coverage && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", - "precommit": "lint-staged", - "prettier": "prettier . --write" + "precommit": "lint-staged" }, "jest": { "verbose": true, - "testURL": "http://localhost/", - "setupFiles": [ - "./setupJest.js" - ] + "testURL": "http://localhost/" }, "lint-staged": { "*.{js,json,css,md}": [ @@ -39,15 +35,24 @@ "git add" ] }, + "dependencies": { + "superagent": "^6.1.0" + }, "devDependencies": { "coveralls": "^3.1.0", "husky": "^4.3.0", "jest": "^26.6.3", - "jest-fetch-mock": "^3.0.3", - "jest-resolve": "^26.6.2", "lint-staged": "^10.4.0", - "node-fetch": "^2.6.12", - "prettier": "^2.1.2" + "prettier": "^2.1.2", + "sinon": "^9.0.3", + "canvas": "^2.6.1", + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2", + "jest-resolve": "^26.6.2", + "minimist": "^1.2.5", + "set-value": ">=2.0.1", + "mixin-deep": ">=1.3.2", + "ini": ">=1.3.6" }, "keywords": [ "spotify", @@ -60,20 +65,5 @@ ], "browser": { "./src/server.js": "./src/client.js" - }, - "files": [ - "./src", - "./LICENSE", - "./package.json", - "./README.md", - "./CHANGELOG.md" - ], - "prettier": { - "trailingComma": "none", - "tabWidth": 2, - "semi": true, - "singleQuote": true, - "bracketSpacing": true, - "arrowParens": "avoid" } } diff --git a/spotify-web-api-node-plus/src/authentication-request.js b/spotify-web-api-node/src/authentication-request.js similarity index 100% rename from spotify-web-api-node-plus/src/authentication-request.js rename to spotify-web-api-node/src/authentication-request.js diff --git a/spotify-web-api-node-plus/src/base-request.js b/spotify-web-api-node/src/base-request.js similarity index 95% rename from spotify-web-api-node-plus/src/base-request.js rename to spotify-web-api-node/src/base-request.js index e1d039e..175156a 100644 --- a/spotify-web-api-node-plus/src/base-request.js +++ b/spotify-web-api-node/src/base-request.js @@ -12,7 +12,6 @@ const Request = function (builder) { this.bodyParameters = builder.bodyParameters this.headers = builder.headers this.path = builder.path - this.timeout = builder.timeout } Request.prototype._getter = function (key) { @@ -39,8 +38,6 @@ Request.prototype.getBodyParameters = Request.prototype._getter( Request.prototype.getHeaders = Request.prototype._getter('headers') -Request.prototype.getTimeout = Request.prototype._getter('timeout') - Request.prototype.getURI = function () { if (!this.scheme || !this.host || !this.port) { throw new Error('Missing components necessary to construct URI') @@ -139,8 +136,6 @@ Builder.prototype.withBodyParameters = Builder.prototype._assigner( Builder.prototype.withHeaders = Builder.prototype._assigner('headers') -Builder.prototype.withTimeout = Builder.prototype._setter('timeout') - Builder.prototype.withAuth = function (accessToken) { if (accessToken) { this.withHeaders({ Authorization: 'Bearer ' + accessToken }) diff --git a/spotify-web-api-node-plus/src/client.js b/spotify-web-api-node/src/client.js similarity index 100% rename from spotify-web-api-node-plus/src/client.js rename to spotify-web-api-node/src/client.js diff --git a/spotify-web-api-node/src/http-manager.js b/spotify-web-api-node/src/http-manager.js new file mode 100644 index 0000000..7feac6c --- /dev/null +++ b/spotify-web-api-node/src/http-manager.js @@ -0,0 +1,134 @@ +'use strict' + +const superagent = require('superagent') +const { + TimeoutError, + WebapiError, + WebapiRegularError, + WebapiAuthenticationError, + WebapiPlayerError +} = require('./response-error') + +const HttpManager = {} + +/* Create superagent options from the base request */ +const _getParametersFromRequest = function (request) { + const options = {} + + if (request.getQueryParameters()) { + options.query = request.getQueryParameters() + } + + if (request.getHeaders() && request.getHeaders()['Content-Type'] === 'application/json') { + options.data = JSON.stringify(request.getBodyParameters()) + } else if (request.getBodyParameters()) { + options.data = request.getBodyParameters() + } + + if (request.getHeaders()) { + options.headers = request.getHeaders() + } + return options +} + +const _toError = function (response) { + if (typeof response.body === 'object' && response.body.error && typeof response.body.error === 'object' && response.body.error.reason) { + return new WebapiPlayerError(response.body, response.headers, response.statusCode) + } + + if (typeof response.body === 'object' && response.body.error && typeof response.body.error === 'object') { + return new WebapiRegularError(response.body, response.headers, response.statusCode) + } + + if (typeof response.body === 'object' && response.body.error && typeof response.body.error === 'string') { + return new WebapiAuthenticationError(response.body, response.headers, response.statusCode) + } + + /* Other type of error, or unhandled Web API error format */ + return new WebapiError(response.body, response.headers, response.statusCode, response.body) +} + +/* Make the request to the Web API */ +HttpManager._makeRequest = function (method, options, uri, callback) { + const req = method.bind(superagent)(uri) + + if (options.query) { + req.query(options.query) + } + + if (options.headers) { + req.set(options.headers) + } + + if (options.data) { + req.send(options.data) + } + + req.end(function (err, response) { + if (err) { + if (err.timeout) { + return callback(new TimeoutError()) + } else if (err.response) { + return callback(_toError(err.response)) + } else { + return callback(err) + } + } + + return callback(null, { + body: response.body, + headers: response.headers, + statusCode: response.statusCode + }) + }) +} + +/** + * Make a HTTP GET request. + * @param {BaseRequest} The request. + * @param {Function} The callback function. + */ +HttpManager.get = function (request, callback) { + const options = _getParametersFromRequest(request) + const method = superagent.get + + HttpManager._makeRequest(method, options, request.getURI(), callback) +} + +/** + * Make a HTTP POST request. + * @param {BaseRequest} The request. + * @param {Function} The callback function. + */ +HttpManager.post = function (request, callback) { + const options = _getParametersFromRequest(request) + const method = superagent.post + + HttpManager._makeRequest(method, options, request.getURI(), callback) +} + +/** + * Make a HTTP DELETE request. + * @param {BaseRequest} The request. + * @param {Function} The callback function. + */ +HttpManager.del = function (request, callback) { + const options = _getParametersFromRequest(request) + const method = superagent.del + + HttpManager._makeRequest(method, options, request.getURI(), callback) +} + +/** + * Make a HTTP PUT request. + * @param {BaseRequest} The request. + * @param {Function} The callback function. + */ +HttpManager.put = function (request, callback) { + const options = _getParametersFromRequest(request) + const method = superagent.put + + HttpManager._makeRequest(method, options, request.getURI(), callback) +} + +module.exports = HttpManager diff --git a/spotify-web-api-node-plus/src/response-error.js b/spotify-web-api-node/src/response-error.js similarity index 58% rename from spotify-web-api-node-plus/src/response-error.js rename to spotify-web-api-node/src/response-error.js index 69582a7..030ed3c 100644 --- a/spotify-web-api-node-plus/src/response-error.js +++ b/spotify-web-api-node/src/response-error.js @@ -7,8 +7,7 @@ class NamedError extends Error { class TimeoutError extends NamedError { constructor () { - const message = - "A timeout occurred while communicating with Spotify's Web API." + const message = 'A timeout occurred while communicating with Spotify\'s Web API.' super(message) } } @@ -29,11 +28,8 @@ class WebapiError extends NamedError { */ class WebapiRegularError extends WebapiError { constructor (body, headers, statusCode) { - const message = - "An error occurred while communicating with Spotify's Web API.\n" + - 'Details: ' + - body.error.message + - '.' + const message = 'An error occurred while communicating with Spotify\'s Web API.\n' + + 'Details: ' + body.error.message + '.' super(body, headers, statusCode, message) } @@ -45,11 +41,8 @@ class WebapiRegularError extends WebapiError { */ class WebapiAuthenticationError extends WebapiError { constructor (body, headers, statusCode) { - const message = - "An authentication error occurred while communicating with Spotify's Web API.\n" + - 'Details: ' + - body.error + - (body.error_description ? ' ' + body.error_description + '.' : '.') + const message = 'An authentication error occurred while communicating with Spotify\'s Web API.\n' + + 'Details: ' + body.error + (body.error_description ? ' ' + body.error_description + '.' : '.') super(body, headers, statusCode, message) } @@ -61,20 +54,11 @@ class WebapiAuthenticationError extends WebapiError { */ class WebapiPlayerError extends WebapiError { constructor (body, headers, statusCode) { - const message = - "An error occurred while communicating with Spotify's Web API.\n" + - 'Details: ' + - body.error.message + - (body.error.reason ? ' ' + body.error.reason + '.' : '.') + const message = 'An error occurred while communicating with Spotify\'s Web API.\n' + + 'Details: ' + body.error.message + (body.error.reason ? ' ' + body.error.reason + '.' : '.') super(body, headers, statusCode, message) } } -module.exports = { - WebapiError, - TimeoutError, - WebapiRegularError, - WebapiAuthenticationError, - WebapiPlayerError -} +module.exports = { WebapiError, TimeoutError, WebapiRegularError, WebapiAuthenticationError, WebapiPlayerError } diff --git a/spotify-web-api-node-plus/src/server-methods.js b/spotify-web-api-node/src/server-methods.js similarity index 91% rename from spotify-web-api-node-plus/src/server-methods.js rename to spotify-web-api-node/src/server-methods.js index 94cd37a..d9a4fa6 100644 --- a/spotify-web-api-node-plus/src/server-methods.js +++ b/spotify-web-api-node/src/server-methods.js @@ -4,6 +4,7 @@ const AuthenticationRequest = require('./authentication-request') const HttpManager = require('./http-manager') module.exports = { + /** * Retrieve a URL where the user can give the application permissions. * @param {string[]} scopes The scopes corresponding to the permissions the application needs. @@ -12,12 +13,7 @@ module.exports = { * @param {string} responseType An optional parameter that you can use to specify the code response based on the authentication type - can be set to 'code' or 'token'. Default 'code' to ensure backwards compatability. * @returns {string} The URL where the user can give application permissions. */ - createAuthorizeURL: function ( - scopes, - state, - showDialog, - responseType = 'code' - ) { + createAuthorizeURL: function (scopes, state, showDialog, responseType = 'code') { return AuthenticationRequest.builder() .withPath('/authorize') .withQueryParameters({ @@ -46,7 +42,11 @@ module.exports = { grant_type: 'client_credentials' }) .withHeaders({ - Authorization: 'Basic ' + Buffer.from(this.getClientId() + ':' + this.getClientSecret()).toString('base64'), + Authorization: + 'Basic ' + + Buffer.from( + this.getClientId() + ':' + this.getClientSecret() + ).toString('base64'), 'Content-Type': 'application/x-www-form-urlencoded' }) .build() @@ -94,7 +94,10 @@ module.exports = { }) .withHeaders({ Authorization: - 'Basic ' + Buffer.from(this.getClientId() + ':' + this.getClientSecret()).toString('base64'), + 'Basic ' + + Buffer.from( + this.getClientId() + ':' + this.getClientSecret() + ).toString('base64'), 'Content-Type': 'application/x-www-form-urlencoded' }) .build() diff --git a/spotify-web-api-node-plus/src/server.js b/spotify-web-api-node/src/server.js similarity index 100% rename from spotify-web-api-node-plus/src/server.js rename to spotify-web-api-node/src/server.js diff --git a/spotify-web-api-node-plus/src/spotify-web-api.js b/spotify-web-api-node/src/spotify-web-api.js similarity index 96% rename from spotify-web-api-node-plus/src/spotify-web-api.js rename to spotify-web-api-node/src/spotify-web-api.js index d3b4fa2..9e1379c 100644 --- a/spotify-web-api-node-plus/src/spotify-web-api.js +++ b/spotify-web-api-node/src/spotify-web-api.js @@ -1,6 +1,6 @@ 'use strict' -/* const AuthenticationRequest = require('./authentication-request') */ +const AuthenticationRequest = require('./authentication-request') const WebApiRequest = require('./webapi-request') const HttpManager = require('./http-manager') @@ -91,13 +91,17 @@ SpotifyWebApi.prototype = { }, _getCredential: function (credentialKey) { - if (this._credentials) { + if (!this._credentials) { + + } else { return this._credentials[credentialKey] } }, _resetCredential: function (credentialKey) { - if (this._credentials) { + if (!this._credentials) { + + } else { this._credentials[credentialKey] = null } }, @@ -180,27 +184,6 @@ SpotifyWebApi.prototype = { .execute(HttpManager.get, callback) }, - getPlaylistCoverImage: function (playlistId, callback) { - return WebApiRequest.builder(this.getAccessToken()) - .withPath('/v1/playlists/' + playlistId + '/images') - .withHeaders({ 'Content-Type': 'application/json' }) - .build() - .execute(HttpManager.get, callback) - }, - - /** Get available markets that Spotify support - * @param {requestCallback} callback - * @returns {Promise|undefined} A promise that if successful, returns an object containing the user's devices. Not returned if a callback is given. - */ - getAvailableMarkets: function (callback) { - const path = '/v1/markets' - return WebApiRequest.builder(this.getAccessToken()) - .withPath(path) - .withHeaders({ 'Content-Type': 'application/json' }) - .build() - .execute(HttpManager.get, callback) - }, - /** * Look up an artist. * @param {string} artistId The artist's ID. @@ -496,12 +479,9 @@ SpotifyWebApi.prototype = { return WebApiRequest.builder(this.getAccessToken()) .withPath('/v1/me/playlists') .withHeaders({ 'Content-Type': 'application/json' }) - .withBodyParameters( - { - name - }, - options - ) + .withBodyParameters({ + name + }, options) .build() .execute(HttpManager.post, callback) }, @@ -574,16 +554,16 @@ SpotifyWebApi.prototype = { }, /** - * Add tracks to a playlist. - * @param {string} playlistId The playlist's ID - * @param {string[]} tracks URIs of the tracks to add to the playlist. - * @param {Object} [options] Options, position being the only one. - * @param {requestCallback} [callback] Optional callback method to be called instead of the promise. - * @example addTracksToPlaylist('3EsfV6XzCHU8SPNdbnFogK', - '["spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M"]').then(...) - * @returns {Promise|undefined} A promise that if successful returns an object containing a snapshot_id. If rejected, - * it contains an error object. Not returned if a callback is given. - */ + * Add tracks to a playlist. + * @param {string} playlistId The playlist's ID + * @param {string[]} tracks URIs of the tracks to add to the playlist. + * @param {Object} [options] Options, position being the only one. + * @param {requestCallback} [callback] Optional callback method to be called instead of the promise. + * @example addTracksToPlaylist('3EsfV6XzCHU8SPNdbnFogK', + '["spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M"]').then(...) + * @returns {Promise|undefined} A promise that if successful returns an object containing a snapshot_id. If rejected, + * it contains an error object. Not returned if a callback is given. + */ addTracksToPlaylist: function (playlistId, tracks, options, callback) { return WebApiRequest.builder(this.getAccessToken()) .withPath('/v1/playlists/' + playlistId + '/tracks') @@ -1359,15 +1339,22 @@ SpotifyWebApi.prototype = { /** * Check if users are following a playlist. - * @param {String[]} followerIds IDs of the following users + * @param {string} userId The playlist's owner's user ID * @param {string} playlistId The playlist's ID + * @param {String[]} User IDs of the following users * @param {requestCallback} [callback] Optional callback method to be called instead of the promise. * @returns {Promise|undefined} A promise that if successful returns an array of booleans. If rejected, * it contains an error object. Not returned if a callback is given. */ - areFollowingPlaylist: function (playlistId, followerIds, callback) { + areFollowingPlaylist: function (userId, playlistId, followerIds, callback) { return WebApiRequest.builder(this.getAccessToken()) - .withPath('/v1/playlists/' + playlistId + '/followers/contains') + .withPath( + '/v1/users/' + + encodeURIComponent(userId) + + '/playlists/' + + playlistId + + '/followers/contains' + ) .withQueryParameters({ ids: followerIds.join(',') }) @@ -1542,9 +1529,7 @@ SpotifyWebApi.prototype = { return WebApiRequest.builder(this.getAccessToken()) .withPath('/v1/me/shows') .withHeaders({ 'Content-Type': 'application/json' }) - .withQueryParameters({ - ids: showIds.join(',') - }) + .withBodyParameters(showIds) .build() .execute(HttpManager.del, callback) }, @@ -1662,21 +1647,7 @@ SpotifyWebApi.prototype = { ) .build() .execute(HttpManager.get, callback) - }, - - /** Get current Queue - * @param {requestCallback} [callback] Optional callback method to be called instead of the promise. - * @returns {Promise|undefined} A promise that if successful, returns an object containing information - * about the episodes. Not returned if a callback is given. - */ - - getQueue: function (callback) { - return WebApiRequest.builder(this.getAccessToken()) - .withPath('/v1/me/player/queue') - .build() - .execute(HttpManager.get, callback) } - } SpotifyWebApi._addMethods = function (methods) { diff --git a/spotify-web-api-node-plus/src/webapi-request.js b/spotify-web-api-node/src/webapi-request.js similarity index 100% rename from spotify-web-api-node-plus/src/webapi-request.js rename to spotify-web-api-node/src/webapi-request.js diff --git a/webui/src/components/pages/About.vue b/webui/src/components/pages/About.vue index 7c672b6..a9e8b9c 100644 --- a/webui/src/components/pages/About.vue +++ b/webui/src/components/pages/About.vue @@ -37,6 +37,10 @@

Changelog

    +

    31/07/2024

    + - Reverted to spotify-web-api-node
    + - Fixed (again) the Buffer method in spotify-web-api-node
    +

    26/07/2024

    - Moved all scripts to pnpm
    - Fixed a few dependencies
    @@ -128,7 +132,7 @@
      - diff --git a/webui/src/components/pages/Home.vue b/webui/src/components/pages/Home.vue index 976663a..c1264ce 100644 --- a/webui/src/components/pages/Home.vue +++ b/webui/src/components/pages/Home.vue @@ -1,7 +1,7 @@