├── .github
└── workflows
│ └── build.yml
├── .gitignore
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE.md
├── README.md
├── composer.json
├── docs
├── README.md
├── examples
│ ├── access-token-with-authorization-code-flow.md
│ ├── access-token-with-client-credentials-flow.md
│ ├── access-token-with-pkce-flow.md
│ ├── controlling-user-playback.md
│ ├── fetching-album-information.md
│ ├── fetching-artist-information.md
│ ├── fetching-audiobook-information.md
│ ├── fetching-podcast-information.md
│ ├── fetching-spotify-featured-content.md
│ ├── fetching-track-information.md
│ ├── following-artists-playlists-and-users.md
│ ├── handling-errors.md
│ ├── managing-user-library.md
│ ├── managing-user-playlists.md
│ ├── managing-user-profiles.md
│ ├── passing-a-custom-request-instance.md
│ ├── refreshing-access-tokens.md
│ ├── searching-the-spotify-catalog.md
│ ├── setting-custom-curl-options.md
│ ├── setting-options.md
│ └── working-with-scopes.md
├── getting-started.md
└── method-reference
│ ├── Request.md
│ ├── Session.md
│ ├── SpotifyWebAPI.md
│ ├── SpotifyWebAPIAuthException.md
│ └── SpotifyWebAPIException.md
├── phpcs.xml
├── phpunit.dist.xml
├── phpunit.php
├── src
├── Request.php
├── Session.php
├── SpotifyWebAPI.php
├── SpotifyWebAPIAuthException.php
├── SpotifyWebAPIException.php
└── cacert.pem
└── tests
├── RequestTest.php
├── SessionTest.php
├── SpotifyWebAPITest.php
└── fixtures
├── access-token.json
├── album-tracks.json
├── album.json
├── albums.json
├── artist-albums.json
├── artist-related-artists.json
├── artist-top-tracks.json
├── artist.json
├── artists.json
├── audio-analysis.json
├── audio-features.json
├── audiobook.json
├── audiobooks.json
├── available-genre-seeds.json
├── categories-list.json
├── category-playlists.json
├── category.json
├── chapter.json
├── chapters.json
├── episode.json
├── episodes.json
├── featured-playlists.json
├── markets.json
├── multiple-audio-features.json
├── my-playlists.json
├── my-queue.json
├── new-releases.json
├── playlist-cover-image.json
├── recently-played.json
├── recommendations.json
├── refresh-token-no-refresh-token.json
├── refresh-token.json
├── search-album.json
├── show-episodes.json
├── show.json
├── shows.json
├── snapshot-id.json
├── top-artists-and-tracks.json
├── track.json
├── tracks.json
├── user-albums-contains.json
├── user-albums.json
├── user-current-playback-info.json
├── user-current-track.json
├── user-devices.json
├── user-episodes-contains.json
├── user-episodes.json
├── user-followed-artists.json
├── user-follows-playlist.json
├── user-follows.json
├── user-playlist-tracks.json
├── user-playlist.json
├── user-playlists.json
├── user-shows-contains.json
├── user-shows.json
├── user-tracks-contains.json
├── user-tracks.json
├── user.json
└── users-follows-playlist.json
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on: [push, pull_request]
4 |
5 | env:
6 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | php-versions: ['8.4', '8.3', '8.2', '8.1']
14 |
15 | steps:
16 | - uses: actions/checkout@v4
17 |
18 | - uses: shivammathur/setup-php@v2
19 | with:
20 | php-version: ${{ matrix.php-versions }}
21 | coverage: pcov
22 |
23 | - run: composer install
24 | - run: composer test
25 | - run: php vendor/bin/php-coveralls -v
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .phpunit.cache
3 | build
4 | composer.lock
5 | phpunit.xml
6 | vendor
7 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribution Guide
2 |
3 | ## Issues
4 | Please submit all your bug reports, feature requests and pull requests here but note that this isn't the place for support requests. Please use [Stack Overflow](http://stackoverflow.com/) for this.
5 |
6 | ## Bug reports
7 | 1. Search the issues, has it already been reported?
8 | 2. Download the latest source, did this solve the problem?
9 | 4. If the answer to all of the above questions are "No" then open a bug report and include the following:
10 | * A short, descriptive title.
11 | * A summary of the problem.
12 | * The steps to reproduce the problem.
13 | * Possible solutions or other relevant information/suggestions.
14 |
15 | ## New features
16 | If you have an idea for a new feature, please file an issue first to see if it fits the scope of this project. That way no one's time needs to be wasted.
17 |
18 | ## Coding Guidelines
19 | We follow the coding standards outlined in [PSR-1](https://www.php-fig.org/psr/psr-1/) and [PSR-12](https://www.php-fig.org/psr/psr-12/). Please follow these guidelines when committing new code.
20 |
21 | In addition to the PSR guidelines we try to adhere to the following points:
22 | * We order all methods by visibility and then alphabetically, `private`/`protected` methods first and then `public`. For example:
23 |
24 | ```
25 | protected function b() {}
26 |
27 | public function a() {}
28 | ```
29 |
30 | instead of
31 |
32 | ```
33 | public function a() {}
34 |
35 | protected function b() {}
36 | ```
37 |
38 | * We strive to keep the inline documentation language consistent, take a look at existing docs for examples.
39 |
40 | Before committing any code, be sure to run `composer test` to ensure that the code style is consistent and all the tests pass.
41 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) Jonathan Wilsson
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spotify Web API PHP
2 |
3 | [](https://packagist.org/packages/jwilsson/spotify-web-api-php)
4 | 
5 | [](https://coveralls.io/r/jwilsson/spotify-web-api-php?branch=main)
6 |
7 | This is a PHP wrapper for [Spotify's Web API](https://developer.spotify.com/web-api/). It includes the following:
8 |
9 | * Helper methods for all API endpoints:
10 | * Information about artists, albums, tracks, podcasts, audiobooks, and users.
11 | * List music featured by Spotify.
12 | * Playlist and user music library management.
13 | * Spotify catalog search.
14 | * User playback control.
15 | * Authorization flow helpers.
16 | * Automatic refreshing of access tokens.
17 | * Automatic retry of rate limited requests.
18 | * PSR-4 autoloading support.
19 |
20 | ## Requirements
21 | * PHP 8.1 or later.
22 | * PHP [cURL extension](http://php.net/manual/en/book.curl.php) (Usually included with PHP).
23 |
24 | ## Installation
25 | Install it using [Composer](https://getcomposer.org/):
26 |
27 | ```sh
28 | composer require jwilsson/spotify-web-api-php
29 | ```
30 |
31 | ## Usage
32 | Before using the Spotify Web API, you'll need to create an app at [Spotify’s developer site](https://developer.spotify.com/web-api/).
33 |
34 | *Note: Applications created after 2021-05-27 [might need to perform some extra steps](https://developer.spotify.com/community/news/2021/05/27/improving-the-developer-and-user-experience-for-third-party-apps/).*
35 |
36 | Simple example displaying a user's profile:
37 | ```php
38 | require 'vendor/autoload.php';
39 |
40 | $session = new SpotifyWebAPI\Session(
41 | 'CLIENT_ID',
42 | 'CLIENT_SECRET',
43 | 'REDIRECT_URI'
44 | );
45 |
46 | $api = new SpotifyWebAPI\SpotifyWebAPI();
47 |
48 | if (isset($_GET['code'])) {
49 | $session->requestAccessToken($_GET['code']);
50 | $api->setAccessToken($session->getAccessToken());
51 |
52 | print_r($api->me());
53 | } else {
54 | $options = [
55 | 'scope' => [
56 | 'user-read-email',
57 | ],
58 | ];
59 |
60 | header('Location: ' . $session->getAuthorizeUrl($options));
61 | die();
62 | }
63 | ```
64 |
65 | For more instructions and examples, check out the [documentation](/docs/).
66 |
67 | The [Spotify Web API Console](https://developer.spotify.com/web-api/console/) can also be of great help when trying out the API.
68 |
69 | ## Contributing
70 | Contributions are more than welcome! See [CONTRIBUTING.md](/CONTRIBUTING.md) for more info.
71 |
72 | ## License
73 | MIT license. Please see [LICENSE.md](LICENSE.md) for more info.
74 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jwilsson/spotify-web-api-php",
3 | "description": "A PHP wrapper for Spotify's Web API.",
4 | "type": "library",
5 | "license": "MIT",
6 | "keywords": [
7 | "spotify"
8 | ],
9 | "homepage": "https://github.com/jwilsson/spotify-web-api-php",
10 | "authors": [
11 | {
12 | "name": "Jonathan Wilsson",
13 | "email": "jonathan.wilsson@gmail.com"
14 | }
15 | ],
16 | "require": {
17 | "php": "^8.1",
18 | "ext-curl": "*"
19 | },
20 | "require-dev": {
21 | "php-coveralls/php-coveralls": "^2.5",
22 | "php-mock/php-mock-phpunit": "^2.7",
23 | "phpunit/phpunit": "^10.2",
24 | "squizlabs/php_codesniffer": "^3.0"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "SpotifyWebAPI\\": "src/"
29 | }
30 | },
31 | "scripts": {
32 | "test": "phpcs src -v && phpunit"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Spotify Web API for PHP Documentation
2 | There are a lot of things possible with the Spotify Web API and these pages exist to give you an overview of how to make the most out of them. Which methods and options are available, how to use them, and what to do when something goes wrong.
3 |
4 | First, start by checking out the [Getting started guide](/docs/getting-started.md) before continuing with the examples below.
5 |
6 | ## Examples
7 | * **Authorization**
8 | * [Obtaining an access token using the Authorization Code Flow](/docs/examples/access-token-with-authorization-code-flow.md)
9 | * [Obtaining an access token using the Client Credentials Flow](/docs/examples/access-token-with-client-credentials-flow.md)
10 | * [Obtaining an access token using the Proof Key for Code Exchange (PKCE) Flow](/docs/examples/access-token-with-pkce-flow.md)
11 | * [Refreshing access tokens](/docs/examples/refreshing-access-tokens.md)
12 | * [Working with scopes](/docs/examples/working-with-scopes.md)
13 | * **Fetching data**
14 | * [Fetching information about albums](/docs/examples/fetching-album-information.md)
15 | * [Fetching information about artists](/docs/examples/fetching-artist-information.md)
16 | * [Fetching information about audiobooks](/docs/examples/fetching-audiobook-information.md)
17 | * [Fetching information about podcasts](/docs/examples/fetching-podcast-information.md)
18 | * [Fetching information about tracks](/docs/examples/fetching-track-information.md)
19 | * [Fetching Spotify featured content](/docs/examples/fetching-spotify-featured-content.md)
20 | * [Searching the Spotify catalog](/docs/examples/searching-the-spotify-catalog.md)
21 | * **Managing users**
22 | * [Controlling user playback](/docs/examples/controlling-user-playback.md)
23 | * [Following artists, playlists, and users](/docs/examples/following-artists-playlists-and-users.md)
24 | * [Managing a user's library](/docs/examples/managing-user-library.md)
25 | * [Managing a user's playlists](/docs/examples/managing-user-playlists.md)
26 | * [Managing a user's profile](/docs/examples/managing-user-profiles.md)
27 | * **Working with the API**
28 | * [Handling errors](/docs/examples/handling-errors.md)
29 | * [Passing a custom Request instance](/docs/examples/passing-a-custom-request-instance.md)
30 | * [Setting custom cURL options](/docs/examples/setting-custom-curl-options.md)
31 | * [Setting options](/docs/examples/setting-options.md)
32 |
33 | ## Method Reference
34 | A full method reference listing all public methods is [available here](/docs/method-reference/).
35 |
--------------------------------------------------------------------------------
/docs/examples/access-token-with-authorization-code-flow.md:
--------------------------------------------------------------------------------
1 | # Authorization Using the Authorization Code Flow
2 |
3 | All API methods require authorization. Before using these methods you'll need to create an app at [Spotify's developer site](https://developer.spotify.com/documentation/web-api/).
4 |
5 | The Authorization Code flow method requires some interaction from the user but in turn allows access to user information. There are two steps required to authenticate the user. The first step is to request access to the user's account and data (known as *scopes*) and redirecting them to your app's authorize URL (also known as the callback URL).
6 |
7 | ### Step 1
8 | Put the following code in its own file, lets call it `auth.php`. Replace `CLIENT_ID` and `CLIENT_SECRET` with the values given to you by Spotify. The `REDIRECT_URI` is the one you entered when creating the Spotify app, make sure it's an exact match.
9 |
10 | ```php
11 | require 'vendor/autoload.php';
12 |
13 | $session = new SpotifyWebAPI\Session(
14 | 'CLIENT_ID',
15 | 'CLIENT_SECRET',
16 | 'REDIRECT_URI'
17 | );
18 |
19 | $state = $session->generateState();
20 | $options = [
21 | 'scope' => [
22 | 'playlist-read-private',
23 | 'user-read-private',
24 | ],
25 | 'state' => $state,
26 | ];
27 |
28 | header('Location: ' . $session->getAuthorizeUrl($options));
29 | die();
30 | ```
31 |
32 | __Note:__ The `state` parameter is optional but highly recommended to prevent CSRF attacks. The value will need to be stored between requests and verfied when the user is redirected back to your application from Spotify.
33 |
34 | To read more about scopes, see [Working with Scopes](/docs/examples/working-with-scopes.md). To see all of the available options for `getAuthorizeUrl()`, refer to the [method reference](/docs/method-reference/Session.md#getauthorizeurl).
35 |
36 | ### Step 2
37 | When the user has approved your app, Spotify will redirect the user together with a `code` to the specifed redirect URI. You'll need to use this code to request a access token from Spotify.
38 |
39 | __Note:__ The API wrapper does not include any token management. It's up to you to save the access token somewhere (in a database, a PHP session, or wherever appropriate for your application) and request a new access token when the old one has expired.
40 |
41 | Lets put this code in a new file called `callback.php`:
42 |
43 | ```php
44 | require 'vendor/autoload.php';
45 |
46 | $session = new SpotifyWebAPI\Session(
47 | 'CLIENT_ID',
48 | 'CLIENT_SECRET',
49 | 'REDIRECT_URI'
50 | );
51 |
52 | $state = $_GET['state'];
53 |
54 | // Fetch the stored state value from somewhere. A session for example
55 |
56 | if ($state !== $storedState) {
57 | // The state returned isn't the same as the one we've stored, we shouldn't continue
58 | die('State mismatch');
59 | }
60 |
61 | // Request a access token using the code from Spotify
62 | $session->requestAccessToken($_GET['code']);
63 |
64 | $accessToken = $session->getAccessToken();
65 | $refreshToken = $session->getRefreshToken();
66 |
67 | // Store the access and refresh tokens somewhere. In a session for example
68 |
69 | // Send the user along and fetch some data!
70 | header('Location: app.php');
71 | die();
72 | ```
73 |
74 | When requesting a access token, a **refresh token** will also be included. This can be used to extend the validity of access tokens. It's recommended to also store this somewhere persistent, in a database for example. [Read more about refresh tokens here](refreshing-access-tokens.md).
75 |
76 | ### Step 3
77 | In a third file, `app.php`, tell the API wrapper which access token to use, and then make some API calls!
78 |
79 | ```php
80 | require 'vendor/autoload.php';
81 |
82 | $api = new SpotifyWebAPI\SpotifyWebAPI();
83 |
84 | // Fetch the saved access token from somewhere. A session for example.
85 | $api->setAccessToken($accessToken);
86 |
87 | // It's now possible to request data about the currently authenticated user
88 | print_r(
89 | $api->me()
90 | );
91 |
92 | // Getting Spotify catalog data is of course also possible
93 | print_r(
94 | $api->getTrack('7EjyzZcbLxW7PaaLua9Ksb')
95 | );
96 | ```
97 |
98 | For more in-depth technical information about the Authorization Code flow, please refer to the [Spotify Web API documentation](https://developer.spotify.com/documentation/general/guides/authorization/code-flow/).
99 |
--------------------------------------------------------------------------------
/docs/examples/access-token-with-client-credentials-flow.md:
--------------------------------------------------------------------------------
1 | # Authorization Using the Client Credentials Flow
2 |
3 | All API methods require authorization. Before using these methods you'll need to create an app at [Spotify's developer site](https://developer.spotify.com/documentation/web-api/).
4 |
5 | This method doesn't require any user interaction and no access to user information is therefore granted. This is the recommended method if you only need access to Spotify catalog data.
6 |
7 | ## Step 1
8 | The first step is to request a access token. Put the following code in its own file, lets call it `auth.php`. Replace `CLIENT_ID` and `CLIENT_SECRET` with the values given to you by Spotify.
9 |
10 | __Note:__ The API wrapper does not include any token management. It's up to you to save the access token somewhere (in a database, a PHP session, or wherever appropriate for your application) and request a new access token when the old one has expired.
11 |
12 | ```php
13 | require 'vendor/autoload.php';
14 |
15 | $session = new SpotifyWebAPI\Session(
16 | 'CLIENT_ID',
17 | 'CLIENT_SECRET'
18 | );
19 |
20 | $session->requestCredentialsToken();
21 | $accessToken = $session->getAccessToken();
22 |
23 | // Store the access token somewhere. In a database for example.
24 |
25 | // Send the user along and fetch some data!
26 | header('Location: app.php');
27 | die();
28 | ```
29 |
30 | You'll notice the missing redirect URI when initializing the `Session`. When using the Client Credentials Flow, it isn't needed and can simply be omitted from the constructor call.
31 |
32 | ## Step 2
33 | In a second file, `app.php`, tell the API wrapper which access token to use, and then make some API calls!
34 |
35 | ```php
36 | require 'vendor/autoload.php';
37 |
38 | // Fetch the saved access token from somewhere. A database for example.
39 |
40 | $api = new SpotifyWebAPI\SpotifyWebAPI();
41 | $api->setAccessToken($accessToken);
42 |
43 | // It's now possible to request data from the Spotify catalog
44 | print_r(
45 | $api->getTrack('7EjyzZcbLxW7PaaLua9Ksb')
46 | );
47 | ```
48 |
49 | For more in-depth technical information about the Client Credentials Flow, please refer to the [Spotify Web API documentation](https://developer.spotify.com/documentation/general/guides/authorization/client-credentials/).
50 |
--------------------------------------------------------------------------------
/docs/examples/access-token-with-pkce-flow.md:
--------------------------------------------------------------------------------
1 | # Authorization Using the Proof Key for Code Exchange (PKCE) Flow
2 |
3 | All API methods require authorization. Before using these methods you'll need to create an app at [Spotify's developer site](https://developer.spotify.com/documentation/web-api/).
4 |
5 | The Proof Key for Code Exchange Flow is very similar to the [Authorization Code flow](access-token-with-authorization-code-flow.md), but instead of using a client secret which might not always be viable it uses a code challenge flow.
6 |
7 | Just like the Authorization Code flow, this method requires some interaction from the user but in turn allows access to user information. There are two steps required to authenticate the user. The first step is to request access to the user's account and data (known as *scopes*) and redirecting them to your app's authorize URL (also known as the callback URL).
8 |
9 | ### Step 1
10 | Put the following code in its own file, lets call it `auth.php`. Replace `CLIENT_ID` with the value given to you by Spotify. The `REDIRECT_URI` is the one you entered when creating the Spotify app, make sure it's an exact match. You'll also need to create a *code verifier* and store it somewhere between requests. It will be used again in the second step.
11 |
12 | ```php
13 | require 'vendor/autoload.php';
14 |
15 | $session = new SpotifyWebAPI\Session(
16 | 'CLIENT_ID',
17 | '', // Normally the client secret, but this value can be omitted when using the PKCE flow
18 | 'REDIRECT_URI'
19 | );
20 |
21 | $verifier = $session->generateCodeVerifier(); // Store this value somewhere, a session for example
22 | $challenge = $session->generateCodeChallenge($verifier);
23 | $state = $session->generateState();
24 |
25 | $options = [
26 | 'code_challenge' => $challenge,
27 | 'scope' => [
28 | 'playlist-read-private',
29 | 'user-read-private',
30 | ],
31 | 'state' => $state,
32 | ];
33 |
34 | header('Location: ' . $session->getAuthorizeUrl($options));
35 | die();
36 | ```
37 |
38 | __Note:__ The `state` parameter is optional but highly recommended to prevent CSRF attacks. The value will need to be stored between requests and verfied when the user is redirected back to your application from Spotify.
39 |
40 | To read more about scopes, see [Working with Scopes](/docs/examples/working-with-scopes.md). To see all of the available options for `getAuthorizeUrl()`, refer to the [method reference](/docs/method-reference/Session.md#getauthorizeurl).
41 |
42 | ### Step 2
43 | When the user has approved your app, Spotify will redirect the user together with a `code` to the specifed redirect URI. You'll need to use this code to request a access token from Spotify. The *code verifier* created in the previous step will also be needed.
44 |
45 | __Note:__ The API wrapper does not include any token management. It's up to you to save the access token somewhere (in a database, a PHP session, or wherever appropriate for your application) and request a new access token when the old one has expired.
46 |
47 | Lets put this code in a new file called `callback.php`:
48 |
49 | ```php
50 | require 'vendor/autoload.php';
51 |
52 | $session = new SpotifyWebAPI\Session(
53 | 'CLIENT_ID',
54 | 'CLIENT_SECRET',
55 | 'REDIRECT_URI'
56 | );
57 |
58 | $state = $_GET['state'];
59 |
60 | // Fetch the stored state value from somewhere. A session for example
61 |
62 | if ($state !== $storedState) {
63 | // The state returned isn't the same as the one we've stored, we shouldn't continue
64 | die('State mismatch');
65 | }
66 |
67 | // Request a access token using the code from Spotify and the previously created code verifier
68 | $session->requestAccessToken($_GET['code'], $verifier);
69 |
70 | $accessToken = $session->getAccessToken();
71 | $refreshToken = $session->getRefreshToken();
72 |
73 | // Store the access and refresh tokens somewhere. In a session for example
74 |
75 | // Send the user along and fetch some data!
76 | header('Location: app.php');
77 | die();
78 | ```
79 |
80 | When requesting a access token, a **refresh token** will also be included. This can be used to extend the validity of access tokens. It's recommended to also store this somewhere persistent, in a database for example. [Read more about refresh tokens here](refreshing-access-tokens.md).
81 |
82 | ### Step 3
83 | In a third file, `app.php`, tell the API wrapper which access token to use, and then make some API calls!
84 |
85 | ```php
86 | require 'vendor/autoload.php';
87 |
88 | $api = new SpotifyWebAPI\SpotifyWebAPI();
89 |
90 | // Fetch the saved access token from somewhere. A session for example.
91 | $api->setAccessToken($accessToken);
92 |
93 | // It's now possible to request data about the currently authenticated user
94 | print_r(
95 | $api->me()
96 | );
97 |
98 | // Getting Spotify catalog data is of course also possible
99 | print_r(
100 | $api->getTrack('7EjyzZcbLxW7PaaLua9Ksb')
101 | );
102 | ```
103 |
104 | For more in-depth technical information about the Proof Key for Code Exchange flow, please refer to the [Spotify Web API documentation](https://developer.spotify.com/documentation/general/guides/authorization/code-flow/).
105 |
--------------------------------------------------------------------------------
/docs/examples/controlling-user-playback.md:
--------------------------------------------------------------------------------
1 | # Controlling User Playback
2 |
3 | Using Spotify Connect, it's possible to control the playback of the currently authenticated user.
4 |
5 | ## Getting user devices
6 |
7 | The `SpotifyWebAPI::getMyDevices()` method can be used to list out a user's devices.
8 |
9 | ```php
10 | // Get the Devices
11 | $api->getMyDevices();
12 | ```
13 |
14 | It is worth noting that if all devices have `is_active` set to `false` then using `SpotifyWebApi::play()` will fail.
15 |
16 | ## Start and stop playback
17 | ```php
18 | // With Device ID
19 | $api->play($deviceId, [
20 | 'uris' => ['TRACK_URI'],
21 | ]);
22 |
23 | // Without Device ID
24 | $api->play(false, [
25 | 'uris' => ['TRACK_URI'],
26 | ]);
27 |
28 | $api->pause();
29 | ```
30 |
31 | ## Playing the next or previous track
32 | ```php
33 | $api->previous();
34 |
35 | $api->next();
36 | ```
37 |
38 | ## Adding a track to the queue
39 | ```php
40 | $api->queue('TRACK_ID');
41 | ```
42 |
43 | ## Get info about the queue
44 | ```php
45 | $api->getMyQueue();
46 | ```
47 |
48 | ## Get the currently playing track
49 | ```php
50 | $api->getMyCurrentTrack();
51 | ```
52 |
53 | ## Get info about the current playback
54 | ```php
55 | $api->getMyCurrentPlaybackInfo();
56 | ```
57 |
58 | ## Move to a specific position in a track
59 | ```php
60 | $api->seek([
61 | 'position_ms' => 60000 + 37000, // Move to the 1.37 minute mark
62 | ]);
63 | ```
64 |
65 | ## Set repeat and shuffle mode
66 | ```php
67 | $api->repeat([
68 | 'state' => 'track',
69 | ]);
70 |
71 | $api->shuffle([
72 | 'state' => false,
73 | ]);
74 | ```
75 |
76 | ## Control the volume
77 | ```php
78 | $api->changeVolume([
79 | 'volume_percent' => 78,
80 | ]);
81 | ```
82 |
83 | ## Retrying API calls
84 | Sometimes, a API call might return a `202 Accepted` response code. When this occurs, you should retry the request after a few seconds. For example:
85 |
86 | ```php
87 | try {
88 | $wasPaused = $api->pause():
89 |
90 | if (!$wasPaused) {
91 | $lastResponse = $api->getLastResponse();
92 |
93 | if ($lastResponse['status'] == 202) {
94 | // Perform some logic to retry the request after a few seconds
95 | }
96 | }
97 | } catch (Exception $e) {
98 | $reason = $e->getReason();
99 |
100 | // Check the reason for the failure and handle the error
101 | }
102 | ```
103 |
104 | Read more about working with Spotify Connect in the [Spotify API docs](https://developer.spotify.com/documentation/web-api/guides/using-connect-web-api/).
105 |
--------------------------------------------------------------------------------
/docs/examples/fetching-album-information.md:
--------------------------------------------------------------------------------
1 | # Fetching Information About Albums
2 |
3 | There are a few methods for retrieving information about one or more albums from the Spotify catalog. For example, info about a albums's artist or all the tracks on an album.
4 |
5 | ## Getting info about a single album
6 |
7 | ```php
8 | $album = $api->getAlbum('ALBUM_ID');
9 |
10 | echo '' . $album->name . '';
11 | ```
12 |
13 | ## Getting info about multiple albums
14 |
15 | ```php
16 | $albums = $api->getAlbums([
17 | 'ALBUM_ID',
18 | 'ALBUM_ID',
19 | ]);
20 |
21 | foreach ($albums->albums as $album) {
22 | echo '' . $album->name . '
';
23 | }
24 | ```
25 |
26 | ## Getting all tracks on an album
27 |
28 | ```php
29 | $tracks = $api->getAlbumTracks('ALBUM_ID');
30 |
31 | foreach ($tracks->items as $track) {
32 | echo '' . $track->name . '
';
33 | }
34 | ```
35 |
36 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
37 |
--------------------------------------------------------------------------------
/docs/examples/fetching-artist-information.md:
--------------------------------------------------------------------------------
1 | # Fetching Information About Artists
2 |
3 | There are a few methods for retrieving information about one or more albums from the Spotify catalog. For example, info about a single artist or an artist's top tracks in a country.
4 |
5 | ## Getting info about a single artist
6 |
7 | ```php
8 | $artist = $api->getArtist('ARTIST_ID');
9 |
10 | echo '' . $artist->name . '';
11 | ```
12 |
13 | ## Getting info about multiple artists
14 |
15 | ```php
16 | $artists = $api->getArtists([
17 | 'ARTIST_ID',
18 | 'ARTIST_ID',
19 | ]);
20 |
21 | foreach ($artists->artists as $artist) {
22 | echo '' . $artist->name . '
';
23 | }
24 | ```
25 |
26 | ## Getting an artist's albums
27 |
28 | ```php
29 | $albums = $api->getArtistAlbums('ARTIST_ID');
30 |
31 | foreach ($albums->items as $album) {
32 | echo '' . $album->name . '
';
33 | }
34 | ```
35 |
36 | ## Getting an artist's related artists
37 |
38 | ```php
39 | $artists = $api->getArtistRelatedArtists('ARTIST_ID');
40 |
41 | foreach ($artists->artists as $artist) {
42 | echo '' . $artist->name . '
';
43 | }
44 | ```
45 |
46 | ## Getting an artist’s top tracks in a country
47 |
48 | ```php
49 | $tracks = $api->getArtistTopTracks('ARTIST_ID', [
50 | 'country' => 'se',
51 | ]);
52 |
53 | foreach ($tracks->tracks as $track) {
54 | echo '' . $track->name . '
';
55 | }
56 | ```
57 |
58 | ## Getting recommendations based on artists
59 |
60 | ```php
61 | $seedArtist = ['ARTIST_ID', 'ARTIST_ID'];
62 |
63 | $recommendations = $api->getRecommendations([
64 | 'seed_artists' => $seedArtist,
65 | ]);
66 |
67 | print_r($recommendations);
68 | ```
69 |
70 | It's also possible to fetch recommendations based on genres and tracks, see the [Spotify docs](https://developer.spotify.com/documentation/web-api/reference/get-recommendations) for more info.
71 |
72 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
73 |
--------------------------------------------------------------------------------
/docs/examples/fetching-audiobook-information.md:
--------------------------------------------------------------------------------
1 | # Fetching Information About Audiobooks
2 |
3 | There are a few methods for retrieving information about one or more audiobooks from the Spotify catalog. For example, the description of a audiobook or all of a audiobook's chapters.
4 |
5 | ## Getting info about a single audiobook
6 |
7 | ```php
8 | $audiobook = $api->getAudiobook('AUDIOBOOK_ID');
9 |
10 | echo '' . $audiobook->name . '';
11 | ```
12 |
13 | ## Getting info about multiple audiobooks
14 |
15 | ```php
16 | $audiobooks = $api->getAudiobooks([
17 | 'AUDIOBOOK_ID',
18 | 'AUDIOBOOK_ID',
19 | ]);
20 |
21 | foreach ($audiobooks->audiobooks as $audiobook) {
22 | echo '' . $audiobook->name . '
';
23 | }
24 | ```
25 |
26 | ## Getting info about a single audiobook chapter
27 |
28 | ```php
29 | $chapter = $api->getChapter('CHAPTER_ID');
30 |
31 | echo '' . $chapter->name . '';
32 | ```
33 |
34 | ## Getting info about multiple audiobook chapters
35 |
36 | ```php
37 | $chapters = $api->getChapters([
38 | 'CHAPTER_ID',
39 | 'CHAPTER_ID',
40 | ]);
41 |
42 | foreach ($chapters->chapters as $chapter) {
43 | echo '' . $chapter->name . '
';
44 | }
45 | ```
46 |
47 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
48 |
--------------------------------------------------------------------------------
/docs/examples/fetching-podcast-information.md:
--------------------------------------------------------------------------------
1 | # Fetching Information About Podcasts
2 |
3 | There are a few methods for retrieving information about one or more podcasts from the Spotify catalog. For example, the description of a podcast or all of a podcast's episodes.
4 |
5 | ## Getting info about a single podcast show
6 |
7 | ```php
8 | $show = $api->getShow('SHOW_ID');
9 |
10 | echo '' . $show->name . '';
11 | ```
12 |
13 | ## Getting info about multiple podcast shows
14 |
15 | ```php
16 | $shows = $api->getShows([
17 | 'SHOW_ID',
18 | 'SHOW_ID',
19 | ]);
20 |
21 | foreach ($shows->shows as $show) {
22 | echo '' . $show->name . '
';
23 | }
24 | ```
25 |
26 | ## Getting info about a single podcast episode
27 |
28 | ```php
29 | $episode = $api->getEpisode('EPISODE_ID');
30 |
31 | echo '' . $episode->name . '';
32 | ```
33 |
34 | ## Getting info about multiple podcast episodes
35 |
36 | ```php
37 | $episodes = $api->getEpisodeS([
38 | 'EPISODE_ID',
39 | 'EPISODE_ID',
40 | ]);
41 |
42 | foreach ($episodes->episodes as $episode) {
43 | echo '' . $episode->name . '
';
44 | }
45 | ```
46 |
47 | ## Getting a podcast show's episodes
48 |
49 | ```php
50 | $episodes = $api->getShowEpisodes('SHOW_ID');
51 |
52 | foreach ($episodes->items as $episode) {
53 | echo '' . $episode->name . '
';
54 | }
55 | ```
56 |
57 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
58 |
--------------------------------------------------------------------------------
/docs/examples/fetching-spotify-featured-content.md:
--------------------------------------------------------------------------------
1 | # Fetching Spotify Featured Content
2 |
3 | If you wish to access content that's featured and/or curated by Spotify there are a number of methods available to achieve that.
4 |
5 | ## Getting a list of new releases
6 |
7 | ```php
8 | $releases = $api->getNewReleases([
9 | 'country' => 'se',
10 | ]);
11 |
12 | foreach ($releases->albums->items as $album) {
13 | echo '' . $album->name . '
';
14 | }
15 | ```
16 |
17 | ## Getting a list of featured playlists
18 |
19 | ```php
20 | $playlists = $api->getFeaturedPlaylists([
21 | 'country' => 'se',
22 | 'locale' => 'sv_SE',
23 | 'timestamp' => '2015-01-17T21:00:00', // Saturday night
24 | ]);
25 |
26 | foreach ($playlists->playlists->items as $playlist) {
27 | echo '' . $playlist->name . '
';
28 | }
29 | ```
30 |
31 | ## Getting a list of Spotify categories
32 |
33 | ```php
34 | $categories = $api->getCategoriesList([
35 | 'country' => 'se',
36 | 'locale' => 'sv_SE',
37 | 'limit' => 10,
38 | 'offset' => 0,
39 | ]);
40 |
41 | foreach ($categories->categories->items as $category) {
42 | echo '' . $category->name . '
';
43 | }
44 |
45 | ## Getting a single Spotify category
46 |
47 | ```php
48 | $category = $api->getCategory('dinner', [
49 | 'country' => 'se',
50 | ]);
51 |
52 | echo '' . $category->name . '';
53 | ```
54 |
55 | ## Getting a category's playlists
56 |
57 | ```php
58 | $playlists = $api->getCategoryPlaylists('dinner', [
59 | 'country' => 'se',
60 | 'limit' => 10,
61 | 'offset' => 0
62 | ]);
63 |
64 | foreach ($playlists->playlists->items as $playlist) {
65 | echo '' . $playlist->name . '
';
66 | }
67 | ```
68 |
69 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
70 |
--------------------------------------------------------------------------------
/docs/examples/fetching-track-information.md:
--------------------------------------------------------------------------------
1 | # Fetching Information About Tracks
2 |
3 | There are a few methods for retrieving information about one or more albums from the Spotify catalog. For example, info about a track's artist or recommendations on similar tracks.
4 |
5 | ## Getting info about a single track
6 |
7 | ```php
8 | $track = $api->getTrack('TRACK_ID');
9 |
10 | echo '' . $track->name . ' by ' . $track->artists[0]->name . '';
11 | ```
12 |
13 | ## Getting info about multiple tracks
14 |
15 | ```php
16 | $tracks = $api->getTracks([
17 | 'TRACK_ID',
18 | 'TRACK_ID',
19 | ]);
20 |
21 | foreach ($tracks->tracks as $track) {
22 | echo '' . $track->name . ' by ' . $track->artists[0]->name . '
';
23 | }
24 | ```
25 |
26 | ## Getting the audio analysis of a track
27 |
28 | ```php
29 | $analysis = $api->getAudioAnalysis('TRACK_ID');
30 |
31 | print_r($analysis);
32 | ```
33 |
34 | ## Getting the audio features of a track
35 |
36 | ```php
37 | $features = $api->getAudioFeatures('TRACK_ID');
38 |
39 | print_r($features);
40 | ```
41 |
42 | ## Getting the audio features of multiple tracks
43 |
44 | ```php
45 | $tracks = [
46 | 'TRACK_ID',
47 | 'TRACK_ID',
48 | ];
49 |
50 | $features = $api->getMultipleAudioFeatures($tracks);
51 |
52 | print_r($features);
53 | ```
54 |
55 | ## Getting recommendations based on tracks
56 |
57 | ```php
58 | $seedTracks = ['TRACK_ID', 'TRACK_ID'];
59 |
60 | $recommendations = $api->getRecommendations([
61 | 'seed_tracks' => $seedTracks,
62 | ]);
63 |
64 | print_r($recommendations);
65 | ```
66 |
67 | It's also possible to fetch recommendations based on genres and artists, see the [Spotify docs](https://developer.spotify.com/documentation/web-api/reference/get-recommendations) for more info.
68 |
69 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
70 |
--------------------------------------------------------------------------------
/docs/examples/following-artists-playlists-and-users.md:
--------------------------------------------------------------------------------
1 | # Following Artists, Playlists, and Users
2 |
3 | A Spotify user can follow artists, playlists, and users. The API contains methods for all of this functionality.
4 |
5 | ## Following an artist or user
6 |
7 | ```php
8 | $api->followArtistsOrUsers('artist', 'ARTIST_ID');
9 | ```
10 |
11 | ## Unfollowing an artist or user
12 |
13 | ```php
14 | $api->unfollowArtistsOrUsers('artist', 'ARTIST_ID');
15 | ```
16 |
17 | ## Checking if a user is following an artist or user
18 |
19 | ```php
20 | $following = $api->currentUserFollows('user', 'spotify');
21 |
22 | var_dump($following);
23 | ```
24 |
25 | ## Following a playlist
26 |
27 | ```php
28 | $api->followPlaylist('PLAYLIST_ID');
29 | ```
30 |
31 | ## Unfollowing a playlist
32 |
33 | ```php
34 | $api->unfollowPlaylist('PLAYLIST_ID');
35 | ```
36 |
37 | ## Checking if user(s) are following a playlist
38 |
39 | ```php
40 | $users = [
41 | 'USER_1',
42 | 'USER_2',
43 | ];
44 |
45 | $api->usersFollowsPlaylist('PLAYLIST_ID', [
46 | 'ids' => $users,
47 | ]);
48 | ```
49 |
50 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
51 |
--------------------------------------------------------------------------------
/docs/examples/handling-errors.md:
--------------------------------------------------------------------------------
1 | # Handling Errors
2 |
3 | Whenever the API returns a error of some sort, a `SpotifyWebAPIException` extending from the native [PHP Exception](http://php.net/manual/en/language.exceptions.php) will be thrown.
4 |
5 | The `message` property will be set to the error message returned by the Spotify API and the `code` property will be set to the HTTP status code returned by the Spotify API.
6 |
7 | ```php
8 | try {
9 | $track = $api->getTrack('non-existing-track');
10 | } catch (SpotifyWebAPI\SpotifyWebAPIException $e) {
11 | echo 'Spotify API Error: ' . $e->getCode(); // Will be 404
12 | }
13 | ```
14 |
15 | When an authentication error occurs, a `SpotifyWebAPIAuthException` will be thrown. This will contain the same properties as above.
16 |
17 | ## Handling expired access tokens
18 | _As of version `2.11.0` it's possible to automatically refresh expired access tokens. [Read more here](refreshing-access-tokens.md#automatically-refreshing-access-tokens)._
19 |
20 | When the access token has expired you'll get an error back. The `SpotifyWebAPIException` class supplies a helper method to easily check if an expired access token is the issue.
21 |
22 | ```php
23 | try {
24 | $track = $api->me();
25 | } catch (SpotifyWebAPI\SpotifyWebAPIException $e) {
26 | if ($e->hasExpiredToken()) {
27 | // Refresh the access token
28 | } else {
29 | // Some other kind of error
30 | }
31 | }
32 | ```
33 |
34 | Read more about how to [refresh access tokens](refreshing-access-tokens.md).
35 |
36 | ## Handling rate limit errors
37 | _As of version `2.12.0` it's possible to automatically retry rate limited requests by setting the `auto_retry` option to `true`._
38 |
39 | If your application should hit the Spotify API rate limit, you will get an error back and the number of seconds you need to wait before sending another request.
40 |
41 | Here's an example of how to handle this:
42 |
43 | ```php
44 | try {
45 | $track = $api->getTrack('7EjyzZcbLxW7PaaLua9Ksb');
46 | } catch (SpotifyWebAPI\SpotifyWebAPIException $e) {
47 | if ($e->getCode() == 429) { // 429 is Too Many Requests
48 | $lastResponse = $api->getRequest()->getLastResponse();
49 |
50 | $retryAfter = $lastResponse['headers']['retry-after']; // Number of seconds to wait before sending another request
51 | } else {
52 | // Some other kind of error
53 | }
54 | }
55 | ```
56 |
57 | Read more about the exact mechanics of rate limiting in the [Spotify API docs](https://developer.spotify.com/documentation/web-api/guides/rate-limits/).
58 |
--------------------------------------------------------------------------------
/docs/examples/managing-user-library.md:
--------------------------------------------------------------------------------
1 | # Managing a User's Library
2 |
3 | There are lots of operations involving a user's library that can be performed. Remember to request the correct [scopes](working-with-scopes.md) beforehand.
4 |
5 | ## Listing the tracks in a user's library
6 |
7 | ```php
8 | $tracks = $api->getMySavedTracks([
9 | 'limit' => 5,
10 | ]);
11 |
12 | foreach ($tracks->items as $track) {
13 | $track = $track->track;
14 |
15 | echo '' . $track->name . '
';
16 | }
17 | ```
18 |
19 | It's also possible to list the albums, podcast episodes, or podcast shows in a user's library using `getMySavedAlbums`, `getMySavedEpisodes`, or `getMySavedShows`.
20 |
21 | ## Adding tracks to a user's library
22 |
23 | ```php
24 | $api->addMyTracks([
25 | 'TRACK_ID',
26 | 'TRACK_ID',
27 | ]);
28 | ```
29 |
30 | It's also possible to add an album, a podcast episode, or a podcast show to a user's library using `addMyAlbums`, `addMyEpisodes`, or `addMyShows`.
31 |
32 | ## Deleting tracks from a user's library
33 |
34 | ```php
35 | $api->deleteMyTracks([
36 | 'TRACK_ID',
37 | 'TRACK_ID',
38 | ]);
39 | ```
40 |
41 | It's also possible to delete an album, a podcast episode, or a podcast show from a user's library using `deleteMyAlbums`, `deleteMyEpisodes`, or `deleteMyShows`.
42 |
43 | ## Checking if tracks are present in a user's library
44 |
45 | ```php
46 | $contains = $api->myTracksContains([
47 | 'TRACK_ID',
48 | 'TRACK_ID',
49 | ]);
50 |
51 | var_dump($contains);
52 | ```
53 |
54 | It's also possible to check if an album, a podcast episode, or a podcast show is present in a user's library using `myAlbumsContains`, `myEpisodesContains`, or `myShowsContains`.
55 |
56 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
57 |
--------------------------------------------------------------------------------
/docs/examples/managing-user-playlists.md:
--------------------------------------------------------------------------------
1 | # Managing a User's Playlists
2 |
3 | There are lots of operations involving user's playlists that can be performed. Remember to request the correct [scopes](working-with-scopes.md) beforehand.
4 |
5 | ## Listing a user's playlists
6 |
7 | ```php
8 | $playlists = $api->getUserPlaylists('USER_ID', [
9 | 'limit' => 5
10 | ]);
11 |
12 | foreach ($playlists->items as $playlist) {
13 | echo '' . $playlist->name . '
';
14 | }
15 | ```
16 |
17 | ## Getting info about a specific playlist
18 |
19 | ```php
20 | $playlist = $api->getPlaylist('PLAYLIST_ID');
21 |
22 | echo $playlist->name;
23 | ```
24 |
25 | ## Getting the image of a user's playlist
26 | ```php
27 | $playlistImage = $api->getPlaylistImage('PLAYLIST_ID');
28 | ```
29 |
30 | ## Getting all tracks in a playlist
31 |
32 | ```php
33 | $playlistTracks = $api->getPlaylistTracks('PLAYLIST_ID');
34 |
35 | foreach ($playlistTracks->items as $track) {
36 | $track = $track->track;
37 |
38 | echo '' . $track->name . '
';
39 | }
40 | ```
41 |
42 | ## Creating a new playlist
43 |
44 | ```php
45 | $api->createPlaylist('USER_ID', [
46 | 'name' => 'My shiny playlist'
47 | ]);
48 | ```
49 |
50 | ## Updating the details of a user's playlist
51 |
52 | ```php
53 | $api->updatePlaylist('PLAYLIST_ID', [
54 | 'name' => 'New name'
55 | ]);
56 | ```
57 |
58 | ## Updating the image of a user's playlist
59 | ```php
60 | $imageData = base64_encode(file_get_contents('image.jpg'));
61 |
62 | $api->updatePlaylistImage('PLAYLIST_ID', $imageData);
63 | ```
64 |
65 | ## Adding tracks to a user's playlist
66 |
67 | ```php
68 | $api->addPlaylistTracks('PLAYLIST_ID', [
69 | 'TRACK_ID',
70 | 'EPISODE_URI'
71 | ]);
72 | ```
73 |
74 | ## Delete tracks from a user's playlist based on IDs
75 |
76 | ```php
77 | $tracks = [
78 | 'tracks' => [
79 | ['uri' => 'TRACK_ID'],
80 | ['uri' => 'EPISODE_URI'],
81 | ],
82 | ];
83 |
84 | $api->deletePlaylistTracks('PLAYLIST_ID', $tracks, 'SNAPSHOT_ID');
85 | ```
86 |
87 | ## Delete tracks from a user's playlist based on positions
88 |
89 | ```php
90 | $trackOptions = [
91 | 'positions' => [
92 | 5,
93 | 12,
94 | ],
95 | ];
96 |
97 | $api->deletePlaylistTracks('PLAYLIST_ID', $trackOptions, 'SNAPSHOT_ID');
98 | ```
99 |
100 | ## Replacing all tracks in a user's playlist with new ones
101 |
102 | ```php
103 | $api->replacePlaylistTracks('PLAYLIST_ID', [
104 | 'TRACK_ID',
105 | 'EPISODE_URI'
106 | ]);
107 | ```
108 |
109 | ## Reorder the tracks in a user's playlist
110 |
111 | ```php
112 | $api->reorderPlaylistTracks('PLAYLIST_ID', [
113 | 'range_start' => 1,
114 | 'range_length' => 5,
115 | 'insert_before' => 10,
116 | 'snapshot_id' => 'SNAPSHOT_ID'
117 | ]);
118 | ```
119 |
120 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
121 |
--------------------------------------------------------------------------------
/docs/examples/managing-user-profiles.md:
--------------------------------------------------------------------------------
1 | # Managing a User's Profile
2 |
3 | There are lots of operations involving a user's profile that can be performed. Remember to request the correct [scopes](working-with-scopes.md) beforehand.
4 |
5 | ### Getting the current user's profile
6 |
7 | ```php
8 | $me = $api->me();
9 |
10 | echo $me->display_name;
11 | ```
12 |
13 | ### Getting any user's profile
14 |
15 | ```php
16 | $user = $api->getUser('USER_ID');
17 |
18 | echo $user->display_name;
19 | ```
20 |
21 | Please see the [method reference](/docs/method-reference/SpotifyWebAPI.md) for more available options for each method.
22 |
--------------------------------------------------------------------------------
/docs/examples/passing-a-custom-request-instance.md:
--------------------------------------------------------------------------------
1 | # Passing a Custom Request Instance
2 |
3 | Sometimes you want to pass a custom `Request` instance to `SpotifyWebAPI`. For example to use another HTTP library or provide additional logging.
4 |
5 | This is possible by extending the `Request` class and providing your own `send` method.
6 |
7 | ```php
8 | class MyRequest extends SpotifyWebAPI\Request
9 | {
10 | public function send(string $method, string $url, string|array|object $parameters = [], array $headers = []): array
11 | {
12 | // Do your thing here
13 |
14 | // But be sure to set the lastResponse property for other parts to work correctly
15 | $this->lastResponse = [
16 | 'body' => $body, // The JSON response body parsed to a PHP value
17 | 'headers' => $headers, // An array of the headers returned
18 | 'status' => $status, // The HTTP response status code
19 | 'url' => $url, // The requested URL
20 | ];
21 |
22 | return $this->lastResponse;
23 | }
24 | }
25 | ```
26 |
27 | Then when you wish to use it, pass it to the `SpotifyWebAPI` and `Session` constructors.
28 |
29 | ```php
30 | $request = new MyRequest();
31 |
32 | $session = new SpotifyWebAPI\Session(
33 | 'CLIENT_ID',
34 | 'CLIENT_SECRET',
35 | 'REDIRECT_URI',
36 | $request
37 | );
38 |
39 | $api = new SpotifyWebAPI\SpotifyWebAPI(
40 | $options,
41 | $session,
42 | $request
43 | );
44 | ```
45 |
46 | ## Helper functions
47 | The `Request` class provides a few helper functions that can be useful when working with the responses from Spotify.
48 |
49 | ### handleResponseError
50 | `protected function handleResponseError(string $body, int $status): void`
51 |
52 | Takes the unparsed response body and tries to figure out what kind of error occured in order to provide some additional info in the error thrown.
53 |
54 | ### parseBody
55 | `protected function parseBody(string $body): mixed`
56 |
57 | Takes the unparsed response body and parses it, taking the [`return_assoc`](/docs/examples/setting-options.md#return_assoc) option into account.
58 |
59 | ### parseHeaders
60 | `protected function parseHeaders(string $headers): array`
61 |
62 | Takes the HTTP header block, parses it to a key-value array while normalizing header names. If you're using an external HTTP library it will most definitely already include a method for this.
63 |
64 | ### splitResponse
65 | `protected function splitResponse(string $response): array`
66 |
67 | Takes the full HTTP response and splits it into `headers` and `body` while stripping additional headers sometimes added by proxy servers.
68 |
--------------------------------------------------------------------------------
/docs/examples/refreshing-access-tokens.md:
--------------------------------------------------------------------------------
1 | # Refreshing Access Tokens
2 | When requesting access tokens using the [Authorization Code](access-token-with-authorization-code-flow.md) or [Proof Key for Code Exchange (PKCE)](access-token-with-pkce-flow.md) flows, a _refresh token_ will also be included. This token can be used to request a new access token when the previous one has expired, but without any user interaction.
3 |
4 | ```php
5 | require 'vendor/autoload.php';
6 |
7 | $session = new SpotifyWebAPI\Session(
8 | 'CLIENT_ID',
9 | 'CLIENT_SECRET',
10 | 'REDIRECT_URI'
11 | );
12 |
13 | // Fetch the refresh token from somewhere. A database for example.
14 |
15 | $session->refreshAccessToken($refreshToken);
16 |
17 | $accessToken = $session->getAccessToken();
18 | $refreshToken = $session->getRefreshToken();
19 |
20 | // Set our new access token on the API wrapper and continue to use the API as usual
21 | $api->setAccessToken($accessToken);
22 |
23 | // Store the new refresh token somewhere for later use
24 | ```
25 |
26 | ## Automatically Refreshing Access Tokens
27 | _Note: This feature is available as of version `2.11.0`._
28 |
29 | Start off by requesting an access token as usual. But instead of setting the access token on a `SpotifyWebAPI` instance, pass the complete `Session` instance when initializing a new `SpotifyWebAPI` instance or by using the `setSession()` method. Remember to also set the `auto_refresh` option to `true`. For example:
30 |
31 | ```php
32 | $session = new SpotifyWebAPI\Session(
33 | 'CLIENT_ID',
34 | 'CLIENT_SECRET',
35 | 'REDIRECT_URI'
36 | );
37 |
38 | $options = [
39 | 'auto_refresh' => true,
40 | ];
41 |
42 | $api = new SpotifyWebAPI\SpotifyWebAPI($options, $session);
43 |
44 | // You can also call setSession on an existing SpotifyWebAPI instance
45 | $api->setSession($session);
46 |
47 | // Call the API as usual
48 | $api->me();
49 |
50 | // Remember to grab the tokens afterwards, they might have been updated
51 | $newAccessToken = $session->getAccessToken();
52 | $newRefreshToken = $session->getRefreshToken();
53 | ```
54 |
55 | ### With an existing refresh token
56 |
57 | When you already have existing access and refresh tokens, add them to the `Session` instance and call the API.
58 |
59 | ```php
60 | $session = new SpotifyWebAPI\Session(
61 | 'CLIENT_ID',
62 | 'CLIENT_SECRET'
63 | );
64 |
65 | // Use previously requested tokens fetched from somewhere. A database for example.
66 | if ($accessToken) {
67 | $session->setAccessToken($accessToken);
68 | $session->setRefreshToken($refreshToken);
69 | } else {
70 | // Or request a new access token
71 | $session->refreshAccessToken($refreshToken);
72 | }
73 |
74 | $options = [
75 | 'auto_refresh' => true,
76 | ];
77 |
78 | $api = new SpotifyWebAPI\SpotifyWebAPI($options, $session);
79 |
80 | // You can also call setSession on an existing SpotifyWebAPI instance
81 | $api->setSession($session);
82 |
83 | // Call the API as usual
84 | $api->me();
85 |
86 | // Remember to grab the tokens afterwards, they might have been updated
87 | $newAccessToken = $session->getAccessToken();
88 | $newRefreshToken = $session->getRefreshToken();
89 | ```
90 |
--------------------------------------------------------------------------------
/docs/examples/searching-the-spotify-catalog.md:
--------------------------------------------------------------------------------
1 | # Searching the Spotify Catalog
2 |
3 | The whole Spotify catalog, including playlists, can be searched in various ways. Since the Spotify search contains so many features, this page just includes a basic example and one should refer to the
4 | [Spotify documentation](https://developer.spotify.com/documentation/web-api/reference/search) and [method reference](/docs/method-reference/SpotifyWebAPI.md) for more information.
5 |
6 | ```php
7 | $results = $api->search('blur', 'artist');
8 |
9 | foreach ($results->artists->items as $artist) {
10 | echo $artist->name, '
';
11 | }
12 | ```
13 |
--------------------------------------------------------------------------------
/docs/examples/setting-custom-curl-options.md:
--------------------------------------------------------------------------------
1 | # Setting custom cURL options
2 |
3 | Sometimes, you need to override the default cURL options. For example increasing the timeout or setting some proxy setting.
4 |
5 | In order to set custom cURL options, you'll need to instantiate a `Request` object yourself and passing it to `SpotifyWebAPI` instead of letting it set it up itself.
6 |
7 | For example:
8 | ```php
9 | $options = [
10 | 'curl_options' => [
11 | CURLOPT_TIMEOUT => 60,
12 | ],
13 | ];
14 |
15 | $request = new SpotifyWebAPI\Request($options);
16 |
17 | // You can also call setOptions on an existing Request instance
18 | $request->setOptions($options);
19 |
20 | // Then, pass the $request when instantiating Session and SpotifyWebAPI
21 | $session = new SpotifyWebAPI\Session(
22 | 'CLIENT_ID',
23 | 'CLIENT_SECRET',
24 | 'REDIRECT_URI',
25 | $request
26 | );
27 |
28 | $api = new SpotifyWebAPI\SpotifyWebAPI([], null, $request);
29 |
30 | // And continue as usual
31 | ```
32 |
33 | The options you pass in `curl_options` will be merged with the default ones and existing options with the same key will be overwritten by the ones passed by you.
34 |
35 | Refer to the [PHP docs](https://www.php.net/manual/en/function.curl-setopt.php) for a complete list of cURL options.
36 |
--------------------------------------------------------------------------------
/docs/examples/setting-options.md:
--------------------------------------------------------------------------------
1 | # Setting options
2 |
3 | There are a few options that can be used to control the behaviour of the API. All options can be set when initializing a new `SpotifyWebAPI` instance or by using the `setOptions()` method. Both approaches will merge the new options with the defaults and multiple calls to `setOptions()` will merge the new options with the ones already set.
4 |
5 | ```php
6 | $options = [
7 | 'auto_refresh' => true,
8 | ];
9 |
10 | // Options can be set using the SpotifyWebAPI constructor
11 | $api = new SpotifyWebAPI\SpotifyWebAPI($options);
12 |
13 | // Or by using the setOptions method
14 | $api->setOptions($options);
15 | ```
16 |
17 | ## Available options
18 |
19 | ### `auto_refresh`
20 |
21 | * Possible values: `true`/`false` (default)
22 |
23 | Used to control [automatic refresh of access tokens](refreshing-access-tokens.md#automatically-refreshing-access-tokens).
24 |
25 | ### `auto_retry`
26 |
27 | * Possible values: `true`/`false` (default)
28 |
29 | Used to control automatic retries of [rate limited requests](https://developer.spotify.com/documentation/web-api/guides/rate-limits/).
30 |
31 | ### `return_assoc`
32 |
33 | * Possible values: `true`/`false` (default)
34 |
35 | Used to control return type of API calls. Setting it to `true` will return associative arrays instead of objects.
36 |
--------------------------------------------------------------------------------
/docs/examples/working-with-scopes.md:
--------------------------------------------------------------------------------
1 | # Working with Scopes
2 |
3 | All operations involving a user or their information requires your app to request one or more scopes. You request scopes at the same time as the access token, for example:
4 |
5 | ```php
6 | require 'vendor/autoload.php';
7 |
8 | $session = new SpotifyWebAPI\Session(
9 | 'CLIENT_ID',
10 | 'CLIENT_SECRET',
11 | 'REDIRECT_URI'
12 | );
13 |
14 | $options = [
15 | 'scope' => [
16 | 'user-library-modify',
17 | 'user-read-birthdate',
18 | ],
19 | ];
20 |
21 | header('Location: ' . $session->getAuthorizeUrl($options));
22 | die();
23 | ```
24 |
25 | It's possible to request more scopes at any time, simply add new ones to the list. This will ask the user to approve your app again.
26 |
27 | Please refer to the Spotify docs for a full list of [all available scopes](https://developer.spotify.com/documentation/general/guides/authorization/scopes/).
28 |
29 | ## Checking Requested Scopes
30 | If you wish to check which scopes are granted for an access token, the `Session::getScope()` method can be used. For example, put the following code in your callback where the user will be redirected to by Spotify:
31 |
32 | ```php
33 | $accessToken = $session->requestAccessToken($_GET['code']);
34 | $scopes = $session->getScope();
35 | ```
36 |
37 | The same method can also be used after refreshing an access token, for example:
38 |
39 | ```php
40 | $session->refreshAccessToken($refreshToken);
41 |
42 | $scopes = $session->getScope();
43 | ```
44 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ## Requirements
4 | * PHP 8.1 or later.
5 | * PHP [cURL extension](http://php.net/manual/en/book.curl.php) (Usually included with PHP).
6 |
7 | ## Autoloading
8 | The Spotify Web API for PHP is compatible with [PSR-4](http://www.php-fig.org/psr/psr-4/). This means that the code makes heavy use of namespaces and the correct files can be loaded automatically. All examples throughout this documentation will assume the use of a PSR-4 compatible autoloader, for example via [Composer](https://getcomposer.org/).
9 |
10 | ## Installation
11 |
12 | ### Installation via Composer
13 | This is the preferred way of installing the Spotify Web API for PHP. Run the following command in the root of your project:
14 |
15 | ```sh
16 | composer require jwilsson/spotify-web-api-php
17 | ```
18 |
19 | Then, in every file where you wish to use the Spotify Web API for PHP, include the following line:
20 |
21 | ```php
22 | require_once 'vendor/autoload.php';
23 | ```
24 |
25 | ### Manual installation
26 |
27 | Download the latest release from the [releases page](https://github.com/jwilsson/spotify-web-api-php/releases). Unzip the files somewhere in your project and include a [PSR-4 compatible autoloader](http://www.php-fig.org/psr/psr-4/examples/) in your project.
28 |
29 | ## Configuration and setup
30 | First off, make sure you've created an app on [Spotify's developer site](https://developer.spotify.com/documentation/web-api/).
31 |
32 | *Note: Applications created after 2021-05-27 [might need to perform some extra steps](https://developer.spotify.com/community/news/2021/05/27/improving-the-developer-and-user-experience-for-third-party-apps/).*
33 |
34 | Now, before sending requests to Spotify, we need to create a session using your app info:
35 |
36 | ```php
37 | $session = new SpotifyWebAPI\Session(
38 | 'CLIENT_ID',
39 | 'CLIENT_SECRET',
40 | 'REDIRECT_URI'
41 | );
42 | ```
43 |
44 | Replace the values here with the ones given to you from Spotify.
45 |
46 | ## Authentication and authorization
47 | After creating a session it's time to request access to the Spotify Web API. There are three ways to request an access token.
48 |
49 | The first method is called *Proof Key for Code Exchange (PKCE)* and requires some interaction from the user, but in turn gives you some access to the user's account. This is the recommended method if you need access to a user's account.
50 |
51 | The second method is called the *Authorization Code Flow* and just like the PKCE method it requires some interaction from the user, but will also give you access to the user's account.
52 |
53 | The last method is called the *Client Credentials Flow* and doesn't require any user interaction but also doesn't provide any user information. This method is the recommended one if you just need access to Spotify catalog data.
54 |
55 | For more info about each authorization method, checkout these examples:
56 | * [Obtaining an access token using the Proof Key for Code Exchange (PKCE) Flow](/docs/examples/access-token-with-pkce-flow.md)
57 | * [Obtaining an access token using the Authorization Code Flow](/docs/examples/access-token-with-authorization-code-flow.md)
58 | * [Obtaining an access token using the Client Credentials Flow](/docs/examples/access-token-with-client-credentials-flow.md)
59 |
60 | ## Making requests to the Spotify API
61 | Assuming you've followed one of the authorization guides above and successfully requested an access token, now it's time to create a new file called `app.php`. In this file we'll tell the API wrapper about the access token to use and then request some data from Spotify!
62 |
63 | ```php
64 | require 'vendor/autoload.php';
65 |
66 | // Fetch your access token from somewhere. A session for example.
67 |
68 | $api = new SpotifyWebAPI\SpotifyWebAPI();
69 | $api->setAccessToken($accessToken);
70 |
71 | print_r(
72 | $api->getTrack('4uLU6hMCjMI75M1A2tKUQC')
73 | );
74 | ```
75 |
76 | Congratulations! You now know how to use the Spotify Web API for PHP. The next step is to check out [some examples](/docs/examples/) and the [method reference](/docs/method-reference/).
77 |
--------------------------------------------------------------------------------
/docs/method-reference/Request.md:
--------------------------------------------------------------------------------
1 | # Request
2 |
3 | ## Table of Contents
4 | * [__construct](#__construct)
5 | * [account](#account)
6 | * [api](#api)
7 | * [getLastResponse](#getlastresponse)
8 | * [send](#send)
9 | * [setOptions](#setoptions)
10 |
11 | ## Constants
12 | * **ACCOUNT_URL**
13 | * **API_URL**
14 |
15 | ## Methods
16 | ### __construct
17 |
18 |
19 | ```php
20 | Request::__construct($options)
21 | ```
22 |
23 | Constructor
24 | Set options.
25 |
26 | #### Arguments
27 | * `$options` **array\|object** - Optional. Options to set.
28 |
29 |
30 | ---
31 | ### account
32 |
33 |
34 | ```php
35 | Request::account($method, $uri, $parameters, $headers)
36 | ```
37 |
38 | Make a request to the "account" endpoint.
39 |
40 | #### Arguments
41 | * `$method` **string** - The HTTP method to use.
42 | * `$uri` **string** - The URI to request.
43 | * `$parameters` **string\|array** - Optional. Query string parameters or HTTP body, depending on $method.
44 | * `$headers` **array** - Optional. HTTP headers.
45 |
46 | #### Return values
47 | * **array** Response data.
48 | * array\|object body The response body. Type is controlled by the `return_assoc` option.
49 | * array headers Response headers.
50 | * int status HTTP status code.
51 | * string url The requested URL.
52 |
53 | ---
54 | ### api
55 |
56 |
57 | ```php
58 | Request::api($method, $uri, $parameters, $headers)
59 | ```
60 |
61 | Make a request to the "api" endpoint.
62 |
63 | #### Arguments
64 | * `$method` **string** - The HTTP method to use.
65 | * `$uri` **string** - The URI to request.
66 | * `$parameters` **string\|array** - Optional. Query string parameters or HTTP body, depending on $method.
67 | * `$headers` **array** - Optional. HTTP headers.
68 |
69 | #### Return values
70 | * **array** Response data.
71 | * array\|object body The response body. Type is controlled by the `return_assoc` option.
72 | * array headers Response headers.
73 | * int status HTTP status code.
74 | * string url The requested URL.
75 |
76 | ---
77 | ### getLastResponse
78 |
79 |
80 | ```php
81 | Request::getLastResponse()
82 | ```
83 |
84 | Get the latest full response from the Spotify API.
85 |
86 |
87 | #### Return values
88 | * **array** Response data.
89 | * array\|object body The response body. Type is controlled by the `return_assoc` option.
90 | * array headers Response headers.
91 | * int status HTTP status code.
92 | * string url The requested URL.
93 |
94 | ---
95 | ### send
96 |
97 |
98 | ```php
99 | Request::send($method, $url, $parameters, $headers)
100 | ```
101 |
102 | Make a request to Spotify.
103 | You'll probably want to use one of the convenience methods instead.
104 |
105 | #### Arguments
106 | * `$method` **string** - The HTTP method to use.
107 | * `$url` **string** - The URL to request.
108 | * `$parameters` **string\|array\|object** - Optional. Query string parameters or HTTP body, depending on $method.
109 | * `$headers` **array** - Optional. HTTP headers.
110 |
111 | #### Return values
112 | * **array** Response data.
113 | * array\|object body The response body. Type is controlled by the `return_assoc` option.
114 | * array headers Response headers.
115 | * int status HTTP status code.
116 | * string url The requested URL.
117 |
118 | ---
119 | ### setOptions
120 |
121 |
122 | ```php
123 | Request::setOptions($options)
124 | ```
125 |
126 | Set options
127 |
128 | #### Arguments
129 | * `$options` **array\|object** - Options to set.
130 |
131 | #### Return values
132 | * **self**
133 |
134 | ---
135 |
--------------------------------------------------------------------------------
/docs/method-reference/Session.md:
--------------------------------------------------------------------------------
1 | # Session
2 |
3 | ## Table of Contents
4 | * [__construct](#__construct)
5 | * [generateCodeChallenge](#generatecodechallenge)
6 | * [generateCodeVerifier](#generatecodeverifier)
7 | * [generateState](#generatestate)
8 | * [getAuthorizeUrl](#getauthorizeurl)
9 | * [getAccessToken](#getaccesstoken)
10 | * [getClientId](#getclientid)
11 | * [getClientSecret](#getclientsecret)
12 | * [getTokenExpiration](#gettokenexpiration)
13 | * [getRedirectUri](#getredirecturi)
14 | * [getRefreshToken](#getrefreshtoken)
15 | * [getScope](#getscope)
16 | * [refreshAccessToken](#refreshaccesstoken)
17 | * [requestAccessToken](#requestaccesstoken)
18 | * [requestCredentialsToken](#requestcredentialstoken)
19 | * [setAccessToken](#setaccesstoken)
20 | * [setClientId](#setclientid)
21 | * [setClientSecret](#setclientsecret)
22 | * [setRedirectUri](#setredirecturi)
23 | * [setRefreshToken](#setrefreshtoken)
24 |
25 | ## Constants
26 |
27 | ## Methods
28 | ### __construct
29 |
30 |
31 | ```php
32 | Session::__construct($clientId, $clientSecret, $redirectUri, $request)
33 | ```
34 |
35 | Constructor
36 | Set up client credentials.
37 |
38 | #### Arguments
39 | * `$clientId` **string** - The client ID.
40 | * `$clientSecret` **string** - Optional. The client secret.
41 | * `$redirectUri` **string** - Optional. The redirect URI.
42 | * `$request` **\SpotifyWebAPI\Request** - Optional. The Request object to use.
43 |
44 |
45 | ---
46 | ### generateCodeChallenge
47 |
48 |
49 | ```php
50 | Session::generateCodeChallenge($codeVerifier, $hashAlgo)
51 | ```
52 |
53 | Generate a code challenge from a code verifier for use with the PKCE flow.
54 |
55 | #### Arguments
56 | * `$codeVerifier` **string** - The code verifier to create a challenge from.
57 | * `$hashAlgo` **string** - Optional. The hash algorithm to use. Defaults to "sha256".
58 |
59 | #### Return values
60 | * **string** The code challenge.
61 |
62 | ---
63 | ### generateCodeVerifier
64 |
65 |
66 | ```php
67 | Session::generateCodeVerifier($length)
68 | ```
69 |
70 | Generate a code verifier for use with the PKCE flow.
71 |
72 | #### Arguments
73 | * `$length` **int** - Optional. Code verifier length. Must be between 43 and 128 characters long, default is 128.
74 |
75 | #### Return values
76 | * **string** A code verifier string.
77 |
78 | ---
79 | ### generateState
80 |
81 |
82 | ```php
83 | Session::generateState($length)
84 | ```
85 |
86 | Generate a random state value.
87 |
88 | #### Arguments
89 | * `$length` **int** - Optional. Length of the state. Default is 16 characters.
90 |
91 | #### Return values
92 | * **string** A random state value.
93 |
94 | ---
95 | ### getAuthorizeUrl
96 |
97 |
98 | ```php
99 | Session::getAuthorizeUrl($options)
100 | ```
101 |
102 | Get the authorization URL.
103 |
104 | #### Arguments
105 | * `$options` **array\|object** - Optional. Options for the authorization URL.
106 | * string code_challenge Optional. A PKCE code challenge.
107 | * array scope Optional. Scope(s) to request from the user.
108 | * boolean show_dialog Optional. Whether or not to force the user to always approve the app. Default is false.
109 | * string state Optional. A CSRF token.
110 |
111 | #### Return values
112 | * **string** The authorization URL.
113 |
114 | ---
115 | ### getAccessToken
116 |
117 |
118 | ```php
119 | Session::getAccessToken()
120 | ```
121 |
122 | Get the access token.
123 |
124 |
125 | #### Return values
126 | * **string** The access token.
127 |
128 | ---
129 | ### getClientId
130 |
131 |
132 | ```php
133 | Session::getClientId()
134 | ```
135 |
136 | Get the client ID.
137 |
138 |
139 | #### Return values
140 | * **string** The client ID.
141 |
142 | ---
143 | ### getClientSecret
144 |
145 |
146 | ```php
147 | Session::getClientSecret()
148 | ```
149 |
150 | Get the client secret.
151 |
152 |
153 | #### Return values
154 | * **string** The client secret.
155 |
156 | ---
157 | ### getTokenExpiration
158 |
159 |
160 | ```php
161 | Session::getTokenExpiration()
162 | ```
163 |
164 | Get the access token expiration time.
165 |
166 |
167 | #### Return values
168 | * **int** A Unix timestamp indicating the token expiration time.
169 |
170 | ---
171 | ### getRedirectUri
172 |
173 |
174 | ```php
175 | Session::getRedirectUri()
176 | ```
177 |
178 | Get the client's redirect URI.
179 |
180 |
181 | #### Return values
182 | * **string** The redirect URI.
183 |
184 | ---
185 | ### getRefreshToken
186 |
187 |
188 | ```php
189 | Session::getRefreshToken()
190 | ```
191 |
192 | Get the refresh token.
193 |
194 |
195 | #### Return values
196 | * **string** The refresh token.
197 |
198 | ---
199 | ### getScope
200 |
201 |
202 | ```php
203 | Session::getScope()
204 | ```
205 |
206 | Get the scope for the current access token
207 |
208 |
209 | #### Return values
210 | * **array** The scope for the current access token
211 |
212 | ---
213 | ### refreshAccessToken
214 |
215 |
216 | ```php
217 | Session::refreshAccessToken($refreshToken)
218 | ```
219 |
220 | Refresh an access token.
221 |
222 | #### Arguments
223 | * `$refreshToken` **string** - Optional. The refresh token to use.
224 |
225 | #### Return values
226 | * **bool** Whether the access token was successfully refreshed.
227 |
228 | ---
229 | ### requestAccessToken
230 |
231 |
232 | ```php
233 | Session::requestAccessToken($authorizationCode, $codeVerifier)
234 | ```
235 |
236 | Request an access token given an authorization code.
237 |
238 | #### Arguments
239 | * `$authorizationCode` **string** - The authorization code from Spotify.
240 | * `$codeVerifier` **string** - Optional. A previously generated code verifier. Will assume a PKCE flow if passed.
241 |
242 | #### Return values
243 | * **bool** True when the access token was successfully granted, false otherwise.
244 |
245 | ---
246 | ### requestCredentialsToken
247 |
248 |
249 | ```php
250 | Session::requestCredentialsToken()
251 | ```
252 |
253 | Request an access token using the Client Credentials Flow.
254 |
255 |
256 | #### Return values
257 | * **bool** True when an access token was successfully granted, false otherwise.
258 |
259 | ---
260 | ### setAccessToken
261 |
262 |
263 | ```php
264 | Session::setAccessToken($accessToken)
265 | ```
266 |
267 | Set the access token.
268 |
269 | #### Arguments
270 | * `$accessToken` **string** - The access token
271 |
272 | #### Return values
273 | * **self**
274 |
275 | ---
276 | ### setClientId
277 |
278 |
279 | ```php
280 | Session::setClientId($clientId)
281 | ```
282 |
283 | Set the client ID.
284 |
285 | #### Arguments
286 | * `$clientId` **string** - The client ID.
287 |
288 | #### Return values
289 | * **self**
290 |
291 | ---
292 | ### setClientSecret
293 |
294 |
295 | ```php
296 | Session::setClientSecret($clientSecret)
297 | ```
298 |
299 | Set the client secret.
300 |
301 | #### Arguments
302 | * `$clientSecret` **string** - The client secret.
303 |
304 | #### Return values
305 | * **self**
306 |
307 | ---
308 | ### setRedirectUri
309 |
310 |
311 | ```php
312 | Session::setRedirectUri($redirectUri)
313 | ```
314 |
315 | Set the client's redirect URI.
316 |
317 | #### Arguments
318 | * `$redirectUri` **string** - The redirect URI.
319 |
320 | #### Return values
321 | * **self**
322 |
323 | ---
324 | ### setRefreshToken
325 |
326 |
327 | ```php
328 | Session::setRefreshToken($refreshToken)
329 | ```
330 |
331 | Set the session's refresh token.
332 |
333 | #### Arguments
334 | * `$refreshToken` **string** - The refresh token.
335 |
336 | #### Return values
337 | * **self**
338 |
339 | ---
340 |
--------------------------------------------------------------------------------
/docs/method-reference/SpotifyWebAPIAuthException.md:
--------------------------------------------------------------------------------
1 | # SpotifyWebAPIAuthException
2 |
3 | ## Table of Contents
4 | * [hasInvalidCredentials](#hasinvalidcredentials)
5 | * [hasInvalidRefreshToken](#hasinvalidrefreshtoken)
6 | * [getReason](#getreason)
7 | * [hasExpiredToken](#hasexpiredtoken)
8 | * [isRateLimited](#isratelimited)
9 | * [setReason](#setreason)
10 |
11 | ## Constants
12 | * **INVALID_CLIENT**
13 | * **INVALID_CLIENT_SECRET**
14 | * **INVALID_REFRESH_TOKEN**
15 | * **TOKEN_EXPIRED**
16 | * **RATE_LIMIT_STATUS**
17 |
18 | ## Methods
19 | ### hasInvalidCredentials
20 |
21 |
22 | ```php
23 | SpotifyWebAPIAuthException::hasInvalidCredentials()
24 | ```
25 |
26 | Returns whether the exception was thrown because of invalid credentials.
27 |
28 |
29 | #### Return values
30 | * **bool**
31 |
32 | ---
33 | ### hasInvalidRefreshToken
34 |
35 |
36 | ```php
37 | SpotifyWebAPIAuthException::hasInvalidRefreshToken()
38 | ```
39 |
40 | Returns whether the exception was thrown because of an invalid refresh token.
41 |
42 |
43 | #### Return values
44 | * **bool**
45 |
46 | ---
47 | ### getReason
48 |
49 |
50 | ```php
51 | SpotifyWebAPIAuthException::getReason()
52 | ```
53 |
54 | Returns the reason string from a player request's error object.
55 |
56 |
57 | #### Return values
58 | * **string**
59 |
60 | ---
61 | ### hasExpiredToken
62 |
63 |
64 | ```php
65 | SpotifyWebAPIAuthException::hasExpiredToken()
66 | ```
67 |
68 | Returns whether the exception was thrown because of an expired access token.
69 |
70 |
71 | #### Return values
72 | * **bool**
73 |
74 | ---
75 | ### isRateLimited
76 |
77 |
78 | ```php
79 | SpotifyWebAPIAuthException::isRateLimited()
80 | ```
81 |
82 | Returns whether the exception was thrown because of rate limiting.
83 |
84 |
85 | #### Return values
86 | * **bool**
87 |
88 | ---
89 | ### setReason
90 |
91 |
92 | ```php
93 | SpotifyWebAPIAuthException::setReason($reason)
94 | ```
95 |
96 | Set the reason string.
97 |
98 | #### Arguments
99 | * `$reason` **string**
100 |
101 | #### Return values
102 | * **void**
103 |
104 | ---
105 |
--------------------------------------------------------------------------------
/docs/method-reference/SpotifyWebAPIException.md:
--------------------------------------------------------------------------------
1 | # SpotifyWebAPIException
2 |
3 | ## Table of Contents
4 | * [getReason](#getreason)
5 | * [hasExpiredToken](#hasexpiredtoken)
6 | * [isRateLimited](#isratelimited)
7 | * [setReason](#setreason)
8 |
9 | ## Constants
10 | * **TOKEN_EXPIRED**
11 | * **RATE_LIMIT_STATUS**
12 |
13 | ## Methods
14 | ### getReason
15 |
16 |
17 | ```php
18 | SpotifyWebAPIException::getReason()
19 | ```
20 |
21 | Returns the reason string from a player request's error object.
22 |
23 |
24 | #### Return values
25 | * **string**
26 |
27 | ---
28 | ### hasExpiredToken
29 |
30 |
31 | ```php
32 | SpotifyWebAPIException::hasExpiredToken()
33 | ```
34 |
35 | Returns whether the exception was thrown because of an expired access token.
36 |
37 |
38 | #### Return values
39 | * **bool**
40 |
41 | ---
42 | ### isRateLimited
43 |
44 |
45 | ```php
46 | SpotifyWebAPIException::isRateLimited()
47 | ```
48 |
49 | Returns whether the exception was thrown because of rate limiting.
50 |
51 |
52 | #### Return values
53 | * **bool**
54 |
55 | ---
56 | ### setReason
57 |
58 |
59 | ```php
60 | SpotifyWebAPIException::setReason($reason)
61 | ```
62 |
63 | Set the reason string.
64 |
65 | #### Arguments
66 | * `$reason` **string**
67 |
68 | #### Return values
69 | * **void**
70 |
71 | ---
72 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/phpunit.dist.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | ./tests/
25 |
26 |
27 |
28 |
29 |
30 | ./src/
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/phpunit.php:
--------------------------------------------------------------------------------
1 | [],
15 | 'return_assoc' => false,
16 | ];
17 |
18 | /**
19 | * Constructor
20 | * Set options.
21 | *
22 | * @param array|object $options Optional. Options to set.
23 | */
24 | public function __construct(array|object $options = [])
25 | {
26 | $this->setOptions($options);
27 | }
28 |
29 | /**
30 | * Handle response errors.
31 | *
32 | * @param string $body The raw, unparsed response body.
33 | * @param int $status The HTTP status code, passed along to any exceptions thrown.
34 | *
35 | * @throws SpotifyWebAPIException
36 | * @throws SpotifyWebAPIAuthException
37 | *
38 | * @return void
39 | */
40 | protected function handleResponseError(string $body, int $status): void
41 | {
42 | $parsedBody = json_decode($body);
43 | $error = $parsedBody->error ?? null;
44 |
45 | if (isset($error->message) && isset($error->status)) {
46 | // It's an API call error
47 | $exception = new SpotifyWebAPIException($error->message, $error->status);
48 |
49 | if (isset($error->reason)) {
50 | $exception->setReason($error->reason);
51 | }
52 |
53 | throw $exception;
54 | } elseif (isset($parsedBody->error_description)) {
55 | // It's an auth call error
56 | throw new SpotifyWebAPIAuthException($parsedBody->error_description, $status);
57 | } elseif ($body) {
58 | // Something else went wrong, try to give at least some info
59 | throw new SpotifyWebAPIException($body, $status);
60 | } else {
61 | // Something went really wrong, we don't know what
62 | throw new SpotifyWebAPIException('An unknown error occurred.', $status);
63 | }
64 | }
65 |
66 | /**
67 | * Parse HTTP response body, taking the "return_assoc" option into account.
68 | */
69 | protected function parseBody(string $body): mixed
70 | {
71 | return json_decode($body, $this->options['return_assoc']);
72 | }
73 |
74 | /**
75 | * Parse HTTP response headers and normalize names.
76 | *
77 | * @param string $headers The raw, unparsed response headers.
78 | *
79 | * @return array Headers as key–value pairs.
80 | */
81 | protected function parseHeaders(string $headers): array
82 | {
83 | $headers = explode("\n", $headers);
84 |
85 | array_shift($headers);
86 |
87 | $parsedHeaders = [];
88 | foreach ($headers as $header) {
89 | [$key, $value] = explode(':', $header, 2);
90 |
91 | $key = strtolower($key);
92 | $parsedHeaders[$key] = trim($value);
93 | }
94 |
95 | return $parsedHeaders;
96 | }
97 |
98 | /**
99 | * Make a request to the "account" endpoint.
100 | *
101 | * @param string $method The HTTP method to use.
102 | * @param string $uri The URI to request.
103 | * @param string|array $parameters Optional. Query string parameters or HTTP body, depending on $method.
104 | * @param array $headers Optional. HTTP headers.
105 | *
106 | * @throws SpotifyWebAPIException
107 | * @throws SpotifyWebAPIAuthException
108 | *
109 | * @return array Response data.
110 | * - array|object body The response body. Type is controlled by the `return_assoc` option.
111 | * - array headers Response headers.
112 | * - int status HTTP status code.
113 | * - string url The requested URL.
114 | */
115 | public function account(string $method, string $uri, string|array $parameters = [], array $headers = []): array
116 | {
117 | return $this->send($method, static::ACCOUNT_URL . $uri, $parameters, $headers);
118 | }
119 |
120 | /**
121 | * Make a request to the "api" endpoint.
122 | *
123 | * @param string $method The HTTP method to use.
124 | * @param string $uri The URI to request.
125 | * @param string|array $parameters Optional. Query string parameters or HTTP body, depending on $method.
126 | * @param array $headers Optional. HTTP headers.
127 | *
128 | * @throws SpotifyWebAPIException
129 | * @throws SpotifyWebAPIAuthException
130 | *
131 | * @return array Response data.
132 | * - array|object body The response body. Type is controlled by the `return_assoc` option.
133 | * - array headers Response headers.
134 | * - int status HTTP status code.
135 | * - string url The requested URL.
136 | */
137 | public function api(string $method, string $uri, string|array $parameters = [], array $headers = []): array
138 | {
139 | return $this->send($method, static::API_URL . $uri, $parameters, $headers);
140 | }
141 |
142 | /**
143 | * Get the latest full response from the Spotify API.
144 | *
145 | * @return array Response data.
146 | * - array|object body The response body. Type is controlled by the `return_assoc` option.
147 | * - array headers Response headers.
148 | * - int status HTTP status code.
149 | * - string url The requested URL.
150 | */
151 | public function getLastResponse(): array
152 | {
153 | return $this->lastResponse;
154 | }
155 |
156 | /**
157 | * Make a request to Spotify.
158 | * You'll probably want to use one of the convenience methods instead.
159 | *
160 | * @param string $method The HTTP method to use.
161 | * @param string $url The URL to request.
162 | * @param string|array|object $parameters Optional. Query string parameters or HTTP body, depending on $method.
163 | * @param array $headers Optional. HTTP headers.
164 | *
165 | * @throws SpotifyWebAPIException
166 | * @throws SpotifyWebAPIAuthException
167 | *
168 | * @return array Response data.
169 | * - array|object body The response body. Type is controlled by the `return_assoc` option.
170 | * - array headers Response headers.
171 | * - int status HTTP status code.
172 | * - string url The requested URL.
173 | */
174 | public function send(string $method, string $url, string|array|object $parameters = [], array $headers = []): array
175 | {
176 | // Reset any old responses
177 | $this->lastResponse = [];
178 |
179 | // Sometimes a stringified JSON object is passed
180 | if (is_array($parameters) || is_object($parameters)) {
181 | $parameters = http_build_query($parameters, '', '&');
182 | }
183 |
184 | $options = [
185 | CURLOPT_CAINFO => __DIR__ . '/cacert.pem',
186 | CURLOPT_ENCODING => '',
187 | CURLOPT_HEADER => true,
188 | CURLOPT_HTTPHEADER => [],
189 | CURLOPT_RETURNTRANSFER => true,
190 | CURLOPT_URL => rtrim($url, '/'),
191 | ];
192 |
193 | foreach ($headers as $key => $val) {
194 | $options[CURLOPT_HTTPHEADER][] = "$key: $val";
195 | }
196 |
197 | $method = strtoupper($method);
198 |
199 | switch ($method) {
200 | case 'DELETE': // No break
201 | case 'PUT':
202 | $options[CURLOPT_CUSTOMREQUEST] = $method;
203 | $options[CURLOPT_POSTFIELDS] = $parameters;
204 |
205 | break;
206 | case 'POST':
207 | $options[CURLOPT_POST] = true;
208 | $options[CURLOPT_POSTFIELDS] = $parameters;
209 |
210 | break;
211 | default:
212 | $options[CURLOPT_CUSTOMREQUEST] = $method;
213 |
214 | if ($parameters) {
215 | $options[CURLOPT_URL] .= '/?' . $parameters;
216 | }
217 |
218 | break;
219 | }
220 |
221 | $ch = curl_init();
222 |
223 | curl_setopt_array($ch, array_replace($options, $this->options['curl_options']));
224 |
225 | $response = curl_exec($ch);
226 |
227 | if (curl_error($ch)) {
228 | $error = curl_error($ch);
229 | $errno = curl_errno($ch);
230 | curl_close($ch);
231 |
232 | throw new SpotifyWebAPIException('cURL transport error: ' . $errno . ' ' . $error);
233 | }
234 |
235 | [$headers, $body] = $this->splitResponse($response);
236 |
237 | $parsedBody = $this->parseBody($body);
238 | $parsedHeaders = $this->parseHeaders($headers);
239 | $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
240 |
241 | $this->lastResponse = [
242 | 'body' => $parsedBody,
243 | 'headers' => $parsedHeaders,
244 | 'status' => $status,
245 | 'url' => $url,
246 | ];
247 |
248 | curl_close($ch);
249 |
250 | if ($status >= 400) {
251 | $this->handleResponseError($body, $status);
252 | }
253 |
254 | return $this->lastResponse;
255 | }
256 |
257 | /**
258 | * Set options
259 | *
260 | * @param array|object $options Options to set.
261 | *
262 | * @return self
263 | */
264 | public function setOptions(array|object $options): self
265 | {
266 | $this->options = array_merge($this->options, (array) $options);
267 |
268 | return $this;
269 | }
270 |
271 | /**
272 | * Split response into headers and body, taking proxy response headers etc. into account.
273 | *
274 | * @param string $response The complete response.
275 | *
276 | * @return array An array consisting of two elements, headers and body.
277 | */
278 | protected function splitResponse(string $response): array
279 | {
280 | $response = str_replace("\r\n", "\n", $response);
281 | $parts = explode("\n\n", $response, 3);
282 |
283 | // Skip first set of headers for proxied requests etc.
284 | if (
285 | preg_match('/^HTTP\/1.\d 100 Continue/', $parts[0]) ||
286 | preg_match('/^HTTP\/1.\d 200 Connection established/', $parts[0]) ||
287 | preg_match('/^HTTP\/1.\d 200 Tunnel established/', $parts[0])
288 | ) {
289 | return [
290 | $parts[1],
291 | $parts[2],
292 | ];
293 | }
294 |
295 | return [
296 | $parts[0],
297 | $parts[1],
298 | ];
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/src/SpotifyWebAPIAuthException.php:
--------------------------------------------------------------------------------
1 | getMessage(), [
22 | self::INVALID_CLIENT,
23 | self::INVALID_CLIENT_SECRET,
24 | ]);
25 | }
26 |
27 | /**
28 | * Returns whether the exception was thrown because of an invalid refresh token.
29 | *
30 | * @return bool
31 | */
32 | public function hasInvalidRefreshToken(): bool
33 | {
34 | return $this->getMessage() === self::INVALID_REFRESH_TOKEN;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/SpotifyWebAPIException.php:
--------------------------------------------------------------------------------
1 | reason;
27 | }
28 |
29 | /**
30 | * Returns whether the exception was thrown because of an expired access token.
31 | *
32 | * @return bool
33 | */
34 | public function hasExpiredToken(): bool
35 | {
36 | return $this->getMessage() === self::TOKEN_EXPIRED;
37 | }
38 |
39 | /**
40 | * Returns whether the exception was thrown because of rate limiting.
41 | *
42 | * @return bool
43 | */
44 | public function isRateLimited(): bool
45 | {
46 | return $this->getCode() === self::RATE_LIMIT_STATUS;
47 | }
48 |
49 | /**
50 | * Set the reason string.
51 | *
52 | * @param string $reason
53 | *
54 | * @return void
55 | */
56 | public function setReason(string $reason): void
57 | {
58 | $this->reason = $reason;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/fixtures/access-token.json:
--------------------------------------------------------------------------------
1 | {
2 | "access_token": "75a25c2be83fdfa0bb221b04cf3a4525e9f1203a",
3 | "token_type": "Bearer",
4 | "expires_in": 3600,
5 | "refresh_token": "030b53252ef9f646d2981f9a2bc92353c32f5f22",
6 | "scope": "user-follow-read user-follow-modify user-library-read user-library-modify"
7 | }
8 |
--------------------------------------------------------------------------------
/tests/fixtures/album-tracks.json:
--------------------------------------------------------------------------------
1 | {
2 | "href" : "https://api.spotify.com/v1/albums/1oR3KrPIp4CbagPa3PhtPp/tracks?offset=0&limit=5",
3 | "items" : [ {
4 | "artists" : [ {
5 | "external_urls" : {
6 | "spotify" : "https://open.spotify.com/artist/3qm84nBOXUEQ2vnTfUTTFC"
7 | },
8 | "href" : "https://api.spotify.com/v1/artists/3qm84nBOXUEQ2vnTfUTTFC",
9 | "id" : "3qm84nBOXUEQ2vnTfUTTFC",
10 | "name" : "Guns N' Roses",
11 | "type" : "artist",
12 | "uri" : "spotify:artist:3qm84nBOXUEQ2vnTfUTTFC"
13 | } ],
14 | "available_markets" : [ "CR", "DO", "GT", "HN", "NI", "PA", "SV" ],
15 | "disc_number" : 1,
16 | "duration_ms" : 273600,
17 | "explicit" : false,
18 | "external_urls" : {
19 | "spotify" : "https://open.spotify.com/track/0oWHLtxWeMJhmwxtrxhNK0"
20 | },
21 | "href" : "https://api.spotify.com/v1/tracks/0oWHLtxWeMJhmwxtrxhNK0",
22 | "id" : "0oWHLtxWeMJhmwxtrxhNK0",
23 | "name" : "Welcome To The Jungle",
24 | "preview_url" : "https://p.scdn.co/mp3-preview/6ac585f7791a2ec0a60c17e04846345ed7671d0b",
25 | "track_number" : 1,
26 | "type" : "track",
27 | "uri" : "spotify:track:0oWHLtxWeMJhmwxtrxhNK0"
28 | }, {
29 | "artists" : [ {
30 | "external_urls" : {
31 | "spotify" : "https://open.spotify.com/artist/3qm84nBOXUEQ2vnTfUTTFC"
32 | },
33 | "href" : "https://api.spotify.com/v1/artists/3qm84nBOXUEQ2vnTfUTTFC",
34 | "id" : "3qm84nBOXUEQ2vnTfUTTFC",
35 | "name" : "Guns N' Roses",
36 | "type" : "artist",
37 | "uri" : "spotify:artist:3qm84nBOXUEQ2vnTfUTTFC"
38 | } ],
39 | "available_markets" : [ "CR", "DO", "GT", "HN", "NI", "PA", "SV" ],
40 | "disc_number" : 1,
41 | "duration_ms" : 202893,
42 | "explicit" : true,
43 | "external_urls" : {
44 | "spotify" : "https://open.spotify.com/track/0fu0HHl1yVC2iCUuyBT144"
45 | },
46 | "href" : "https://api.spotify.com/v1/tracks/0fu0HHl1yVC2iCUuyBT144",
47 | "id" : "0fu0HHl1yVC2iCUuyBT144",
48 | "name" : "It's So Easy",
49 | "preview_url" : "https://p.scdn.co/mp3-preview/ee696163ab8f41d9bee0b44f6d2c49ab8b28e490",
50 | "track_number" : 2,
51 | "type" : "track",
52 | "uri" : "spotify:track:0fu0HHl1yVC2iCUuyBT144"
53 | }, {
54 | "artists" : [ {
55 | "external_urls" : {
56 | "spotify" : "https://open.spotify.com/artist/3qm84nBOXUEQ2vnTfUTTFC"
57 | },
58 | "href" : "https://api.spotify.com/v1/artists/3qm84nBOXUEQ2vnTfUTTFC",
59 | "id" : "3qm84nBOXUEQ2vnTfUTTFC",
60 | "name" : "Guns N' Roses",
61 | "type" : "artist",
62 | "uri" : "spotify:artist:3qm84nBOXUEQ2vnTfUTTFC"
63 | } ],
64 | "available_markets" : [ "CR", "DO", "GT", "HN", "NI", "PA", "SV" ],
65 | "disc_number" : 1,
66 | "duration_ms" : 268506,
67 | "explicit" : false,
68 | "external_urls" : {
69 | "spotify" : "https://open.spotify.com/track/23ZdycsvnFHK9NF1X2V7bc"
70 | },
71 | "href" : "https://api.spotify.com/v1/tracks/23ZdycsvnFHK9NF1X2V7bc",
72 | "id" : "23ZdycsvnFHK9NF1X2V7bc",
73 | "name" : "Nightrain",
74 | "preview_url" : "https://p.scdn.co/mp3-preview/30e5ca88cae78b02dea6a718b73b7db5e8ddcac6",
75 | "track_number" : 3,
76 | "type" : "track",
77 | "uri" : "spotify:track:23ZdycsvnFHK9NF1X2V7bc"
78 | }, {
79 | "artists" : [ {
80 | "external_urls" : {
81 | "spotify" : "https://open.spotify.com/artist/3qm84nBOXUEQ2vnTfUTTFC"
82 | },
83 | "href" : "https://api.spotify.com/v1/artists/3qm84nBOXUEQ2vnTfUTTFC",
84 | "id" : "3qm84nBOXUEQ2vnTfUTTFC",
85 | "name" : "Guns N' Roses",
86 | "type" : "artist",
87 | "uri" : "spotify:artist:3qm84nBOXUEQ2vnTfUTTFC"
88 | } ],
89 | "available_markets" : [ "CR", "DO", "GT", "HN", "NI", "PA", "SV" ],
90 | "disc_number" : 1,
91 | "duration_ms" : 263866,
92 | "explicit" : true,
93 | "external_urls" : {
94 | "spotify" : "https://open.spotify.com/track/0DEGMFohkJUQprEG1wU4SN"
95 | },
96 | "href" : "https://api.spotify.com/v1/tracks/0DEGMFohkJUQprEG1wU4SN",
97 | "id" : "0DEGMFohkJUQprEG1wU4SN",
98 | "name" : "Out Ta Get Me",
99 | "preview_url" : "https://p.scdn.co/mp3-preview/dc59755154bb63036390eba86ce774db660e1740",
100 | "track_number" : 4,
101 | "type" : "track",
102 | "uri" : "spotify:track:0DEGMFohkJUQprEG1wU4SN"
103 | }, {
104 | "artists" : [ {
105 | "external_urls" : {
106 | "spotify" : "https://open.spotify.com/artist/3qm84nBOXUEQ2vnTfUTTFC"
107 | },
108 | "href" : "https://api.spotify.com/v1/artists/3qm84nBOXUEQ2vnTfUTTFC",
109 | "id" : "3qm84nBOXUEQ2vnTfUTTFC",
110 | "name" : "Guns N' Roses",
111 | "type" : "artist",
112 | "uri" : "spotify:artist:3qm84nBOXUEQ2vnTfUTTFC"
113 | } ],
114 | "available_markets" : [ "CR", "DO", "GT", "HN", "NI", "PA", "SV" ],
115 | "disc_number" : 1,
116 | "duration_ms" : 228893,
117 | "explicit" : true,
118 | "external_urls" : {
119 | "spotify" : "https://open.spotify.com/track/0waeZEK1KpCuUvXkXCTOM2"
120 | },
121 | "href" : "https://api.spotify.com/v1/tracks/0waeZEK1KpCuUvXkXCTOM2",
122 | "id" : "0waeZEK1KpCuUvXkXCTOM2",
123 | "name" : "Mr. Brownstone",
124 | "preview_url" : "https://p.scdn.co/mp3-preview/7ef4ea55e9a9183f9ca504ee532b23ad90b944b3",
125 | "track_number" : 5,
126 | "type" : "track",
127 | "uri" : "spotify:track:0waeZEK1KpCuUvXkXCTOM2"
128 | } ],
129 | "limit" : 5,
130 | "next" : "https://api.spotify.com/v1/albums/1oR3KrPIp4CbagPa3PhtPp/tracks?offset=5&limit=5",
131 | "offset" : 0,
132 | "previous" : null,
133 | "total" : 12
134 | }
135 |
--------------------------------------------------------------------------------
/tests/fixtures/artist-albums.json:
--------------------------------------------------------------------------------
1 | {
2 | "href" : "https://api.spotify.com/v1/artists/6v8FB84lnmJs434UJf2Mrm/albums?offset=0&limit=5&album_type=single,album,compilation,appears_on",
3 | "items" : [ {
4 | "album_type" : "album",
5 | "available_markets" : [ "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
6 | "external_urls" : {
7 | "spotify" : "https://open.spotify.com/album/361dgrFRURo9gwWnSQG6Uu"
8 | },
9 | "href" : "https://api.spotify.com/v1/albums/361dgrFRURo9gwWnSQG6Uu",
10 | "id" : "361dgrFRURo9gwWnSQG6Uu",
11 | "images" : [ {
12 | "height" : 640,
13 | "url" : "https://i.scdn.co/image/e052cc9842df2738bf2c3368bafa49c43fad83a4",
14 | "width" : 640
15 | }, {
16 | "height" : 300,
17 | "url" : "https://i.scdn.co/image/4996a3b2db84b5d121b54e457d8b173d7162f917",
18 | "width" : 300
19 | }, {
20 | "height" : 64,
21 | "url" : "https://i.scdn.co/image/db0c983e1171f03186b61213ce9fe1f718b56ed1",
22 | "width" : 64
23 | } ],
24 | "name" : "Storytone",
25 | "type" : "album",
26 | "uri" : "spotify:album:361dgrFRURo9gwWnSQG6Uu"
27 | }, {
28 | "album_type" : "album",
29 | "available_markets" : [ "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
30 | "external_urls" : {
31 | "spotify" : "https://open.spotify.com/album/1HhdHx1asrL4Uf6rlQxCx1"
32 | },
33 | "href" : "https://api.spotify.com/v1/albums/1HhdHx1asrL4Uf6rlQxCx1",
34 | "id" : "1HhdHx1asrL4Uf6rlQxCx1",
35 | "images" : [ {
36 | "height" : 640,
37 | "url" : "https://i.scdn.co/image/709702b800b5239e4bf345dbeb4fe78cd8e068b1",
38 | "width" : 640
39 | }, {
40 | "height" : 300,
41 | "url" : "https://i.scdn.co/image/637262e8c1c45457ec8773ecd08eef81f9cdb41d",
42 | "width" : 300
43 | }, {
44 | "height" : 64,
45 | "url" : "https://i.scdn.co/image/976b30553009b1866368a2afd01b336a2a36f180",
46 | "width" : 64
47 | } ],
48 | "name" : "Storytone (Deluxe Version)",
49 | "type" : "album",
50 | "uri" : "spotify:album:1HhdHx1asrL4Uf6rlQxCx1"
51 | }, {
52 | "album_type" : "album",
53 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
54 | "external_urls" : {
55 | "spotify" : "https://open.spotify.com/album/0qeBoQjqVd5L0T93Pf7Vwm"
56 | },
57 | "href" : "https://api.spotify.com/v1/albums/0qeBoQjqVd5L0T93Pf7Vwm",
58 | "id" : "0qeBoQjqVd5L0T93Pf7Vwm",
59 | "images" : [ {
60 | "height" : 640,
61 | "url" : "https://i.scdn.co/image/aef94edcf57574157875dd9b7d496656b25d4aec",
62 | "width" : 640
63 | }, {
64 | "height" : 300,
65 | "url" : "https://i.scdn.co/image/6bac516642b884fb5661fa0c109bd3a5933d2b51",
66 | "width" : 300
67 | }, {
68 | "height" : 64,
69 | "url" : "https://i.scdn.co/image/2cc77e54f4519bfa72566fa293fbbd912c33d0bc",
70 | "width" : 64
71 | } ],
72 | "name" : "A Letter Home",
73 | "type" : "album",
74 | "uri" : "spotify:album:0qeBoQjqVd5L0T93Pf7Vwm"
75 | }, {
76 | "album_type" : "album",
77 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
78 | "external_urls" : {
79 | "spotify" : "https://open.spotify.com/album/2h2zv7tulv5460Ijs1tpxs"
80 | },
81 | "href" : "https://api.spotify.com/v1/albums/2h2zv7tulv5460Ijs1tpxs",
82 | "id" : "2h2zv7tulv5460Ijs1tpxs",
83 | "images" : [ {
84 | "height" : 640,
85 | "url" : "https://i.scdn.co/image/a042e0e50696368635ba9df8c598f79d856e4b92",
86 | "width" : 640
87 | }, {
88 | "height" : 300,
89 | "url" : "https://i.scdn.co/image/adf120673a98a0dec42bb9cde4b8a437ec60ec97",
90 | "width" : 300
91 | }, {
92 | "height" : 64,
93 | "url" : "https://i.scdn.co/image/e2ee2d191ca0b30e417a1ae0d8889a0f365b8d37",
94 | "width" : 64
95 | } ],
96 | "name" : "Live At The Cellar Door",
97 | "type" : "album",
98 | "uri" : "spotify:album:2h2zv7tulv5460Ijs1tpxs"
99 | }, {
100 | "album_type" : "album",
101 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
102 | "external_urls" : {
103 | "spotify" : "https://open.spotify.com/album/4j9JVeEgEEiwYyYMiueTvG"
104 | },
105 | "href" : "https://api.spotify.com/v1/albums/4j9JVeEgEEiwYyYMiueTvG",
106 | "id" : "4j9JVeEgEEiwYyYMiueTvG",
107 | "images" : [ {
108 | "height" : 640,
109 | "url" : "https://i.scdn.co/image/65d3f76280a7dd0344e26529792711c42c182d01",
110 | "width" : 640
111 | }, {
112 | "height" : 300,
113 | "url" : "https://i.scdn.co/image/48faa882bceddcf24bc9c867ec600ccb3a6f250f",
114 | "width" : 300
115 | }, {
116 | "height" : 64,
117 | "url" : "https://i.scdn.co/image/94f0b553fd359907deb913aa4ffdeaad9cf09148",
118 | "width" : 64
119 | } ],
120 | "name" : "Le Noise",
121 | "type" : "album",
122 | "uri" : "spotify:album:4j9JVeEgEEiwYyYMiueTvG"
123 | } ],
124 | "limit" : 5,
125 | "next" : "https://api.spotify.com/v1/artists/6v8FB84lnmJs434UJf2Mrm/albums?offset=5&limit=5&album_type=single,album,compilation,appears_on",
126 | "offset" : 0,
127 | "previous" : null,
128 | "total" : 113
129 | }
130 |
--------------------------------------------------------------------------------
/tests/fixtures/artist.json:
--------------------------------------------------------------------------------
1 | {
2 | "external_urls" : {
3 | "spotify" : "https://open.spotify.com/artist/36QJpDe2go2KgaRleHCDTp"
4 | },
5 | "followers" : {
6 | "href" : null,
7 | "total" : 499010
8 | },
9 | "genres" : [ "album rock", "blues-rock", "classic rock", "rock" ],
10 | "href" : "https://api.spotify.com/v1/artists/36QJpDe2go2KgaRleHCDTp",
11 | "id" : "36QJpDe2go2KgaRleHCDTp",
12 | "images" : [ {
13 | "height" : 600,
14 | "url" : "https://i.scdn.co/image/f66bcaf9b7d00b6bd5dafa1d99586390a23f4935",
15 | "width" : 600
16 | }, {
17 | "height" : 200,
18 | "url" : "https://i.scdn.co/image/89f85b03f128056ea2d0ca941be999853bea3d7c",
19 | "width" : 200
20 | }, {
21 | "height" : 64,
22 | "url" : "https://i.scdn.co/image/f0959931aeb8aa174ef264d0b1ab7dd99cd22c03",
23 | "width" : 64
24 | } ],
25 | "name" : "Led Zeppelin",
26 | "popularity" : 56,
27 | "type" : "artist",
28 | "uri" : "spotify:artist:36QJpDe2go2KgaRleHCDTp"
29 | }
30 |
--------------------------------------------------------------------------------
/tests/fixtures/artists.json:
--------------------------------------------------------------------------------
1 | {
2 | "artists" : [ {
3 | "external_urls" : {
4 | "spotify" : "https://open.spotify.com/artist/6v8FB84lnmJs434UJf2Mrm"
5 | },
6 | "followers" : {
7 | "href" : null,
8 | "total" : 353848
9 | },
10 | "genres" : [ "country rock", "folk rock", "roots rock", "singer-songwriter" ],
11 | "href" : "https://api.spotify.com/v1/artists/6v8FB84lnmJs434UJf2Mrm",
12 | "id" : "6v8FB84lnmJs434UJf2Mrm",
13 | "images" : [ {
14 | "height" : 667,
15 | "url" : "https://i.scdn.co/image/9cf3c4417d299cd974c156636aed25cf776cc588",
16 | "width" : 1000
17 | }, {
18 | "height" : 427,
19 | "url" : "https://i.scdn.co/image/28056039df6056d8cadc9f10a6bf407af316bf32",
20 | "width" : 640
21 | }, {
22 | "height" : 133,
23 | "url" : "https://i.scdn.co/image/fe3f3cc057d65f0a3425f1b95f589f0a894bac56",
24 | "width" : 200
25 | }, {
26 | "height" : 43,
27 | "url" : "https://i.scdn.co/image/fe257b86f46d004487856f413d4e1e8f505986de",
28 | "width" : 64
29 | } ],
30 | "name" : "Neil Young",
31 | "popularity" : 65,
32 | "type" : "artist",
33 | "uri" : "spotify:artist:6v8FB84lnmJs434UJf2Mrm"
34 | }, {
35 | "external_urls" : {
36 | "spotify" : "https://open.spotify.com/artist/6olE6TJLqED3rqDCT0FyPh"
37 | },
38 | "followers" : {
39 | "href" : null,
40 | "total" : 962884
41 | },
42 | "genres" : [ "alternative rock", "grunge", "permanent wave" ],
43 | "href" : "https://api.spotify.com/v1/artists/6olE6TJLqED3rqDCT0FyPh",
44 | "id" : "6olE6TJLqED3rqDCT0FyPh",
45 | "images" : [ {
46 | "height" : 1057,
47 | "url" : "https://i.scdn.co/image/5d66307bbf73337bb073bfb2bf242e099a47e219",
48 | "width" : 1000
49 | }, {
50 | "height" : 677,
51 | "url" : "https://i.scdn.co/image/695867a7c6a0df25c5bddf0b00cc2cfde35b3ffc",
52 | "width" : 640
53 | }, {
54 | "height" : 211,
55 | "url" : "https://i.scdn.co/image/3a9c12e86ad8e2bbecb0e919b80bb5ece6f1dbe3",
56 | "width" : 200
57 | }, {
58 | "height" : 68,
59 | "url" : "https://i.scdn.co/image/5ae6f10633adc53210cd18a4f216f27de330f19d",
60 | "width" : 64
61 | } ],
62 | "name" : "Nirvana",
63 | "popularity" : 67,
64 | "type" : "artist",
65 | "uri" : "spotify:artist:6olE6TJLqED3rqDCT0FyPh"
66 | } ]
67 | }
68 |
--------------------------------------------------------------------------------
/tests/fixtures/audio-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "danceability": 0.808,
3 | "energy": 0.626,
4 | "key": 7,
5 | "loudness": -12.733,
6 | "mode": 1,
7 | "speechiness": 0.168,
8 | "acousticness": 0.00187,
9 | "instrumentalness": 0.159,
10 | "liveness": 0.376,
11 | "valence": 0.369,
12 | "tempo": 123.99,
13 | "type": "audio_features",
14 | "id": "0eGsygTp906u18L0Oimnem",
15 | "uri": "spotify:track:0eGsygTp906u18L0Oimnem",
16 | "track_href": "https://api.spotify.com/v1/tracks/0eGsygTp906u18L0Oimnem",
17 | "analysis_url": "http://echonest-analysis.s3.amazonaws.com/TR/WhpYUARk1kNJ_qP0AdKGcDDFKOQTTgsOoINrqyPQjkUnbteuuBiyj_u94iFCSGzdxGiwqQ6d77f4QLL_8=/3/full.json?AWSAccessKeyId=AKIAJRDFEY23UEVW42BQ&Expires=1458063189&Signature=JRE8SDZStpNOdUsPN/PoS49FMtQ%3D",
18 | "duration_ms": 535223,
19 | "time_signature": 4
20 | }
21 |
--------------------------------------------------------------------------------
/tests/fixtures/available-genre-seeds.json:
--------------------------------------------------------------------------------
1 | {
2 | "genres" : [ "acoustic", "afrobeat", "alt-rock", "alternative", "ambient", "anime", "black-metal", "bluegrass", "blues", "bossanova", "brazil", "breakbeat", "british", "cantopop", "chicago-house", "children", "chill", "classical", "club", "comedy", "country", "dance", "dancehall", "death-metal", "deep-house", "detroit-techno", "disco", "disney", "drum-and-bass", "dub", "dubstep", "edm", "electro", "electronic", "emo", "folk", "forro", "french", "funk", "garage", "german", "gospel", "goth", "grindcore", "groove", "grunge", "guitar", "happy", "hard-rock", "hardcore", "hardstyle", "heavy-metal", "hip-hop", "holidays", "honky-tonk", "house", "idm", "indian", "indie", "indie-pop", "industrial", "iranian", "j-dance", "j-idol", "j-pop", "j-rock", "jazz", "k-pop", "kids", "latin", "latino", "malay", "mandopop", "metal", "metal-misc", "metalcore", "minimal-techno", "movies", "mpb", "new-age", "new-release", "opera", "pagode", "party", "philippines-opm", "piano", "pop", "pop-film", "post-dubstep", "power-pop", "progressive-house", "psych-rock", "punk", "punk-rock", "r-n-b", "rainy-day", "reggae", "reggaeton", "road-trip", "rock", "rock-n-roll", "rockabilly", "romance", "sad", "salsa", "samba", "sertanejo", "show-tunes", "singer-songwriter", "ska", "sleep", "songwriter", "soul", "soundtracks", "spanish", "study", "summer", "swedish", "synth-pop", "tango", "techno", "trance", "trip-hop", "turkish", "work-out", "world-music" ]
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/categories-list.json:
--------------------------------------------------------------------------------
1 | {
2 | "categories" : {
3 | "href" : "https://api.spotify.com/v1/browse/categories?country=FR&offset=0&limit=20",
4 | "items" : [ {
5 | "href" : "https://api.spotify.com/v1/browse/categories/toplists",
6 | "icons" : [ {
7 | "height" : 275,
8 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/toplists_11160599e6a04ac5d6f2757f5511778f_0_0_275_275.jpg",
9 | "width" : 275
10 | } ],
11 | "id" : "toplists",
12 | "name" : "Top Lists"
13 | }, {
14 | "href" : "https://api.spotify.com/v1/browse/categories/mood",
15 | "icons" : [ {
16 | "height" : 274,
17 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/original/mood-274x274_976986a31ac8c49794cbdc7246fd5ad7_274x274.jpg",
18 | "width" : 274
19 | } ],
20 | "id" : "mood",
21 | "name" : "Mood"
22 | }, {
23 | "href" : "https://api.spotify.com/v1/browse/categories/party",
24 | "icons" : [ {
25 | "height" : 274,
26 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/party-274x274_73d1907a7371c3bb96a288390a96ee27_0_0_274_274.jpg",
27 | "width" : 274
28 | } ],
29 | "id" : "party",
30 | "name" : "Party"
31 | }, {
32 | "href" : "https://api.spotify.com/v1/browse/categories/pop",
33 | "icons" : [ {
34 | "height" : 274,
35 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/pop-274x274_447148649685019f5e2a03a39e78ba52_0_0_274_274.jpg",
36 | "width" : 274
37 | } ],
38 | "id" : "pop",
39 | "name" : "Pop Culture"
40 | }, {
41 | "href" : "https://api.spotify.com/v1/browse/categories/workout",
42 | "icons" : [ {
43 | "height" : 275,
44 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/workout_856581c1c545a5305e49a3cd8be804a0_0_0_275_275.jpg",
45 | "width" : 275
46 | } ],
47 | "id" : "workout",
48 | "name" : "Workout"
49 | }, {
50 | "href" : "https://api.spotify.com/v1/browse/categories/focus",
51 | "icons" : [ {
52 | "height" : 274,
53 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/original/genre-images-square-274x274_5e50d72b846a198fcd2ca9b3aef5f0c8_274x274.jpg",
54 | "width" : 274
55 | } ],
56 | "id" : "focus",
57 | "name" : "Focus"
58 | }, {
59 | "href" : "https://api.spotify.com/v1/browse/categories/rock",
60 | "icons" : [ {
61 | "height" : 274,
62 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/rock_9ce79e0a4ef901bbd10494f5b855d3cc_0_0_274_274.jpg",
63 | "width" : 274
64 | } ],
65 | "id" : "rock",
66 | "name" : "Rock"
67 | }, {
68 | "href" : "https://api.spotify.com/v1/browse/categories/indie_alt",
69 | "icons" : [ {
70 | "height" : 274,
71 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/indie-274x274_add35b2b767ff7f3897262ad86809bdb_0_0_274_274.jpg",
72 | "width" : 274
73 | } ],
74 | "id" : "indie_alt",
75 | "name" : "Indie/Alternative"
76 | }, {
77 | "href" : "https://api.spotify.com/v1/browse/categories/edm_dance",
78 | "icons" : [ {
79 | "height" : 274,
80 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/edm-274x274_0ef612604200a9c14995432994455a6d_0_0_274_274.jpg",
81 | "width" : 274
82 | } ],
83 | "id" : "edm_dance",
84 | "name" : "EDM/Dance"
85 | }, {
86 | "href" : "https://api.spotify.com/v1/browse/categories/chill",
87 | "icons" : [ {
88 | "height" : 274,
89 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/chill-274x274_4c46374f007813dd10b37e8d8fd35b4b_0_0_274_274.jpg",
90 | "width" : 274
91 | } ],
92 | "id" : "chill",
93 | "name" : "Chill"
94 | }, {
95 | "href" : "https://api.spotify.com/v1/browse/categories/dinner",
96 | "icons" : [ {
97 | "height" : 274,
98 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/original/dinner_1b6506abba0ba52c54e6d695c8571078_274x274.jpg",
99 | "width" : 274
100 | } ],
101 | "id" : "dinner",
102 | "name" : "Dinner"
103 | }, {
104 | "href" : "https://api.spotify.com/v1/browse/categories/sleep",
105 | "icons" : [ {
106 | "height" : 274,
107 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/sleep-274x274_0d4f836af8fab7bf31526968073e671c_0_0_274_274.jpg",
108 | "width" : 274
109 | } ],
110 | "id" : "sleep",
111 | "name" : "Sleep"
112 | }, {
113 | "href" : "https://api.spotify.com/v1/browse/categories/hiphop",
114 | "icons" : [ {
115 | "height" : 274,
116 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/original/hip-274_0a661854d61e29eace5fe63f73495e68_274x274.jpg",
117 | "width" : 274
118 | } ],
119 | "id" : "hiphop",
120 | "name" : "Hip Hop"
121 | }, {
122 | "href" : "https://api.spotify.com/v1/browse/categories/rnb",
123 | "icons" : [ {
124 | "height" : 274,
125 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/rb-274x274_a0e7a187f9449dd7722e1ddbd6191f48_0_0_274_274.jpg",
126 | "width" : 274
127 | } ],
128 | "id" : "rnb",
129 | "name" : "RnB"
130 | }, {
131 | "href" : "https://api.spotify.com/v1/browse/categories/country",
132 | "icons" : [ {
133 | "height" : 274,
134 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/country-folk_dd35e932222c37ea75623a5788a12935_0_0_274_274.jpg",
135 | "width" : 274
136 | } ],
137 | "id" : "country",
138 | "name" : "Country"
139 | }, {
140 | "href" : "https://api.spotify.com/v1/browse/categories/folk_americana",
141 | "icons" : [ {
142 | "height" : 274,
143 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/folk-icon_1682b3a3e59b9a351243e6b7b26129fb_0_0_274_274.jpg",
144 | "width" : 274
145 | } ],
146 | "id" : "folk_americana",
147 | "name" : "Folk & Americana"
148 | }, {
149 | "href" : "https://api.spotify.com/v1/browse/categories/metal",
150 | "icons" : [ {
151 | "height" : 274,
152 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/original/metal_27c921443fd0a5ba95b1b2c2ae654b2b_274x274.jpg",
153 | "width" : 274
154 | } ],
155 | "id" : "metal",
156 | "name" : "Metal"
157 | }, {
158 | "href" : "https://api.spotify.com/v1/browse/categories/soul",
159 | "icons" : [ {
160 | "height" : 274,
161 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/soul-274x274_266bc900b35dda8956380cffc73a4d8c_0_0_274_274.jpg",
162 | "width" : 274
163 | } ],
164 | "id" : "soul",
165 | "name" : "Soul"
166 | }, {
167 | "href" : "https://api.spotify.com/v1/browse/categories/travel",
168 | "icons" : [ {
169 | "height" : 274,
170 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/travel-274x274_1e89cd5b42cf8bd2ff8fc4fb26f2e955_0_0_274_274.jpg",
171 | "width" : 274
172 | } ],
173 | "id" : "travel",
174 | "name" : "Travel"
175 | }, {
176 | "href" : "https://api.spotify.com/v1/browse/categories/decades",
177 | "icons" : [ {
178 | "height" : 274,
179 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/decades_9ad8e458242b2ac8b184e79ef336c455_0_0_274_274.jpg",
180 | "width" : 274
181 | } ],
182 | "id" : "decades",
183 | "name" : "Decades"
184 | } ],
185 | "limit" : 20,
186 | "next" : "https://api.spotify.com/v1/browse/categories?country=FR&offset=20&limit=20",
187 | "offset" : 0,
188 | "previous" : null,
189 | "total" : 31
190 | }
191 | }
--------------------------------------------------------------------------------
/tests/fixtures/category.json:
--------------------------------------------------------------------------------
1 | {
2 | "href" : "https://api.spotify.com/v1/browse/categories/party",
3 | "icons" : [ {
4 | "height" : 274,
5 | "url" : "https://datsnxq1rwndn.cloudfront.net/media/derived/party-274x274_73d1907a7371c3bb96a288390a96ee27_0_0_274_274.jpg",
6 | "width" : 274
7 | } ],
8 | "id" : "party",
9 | "name" : "Party"
10 | }
--------------------------------------------------------------------------------
/tests/fixtures/episode.json:
--------------------------------------------------------------------------------
1 | {
2 | "audio_preview_url": "https://p.scdn.co/mp3-preview/566fcc94708f39bcddc09e4ce84a8e5db8f07d4d",
3 | "description": "En ny tysk bok granskar för första gången Tredje rikets drogberoende, från Führerns knarkande till hans soldater på speed. Och kändisförfattaren Antony Beevor får nu kritik av en svensk kollega. Hitler var beroende av sin livläkare, som gav honom mängder av narkotiska preparat, och blitzkrigssoldaterna knaprade 35 miljoner speedtabletter under invasionen av Frankrike 1940. I den nyutkomna boken Der Totale Rausch, Det totala ruset, ger författaren Norman Ohler för första gången en samlad bild av knarkandet i Tredje riket. Mycket tyder på att Hitler var gravt drogpåverkad under flera avgörande beslut under kriget, säger han, och får medhåll av medicinhistorikern Peter Steinkamp som undersökt de tyska soldaternas intensiva användande av pervitin, en variant av crystal meth.Dessutom får nu den kände militärhistoriska författaren Antony Beevor kritik för att hans senaste bok om Ardenneroffensiven lutar sig alltför tungt mot amerikanska källor, och dessutom innehåller många felaktiga detaljer. Det menar författarkollegan Christer Bergström, som själv skrivit en bok om striderna i Ardennerna.Programledare är Tobias Svanelid.",
4 | "duration_ms": 1502795,
5 | "explicit": false,
6 | "external_urls": {
7 | "spotify": "https://open.spotify.com/episode/512ojhOuo1ktJprKbVcKyQ"
8 | },
9 | "href": "https://api.spotify.com/v1/episodes/512ojhOuo1ktJprKbVcKyQ",
10 | "id": "512ojhOuo1ktJprKbVcKyQ",
11 | "images": [
12 | {
13 | "height": 640,
14 | "url": "https://i.scdn.co/image/6bcff849a483dd3c2883b3f0272848b909f1bbce",
15 | "width": 640
16 | },
17 | {
18 | "height": 300,
19 | "url": "https://i.scdn.co/image/66250bd121ee949ed5026decbfd97e255b25a5c8",
20 | "width": 300
21 | },
22 | {
23 | "height": 64,
24 | "url": "https://i.scdn.co/image/e29c75799cad73927fad713011edad574868d8da",
25 | "width": 64
26 | }
27 | ],
28 | "is_externally_hosted": false,
29 | "is_playable": true,
30 | "language": "sv",
31 | "languages": [
32 | "sv"
33 | ],
34 | "name": "Tredje rikets knarkande granskas",
35 | "release_date": "2015-10-01",
36 | "release_date_precision": "day",
37 | "show": {
38 | "available_markets": [
39 | "AD",
40 | "AE",
41 | "AR",
42 | "AT",
43 | "AU",
44 | "BE",
45 | "BG",
46 | "BH",
47 | "BO",
48 | "BR",
49 | "CA",
50 | "CH",
51 | "CL",
52 | "CO",
53 | "CR",
54 | "CY",
55 | "CZ",
56 | "DE",
57 | "DK",
58 | "DO",
59 | "DZ",
60 | "EC",
61 | "EE",
62 | "ES",
63 | "FI",
64 | "FR",
65 | "GB",
66 | "GR",
67 | "GT",
68 | "HK",
69 | "HN",
70 | "HU",
71 | "ID",
72 | "IE",
73 | "IL",
74 | "IN",
75 | "IS",
76 | "IT",
77 | "JO",
78 | "JP",
79 | "KW",
80 | "LB",
81 | "LI",
82 | "LT",
83 | "LU",
84 | "LV",
85 | "MA",
86 | "MC",
87 | "MT",
88 | "MX",
89 | "MY",
90 | "NI",
91 | "NL",
92 | "NO",
93 | "NZ",
94 | "OM",
95 | "PA",
96 | "PE",
97 | "PH",
98 | "PL",
99 | "PS",
100 | "PT",
101 | "PY",
102 | "QA",
103 | "RO",
104 | "SE",
105 | "SG",
106 | "SK",
107 | "SV",
108 | "TH",
109 | "TN",
110 | "TR",
111 | "TW",
112 | "US",
113 | "UY",
114 | "VN",
115 | "ZA"
116 | ],
117 | "copyrights": [],
118 | "description": "Vi är där historien är. Ansvarig utgivare: Nina Glans",
119 | "explicit": false,
120 | "external_urls": {
121 | "spotify": "https://open.spotify.com/show/38bS44xjbVVZ3No3ByF1dJ"
122 | },
123 | "href": "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ",
124 | "id": "38bS44xjbVVZ3No3ByF1dJ",
125 | "images": [
126 | {
127 | "height": 640,
128 | "url": "https://i.scdn.co/image/3c59a8b611000c8b10c8013013c3783dfb87a3bc",
129 | "width": 640
130 | },
131 | {
132 | "height": 300,
133 | "url": "https://i.scdn.co/image/2d70c06ac70d8c6144c94cabf7f4abcf85c4b7e4",
134 | "width": 300
135 | },
136 | {
137 | "height": 64,
138 | "url": "https://i.scdn.co/image/3dc007829bc0663c24089e46743a9f4ae15e65f8",
139 | "width": 64
140 | }
141 | ],
142 | "is_externally_hosted": false,
143 | "languages": [
144 | "sv"
145 | ],
146 | "media_type": "audio",
147 | "name": "Vetenskapsradion Historia",
148 | "publisher": "Sveriges Radio",
149 | "type": "show",
150 | "uri": "spotify:show:38bS44xjbVVZ3No3ByF1dJ"
151 | },
152 | "type": "episode",
153 | "uri": "spotify:episode:512ojhOuo1ktJprKbVcKyQ"
154 | }
155 |
--------------------------------------------------------------------------------
/tests/fixtures/episodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "episodes" : [ {
3 | "audio_preview_url" : "https://p.scdn.co/mp3-preview/7e8f7a00f1425d495bcb992bae48a19c31342490",
4 | "description" : "Följ med till Riddarhuset och hör om dråpliga motiv och billiga lösningar på husets drygt 2 300 vapensköldar som nu studerats. Och hör hur stormakten Sveriges krig finansierades av Frankrike. Skelögda ugglor och halshuggna troll är några av motiven på de drygt 2 300 vapensköldar som hänger i Riddarhuset i Stockholm. Den svenska adelns grafiska profiler har nu hamnat under luppen när heraldikern Magnus Bäckmark som förste forskare skärskådat detta bortglömda kulturarvs estetik och historia. Vetenskapsradion Historia följer med honom till Riddarhuset för att fascineras av både vackra och tokfula motiv. Dessutom om att den svenska stormaktstiden nu måste omvärderas efter att historikern Svante Norrhem undersökt de enorma summor som Sverige erhöll av Frankrike. Under närmare 170 år var Sverige närmast en klientstat till Frankrike, där närmare 20 procent av svensk ekonomi bestod av franska subsidier. Tobias Svanelid undersöker hur förhållandet påverkade länderna och hur mycket av den svenska stormaktstiden som egentligen var fransk.",
5 | "duration_ms" : 2685023,
6 | "explicit" : false,
7 | "external_urls" : {
8 | "spotify" : "https://open.spotify.com/episode/77o6BIVlYM3msb4MMIL1jH"
9 | },
10 | "href" : "https://api.spotify.com/v1/episodes/77o6BIVlYM3msb4MMIL1jH",
11 | "id" : "77o6BIVlYM3msb4MMIL1jH",
12 | "images" : [ {
13 | "height" : 640,
14 | "url" : "https://i.scdn.co/image/8092469858486ff19eeefcea7ec5c17b72c9590a",
15 | "width" : 640
16 | }, {
17 | "height" : 300,
18 | "url" : "https://i.scdn.co/image/7e921e844f4deb5a8fbdacba7abb6210357237e5",
19 | "width" : 300
20 | }, {
21 | "height" : 64,
22 | "url" : "https://i.scdn.co/image/729df823ef7f9a6f8aaf57d532490c9aab43e0dc",
23 | "width" : 64
24 | } ],
25 | "is_externally_hosted" : false,
26 | "is_playable" : true,
27 | "language" : "sv",
28 | "name" : "Riddarnas vapensköldar under lupp",
29 | "release_date" : "2019-09-10",
30 | "release_date_precision" : "day",
31 | "show" : {
32 | "available_markets" : [ "AD", "AE", "AR", "AT", "AU", "BE", "BG", "BH", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IL", "IN", "IS", "IT", "JO", "JP", "KW", "LB", "LI", "LT", "LU", "LV", "MA", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "OM", "PA", "PE", "PH", "PL", "PS", "PT", "PY", "QA", "RO", "SE", "SG", "SK", "SV", "TH", "TN", "TR", "TW", "US", "UY", "VN", "ZA" ],
33 | "copyrights" : [ ],
34 | "description" : "Vi är där historien är. Ansvarig utgivare: Nina Glans",
35 | "explicit" : false,
36 | "external_urls" : {
37 | "spotify" : "https://open.spotify.com/show/38bS44xjbVVZ3No3ByF1dJ"
38 | },
39 | "href" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ",
40 | "id" : "38bS44xjbVVZ3No3ByF1dJ",
41 | "images" : [ {
42 | "height" : 640,
43 | "url" : "https://i.scdn.co/image/3c59a8b611000c8b10c8013013c3783dfb87a3bc",
44 | "width" : 640
45 | }, {
46 | "height" : 300,
47 | "url" : "https://i.scdn.co/image/2d70c06ac70d8c6144c94cabf7f4abcf85c4b7e4",
48 | "width" : 300
49 | }, {
50 | "height" : 64,
51 | "url" : "https://i.scdn.co/image/3dc007829bc0663c24089e46743a9f4ae15e65f8",
52 | "width" : 64
53 | } ],
54 | "is_externally_hosted" : false,
55 | "languages" : [ "sv" ],
56 | "media_type" : "audio",
57 | "name" : "Vetenskapsradion Historia",
58 | "publisher" : "Sveriges Radio",
59 | "type" : "show",
60 | "uri" : "spotify:show:38bS44xjbVVZ3No3ByF1dJ"
61 | },
62 | "type" : "episode",
63 | "uri" : "spotify:episode:77o6BIVlYM3msb4MMIL1jH"
64 | }, {
65 | "audio_preview_url" : "https://p.scdn.co/mp3-preview/83bc7f2d40e850582a4ca118b33c256358de06ff",
66 | "description" : "Följ med Tobias Svanelid till Sveriges äldsta tegelkyrka, till Edsleskog mitt i den dalsländska granskogen, där ett religiöst skrytbygge skulle resas över ett skändligt brott. I Edsleskog i Dalsland gräver arkeologerna nu ut vad som en gång verkar ha varit en av Sveriges största medeltidskyrkor, och kanske också den äldsta som byggts i tegel, 1200-talets high-tech-material. Tobias Svanelid reser dit för att höra historien om den märkliga och bortglömda kyrkan som grundlades på platsen för ett prästmord och dessutom kan ha varit Skarabiskopens försök att lägga beslag på det vilda Dalsland. Dessutom om sjudagarsveckan idag ett välkänt koncept runt hela världen, men hur gammal är egentligen veckans historia? Dick Harrison vet svaret.",
67 | "duration_ms" : 2685023,
68 | "explicit" : false,
69 | "external_urls" : {
70 | "spotify" : "https://open.spotify.com/episode/0Q86acNRm6V9GYx55SXKwf"
71 | },
72 | "href" : "https://api.spotify.com/v1/episodes/0Q86acNRm6V9GYx55SXKwf",
73 | "id" : "0Q86acNRm6V9GYx55SXKwf",
74 | "images" : [ {
75 | "height" : 640,
76 | "url" : "https://i.scdn.co/image/b2398424d6158a21fe8677e2de5f6f3d1dc4a04f",
77 | "width" : 640
78 | }, {
79 | "height" : 300,
80 | "url" : "https://i.scdn.co/image/a52780a1d7e1bc42619413c3dea7042396c87f49",
81 | "width" : 300
82 | }, {
83 | "height" : 64,
84 | "url" : "https://i.scdn.co/image/88e21be860cf11f0b95ee8dfb47ddb08a13319a7",
85 | "width" : 64
86 | } ],
87 | "is_externally_hosted" : false,
88 | "is_playable" : true,
89 | "language" : "sv",
90 | "name" : "Okända katedralen i Dalsland",
91 | "release_date" : "2019-09-03",
92 | "release_date_precision" : "day",
93 | "show" : {
94 | "available_markets" : [ "AD", "AE", "AR", "AT", "AU", "BE", "BG", "BH", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IL", "IN", "IS", "IT", "JO", "JP", "KW", "LB", "LI", "LT", "LU", "LV", "MA", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "OM", "PA", "PE", "PH", "PL", "PS", "PT", "PY", "QA", "RO", "SE", "SG", "SK", "SV", "TH", "TN", "TR", "TW", "US", "UY", "VN", "ZA" ],
95 | "copyrights" : [ ],
96 | "description" : "Vi är där historien är. Ansvarig utgivare: Nina Glans",
97 | "explicit" : false,
98 | "external_urls" : {
99 | "spotify" : "https://open.spotify.com/show/38bS44xjbVVZ3No3ByF1dJ"
100 | },
101 | "href" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ",
102 | "id" : "38bS44xjbVVZ3No3ByF1dJ",
103 | "images" : [ {
104 | "height" : 640,
105 | "url" : "https://i.scdn.co/image/3c59a8b611000c8b10c8013013c3783dfb87a3bc",
106 | "width" : 640
107 | }, {
108 | "height" : 300,
109 | "url" : "https://i.scdn.co/image/2d70c06ac70d8c6144c94cabf7f4abcf85c4b7e4",
110 | "width" : 300
111 | }, {
112 | "height" : 64,
113 | "url" : "https://i.scdn.co/image/3dc007829bc0663c24089e46743a9f4ae15e65f8",
114 | "width" : 64
115 | } ],
116 | "is_externally_hosted" : false,
117 | "languages" : [ "sv" ],
118 | "media_type" : "audio",
119 | "name" : "Vetenskapsradion Historia",
120 | "publisher" : "Sveriges Radio",
121 | "type" : "show",
122 | "uri" : "spotify:show:38bS44xjbVVZ3No3ByF1dJ"
123 | },
124 | "type" : "episode",
125 | "uri" : "spotify:episode:0Q86acNRm6V9GYx55SXKwf"
126 | } ]
127 | }
128 |
--------------------------------------------------------------------------------
/tests/fixtures/featured-playlists.json:
--------------------------------------------------------------------------------
1 | {
2 | "message" : "Your Saturday night just got so much better...",
3 | "playlists" : {
4 | "href" : "https://api.spotify.com/v1/browse/featured-playlists?country=SE&locale=sv_SE×tamp=2014-10-25T21:00:00&offset=0&limit=5",
5 | "items" : [ {
6 | "collaborative" : false,
7 | "external_urls" : {
8 | "spotify" : "http://open.spotify.com/user/spotify/playlist/5Oo5QuAOjjXMMXphFqC6eo"
9 | },
10 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/5Oo5QuAOjjXMMXphFqC6eo",
11 | "id" : "5Oo5QuAOjjXMMXphFqC6eo",
12 | "images" : [ {
13 | "url" : "https://i.scdn.co/image/48294c9d3d171ac4c5b6400e0d8f2c357569ce75"
14 | } ],
15 | "name" : "Weekend Hangouts",
16 | "owner" : {
17 | "external_urls" : {
18 | "spotify" : "http://open.spotify.com/user/spotify"
19 | },
20 | "href" : "https://api.spotify.com/v1/users/spotify",
21 | "id" : "spotify",
22 | "type" : "user",
23 | "uri" : "spotify:user:spotify"
24 | },
25 | "public" : null,
26 | "tracks" : {
27 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/5Oo5QuAOjjXMMXphFqC6eo/tracks",
28 | "total" : 223
29 | },
30 | "type" : "playlist",
31 | "uri" : "spotify:user:spotify:playlist:5Oo5QuAOjjXMMXphFqC6eo"
32 | }, {
33 | "collaborative" : false,
34 | "external_urls" : {
35 | "spotify" : "http://open.spotify.com/user/tv4se/playlist/6gI54Ois1dlCKZJqrLyVKN"
36 | },
37 | "href" : "https://api.spotify.com/v1/users/tv4se/playlists/6gI54Ois1dlCKZJqrLyVKN",
38 | "id" : "6gI54Ois1dlCKZJqrLyVKN",
39 | "images" : [ {
40 | "url" : "https://i.scdn.co/image/3be6c5a5879dc630c7136a63e30ad61f55c64ec6"
41 | } ],
42 | "name" : "SÅ MYCKET BÄTTRE 2014",
43 | "owner" : {
44 | "external_urls" : {
45 | "spotify" : "http://open.spotify.com/user/tv4se"
46 | },
47 | "href" : "https://api.spotify.com/v1/users/tv4se",
48 | "id" : "tv4se",
49 | "type" : "user",
50 | "uri" : "spotify:user:tv4se"
51 | },
52 | "public" : null,
53 | "tracks" : {
54 | "href" : "https://api.spotify.com/v1/users/tv4se/playlists/6gI54Ois1dlCKZJqrLyVKN/tracks",
55 | "total" : 12
56 | },
57 | "type" : "playlist",
58 | "uri" : "spotify:user:tv4se:playlist:6gI54Ois1dlCKZJqrLyVKN"
59 | }, {
60 | "collaborative" : false,
61 | "external_urls" : {
62 | "spotify" : "http://open.spotify.com/user/spotify/playlist/63UdjnaWDFp0VNrwQ28qUw"
63 | },
64 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/63UdjnaWDFp0VNrwQ28qUw",
65 | "id" : "63UdjnaWDFp0VNrwQ28qUw",
66 | "images" : [ {
67 | "url" : "https://i.scdn.co/image/68dc8102daec4e47d501f5f83f0ed7c59e14ab23"
68 | } ],
69 | "name" : "Dinner Party",
70 | "owner" : {
71 | "external_urls" : {
72 | "spotify" : "http://open.spotify.com/user/spotify"
73 | },
74 | "href" : "https://api.spotify.com/v1/users/spotify",
75 | "id" : "spotify",
76 | "type" : "user",
77 | "uri" : "spotify:user:spotify"
78 | },
79 | "public" : null,
80 | "tracks" : {
81 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/63UdjnaWDFp0VNrwQ28qUw/tracks",
82 | "total" : 121
83 | },
84 | "type" : "playlist",
85 | "uri" : "spotify:user:spotify:playlist:63UdjnaWDFp0VNrwQ28qUw"
86 | }, {
87 | "collaborative" : false,
88 | "external_urls" : {
89 | "spotify" : "http://open.spotify.com/user/spotify/playlist/5ILSWr90l2Bgk89xuhsysy"
90 | },
91 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/5ILSWr90l2Bgk89xuhsysy",
92 | "id" : "5ILSWr90l2Bgk89xuhsysy",
93 | "images" : [ {
94 | "url" : "https://i.scdn.co/image/b15df0f45ccf5b0b4ac97674658de5b42c342df1"
95 | } ],
96 | "name" : "Pre-Party",
97 | "owner" : {
98 | "external_urls" : {
99 | "spotify" : "http://open.spotify.com/user/spotify"
100 | },
101 | "href" : "https://api.spotify.com/v1/users/spotify",
102 | "id" : "spotify",
103 | "type" : "user",
104 | "uri" : "spotify:user:spotify"
105 | },
106 | "public" : null,
107 | "tracks" : {
108 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/5ILSWr90l2Bgk89xuhsysy/tracks",
109 | "total" : 106
110 | },
111 | "type" : "playlist",
112 | "uri" : "spotify:user:spotify:playlist:5ILSWr90l2Bgk89xuhsysy"
113 | }, {
114 | "collaborative" : false,
115 | "external_urls" : {
116 | "spotify" : "http://open.spotify.com/user/spotify/playlist/7BixMZxL4bhgULJQ5wPbUz"
117 | },
118 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/7BixMZxL4bhgULJQ5wPbUz",
119 | "id" : "7BixMZxL4bhgULJQ5wPbUz",
120 | "images" : [ {
121 | "url" : "https://i.scdn.co/image/202df3b14174a06f1cd9541302d51c291eed8bee"
122 | } ],
123 | "name" : "Chillax",
124 | "owner" : {
125 | "external_urls" : {
126 | "spotify" : "http://open.spotify.com/user/spotify"
127 | },
128 | "href" : "https://api.spotify.com/v1/users/spotify",
129 | "id" : "spotify",
130 | "type" : "user",
131 | "uri" : "spotify:user:spotify"
132 | },
133 | "public" : null,
134 | "tracks" : {
135 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/7BixMZxL4bhgULJQ5wPbUz/tracks",
136 | "total" : 102
137 | },
138 | "type" : "playlist",
139 | "uri" : "spotify:user:spotify:playlist:7BixMZxL4bhgULJQ5wPbUz"
140 | } ],
141 | "limit" : 5,
142 | "next" : "https://api.spotify.com/v1/browse/featured-playlists?country=SE&locale=sv_SE×tamp=2014-10-25T21:00:00&offset=5&limit=5",
143 | "offset" : 0,
144 | "previous" : null,
145 | "total" : 12
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/tests/fixtures/markets.json:
--------------------------------------------------------------------------------
1 | {
2 | "markets": ["AD","AE","AR","AT","AU","BE","BG","BH"]
3 | }
4 |
--------------------------------------------------------------------------------
/tests/fixtures/multiple-audio-features.json:
--------------------------------------------------------------------------------
1 | { "audio_features":
2 | [ { "danceability": 0.808,
3 | "energy": 0.626,
4 | "key": 7,
5 | "loudness": -12.733,
6 | "mode": 1,
7 | "speechiness": 0.168,
8 | "acousticness": 0.00187,
9 | "instrumentalness": 0.159,
10 | "liveness": 0.376,
11 | "valence": 0.369,
12 | "tempo": 123.99,
13 | "type": "audio_features",
14 | "id": "0eGsygTp906u18L0Oimnem",
15 | "uri": "spotify:track:0eGsygTp906u18L0Oimnem",
16 | "track_href": "https://api.spotify.com/v1/tracks/0eGsygTp906u18L0Oimnem",
17 | "analysis_url": "http://echonest-analysis.s3.amazonaws.com/TR/WhpYUARk1kNJ_qP0AdKGcDDFKOQTTgsOoINrqyPQjkUnbteuuBiyj_u94iFCSGzdxGiwqQ6d77f4QLL_8=/3/full.json?AWSAccessKeyId=AKIAJRDFEY23UEVW42BQ&Expires=1458063189&Signature=JRE8SDZStpNOdUsPN/PoS49FMtQ%3D",
18 | "duration_ms": 535223,
19 | "time_signature": 4
20 | },
21 | { "danceability": 0.457,
22 | "energy": 0.815,
23 | "key": 1,
24 | "loudness": -7.199,
25 | "mode": 1,
26 | "speechiness": 0.034,
27 | "acousticness": 0.102,
28 | "instrumentalness": 0.0319,
29 | "liveness": 0.103,
30 | "valence": 0.382,
31 | "tempo": 96.083,
32 | "type": "audio_features",
33 | "id": "1lDWb6b6ieDQ2xT7ewTC3G",
34 | "uri": "spotify:track:1lDWb6b6ieDQ2xT7ewTC3G",
35 | "track_href": "https://api.spotify.com/v1/tracks/1lDWb6b6ieDQ2xT7ewTC3G",
36 | "analysis_url": "http://echonest-analysis.s3.amazonaws.com/TR/WhuQhwPDhmEg5TO4JjbJu0my-awIhk3eaXkRd1ofoJ7tXogPnMtbxkTyLOeHXu5Jke0FCIt52saKJyfPM=/3/full.json?AWSAccessKeyId=AKIAJRDFEY23UEVW42BQ&Expires=1458063189&Signature=qfclum7FwTaR/7aQbnBNO0daCsM%3D",
37 | "duration_ms": 187800,
38 | "time_signature": 4
39 | } ]
40 | }
41 |
--------------------------------------------------------------------------------
/tests/fixtures/my-playlists.json:
--------------------------------------------------------------------------------
1 | {
2 | "href": "https://api.spotify.com/v1/users/wizzler/playlists",
3 | "items": [ {
4 | "collaborative": false,
5 | "external_urls": {
6 | "spotify": "http://open.spotify.com/user/wizzler/playlists/53Y8wT46QIMz5H4WQ8O22c"
7 | },
8 | "href": "https://api.spotify.com/v1/users/wizzler/playlists/53Y8wT46QIMz5H4WQ8O22c",
9 | "id": "53Y8wT46QIMz5H4WQ8O22c",
10 | "images" : [ ],
11 | "name": "Wizzlers Big Playlist",
12 | "owner": {
13 | "external_urls": {
14 | "spotify": "http://open.spotify.com/user/wizzler"
15 | },
16 | "href": "https://api.spotify.com/v1/users/wizzler",
17 | "id": "wizzler",
18 | "type": "user",
19 | "uri": "spotify:user:wizzler"
20 | },
21 | "public": true,
22 | "snapshot_id" : "bNLWdmhh+HDsbHzhckXeDC0uyKyg4FjPI/KEsKjAE526usnz2LxwgyBoMShVL+z+",
23 | "tracks": {
24 | "href": "https://api.spotify.com/v1/users/wizzler/playlists/53Y8wT46QIMz5H4WQ8O22c/tracks",
25 | "total": 30
26 | },
27 | "type": "playlist",
28 | "uri": "spotify:user:wizzler:playlist:53Y8wT46QIMz5H4WQ8O22c"
29 | }, {
30 | "collaborative": false,
31 | "external_urls": {
32 | "spotify": "http://open.spotify.com/user/wizzlersmate/playlists/1AVZz0mBuGbCEoNRQdYQju"
33 | },
34 | "href": "https://api.spotify.com/v1/users/wizzlersmate/playlists/1AVZz0mBuGbCEoNRQdYQju",
35 | "id": "1AVZz0mBuGbCEoNRQdYQju",
36 | "images" : [ ],
37 | "name": "Another Playlist",
38 | "owner": {
39 | "external_urls": {
40 | "spotify": "http://open.spotify.com/user/wizzlersmate"
41 | },
42 | "href": "https://api.spotify.com/v1/users/wizzlersmate",
43 | "id": "wizzlersmate",
44 | "type": "user",
45 | "uri": "spotify:user:wizzlersmate"
46 | },
47 | "public": true,
48 | "snapshot_id" : "Y0qg/IT5T02DKpw4uQKc/9RUrqQJ07hbTKyEeDRPOo9LU0g0icBrIXwVkHfQZ/aD",
49 | "tracks": {
50 | "href": "https://api.spotify.com/v1/users/wizzlersmate/playlists/1AVZz0mBuGbCEoNRQdYQju/tracks",
51 | "total": 58
52 | },
53 | "type": "playlist",
54 | "uri": "spotify:user:wizzlersmate:playlist:1AVZz0mBuGbCEoNRQdYQju"
55 | } ],
56 | "limit": 9,
57 | "next": null,
58 | "offset": 0,
59 | "previous": null,
60 | "total": 9
61 | }
62 |
--------------------------------------------------------------------------------
/tests/fixtures/my-queue.json:
--------------------------------------------------------------------------------
1 | {
2 | "currently_playing": {
3 | "album": {
4 | "album_type": "album",
5 | "artists": [
6 | {
7 | "external_urls": {
8 | "spotify": "https://open.spotify.com/artist/3Mcii5XWf6E0lrY3Uky4cA"
9 | },
10 | "href": "https://api.spotify.com/v1/artists/3Mcii5XWf6E0lrY3Uky4cA",
11 | "id": "3Mcii5XWf6E0lrY3Uky4cA",
12 | "name": "Ice Cube",
13 | "type": "artist",
14 | "uri": "spotify:artist:3Mcii5XWf6E0lrY3Uky4cA"
15 | }
16 | ],
17 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
18 | "external_urls": {
19 | "spotify": "https://open.spotify.com/album/3xbWu3mLkkWZvgHtExIaKZ"
20 | },
21 | "href": "https://api.spotify.com/v1/albums/3xbWu3mLkkWZvgHtExIaKZ",
22 | "id": "3xbWu3mLkkWZvgHtExIaKZ",
23 | "images": [
24 | {
25 | "height": 640,
26 | "url": "https://i.scdn.co/image/ab67616d0000b273e83d58253e2e7c6742bbf9e4",
27 | "width": 640
28 | },
29 | {
30 | "height": 300,
31 | "url": "https://i.scdn.co/image/ab67616d00001e02e83d58253e2e7c6742bbf9e4",
32 | "width": 300
33 | },
34 | {
35 | "height": 64,
36 | "url": "https://i.scdn.co/image/ab67616d00004851e83d58253e2e7c6742bbf9e4",
37 | "width": 64
38 | }
39 | ],
40 | "name": "Raw Footage",
41 | "release_date": "2008-01-01",
42 | "release_date_precision": "day",
43 | "total_tracks": 16,
44 | "type": "album",
45 | "uri": "spotify:album:3xbWu3mLkkWZvgHtExIaKZ"
46 | },
47 | "artists": [
48 | {
49 | "external_urls": {
50 | "spotify": "https://open.spotify.com/artist/3Mcii5XWf6E0lrY3Uky4cA"
51 | },
52 | "href": "https://api.spotify.com/v1/artists/3Mcii5XWf6E0lrY3Uky4cA",
53 | "id": "3Mcii5XWf6E0lrY3Uky4cA",
54 | "name": "Ice Cube",
55 | "type": "artist",
56 | "uri": "spotify:artist:3Mcii5XWf6E0lrY3Uky4cA"
57 | }
58 | ],
59 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
60 | "disc_number": 1,
61 | "duration_ms": 247893,
62 | "explicit": true,
63 | "external_ids": { "isrc": "USNPD0800740" },
64 | "external_urls": {
65 | "spotify": "https://open.spotify.com/track/5RAMj0e8KWDBYXZeWYmfEx"
66 | },
67 | "href": "https://api.spotify.com/v1/tracks/5RAMj0e8KWDBYXZeWYmfEx",
68 | "id": "5RAMj0e8KWDBYXZeWYmfEx",
69 | "is_local": false,
70 | "name": "Get Money, Spend Money, No Money",
71 | "popularity": 29,
72 | "preview_url": "https://p.scdn.co/mp3-preview/9ed43baa5752713105962f8bcaa8ea092cb17764?cid=ab12ee273d0b40b38b78f557aaccc0fe",
73 | "track_number": 12,
74 | "type": "track",
75 | "uri": "spotify:track:5RAMj0e8KWDBYXZeWYmfEx"
76 | },
77 | "queue": [
78 | {
79 | "album": {
80 | "album_type": "album",
81 | "artists": [
82 | {
83 | "external_urls": {
84 | "spotify": "https://open.spotify.com/artist/6NyJIFHAePjHR1pFxwisqz"
85 | },
86 | "href": "https://api.spotify.com/v1/artists/6NyJIFHAePjHR1pFxwisqz",
87 | "id": "6NyJIFHAePjHR1pFxwisqz",
88 | "name": "Kurupt",
89 | "type": "artist",
90 | "uri": "spotify:artist:6NyJIFHAePjHR1pFxwisqz"
91 | }
92 | ],
93 | "available_markets": [ "AE", "BH", "CA", "DZ", "EG", "IQ", "JO", "KW", "LB", "LY", "MA", "MX", "OM", "QA", "SA", "TN", "US" ],
94 | "external_urls": {
95 | "spotify": "https://open.spotify.com/album/4dSH1oNZoziNjKAanUimWd"
96 | },
97 | "href": "https://api.spotify.com/v1/albums/4dSH1oNZoziNjKAanUimWd",
98 | "id": "4dSH1oNZoziNjKAanUimWd",
99 | "images": [
100 | {
101 | "height": 640,
102 | "url": "https://i.scdn.co/image/ab67616d0000b2730c46f1a7e2cb5ebfd4714b28",
103 | "width": 640
104 | },
105 | {
106 | "height": 300,
107 | "url": "https://i.scdn.co/image/ab67616d00001e020c46f1a7e2cb5ebfd4714b28",
108 | "width": 300
109 | },
110 | {
111 | "height": 64,
112 | "url": "https://i.scdn.co/image/ab67616d000048510c46f1a7e2cb5ebfd4714b28",
113 | "width": 64
114 | }
115 | ],
116 | "name": "Kuruption!",
117 | "release_date": "1998-10-06",
118 | "release_date_precision": "day",
119 | "total_tracks": 23,
120 | "type": "album",
121 | "uri": "spotify:album:4dSH1oNZoziNjKAanUimWd"
122 | },
123 | "artists": [
124 | {
125 | "external_urls": {
126 | "spotify": "https://open.spotify.com/artist/6NyJIFHAePjHR1pFxwisqz"
127 | },
128 | "href": "https://api.spotify.com/v1/artists/6NyJIFHAePjHR1pFxwisqz",
129 | "id": "6NyJIFHAePjHR1pFxwisqz",
130 | "name": "Kurupt",
131 | "type": "artist",
132 | "uri": "spotify:artist:6NyJIFHAePjHR1pFxwisqz"
133 | },
134 | {
135 | "external_urls": {
136 | "spotify": "https://open.spotify.com/artist/0bqBpcIABLyrGD6e6llQ1S"
137 | },
138 | "href": "https://api.spotify.com/v1/artists/0bqBpcIABLyrGD6e6llQ1S",
139 | "id": "0bqBpcIABLyrGD6e6llQ1S",
140 | "name": "Tray Dee",
141 | "type": "artist",
142 | "uri": "spotify:artist:0bqBpcIABLyrGD6e6llQ1S"
143 | },
144 | {
145 | "external_urls": {
146 | "spotify": "https://open.spotify.com/artist/197MEJ6lXwJXav4BstjTxl"
147 | },
148 | "href": "https://api.spotify.com/v1/artists/197MEJ6lXwJXav4BstjTxl",
149 | "id": "197MEJ6lXwJXav4BstjTxl",
150 | "name": "Slip Capone",
151 | "type": "artist",
152 | "uri": "spotify:artist:197MEJ6lXwJXav4BstjTxl"
153 | }
154 | ],
155 | "available_markets": [ "AE", "BH", "CA", "DZ", "EG", "IQ", "JO", "KW", "LB", "LY", "MA", "MX", "OM", "QA", "SA", "TN", "US" ],
156 | "disc_number": 1,
157 | "duration_ms": 311666,
158 | "explicit": true,
159 | "external_ids": { "isrc": "USAM19800243" },
160 | "external_urls": {
161 | "spotify": "https://open.spotify.com/track/6S1oHrwjeF66VRUl1b76P6"
162 | },
163 | "href": "https://api.spotify.com/v1/tracks/6S1oHrwjeF66VRUl1b76P6",
164 | "id": "6S1oHrwjeF66VRUl1b76P6",
165 | "is_local": false,
166 | "name": "C-Walk",
167 | "popularity": 55,
168 | "preview_url": "https://p.scdn.co/mp3-preview/34ba7cde30c9ece93e927db9f90d29484a2aa712?cid=ab12ee273d0b40b38b78f557aaccc0fe",
169 | "track_number": 7,
170 | "type": "track",
171 | "uri": "spotify:track:6S1oHrwjeF66VRUl1b76P6"
172 | },
173 | {
174 | "album": {
175 | "album_type": "single",
176 | "artists": [
177 | {
178 | "external_urls": {
179 | "spotify": "https://open.spotify.com/artist/7B4hKK0S9QYnaoqa9OuwgX"
180 | },
181 | "href": "https://api.spotify.com/v1/artists/7B4hKK0S9QYnaoqa9OuwgX",
182 | "id": "7B4hKK0S9QYnaoqa9OuwgX",
183 | "name": "Eazy-E",
184 | "type": "artist",
185 | "uri": "spotify:artist:7B4hKK0S9QYnaoqa9OuwgX"
186 | }
187 | ],
188 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
189 | "external_urls": {
190 | "spotify": "https://open.spotify.com/album/65bIyEn06DYO7oFkEYKOdl"
191 | },
192 | "href": "https://api.spotify.com/v1/albums/65bIyEn06DYO7oFkEYKOdl",
193 | "id": "65bIyEn06DYO7oFkEYKOdl",
194 | "images": [
195 | {
196 | "height": 640,
197 | "url": "https://i.scdn.co/image/ab67616d0000b273edfb9175857fb59639e148e0",
198 | "width": 640
199 | },
200 | {
201 | "height": 300,
202 | "url": "https://i.scdn.co/image/ab67616d00001e02edfb9175857fb59639e148e0",
203 | "width": 300
204 | },
205 | {
206 | "height": 64,
207 | "url": "https://i.scdn.co/image/ab67616d00004851edfb9175857fb59639e148e0",
208 | "width": 64
209 | }
210 | ],
211 | "name": "5150 Home 4 Tha Sick",
212 | "release_date": "1992-12-10",
213 | "release_date_precision": "day",
214 | "total_tracks": 5,
215 | "type": "album",
216 | "uri": "spotify:album:65bIyEn06DYO7oFkEYKOdl"
217 | },
218 | "artists": [
219 | {
220 | "external_urls": {
221 | "spotify": "https://open.spotify.com/artist/7B4hKK0S9QYnaoqa9OuwgX"
222 | },
223 | "href": "https://api.spotify.com/v1/artists/7B4hKK0S9QYnaoqa9OuwgX",
224 | "id": "7B4hKK0S9QYnaoqa9OuwgX",
225 | "name": "Eazy-E",
226 | "type": "artist",
227 | "uri": "spotify:artist:7B4hKK0S9QYnaoqa9OuwgX"
228 | }
229 | ],
230 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
231 | "disc_number": 1,
232 | "duration_ms": 183333,
233 | "explicit": true,
234 | "external_ids": { "isrc": "USPO19200009" },
235 | "external_urls": {
236 | "spotify": "https://open.spotify.com/track/217m5TxpXyqbpR7WWmoSqO"
237 | },
238 | "href": "https://api.spotify.com/v1/tracks/217m5TxpXyqbpR7WWmoSqO",
239 | "id": "217m5TxpXyqbpR7WWmoSqO",
240 | "is_local": false,
241 | "name": "Only If You Want It",
242 | "popularity": 50,
243 | "preview_url": "https://p.scdn.co/mp3-preview/7d638c71412ab30df978e5684e04d86c50b84a7e?cid=ab12ee273d0b40b38b78f557aaccc0fe",
244 | "track_number": 2,
245 | "type": "track",
246 | "uri": "spotify:track:217m5TxpXyqbpR7WWmoSqO"
247 | }
248 | ]
249 | }
250 |
--------------------------------------------------------------------------------
/tests/fixtures/new-releases.json:
--------------------------------------------------------------------------------
1 | {
2 | "albums" : {
3 | "href" : "https://api.spotify.com/v1/browse/new-releases?country=SE&offset=0&limit=5",
4 | "items" : [ {
5 | "album_type" : "album",
6 | "available_markets" : [ "AT", "AU", "CH", "CZ", "DE", "FI", "IE", "NL", "NZ", "SI", "SK", "HK", "MY", "PH", "SG", "TW", "BG", "CY", "EE", "GR", "LT", "LV", "RO", "TR", "AD", "BE", "DK", "ES", "FR", "HU", "IT", "LI", "LU", "MC", "MT", "NO", "PL", "SE", "GB", "PT", "IS", "UY", "AR", "CL", "PY", "BO", "BR", "DO", "CO", "EC", "PA", "PE", "CR", "GT", "HN", "NI", "SV" ],
7 | "external_urls" : {
8 | "spotify" : "https://open.spotify.com/album/59Mjf7KcSU7niudeL4JsmQ"
9 | },
10 | "href" : "https://api.spotify.com/v1/albums/59Mjf7KcSU7niudeL4JsmQ",
11 | "id" : "59Mjf7KcSU7niudeL4JsmQ",
12 | "images" : [ {
13 | "height" : 640,
14 | "url" : "https://i.scdn.co/image/34ad44065027a8e99b5fc071b09b8109928a1e59",
15 | "width" : 640
16 | }, {
17 | "height" : 300,
18 | "url" : "https://i.scdn.co/image/b9fc808bcf4a9e1d9576489e3f90ec6bfbf762df",
19 | "width" : 300
20 | }, {
21 | "height" : 64,
22 | "url" : "https://i.scdn.co/image/7319c88ba4e6c9a091dd62a1bfbc1f72fe68e779",
23 | "width" : 64
24 | } ],
25 | "name" : "Feels So Good",
26 | "type" : "album",
27 | "uri" : "spotify:album:59Mjf7KcSU7niudeL4JsmQ"
28 | }, {
29 | "album_type" : "album",
30 | "available_markets" : [ "AT", "BE", "CH", "CZ", "DE", "EE", "FI", "IE", "LI", "LT", "LU", "LV", "NL", "SE", "SK", "TR" ],
31 | "external_urls" : {
32 | "spotify" : "https://open.spotify.com/album/4KyyjcX6N1xcxTqdOTOghN"
33 | },
34 | "href" : "https://api.spotify.com/v1/albums/4KyyjcX6N1xcxTqdOTOghN",
35 | "id" : "4KyyjcX6N1xcxTqdOTOghN",
36 | "images" : [ {
37 | "height" : 640,
38 | "url" : "https://i.scdn.co/image/4b12129e2b6630cab568c2941835d0e28f85840b",
39 | "width" : 640
40 | }, {
41 | "height" : 300,
42 | "url" : "https://i.scdn.co/image/c9ed2290296aa4038139f708ff7b6cefa65b98af",
43 | "width" : 300
44 | }, {
45 | "height" : 64,
46 | "url" : "https://i.scdn.co/image/e8dd9b7e27363122b27eb99e63bf274a0849bd5f",
47 | "width" : 64
48 | } ],
49 | "name" : "Back to Earth",
50 | "type" : "album",
51 | "uri" : "spotify:album:4KyyjcX6N1xcxTqdOTOghN"
52 | }, {
53 | "album_type" : "album",
54 | "available_markets" : [ "AT", "CH", "CZ", "DE", "FI", "NL", "NZ", "SE", "SI", "SK" ],
55 | "external_urls" : {
56 | "spotify" : "https://open.spotify.com/album/7yYva2MJYef1aOFVBtlA8I"
57 | },
58 | "href" : "https://api.spotify.com/v1/albums/7yYva2MJYef1aOFVBtlA8I",
59 | "id" : "7yYva2MJYef1aOFVBtlA8I",
60 | "images" : [ {
61 | "height" : 640,
62 | "url" : "https://i.scdn.co/image/a904eaf3c6440542493b10ef552cd955a8b1515a",
63 | "width" : 640
64 | }, {
65 | "height" : 300,
66 | "url" : "https://i.scdn.co/image/c9bbdc3adc7a8a2f159f31da13a989457572353d",
67 | "width" : 300
68 | }, {
69 | "height" : 64,
70 | "url" : "https://i.scdn.co/image/fdaa69c01adb12a603a7af8f4bfbce39a413ef21",
71 | "width" : 64
72 | } ],
73 | "name" : "Nostalgia",
74 | "type" : "album",
75 | "uri" : "spotify:album:7yYva2MJYef1aOFVBtlA8I"
76 | }, {
77 | "album_type" : "album",
78 | "available_markets" : [ "SE" ],
79 | "external_urls" : {
80 | "spotify" : "https://open.spotify.com/album/517pAxOuxIz2HyltDuBWYf"
81 | },
82 | "href" : "https://api.spotify.com/v1/albums/517pAxOuxIz2HyltDuBWYf",
83 | "id" : "517pAxOuxIz2HyltDuBWYf",
84 | "images" : [ {
85 | "height" : 640,
86 | "url" : "https://i.scdn.co/image/8db1fb7e5589d4d98022f3cc4ec0175a8bbc9b0e",
87 | "width" : 640
88 | }, {
89 | "height" : 300,
90 | "url" : "https://i.scdn.co/image/1342d206b6f1191c7b900878e1b2d1f147215d9d",
91 | "width" : 300
92 | }, {
93 | "height" : 64,
94 | "url" : "https://i.scdn.co/image/091236fc857a1c13a84319e614a0611d6829324f",
95 | "width" : 64
96 | } ],
97 | "name" : "Så mycket bättre 5 - Orups dag",
98 | "type" : "album",
99 | "uri" : "spotify:album:517pAxOuxIz2HyltDuBWYf"
100 | }, {
101 | "album_type" : "single",
102 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
103 | "external_urls" : {
104 | "spotify" : "https://open.spotify.com/album/50nrlTw0DWxBBvjNh1aiaq"
105 | },
106 | "href" : "https://api.spotify.com/v1/albums/50nrlTw0DWxBBvjNh1aiaq",
107 | "id" : "50nrlTw0DWxBBvjNh1aiaq",
108 | "images" : [ {
109 | "height" : 640,
110 | "url" : "https://i.scdn.co/image/e351bd24bd85140f3558022a101363bfc61c7e30",
111 | "width" : 640
112 | }, {
113 | "height" : 300,
114 | "url" : "https://i.scdn.co/image/159230ff048626f793c86c7763c2dc782430d95e",
115 | "width" : 300
116 | }, {
117 | "height" : 64,
118 | "url" : "https://i.scdn.co/image/8118a38f53efeb1dc9049d673851ed0ae3b6ac08",
119 | "width" : 64
120 | } ],
121 | "name" : "Skuggor från Karelen",
122 | "type" : "album",
123 | "uri" : "spotify:album:50nrlTw0DWxBBvjNh1aiaq"
124 | } ],
125 | "limit" : 5,
126 | "next" : "https://api.spotify.com/v1/browse/new-releases?country=SE&offset=5&limit=5",
127 | "offset" : 0,
128 | "previous" : null,
129 | "total" : 500
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/tests/fixtures/playlist-cover-image.json:
--------------------------------------------------------------------------------
1 | {
2 | "height" : null,
3 | "url" : "https://i.scdn.co/image/ab67706c0000bebb8d0ce13d55f634e290f744ba",
4 | "width" : null
5 | }
--------------------------------------------------------------------------------
/tests/fixtures/recently-played.json:
--------------------------------------------------------------------------------
1 | {
2 | "items": [
3 | {
4 | "track": {
5 | "artists": [
6 | {
7 | "external_urls": {
8 | "spotify": "https://open.spotify.com/artist/5EGOerlVYxwqxaTLEWumBR"
9 | },
10 | "href": "https://api.spotify.com/v1/artists/5EGOerlVYxwqxaTLEWumBR",
11 | "id": "5EGOerlVYxwqxaTLEWumBR",
12 | "name": "Synapson",
13 | "type": "artist",
14 | "uri": "spotify:artist:5EGOerlVYxwqxaTLEWumBR"
15 | },
16 | {
17 | "external_urls": {
18 | "spotify": "https://open.spotify.com/artist/1FCJ4zmRfkGUOtY65Jettg"
19 | },
20 | "href": "https://api.spotify.com/v1/artists/1FCJ4zmRfkGUOtY65Jettg",
21 | "id": "1FCJ4zmRfkGUOtY65Jettg",
22 | "name": "Victor D\u00e9m\u00e9",
23 | "type": "artist",
24 | "uri": "spotify:artist:1FCJ4zmRfkGUOtY65Jettg"
25 | }
26 | ],
27 | "available_markets": [
28 | "AD",
29 | "BE",
30 | "CH",
31 | "FR",
32 | "MC"
33 | ],
34 | "disc_number": 1,
35 | "duration_ms": 194706,
36 | "explicit": false,
37 | "external_urls": {
38 | "spotify": "https://open.spotify.com/track/3hxRKXzZS0XRYGZ123JDNH"
39 | },
40 | "href": "https://api.spotify.com/v1/tracks/3hxRKXzZS0XRYGZ123JDNH",
41 | "id": "3hxRKXzZS0XRYGZ123JDNH",
42 | "name": "Djon Maya Ma\u00ef - feat. Victor D\u00e9m\u00e9 [Original Mix]",
43 | "preview_url": "https://p.scdn.co/mp3-preview/000f50cee43b1dad8ba39983b519615f2db52265?cid=63c1bb278a88426aa745cb4d887e4054",
44 | "track_number": 1,
45 | "type": "track",
46 | "uri": "spotify:track:3hxRKXzZS0XRYGZ123JDNH"
47 | },
48 | "context": {
49 | "uri": "spotify:user:spotify_france:playlist:2sxfgEZ1yRhGo0mF3v8YSM",
50 | "external_urls": {
51 | "spotify": "https://open.spotify.com/user/spotify_france/playlist/2sxfgEZ1yRhGo0mF3v8YSM"
52 | },
53 | "href": "https://api.spotify.com/v1/users/spotify_france/playlists/2sxfgEZ1yRhGo0mF3v8YSM",
54 | "type": "playlist"
55 | },
56 | "played_at": "2017-03-05T10:07:38.946Z"
57 | },
58 | {
59 | "track": {
60 | "artists": [
61 | {
62 | "external_urls": {
63 | "spotify": "https://open.spotify.com/artist/1BNQnTVxfQqeMxr6xBi8X6"
64 | },
65 | "href": "https://api.spotify.com/v1/artists/1BNQnTVxfQqeMxr6xBi8X6",
66 | "id": "1BNQnTVxfQqeMxr6xBi8X6",
67 | "name": "Puggy",
68 | "type": "artist",
69 | "uri": "spotify:artist:1BNQnTVxfQqeMxr6xBi8X6"
70 | }
71 | ],
72 | "available_markets": [
73 | "AD",
74 | "AR",
75 | "AT",
76 | "AU",
77 | "BE",
78 | "BG",
79 | "BO",
80 | "BR",
81 | "CH",
82 | "CL",
83 | "CO",
84 | "CR",
85 | "CY",
86 | "CZ",
87 | "DE",
88 | "DK",
89 | "DO",
90 | "EC",
91 | "EE",
92 | "ES",
93 | "FI",
94 | "FR",
95 | "GB",
96 | "GR",
97 | "GT",
98 | "HK",
99 | "HN",
100 | "HU",
101 | "ID",
102 | "IE",
103 | "IS",
104 | "IT",
105 | "LI",
106 | "LT",
107 | "LU",
108 | "LV",
109 | "MC",
110 | "MT",
111 | "MY",
112 | "NI",
113 | "NO",
114 | "NZ",
115 | "PA",
116 | "PE",
117 | "PH",
118 | "PL",
119 | "PT",
120 | "PY",
121 | "SE",
122 | "SG",
123 | "SK",
124 | "SV",
125 | "TR",
126 | "TW",
127 | "UY"
128 | ],
129 | "disc_number": 1,
130 | "duration_ms": 284026,
131 | "explicit": false,
132 | "external_urls": {
133 | "spotify": "https://open.spotify.com/track/5ebiH7S9MKr4xRGXm7Wc8o"
134 | },
135 | "href": "https://api.spotify.com/v1/tracks/5ebiH7S9MKr4xRGXm7Wc8o",
136 | "id": "5ebiH7S9MKr4xRGXm7Wc8o",
137 | "name": "Lonely Town",
138 | "preview_url": "https://p.scdn.co/mp3-preview/e18ca6560f3bb74e661677e57741d527a9ae5747?cid=63c1bb278a88426aa745cb4d887e4054",
139 | "track_number": 4,
140 | "type": "track",
141 | "uri": "spotify:track:5ebiH7S9MKr4xRGXm7Wc8o"
142 | },
143 | "context": {
144 | "uri": "spotify:user:spotify_france:playlist:2sxfgEZ1yRhGo0mF3v8YSM",
145 | "external_urls": {
146 | "spotify": "https://open.spotify.com/user/spotify_france/playlist/2sxfgEZ1yRhGo0mF3v8YSM"
147 | },
148 | "href": "https://api.spotify.com/v1/users/spotify_france/playlists/2sxfgEZ1yRhGo0mF3v8YSM",
149 | "type": "playlist"
150 | },
151 | "played_at": "2017-03-05T09:46:23.542Z"
152 | }
153 | ],
154 | "next": "https://api.spotify.com/v1/me/player/recently-played?before=1488707183542&limit=2",
155 | "cursors": {
156 | "after": "1488708458946",
157 | "before": "1488707183542"
158 | },
159 | "limit": 2,
160 | "href": "https://api.spotify.com/v1/me/player/recently-played?before=1488708498755&limit=2"
161 | }
162 |
--------------------------------------------------------------------------------
/tests/fixtures/recommendations.json:
--------------------------------------------------------------------------------
1 | {
2 | "tracks": [
3 | {
4 | "artists" : [ {
5 | "external_urls" : {
6 | "spotify" : "https://open.spotify.com/artist/134GdR5tUtxJrf8cpsfpyY"
7 | },
8 | "href" : "https://api.spotify.com/v1/artists/134GdR5tUtxJrf8cpsfpyY",
9 | "id" : "134GdR5tUtxJrf8cpsfpyY",
10 | "name" : "Elliphant",
11 | "type" : "artist",
12 | "uri" : "spotify:artist:134GdR5tUtxJrf8cpsfpyY"
13 | }, {
14 | "external_urls" : {
15 | "spotify" : "https://open.spotify.com/artist/1D2oK3cJRq97OXDzu77BFR"
16 | },
17 | "href" : "https://api.spotify.com/v1/artists/1D2oK3cJRq97OXDzu77BFR",
18 | "id" : "1D2oK3cJRq97OXDzu77BFR",
19 | "name" : "Ras Fraser Jr.",
20 | "type" : "artist",
21 | "uri" : "spotify:artist:1D2oK3cJRq97OXDzu77BFR"
22 | } ],
23 | "disc_number" : 1,
24 | "duration_ms" : 199133,
25 | "explicit" : false,
26 | "external_urls" : {
27 | "spotify" : "https://open.spotify.com/track/1TKYPzH66GwsqyJFKFkBHQ"
28 | },
29 | "href" : "https://api.spotify.com/v1/tracks/1TKYPzH66GwsqyJFKFkBHQ",
30 | "id" : "1TKYPzH66GwsqyJFKFkBHQ",
31 | "is_playable" : true,
32 | "name" : "Music Is Life",
33 | "preview_url" : "https://p.scdn.co/mp3-preview/546099103387186dfe16743a33edd77e52cec738",
34 | "track_number" : 1,
35 | "type" : "track",
36 | "uri" : "spotify:track:1TKYPzH66GwsqyJFKFkBHQ"
37 | }, {
38 | "artists" : [ {
39 | "external_urls" : {
40 | "spotify" : "https://open.spotify.com/artist/1VBflYyxBhnDc9uVib98rw"
41 | },
42 | "href" : "https://api.spotify.com/v1/artists/1VBflYyxBhnDc9uVib98rw",
43 | "id" : "1VBflYyxBhnDc9uVib98rw",
44 | "name" : "Icona Pop",
45 | "type" : "artist",
46 | "uri" : "spotify:artist:1VBflYyxBhnDc9uVib98rw"
47 | } ],
48 | "disc_number" : 1,
49 | "duration_ms" : 187026,
50 | "explicit" : false,
51 | "external_urls" : {
52 | "spotify" : "https://open.spotify.com/track/15iosIuxC3C53BgsM5Uggs"
53 | },
54 | "href" : "https://api.spotify.com/v1/tracks/15iosIuxC3C53BgsM5Uggs",
55 | "id" : "15iosIuxC3C53BgsM5Uggs",
56 | "is_playable" : true,
57 | "name" : "All Night",
58 | "preview_url" : "https://p.scdn.co/mp3-preview/9ee589fa7fe4e96bad3483c20b3405fb59776424",
59 | "track_number" : 2,
60 | "type" : "track",
61 | "uri" : "spotify:track:15iosIuxC3C53BgsM5Uggs"
62 | }
63 | ],
64 | "seeds": [
65 | {
66 | "initial_pool_size": 500,
67 | "after_filtering_size": 380,
68 | "after_relinking_size": 365,
69 | "href": "https://api.spotify.com/v1/artists/4NHQUGzhtTLFvgF5SZesLK",
70 | "id": "4NHQUGzhtTLFvgF5SZesLK",
71 | "type": "artist"
72 | }, {
73 | "initial_pool_size": 250,
74 | "after_filtering_size": 172,
75 | "after_relinking_size": 144,
76 | "href": "https://api.spotify.com/v1/tracks/0c6xIDDpzE81m2q797ordA",
77 | "id": "0c6xIDDpzE81m2q797ordA",
78 | "type": "track"
79 | }
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/tests/fixtures/refresh-token-no-refresh-token.json:
--------------------------------------------------------------------------------
1 | {
2 | "access_token": "64b2b6d12bfe4baae7dad3d018f8cbf6b0e7a044",
3 | "token_type": "Bearer",
4 | "expires_in": 3600,
5 | "scope": "user-follow-read user-follow-modify"
6 | }
7 |
--------------------------------------------------------------------------------
/tests/fixtures/refresh-token.json:
--------------------------------------------------------------------------------
1 | {
2 | "access_token": "64b2b6d12bfe4baae7dad3d018f8cbf6b0e7a044",
3 | "token_type": "Bearer",
4 | "expires_in": 3600,
5 | "scope": "user-follow-read user-follow-modify",
6 | "refresh_token": "4c82f23d91a75961f4d08134fc5ad0dfe6a4c36a"
7 | }
8 |
--------------------------------------------------------------------------------
/tests/fixtures/show-episodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "href" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ/episodes?offset=1&limit=2",
3 | "items" : [ {
4 | "audio_preview_url" : "https://p.scdn.co/mp3-preview/83bc7f2d40e850582a4ca118b33c256358de06ff",
5 | "description" : "Följ med Tobias Svanelid till Sveriges äldsta tegelkyrka, till Edsleskog mitt i den dalsländska granskogen, där ett religiöst skrytbygge skulle resas över ett skändligt brott. I Edsleskog i Dalsland gräver arkeologerna nu ut vad som en gång verkar ha varit en av Sveriges största medeltidskyrkor, och kanske också den äldsta som byggts i tegel, 1200-talets high-tech-material. Tobias Svanelid reser dit för att höra historien om den märkliga och bortglömda kyrkan som grundlades på platsen för ett prästmord och dessutom kan ha varit Skarabiskopens försök att lägga beslag på det vilda Dalsland. Dessutom om sjudagarsveckan idag ett välkänt koncept runt hela världen, men hur gammal är egentligen veckans historia? Dick Harrison vet svaret.",
6 | "duration_ms" : 2685023,
7 | "explicit" : false,
8 | "external_urls" : {
9 | "spotify" : "https://open.spotify.com/episode/0Q86acNRm6V9GYx55SXKwf"
10 | },
11 | "href" : "https://api.spotify.com/v1/episodes/0Q86acNRm6V9GYx55SXKwf",
12 | "id" : "0Q86acNRm6V9GYx55SXKwf",
13 | "images" : [ {
14 | "height" : 640,
15 | "url" : "https://i.scdn.co/image/b2398424d6158a21fe8677e2de5f6f3d1dc4a04f",
16 | "width" : 640
17 | }, {
18 | "height" : 300,
19 | "url" : "https://i.scdn.co/image/a52780a1d7e1bc42619413c3dea7042396c87f49",
20 | "width" : 300
21 | }, {
22 | "height" : 64,
23 | "url" : "https://i.scdn.co/image/88e21be860cf11f0b95ee8dfb47ddb08a13319a7",
24 | "width" : 64
25 | } ],
26 | "is_externally_hosted" : false,
27 | "is_playable" : true,
28 | "language" : "sv",
29 | "languages" : [ "sv" ],
30 | "name" : "Okända katedralen i Dalsland",
31 | "release_date" : "2019-09-03",
32 | "release_date_precision" : "day",
33 | "type" : "episode",
34 | "uri" : "spotify:episode:0Q86acNRm6V9GYx55SXKwf"
35 | }, {
36 | "audio_preview_url" : "https://p.scdn.co/mp3-preview/a712dea885b8d4090a61f0a903094181bd7d2005",
37 | "description" : "Electrolux firar hundra år av att ha dammsugit folkhemmet rent. Följ med Tobias Svanelid genom företagets historia och hör om dess grundare Axel Wenner-Gren - folkhemmets Elon Musk. 1919 drog Axel Wenner-Gren igång företaget som skulle komma att städa rent i folkhemmet Sverige och samtidigt göra sin grundare ofantligt rik. Författaren Ronald Fagerfjäll har färdigställt Electrolux jubileumsbok och guidar runt bland hundraåriga dammsugare och kylskåp. Men Wenner-grens sista projekt skulle bli ett fiasko, i Wennergrenland i British Columbia krossades de flesta av entreprenörens drömmar berättar den kanadensiske historikern Frank Leonard. Dessutom reder Dick Harrison ut huruvida Julius Caesar fått gå på plankan.",
38 | "duration_ms" : 2685023,
39 | "explicit" : false,
40 | "external_urls" : {
41 | "spotify" : "https://open.spotify.com/episode/1spUiev4ggXPq95a7KKHjW"
42 | },
43 | "href" : "https://api.spotify.com/v1/episodes/1spUiev4ggXPq95a7KKHjW",
44 | "id" : "1spUiev4ggXPq95a7KKHjW",
45 | "images" : [ {
46 | "height" : 640,
47 | "url" : "https://i.scdn.co/image/0bcb5e368982156f9da093d1e471b188af184559",
48 | "width" : 640
49 | }, {
50 | "height" : 300,
51 | "url" : "https://i.scdn.co/image/2023d6f3dc774a89e26b607e25c3b08ecaede0e9",
52 | "width" : 300
53 | }, {
54 | "height" : 64,
55 | "url" : "https://i.scdn.co/image/a2fa6895a86e61c149083adce7aa5f0404a19d8c",
56 | "width" : 64
57 | } ],
58 | "is_externally_hosted" : false,
59 | "is_playable" : true,
60 | "language" : "sv",
61 | "name" : "Electrolux och folkhemmets Elon Musk",
62 | "release_date" : "2019-08-27",
63 | "release_date_precision" : "day",
64 | "type" : "episode",
65 | "uri" : "spotify:episode:1spUiev4ggXPq95a7KKHjW"
66 | } ],
67 | "limit" : 2,
68 | "next" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ/episodes?offset=3&limit=2",
69 | "offset" : 1,
70 | "previous" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ/episodes?offset=0&limit=2",
71 | "total" : 499
72 | }
73 |
--------------------------------------------------------------------------------
/tests/fixtures/show.json:
--------------------------------------------------------------------------------
1 | {
2 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IL", "IS", "IT", "JP", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SK", "SV", "TH", "TR", "TW", "US", "UY", "VN", "ZA" ],
3 | "copyrights" : [ ],
4 | "description" : "Vi är där historien är. Ansvarig utgivare: Nina Glans",
5 | "episodes" : {
6 | "href" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ/episodes?offset=0&limit=50",
7 | "items" : [ {
8 | "audio_preview_url" : "https://p.scdn.co/mp3-preview/7a785904a33e34b0b2bd382c82fca16be7060c36",
9 | "description" : "Hör Tobias Svanelid och hans bok- och spelpaneler tipsa om de bästa historiska böckerna och spelen att njuta av i sommar! Hör Vetenskapsradion Historias Tobias Svanelid tipsa tillsammans med Kristina Ekero Eriksson och Urban Björstadius om de bästa böckerna att ta med till hängmattan i sommar. Något för alla utlovas såsom stormande 1700-talskärlek, seglivade småfolk och kvinnliga bågskyttar under korstågen. Dessutom tipsas om två historiska brädspel, där spelarna i det ena förflyttas till medeltidens Orléans, i det andra ska överleva på Robinson Kruses öde ö, bland annat genom att tillaga tigerstek! De böcker som nämns i programmet är: Völvor, krigare och vanligt folk av Kent Andersson Kvinnliga krigare av Stefan Högberg De små folkens historia av Ingmar Karlsson Jugoslaviens undergång av Sanimir Resic Venedig: En vägvisare i tid och rum av Carin Norberg och Carl Tham Samlare, jägare och andra fågelskådare av Susanne Nylund Skog Ebba Hochschild: Att leva efter döden av Caroline Ranby 68 av Henrik Berggren Förnuft eller känsla av Brita Planck Finanskrascher av Lars Magnusson Spelen som testas är: Orléans av Reiner Stockhausen Robinson Crusoe - Adventures on the Cursed Island av Ignazy Trzewiczek",
10 | "duration_ms" : 2677448,
11 | "external_urls" : {
12 | "spotify" : "https://open.spotify.com/episode/4d237GqKH4NP1jtgwy6bP3"
13 | },
14 | "href" : "https://api.spotify.com/v1/episodes/4d237GqKH4NP1jtgwy6bP3",
15 | "id" : "4d237GqKH4NP1jtgwy6bP3",
16 | "images" : [ {
17 | "height" : 640,
18 | "url" : "https://i.scdn.co/image/606eba074860660c91819636bcb5e141ddf4e23d",
19 | "width" : 640
20 | }, {
21 | "height" : 300,
22 | "url" : "https://i.scdn.co/image/245062bc785bb8672cd002cb96b518a4f50e9067",
23 | "width" : 300
24 | }, {
25 | "height" : 64,
26 | "url" : "https://i.scdn.co/image/cc0797a99e21733caf0f4e23685a173033fdaa49",
27 | "width" : 64
28 | } ],
29 | "is_externally_hosted" : false,
30 | "language" : "sv",
31 | "name" : "Pyttefolk och tigerstekar i hängmattan",
32 | "release_date" : "2018-06-19",
33 | "release_date_precision" : "day",
34 | "type" : "episode",
35 | "uri" : "spotify:episode:4d237GqKH4NP1jtgwy6bP3"
36 | } ],
37 | "limit" : 50,
38 | "next" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ/episodes?offset=50&limit=50",
39 | "offset" : 0,
40 | "previous" : null,
41 | "total" : 520
42 | },
43 | "explicit" : false,
44 | "external_urls" : {
45 | "spotify" : "https://open.spotify.com/show/38bS44xjbVVZ3No3ByF1dJ"
46 | },
47 | "href" : "https://api.spotify.com/v1/shows/38bS44xjbVVZ3No3ByF1dJ",
48 | "id" : "38bS44xjbVVZ3No3ByF1dJ",
49 | "images" : [ {
50 | "height" : 640,
51 | "url" : "https://i.scdn.co/image/3c59a8b611000c8b10c8013013c3783dfb87a3bc",
52 | "width" : 640
53 | }, {
54 | "height" : 300,
55 | "url" : "https://i.scdn.co/image/2d70c06ac70d8c6144c94cabf7f4abcf85c4b7e4",
56 | "width" : 300
57 | }, {
58 | "height" : 64,
59 | "url" : "https://i.scdn.co/image/3dc007829bc0663c24089e46743a9f4ae15e65f8",
60 | "width" : 64
61 | } ],
62 | "is_externally_hosted" : false,
63 | "languages" : [ "sv" ],
64 | "media_type" : "audio",
65 | "name" : "Vetenskapsradion Historia",
66 | "publisher" : "Sveriges Radio",
67 | "type" : "show",
68 | "uri" : "spotify:show:38bS44xjbVVZ3No3ByF1dJ"
69 | }
70 |
--------------------------------------------------------------------------------
/tests/fixtures/shows.json:
--------------------------------------------------------------------------------
1 | {
2 | "shows" : [ {
3 | "available_markets" : [ "AD", "AE", "AR", "AT", "AU", "BE", "BG", "BH", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IL", "IN", "IS", "IT", "JO", "JP", "KW", "LB", "LI", "LT", "LU", "LV", "MA", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "OM", "PA", "PE", "PH", "PL", "PS", "PT", "PY", "QA", "RO", "SE", "SG", "SK", "SV", "TH", "TN", "TR", "TW", "US", "UY", "VN", "ZA" ],
4 | "copyrights" : [ ],
5 | "description" : "Candid conversations with entrepreneurs, artists, athletes, visionaries of all kinds—about their successes, and their failures, and what they learned from both. Hosted by Alex Blumberg, from Gimlet Media.",
6 | "explicit" : true,
7 | "external_urls" : {
8 | "spotify" : "https://open.spotify.com/show/5CfCWKI5pZ28U0uOzXkDHe"
9 | },
10 | "href" : "https://api.spotify.com/v1/shows/5CfCWKI5pZ28U0uOzXkDHe",
11 | "id" : "5CfCWKI5pZ28U0uOzXkDHe",
12 | "images" : [ {
13 | "height" : 640,
14 | "url" : "https://i.scdn.co/image/12903409b9e5dd26f2a41e401cd7fcabd5164ed4",
15 | "width" : 640
16 | }, {
17 | "height" : 300,
18 | "url" : "https://i.scdn.co/image/4f19eb7986a7c2246d713dcc46684e2187ccea4f",
19 | "width" : 300
20 | }, {
21 | "height" : 64,
22 | "url" : "https://i.scdn.co/image/c0b072976a28792a4b451dfc7011a2176ec8cd34",
23 | "width" : 64
24 | } ],
25 | "is_externally_hosted" : false,
26 | "languages" : [ "en" ],
27 | "media_type" : "audio",
28 | "name" : "Without Fail",
29 | "publisher" : "Gimlet",
30 | "type" : "show",
31 | "uri" : "spotify:show:5CfCWKI5pZ28U0uOzXkDHe"
32 | }, {
33 | "available_markets" : [ "AD", "AE", "AR", "AT", "AU", "BE", "BG", "BH", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "DZ", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "ID", "IE", "IL", "IN", "IS", "IT", "JO", "JP", "KW", "LB", "LI", "LT", "LU", "LV", "MA", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "OM", "PA", "PE", "PH", "PL", "PS", "PT", "PY", "QA", "RO", "SE", "SG", "SK", "SV", "TH", "TN", "TR", "TW", "US", "UY", "VN", "ZA" ],
34 | "copyrights" : [ ],
35 | "description" : "Giant Bomb discusses the latest video game news and new releases, taste-test questionable beverages, and get wildly off-topic in this weekly podcast.",
36 | "explicit" : false,
37 | "external_urls" : {
38 | "spotify" : "https://open.spotify.com/show/5as3aKmN2k11yfDDDSrvaZ"
39 | },
40 | "href" : "https://api.spotify.com/v1/shows/5as3aKmN2k11yfDDDSrvaZ",
41 | "id" : "5as3aKmN2k11yfDDDSrvaZ",
42 | "images" : [ {
43 | "height" : 640,
44 | "url" : "https://i.scdn.co/image/9bd9b3be1111810a91cd768115a57ee5a08c7145",
45 | "width" : 640
46 | }, {
47 | "height" : 300,
48 | "url" : "https://i.scdn.co/image/1f5c122086aa4602742ba2301302f2f9bc1f0345",
49 | "width" : 300
50 | }, {
51 | "height" : 64,
52 | "url" : "https://i.scdn.co/image/b97f288023e547f40862976c89a5c342eacaaac1",
53 | "width" : 64
54 | } ],
55 | "is_externally_hosted" : false,
56 | "languages" : [ "en-US" ],
57 | "media_type" : "audio",
58 | "name" : "Giant Bombcast",
59 | "publisher" : "Giant Bomb",
60 | "type" : "show",
61 | "uri" : "spotify:show:5as3aKmN2k11yfDDDSrvaZ"
62 | } ]
63 | }
64 |
--------------------------------------------------------------------------------
/tests/fixtures/snapshot-id.json:
--------------------------------------------------------------------------------
1 | { "snapshot_id" : "JbtmHBDBAYu3/bt8BOXKjzKx3i0b6LCa/wVjyl6qQ2Yf6nFXkbmzuEa+ZI/U1yF+" }
2 |
--------------------------------------------------------------------------------
/tests/fixtures/top-artists-and-tracks.json:
--------------------------------------------------------------------------------
1 | {
2 | "items" : [ {
3 | "external_urls" : {
4 | "spotify" : "https://open.spotify.com/artist/0I2XqVXqHScXjHhk6AYYRe"
5 | },
6 | "followers" : {
7 | "href" : null,
8 | "total" : 7753
9 | },
10 | "genres" : [ "swedish hip hop" ],
11 | "href" : "https://api.spotify.com/v1/artists/0I2XqVXqHScXjHhk6AYYRe",
12 | "id" : "0I2XqVXqHScXjHhk6AYYRe",
13 | "images" : [ {
14 | "height" : 640,
15 | "url" : "https://i.scdn.co/image/2c8c0cea05bf3d3c070b7498d8d0b957c4cdec20",
16 | "width" : 640
17 | }, {
18 | "height" : 300,
19 | "url" : "https://i.scdn.co/image/394302b42c4b894786943e028cdd46d7baaa29b7",
20 | "width" : 300
21 | }, {
22 | "height" : 64,
23 | "url" : "https://i.scdn.co/image/ca9df7225ade6e5dfc62e7076709ca3409a7cbbf",
24 | "width" : 64
25 | } ],
26 | "name" : "Afasi & Filthy",
27 | "popularity" : 54,
28 | "type" : "artist",
29 | "uri" : "spotify:artist:0I2XqVXqHScXjHhk6AYYRe"
30 | }],
31 | "next" : "https://api.spotify.com/v1/me/top/artists?offset=20",
32 | "previous" : null,
33 | "total" : 50,
34 | "limit" : 20,
35 | "href" : "https://api.spotify.com/v1/me/top/artists"
36 | }
37 |
--------------------------------------------------------------------------------
/tests/fixtures/track.json:
--------------------------------------------------------------------------------
1 | {
2 | "album" : {
3 | "album_type" : "album",
4 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
5 | "external_urls" : {
6 | "spotify" : "https://open.spotify.com/album/3tpJtzZm4Urb0n2ITN5mwF"
7 | },
8 | "href" : "https://api.spotify.com/v1/albums/3tpJtzZm4Urb0n2ITN5mwF",
9 | "id" : "3tpJtzZm4Urb0n2ITN5mwF",
10 | "images" : [ {
11 | "height" : 640,
12 | "url" : "https://i.scdn.co/image/42f1407919f09c53af5cac7290f5224cc098b6cc",
13 | "width" : 640
14 | }, {
15 | "height" : 300,
16 | "url" : "https://i.scdn.co/image/1844e9ff8ea90e78e62ca892900c1c36e98a58ee",
17 | "width" : 300
18 | }, {
19 | "height" : 64,
20 | "url" : "https://i.scdn.co/image/7c5288b11adf33302a5eef5a722372494335114a",
21 | "width" : 64
22 | } ],
23 | "name" : "Original Motion Picture Soundtrack - Full Metal Jacket",
24 | "type" : "album",
25 | "uri" : "spotify:album:3tpJtzZm4Urb0n2ITN5mwF"
26 | },
27 | "artists" : [ {
28 | "external_urls" : {
29 | "spotify" : "https://open.spotify.com/artist/5QEA3sofVt5QckQA6QX2nN"
30 | },
31 | "href" : "https://api.spotify.com/v1/artists/5QEA3sofVt5QckQA6QX2nN",
32 | "id" : "5QEA3sofVt5QckQA6QX2nN",
33 | "name" : "The Trashmen",
34 | "type" : "artist",
35 | "uri" : "spotify:artist:5QEA3sofVt5QckQA6QX2nN"
36 | } ],
37 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
38 | "disc_number" : 1,
39 | "duration_ms" : 137133,
40 | "explicit" : false,
41 | "external_ids" : {
42 | "isrc" : "USWB10102873"
43 | },
44 | "external_urls" : {
45 | "spotify" : "https://open.spotify.com/track/7EjyzZcbLxW7PaaLua9Ksb"
46 | },
47 | "href" : "https://api.spotify.com/v1/tracks/7EjyzZcbLxW7PaaLua9Ksb",
48 | "id" : "7EjyzZcbLxW7PaaLua9Ksb",
49 | "name" : "Surfin' Bird",
50 | "popularity" : 53,
51 | "preview_url" : "https://p.scdn.co/mp3-preview/c1a3f50a46ad126ec97442119bf912fc4017c2a5",
52 | "track_number" : 7,
53 | "type" : "track",
54 | "uri" : "spotify:track:7EjyzZcbLxW7PaaLua9Ksb"
55 | }
56 |
--------------------------------------------------------------------------------
/tests/fixtures/tracks.json:
--------------------------------------------------------------------------------
1 | {
2 | "tracks" : [ {
3 | "album" : {
4 | "album_type" : "album",
5 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
6 | "external_urls" : {
7 | "spotify" : "https://open.spotify.com/album/6TJmQnO44YE5BtTxH8pop1"
8 | },
9 | "href" : "https://api.spotify.com/v1/albums/6TJmQnO44YE5BtTxH8pop1",
10 | "id" : "6TJmQnO44YE5BtTxH8pop1",
11 | "images" : [ {
12 | "height" : 640,
13 | "url" : "https://i.scdn.co/image/8e13218039f81b000553e25522a7f0d7a0600f2e",
14 | "width" : 629
15 | }, {
16 | "height" : 300,
17 | "url" : "https://i.scdn.co/image/8c1e066b5d1045038437d92815d49987f519e44f",
18 | "width" : 295
19 | }, {
20 | "height" : 64,
21 | "url" : "https://i.scdn.co/image/d49268a8fc0768084f4750cf1647709e89a27172",
22 | "width" : 63
23 | } ],
24 | "name" : "Hot Fuss",
25 | "type" : "album",
26 | "uri" : "spotify:album:6TJmQnO44YE5BtTxH8pop1"
27 | },
28 | "artists" : [ {
29 | "external_urls" : {
30 | "spotify" : "https://open.spotify.com/artist/0C0XlULifJtAgn6ZNCW2eu"
31 | },
32 | "href" : "https://api.spotify.com/v1/artists/0C0XlULifJtAgn6ZNCW2eu",
33 | "id" : "0C0XlULifJtAgn6ZNCW2eu",
34 | "name" : "The Killers",
35 | "type" : "artist",
36 | "uri" : "spotify:artist:0C0XlULifJtAgn6ZNCW2eu"
37 | } ],
38 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
39 | "disc_number" : 1,
40 | "duration_ms" : 222075,
41 | "explicit" : false,
42 | "external_ids" : {
43 | "isrc" : "USIR20400274"
44 | },
45 | "external_urls" : {
46 | "spotify" : "https://open.spotify.com/track/0eGsygTp906u18L0Oimnem"
47 | },
48 | "href" : "https://api.spotify.com/v1/tracks/0eGsygTp906u18L0Oimnem",
49 | "id" : "0eGsygTp906u18L0Oimnem",
50 | "name" : "Mr. Brightside",
51 | "popularity" : 75,
52 | "preview_url" : "https://p.scdn.co/mp3-preview/f454c8224828e21fa146af84916fd22cb89cedc6",
53 | "track_number" : 2,
54 | "type" : "track",
55 | "uri" : "spotify:track:0eGsygTp906u18L0Oimnem"
56 | }, {
57 | "album" : {
58 | "album_type" : "album",
59 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
60 | "external_urls" : {
61 | "spotify" : "https://open.spotify.com/album/6TJmQnO44YE5BtTxH8pop1"
62 | },
63 | "href" : "https://api.spotify.com/v1/albums/6TJmQnO44YE5BtTxH8pop1",
64 | "id" : "6TJmQnO44YE5BtTxH8pop1",
65 | "images" : [ {
66 | "height" : 640,
67 | "url" : "https://i.scdn.co/image/8e13218039f81b000553e25522a7f0d7a0600f2e",
68 | "width" : 629
69 | }, {
70 | "height" : 300,
71 | "url" : "https://i.scdn.co/image/8c1e066b5d1045038437d92815d49987f519e44f",
72 | "width" : 295
73 | }, {
74 | "height" : 64,
75 | "url" : "https://i.scdn.co/image/d49268a8fc0768084f4750cf1647709e89a27172",
76 | "width" : 63
77 | } ],
78 | "name" : "Hot Fuss",
79 | "type" : "album",
80 | "uri" : "spotify:album:6TJmQnO44YE5BtTxH8pop1"
81 | },
82 | "artists" : [ {
83 | "external_urls" : {
84 | "spotify" : "https://open.spotify.com/artist/0C0XlULifJtAgn6ZNCW2eu"
85 | },
86 | "href" : "https://api.spotify.com/v1/artists/0C0XlULifJtAgn6ZNCW2eu",
87 | "id" : "0C0XlULifJtAgn6ZNCW2eu",
88 | "name" : "The Killers",
89 | "type" : "artist",
90 | "uri" : "spotify:artist:0C0XlULifJtAgn6ZNCW2eu"
91 | } ],
92 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "UY" ],
93 | "disc_number" : 1,
94 | "duration_ms" : 197160,
95 | "explicit" : false,
96 | "external_ids" : {
97 | "isrc" : "USIR20400195"
98 | },
99 | "external_urls" : {
100 | "spotify" : "https://open.spotify.com/track/1lDWb6b6ieDQ2xT7ewTC3G"
101 | },
102 | "href" : "https://api.spotify.com/v1/tracks/1lDWb6b6ieDQ2xT7ewTC3G",
103 | "id" : "1lDWb6b6ieDQ2xT7ewTC3G",
104 | "name" : "Somebody Told Me",
105 | "popularity" : 71,
106 | "preview_url" : "https://p.scdn.co/mp3-preview/4c63a3d4eaf7f8f86cfdb8bf46ef3974f4092357",
107 | "track_number" : 4,
108 | "type" : "track",
109 | "uri" : "spotify:track:1lDWb6b6ieDQ2xT7ewTC3G"
110 | } ]
111 | }
112 |
--------------------------------------------------------------------------------
/tests/fixtures/user-albums-contains.json:
--------------------------------------------------------------------------------
1 | [true, true]
2 |
--------------------------------------------------------------------------------
/tests/fixtures/user-albums.json:
--------------------------------------------------------------------------------
1 | {
2 | "href": "https://api.spotify.com/v1/me/albums?offset=0&limit=20",
3 | "items": [
4 | {
5 | "added_at": "2014-07-08T18:18:33Z",
6 | "album": {
7 | "album_type": "album",
8 | "artists": [
9 | {
10 | "external_urls": {
11 | "spotify": "https://open.spotify.com/artist/44gRHbEm4Uqa0ykW0rDTNk"
12 | },
13 | "href": "https://api.spotify.com/v1/artists/44gRHbEm4Uqa0ykW0rDTNk",
14 | "id": "44gRHbEm4Uqa0ykW0rDTNk",
15 | "name": "Ben Folds Five",
16 | "type": "artist",
17 | "uri": "spotify:artist:44gRHbEm4Uqa0ykW0rDTNk"
18 | }
19 | ],
20 | "available_markets": [
21 | "AD",
22 | "AR",
23 | "AT",
24 | "TW",
25 | "US",
26 | "UY"
27 | ],
28 | "external_ids": {
29 | "upc": "074646776223"
30 | },
31 | "external_urls": {
32 | "spotify": "https://open.spotify.com/album/7ggEMmVTUloqdMw18rv0ve"
33 | },
34 | "genres": [
35 | "Adult Alternative Pop/Rock",
36 | "Alternative Pop/Rock",
37 | "Alternative/Indie Rock",
38 | "Contemporary Pop/Rock",
39 | "Pop/Rock"
40 | ],
41 | "href": "https://api.spotify.com/v1/albums/7ggEMmVTUloqdMw18rv0ve",
42 | "id": "7ggEMmVTUloqdMw18rv0ve",
43 | "images": [
44 | {
45 | "height": 640,
46 | "url": "https://i.scdn.co/image/8b5b036272caf598e4d708e4db4a418031486cca",
47 | "width": 640
48 | },
49 | {
50 | "height": 300,
51 | "url": "https://i.scdn.co/image/47030c6e70229ba53fcb752d6114813e893d8b40",
52 | "width": 300
53 | },
54 | {
55 | "height": 64,
56 | "url": "https://i.scdn.co/image/2e8be5564680cccbe6888e57ba2f00521381c8f5",
57 | "width": 64
58 | }
59 | ],
60 | "name": "Whatever and Ever Amen",
61 | "popularity": 53,
62 | "release_date": "1997-02-10",
63 | "release_date_precision": "day",
64 | "tracks": {
65 | "href": "https://api.spotify.com/v1/albums/7ggEMmVTUloqdMw18rv0ve/tracks?offset=0&limit=50",
66 | "items": [
67 | {
68 | "artists": [
69 | {
70 | "external_urls": {
71 | "spotify": "https://open.spotify.com/artist/44gRHbEm4Uqa0ykW0rDTNk"
72 | },
73 | "href": "https://api.spotify.com/v1/artists/44gRHbEm4Uqa0ykW0rDTNk",
74 | "id": "44gRHbEm4Uqa0ykW0rDTNk",
75 | "name": "Ben Folds Five",
76 | "type": "artist",
77 | "uri": "spotify:artist:44gRHbEm4Uqa0ykW0rDTNk"
78 | }
79 | ],
80 | "disc_number": 1,
81 | "duration_ms": 232293,
82 | "explicit": false,
83 | "external_urls": {
84 | "spotify": "https://open.spotify.com/track/4rOPzC3EoK0v3EDQW3V4oD"
85 | },
86 | "href": "https://api.spotify.com/v1/tracks/4rOPzC3EoK0v3EDQW3V4oD",
87 | "id": "4rOPzC3EoK0v3EDQW3V4oD",
88 | "name": "One Angry Dwarf and 200 Solemn Faces",
89 | "preview_url": "https://p.scdn.co/mp3-preview/921ae28a02ddb95da1f34606ab0b18a92f29c253",
90 | "track_number": 1,
91 | "type": "track",
92 | "uri": "spotify:track:4rOPzC3EoK0v3EDQW3V4oD"
93 | }
94 | ],
95 | "limit": 50,
96 | "next": null,
97 | "offset": 0,
98 | "previous": null,
99 | "total": 12
100 | },
101 | "type": "album",
102 | "uri": "spotify:album:7ggEMmVTUloqdMw18rv0ve"
103 | }
104 | }
105 | ],
106 | "limit": 20,
107 | "next": null,
108 | "offset": 0,
109 | "previous": null,
110 | "total": 9
111 | }
112 |
--------------------------------------------------------------------------------
/tests/fixtures/user-current-playback-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "timestamp": 1490252122574,
3 | "device": {
4 | "id": "3f228e06c8562e2f439e22932da6c3231715ed53",
5 | "is_active": false,
6 | "is_restricted2": false,
7 | "name": "Xperia Z5 Compact",
8 | "type": "Smartphone",
9 | "volume_percent": 54
10 | },
11 | "progress_ms": "44272",
12 | "is_playing": true,
13 | "item": {
14 | },
15 | "shuffle_state": false,
16 | "repeat_state": "off",
17 | "context": {
18 | "external_urls" : {
19 | "spotify" : "http://open.spotify.com/user/spotify/playlist/49znshcYJROspEqBoHg3Sv"
20 | },
21 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/49znshcYJROspEqBoHg3Sv",
22 | "type" : "playlist",
23 | "uri" : "spotify:user:spotify:playlist:49znshcYJROspEqBoHg3Sv"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/fixtures/user-current-track.json:
--------------------------------------------------------------------------------
1 | {
2 | "context": {
3 | "external_urls" : {
4 | "spotify" : "http://open.spotify.com/user/spotify/playlist/49znshcYJROspEqBoHg3Sv"
5 | },
6 | "href" : "https://api.spotify.com/v1/users/spotify/playlists/49znshcYJROspEqBoHg3Sv",
7 | "type" : "playlist",
8 | "uri" : "spotify:user:spotify:playlist:49znshcYJROspEqBoHg3Sv"
9 | },
10 | "timestamp": 1490252122574,
11 | "progress_ms": 44272,
12 | "is_playing": true,
13 | "item": {
14 | "album": {
15 | "album_type": "album",
16 | "external_urls": {
17 | "spotify": "https://open.spotify.com/album/6TJmQnO44YE5BtTxH8pop1"
18 | },
19 | "href": "https://api.spotify.com/v1/albums/6TJmQnO44YE5BtTxH8pop1",
20 | "id": "6TJmQnO44YE5BtTxH8pop1",
21 | "images": [
22 | {
23 | "height": 640,
24 | "url": "https://i.scdn.co/image/8e13218039f81b000553e25522a7f0d7a0600f2e",
25 | "width": 629
26 | },
27 | {
28 | "height": 300,
29 | "url": "https://i.scdn.co/image/8c1e066b5d1045038437d92815d49987f519e44f",
30 | "width": 295
31 | },
32 | {
33 | "height": 64,
34 | "url": "https://i.scdn.co/image/d49268a8fc0768084f4750cf1647709e89a27172",
35 | "width": 63
36 | }
37 | ],
38 | "name": "Hot Fuss",
39 | "type": "album",
40 | "uri": "spotify:album:6TJmQnO44YE5BtTxH8pop1"
41 | },
42 | "artists": [
43 | {
44 | "external_urls": {
45 | "spotify": "https://open.spotify.com/artist/0C0XlULifJtAgn6ZNCW2eu"
46 | },
47 | "href": "https://api.spotify.com/v1/artists/0C0XlULifJtAgn6ZNCW2eu",
48 | "id": "0C0XlULifJtAgn6ZNCW2eu",
49 | "name": "The Killers",
50 | "type": "artist",
51 | "uri": "spotify:artist:0C0XlULifJtAgn6ZNCW2eu"
52 | }
53 | ],
54 | "available_markets": [
55 | "AD",
56 | "AR",
57 | "TW",
58 | "UY"
59 | ],
60 | "disc_number": 1,
61 | "duration_ms": 222075,
62 | "explicit": false,
63 | "external_ids": {
64 | "isrc": "USIR20400274"
65 | },
66 | "external_urls": {
67 | "spotify": "https://open.spotify.com/track/0eGsygTp906u18L0Oimnem"
68 | },
69 | "href": "https://api.spotify.com/v1/tracks/0eGsygTp906u18L0Oimnem",
70 | "id": "0eGsygTp906u18L0Oimnem",
71 | "name": "Mr. Brightside",
72 | "popularity": 0,
73 | "preview_url": "http://d318706lgtcm8e.cloudfront.net/mp3-preview/f454c8224828e21fa146af84916fd22cb89cedc6",
74 | "track_number": 2,
75 | "type": "track",
76 | "uri": "spotify:track:0eGsygTp906u18L0Oimnem"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tests/fixtures/user-devices.json:
--------------------------------------------------------------------------------
1 | {
2 | "devices" : [ {
3 | "id" : "5fbb3ba6aa454b5534c4ba43a8c7e8e45a63ad0e",
4 | "is_active" : false,
5 | "is_restricted" : false,
6 | "name" : "My fridge",
7 | "type" : "Computer",
8 | "volume_percent" : 100
9 | } ]
10 | }
11 |
--------------------------------------------------------------------------------
/tests/fixtures/user-episodes-contains.json:
--------------------------------------------------------------------------------
1 | [true, true, false]
2 |
--------------------------------------------------------------------------------
/tests/fixtures/user-episodes.json:
--------------------------------------------------------------------------------
1 | {
2 | "href": "https://api.spotify.com/v1/me/episodes?offset=0&limit=1&market=SE",
3 | "items": [
4 | {
5 | "added_at": "2021-02-24T14:13:23Z",
6 | "episode": {
7 | "audio_preview_url": "https://p.scdn.co/mp3-preview/499805f296742a8b639537dd93dac043afa9beae",
8 | "description": "Have you ever wondered how Spotify enters new listening markets? Kossy walks us through her experience launching new markets all across Asia. She explains some of the most challenging parts and also reveals the most rewarding aspects of bringing music to different people all over the world. ",
9 | "duration_ms": 682512,
10 | "explicit": false,
11 | "external_urls": {
12 | "spotify": "https://open.spotify.com/episode/0nrJxkuCUzLgYsXfD4s8Um"
13 | },
14 | "href": "https://api.spotify.com/v1/episodes/0nrJxkuCUzLgYsXfD4s8Um",
15 | "id": "0nrJxkuCUzLgYsXfD4s8Um",
16 | "images": [
17 | {
18 | "height": 640,
19 | "url": "https://i.scdn.co/image/b033e84a9136601e63a7a0713355b1365330d29f",
20 | "width": 640
21 | },
22 | {
23 | "height": 300,
24 | "url": "https://i.scdn.co/image/11ee74114995e9ad711fe6220c366cc2e8ea9af3",
25 | "width": 300
26 | },
27 | {
28 | "height": 64,
29 | "url": "https://i.scdn.co/image/14af1d17732a4ea525ff7878d1592f13045fecc0",
30 | "width": 64
31 | }
32 | ],
33 | "is_externally_hosted": false,
34 | "is_playable": true,
35 | "language": "en",
36 | "languages": [
37 | "en"
38 | ],
39 | "name": "S2E8: Kossy Ng & Launching New Markets",
40 | "release_date": "2020-12-01",
41 | "release_date_precision": "day",
42 | "resume_point": {
43 | "fully_played": false,
44 | "resume_position_ms": 0
45 | },
46 | "show": {
47 | "available_markets": [
48 | "AD",
49 | "AE",
50 | "AG",
51 | "AL",
52 | "AM",
53 | "AR",
54 | "AT",
55 | "AU",
56 | "BA",
57 | "BB",
58 | "BE",
59 | "BF",
60 | "BG",
61 | "BH",
62 | "BO",
63 | "BR",
64 | "BS",
65 | "BT",
66 | "BW",
67 | "BZ",
68 | "CA",
69 | "CH",
70 | "CL",
71 | "CO",
72 | "CR",
73 | "CV",
74 | "CW",
75 | "CY",
76 | "CZ",
77 | "DE",
78 | "DK",
79 | "DM",
80 | "DO",
81 | "DZ",
82 | "EC",
83 | "EE",
84 | "ES",
85 | "FI",
86 | "FJ",
87 | "FM",
88 | "FR",
89 | "GB",
90 | "GD",
91 | "GE",
92 | "GH",
93 | "GM",
94 | "GR",
95 | "GT",
96 | "GW",
97 | "GY",
98 | "HK",
99 | "HN",
100 | "HR",
101 | "HT",
102 | "HU",
103 | "ID",
104 | "IE",
105 | "IL",
106 | "IN",
107 | "IS",
108 | "IT",
109 | "JM",
110 | "JO",
111 | "JP",
112 | "KE",
113 | "KI",
114 | "KN",
115 | "KW",
116 | "LB",
117 | "LC",
118 | "LI",
119 | "LR",
120 | "LS",
121 | "LT",
122 | "LU",
123 | "LV",
124 | "MA",
125 | "MC",
126 | "ME",
127 | "MH",
128 | "MK",
129 | "ML",
130 | "MT",
131 | "MV",
132 | "MW",
133 | "MX",
134 | "MY",
135 | "NA",
136 | "NE",
137 | "NG",
138 | "NI",
139 | "NL",
140 | "NO",
141 | "NR",
142 | "NZ",
143 | "OM",
144 | "PA",
145 | "PE",
146 | "PG",
147 | "PH",
148 | "PL",
149 | "PS",
150 | "PT",
151 | "PW",
152 | "PY",
153 | "QA",
154 | "RO",
155 | "RS",
156 | "SB",
157 | "SC",
158 | "SE",
159 | "SG",
160 | "SI",
161 | "SK",
162 | "SL",
163 | "SM",
164 | "SN",
165 | "SR",
166 | "ST",
167 | "SV",
168 | "TH",
169 | "TL",
170 | "TN",
171 | "TO",
172 | "TR",
173 | "TT",
174 | "TV",
175 | "TW",
176 | "US",
177 | "UY",
178 | "VC",
179 | "VN",
180 | "VU",
181 | "WS",
182 | "XK",
183 | "ZA"
184 | ],
185 | "copyrights": [],
186 | "description": "At Spotify, we like to think of ourselves as a really big band! On the GreenRoom podcast, we give you a behind the scenes look into what being a band member is all about. Our mission is to showcase Spotifers and the unique things that drive our culture and help us all grow. Join Spotifiers, Mal & Gus, as they host an array of other band members from around the globe to chat about #lifeatspotify · Follow along on Instagram: @spotifyjobs · Check out our career page for open roles: spotifyjobs.com · Join the convo on Twitter: @spotifyjobs ",
187 | "explicit": false,
188 | "external_urls": {
189 | "spotify": "https://open.spotify.com/show/2bzjLBEWRldARaf1IytFFS"
190 | },
191 | "href": "https://api.spotify.com/v1/shows/2bzjLBEWRldARaf1IytFFS",
192 | "id": "2bzjLBEWRldARaf1IytFFS",
193 | "images": [
194 | {
195 | "height": 640,
196 | "url": "https://i.scdn.co/image/b033e84a9136601e63a7a0713355b1365330d29f",
197 | "width": 640
198 | },
199 | {
200 | "height": 300,
201 | "url": "https://i.scdn.co/image/11ee74114995e9ad711fe6220c366cc2e8ea9af3",
202 | "width": 300
203 | },
204 | {
205 | "height": 64,
206 | "url": "https://i.scdn.co/image/14af1d17732a4ea525ff7878d1592f13045fecc0",
207 | "width": 64
208 | }
209 | ],
210 | "is_externally_hosted": false,
211 | "languages": [
212 | "en"
213 | ],
214 | "media_type": "audio",
215 | "name": "Spotify: The GreenRoom",
216 | "publisher": "Spotify Jobs",
217 | "total_episodes": 15,
218 | "type": "show",
219 | "uri": "spotify:show:2bzjLBEWRldARaf1IytFFS"
220 | },
221 | "type": "episode",
222 | "uri": "spotify:episode:0nrJxkuCUzLgYsXfD4s8Um"
223 | }
224 | }
225 | ],
226 | "limit": 1,
227 | "next": null,
228 | "offset": 0,
229 | "previous": null,
230 | "total": 1
231 | }
232 |
--------------------------------------------------------------------------------
/tests/fixtures/user-followed-artists.json:
--------------------------------------------------------------------------------
1 | {
2 | "artists" : {
3 | "items" : [ {
4 | "external_urls" : {
5 | "spotify" : "https://open.spotify.com/artist/4tZwfgrHOc3mvqYlEYSvVi"
6 | },
7 | "followers" : {
8 | "href" : null,
9 | "total" : 2371485
10 | },
11 | "genres" : [ "house" ],
12 | "href" : "https://api.spotify.com/v1/artists/4tZwfgrHOc3mvqYlEYSvVi",
13 | "id" : "4tZwfgrHOc3mvqYlEYSvVi",
14 | "images" : [ {
15 | "height" : 751,
16 | "url" : "https://i.scdn.co/image/e52651f03da8c9bf264f75cdabf39cf039606ddc",
17 | "width" : 999
18 | }, {
19 | "height" : 481,
20 | "url" : "https://i.scdn.co/image/b96d08f790bf2be50fee0aa490a7d06d40ba36bb",
21 | "width" : 640
22 | }, {
23 | "height" : 150,
24 | "url" : "https://i.scdn.co/image/5bafab32f2eb71e881de73ec4b088e106feb9e3f",
25 | "width" : 200
26 | }, {
27 | "height" : 48,
28 | "url" : "https://i.scdn.co/image/809d6b89f4bb3f0f42f86c953a8b312234a31f31",
29 | "width" : 64
30 | } ],
31 | "name" : "Daft Punk",
32 | "popularity" : 85,
33 | "type" : "artist",
34 | "uri" : "spotify:artist:4tZwfgrHOc3mvqYlEYSvVi"
35 | } ],
36 | "next" : null,
37 | "total" : 1,
38 | "cursors" : {
39 | "after" : null
40 | },
41 | "limit" : 20,
42 | "href" : "https://api.spotify.com/v1/users/mcgurk/following?type=artist&limit=20"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tests/fixtures/user-follows-playlist.json:
--------------------------------------------------------------------------------
1 | [true]
2 |
--------------------------------------------------------------------------------
/tests/fixtures/user-follows.json:
--------------------------------------------------------------------------------
1 | [true, true]
2 |
--------------------------------------------------------------------------------
/tests/fixtures/user-playlists.json:
--------------------------------------------------------------------------------
1 | {
2 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists?offset=0&limit=5",
3 | "items" : [ {
4 | "collaborative" : false,
5 | "external_urls" : {
6 | "spotify" : "http://open.spotify.com/user/mcgurk/playlist/4YOPp7RlelOVGJ1d4CZBqW"
7 | },
8 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/4YOPp7RlelOVGJ1d4CZBqW",
9 | "id" : "4YOPp7RlelOVGJ1d4CZBqW",
10 | "images" : [ ],
11 | "name" : "Soft chill",
12 | "owner" : {
13 | "external_urls" : {
14 | "spotify" : "http://open.spotify.com/user/mcgurk"
15 | },
16 | "href" : "https://api.spotify.com/v1/users/mcgurk",
17 | "id" : "mcgurk",
18 | "type" : "user",
19 | "uri" : "spotify:user:mcgurk"
20 | },
21 | "public" : false,
22 | "tracks" : {
23 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/4YOPp7RlelOVGJ1d4CZBqW/tracks",
24 | "total" : 7
25 | },
26 | "type" : "playlist",
27 | "uri" : "spotify:user:mcgurk:playlist:4YOPp7RlelOVGJ1d4CZBqW"
28 | }, {
29 | "collaborative" : false,
30 | "external_urls" : {
31 | "spotify" : "http://open.spotify.com/user/mcgurk/playlist/2zNRHPRBynwGI2qIZZ2EGp"
32 | },
33 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/2zNRHPRBynwGI2qIZZ2EGp",
34 | "id" : "2zNRHPRBynwGI2qIZZ2EGp",
35 | "images" : [ ],
36 | "name" : "Liked from Radio",
37 | "owner" : {
38 | "external_urls" : {
39 | "spotify" : "http://open.spotify.com/user/mcgurk"
40 | },
41 | "href" : "https://api.spotify.com/v1/users/mcgurk",
42 | "id" : "mcgurk",
43 | "type" : "user",
44 | "uri" : "spotify:user:mcgurk"
45 | },
46 | "public" : false,
47 | "tracks" : {
48 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/2zNRHPRBynwGI2qIZZ2EGp/tracks",
49 | "total" : 66
50 | },
51 | "type" : "playlist",
52 | "uri" : "spotify:user:mcgurk:playlist:2zNRHPRBynwGI2qIZZ2EGp"
53 | }, {
54 | "collaborative" : false,
55 | "external_urls" : {
56 | "spotify" : "http://open.spotify.com/user/mcgurk/playlist/0CQyRJcgAUtu3HXedHTukP"
57 | },
58 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/0CQyRJcgAUtu3HXedHTukP",
59 | "id" : "0CQyRJcgAUtu3HXedHTukP",
60 | "images" : [ ],
61 | "name" : "Musica",
62 | "owner" : {
63 | "external_urls" : {
64 | "spotify" : "http://open.spotify.com/user/mcgurk"
65 | },
66 | "href" : "https://api.spotify.com/v1/users/mcgurk",
67 | "id" : "mcgurk",
68 | "type" : "user",
69 | "uri" : "spotify:user:mcgurk"
70 | },
71 | "public" : false,
72 | "tracks" : {
73 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/0CQyRJcgAUtu3HXedHTukP/tracks",
74 | "total" : 774
75 | },
76 | "type" : "playlist",
77 | "uri" : "spotify:user:mcgurk:playlist:0CQyRJcgAUtu3HXedHTukP"
78 | }, {
79 | "collaborative" : false,
80 | "external_urls" : {
81 | "spotify" : "http://open.spotify.com/user/mcgurk/playlist/0dWICxwZcXNTxavch2ViJl"
82 | },
83 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/0dWICxwZcXNTxavch2ViJl",
84 | "id" : "0dWICxwZcXNTxavch2ViJl",
85 | "images" : [ ],
86 | "name" : "Mmmh marabou",
87 | "owner" : {
88 | "external_urls" : {
89 | "spotify" : "http://open.spotify.com/user/mcgurk"
90 | },
91 | "href" : "https://api.spotify.com/v1/users/mcgurk",
92 | "id" : "mcgurk",
93 | "type" : "user",
94 | "uri" : "spotify:user:mcgurk"
95 | },
96 | "public" : false,
97 | "tracks" : {
98 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/0dWICxwZcXNTxavch2ViJl/tracks",
99 | "total" : 58
100 | },
101 | "type" : "playlist",
102 | "uri" : "spotify:user:mcgurk:playlist:0dWICxwZcXNTxavch2ViJl"
103 | }, {
104 | "collaborative" : false,
105 | "external_urls" : {
106 | "spotify" : "http://open.spotify.com/user/mcgurk/playlist/1f1zO9SrQXk9X0bY59HS5r"
107 | },
108 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/1f1zO9SrQXk9X0bY59HS5r",
109 | "id" : "1f1zO9SrQXk9X0bY59HS5r",
110 | "images" : [ ],
111 | "name" : "Mys",
112 | "owner" : {
113 | "external_urls" : {
114 | "spotify" : "http://open.spotify.com/user/mcgurk"
115 | },
116 | "href" : "https://api.spotify.com/v1/users/mcgurk",
117 | "id" : "mcgurk",
118 | "type" : "user",
119 | "uri" : "spotify:user:mcgurk"
120 | },
121 | "public" : false,
122 | "tracks" : {
123 | "href" : "https://api.spotify.com/v1/users/mcgurk/playlists/1f1zO9SrQXk9X0bY59HS5r/tracks",
124 | "total" : 4
125 | },
126 | "type" : "playlist",
127 | "uri" : "spotify:user:mcgurk:playlist:1f1zO9SrQXk9X0bY59HS5r"
128 | } ],
129 | "limit" : 5,
130 | "next" : "https://api.spotify.com/v1/users/mcgurk/playlists?offset=5&limit=5",
131 | "offset" : 0,
132 | "previous" : null,
133 | "total" : 8
134 | }
135 |
--------------------------------------------------------------------------------
/tests/fixtures/user-shows-contains.json:
--------------------------------------------------------------------------------
1 | [true, false, true]
2 |
--------------------------------------------------------------------------------
/tests/fixtures/user-shows.json:
--------------------------------------------------------------------------------
1 | {
2 | "href": "https://api.spotify.com/v1/me/shows?offset=0&limit=20",
3 | "items": [
4 | {
5 | "added_at": "2019-12-08T21:14:30Z",
6 | "show": {
7 | "available_markets": [
8 | "AD",
9 | "AE"
10 | ],
11 | "copyrights": [],
12 | "description": "Explore the dark side of the Internet with host Jack Rhysider as he takes you on a journey through the chilling world of privacy hacks, data breaches, and cyber crime. The masterful criminal hackers who dwell on the dark side show us just how vulnerable we all are.",
13 | "explicit": false,
14 | "external_urls": {
15 | "spotify": "https://open.spotify.com/show/4XPl3uEEL9hvqMkoZrzbx5"
16 | },
17 | "href": "https://api.spotify.com/v1/shows/4XPl3uEEL9hvqMkoZrzbx5",
18 | "id": "4XPl3uEEL9hvqMkoZrzbx5",
19 | "images": [
20 | {
21 | "height": 640,
22 | "url": "https://i.scdn.co/image/53ba2adaaf2d3e47898aed9edb64026145032e7b",
23 | "width": 640
24 | },
25 | {
26 | "height": 300,
27 | "url": "https://i.scdn.co/image/5f4726afb1e227c80f228b6b1ea7a6a1209ebe97",
28 | "width": 300
29 | },
30 | {
31 | "height": 64,
32 | "url": "https://i.scdn.co/image/33cf2b6fea8d62ab730f902b52b0dc1f676cf015",
33 | "width": 64
34 | }
35 | ],
36 | "is_externally_hosted": false,
37 | "languages": [
38 | "en"
39 | ],
40 | "media_type": "audio",
41 | "name": "Darknet Diaries",
42 | "publisher": "Jack Rhysider",
43 | "type": "show",
44 | "uri": "spotify:show:4XPl3uEEL9hvqMkoZrzbx5"
45 | }
46 | },
47 | {
48 | "added_at": "2019-11-22T11:08:10Z",
49 | "show": {
50 | "available_markets": [
51 | "AD",
52 | "AE"
53 | ],
54 | "copyrights": [],
55 | "description": "Fest & Flauschig mit Jan Böhmermann und Olli Schulz. Der preisgekrönte, verblüffend fabelhafte, grenzenlos fantastische Podcast für sie, ihn und es.",
56 | "explicit": false,
57 | "external_urls": {
58 | "spotify": "https://open.spotify.com/show/1OLcQdw2PFDPG1jo3s0wbp"
59 | },
60 | "href": "https://api.spotify.com/v1/shows/1OLcQdw2PFDPG1jo3s0wbp",
61 | "id": "1OLcQdw2PFDPG1jo3s0wbp",
62 | "images": [
63 | {
64 | "height": 640,
65 | "url": "https://i.scdn.co/image/79364dab39c9d3757838940fc7cb133c75fdaad2",
66 | "width": 640
67 | },
68 | {
69 | "height": 300,
70 | "url": "https://i.scdn.co/image/eaf33726dff2bcafeff475813f5bd18ddf51b89d",
71 | "width": 300
72 | },
73 | {
74 | "height": 64,
75 | "url": "https://i.scdn.co/image/a6514cfa222d1ee22ece832500334903154ffa83",
76 | "width": 64
77 | }
78 | ],
79 | "is_externally_hosted": false,
80 | "languages": [
81 | "de"
82 | ],
83 | "media_type": "audio",
84 | "name": "Fest & Flauschig",
85 | "publisher": "Jan Böhmermann & Olli Schulz",
86 | "type": "show",
87 | "uri": "spotify:show:1OLcQdw2PFDPG1jo3s0wbp"
88 | }
89 | },
90 | {
91 | "added_at": "2019-10-19T10:57:38Z",
92 | "show": {
93 | "available_markets": [
94 | "AD",
95 | "AE"
96 | ],
97 | "copyrights": [],
98 | "description": "A series about what it's really like to start a business.",
99 | "explicit": false,
100 | "external_urls": {
101 | "spotify": "https://open.spotify.com/show/5CnDmMUG0S5bSSw612fs8C"
102 | },
103 | "href": "https://api.spotify.com/v1/shows/5CnDmMUG0S5bSSw612fs8C",
104 | "id": "5CnDmMUG0S5bSSw612fs8C",
105 | "images": [
106 | {
107 | "height": 640,
108 | "url": "https://i.scdn.co/image/6fe88d8c175bdec76c7f9f204c60f4331ce89bdc",
109 | "width": 640
110 | },
111 | {
112 | "height": 300,
113 | "url": "https://i.scdn.co/image/00511e028a3b993efaf5e2e12b552cda1e979206",
114 | "width": 300
115 | },
116 | {
117 | "height": 64,
118 | "url": "https://i.scdn.co/image/aa1dbf8c6c545c623d088d5e432afdf8dee3029d",
119 | "width": 64
120 | }
121 | ],
122 | "is_externally_hosted": false,
123 | "languages": [
124 | "en"
125 | ],
126 | "media_type": "audio",
127 | "name": "StartUp Podcast",
128 | "publisher": "Gimlet",
129 | "type": "show",
130 | "uri": "spotify:show:5CnDmMUG0S5bSSw612fs8C"
131 | }
132 | }
133 | ],
134 | "limit": 20,
135 | "next": null,
136 | "offset": 0,
137 | "previous": null,
138 | "total": 3
139 | }
140 |
--------------------------------------------------------------------------------
/tests/fixtures/user-tracks-contains.json:
--------------------------------------------------------------------------------
1 | [true, true]
2 |
--------------------------------------------------------------------------------
/tests/fixtures/user-tracks.json:
--------------------------------------------------------------------------------
1 | {
2 | "href" : "https://api.spotify.com/v1/me/tracks?offset=0&limit=5",
3 | "items" : [ {
4 | "added_at" : "2014-10-26T14:22:34Z",
5 | "track" : {
6 | "album" : {
7 | "album_type" : "album",
8 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
9 | "external_urls" : {
10 | "spotify" : "https://open.spotify.com/album/4m2880jivSbbyEGAKfITCa"
11 | },
12 | "href" : "https://api.spotify.com/v1/albums/4m2880jivSbbyEGAKfITCa",
13 | "id" : "4m2880jivSbbyEGAKfITCa",
14 | "images" : [ {
15 | "height" : 636,
16 | "url" : "https://i.scdn.co/image/6710552422788861893233437ad9f0830b95c07c",
17 | "width" : 640
18 | }, {
19 | "height" : 298,
20 | "url" : "https://i.scdn.co/image/4df66c8b705efa6dd3be4e884fe2ca8779fd4fc9",
21 | "width" : 300
22 | }, {
23 | "height" : 64,
24 | "url" : "https://i.scdn.co/image/a3d945a1c9de931ed6eeb3dbca90579f6a6388c8",
25 | "width" : 64
26 | } ],
27 | "name" : "Random Access Memories",
28 | "type" : "album",
29 | "uri" : "spotify:album:4m2880jivSbbyEGAKfITCa"
30 | },
31 | "artists" : [ {
32 | "external_urls" : {
33 | "spotify" : "https://open.spotify.com/artist/4tZwfgrHOc3mvqYlEYSvVi"
34 | },
35 | "href" : "https://api.spotify.com/v1/artists/4tZwfgrHOc3mvqYlEYSvVi",
36 | "id" : "4tZwfgrHOc3mvqYlEYSvVi",
37 | "name" : "Daft Punk",
38 | "type" : "artist",
39 | "uri" : "spotify:artist:4tZwfgrHOc3mvqYlEYSvVi"
40 | } ],
41 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
42 | "disc_number" : 1,
43 | "duration_ms" : 544626,
44 | "explicit" : false,
45 | "external_ids" : {
46 | "isrc" : "USQX91300103"
47 | },
48 | "external_urls" : {
49 | "spotify" : "https://open.spotify.com/track/0oks4FnzhNp5QPTZtoet7c"
50 | },
51 | "href" : "https://api.spotify.com/v1/tracks/0oks4FnzhNp5QPTZtoet7c",
52 | "id" : "0oks4FnzhNp5QPTZtoet7c",
53 | "name" : "Giorgio by Moroder",
54 | "popularity" : 68,
55 | "preview_url" : "https://p.scdn.co/mp3-preview/9f30c1ab377fab3d5fba3c376ce32174f70d5546",
56 | "track_number" : 3,
57 | "type" : "track",
58 | "uri" : "spotify:track:0oks4FnzhNp5QPTZtoet7c"
59 | }
60 | }, {
61 | "added_at" : "2014-10-26T14:22:29Z",
62 | "track" : {
63 | "album" : {
64 | "album_type" : "album",
65 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
66 | "external_urls" : {
67 | "spotify" : "https://open.spotify.com/album/4m2880jivSbbyEGAKfITCa"
68 | },
69 | "href" : "https://api.spotify.com/v1/albums/4m2880jivSbbyEGAKfITCa",
70 | "id" : "4m2880jivSbbyEGAKfITCa",
71 | "images" : [ {
72 | "height" : 636,
73 | "url" : "https://i.scdn.co/image/6710552422788861893233437ad9f0830b95c07c",
74 | "width" : 640
75 | }, {
76 | "height" : 298,
77 | "url" : "https://i.scdn.co/image/4df66c8b705efa6dd3be4e884fe2ca8779fd4fc9",
78 | "width" : 300
79 | }, {
80 | "height" : 64,
81 | "url" : "https://i.scdn.co/image/a3d945a1c9de931ed6eeb3dbca90579f6a6388c8",
82 | "width" : 64
83 | } ],
84 | "name" : "Random Access Memories",
85 | "type" : "album",
86 | "uri" : "spotify:album:4m2880jivSbbyEGAKfITCa"
87 | },
88 | "artists" : [ {
89 | "external_urls" : {
90 | "spotify" : "https://open.spotify.com/artist/4tZwfgrHOc3mvqYlEYSvVi"
91 | },
92 | "href" : "https://api.spotify.com/v1/artists/4tZwfgrHOc3mvqYlEYSvVi",
93 | "id" : "4tZwfgrHOc3mvqYlEYSvVi",
94 | "name" : "Daft Punk",
95 | "type" : "artist",
96 | "uri" : "spotify:artist:4tZwfgrHOc3mvqYlEYSvVi"
97 | }, {
98 | "external_urls" : {
99 | "spotify" : "https://open.spotify.com/artist/2RdwBSPQiwcmiDo9kixcl8"
100 | },
101 | "href" : "https://api.spotify.com/v1/artists/2RdwBSPQiwcmiDo9kixcl8",
102 | "id" : "2RdwBSPQiwcmiDo9kixcl8",
103 | "name" : "Pharrell Williams",
104 | "type" : "artist",
105 | "uri" : "spotify:artist:2RdwBSPQiwcmiDo9kixcl8"
106 | }, {
107 | "external_urls" : {
108 | "spotify" : "https://open.spotify.com/artist/3yDIp0kaq9EFKe07X1X2rz"
109 | },
110 | "href" : "https://api.spotify.com/v1/artists/3yDIp0kaq9EFKe07X1X2rz",
111 | "id" : "3yDIp0kaq9EFKe07X1X2rz",
112 | "name" : "Nile Rodgers",
113 | "type" : "artist",
114 | "uri" : "spotify:artist:3yDIp0kaq9EFKe07X1X2rz"
115 | } ],
116 | "available_markets" : [ "AD", "AR", "AT", "AU", "BE", "BG", "BO", "BR", "CA", "CH", "CL", "CO", "CR", "CY", "CZ", "DE", "DK", "DO", "EC", "EE", "ES", "FI", "FR", "GB", "GR", "GT", "HK", "HN", "HU", "IE", "IS", "IT", "LI", "LT", "LU", "LV", "MC", "MT", "MX", "MY", "NI", "NL", "NO", "NZ", "PA", "PE", "PH", "PL", "PT", "PY", "RO", "SE", "SG", "SI", "SK", "SV", "TR", "TW", "US", "UY" ],
117 | "disc_number" : 1,
118 | "duration_ms" : 369626,
119 | "explicit" : false,
120 | "external_ids" : {
121 | "isrc" : "USQX91300108"
122 | },
123 | "external_urls" : {
124 | "spotify" : "https://open.spotify.com/track/69kOkLUCkxIZYexIgSG8rq"
125 | },
126 | "href" : "https://api.spotify.com/v1/tracks/69kOkLUCkxIZYexIgSG8rq",
127 | "id" : "69kOkLUCkxIZYexIgSG8rq",
128 | "name" : "Get Lucky",
129 | "popularity" : 78,
130 | "preview_url" : "https://p.scdn.co/mp3-preview/31208a018b7ab55969b945200901b7ef371c3a72",
131 | "track_number" : 8,
132 | "type" : "track",
133 | "uri" : "spotify:track:69kOkLUCkxIZYexIgSG8rq"
134 | }
135 | } ],
136 | "limit" : 5,
137 | "next" : null,
138 | "offset" : 0,
139 | "previous" : null,
140 | "total" : 2
141 | }
142 |
--------------------------------------------------------------------------------
/tests/fixtures/user.json:
--------------------------------------------------------------------------------
1 | {
2 | "display_name" : null,
3 | "external_urls" : {
4 | "spotify" : "https://open.spotify.com/user/mcgurk"
5 | },
6 | "followers" : {
7 | "href" : null,
8 | "total" : 1
9 | },
10 | "href" : "https://api.spotify.com/v1/users/mcgurk",
11 | "id" : "mcgurk",
12 | "images" : [ ],
13 | "type" : "user",
14 | "uri" : "spotify:user:mcgurk"
15 | }
16 |
--------------------------------------------------------------------------------
/tests/fixtures/users-follows-playlist.json:
--------------------------------------------------------------------------------
1 | [true, true]
2 |
--------------------------------------------------------------------------------