├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── docs └── api.md ├── package-lock.json ├── package.json ├── src └── index.js └── test ├── album.js ├── artist.js ├── class.js ├── image.js ├── login.js ├── playlist.js ├── search.js └── track.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015","stage-0"], 3 | "plugins": ["transform-runtime", "add-module-exports"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "rules": { 9 | "padded-blocks": 0, 10 | "import/no-extraneous-dependencies": 0, 11 | "class-methods-use-this": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | out/ 3 | coverage/ 4 | .env 5 | docs.js 6 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | out/ 3 | coverage/ 4 | test/ 5 | docs/ 6 | .eslintrc 7 | .babelrc 8 | .gitignore 9 | .travis.yml 10 | .env 11 | docs.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4' 4 | - node 5 | script: npm run-script test:cover 6 | after_script: cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 7 | env: 8 | global: 9 | - secure: GMiDv6PVRI45p/0v5FPzIxaQps+XMhErd33T5lfeYrkhp9MLxQPvCvc3V4oHs+e1hxNtSX8zOtuuAgoGPEVLyW3Fv29TYY4Ct+aPh7o2dJe6w/8MOEvuH28pOZIZMvvY3giN4PxCg5BsU07P2vEJFMleLqMYxZ/sERf9cuh/VeP6SYOBfNu8eBbi3rUcBizj/FxScstKhIfrmFAl6Xk6sJcexi4AWOcD0e7a9uHmuA0igrtOwZO8hWSH+tguCE8tbGf6s2/HEW+h9Hb2PC6FQNgtRy+BhxdLzY4uzN7UPuNC7DvTU4Le2J+ECzyXeC238PS3M/mdcvxK4SrwZ9IOg+hbhWCxnJtZh0yNuaDzuv5boCyrhCpu0sk5T3JIK+Yq7gz3qz79hsK8VzfFIuTWvSGleOriurNH1NbTwwoXL2jAPLZOQC0ggUp4pKDrIwivYCbL8mKdT2zKVn2RPIN/achSBbV0D9D9iDwtAjDDHhwNBvyhJwLYakbiNhRKW0CCiXEumE92P00cvSySjkCePcl821KdVXTGiEXIHyPvQKDkTbR1mWpf6EMIb0zjuS0+eDcdUb2irBuDFB3vqWQnS+CnedokWnFqb+Txdmf5mZA65Gr7DVTZPg6NC6PpiT0yTFmsQl2l7bxiObXJm12AMF4vHqCbJovADTjLodcDhjI= 10 | - secure: xb/69+bDQhx8FBIEHGfWlFTnZ7Kk1UzZ5nWXavdOwECf/v09sbt5dabbvKuQ2YgyIh/srPTKnOQH35j6C7mva5EVev7/ypx9xJcl+azgcsW3RHVNb3+TWLtnMxIIamDW+1H/edD/bM06Q5aZjwqk3+Mm7mKnxrCejGzap9vvaP+6MKuRGHIFrwASZINAj+jO6bfZY6sRJ2KGrHBSObTsiRxciKkA125FWndJiK3nt2kSCe6wB25qwqg7DG+KjJQdcVZXfvd4kseSgaeBKAxkIcDx0+6dLDOjuL0v6fUjq4mKmNTDtCLakwrUr8t0jH1lB8mtlficifAsJC8rasknZPXUSw8yLrtzmc38/NKlXW1v2oAKP21LPoQdNOQpiVHwDydZ23lrdObpZMa6RCtkqFX28jKbEBIBx9L6UwP9yDBFpdyg8jkOl9tx1RLdBAYgB5B11rQsMjKb8RklyX6iMXIzP2VR/rFrPYcTQWzhNje3FQEhEi9B1qV/V4YlM2szrqN8t6jYlcx+WCs9gC7vSYATmTiy+PbJ64/mUwGTwnAJDeG+8XJPd3oOQjn1WxG5Sjn5JLOEadptflPdW/b0eJIaZHj5Zdcl8Oy6/FCUnYgWHvrBuNFAGLTHgEYVRf3+lBnVxrnlq1f3kZ5oL1XzKjvL3LrxJ1Fr2yELXip8ywo= 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | THIS IS UNMAINTAINED 2 | -------------------- 3 | 4 | # tidal-api-wrapper 5 | 6 | This library is no longer maintained. It will remain hosted in read-only mode for forking. 7 | 8 | ## Documentation 9 | 10 | [API Reference](https://github.com/spencercharest/tidal-api/tree/master/docs/api.md) 11 | -------------------------------------------------------------------------------- /docs/api.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Tidal 4 | Class 5 | 6 | **Kind**: global class 7 | 8 | * [Tidal](#Tidal) 9 | * [new Tidal([options])](#new_Tidal_new) 10 | * [.login(username, password)](#Tidal+login) ⇒ Promise 11 | * [.search(query, type, [limit])](#Tidal+search) ⇒ Promise 12 | * [.getTrack(id)](#Tidal+getTrack) ⇒ Promise 13 | * [.getFavoriteTracks()](#Tidal+getFavoriteTracks) ⇒ Promise 14 | * [.getAlbum(id)](#Tidal+getAlbum) ⇒ Promise 15 | * [.getAlbumTracks(id)](#Tidal+getAlbumTracks) ⇒ Promise 16 | * [.getTopAlbums()](#Tidal+getTopAlbums) ⇒ Promise 17 | * [.getNewAlbums()](#Tidal+getNewAlbums) ⇒ Promise 18 | * [.getStaffPickAlbums()](#Tidal+getStaffPickAlbums) ⇒ Promise 19 | * [.getFavoriteAlbums()](#Tidal+getFavoriteAlbums) ⇒ Promise 20 | * [.getArtist(id)](#Tidal+getArtist) ⇒ Promise 21 | * [.getArtistAlbums(id)](#Tidal+getArtistAlbums) ⇒ Promise 22 | * [.getArtistEPsAndSingles(id)](#Tidal+getArtistEPsAndSingles) ⇒ Promise 23 | * [.getArtistCompilations(id)](#Tidal+getArtistCompilations) ⇒ Promise 24 | * [.getArtistTopTracks(id, [limit])](#Tidal+getArtistTopTracks) ⇒ Promise 25 | * [.getSimilarArtists(id)](#Tidal+getSimilarArtists) ⇒ Promise 26 | * [.getFavoriteArtists()](#Tidal+getFavoriteArtists) ⇒ Promise 27 | * [.getPlaylist(uuid)](#Tidal+getPlaylist) ⇒ Promise 28 | * [.getPlaylistTracks(uuid)](#Tidal+getPlaylistTracks) ⇒ Promise 29 | * [.getFavoritePlaylists()](#Tidal+getFavoritePlaylists) ⇒ Promise 30 | * [.getPlaylists()](#Tidal+getPlaylists) ⇒ Promise 31 | * [.artistPicToUrl(uuid)](#Tidal+artistPicToUrl) ⇒ Object 32 | * [.albumArtToUrl(uuid)](#Tidal+albumArtToUrl) ⇒ Object 33 | 34 | 35 | 36 | ### new Tidal([options]) 37 | 38 | | Param | Type | Default | Description | 39 | | --- | --- | --- | --- | 40 | | [options] | object | | Tidal options (optional) | 41 | | [options.countryCode] | string | "US" | Tidal country code | 42 | | [options.limit] | number | 1000 | API results limit | 43 | 44 | 45 | 46 | ### tidal.login(username, password) ⇒ Promise 47 | login to Tidal in order to use methods that require authentication 48 | 49 | **Kind**: instance method of [Tidal](#Tidal) 50 | **Fulfil**: Object - user data object (see example for object properties) 51 | **Reject**: Error 52 | 53 | | Param | Type | Description | 54 | | --- | --- | --- | 55 | | username | string | Tidal username or email | 56 | | password | string | Tidal password | 57 | 58 | **Example** 59 | ```js 60 | tidal.login('username', 'password') 61 | // returns a promise that resolves to 62 | { 63 | userId: 49927020, 64 | sessionId: '24d3d406-e6b9-457a-bf57-eac7b113a20c', 65 | countryCode: 'US' 66 | } 67 | ``` 68 | 69 | 70 | ### tidal.search(query, type, [limit]) ⇒ Promise 71 | search for artists, albums, tracks, or playlists 72 | 73 | **Kind**: instance method of [Tidal](#Tidal) 74 | **Fulfil**: Array - an array of objects (object properties are dependent on search type) 75 | **Reject**: Error 76 | 77 | | Param | Type | Default | Description | 78 | | --- | --- | --- | --- | 79 | | query | string | | search query | 80 | | type | string | | search type ('artists', 'albums', 'tracks', 'playlists') | 81 | | [limit] | number | 25 | search limit | 82 | 83 | **Example** 84 | ```js 85 | tidal.search('Four Year Strong', 'artists', 1) 86 | // returns a promise that resolves to: 87 | [ 88 | { 89 | "id": 3575680, 90 | "name": "Four Year Strong", 91 | "url": "http://www.tidal.com/artist/3575680", 92 | "picture": "04d63cd8-a1a5-42e0-b1ec-8e336b7d9200", 93 | "popularity": 28 94 | } 95 | ] 96 | ``` 97 | 98 | 99 | ### tidal.getTrack(id) ⇒ Promise 100 | get a track by its id 101 | 102 | **Kind**: instance method of [Tidal](#Tidal) 103 | **Fulfil**: Object - a track object (see example for object properties) 104 | **Reject**: Error 105 | 106 | | Param | Type | Description | 107 | | --- | --- | --- | 108 | | id | number | track id | 109 | 110 | **Example** 111 | ```js 112 | tidal.getTrack(64975224) 113 | // returns a promise that resolves to: 114 | { 115 | "id": 64975224, 116 | "title": "22 (OVER S∞∞N)", 117 | "duration": 168, 118 | "replayGain": -10.71, 119 | "peak": 0.692531, 120 | "allowStreaming": true, 121 | "streamReady": true, 122 | "streamStartDate": "2016-09-30T00:00:00.000+0000", 123 | "premiumStreamingOnly": false, 124 | "trackNumber": 1, 125 | "volumeNumber": 1, 126 | "version": null, 127 | "popularity": 47, 128 | "copyright": "2016 Jagjaguwar", 129 | "url": "http://www.tidal.com/track/64975224", 130 | "isrc": "US38Y1630001", 131 | "editable": true, 132 | "explicit": false, 133 | "audioQuality": "LOSSLESS", 134 | "artist": { 135 | "id": 3566315, 136 | "name": "Bon Iver", 137 | "type": "MAIN" 138 | }, 139 | "artists": [ 140 | { 141 | "id": 3566315, 142 | "name": "Bon Iver", 143 | "type": "MAIN" 144 | } 145 | ], 146 | "album": { 147 | "id": 64975223, 148 | "title": "22, A Million", 149 | "cover": "5ac41fbb-927b-427e-8224-87bf12d218a3" 150 | } 151 | } 152 | ``` 153 | 154 | 155 | ### tidal.getFavoriteTracks() ⇒ Promise 156 | get your favorite (starred) tracks (requires login() to be called) 157 | 158 | **Kind**: instance method of [Tidal](#Tidal) 159 | **Fulfil**: Array - an array of track objects 160 | **Reject**: Error 161 | **See** 162 | 163 | - [login](#Tidal+login) - login method must be called first 164 | - [getTrack](#Tidal+getTrack) - track object example 165 | 166 | **Example** 167 | ```js 168 | tidal.getFavoriteTracks() 169 | ``` 170 | 171 | 172 | ### tidal.getAlbum(id) ⇒ Promise 173 | get an album by its id 174 | 175 | **Kind**: instance method of [Tidal](#Tidal) 176 | **Fulfil**: Object - an album object (see example for object properties) 177 | **Reject**: Error 178 | 179 | | Param | Type | Description | 180 | | --- | --- | --- | 181 | | id | number | album id | 182 | 183 | **Example** 184 | ```js 185 | tidal.getAlbum(80216363) 186 | // returns a promise that resolves to: 187 | { 188 | "id": 80216363, 189 | "title": "Pacific Daydream", 190 | "duration": 2069, 191 | "streamReady": true, 192 | "streamStartDate": "2017-10-27T00:00:00.000+0000", 193 | "allowStreaming": true, 194 | "premiumStreamingOnly": false, 195 | "numberOfTracks": 10, 196 | "numberOfVideos": 0, 197 | "numberOfVolumes": 1, 198 | "releaseDate": "2017-10-27", 199 | "copyright": "2017 Weezer under exclusive license to Crush Music / \ 200 | Atlantic Recording Corporation for the United States and Crush Music / \ 201 | WEA International Inc. for the world excluding the United States. \ 202 | A Warner Music Company.", 203 | "type": "ALBUM", 204 | "version": null, 205 | "url": "http://www.tidal.com/album/80216363", 206 | "cover": "86538ca7-08fd-40ff-9a75-af88d74d1f48", 207 | "videoCover": null, 208 | "explicit": false, 209 | "upc": "075679889355", 210 | "popularity": 58, 211 | "audioQuality": "LOSSLESS", 212 | "artist": { 213 | "id": 30157, 214 | "name": "Weezer", 215 | "type": "MAIN" 216 | }, 217 | "artists": [ 218 | { 219 | "id": 30157, 220 | "name": "Weezer", 221 | "type": "MAIN" 222 | } 223 | ] 224 | } 225 | ``` 226 | 227 | 228 | ### tidal.getAlbumTracks(id) ⇒ Promise 229 | get album tracks by album id 230 | 231 | **Kind**: instance method of [Tidal](#Tidal) 232 | **Fulfil**: Array - an array of track objects 233 | **Reject**: Error 234 | **See**: [getTrack](#Tidal+getTrack) - track object example 235 | 236 | | Param | Type | Description | 237 | | --- | --- | --- | 238 | | id | number | album id | 239 | 240 | **Example** 241 | ```js 242 | tidal.getAlbumTracks(80216363) 243 | ``` 244 | 245 | 246 | ### tidal.getTopAlbums() ⇒ Promise 247 | get top 20 albums on Tidal 248 | 249 | **Kind**: instance method of [Tidal](#Tidal) 250 | **Fulfil**: Array - an array of album objects 251 | **Reject**: Error 252 | **See**: [getAlbum](#Tidal+getAlbum) - album object example 253 | **Example** 254 | ```js 255 | tidal.getTopAlbums() 256 | ``` 257 | 258 | 259 | ### tidal.getNewAlbums() ⇒ Promise 260 | get new albums on Tidal 261 | 262 | **Kind**: instance method of [Tidal](#Tidal) 263 | **Fulfil**: Array - an array of album objects 264 | **Reject**: Error 265 | **See**: [getAlbum](#Tidal+getAlbum) - album object example 266 | **Example** 267 | ```js 268 | tidal.getNewAlbums() 269 | ``` 270 | 271 | 272 | ### tidal.getStaffPickAlbums() ⇒ Promise 273 | get staff pick albums on Tidal 274 | 275 | **Kind**: instance method of [Tidal](#Tidal) 276 | **Fulfil**: Array - an array of album objects 277 | **Reject**: Error 278 | **See**: [getAlbum](#Tidal+getAlbum) - album object example 279 | **Example** 280 | ```js 281 | tidal.getStaffPickAlbums() 282 | ``` 283 | 284 | 285 | ### tidal.getFavoriteAlbums() ⇒ Promise 286 | get your favorite (starred) albums (requires login() to be called) 287 | 288 | **Kind**: instance method of [Tidal](#Tidal) 289 | **Fulfil**: Array - an array of album objects 290 | **Reject**: Error 291 | **See** 292 | 293 | - [login](#Tidal+login) - login method must be called first 294 | - [getAlbum](#Tidal+getAlbum) - album object example 295 | 296 | **Example** 297 | ```js 298 | tidal.getFavoriteAlbums() 299 | ``` 300 | 301 | 302 | ### tidal.getArtist(id) ⇒ Promise 303 | get an artist by its id 304 | 305 | **Kind**: instance method of [Tidal](#Tidal) 306 | **Fulfil**: Object - an artist object (see example for object properties) 307 | **Reject**: Error 308 | 309 | | Param | Type | Description | 310 | | --- | --- | --- | 311 | | id | number | artist id | 312 | 313 | **Example** 314 | ```js 315 | tidal.getArtist(3575680) 316 | // returns a promise that resolves to: 317 | { 318 | "id": 3575680, 319 | "name": "Four Year Strong", 320 | "url": "http://www.tidal.com/artist/3575680", 321 | "picture": "04d63cd8-a1a5-42e0-b1ec-8e336b7d9200", 322 | "popularity": 28 323 | } 324 | ``` 325 | 326 | 327 | ### tidal.getArtistAlbums(id) ⇒ Promise 328 | get artist albums by artist id 329 | 330 | **Kind**: instance method of [Tidal](#Tidal) 331 | **Fulfil**: Array - an array of album objects 332 | **Reject**: Error 333 | **See**: [getAlbum](#Tidal+getAlbum) - album object example 334 | 335 | | Param | Type | Description | 336 | | --- | --- | --- | 337 | | id | number | artist id | 338 | 339 | **Example** 340 | ```js 341 | tidal.getArtistAlbums(3575680) 342 | ``` 343 | 344 | 345 | ### tidal.getArtistEPsAndSingles(id) ⇒ Promise 346 | get artist EPs and singles by artist id 347 | 348 | **Kind**: instance method of [Tidal](#Tidal) 349 | **Fulfil**: Array - an array of album objects 350 | **Reject**: Error 351 | **See**: [getAlbum](#Tidal+getAlbum) - album object example 352 | 353 | | Param | Type | Description | 354 | | --- | --- | --- | 355 | | id | number | artist id | 356 | 357 | **Example** 358 | ```js 359 | tidal.getArtistEPsAndSingles(3575680) 360 | ``` 361 | 362 | 363 | ### tidal.getArtistCompilations(id) ⇒ Promise 364 | get compliations that artist has appeared on by artist id 365 | 366 | **Kind**: instance method of [Tidal](#Tidal) 367 | **Fulfil**: Array - an array of album objects 368 | **Reject**: Error 369 | **See**: [getAlbum](#Tidal+getAlbum) - album object example 370 | 371 | | Param | Type | Description | 372 | | --- | --- | --- | 373 | | id | number | artist id | 374 | 375 | **Example** 376 | ```js 377 | tidal.getArtistCompilations(3575680) 378 | ``` 379 | 380 | 381 | ### tidal.getArtistTopTracks(id, [limit]) ⇒ Promise 382 | get top tracks by artist 383 | 384 | **Kind**: instance method of [Tidal](#Tidal) 385 | **Fulfil**: Array - an array of track objects 386 | **Reject**: Error 387 | **See**: [getTrack](#Tidal+getTrack) - track object example 388 | 389 | | Param | Type | Default | Description | 390 | | --- | --- | --- | --- | 391 | | id | number | | artist id | 392 | | [limit] | number | 10 | results limit | 393 | 394 | **Example** 395 | ```js 396 | tidal.getArtistTopTracks(3575680) 397 | ``` 398 | 399 | 400 | ### tidal.getSimilarArtists(id) ⇒ Promise 401 | get similar artists 402 | 403 | **Kind**: instance method of [Tidal](#Tidal) 404 | **Fulfil**: Object - artist object 405 | **Reject**: Error 406 | **See**: [getArtist](#Tidal+getArtist) - artist object example 407 | 408 | | Param | Type | Description | 409 | | --- | --- | --- | 410 | | id | number | artist id | 411 | 412 | **Example** 413 | ```js 414 | tidal.getSimilarArtists(3575680) 415 | ``` 416 | 417 | 418 | ### tidal.getFavoriteArtists() ⇒ Promise 419 | get your favorite (starred) artists (requires login() to be called) 420 | 421 | **Kind**: instance method of [Tidal](#Tidal) 422 | **Fulfil**: Array - an array of artist objects 423 | **Reject**: Error 424 | **See** 425 | 426 | - [login](#Tidal+login) - login method must be called first 427 | - [getArtist](#Tidal+getArtist) - artist object example 428 | 429 | **Example** 430 | ```js 431 | tidal.getFavoriteArtists() 432 | ``` 433 | 434 | 435 | ### tidal.getPlaylist(uuid) ⇒ Promise 436 | get a playlist by its uuid 437 | 438 | **Kind**: instance method of [Tidal](#Tidal) 439 | **Fulfil**: Object - playlist object (see example for object properties) 440 | **Reject**: Error 441 | 442 | | Param | Type | Description | 443 | | --- | --- | --- | 444 | | uuid | string | playlist uuid | 445 | 446 | **Example** 447 | ```js 448 | tidal.getPlaylist('1c5d01ed-4f05-40c4-bd28-0f73099e9648') 449 | // returns a promise that resolves to: 450 | { 451 | "uuid": "1c5d01ed-4f05-40c4-bd28-0f73099e9648", 452 | "title": "Get Down On It: Soul, Funk and Disco Supremo", 453 | "numberOfTracks": 100, 454 | "numberOfVideos": 0, 455 | "creator": { 456 | "id": 0 457 | }, 458 | "description": "Get down and dirty with some of the finest soul, funk \ 459 | and four-to-the floor disco out there. Bound to get the blood pumping, \ 460 | this playlist boasts more hits than a boxing match, more hooks than a \ 461 | tackle box and marks the perfect prescription both for champagne days \ 462 | and boogie nights and alike. Whether you feel like being a sex machine \ 463 | or simply wish to dance to the music, rock your body, dig it and don't \ 464 | stop 'til you get enough! ", 465 | "duration": 25732, 466 | "lastUpdated": "2017-01-18T16:31:51.839+0000", 467 | "created": "2016-09-22T16:42:40.911+0000", 468 | "type": "EDITORIAL", 469 | "publicPlaylist": true, 470 | "url": "http://www.tidal.com/playlist/1c5d01ed-4f05-40c4-bd28-0f73099e9648", 471 | "image": "7a707631-02cf-47d8-a34c-e1395165f169", 472 | "popularity": 0 473 | } 474 | ``` 475 | 476 | 477 | ### tidal.getPlaylistTracks(uuid) ⇒ Promise 478 | get playlist tracks by playlist uuid 479 | 480 | **Kind**: instance method of [Tidal](#Tidal) 481 | **Fulfil**: Array - an array of track objects 482 | **Reject**: Error 483 | **See**: [getTrack](#Tidal+getTrack) - track object example 484 | 485 | | Param | Type | Description | 486 | | --- | --- | --- | 487 | | uuid | string | playlist uuid | 488 | 489 | **Example** 490 | ```js 491 | tidal.getPlaylistTracks('1c5d01ed-4f05-40c4-bd28-0f73099e9648') 492 | ``` 493 | 494 | 495 | ### tidal.getFavoritePlaylists() ⇒ Promise 496 | get your favorite (starred) playlists (requires login() to be called) 497 | 498 | **Kind**: instance method of [Tidal](#Tidal) 499 | **Fulfil**: Array - an array of playlist objects 500 | **Reject**: Error 501 | **See** 502 | 503 | - [login](#Tidal+login) - login method must be called first 504 | - [getPlaylist](#Tidal+getPlaylist) - playlist object example 505 | 506 | **Example** 507 | ```js 508 | tidal.getFavoritePlaylists() 509 | ``` 510 | 511 | 512 | ### tidal.getPlaylists() ⇒ Promise 513 | get your created playlists (requires login() to be called) 514 | 515 | **Kind**: instance method of [Tidal](#Tidal) 516 | **Fulfil**: Array - an array of playlist objects 517 | **Reject**: Error 518 | **See** 519 | 520 | - [login](#Tidal+login) - login method must be called first 521 | - [getPlaylist](#Tidal+getPlaylist) - playlist object example 522 | 523 | **Example** 524 | ```js 525 | tidal.getPlaylists() 526 | ``` 527 | 528 | 529 | ### tidal.artistPicToUrl(uuid) ⇒ Object 530 | get valid urls to artist pictures 531 | 532 | **Kind**: instance method of [Tidal](#Tidal) 533 | 534 | | Param | Type | Description | 535 | | --- | --- | --- | 536 | | uuid | string | artist picture uuid (can be found as picture property in artist object) | 537 | 538 | **Example** 539 | ```js 540 | tidal.artistPicToUrl('04d63cd8-a1a5-42e0-b1ec-8e336b7d9200') 541 | // returns 542 | { 543 | sm: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/160x107.jpg', 544 | md: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/320x214.jpg', 545 | lg: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/640x428.jpg' 546 | } 547 | ``` 548 | 549 | 550 | ### tidal.albumArtToUrl(uuid) ⇒ Object 551 | get valid urls to album art 552 | 553 | **Kind**: instance method of [Tidal](#Tidal) 554 | 555 | | Param | Type | Description | 556 | | --- | --- | --- | 557 | | uuid | string | album art uuid (can be found as cover property in album object) | 558 | 559 | **Example** 560 | ```js 561 | tidal.albumArtToUrl('9a56f482-e9cf-46c3-bb21-82710e7854d4') 562 | // returns 563 | { 564 | sm: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/160x160.jpg', 565 | md: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/320x320.jpg', 566 | lg: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/640x640.jpg', 567 | xl: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/1280x1280.jpg' 568 | } 569 | ``` 570 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tidal-api-wrapper", 3 | "version": "1.7.1", 4 | "description": "An unofficial API wrapper for Tidal Music", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "babel -d lib/ src/", 8 | "start": "babel-node src/", 9 | "pretest": "npm run build", 10 | "test": "mocha --timeout 5000 --require babel-core/register --require babel-polyfill ./test/**/*.js", 11 | "test:cover": "babel-node ./node_modules/.bin/babel-istanbul cover _mocha -- --timeout 10000", 12 | "prepublish": "npm run build", 13 | "docs": "jsdoc2md src/*.js > docs/api.md" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/spencercharest/tidal-api.git" 18 | }, 19 | "keywords": [ 20 | "flac", 21 | "alac", 22 | "lossless", 23 | "music", 24 | "stream", 25 | "streaming", 26 | "tidal" 27 | ], 28 | "author": "Spencer Charest", 29 | "license": "ISC", 30 | "bugs": { 31 | "url": "https://github.com/spencercharest/tidal-api/issues" 32 | }, 33 | "homepage": "https://github.com/spencercharest/tidal-api#readme", 34 | "dependencies": { 35 | "axios": "^0.17.0", 36 | "babel-runtime": "^6.26.0" 37 | }, 38 | "devDependencies": { 39 | "babel-cli": "^6.26.0", 40 | "babel-core": "^6.26.0", 41 | "babel-istanbul": "^0.12.2", 42 | "babel-plugin-add-module-exports": "^0.2.1", 43 | "babel-plugin-transform-runtime": "^6.23.0", 44 | "babel-preset-es2015": "^6.24.1", 45 | "babel-preset-stage-0": "^6.24.1", 46 | "babel-register": "^6.26.0", 47 | "chai": "^4.1.2", 48 | "chai-as-promised": "^7.1.1", 49 | "chai-things": "^0.2.0", 50 | "coveralls": "^3.0.0", 51 | "dotenv": "^4.0.0", 52 | "eslint": "^4.9.0", 53 | "eslint-config-airbnb": "^16.1.0", 54 | "eslint-plugin-import": "^2.8.0", 55 | "eslint-plugin-jsx-a11y": "^6.0.2", 56 | "eslint-plugin-react": "^7.4.0", 57 | "jsdoc-to-markdown": "^3.1.0-0", 58 | "mocha": "^4.0.1" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import qs from 'querystring'; 3 | 4 | /** Class */ 5 | class Tidal { 6 | 7 | /** 8 | * 9 | * @param {object} [options] - Tidal options (optional) 10 | * @param {string} [options.countryCode=US] - Tidal country code 11 | * @param {number} [options.limit=1000] - API results limit 12 | */ 13 | constructor(options = {}) { 14 | this.url = 'https://api.tidal.com/v1'; 15 | this.webToken = 'kgsOOmYk3zShYrNP'; 16 | this.countryCode = options.countryCode || 'US'; 17 | this.limit = options.limit || 1000; 18 | this.api = axios.create({ 19 | baseURL: this.url, 20 | headers: { 21 | 'x-tidal-token': this.webToken, 22 | }, 23 | }); 24 | // some base params for GET requests 25 | this.params = `limit=${this.limit}&countryCode=${this.countryCode}`; 26 | // params for Tidal pages that require a locale and device type 27 | this.localeParams = `locale=en_${this.countryCode}&deviceType=BROWSER&countryCode=${this.countryCode}`; 28 | } 29 | 30 | /** 31 | * login to Tidal in order to use methods that require authentication 32 | * @param {string} username - Tidal username or email 33 | * @param {string} password - Tidal password 34 | * @example tidal.login('username', 'password') 35 | * // returns a promise that resolves to 36 | { 37 | userId: 49927020, 38 | sessionId: '24d3d406-e6b9-457a-bf57-eac7b113a20c', 39 | countryCode: 'US' 40 | } 41 | * @returns {Promise} 42 | * @fulfil {Object} - user data object (see example for object properties) 43 | * @reject {Error} 44 | */ 45 | async login(username, password) { 46 | 47 | if (!username || !password) { 48 | throw new Error('Username and password are required arguments of login()'); 49 | } 50 | 51 | const params = qs.stringify({ 52 | username, 53 | password, 54 | }); 55 | 56 | const res = await this.api({ 57 | method: 'POST', 58 | url: `/login/username?token=${this.webToken}`, 59 | data: params, 60 | }); 61 | 62 | // store this info for use in other methods 63 | this.userId = res.data.userId; 64 | this.sessionId = res.data.sessionId; 65 | this.params = `${this.params}&sessionId=${res.data.sessionId}`; 66 | 67 | return res.data; 68 | } 69 | 70 | /** 71 | * search for artists, albums, tracks, or playlists 72 | * @param {string} query - search query 73 | * @param {string} type - search type ('artists', 'albums', 'tracks', 'playlists') 74 | * @param {number} [limit] - search limit 75 | * @example 76 | * tidal.search('Four Year Strong', 'artists', 1) 77 | * // returns a promise that resolves to: 78 | [ 79 | { 80 | "id": 3575680, 81 | "name": "Four Year Strong", 82 | "url": "http://www.tidal.com/artist/3575680", 83 | "picture": "04d63cd8-a1a5-42e0-b1ec-8e336b7d9200", 84 | "popularity": 28 85 | } 86 | ] 87 | * @returns {Promise} 88 | * @fulfil {Array} - an array of objects (object properties are dependent on search type) 89 | * @reject {Error} 90 | */ 91 | async search(query, type, limit = 25) { 92 | 93 | const accTypes = ['artists', 'albums', 'tracks', 'playlists']; 94 | 95 | if (!type) { 96 | throw new Error('Search requires type as a second argument (artists, albums, tracks, or playlists)'); 97 | } 98 | 99 | if (accTypes.indexOf(type) < 0) { 100 | throw new Error(`${type} is not a valid search type('artists', 'albums', 'tracks', 'playlists' are valid).`); 101 | } 102 | 103 | const res = await this.api({ 104 | method: 'GET', 105 | url: `/search/${type}?query=${query}&limit=${limit}&countryCode=${this.countryCode}`, 106 | }); 107 | 108 | return res.data.items; 109 | } 110 | 111 | /** 112 | * get a track by its id 113 | * @param {number} id - track id 114 | * @example 115 | * tidal.getTrack(64975224) 116 | * // returns a promise that resolves to: 117 | { 118 | "id": 64975224, 119 | "title": "22 (OVER S∞∞N)", 120 | "duration": 168, 121 | "replayGain": -10.71, 122 | "peak": 0.692531, 123 | "allowStreaming": true, 124 | "streamReady": true, 125 | "streamStartDate": "2016-09-30T00:00:00.000+0000", 126 | "premiumStreamingOnly": false, 127 | "trackNumber": 1, 128 | "volumeNumber": 1, 129 | "version": null, 130 | "popularity": 47, 131 | "copyright": "2016 Jagjaguwar", 132 | "url": "http://www.tidal.com/track/64975224", 133 | "isrc": "US38Y1630001", 134 | "editable": true, 135 | "explicit": false, 136 | "audioQuality": "LOSSLESS", 137 | "artist": { 138 | "id": 3566315, 139 | "name": "Bon Iver", 140 | "type": "MAIN" 141 | }, 142 | "artists": [ 143 | { 144 | "id": 3566315, 145 | "name": "Bon Iver", 146 | "type": "MAIN" 147 | } 148 | ], 149 | "album": { 150 | "id": 64975223, 151 | "title": "22, A Million", 152 | "cover": "5ac41fbb-927b-427e-8224-87bf12d218a3" 153 | } 154 | } 155 | * @returns {Promise} 156 | * @fulfil {Object} - a track object (see example for object properties) 157 | * @reject {Error} 158 | */ 159 | async getTrack(id) { 160 | 161 | const res = await this.api({ 162 | method: 'GET', 163 | url: `/tracks/${id}?${this.params}`, 164 | }); 165 | 166 | return res.data; 167 | } 168 | 169 | /** 170 | * get your favorite (starred) tracks (requires login() to be called) 171 | * @example tidal.getFavoriteTracks() 172 | * @returns {Promise} 173 | * @fulfil {Array} - an array of track objects 174 | * @reject {Error} 175 | * @see {@link Tidal#login} - login method must be called first 176 | * @see {@link Tidal#getTrack} - track object example 177 | */ 178 | async getFavoriteTracks() { 179 | 180 | if (!this.userId || !this.sessionId) { 181 | throw new Error('You must call the login method first'); 182 | } 183 | 184 | const res = await this.api({ 185 | method: 'GET', 186 | url: `/users/${this.userId}/favorites/tracks?${this.params}`, 187 | }); 188 | 189 | const { items } = res.data; 190 | 191 | const tracks = items.map(item => item.item); 192 | 193 | return tracks; 194 | } 195 | 196 | /** 197 | * get an album by its id 198 | * @param {number} id - album id 199 | * @example tidal.getAlbum(80216363) 200 | * // returns a promise that resolves to: 201 | { 202 | "id": 80216363, 203 | "title": "Pacific Daydream", 204 | "duration": 2069, 205 | "streamReady": true, 206 | "streamStartDate": "2017-10-27T00:00:00.000+0000", 207 | "allowStreaming": true, 208 | "premiumStreamingOnly": false, 209 | "numberOfTracks": 10, 210 | "numberOfVideos": 0, 211 | "numberOfVolumes": 1, 212 | "releaseDate": "2017-10-27", 213 | "copyright": "2017 Weezer under exclusive license to Crush Music / \ 214 | Atlantic Recording Corporation for the United States and Crush Music / \ 215 | WEA International Inc. for the world excluding the United States. \ 216 | A Warner Music Company.", 217 | "type": "ALBUM", 218 | "version": null, 219 | "url": "http://www.tidal.com/album/80216363", 220 | "cover": "86538ca7-08fd-40ff-9a75-af88d74d1f48", 221 | "videoCover": null, 222 | "explicit": false, 223 | "upc": "075679889355", 224 | "popularity": 58, 225 | "audioQuality": "LOSSLESS", 226 | "artist": { 227 | "id": 30157, 228 | "name": "Weezer", 229 | "type": "MAIN" 230 | }, 231 | "artists": [ 232 | { 233 | "id": 30157, 234 | "name": "Weezer", 235 | "type": "MAIN" 236 | } 237 | ] 238 | } 239 | * @returns {Promise} 240 | * @fulfil {Object} - an album object (see example for object properties) 241 | * @reject {Error} 242 | */ 243 | async getAlbum(id) { 244 | 245 | const res = await this.api({ 246 | method: 'GET', 247 | url: `/albums/${id}?${this.params}`, 248 | }); 249 | 250 | return res.data; 251 | } 252 | 253 | /** 254 | * get album tracks by album id 255 | * @param {number} id - album id 256 | * @example tidal.getAlbumTracks(80216363) 257 | * @returns {Promise} 258 | * @fulfil {Array} - an array of track objects 259 | * @reject {Error} 260 | * @see {@link Tidal#getTrack} - track object example 261 | */ 262 | async getAlbumTracks(id) { 263 | 264 | const res = await this.api({ 265 | method: 'GET', 266 | url: `/albums/${id}/tracks?${this.params}`, 267 | }); 268 | 269 | return res.data.items; 270 | } 271 | 272 | // this is an internal method and so won't be included in JSDOC 273 | async getFeaturedAlbums() { 274 | 275 | const res = await this.api({ 276 | method: 'GET', 277 | url: `/pages/show_more_featured_albums?${this.localeParams}`, 278 | }); 279 | 280 | const { tabs } = res.data.rows[0].modules[0]; 281 | 282 | const topAlbums = tabs.find(tab => tab.key === 'featured-top'); 283 | const newAlbums = tabs.find(tab => tab.key === 'featured-new'); 284 | const staffPicks = tabs.find(tab => tab.key === 'featured-recommended'); 285 | 286 | return { 287 | topAlbums: topAlbums.pagedList.items, 288 | newAlbums: newAlbums.pagedList.items, 289 | staffPicks: staffPicks.pagedList.items, 290 | }; 291 | } 292 | 293 | /** 294 | * get top 20 albums on Tidal 295 | * @example tidal.getTopAlbums() 296 | * @returns {Promise} 297 | * @fulfil {Array} - an array of album objects 298 | * @reject {Error} 299 | * @see {@link Tidal#getAlbum} - album object example 300 | */ 301 | async getTopAlbums() { 302 | 303 | const featuredAlbums = await this.getFeaturedAlbums(); 304 | 305 | return featuredAlbums.topAlbums; 306 | } 307 | 308 | /** 309 | * get new albums on Tidal 310 | * @example tidal.getNewAlbums() 311 | * @returns {Promise} 312 | * @fulfil {Array} - an array of album objects 313 | * @reject {Error} 314 | * @see {@link Tidal#getAlbum} - album object example 315 | */ 316 | async getNewAlbums() { 317 | 318 | const featuredAlbums = await this.getFeaturedAlbums(); 319 | 320 | return featuredAlbums.newAlbums; 321 | } 322 | 323 | /** 324 | * get staff pick albums on Tidal 325 | * @example tidal.getStaffPickAlbums() 326 | * @returns {Promise} 327 | * @fulfil {Array} - an array of album objects 328 | * @reject {Error} 329 | * @see {@link Tidal#getAlbum} - album object example 330 | */ 331 | async getStaffPickAlbums() { 332 | 333 | const featuredAlbums = await this.getFeaturedAlbums(); 334 | 335 | return featuredAlbums.staffPicks; 336 | } 337 | 338 | /** 339 | * get your favorite (starred) albums (requires login() to be called) 340 | * @example tidal.getFavoriteAlbums() 341 | * @returns {Promise} 342 | * @fulfil {Array} - an array of album objects 343 | * @reject {Error} 344 | * @see {@link Tidal#login} - login method must be called first 345 | * @see {@link Tidal#getAlbum} - album object example 346 | */ 347 | async getFavoriteAlbums() { 348 | 349 | if (!this.userId || !this.sessionId) { 350 | throw new Error('You must call the login method first'); 351 | } 352 | 353 | const res = await this.api({ 354 | method: 'GET', 355 | url: `/users/${this.userId}/favorites/albums?${this.params}`, 356 | }); 357 | 358 | const { items } = res.data; 359 | 360 | const albums = items.map(item => item.item); 361 | 362 | return albums; 363 | } 364 | 365 | /** 366 | * get an artist by its id 367 | * @param {number} id - artist id 368 | * @example tidal.getArtist(3575680) 369 | * // returns a promise that resolves to: 370 | { 371 | "id": 3575680, 372 | "name": "Four Year Strong", 373 | "url": "http://www.tidal.com/artist/3575680", 374 | "picture": "04d63cd8-a1a5-42e0-b1ec-8e336b7d9200", 375 | "popularity": 28 376 | } 377 | * @returns {Promise} 378 | * @fulfil {Object} - an artist object (see example for object properties) 379 | * @reject {Error} 380 | */ 381 | async getArtist(id) { 382 | 383 | const res = await this.api({ 384 | method: 'GET', 385 | url: `/artists/${id}?${this.params}`, 386 | }); 387 | 388 | return res.data; 389 | } 390 | 391 | /** 392 | * get artist albums by artist id 393 | * @param {number} id - artist id 394 | * @example tidal.getArtistAlbums(3575680) 395 | * @returns {Promise} 396 | * @fulfil {Array} - an array of album objects 397 | * @reject {Error} 398 | * @see {@link Tidal#getAlbum} - album object example 399 | */ 400 | async getArtistAlbums(id) { 401 | 402 | const res = await this.api({ 403 | method: 'GET', 404 | url: `/artists/${id}/albums?${this.params}`, 405 | }); 406 | 407 | return res.data.items; 408 | } 409 | 410 | /** 411 | * get artist EPs and singles by artist id 412 | * @param {number} id - artist id 413 | * @example tidal.getArtistEPsAndSingles(3575680) 414 | * @returns {Promise} 415 | * @fulfil {Array} - an array of album objects 416 | * @reject {Error} 417 | * @see {@link Tidal#getAlbum} - album object example 418 | */ 419 | async getArtistEPsAndSingles(id) { 420 | 421 | const res = await this.api({ 422 | method: 'GET', 423 | url: `/artists/${id}/albums?${this.params}`, 424 | }); 425 | 426 | return res.data.items; 427 | } 428 | 429 | /** 430 | * get compliations that artist has appeared on by artist id 431 | * @param {number} id - artist id 432 | * @example tidal.getArtistCompilations(3575680) 433 | * @returns {Promise} 434 | * @fulfil {Array} - an array of album objects 435 | * @reject {Error} 436 | * @see {@link Tidal#getAlbum} - album object example 437 | */ 438 | async getArtistCompilations(id) { 439 | 440 | const res = await this.api({ 441 | method: 'GET', 442 | url: `/artists/${id}/albums?${this.params}&filter=COMPILATIONS`, 443 | }); 444 | 445 | return res.data.items; 446 | } 447 | 448 | /** 449 | * get top tracks by artist 450 | * @param {number} id - artist id 451 | * @param {number} [limit] - results limit 452 | * @example tidal.getArtistTopTracks(3575680) 453 | * @returns {Promise} 454 | * @fulfil {Array} - an array of track objects 455 | * @reject {Error} 456 | * @see {@link Tidal#getTrack} - track object example 457 | */ 458 | async getArtistTopTracks(id, limit = 10) { 459 | 460 | const res = await this.api({ 461 | method: 'GET', 462 | url: `/artists/${id}/toptracks?limit=${limit}&countryCode=${this.countryCode}`, 463 | }); 464 | 465 | return res.data.items; 466 | } 467 | 468 | /** 469 | * get similar artists 470 | * @param {number} id - artist id 471 | * @example tidal.getSimilarArtists(3575680) 472 | * @returns {Promise} 473 | * @fulfil {Object} - artist object 474 | * @reject {Error} 475 | * @see {@link Tidal#getArtist} - artist object example 476 | */ 477 | async getSimilarArtists(id) { 478 | const res = await this.api({ 479 | method: 'GET', 480 | url: `/artists/${id}/similar?${this.params}`, 481 | }); 482 | 483 | return res.data.items; 484 | } 485 | 486 | /** 487 | * get your favorite (starred) artists (requires login() to be called) 488 | * @example tidal.getFavoriteArtists() 489 | * @returns {Promise} 490 | * @fulfil {Array} - an array of artist objects 491 | * @reject {Error} 492 | * @see {@link Tidal#login} - login method must be called first 493 | * @see {@link Tidal#getArtist} - artist object example 494 | */ 495 | async getFavoriteArtists() { 496 | 497 | if (!this.userId || !this.sessionId) { 498 | throw new Error('You must call the login method first'); 499 | } 500 | 501 | const res = await this.api({ 502 | method: 'GET', 503 | url: `/users/${this.userId}/favorites/artists?${this.params}`, 504 | }); 505 | 506 | const { items } = res.data; 507 | 508 | const artists = items.map(item => item.item); 509 | 510 | return artists; 511 | } 512 | 513 | /** 514 | * get a playlist by its uuid 515 | * @param {string} uuid - playlist uuid 516 | * @example tidal.getPlaylist('1c5d01ed-4f05-40c4-bd28-0f73099e9648') 517 | * // returns a promise that resolves to: 518 | { 519 | "uuid": "1c5d01ed-4f05-40c4-bd28-0f73099e9648", 520 | "title": "Get Down On It: Soul, Funk and Disco Supremo", 521 | "numberOfTracks": 100, 522 | "numberOfVideos": 0, 523 | "creator": { 524 | "id": 0 525 | }, 526 | "description": "Get down and dirty with some of the finest soul, funk \ 527 | and four-to-the floor disco out there. Bound to get the blood pumping, \ 528 | this playlist boasts more hits than a boxing match, more hooks than a \ 529 | tackle box and marks the perfect prescription both for champagne days \ 530 | and boogie nights and alike. Whether you feel like being a sex machine \ 531 | or simply wish to dance to the music, rock your body, dig it and don't \ 532 | stop 'til you get enough! ", 533 | "duration": 25732, 534 | "lastUpdated": "2017-01-18T16:31:51.839+0000", 535 | "created": "2016-09-22T16:42:40.911+0000", 536 | "type": "EDITORIAL", 537 | "publicPlaylist": true, 538 | "url": "http://www.tidal.com/playlist/1c5d01ed-4f05-40c4-bd28-0f73099e9648", 539 | "image": "7a707631-02cf-47d8-a34c-e1395165f169", 540 | "popularity": 0 541 | } 542 | * @returns {Promise} 543 | * @fulfil {Object} - playlist object (see example for object properties) 544 | * @reject {Error} 545 | */ 546 | async getPlaylist(uuid) { 547 | 548 | const res = await this.api({ 549 | method: 'GET', 550 | url: `/playlists/${uuid}?${this.params}`, 551 | }); 552 | 553 | return res.data; 554 | } 555 | 556 | /** 557 | * get playlist tracks by playlist uuid 558 | * @param {string} uuid - playlist uuid 559 | * @example tidal.getPlaylistTracks('1c5d01ed-4f05-40c4-bd28-0f73099e9648') 560 | * @returns {Promise} 561 | * @fulfil {Array} - an array of track objects 562 | * @reject {Error} 563 | * @see {@link Tidal#getTrack} - track object example 564 | */ 565 | async getPlaylistTracks(uuid) { 566 | 567 | const res = await this.api({ 568 | method: 'GET', 569 | url: `/playlists/${uuid}/tracks?${this.params}`, 570 | }); 571 | 572 | return res.data.items; 573 | } 574 | 575 | /** 576 | * get your favorite (starred) playlists (requires login() to be called) 577 | * @example tidal.getFavoritePlaylists() 578 | * @returns {Promise} 579 | * @fulfil {Array} - an array of playlist objects 580 | * @reject {Error} 581 | * @see {@link Tidal#login} - login method must be called first 582 | * @see {@link Tidal#getPlaylist} - playlist object example 583 | */ 584 | async getFavoritePlaylists() { 585 | 586 | if (!this.userId || !this.sessionId) { 587 | throw new Error('You must call the login method first'); 588 | } 589 | 590 | const res = await this.api({ 591 | method: 'GET', 592 | url: `/users/${this.userId}/favorites/playlists?${this.params}`, 593 | }); 594 | 595 | const { items } = res.data; 596 | 597 | const playlists = items.map(item => item.item); 598 | 599 | return playlists; 600 | } 601 | 602 | /** 603 | * get your created playlists (requires login() to be called) 604 | * @example tidal.getPlaylists() 605 | * @returns {Promise} 606 | * @fulfil {Array} - an array of playlist objects 607 | * @reject {Error} 608 | * @see {@link Tidal#login} - login method must be called first 609 | * @see {@link Tidal#getPlaylist} - playlist object example 610 | */ 611 | async getPlaylists() { 612 | 613 | if (!this.userId || !this.sessionId) { 614 | throw new Error('You must call the login method first'); 615 | } 616 | 617 | const res = await this.api({ 618 | method: 'GET', 619 | url: `/users/${this.userId}/playlists?${this.params}`, 620 | }); 621 | 622 | return res.data.items; 623 | } 624 | 625 | /** 626 | * get valid urls to artist pictures 627 | * @param {string} uuid - artist picture uuid (can be found as picture property in artist object) 628 | * @example tidal.artistPicToUrl('04d63cd8-a1a5-42e0-b1ec-8e336b7d9200') 629 | * // returns 630 | { 631 | sm: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/160x107.jpg', 632 | md: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/320x214.jpg', 633 | lg: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/640x428.jpg' 634 | } 635 | * @returns {Object} 636 | */ 637 | artistPicToUrl(uuid) { 638 | const baseUrl = `https://resources.tidal.com/images/${uuid.replace(/-/g, '/')}`; 639 | return { 640 | sm: `${baseUrl}/160x107.jpg`, 641 | md: `${baseUrl}/320x214.jpg`, 642 | lg: `${baseUrl}/640x428.jpg`, 643 | }; 644 | } 645 | 646 | /** 647 | * get valid urls to album art 648 | * @param {string} uuid - album art uuid (can be found as cover property in album object) 649 | * @example tidal.albumArtToUrl('9a56f482-e9cf-46c3-bb21-82710e7854d4') 650 | * // returns 651 | { 652 | sm: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/160x160.jpg', 653 | md: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/320x320.jpg', 654 | lg: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/640x640.jpg', 655 | xl: 'https://resources.tidal.com/images/9a56f482-e9cf-46c3-bb21-82710e7854d4/1280x1280.jpg' 656 | } 657 | * @returns {Object} 658 | */ 659 | albumArtToUrl(uuid) { 660 | const baseUrl = `https://resources.tidal.com/images/${uuid.replace(/-/g, '/')}`; 661 | return { 662 | sm: `${baseUrl}/160x160.jpg`, 663 | md: `${baseUrl}/320x320.jpg`, 664 | lg: `${baseUrl}/640x640.jpg`, 665 | xl: `${baseUrl}/1280x1280.jpg`, 666 | }; 667 | } 668 | 669 | } 670 | 671 | export default Tidal; 672 | -------------------------------------------------------------------------------- /test/album.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import Tidal from '../src'; 4 | 5 | chai.use(chaiAsPromised); 6 | 7 | const tidal = new Tidal(); 8 | 9 | describe('album', () => { 10 | 11 | describe('getAlbum', () => { 12 | 13 | it('should return the correct album object', async () => { 14 | const album = await tidal.getAlbum(80216363); 15 | 16 | expect(album).to.be.an('object') 17 | .and.to.include({ id: 80216363 }); 18 | 19 | }); 20 | 21 | }); 22 | 23 | describe('getAlbumTracks', () => { 24 | 25 | it('should return an array with the correct number of track objects', async () => { 26 | const tracks = await tidal.getAlbumTracks(80216363); 27 | expect(tracks).to.be.an('array') 28 | .and.to.have.lengthOf(10); 29 | 30 | expect(tracks[0]).to.have.property('trackNumber'); 31 | 32 | }); 33 | 34 | }); 35 | 36 | describe('getTopAlbums', () => { 37 | 38 | it('should return an array of album objects', async () => { 39 | 40 | const albums = await tidal.getTopAlbums(); 41 | 42 | expect(albums).to.be.an('array'); 43 | 44 | expect(albums[0]).to.have.property('numberOfTracks'); 45 | 46 | }); 47 | 48 | }); 49 | 50 | describe('getNewAlbums', () => { 51 | 52 | it('should return an array of album objects', async () => { 53 | 54 | const albums = await tidal.getNewAlbums(); 55 | 56 | expect(albums).to.be.an('array'); 57 | 58 | expect(albums[0]).to.have.property('numberOfTracks'); 59 | 60 | }); 61 | 62 | }); 63 | 64 | describe('getStaffPickAlbums', () => { 65 | 66 | it('should return an array of album objects', async () => { 67 | 68 | const albums = await tidal.getStaffPickAlbums(); 69 | 70 | expect(albums).to.be.an('array'); 71 | 72 | expect(albums[0]).to.have.property('numberOfTracks'); 73 | 74 | }); 75 | 76 | }); 77 | 78 | describe('getFavoriteAlbums', () => { 79 | 80 | it('should reject if login() has not been called', () => expect(tidal.getFavoriteAlbums()).to.eventually.be.rejectedWith(Error)); 81 | 82 | it('should return an array of album object', async () => { 83 | 84 | await tidal.login(process.env.USERNAME, process.env.PASSWORD); 85 | 86 | const favorites = await tidal.getFavoriteAlbums(); 87 | const album = favorites[0]; 88 | 89 | expect(favorites).to.be.an('array'); 90 | 91 | expect(album).to.be.an('object') 92 | .and.to.have.property('title'); 93 | 94 | }); 95 | 96 | }); 97 | 98 | }); 99 | -------------------------------------------------------------------------------- /test/artist.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import Tidal from '../src'; 4 | 5 | chai.use(chaiAsPromised); 6 | 7 | const tidal = new Tidal(); 8 | 9 | describe('artist', () => { 10 | 11 | describe('getArtist', () => { 12 | 13 | it('should return the expected artist object', async () => { 14 | const artist = await tidal.getArtist(3575680); 15 | 16 | expect(artist).to.be.an('object') 17 | .and.to.include({ id: 3575680 }); 18 | 19 | }); 20 | 21 | }); 22 | 23 | describe('getArtistAlbums', () => { 24 | 25 | it('should return an array of album objects', async () => { 26 | const albums = await tidal.getArtistAlbums(3575680); 27 | 28 | expect(albums).to.be.an('array'); 29 | expect(albums[0]).to.be.an('object') 30 | .and.to.include({ type: 'ALBUM' }); 31 | 32 | }); 33 | 34 | }); 35 | 36 | describe('getArtistEPsAndSingles', () => { 37 | 38 | it('should return an array of album objects', async () => { 39 | const albums = await tidal.getArtistEPsAndSingles(3575680); 40 | 41 | expect(albums).to.be.an('array') 42 | .and.to.have.lengthOf.above(0); 43 | 44 | expect(albums[0]).to.be.an('object'); 45 | 46 | }); 47 | }); 48 | 49 | describe('getArtistCompilations', () => { 50 | 51 | it('should return an array of album objects', async () => { 52 | const albums = await tidal.getArtistCompilations(3575680); 53 | 54 | expect(albums).to.be.an('array') 55 | .and.to.have.lengthOf.above(0); 56 | 57 | expect(albums[0]).to.be.an('object'); 58 | 59 | }); 60 | }); 61 | 62 | describe('getArtistTopTracks', () => { 63 | 64 | it('should return an array of 10 track objects', async () => { 65 | const tracks = await tidal.getArtistTopTracks(3575680); 66 | 67 | expect(tracks).to.be.an('array') 68 | .and.to.have.lengthOf(10); 69 | 70 | expect(tracks[0]).to.be.an('object') 71 | .and.to.have.property('trackNumber'); 72 | 73 | }); 74 | 75 | it('should return an array of 25 track objects', async () => { 76 | const tracks = await tidal.getArtistTopTracks(3575680, 25); 77 | 78 | expect(tracks).to.be.an('array') 79 | .and.to.have.lengthOf(25); 80 | }); 81 | }); 82 | 83 | describe('getSimilarArtists', () => { 84 | 85 | it('should return an array of artist objects', async () => { 86 | 87 | const similar = await tidal.getSimilarArtists(3575680); 88 | const artist = similar[0]; 89 | 90 | expect(similar).to.be.an('array') 91 | .and.to.have.length.above(0); 92 | 93 | expect(artist).to.be.an('object') 94 | .and.to.have.property('id'); 95 | 96 | }); 97 | 98 | }); 99 | 100 | describe('getFavoriteArtists', () => { 101 | 102 | it('should reject if login() has not been called', () => expect(tidal.getFavoriteArtists()).to.eventually.be.rejectedWith(Error)); 103 | 104 | it('should return an array of artist objects', async () => { 105 | 106 | await tidal.login(process.env.USERNAME, process.env.PASSWORD); 107 | 108 | const favorites = await tidal.getFavoriteArtists(); 109 | const artist = favorites[0]; 110 | 111 | expect(favorites).to.be.an('array'); 112 | 113 | expect(artist).to.be.an('object') 114 | .and.to.have.property('name'); 115 | 116 | }); 117 | 118 | }); 119 | 120 | }); 121 | -------------------------------------------------------------------------------- /test/class.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Tidal from '../src'; 3 | 4 | describe('Tidal', () => { 5 | 6 | it('should be instantiated with default options', () => { 7 | const tidal = new Tidal(); 8 | 9 | expect(tidal.countryCode).to.equal('US'); 10 | 11 | expect(tidal.limit).to.equal(1000); 12 | }); 13 | 14 | it('should be instantiated with custom options', () => { 15 | 16 | const tidal = new Tidal({ 17 | countryCode: 'DK', 18 | limit: 500, 19 | }); 20 | 21 | expect(tidal.countryCode).to.equal('DK'); 22 | 23 | expect(tidal.limit).to.equal(500); 24 | }); 25 | 26 | }); 27 | 28 | -------------------------------------------------------------------------------- /test/image.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import Tidal from '../src'; 3 | 4 | const tidal = new Tidal(); 5 | 6 | describe('images', () => { 7 | 8 | describe('artistPicToUrl', () => { 9 | 10 | it('should return an object with the correct sm, md, lg properties', () => { 11 | 12 | expect(tidal.artistPicToUrl('04d63cd8-a1a5-42e0-b1ec-8e336b7d9200')) 13 | .to.deep.equal({ 14 | sm: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/160x107.jpg', 15 | md: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/320x214.jpg', 16 | lg: 'https://resources.tidal.com/images/04d63cd8/a1a5/42e0/b1ec/8e336b7d9200/640x428.jpg', 17 | }); 18 | 19 | }); 20 | 21 | }); 22 | 23 | describe('albumArtToUrl', () => { 24 | 25 | it('should return an object with the correct sm, md, lg, xl properties', () => { 26 | 27 | expect(tidal.albumArtToUrl('9a56f482-e9cf-46c3-bb21-82710e7854d4')) 28 | .to.deep.equal({ 29 | sm: 'https://resources.tidal.com/images/9a56f482/e9cf/46c3/bb21/82710e7854d4/160x160.jpg', 30 | md: 'https://resources.tidal.com/images/9a56f482/e9cf/46c3/bb21/82710e7854d4/320x320.jpg', 31 | lg: 'https://resources.tidal.com/images/9a56f482/e9cf/46c3/bb21/82710e7854d4/640x640.jpg', 32 | xl: 'https://resources.tidal.com/images/9a56f482/e9cf/46c3/bb21/82710e7854d4/1280x1280.jpg', 33 | }); 34 | 35 | }); 36 | 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/login.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import dotenv from 'dotenv'; 4 | import Tidal from '../src'; 5 | 6 | chai.use(chaiAsPromised); 7 | 8 | dotenv.config(); 9 | 10 | const tidal = new Tidal(); 11 | 12 | describe('login', () => { 13 | 14 | it('should return user and session data', async () => { 15 | 16 | const data = await tidal.login(process.env.USERNAME, process.env.PASSWORD); 17 | 18 | expect(data).to.be.an('object'); 19 | 20 | expect(data).to.have.property('sessionId'); 21 | 22 | expect(data).to.have.property('userId'); 23 | 24 | }); 25 | 26 | it('should throw an error if username or password are not provided', () => expect(tidal.login()).to.eventually.be.rejectedWith(Error)); 27 | 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /test/playlist.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import Tidal from '../src'; 4 | 5 | chai.use(chaiAsPromised); 6 | 7 | describe('playlist', () => { 8 | let tidal; 9 | 10 | beforeEach(() => { 11 | tidal = new Tidal(); 12 | }); 13 | 14 | describe('getPlaylist', () => { 15 | 16 | it('should return the correct playlist object', async () => { 17 | const playlist = await tidal.getPlaylist('1c5d01ed-4f05-40c4-bd28-0f73099e9648'); 18 | 19 | expect(playlist).to.be.an('object') 20 | .and.to.include({ uuid: '1c5d01ed-4f05-40c4-bd28-0f73099e9648' }); 21 | 22 | }); 23 | 24 | }); 25 | 26 | describe('getPlaylistTracks', () => { 27 | 28 | it('should return an array of track objects from the specified playlist', async () => { 29 | const tracks = await tidal.getPlaylistTracks('1c5d01ed-4f05-40c4-bd28-0f73099e9648'); 30 | const track = tracks[0]; 31 | 32 | expect(tracks).to.be.an('array'); 33 | 34 | expect(track).to.be.an('object') 35 | .and.to.have.property('trackNumber'); 36 | }); 37 | 38 | }); 39 | 40 | describe('getFavoritePlaylists', () => { 41 | it('should reject if login() has not been called', () => { 42 | expect(tidal.getFavoritePlaylists()).to.eventually.be.rejectedWith(Error); 43 | }); 44 | 45 | it('should return an array of playlist objects', async () => { 46 | await tidal.login(process.env.USERNAME, process.env.PASSWORD); 47 | const favorites = await tidal.getFavoritePlaylists(); 48 | const playlist = favorites[0]; 49 | 50 | expect(favorites).to.be.an('array'); 51 | 52 | expect(playlist).to.be.an('object') 53 | .and.to.have.property('uuid'); 54 | }); 55 | 56 | }); 57 | 58 | describe('getPlaylists', () => { 59 | it('should reject if login() has not been called', () => expect(tidal.getPlaylists()).to.eventually.be.rejectedWith(Error)); 60 | 61 | it('should return an array of playlist objects', async () => { 62 | await tidal.login(process.env.USERNAME, process.env.PASSWORD); 63 | const playlists = await tidal.getPlaylists(); 64 | const playlist = playlists[0]; 65 | 66 | expect(playlists).to.be.an('array') 67 | .and.to.have.lengthOf(1); 68 | 69 | expect(playlist).to.be.an('object') 70 | .and.to.have.property('uuid'); 71 | }); 72 | 73 | }); 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /test/search.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import Tidal from '../src'; 4 | 5 | chai.use(chaiAsPromised); 6 | 7 | const tidal = new Tidal(); 8 | 9 | describe('search', () => { 10 | 11 | it('should return an array of 25 track objects', async () => { 12 | const tracks = await tidal.search('adele', 'tracks', 25); 13 | expect(tracks).to.be.an('array') 14 | .and.to.have.lengthOf(25); 15 | 16 | expect(tracks[0]).to.be.an('object') 17 | .and.to.have.property('trackNumber'); 18 | }); 19 | 20 | it('should throw an error when type is undefined', () => 21 | expect(tidal.search('adele')).to.be.rejectedWith(Error)); 22 | 23 | it('should throw an error when type is not valid', () => 24 | expect(tidal.search('adele', 'wrong')).to.be.rejectedWith(Error)); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /test/track.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import Tidal from '../src'; 4 | 5 | chai.use(chaiAsPromised); 6 | 7 | const tidal = new Tidal(); 8 | 9 | describe('track', () => { 10 | 11 | describe('getTrack', () => { 12 | 13 | it('should return the correct track object', async () => { 14 | const track = await tidal.getTrack(64975224); 15 | 16 | expect(track).to.be.an('object') 17 | .and.to.include({ id: 64975224 }); 18 | 19 | }); 20 | 21 | }); 22 | 23 | describe('getFavoriteTracks', () => { 24 | 25 | it('should reject if login() has not been called', () => expect(tidal.getFavoriteTracks()).to.eventually.be.rejectedWith(Error)); 26 | 27 | it('should return an array of track objects', async () => { 28 | 29 | await tidal.login(process.env.USERNAME, process.env.PASSWORD); 30 | const favorites = await tidal.getFavoriteTracks(); 31 | const track = favorites[0]; 32 | 33 | expect(favorites).to.be.an('array'); 34 | 35 | expect(track).to.be.an('object'); 36 | expect(track).to.have.property('title'); 37 | expect(track).to.have.property('album'); 38 | 39 | 40 | }); 41 | 42 | }); 43 | 44 | }); 45 | --------------------------------------------------------------------------------