Started working on spotify plugin implementation

This commit is contained in:
RemixDev 2021-05-28 17:22:07 +02:00
parent 1371e5aeb4
commit fe47f6f60b
6 changed files with 393 additions and 7 deletions

View file

@ -49,7 +49,17 @@ async function generateDownloadObject(dz, link, bitrate, plugins={}, listener){
let link_type, link_id
[link, link_type, link_id] = await parseLink(link)
if (link_type == null || link_id == null) return null
if (link_type == null || link_id == null){
const pluginNames = Object.keys(plugins)
let currentPlugin
let item = null
for (let i = 0; i < pluginNames.length; i++){
currentPlugin = plugins[pluginNames[i]]
item = await currentPlugin.generateDownloadObject(dz, link, bitrate, listener)
if (item) break
}
return item
}
switch (link_type) {
case 'track':
@ -65,7 +75,6 @@ async function generateDownloadObject(dz, link, bitrate, plugins={}, listener){
case 'artist_top':
return generateArtistTopItem(dz, link_id, bitrate)
}
return null
}
module.exports = {
@ -99,5 +108,8 @@ module.exports = {
localpaths: require('./utils/localpaths.js'),
pathtemplates: require('./utils/pathtemplates.js'),
deezer: require('./utils/deezer.js')
},
plugins: {
spotify: require('./plugins/spotify.js')
}
}

View file

@ -307,6 +307,20 @@ class NotYourPrivatePlaylist extends GenerationError {
}
}
class TrackNotOnDeezer extends GenerationError {
constructor(message) {
super(message);
this.name = "TrackNotOnDeezer";
}
}
class AlbumNotOnDeezer extends GenerationError {
constructor(message) {
super(message);
this.name = "AlbumNotOnDeezer";
}
}
module.exports = {
generateTrackItem,
generateAlbumItem,
@ -317,5 +331,7 @@ module.exports = {
GenerationError,
ISRCnotOnDeezer,
NotYourPrivatePlaylist
NotYourPrivatePlaylist,
TrackNotOnDeezer,
AlbumNotOnDeezer
}

15
deemix/plugins/plugin.js Normal file
View file

@ -0,0 +1,15 @@
class Plugin {
constructor(){}
async setup(){}
async parseLink(link){
return [link, undefined, undefined]
}
/*eslint no-unused-vars: ["error", { "args": "none" }]*/
async generateDownloadObject(dz, link, bitrate, listener){
return null
}
}
module.exports = Plugin

203
deemix/plugins/spotify.js Normal file
View file

@ -0,0 +1,203 @@
const Plugin = require('./plugin.js')
const { getConfigFolder } = require('../utils/localpaths.js')
const {
generateTrackItem,
generateAlbumItem,
TrackNotOnDeezer,
AlbumNotOnDeezer
} = require('../itemgen.js')
const { sep } = require('path')
const fs = require('fs')
const SpotifyWebApi = require('spotify-web-api-node')
const got = require('got')
class Spotify extends Plugin {
constructor(configFolder = undefined){
super()
this.credentials = {clientId: "", clientSecret: ""}
this.enabled = false
this.sp
this.configFolder = configFolder || getConfigFolder()
this.configFolder += `spotify${sep}`
return this
}
async setup(){
fs.mkdirSync(this.configFolder, { recursive: true })
if (! fs.existsSync(this.configFolder+'credentials.json')) fs.writeFileSync(this.configFolder+'credentials.json', JSON.stringify(this.credentials))
this.credentials = JSON.parse(fs.readFileSync(this.configFolder+'credentials.json'))
await this.checkCredentials()
}
async parseLink(link){
if (link.includes('link.tospotify.com')){
link = await got.get(link) // Resolve URL shortner
link = link.url
}
// Remove extra stuff
if (link.includes('?')) link = link.slice(0, link.indexOf('?'))
if (link.includes('&')) link = link.slice(0, link.indexOf('&'))
if (link.endsWith('/')) link = link.slice(0, -1) // Remove last slash if present
let link_type, link_id
if (!link.includes('spotify')) return [link, link_type, link_id] // return if not a spotify link
if (link.search(/[/:]track[/:](.+)/g) != -1){
link_type = 'track'
link_id = /[/:]track[/:](.+)/g.exec(link)[1]
}else if (link.search(/[/:]album[/:](.+)/g) != -1){
link_type = 'album'
link_id = /[/:]album[/:](.+)/g.exec(link)[1]
}else if (link.search(/[/:]playlist[/:](\d+)/g) != -1){
link_type = 'playlist'
link_id = /[/:]playlist[/:](.+)/g.exec(link)[1]
}
return [link, link_type, link_id]
}
/*eslint no-unused-vars: ["error", { "args": "none" }]*/
async generateDownloadObject(dz, link, bitrate, _){
let link_type, link_id
[link, link_type, link_id] = await this.parseLink(link)
if (link_type == null || link_id == null) return null
switch (link_type) {
case 'track':
return this.generateTrackItem(dz, link_id, bitrate)
case 'album':
return this.generateAlbumItem(dz, link_id, bitrate)
case 'playlist':
return this.generatePlaylistItem(dz, link_id, bitrate)
}
}
async generateTrackItem(dz, link_id, bitrate){
let [track_id, trackAPI, _] = await this.convertTrack(dz, link_id)
if (track_id !== "0"){
return generateTrackItem(dz, track_id, bitrate, trackAPI)
} else {
throw new TrackNotOnDeezer
}
}
async generateAlbumItem(dz, link_id, bitrate){
let album_id = await this.convertAlbum(dz, link_id)
if (album_id !== "0"){
return generateAlbumItem(dz, album_id, bitrate)
} else {
throw new AlbumNotOnDeezer
}
}
async generatePlaylistItem(dz, link_id, bitrate){
throw new Error("Not implemented yet")
}
async convertTrack(dz, track_id, fallbackSearch = false, cachedTrack = null){
if (!this.enabled) throw new Error("Spotify plugin not enabled")
let shouldSaveCache = false
let cache
if (!cachedTrack){
// Read spotify cache
cache = {tracks: {}, albums: {}}
shouldSaveCache = true
cachedTrack = await this.sp.getTrack(track_id)
cachedTrack = cachedTrack.body
}
let dz_id = "0"
let dz_track = null
let isrc = null
if (cachedTrack.external_ids && cachedTrack.external_ids.isrc){
isrc = cachedTrack.external_ids.isrc
dz_track = await dz.api.get_track_by_ISRC(isrc)
if (dz_track.title && dz_track.id) dz_id = dz_track.id
}
if (dz_id === "0" && fallbackSearch){
dz_id = dz.api.get_track_id_from_metadata(
cachedTrack.artists[0].name,
cachedTrack.name,
cachedTrack.album.name
)
}
if (shouldSaveCache){
cache.tracks[track_id] = {id: dz_id, isrc: isrc}
// Save edited cache
}
return [dz_id, dz_track, isrc]
}
async convertAlbum(dz, album_id){
if (!this.enabled) throw new Error("Spotify plugin not enabled")
let cachedAlbum
let cache
// Read spotify cache
cache = {tracks: {}, albums: {}}
if (!cachedAlbum){
cachedAlbum = await this.sp.getAlbum(album_id)
cachedAlbum = cachedAlbum.body
}
let dz_id = "0"
let dz_album = null
let upc = null
if (cachedAlbum.external_ids && cachedAlbum.external_ids.upc){
upc = cachedAlbum.external_ids.upc
try {
dz_album = await dz.api.get_album_by_UPC(upc)
} catch (e){
dz_album = null
}
if (!dz_album){
upc = ""+parseInt(upc)
try {
dz_album = await dz.api.get_album_by_UPC(upc)
} catch (e) {
dz_album = null
}
}
if (dz_album && dz_album.title && dz_album.id) dz_id = dz_album.id
}
cache.tracks[album_id] = {id: dz_id, upc: upc}
// Save cache
return dz_id
}
async checkCredentials(){
if (this.credentials.clientId === "" || this.credentials.clientSecret === ""){
this.enabled = false
return
}
this.sp = new SpotifyWebApi(this.credentials)
try {
const creds = await this.sp.clientCredentialsGrant()
this.sp.setAccessToken(creds.body.access_token)
this.enabled = true
} catch (e){
this.enabled = false
this.sp = undefined
}
}
getCredentials(){
return this.credentials
}
async setCredentials(newCredentials){
newCredentials.clientId = newCredentials.clientId.trim()
newCredentials.clientSecret = newCredentials.clientSecret.trim()
this.credentials = newCredentials
fs.writeFileSync(this.configFolder+'credentials.json', JSON.stringify(this.credentials))
await this.checkCredentials()
}
}
module.exports = Spotify

View file

@ -14,7 +14,8 @@
"crypto": "^1.0.1",
"deezer-js": "^0.0.8",
"got": "^11.8.2",
"metaflac-js2": "^1.0.7"
"metaflac-js2": "^1.0.7",
"spotify-web-api-node": "^5.0.2"
},
"devDependencies": {
"eslint": "^7.23.0"

145
yarn.lock
View file

@ -225,6 +225,14 @@ cacheable-request@^7.0.1:
normalize-url "^4.1.0"
responselike "^2.0.0"
call-bind@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
dependencies:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
@ -283,7 +291,7 @@ color-name@~1.1.4:
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
combined-stream@^1.0.6, combined-stream@~1.0.6:
combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
@ -295,11 +303,21 @@ commander@^3.0.1:
resolved "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz"
integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==
component-emitter@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
cookiejar@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
core-util-is@1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
@ -555,6 +573,11 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-safe-stringify@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743"
integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
@ -580,6 +603,15 @@ forever-agent@~0.6.1:
resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz"
@ -589,16 +621,35 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formidable@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.2.tgz#bf69aea2972982675f00865342b982986f6b8dd9"
integrity sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
get-intrinsic@^1.0.2:
version "1.1.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
has-symbols "^1.0.1"
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz"
@ -686,6 +737,18 @@ has-flag@^4.0.0:
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-symbols@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
http-cache-semantics@^4.0.0:
version "4.1.0"
resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz"
@ -884,6 +947,11 @@ metaflac-js2@^1.0.7:
commander "^3.0.1"
probe-image-size "^5.0.0"
methods@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
mime-db@1.47.0:
version "1.47.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz"
@ -896,6 +964,11 @@ mime-types@^2.1.12, mime-types@~2.1.19:
dependencies:
mime-db "1.47.0"
mime@^2.4.6:
version "2.5.2"
resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
mimic-response@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz"
@ -943,6 +1016,11 @@ oauth-sign@~0.9.0:
resolved "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
object-inspect@^1.9.0:
version "1.10.3"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
@ -1028,6 +1106,13 @@ punycode@^2.1.0, punycode@^2.1.1:
resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
qs@^6.9.4:
version "6.10.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
dependencies:
side-channel "^1.0.4"
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz"
@ -1038,6 +1123,15 @@ quick-lru@^5.1.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
dependencies:
inherits "^2.0.3"
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
regexpp@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz"
@ -1098,7 +1192,7 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@ -1108,7 +1202,7 @@ safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
semver@^7.2.1:
semver@^7.2.1, semver@^7.3.2:
version "7.3.5"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@ -1127,6 +1221,15 @@ shebang-regex@^3.0.0:
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
side-channel@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
dependencies:
call-bind "^1.0.0"
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"
slice-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz"
@ -1136,6 +1239,13 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
spotify-web-api-node@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/spotify-web-api-node/-/spotify-web-api-node-5.0.2.tgz#683669b3ccc046a5a357300f151df93a2b3539fe"
integrity sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA==
dependencies:
superagent "^6.1.0"
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
@ -1172,6 +1282,13 @@ string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.0"
string_decoder@^1.1.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies:
safe-buffer "~5.2.0"
strip-ansi@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz"
@ -1184,6 +1301,23 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
superagent@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-6.1.0.tgz#09f08807bc41108ef164cfb4be293cebd480f4a6"
integrity sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==
dependencies:
component-emitter "^1.3.0"
cookiejar "^2.1.2"
debug "^4.1.1"
fast-safe-stringify "^2.0.7"
form-data "^3.0.0"
formidable "^1.2.2"
methods "^1.1.2"
mime "^2.4.6"
qs "^6.9.4"
readable-stream "^3.6.0"
semver "^7.3.2"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz"
@ -1274,6 +1408,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
util-deprecate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
uuid@^3.3.2:
version "3.4.0"
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"