diff --git a/README.md b/README.md index d1e0abc..4f73929 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ -# TidalAPI +# TidalPromise -[![Build Status](https://img.shields.io/travis/lucaslg26/TidalAPI.svg)](https://travis-ci.org/lucaslg26/TidalAPI) [![npm version](http://img.shields.io/npm/v/tidalapi.svg)](https://npmjs.org/package/tidalapi) [![npm downloads](https://img.shields.io/npm/dm/tidalapi.svg)](https://npmjs.org/package/tidalapi) [![NPM](https://img.shields.io/npm/l/tidalapi.svg)](https://github.com/lucaslg26/TidalAPI/blob/master/LICENSE.md) [![David](https://img.shields.io/david/lucaslg26/TidalAPI.svg)](https://david-dm.org/lucaslg26/TidalAPI) +[![Build Status](https://img.shields.io/travis/deters/TidalPromise.svg)](https://travis-ci.org/deters/TidalPromise) [![npm version](http://img.shields.io/npm/v/deters.svg)](https://npmjs.org/package/tidalpromise) [![npm downloads](https://img.shields.io/npm/dm/deters.svg)](https://npmjs.org/package/tidalpromise) [![NPM](https://img.shields.io/npm/l/deters.svg)](https://github.com/deters/TidalPromise/blob/master/LICENSE.md) [![David](https://img.shields.io/david/lucaslg26/TidalAPI.svg)](https://david-dm.org/lucaslg26/TidalAPI) ## About -node.js TIDAL API. Use the TIDAL Web API simply using this module ;) +node.js TIDAL API, with Promises and playlist manipulation support. -Created by [Lucas Vasconcelos](https://github.com/lucaslg26) +Mantainer [Lucas Deters](https://github.com/deters) + +This is a fork of node.js Tidal API by [Lucas Vasconcelos](https://github.com/lucaslg26) **NOTE:** Currently not supporting facebook login. @@ -14,7 +16,7 @@ Created by [Lucas Vasconcelos](https://github.com/lucaslg26) Run the following: ``` javascript -npm install tidalapi +npm install tidalpromise ``` ## Obtain the Tidal token needed to use this API @@ -35,7 +37,7 @@ As well as a TIDAL username and password, the Tidal API needs an API `token` whi Simple usage searching and querying a track list ```javascript -var TidalAPI = require('tidalapi'); +var TidalAPI = require('tidalpromise'); var api = new TidalAPI({ username: 'your-username-here', @@ -49,43 +51,43 @@ var api = new TidalAPI({ ### Search ```javascript -api.search({type: 'artists', query: 'Dream Theater', limit: 1}, function(data){ +api.search({type: 'artists', query: 'Dream Theater', limit: 1}).then( function(data){ console.log(data.artists); -}) +}).catch(console.log) -api.search({type: 'albums', query: 'Dream Theater', limit: 1}, function(data){ +api.search({type: 'albums', query: 'Dream Theater', limit: 1}).then( function(data){ console.log(data.albums); -}) +}).catch(console.log) -api.search({type: 'tracks', query: 'Dream Theater', limit: 1}, function(data){ +api.search({type: 'tracks', query: 'Dream Theater', limit: 1}).then( function(data){ console.log(data.tracks); -}) +}).catch(console.log) -api.search({type: 'tracks,albums,artists', query: 'Dream Theater', limit: 1}, function(data){ +api.search({type: 'tracks,albums,artists', query: 'Dream Theater', limit: 1}).then( function(data){ console.log(data.tracks); console.log(data.albums); console.log(data.artists); -}) +}).catch(console.log) ``` ### Track info ```javascript -api.getTrackInfo({id: 22560696 }, function(data){ +api.getTrackInfo({id: 22560696 }).then( function(data){ console.log(data) -}) +}).catch(console.log) ``` ### Streams ```javascript -api.getStreamURL({id: 22560696}, function(data){ +api.getStreamURL({id: 22560696}).then( function(data){ console.log(data) -}) +}).catch(console.log) -api.getVideoStreamURL({id: 25470315}, function(data){ +api.getVideoStreamURL({id: 25470315}).then( function(data){ console.log(data) -}) +}).catch(console.log) ``` ### Album Art @@ -97,18 +99,80 @@ console.log(api.getArtURL('24f52ab0-e7d6-414d-a650-20a4c686aa57', 1280)) //cover ### Videos ```javascript -api.getArtistVideos({id: 14670, limit: 2}, function(data){ +api.getArtistVideos({id: 14670, limit: 2}).then( function(data){ console.log(data) -}) +}).catch(console.log) ``` ### FLAC tags ```javascript -api.genMetaflacTags({id: 22560696, coverPath: './albumart.jpg', songPath: './song.flac'}, function(data){ +api.genMetaflacTags({id: 22560696, coverPath: './albumart.jpg', songPath: './song.flac'}).then( function(data){ console.log(data) /* --remove-all-tags --set-tag="ARTIST=Dream Theater" --set-tag="TITLE=Along For The Ride" --set-tag="ALBUM=Dream Theater" --set-tag="TRACKNUMBER=8" --set-tag="COPYRIGHT=2013 Roadrunner Records, Inc." -set-tag="DATE=2013" --import-picture-from="./albumart.jpg" "./song.flac" --add-replay-gain */ -}) +}).catch(console.log) +``` + +### Playlist manipulation (example using async/await for clarity) + +```javascript +// get the id of the current user +let user_id = await api.getMyID(); +console.log(user_id); + +// query more information about current user +let user = await api.getUser({id: user_id}); +console.log(user); + +// get all playlists of current user +let result = await api.getPlaylists({id: user_id}); +let current_playlists = result.items; +let playlists_resume = current_playlists.map(playlist => playlist.title).filter((value, index) => index < 5); +console.log('listing first 5 playlists of current user: \n',playlists_resume) + +// search some musics +let track_results = await api.search({type: 'tracks', query: 'The Beatles', limit: 10}); +let beatles_tracks = track_results.tracks.items; +let beatles_tracks_resume = beatles_tracks.map(playlist => playlist.title); +console.log('list of beatles tracks:',beatles_tracks_resume); + +// lookup for a specific playlist +let TEST_PLAYLIST = 'Test playlist' +let filtered_playlists = current_playlists.filter( playlist => playlist.title == TEST_PLAYLIST ); +let test_playlist = filtered_playlists[0] + +// create a new playlist if necessary +if (test_playlist == undefined){ + test_playlist = await api.createPlaylist({title: TEST_PLAYLIST, description: 'Automatically created playlist.'}); + console.log('created a new playlist.') +} +console.log('test playlist uuid is:' + test_playlist.uuid) + +// add musics to the beginning of the playlist +result = await api.addPlaylistTracks({playlist: test_playlist, tracks: beatles_tracks, toIndex: 0}); +console.log(beatles_tracks.length + ' tracks added to playlist ', result); + +// get tracks in the playlist +result = await api.getPlaylistTracks({playlist: test_playlist}) +let musics_on_test_playlist = result.items; +let number_of_musics_on_test_playlist = result.totalNumberOfItems; +console.log('Musics on the playlist now : \n',number_of_musics_on_test_playlist); + +// we added new music to the beginning. so we can delete old musics from the end. +let number_of_musics_to_keep = beatles_tracks.length; +let number_of_musics_to_delete = number_of_musics_on_test_playlist - number_of_musics_to_keep; +let positions_to_delete = [...Array(number_of_musics_to_delete).keys()].map(v => v+number_of_musics_to_keep); +result = await api.deletePlaylistTracks({playlist: test_playlist, trackPositions: positions_to_delete}); +console.log(number_of_musics_to_delete + ' tracks deleted from the playlist'); + +// get tracks in the playlist again +result = await api.getPlaylistTracks({playlist: test_playlist}) +musics_on_test_playlist = result.items; +number_of_musics_on_test_playlist = result.totalNumberOfItems; +console.log('Musics on the playlist now : \n',number_of_musics_on_test_playlist); + +result = await api.deletePlaylist(test_playlist); +console.log('Playlist deleted. \n') ``` ## Troubleshooting diff --git a/examples/.config.json b/examples/.config.json new file mode 100644 index 0000000..7b0ebd4 --- /dev/null +++ b/examples/.config.json @@ -0,0 +1,8 @@ +{ + "tidal": { + "username": "", + "password": "", + "token": "", + "quality": "LOSSLESS" + } +} diff --git a/examples/playlists.js b/examples/playlists.js new file mode 100644 index 0000000..f743539 --- /dev/null +++ b/examples/playlists.js @@ -0,0 +1,72 @@ +let TidalClient = require('../'); +let credentials = require('./.config.json'); + +let api = new TidalClient(credentials.tidal); + +async function test_playlists(delete_playlist_after_testing) { + + // get the id of the current user + let user_id = await api.getMyID(); + console.log(user_id); + + // query more information about current user + let user = await api.getUser({id: user_id}); + console.log(user); + + // get all playlists of current user + let result = await api.getPlaylists({id: user_id}); + let current_playlists = result.items; + let playlists_resume = current_playlists.map(playlist => playlist.title).filter((value, index) => index < 5); + console.log('listing first 5 playlists of current user: \n',playlists_resume) + + // search some musics + let track_results = await api.search({type: 'tracks', query: 'The Beatles', limit: 10}); + let beatles_tracks = track_results.tracks.items; + let beatles_tracks_resume = beatles_tracks.map(playlist => playlist.title); + console.log('list of beatles tracks:',beatles_tracks_resume); + + // lookup for a specific playlist + let TEST_PLAYLIST = 'Test playlist' + let filtered_playlists = current_playlists.filter( playlist => playlist.title == TEST_PLAYLIST ); + let test_playlist = filtered_playlists[0] + + // create a new playlist if necessary + if (test_playlist == undefined){ + test_playlist = await api.createPlaylist({title: TEST_PLAYLIST, description: 'Automatically created playlist.'}); + console.log('created a new playlist.') + } + console.log('test playlist uuid is:' + test_playlist.uuid) + + // add musics to the beginning of the playlist + result = await api.addPlaylistTracks({playlist: test_playlist, tracks: beatles_tracks, toIndex: 0}); + console.log(beatles_tracks.length + ' tracks added to playlist ', result); + + // get tracks in the playlist + result = await api.getPlaylistTracks({playlist: test_playlist}) + let musics_on_test_playlist = result.items; + let number_of_musics_on_test_playlist = result.totalNumberOfItems; + console.log('Musics on the playlist now : \n',number_of_musics_on_test_playlist); + + // we added new music to the beginning. so we can delete old musics from the end. + let number_of_musics_to_keep = beatles_tracks.length; + let number_of_musics_to_delete = number_of_musics_on_test_playlist - number_of_musics_to_keep; + let positions_to_delete = [...Array(number_of_musics_to_delete).keys()].map(v => v+number_of_musics_to_keep); + result = await api.deletePlaylistTracks({playlist: test_playlist, trackPositions: positions_to_delete}); + console.log(number_of_musics_to_delete + ' tracks deleted from the playlist'); + + // get tracks in the playlist again + result = await api.getPlaylistTracks({playlist: test_playlist}) + musics_on_test_playlist = result.items; + number_of_musics_on_test_playlist = result.totalNumberOfItems; + console.log('Musics on the playlist now : \n',number_of_musics_on_test_playlist); + + if (delete_playlist_after_testing) { + result = await api.deletePlaylist(test_playlist); + console.log('Playlist deleted. \n') + } + +} + +let DELETE_PLAYLIST_AFTER_TEST = true; + +test_playlists(DELETE_PLAYLIST_AFTER_TEST); diff --git a/examples/search.js b/examples/search.js index f95bb8c..bd232a9 100644 --- a/examples/search.js +++ b/examples/search.js @@ -1,50 +1,53 @@ -var TidalAPI = require('../'); +let TidalClient = require('../'); +let credentials = require('./.config.json'); +let api = new TidalClient(credentials.tidal); -var api = new TidalAPI({ - username: '', - password: '', - token: '_KM2HixcUBZtmktH', - clientVersion: '2.2.1--7', - quality: 'LOSSLESS' -}); +async function test_search(playlist_name, query_musics) { -api.search({type: 'artists', query: 'Dream Theater', limit: 1}, function(data){ - console.log(data.artists); -}) -api.search({type: 'albums', query: 'Dream Theater', limit: 1}, function(data){ - console.log(data.albums); -}) + api.search({type: 'artists', query: 'Dream Theater', limit: 1}).then( data => { + console.log(data.artists); + }) -api.search({type: 'tracks', query: 'Dream Theater', limit: 1}, function(data){ - console.log(data.tracks); -}) + api.search({type: 'albums', query: 'Dream Theater', limit: 1}).then( data => { + console.log(data.albums); + }) -api.search({type: 'tracks,albums,artists', query: 'Dream Theater', limit: 1}, function(data){ - console.log(data.tracks); - console.log(data.albums); - console.log(data.artists); -}) + api.search({type: 'tracks', query: 'Dream Theater', limit: 1}).then( data => { + console.log(data.tracks); + }) -api.getTrackInfo({id: 22560696 }, function(data){ - console.log(data) -}) + api.search({type: 'tracks,albums,artists', query: 'Dream Theater', limit: 1}).then( data => { + console.log(data.tracks); + console.log(data.albums); + console.log(data.artists); + }) -api.getStreamURL({id: 22560696}, function(data){ - console.log(data) -}) + api.getTrackInfo({id: 22560696 }).then( data => { + console.log(data) + }) -api.getVideoStreamURL({id: 25470315}, function(data){ - console.log(data) -}) + api.getStreamURL({id: 22560696}).then( data => { + console.log(data) + }) -console.log(api.getArtURL('24f52ab0-e7d6-414d-a650-20a4c686aa57', 1280)) //coverid + api.getVideoStreamURL({id: 25470315}).then( data => { + console.log(data) + }) -api.getArtistVideos({id: 14670, limit: 2}, function(data){ - console.log(data) -}) + console.log(api.getArtURL('24f52ab0-e7d6-414d-a650-20a4c686aa57', 1280)) //coverid -api.genMetaflacTags({id: 22560696, coverPath: './albumart.jpg', songPath: './song.flac'}, function(data){ - console.log(data) -}) + api.getArtistVideos({id: 14670, limit: 2}).then( data => { + console.log(data) + }) + + api.genMetaflacTags({id: 22560696, coverPath: './albumart.jpg', songPath: './song.flac'}).then( data => { + console.log(data) + }) + + + +} + +test_search(); diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..dfd454a --- /dev/null +++ b/lib/api.js @@ -0,0 +1,593 @@ +'use strict'; + +//Node.js modules + +var request = require('request-promise-native').defaults({ + baseUrl: 'https://api.tidalhifi.com/v1' +}); + +var Semaphore = require('semaphore'); + +/** + * Package.json of TidalAPI + * @type {exports} + * @private + */ +var TidalAPIInfo = require('../package.json'); + + +/** + * Authentication information (username and password) + * @type {Object} + * @private + */ +var authInfo; + +/** + * TIDAL API Session ID + * @type {null|String} + * @private + */ +var _sessionID = null; + +/** + * TIDAL API Country code + * @type {null|String} + * @private + */ +var _countryCode = null; + +/** + * TIDAL API User ID + * @type {null|String} + * @private + */ +var _userID = null; + +/** + * TIDAL API stream quality + * @type {null|String} + * @private + */ +var _streamQuality = null; + +/** + * api logged in + * @type {null|String} + */ +var loggedIn = false; + +/** + * authData + * @type {Object} + */ +var authData = {}; + +/** + * Create TidalAPI instance + * @param {{username: String, password: String, token: String, quality: String, requests_per_secound: Number}} + * @Constructor + */ + +function TidalAPI(authData) { + if (typeof authData !== 'object') { + throw new Error('You must pass auth data into the TidalAPI object correctly'); + } else { + if (typeof authData.username !== 'string') { + throw new Error('Username invalid or missing'); + } + if (typeof authData.password !== 'string') { + throw new Error('Password invalid or missing'); + } + if (typeof authData.token !== 'string') { + throw new Error('Token invalid or missing'); + } + if (typeof authData.quality !== 'string') { + throw new Error('Stream quality invalid or missing'); + } + + } + + this.authData = authData; + this.sem1 = Semaphore(authData.requests_per_secound || 10); + + /* try log in */ + // tryLogin(authData); +} + +/** + * Try login using credentials. + * @param {{username: String, password: String}} + */ +function tryLogin(authInfo) { + /** + * Logging? + * @type {boolean} + */ + var loggingIn = true; + return request({ + method: 'POST', + uri: '/login/username', + headers: { + 'X-Tidal-Token': authInfo.token + }, + form: { + username: authInfo.username, + password: authInfo.password, + clientUniqueKey: "vjknfvjbnjhbgjhbbg" + } + }).then((data) => { + data = JSON.parse(data); + _sessionID = data.sessionId; + _userID = data.userId; + _countryCode = data.countryCode; + _streamQuality = authInfo.quality; + loggingIn = false; + loggedIn = true; + return data; + }); +} +/** + * Return userID. + */ +TidalAPI.prototype.getMyID = function() { + + var self = this; + + return new Promise((resolve, reject) => { + + if (!loggedIn) { + return tryLogin(this.authData).then(() => { + resolve(_userID); + }).catch(reject); + } else { + resolve(_userID); + } + }); + +} +/** + * Global search. + * @param {{query: String, limit: Number, types: String, offset: Number}} + */ +TidalAPI.prototype.search = function(query) { + var self = this; + return self._baseRequest('/search', { + query: query.query || query, + limit: query.limit || 999, + types: query.type || 'ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS', + offset: query.offset || 0, + countryCode: _countryCode + }, 'search'); +} +/** + * Get artist info. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getArtist = function(query) { + var self = this; + return self._baseRequest('/artists/' + (query.id || query), { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'artist'); +} +/** + * Get artist top tracks. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getTopTracks = function(query) { + var self = this; + return self._baseRequest('/artists/' + (query.id || query) + '/toptracks', { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'toptracks'); +} +/** + * Get artist videos. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getArtistVideos = function(query) { + var self = this; + return self._baseRequest('/artists/' + (query.id || query) + '/videos', { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'videos'); +} +/** + * Get artist bio. + * @param {{id: Number}} + */ +TidalAPI.prototype.getArtistBio = function(query) { + var self = this; + return self._baseRequest('/artists/' + (query.id || query) + '/bio', { + countryCode: _countryCode + }, 'bio'); +} +/** + * Get similar artists. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getSimilarArtists = function(query) { + var self = this; + return self._baseRequest('/artists/' + (query.id || query) + '/similar', { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'similar'); +} +/** + * Get artist albums. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getArtistAlbums = function(query) { + var self = this; + return self._baseRequest('/artists/' + (query.id || query) + '/albums', { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'albums'); +} +/** + * Get album info. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getAlbum = function(query) { + var self = this; + return self._baseRequest('/albums/' + (query.id || query), { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'album'); +} +/** + * Get album tracks. + * @param {{id: Number, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getAlbumTracks = function(query) { + var self = this; + return self._baseRequest('/albums/' + (query.id || query) + '/tracks', { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'albums'); +} +/** + * Get playlist info. + * @param {{uuid: String, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getPlaylist = function(query) { + var self = this; + return self._baseRequest('/playlists/' + query.uuid, { + countryCode: _countryCode + }); +} +/** + * Get tracks from a playlist. + * @param {{playlist: { uuid : String }, limit: Number, filter: String, offset: Number}} + */ +TidalAPI.prototype.getPlaylistTracks = function(query) { + var self = this; + return self._baseRequest('/playlists/' + (query.playlist.uuid || query) + '/tracks', { + limit: query.limit || 999, + filter: query.filter || 'ALL', + offset: query.offset || 0, + countryCode: _countryCode + }, 'albums'); +} + +/** + * Create playlist. + * @param {{title: String, description: String}} + */ +TidalAPI.prototype.createPlaylist = function(data) { + var self = this; + return this.getMyID().then(id => { + + return self._basePost('/users/' + id + '/playlists', { + countryCode: _countryCode + }, { + title: data.title, + description: data.description + }).then(JSON.parse); + + + }) +} + +/** + * Delete a playlist + * @param {{uuid : String}} + */ +TidalAPI.prototype.deletePlaylist = function(data) { + var self = this; + return self._baseDelete('/playlists/' + data.uuid, { + countryCode: _countryCode + }, {}); +} + +/** + * Add tracks to a playlist + * @param {{playlist: {uuid: String}, tracks: {id : Number}, toIndex: Number}} + */ +TidalAPI.prototype.addPlaylistTracks = function(data) { + var self = this; + + if (data.tracks.length == 0) { + return; + } + + return self._basePost('/playlists/' + data.playlist.uuid + '/items', { + countryCode: _countryCode + }, { + trackIds: data.tracks.map(v => v.id).join(','), + toIndex: data.toIndex || 0 + }); +} + +/** + * Delete tracks from a playlist, based on their positions + * @param {{playlist: {uuid : String}, trackPositions: List, orderDirection: String, order: String}} + */ +TidalAPI.prototype.deletePlaylistTracks = function(data) { + var self = this; + + if (data.trackPositions == undefined || data.trackPositions.length == 0) { + return; + } + + + return self._baseDelete('/playlists/' + data.playlist.uuid + '/items/' + (data.trackPositions.join(',')), { + countryCode: _countryCode, + orderDirection: data.orderDirection || 'ASC', + order: data.order || 'INDEX' + }, {}); +} + +/** + * Get track info. + * @param {{id: Number, quality: String}} + */ +TidalAPI.prototype.getTrackInfo = function(track) { + var self = this; + return self._baseRequest('/tracks/' + (track.id || track), { + countryCode: _countryCode + }, 'trackInfo'); +} +/** + * Get track stream URL. + * @param {{id: Number, quality: String}} + */ +TidalAPI.prototype.getStreamURL = function(track) { + var self = this; + return self._baseRequest('/tracks/' + (track.id || track) + '/streamUrl', { + soundQuality: track.quality || _streamQuality, + countryCode: _countryCode + }, 'streamURL'); +} +/** + * Get track stream URL. + * @param {{id: Number, quality: String}} + */ +TidalAPI.prototype.getOfflineURL = function(track) { + var self = this; + return self._baseRequest('/tracks/' + (track.id || track) + '/offlineUrl', { + soundQuality: track.quality || _streamQuality, + countryCode: _countryCode + }, 'streamURL'); +} +/** + * Get video stream URL. + * @param {{id: Number}} + */ +TidalAPI.prototype.getVideoStreamURL = function(track) { + var self = this; + return self._baseRequest('/videos/' + (track.id || track) + '/streamUrl', { + countryCode: _countryCode + }, 'streamURL'); +} +/** + * Get user info. + * @param {{id: Number}} + */ +TidalAPI.prototype.getUser = function(user) { + var self = this; + return this.getMyID().then(id => { + return self._baseRequest('/users/' + (id || user), { + limit: user.limit || 999, + offset: user.offset || 0 + }, 'user'); + }) +} + +/** + * Get user playlists. + * @param {{id: Number}} + */ +TidalAPI.prototype.getPlaylists = function(user) { + var self = this; + return this.getMyID().then(id => { + return self._baseRequest('/users/' + (id || user) + "/playlists", { + limit: user.limit || 999, + offset: user.offset || 0, + countryCode: _countryCode + }); + }) +} + +/** + * Get track stream URL. + * @param {id: String, res: Number} + */ + +TidalAPI.prototype.getArtURL = function(id, width, height) { + width = width || 1280; + height = height || 1280; + return 'https://resources.tidal.com/images/' + id.replace(/-/g, '/') + '/' + width + 'x' + height + '.jpg'; +} +/** + * Generate Metaflac tags. + * @param {{id: Number}} + */ +TidalAPI.prototype.genMetaflacTags = function(track) { + var self = this; + return self.getTrackInfo({ + id: track.id || track + }).then((data) => { + self.getAlbum({ + id: data.album.id + }).then((albumData) => { + var metaflacTag; + metaflacTag = '--remove-all-tags '; + metaflacTag += '--set-tag=\"ARTIST=' + data.artist.name + '\" '; + metaflacTag += '--set-tag=\"TITLE=' + data.title + '\" '; + metaflacTag += '--set-tag=\"ALBUM=' + data.album.title + '\" '; + metaflacTag += '--set-tag=\"TRACKNUMBER=' + data.trackNumber + '\" '; + metaflacTag += '--set-tag=\"COPYRIGHT=' + data.copyright + '\" '; + metaflacTag += '-set-tag="DATE=' + albumData.releaseDate.split("-")[0] + '" '; + if (track.coverPath) { + metaflacTag += '--import-picture-from=' + '\"' + track.coverPath + '\" '; + } + if (track.songPath) { + metaflacTag += '\"' + track.songPath + '\" '; + } + metaflacTag += '--add-replay-gain'; + return metaflacTag; + }) + }); +} +/** + * Base request function. + * @param {{method: String, params: Object, type: String}} + */ +TidalAPI.prototype._baseRequest = function(method, params, type) { + var self = this; + + if (!loggedIn) { + return tryLogin(this.authData).then(() => { + + return self._baseRequest(method, params, type); + }); + } + + this.sem1.take(() => { + setTimeout(this.sem1.leave, 1000); + }); + + params.countryCode = params.countryCode ? params.countryCode : _countryCode; + + return request.get({ + uri: method, + headers: { + 'Origin': 'http://listen.tidal.com', + 'X-Tidal-SessionId': _sessionID, + 'Pragma': 'no-cache' + }, + qs: params + }).then((data) => { + let body = JSON.parse(data); + if (params.types) { + var newBody = {}; + if (params.types.indexOf('tracks') > -1) { + newBody['tracks'] = body.tracks; + } + if (params.types.indexOf('artists') > -1) { + newBody['artists'] = body.artists; + } + if (params.types.indexOf('albums') > -1) { + newBody['albums'] = body.albums; + } + if (params.types.indexOf('videos') > -1) { + newBody['videos'] = body.videos; + } + if (params.types.indexOf('playlists') > -1) { + newBody['playlists'] = body.playlists; + } + return newBody; + } else { + return body; + } + }) + +} + +/** + * Base delete function. + * @param {{method: String, params: Object, type: String}} + */ +TidalAPI.prototype._baseDelete = function(method, params, formdata) { + var self = this; + + if (!loggedIn) { + + return tryLogin(this.authData) + .then(() => { + return self._baseDelete(method, params, formdata, type); + }) + + } + + params.countryCode = params.countryCode ? params.countryCode : _countryCode; + + this.sem1.take(() => { + setTimeout(this.sem1.leave, 1000); + }); + + return request({ + method: 'DELETE', + uri: method, + headers: { + 'Origin': 'https://listen.tidal.com', + 'If-None-Match': '"*"', + 'X-Tidal-SessionId': _sessionID + }, + form: formdata + }) +} + +/** + * Base post function. + * @param {{method: String, params: Object}} + */ +TidalAPI.prototype._basePost = function(method, params, formdata) { + + var self = this; + + if (!loggedIn) { + + return tryLogin(this.authData) + .then(() => { + return self._basePost(method, params, formdata); + }) + + } + + params.countryCode = params.countryCode ? params.countryCode : _countryCode; + + this.sem1.take(() => { + setTimeout(this.sem1.leave, 1000); + }); + + return request({ + method: 'POST', + uri: method, + headers: { + 'Origin': 'https://listen.tidal.com', + 'If-None-Match': '"*"', + 'X-Tidal-SessionId': _sessionID + }, + form: formdata + }) + +} + +module.exports = TidalAPI; diff --git a/lib/client.js b/lib/client.js deleted file mode 100644 index 724be1d..0000000 --- a/lib/client.js +++ /dev/null @@ -1,432 +0,0 @@ -'use strict'; - - -//Node.js modules - -var request = require('request').defaults({ - baseUrl: 'https://api.tidalhifi.com/v1' -}); - -/** - * Package.json of TidalAPI - * @type {exports} - * @private - */ -var TidalAPIInfo = require('../package.json'); - - -/** - * Authentication information (username and password) - * @type {Object} - * @private - */ -var authInfo; - -/** - * TIDAL API Session ID - * @type {null|String} - * @private - */ -var _sessionID = null; - -/** - * TIDAL API Country code - * @type {null|String} - * @private - */ -var _countryCode = null; - -/** - * TIDAL API User ID - * @type {null|String} - * @private - */ -var _userID = null; - -/** - * TIDAL API stream quality - * @type {null|String} - * @private - */ -var _streamQuality = null; - -/** - * api logged in - * @type {null|String} - */ -var loggedIn = false; - -/** - * authData - * @type {Object} - */ -var authData = {}; - -/** -* Create TidalAPI instance -* @param {{username: String, password: String, token: String, quality: String}} -* @Constructor -*/ - -function TidalAPI(authData) { - if(typeof authData !== 'object') - { - throw new Error('You must pass auth data into the TidalAPI object correctly'); - } else { - if(typeof authData.username !== 'string') { - throw new Error('Username invalid or missing'); - } - if(typeof authData.password !== 'string') { - throw new Error('Password invalid or missing'); - } - if(typeof authData.token !== 'string') { - throw new Error('Token invalid or missing'); - } - if(typeof authData.quality !== 'string') { - throw new Error('Stream quality invalid or missing'); - } - } - - this.authData = authData; - - /* try log in */ - // tryLogin(authData); -} - -/** -* Try login using credentials. -* @param {{username: String, password: String}} -*/ -function tryLogin(authInfo, cb) { - /** - * Logging? - * @type {boolean} - */ - var loggingIn = true; - request({ - method: 'POST', - uri: '/login/username', - headers: { - 'X-Tidal-Token': authInfo.token - }, - form: { - username: authInfo.username, - password: authInfo.password, - clientUniqueKey: "vjknfvjbnjhbgjhbbg" - } - }, function(err, res, data) { - if(!err){ - if (data && res && res.statusCode !== 200 || err) { - throw new Error(data) - } - data = JSON.parse(data); - _sessionID = data.sessionId; - _userID = data.userId; - _countryCode = data.countryCode; - _streamQuality = authInfo.quality; - loggingIn = false; - loggedIn = true; - if (cb) { - cb(); - } - } - }); -} -/** -* Return userID. -*/ -TidalAPI.prototype.getMyID = function () { - return _userID; -} -/** -* Global search. -* @param {{query: String, limit: Number, types: String, offset: Number}} -*/ -TidalAPI.prototype.search = function (query, callback) { - var self = this; - self._baseRequest('/search', { - query: query.query || query, - limit: query.limit || 999, - types: query.type || 'ARTISTS,ALBUMS,TRACKS,VIDEOS,PLAYLISTS', - offset: query.offset || 0, - countryCode: _countryCode - }, 'search', callback); -} -/** -* Get artist info. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getArtist = function (query, callback) { - var self = this; - self._baseRequest('/artists/' + (query.id || query), { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'artist', callback); -} -/** -* Get artist top tracks. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getTopTracks = function (query, callback) { - var self = this; - self._baseRequest('/artists/' + (query.id || query) + '/toptracks', { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'toptracks', callback); -} -/** -* Get artist videos. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getArtistVideos = function (query, callback) { - var self = this; - self._baseRequest('/artists/' + (query.id || query) + '/videos', { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'videos', callback); -} -/** -* Get artist bio. -* @param {{id: Number}} -*/ -TidalAPI.prototype.getArtistBio = function (query, callback) { - var self = this; - self._baseRequest('/artists/' + (query.id || query) + '/bio', { - countryCode: _countryCode - }, 'bio', callback); -} -/** -* Get similar artists. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getSimilarArtists = function (query, callback) { - var self = this; - self._baseRequest('/artists/' + (query.id || query) + '/similar', { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'similar', callback); -} -/** -* Get artist albums. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getArtistAlbums = function (query, callback) { - var self = this; - self._baseRequest('/artists/' + (query.id || query) + '/albums', { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'albums', callback); -} -/** -* Get album info. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getAlbum = function (query, callback) { - var self = this; - self._baseRequest('/albums/' + (query.id || query), { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'album', callback); -} -/** -* Get album tracks. -* @param {{id: Number, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getAlbumTracks = function (query, callback) { - var self = this; - self._baseRequest('/albums/' + (query.id || query) + '/tracks', { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'albums', callback); -} -/** -* Get playlist info. -* @param {{id: String, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getPlaylist = function (query, callback){ - var self = this; - self._baseRequest('/playlists/' + (query.id || query), { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'album', callback); -} -/** -* Get tracks from a playlist. -* @param {{id: String, limit: Number, filter: String, offset: Number}} -*/ -TidalAPI.prototype.getPlaylistTracks = function (query, callback){ - var self = this; - self._baseRequest('/playlists/' + (query.id || query) + '/tracks', { - limit: query.limit || 999, - filter: query.filter || 'ALL', - offset: query.offset || 0, - countryCode: _countryCode - }, 'albums', callback); -} -/** -* Get track info. -* @param {{id: Number, quality: String}} -*/ -TidalAPI.prototype.getTrackInfo = function (track, callback){ - var self = this; - self._baseRequest('/tracks/' + (track.id || track), { - countryCode: _countryCode - }, 'trackInfo', callback); -} -/** -* Get track stream URL. -* @param {{id: Number, quality: String}} -*/ -TidalAPI.prototype.getStreamURL = function (track, callback) { - var self = this; - self._baseRequest('/tracks/' + (track.id || track) + '/streamUrl', { - soundQuality: track.quality || _streamQuality, - countryCode: _countryCode - }, 'streamURL', callback); -} -/** -* Get track stream URL. -* @param {{id: Number, quality: String}} -*/ -TidalAPI.prototype.getOfflineURL = function (track, callback) { - var self = this; - self._baseRequest('/tracks/' + (track.id || track) + '/offlineUrl', { - soundQuality: track.quality || _streamQuality, - countryCode: _countryCode - }, 'streamURL', callback); -} -/** -* Get video stream URL. -* @param {{id: Number}} -*/ -TidalAPI.prototype.getVideoStreamURL = function (track, callback) { - var self = this; - self._baseRequest('/videos/' + (track.id || track) + '/streamUrl', { - countryCode: _countryCode - }, 'streamURL', callback); -} -/** -* Get user info. -* @param {{id: Number}} -*/ -TidalAPI.prototype.getUser = function (user, callback) { - var self = this; - self._baseRequest('/users/' + (user.id || user), { - limit: user.limit || 999, - offset: user.offset || 0 - }, 'user', callback); -} -/** - * Get user playlists. - * @param {{id: Number}} - */ -TidalAPI.prototype.getPlaylists = function (user, callback) { - var self = this; - self._baseRequest('/users/' + (user.id || user) + "/playlists", { - limit: user.limit || 999, - offset: user.offset || 0, - countryCode: _countryCode - }, 'userPlaylists', callback); -} - -/** -* Get track stream URL. -* @param {id: String, res: Number} -*/ - -TidalAPI.prototype.getArtURL = function(id, width, height) { - width = width || 1280; - height = height || 1280; - return 'https://resources.tidal.com/images/' + id.replace(/-/g, '/') + '/' + width + 'x' + height + '.jpg'; -} -/** -* Generate Metaflac tags. -* @param {{id: Number}} -*/ -TidalAPI.prototype.genMetaflacTags = function(track, callback) { - var self = this; - self.getTrackInfo({id: track.id || track}, function(data) { - self.getAlbum({id: data.album.id }, function(albumData) { - var metaflacTag; - metaflacTag = '--remove-all-tags '; - metaflacTag += '--set-tag=\"ARTIST=' + data.artist.name + '\" '; - metaflacTag += '--set-tag=\"TITLE=' + data.title + '\" '; - metaflacTag += '--set-tag=\"ALBUM=' + data.album.title + '\" '; - metaflacTag += '--set-tag=\"TRACKNUMBER=' + data.trackNumber + '\" '; - metaflacTag += '--set-tag=\"COPYRIGHT=' + data.copyright + '\" '; - metaflacTag += '-set-tag="DATE=' + albumData.releaseDate.split("-")[0] + '" '; - if(track.coverPath){ - metaflacTag += '--import-picture-from=' + '\"' + track.coverPath + '\" '; - } - if(track.songPath){ - metaflacTag += '\"' + track.songPath + '\" '; - } - metaflacTag += '--add-replay-gain'; - callback(metaflacTag); - }) - }); -} -/** -* Base request function. -* @param {{method: String, params: Object, type: String, callback: Function}} -*/ -TidalAPI.prototype._baseRequest = function(method, params, type, callback) { - var self = this; - - if (!loggedIn) { - return tryLogin(this.authData, function() { - self._baseRequest(method, params, type, callback); - }); - } - - params.countryCode = params.countryCode ? params.countryCode : _countryCode; - - request.get({ - uri: method, - headers: { - 'Origin': 'http://listen.tidal.com', - 'X-Tidal-SessionId': _sessionID - }, - qs: params - }, function(err, res, body) { - body = JSON.parse(body); - if(params.types) { - var newBody = {}; - if(params.types.indexOf('tracks') > -1) { - newBody['tracks'] = body.tracks; - } - if(params.types.indexOf('artists') > -1) { - newBody['artists'] = body.artists; - } - if(params.types.indexOf('albums') > -1) { - newBody['albums'] = body.albums; - } - if(params.types.indexOf('videos') > -1) { - newBody['videos'] = body.videos; - } - if(params.types.indexOf('playlists') > -1) { - newBody['playlists'] = body.playlists; - } - callback(newBody); - } else - callback(body); - }); -} - -module.exports = TidalAPI; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8087a7b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,525 @@ +{ + "name": "tidalpromise", + "version": "0.2.11", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ajv": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", + "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "psl": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.3.0.tgz", + "integrity": "sha512-avHdspHO+9rQTLbv1RO+MPYeP/SzsCoxofjVnHanETfQhTJrmB0HlDoW+EiN/R+C0BZ+gERab9NY0lPN2TxNag==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semaphore": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz", + "integrity": "sha512-O4OZEaNtkMd/K0i6js9SL+gqy0ZCBMgUvlSqHKi4IBdjhe7wB8pwztUk1BbZ1fmrvpwFrPbHzqd2w5pTcJH6LA==" + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json index ce4619a..9955cae 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,14 @@ { - "name": "tidalapi", - "version": "0.2.3", - "description": "Unofficial Node.js TIDAL API", - "main": "./lib/client.js", + "name": "tidalpromise", + "version": "0.2.11", + "description": "Unofficial Node.js TIDAL Promise API", + "main": "./lib/api.js", "scripts": { "test": "mocha" }, "repository": { "type": "git", - "url": "git+https://github.com/lucaslg26/TidalAPI.git" + "url": "git+https://github.com/deters/TidalPromise.git" }, "keywords": [ "node", @@ -17,14 +17,16 @@ "music", "flac" ], - "author": "Lucas Vasconcelos (lucaslg26@gmail.com)", + "author": "Lucas Deters (lucasdeters@gmail.com)", "license": "MIT", "bugs": { - "url": "https://github.com/lucaslg26/TidalAPI/issues" + "url": "https://github.com/deters/TidalPromise/issues" }, - "homepage": "https://github.com/lucaslg26/TidalAPI#readme", + "homepage": "https://github.com/deters/TidalPromise#readme", "dependencies": { - "request": "^2.69.0" + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "semaphore": "^1.1.0" }, "devDependencies": { "mocha": "^2.4.5"