Source code for minim.spotify

"""
Spotify
=======
.. moduleauthor:: Benjamin Ye <GitHub: bbye98>

This module contains a complete implementation of all Spotify Web API
endpoints and a minimal implementation to use the private Spotify Lyrics
service.
"""

import base64
import datetime
import hashlib
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import logging
from multiprocessing import Process
import os
import re
import secrets
import time
from typing import Any, Union
import urllib
import warnings
import webbrowser

import requests

from . import FOUND_FLASK, FOUND_PLAYWRIGHT, DIR_HOME, DIR_TEMP, _config

if FOUND_FLASK:
    from flask import Flask, request
if FOUND_PLAYWRIGHT:
    from playwright.sync_api import sync_playwright

__all__ = ["PrivateLyricsService", "WebAPI"]


class _SpotifyRedirectHandler(BaseHTTPRequestHandler):
    """
    HTTP request handler for the Spotify authorization code flow.
    """

    def do_GET(self):
        """
        Handles an incoming GET request and parses the query string.
        """
        self.server.response = dict(
            urllib.parse.parse_qsl(urllib.parse.urlparse(f"{self.path}").query)
        )
        self.send_response(200)
        self.send_header("Content-Type", "text/html")
        self.end_headers()
        status = "denied" if "error" in self.server.response else "granted"
        self.wfile.write(
            f"Access {status}. You may close this page now.".encode()
        )


[docs] class PrivateLyricsService: """ Spotify Lyrics service client. The Spotify Lyrics service, which is powered by Musixmatch (or PetitLyrics in Japan), provides line- or word-synced lyrics for Spotify tracks when available. The Spotify Lyrics interface is not publicly documented, so its endpoints have been determined by watching HTTP network traffic. .. attention:: As the Spotify Lyrics service is not designed to be publicly accessible, this class can be disabled or removed at any time to ensure compliance with the `Spotify Developer Terms of Service <https://developer.spotify.com/terms>`_. Requests to the Spotify Lyrics endpoints must be accompanied by a valid access token in the header. An access token can be obtained using the Spotify Web Player :code:`sp_dc` cookie, which must either be provided to this class's constructor as a keyword argument or be stored as :code:`SPOTIFY_SP_DC` in the operating system's environment variables. .. hint:: The :code:`sp_dc` cookie can be extracted from the local storage of your web browser after you log into Spotify. If an existing access token is available, it and its expiry time can be provided to this class's constructor as keyword arguments to bypass the access token exchange process. It is recommended that all other authorization-related keyword arguments be specified so that a new access token can be obtained when the existing one expires. .. tip:: The :code:`sp_dc` cookie and access token can be changed or updated at any time using :meth:`set_sp_dc` and :meth:`set_access_token`, respectively. Minim also stores and manages access tokens and their properties. When an access token is acquired, it is automatically saved to the Minim configuration file to be loaded on the next instantiation of this class. This behavior can be disabled if there are any security concerns, like if the computer being used is a shared device. Parameters ---------- sp_dc : `str`, optional Spotify Web Player :code:`sp_dc` cookie. If it is not stored as :code:`SPOTIFY_SP_DC` in the operating system's environment variables or found in the Minim configuration file, it must be provided here. access_token : `str`, keyword-only, optional Access token. If provided here or found in the Minim configuration file, the authorization process is bypassed. In the former case, all other relevant keyword arguments should be specified to automatically refresh the access token when it expires. expiry : `datetime.datetime` or `str`, keyword-only, optional Expiry time of `access_token` in the ISO 8601 format :code:`%Y-%m-%dT%H:%M:%SZ`. If provided, the user will be reauthenticated when `access_token` expires. save : `bool`, keyword-only, default: :code:`True` Determines whether newly obtained access tokens and their associated properties are stored to the Minim configuration file. Attributes ---------- LYRICS_URL : `str` Base URL for the Spotify Lyrics service. TOKEN_URL : `str` URL for the Spotify Web Player access token endpoint. session : `requests.Session` Session used to send requests to the Spotify Lyrics service. """ _NAME = f"{__module__}.{__qualname__}" LYRICS_URL = "https://spclient.wg.spotify.com/color-lyrics/v2" TOKEN_URL = "https://open.spotify.com/get_access_token" def __init__( self, *, sp_dc: str = None, access_token: str = None, expiry: Union[datetime.datetime, str] = None, save: bool = True, ) -> None: """ Create a Spotify Lyrics service client. """ self.session = requests.Session() self.session.headers["App-Platform"] = "WebPlayer" if access_token is None and _config.has_section(self._NAME): sp_dc = _config.get(self._NAME, "sp_dc") access_token = _config.get(self._NAME, "access_token") expiry = _config.get(self._NAME, "expiry") self.set_sp_dc(sp_dc, save=save) self.set_access_token(access_token=access_token, expiry=expiry) def _get_json(self, url: str, **kwargs) -> dict: """ Send a GET request and return the JSON-encoded content of the response. Parameters ---------- url : `str` URL for the GET request. **kwargs Keyword arguments to pass to :meth:`requests.request`. Returns ------- resp : `dict` JSON-encoded content of the response. """ return self._request("get", url, **kwargs).json() def _request( self, method: str, url: str, retry: bool = True, **kwargs ) -> requests.Response: """ Construct and send a request with status code checking. Parameters ---------- method : `str` Method for the request. url : `str` URL for the request. retry : `bool` Specifies whether to retry the request if the response has a non-2xx status code. **kwargs Keyword arguments passed to :meth:`requests.request`. Returns ------- resp : `requests.Response` Response to the request. """ if self._expiry is not None and datetime.datetime.now() > self._expiry: self.set_access_token() r = self.session.request(method, url, **kwargs) if r.status_code != 200: emsg = f"{r.status_code} {r.reason}" if r.status_code == 401 and retry: logging.warning(emsg) self.set_access_token() return self._request(method, url, False, **kwargs) else: raise RuntimeError(emsg) return r
[docs] def set_sp_dc(self, sp_dc: str = None, *, save: bool = True) -> None: """ Set the Spotify Web Player :code:`sp_dc` cookie. Parameters ---------- sp_dc : `str`, optional Spotify Web Player :code:`sp_dc` cookie. save : `bool`, keyword-only, default: :code:`True` Determines whether to save the newly obtained access tokens and their associated properties to the Minim configuration file. """ self._sp_dc = sp_dc or os.environ.get("SPOTIFY_SP_DC") self._save = save
[docs] def set_access_token( self, access_token: str = None, expiry: Union[datetime.datetime, str] = None, ) -> None: """ Set the Spotify Lyrics service access token. Parameters ---------- access_token : `str`, optional Access token. If not provided, an access token is obtained from the Spotify Web Player using the :code:`sp_dc` cookie. expiry : `str` or `datetime.datetime`, keyword-only, optional Access token expiry timestamp in the ISO 8601 format :code:`%Y-%m-%dT%H:%M:%SZ`. If provided, the user will be reauthenticated (if `sp_dc` is found or provided) when the `access_token` expires. """ if access_token is None: if not self._sp_dc: raise ValueError("Missing sp_dc cookie.") r = requests.get( self.TOKEN_URL, headers={"cookie": f"sp_dc={self._sp_dc}"}, params={"reason": "transport", "productType": "web_player"}, ).json() if r["isAnonymous"]: raise ValueError("Invalid sp_dc cookie.") access_token = r["accessToken"] expiry = datetime.datetime.fromtimestamp( r["accessTokenExpirationTimestampMs"] / 1000 ) if self._save: _config[self._NAME] = { "sp_dc": self._sp_dc, "access_token": access_token, "expiry": expiry.strftime("%Y-%m-%dT%H:%M:%SZ"), } with open(DIR_HOME / "minim.cfg", "w") as f: _config.write(f) self.session.headers["Authorization"] = f"Bearer {access_token}" self._expiry = ( datetime.datetime.strptime(expiry, "%Y-%m-%dT%H:%M:%SZ") if isinstance(expiry, str) else expiry )
[docs] def get_lyrics(self, track_id: str) -> dict[str, Any]: """ Get lyrics for a Spotify track. Parameters ---------- track_id : `str` The Spotify ID for the track. **Example**: :code:`"0VjIjW4GlUZAMYd2vXMi3b"`. Returns ------- lyrics : `dict` Formatted or time-synced lyrics. .. admonition:: Sample response :class: dropdown .. code:: { "lyrics": { "syncType": <str>, "lines": [ { "startTimeMs": <str>, "words": <str>, "syllables": [], "endTimeMs": <str> } ], "provider": <str>, "providerLyricsId": <str>, "providerDisplayName": <str>, "syncLyricsUri": <str>, "isDenseTypeface": <bool>, "alternatives": [], "language": <str>, "isRtlLanguage": <bool>, "fullscreenAction": <str>, "showUpsell": <bool> }, "colors": { "background": <int>, "text": <int>, "highlightText": <int> }, "hasVocalRemoval": <bool> } """ return self._get_json( f"{self.LYRICS_URL}/track/{track_id}", params={"format": "json", "market": "from_token"}, )
[docs] class WebAPI: """ Spotify Web API client. The Spotify Web API enables the creation of applications that can interact with Spotify's streaming service, such as retrieving content metadata, getting recommendations, creating and managing playlists, or controlling playback. .. important:: * Spotify content may not be downloaded. * Keep visual content in its original form. * Ensure content attribution. .. seealso:: For more information, see the `Spotify Web API Reference <https://developer.spotify.com/documentation/web-api>`_. Requests to the Spotify Web API endpoints must be accompanied by a valid access token in the header. An access token can be obtained with or without user authentication. While authentication is not necessary to search for and retrieve data from public content, it is required to access personal content and control playback. Minim can obtain client-only access tokens via the `client credentials <https://developer.spotify.com/documentation/general /guides/authorization/client-credentials/>`_ flow and user access tokens via the `authorization code <https://developer.spotify.com /documentation/web-api/tutorials/code-flow>`_ and `authorization code with proof key for code exchange (PKCE) <https://developer.spotify.com/documentation/web-api/tutorials/ code-pkce-flow>`_ flows. These OAuth 2.0 authorization flows require valid client credentials (client ID and client secret) to either be provided to this class's constructor as keyword arguments or be stored as :code:`SPOTIFY_CLIENT_ID` and :code:`SPOTIFY_CLIENT_SECRET` in the operating system's environment variables. .. seealso:: To get client credentials, see the `guide on how to create a new Spotify application <https://developer.spotify.com/documentation /general/guides/authorization/app-settings/>`_. To take advantage of Minim's automatic authorization code retrieval functionality for the authorization code (with PKCE) flow, the redirect URI should be in the form :code:`http://localhost:{port}/callback`, where :code:`{port}` is an open port on :code:`localhost`. Alternatively, a access token can be acquired without client credentials through the Spotify Web Player, but this approach is not recommended and should only be used as a last resort since it is not officially supported and can be deprecated by Spotify at any time. The access token is client-only unless a Spotify Web Player :code:`sp_dc` cookie is either provided to this class's constructor as a keyword argument or be stored as :code:`SPOTIFY_SP_DC` in the operating system's environment variables, in which case a user access token with all authorization scopes is granted instead. If an existing access token is available, it and its accompanying information (refresh token and expiry time) can be provided to this class's constructor as keyword arguments to bypass the access token retrieval process. It is recommended that all other authorization-related keyword arguments be specified so that a new access token can be obtained when the existing one expires. .. tip:: The authorization flow and access token can be changed or updated at any time using :meth:`set_flow` and :meth:`set_access_token`, respectively. Minim also stores and manages access tokens and their properties. When any of the authorization flows above are used to acquire an access token, it is automatically saved to the Minim configuration file to be loaded on the next instantiation of this class. This behavior can be disabled if there are any security concerns, like if the computer being used is a shared device. Parameters ---------- client_id : `str`, keyword-only, optional Client ID. Required for the authorization code and client credentials flows. If it is not stored as :code:`SPOTIFY_CLIENT_ID` in the operating system's environment variables or found in the Minim configuration file, it must be provided here. client_secret : `str`, keyword-only, optional Client secret. Required for the authorization code and client credentials flows. If it is not stored as :code:`SPOTIFY_CLIENT_SECRET` in the operating system's environment variables or found in the Minim configuration file, it must be provided here. flow : `str`, keyword-only, default: :code:`"web_player"` Authorization flow. .. container:: **Valid values**: * :code:`"authorization_code"` for the authorization code flow. * :code:`"pkce"` for the authorization code with proof key for code exchange (PKCE) flow. * :code:`"client_credentials"` for the client credentials flow. * :code:`"web_player"` for a Spotify Web Player access token. browser : `bool`, keyword-only, default: :code:`False` Determines whether a web browser is automatically opened for the authorization code (with PKCE) flow. If :code:`False`, users will have to manually open the authorization URL. Not applicable when `web_framework="playwright"`. web_framework : `str`, keyword-only, optional Determines which web framework to use for the authorization code (with PKCE) flow. .. container:: **Valid values**: * :code:`"http.server"` for the built-in implementation of HTTP servers. * :code:`"flask"` for the Flask framework. * :code:`"playwright"` for the Playwright framework by Microsoft. port : `int` or `str`, keyword-only, default: :code:`8888` Port on :code:`localhost` to use for the authorization code flow with the :code:`http.server` and Flask frameworks. Only used if `redirect_uri` is not specified. redirect_uri : `str`, keyword-only, optional Redirect URI for the authorization code flow. If not on :code:`localhost`, the automatic authorization code retrieval functionality is not available. scopes : `str` or `list`, keyword-only, optional Authorization scopes to request user access for in the authorization code flow. .. seealso:: See :meth:`get_scopes` for the complete list of scopes. sp_dc : `str`, keyword-only, optional Spotify Web Player :code:`sp_dc` cookie to send with the access token request. If provided here, stored as :code:`SPOTIFY_SP_DC` in the operating system's environment variables, or found in the Minim configuration file, a user access token with all authorization scopes is obtained instead of a client-only access token. access_token : `str`, keyword-only, optional Access token. If provided here or found in the Minim configuration file, the authorization process is bypassed. In the former case, all other relevant keyword arguments should be specified to automatically refresh the access token when it expires. refresh_token : `str`, keyword-only, optional Refresh token accompanying `access_token`. If not provided, the user will be reauthenticated using the specified authorization flow when `access_token` expires. expiry : `datetime.datetime` or `str`, keyword-only, optional Expiry time of `access_token` in the ISO 8601 format :code:`%Y-%m-%dT%H:%M:%SZ`. If provided, the user will be reauthenticated using `refresh_token` (if available) or the specified authorization flow (if possible) when `access_token` expires. overwrite : `bool`, keyword-only, default: :code:`False` Determines whether to overwrite an existing access token in the Minim configuration file. save : `bool`, keyword-only, default: :code:`True` Determines whether newly obtained access tokens and their associated properties are stored to the Minim configuration file. Attributes ---------- API_URL : `str` Base URL for the Spotify Web API. AUTH_URL : `str` URL for Spotify Web API authorization code requests. TOKEN_URL : `str` URL for Spotify Web API access token requests. WEB_PLAYER_TOKEN_URL : `str` URL for Spotify Web Player access token requests. session : `requests.Session` Session used to send requests to the Spotify Web API. """ _FLOWS = {"authorization_code", "pkce", "client_credentials", "web_player"} _NAME = f"{__module__}.{__qualname__}" API_URL = "https://api.spotify.com/v1" AUTH_URL = "https://accounts.spotify.com/authorize" TOKEN_URL = "https://accounts.spotify.com/api/token" WEB_PLAYER_TOKEN_URL = "https://open.spotify.com/get_access_token"
[docs] @classmethod def get_scopes(cls, categories: Union[str, list[str]]) -> str: """ Get Spotify Web API and Open Access authorization scopes for the specified categories. Parameters ---------- categories : `str` or `list` Categories of authorization scopes to get. .. container:: **Valid values**: * :code:`"images"` for scopes related to custom images, such as :code:`ugc-image-upload`. * :code:`"spotify_connect"` for scopes related to Spotify Connect, such as * :code:`user-read-playback-state`, * :code:`user-modify-playback-state`, and * :code:`user-read-currently-playing`. * :code:`"playback"` for scopes related to playback control, such as :code:`app-remote-control` and :code:`streaming`. * :code:`"playlists"` for scopes related to playlists, such as * :code:`playlist-read-private`, * :code:`playlist-read-collaborative`, * :code:`playlist-modify-private`, and * :code:`playlist-modify-public`. * :code:`"follow"` for scopes related to followed artists and users, such as :code:`user-follow-modify` and :code:`user-follow-read`. * :code:`"listening_history"` for scopes related to playback history, such as * :code:`user-read-playback-position`, * :code:`user-top-read`, and * :code:`user-read-recently-played`. * :code:`"library"` for scopes related to saved content, such as :code:`user-library-modify` and :code:`user-library-read`. * :code:`"users"` for scopes related to user information, such as :code:`user-read-email` and :code:`user-read-private`. * :code:`"all"` for all scopes above. * A substring to match in the possible scopes, such as * :code:`"read"` for all scopes above that grant read access, i.e., scopes with :code:`read` in the name, * :code:`"modify"` for all scopes above that grant modify access, i.e., scopes with :code:`modify` in the name, or * :code:`"user"` for all scopes above that grant access to all user-related information, i.e., scopes with :code:`user` in the name. .. seealso:: For the endpoints that the scopes allow access to, see the `Scopes page of the Spotify Web API Reference <https://developer.spotify.com/documentation/web-api /concepts/scopes>`_. """ SCOPES = { "images": ["ugc-image-upload"], "spotify_connect": [ "user-read-playback-state", "user-modify-playback-state", "user-read-currently-playing", ], "playback": ["app-remote-control streaming"], "playlists": [ "playlist-read-private", "playlist-read-collaborative", "playlist-modify-private", "playlist-modify-public", ], "follow": ["user-follow-modify", "user-follow-read"], "listening_history": [ "user-read-playback-position", "user-top-read", "user-read-recently-played", ], "library": ["user-library-modify", "user-library-read"], "users": ["user-read-email", "user-read-private"], } if isinstance(categories, str): if categories in SCOPES.keys(): return SCOPES[categories] if categories == "all": return " ".join( s for scopes in SCOPES.values() for s in scopes ) return " ".join( s for scopes in SCOPES.values() for s in scopes if categories in s ) return " ".join( s for scopes in (cls.get_scopes[c] for c in categories) for s in scopes )
def __init__( self, *, client_id: str = None, client_secret: str = None, flow: str = "web_player", browser: bool = False, web_framework: str = None, port: Union[int, str] = 8888, redirect_uri: str = None, scopes: Union[str, list[str]] = "", sp_dc: str = None, access_token: str = None, refresh_token: str = None, expiry: Union[datetime.datetime, str] = None, overwrite: bool = False, save: bool = True, ) -> None: """ Create a Spotify Web API client. """ self.session = requests.Session() if ( access_token is None and _config.has_section(self._NAME) and not overwrite ): flow = _config.get(self._NAME, "flow") access_token = _config.get(self._NAME, "access_token") refresh_token = _config.get( self._NAME, "refresh_token", fallback=None ) expiry = _config.get(self._NAME, "expiry", fallback=None) client_id = _config.get(self._NAME, "client_id") client_secret = _config.get( self._NAME, "client_secret", fallback=None ) redirect_uri = _config.get( self._NAME, "redirect_uri", fallback=None ) scopes = _config.get(self._NAME, "scopes") sp_dc = _config.get(self._NAME, "sp_dc", fallback=None) self.set_flow( flow, client_id=client_id, client_secret=client_secret, browser=browser, web_framework=web_framework, port=port, redirect_uri=redirect_uri, scopes=scopes, sp_dc=sp_dc, save=save, ) self.set_access_token( access_token, refresh_token=refresh_token, expiry=expiry ) def _check_scope(self, endpoint: str, scope: str) -> None: """ Check if the user has granted the appropriate authorization scope for the desired endpoint. Parameters ---------- endpoint : `str` Spotify Web API endpoint. scope : `str` Required scope for `endpoint`. """ if scope not in self._scopes: emsg = ( f"{self._NAME}.{endpoint}() requires the '{scope}' " "authorization scope." ) raise RuntimeError(emsg) def _get_authorization_code(self, code_challenge: str = None) -> str: """ Get an authorization code to be exchanged for an access token in the authorization code flow. Parameters ---------- code_challenge : `str`, optional Code challenge for the authorization code with PKCE flow. Returns ------- auth_code : `str` Authorization code. """ params = { "client_id": self._client_id, "redirect_uri": self._redirect_uri, "response_type": "code", "state": secrets.token_urlsafe(), } if self._scopes: params["scope"] = self._scopes if code_challenge is not None: params["code_challenge"] = code_challenge params["code_challenge_method"] = "S256" auth_url = f"{self.AUTH_URL}?{urllib.parse.urlencode(params)}" if self._web_framework == "playwright": har_file = DIR_TEMP / "minim_spotify.har" with sync_playwright() as playwright: browser = playwright.firefox.launch(headless=False) context = browser.new_context(record_har_path=har_file) page = context.new_page() page.goto(auth_url, timeout=0) with page.expect_request( "https://accounts.spotify.com/*/authorize/accept*" ) as _: pass # blocking call context.close() browser.close() with open(har_file, "r") as f: queries = dict( urllib.parse.parse_qsl( urllib.parse.urlparse( re.search( rf'{self._redirect_uri}\?(.*?)"', f.read() ).group(0) ).query ) ) har_file.unlink() else: if self._browser: webbrowser.open(auth_url) else: print( "To grant Minim access to Spotify data and " "features, open the following link in your web " f"browser:\n\n{auth_url}\n" ) if self._web_framework == "http.server": httpd = HTTPServer(("", self._port), _SpotifyRedirectHandler) httpd.handle_request() queries = httpd.response elif self._web_framework == "flask": app = Flask(__name__) json_file = DIR_TEMP / "minim_spotify.json" @app.route("/callback", methods=["GET"]) def _callback() -> str: if "error" in request.args: return "Access denied. You may close this page now." with open(json_file, "w") as f: json.dump(request.args, f) return "Access granted. You may close this page now." server = Process(target=app.run, args=("0.0.0.0", self._port)) server.start() while not json_file.is_file(): time.sleep(0.1) server.terminate() with open(json_file, "rb") as f: queries = json.load(f) json_file.unlink() else: uri = input( "After authorizing Minim to access Spotify on " "your behalf, copy and paste the URI beginning " f"with '{self._redirect_uri}' below.\n\nURI: " ) queries = dict( urllib.parse.parse_qsl(urllib.parse.urlparse(uri).query) ) if "error" in queries: raise RuntimeError( f"Authorization failed. Error: {queries['error']}" ) if params["state"] != queries["state"]: raise RuntimeError("Authorization failed due to state mismatch.") return queries["code"] def _get_json(self, url: str, **kwargs) -> dict: """ Send a GET request and return the JSON-encoded content of the response. Parameters ---------- url : `str` URL for the GET request. **kwargs Keyword arguments to pass to :meth:`requests.request`. Returns ------- resp : `dict` JSON-encoded content of the response. """ return self._request("get", url, **kwargs).json() def _refresh_access_token(self) -> None: """ Refresh the expired excess token. """ if ( self._flow == "web_player" or not self._refresh_token or not self._client_id or not self._client_secret ): self.set_access_token() else: client_b64 = base64.urlsafe_b64encode( f"{self._client_id}:{self._client_secret}".encode() ).decode() r = requests.post( self.TOKEN_URL, data={ "grant_type": "refresh_token", "refresh_token": self._refresh_token, }, headers={"Authorization": f"Basic {client_b64}"}, ).json() self.session.headers["Authorization"] = ( f"Bearer {r['access_token']}" ) self._refresh_token = r["refresh_token"] self._expiry = datetime.datetime.now() + datetime.timedelta( 0, r["expires_in"] ) self._scopes = r["scope"] if self._save: _config[self._NAME].update( { "access_token": r["access_token"], "refresh_token": self._refresh_token, "expiry": self._expiry.strftime("%Y-%m-%dT%H:%M:%SZ"), "scopes": self._scopes, } ) with open(DIR_HOME / "minim.cfg", "w") as f: _config.write(f) def _request( self, method: str, url: str, retry: bool = True, **kwargs ) -> requests.Response: """ Construct and send a request with status code checking. Parameters ---------- method : `str` Method for the request. url : `str` URL for the request. retry : `bool` Specifies whether to retry the request if the response has a non-2xx status code. **kwargs Keyword arguments passed to :meth:`requests.request`. Returns ------- resp : `requests.Response` Response to the request. """ if self._expiry is not None and datetime.datetime.now() > self._expiry: self._refresh_access_token() r = self.session.request(method, url, **kwargs) if r.status_code not in range(200, 299): error = r.json()["error"] emsg = f"{error['status']}" if "message" in error: emsg += f" {error['message']}" if r.status_code == 401 and retry: logging.warning(emsg) self._refresh_access_token() return self._request(method, url, False, **kwargs) else: raise RuntimeError(emsg) return r
[docs] def set_access_token( self, access_token: str = None, *, refresh_token: str = None, expiry: Union[str, datetime.datetime] = None, ) -> None: """ Set the Spotify Web API access token. Parameters ---------- access_token : `str`, optional Access token. If not provided, an access token is obtained using an OAuth 2.0 authorization flow or from the Spotify Web Player. refresh_token : `str`, keyword-only, optional Refresh token accompanying `access_token`. expiry : `str` or `datetime.datetime`, keyword-only, optional Access token expiry timestamp in the ISO 8601 format :code:`%Y-%m-%dT%H:%M:%SZ`. If provided, the user will be reauthenticated using the refresh token (if available) or the default authorization flow (if possible) when `access_token` expires. """ if access_token is None: if self._flow == "web_player": headers = ( {"cookie": f"sp_dc={self._sp_dc}"} if self._sp_dc else {} ) r = requests.get( self.WEB_PLAYER_TOKEN_URL, headers=headers ).json() self._client_id = r["clientId"] access_token = r["accessToken"] expiry = datetime.datetime.fromtimestamp( r["accessTokenExpirationTimestampMs"] / 1000 ) if self._sp_dc and r["isAnonymous"]: wmsg = ( "The sp_dc cookie is invalid, so the " "access token granted is client-only." ) warnings.warn(wmsg) else: if not self._client_id or not self._client_secret: emsg = "Spotify Web API client credentials not provided." raise ValueError(emsg) if self._flow == "client_credentials": r = requests.post( self.TOKEN_URL, data={ "client_id": self._client_id, "client_secret": self._client_secret, "grant_type": "client_credentials", }, ).json() else: client_b64 = base64.urlsafe_b64encode( f"{self._client_id}:{self._client_secret}".encode() ).decode() data = { "grant_type": "authorization_code", "redirect_uri": self._redirect_uri, } if self._flow == "pkce": data["client_id"] = self._client_id data["code_verifier"] = secrets.token_urlsafe(96) data["code"] = self._get_authorization_code( base64.urlsafe_b64encode( hashlib.sha256( data["code_verifier"].encode() ).digest() ) .decode() .replace("=", "") ) else: data["code"] = self._get_authorization_code() r = requests.post( self.TOKEN_URL, data=data, headers={"Authorization": f"Basic {client_b64}"}, ).json() refresh_token = r["refresh_token"] access_token = r["access_token"] expiry = datetime.datetime.now() + datetime.timedelta( 0, r["expires_in"] ) if self._save: _config[self._NAME] = { "flow": self._flow, "client_id": self._client_id, "access_token": access_token, "expiry": expiry.strftime("%Y-%m-%dT%H:%M:%SZ"), "scopes": self._scopes, } if refresh_token: _config[self._NAME]["refresh_token"] = refresh_token for attr in ("client_secret", "redirect_uri", "sp_dc"): if hasattr(self, f"_{attr}"): _config[self._NAME][attr] = ( getattr(self, f"_{attr}") or "" ) with open(DIR_HOME / "minim.cfg", "w") as f: _config.write(f) self.session.headers["Authorization"] = f"Bearer {access_token}" self._refresh_token = refresh_token self._expiry = ( datetime.datetime.strptime(expiry, "%Y-%m-%dT%H:%M:%SZ") if isinstance(expiry, str) else expiry ) if self._flow in {"authorization_code", "pkce"} or ( self._flow == "web_player" and self._sp_dc ): self._user_id = self.get_profile()["id"]
[docs] def set_flow( self, flow: str, *, client_id: str = None, client_secret: str = None, browser: bool = False, web_framework: str = None, port: Union[int, str] = 8888, redirect_uri: str = None, scopes: Union[str, list[str]] = "", sp_dc: str = None, save: bool = True, ) -> None: """ Set the authorization flow. Parameters ---------- flow : `str` Authorization flow. .. container:: **Valid values**: * :code:`"authorization_code"` for the authorization code flow. * :code:`"pkce"` for the authorization code with proof key for code exchange (PKCE) flow. * :code:`"client_credentials"` for the client credentials flow. * :code:`"web_player"` for a Spotify Web Player access token. client_id : `str`, keyword-only, optional Client ID. Required for all OAuth 2.0 authorization flows. client_secret : `str`, keyword-only, optional Client secret. Required for all OAuth 2.0 authorization flows. browser : `bool`, keyword-only, default: :code:`False` Determines whether a web browser is automatically opened for the authorization code (with PKCE) flow. If :code:`False`, users will have to manually open the authorization URL. Not applicable when `web_framework="playwright"`. web_framework : `str`, keyword-only, optional Web framework used to automatically complete the authorization code (with PKCE) flow. .. container:: **Valid values**: * :code:`"http.server"` for the built-in implementation of HTTP servers. * :code:`"flask"` for the Flask framework. * :code:`"playwright"` for the Playwright framework. port : `int` or `str`, keyword-only, default: :code:`8888` Port on :code:`localhost` to use for the authorization code flow with the :code:`http.server` and Flask frameworks. redirect_uri : `str`, keyword-only, optional Redirect URI for the authorization code flow. If not specified, an open port on :code:`localhost` will be used. scopes : `str` or `list`, keyword-only, optional Authorization scopes to request access to in the authorization code flow. sp_dc : `str`, keyword-only, optional Spotify Web Player :code:`sp_dc` cookie. save : `bool`, keyword-only, default: :code:`True` Determines whether to save the newly obtained access tokens and their associated properties to the Minim configuration file. """ if flow not in self._FLOWS: emsg = ( f"Invalid authorization flow ({flow=}). " f"Valid values: {', '.join(self._FLOWS)}." ) raise ValueError(emsg) self._flow = flow self._save = save if flow == "web_player": self._sp_dc = sp_dc or os.environ.get("SPOTIFY_SP_DC") self._scopes = self.get_scopes("all") if self._sp_dc else "" else: self._client_id = client_id or os.environ.get("SPOTIFY_CLIENT_ID") self._client_secret = client_secret or os.environ.get( "SPOTIFY_CLIENT_SECRET" ) if flow in {"authorization_code", "pkce"}: self._browser = browser self._scopes = ( " ".join(scopes) if isinstance(scopes, list) else scopes ) if redirect_uri: self._redirect_uri = redirect_uri if "localhost" in redirect_uri: self._port = re.search( r"localhost:(\d+)", redirect_uri ).group(1) elif web_framework: wmsg = ( "The redirect URI is not on localhost, " "so automatic authorization code " "retrieval is not available." ) logging.warning(wmsg) web_framework = None elif port: self._port = port self._redirect_uri = f"http://localhost:{port}/callback" else: self._port = self._redirect_uri = None self._web_framework = ( web_framework if web_framework in {None, "http.server"} or globals()[f"FOUND_{web_framework.upper()}"] else None ) if self._web_framework is None and web_framework: wmsg = ( f"The {web_framework.capitalize()} web " "framework was not found, so automatic " "authorization code retrieval is not " "available." ) warnings.warn(wmsg) elif flow == "client_credentials": self._scopes = ""
### ALBUMS ################################################################
[docs] def get_album(self, id: str, *, market: str = None) -> dict: """ `Albums > Get Album <https://developer.spotify.com/ documentation/web-api/reference/get-an-album>`_: Get Spotify catalog information for a single album. Parameters ---------- id : `str` The Spotify ID of the album. **Example**: :code:`"4aawyAB9vmqN3uQ7FjRGTy"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- album : `dict` Spotify catalog information for a single album. .. admonition:: Sample response :class: dropdown .. code:: { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "tracks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": <str>, "uri": <str> }, "restrictions": { "reason": <str> }, "name": <str>, "preview_url": <str>, "track_number": <int>, "type": <str>, "uri": <str>, "is_local": <bool> } ] } } """ return self._get_json( f"{self.API_URL}/albums/{id}", params={"market": market} )
[docs] def get_albums( self, ids: Union[str, list[str]], *, market: str = None ) -> dict[str, Any]: """ `Albums > Get Several Albums <https://developer.spotify.com/ documentation/web-api/reference/ get-multiple-albums>`_: Get Spotify catalog information for albums identified by their Spotify IDs. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the albums. **Maximum**: 20 IDs. **Example**: :code:`"382ObEPsp2rxGrnsizN5TX, 1A2GTWGtFfWp7KSQTwWOyo, 2noRn2Aes5aoNVsU6iWThc"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- albums : `list` A list containing Spotify catalog information for multiple albums. .. admonition:: Sample response :class: dropdown .. code:: [ { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "tracks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": <str>, "uri": <str> }, "restrictions": { "reason": <str> }, "name": <str>, "preview_url": <str>, "track_number": <int>, "type": <str>, "uri": <str>, "is_local": <bool> } ] } } ] """ return self._get_json( f"{self.API_URL}/albums", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )["albums"]
[docs] def get_album_tracks( self, id: str, *, limit: int = None, market: str = None, offset: int = None, ) -> dict[str, Any]: """ `Albums > Get Album Tracks <https://developer.spotify.com/ documentation/web-api/reference/ get-an-albums-tracks>`_: Get Spotify catalog information for an album's tracks. Optional parameters can be used to limit the number of tracks returned. Parameters ---------- id : `str` The Spotify ID of the album. **Example**: :code:`"4aawyAB9vmqN3uQ7FjRGTy"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- tracks : `dict` A dictionary containing Spotify catalog information for an album's tracks and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": <str>, "uri": <str> }, "restrictions": { "reason": <str> }, "name": <str>, "preview_url": <str>, "track_number": <int>, "type": <str>, "uri": <str>, "is_local": <bool> } ] } """ return self._get_json( f"{self.API_URL}/albums/{id}/tracks", params={"limit": limit, "market": market, "offset": offset}, )
[docs] def get_saved_albums( self, *, limit: int = None, market: str = None, offset: int = None ) -> dict[str, Any]: """ `Albums > Get User's Saved Albums <https://developer.spotify.com/ documentation/web-api/reference/ get-users-saved-albums>`_: Get a list of the albums saved in the current Spotify user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- albums : `dict` A dictionary containing Spotify catalog information for a user's saved albums and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "tracks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "type": <str>, "uri": <str> }, "restrictions": { "reason": <str> }, "name": <str>, "preview_url": <str>, "track_number": <int>, "type": <str>, "uri": <str>, "is_local": <bool> } ] } } } ] } """ self._check_scope("get_saved_albums", "user-library-read") return self._get_json( f"{self.API_URL}/me/albums", params={"limit": limit, "market": market, "offset": offset}, )
[docs] def save_albums(self, ids: Union[str, list[str]]) -> None: """ `Albums > Save Albums for Current User <https://developer.spotify.com/documentation/web-api/reference/ save-albums-user>`_: Save one or more albums to the current user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the albums. **Maximum**: 20 (`str`) or 50 (`list`) IDs. **Example**: :code:`"382ObEPsp2rxGrnsizN5TX, 1A2GTWGtFfWp7KSQTwWOyo, 2noRn2Aes5aoNVsU6iWThc"`. """ self._check_scope("save_albums", "user-library-modify") if isinstance(ids, str): self._request( "put", f"{self.API_URL}/me/albums", params={"ids": ids} ) elif isinstance(ids, list): self._request( "put", f"{self.API_URL}/me/albums", json={"ids": ids} )
[docs] def remove_saved_albums(self, ids: Union[str, list[str]]) -> None: """ `Albums > Remove Users' Saved Albums <https://developer.spotify.com/documentation/web-api/reference/ remove-albums-user>`_: Remove one or more albums from the current user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the albums. **Maximum**: 20 (`str`) or 50 (`list`) IDs. **Example**: :code:`"382ObEPsp2rxGrnsizN5TX, 1A2GTWGtFfWp7KSQTwWOyo, 2noRn2Aes5aoNVsU6iWThc"`. """ self._check_scope("remove_saved_albums", "user-library-modify") if isinstance(ids, str): self._request( "delete", f"{self.API_URL}/me/albums", params={"ids": ids} ) elif isinstance(ids, list): self._request( "delete", f"{self.API_URL}/me/albums", json={"ids": ids} )
[docs] def check_saved_albums(self, ids: Union[str, list[str]]) -> list[bool]: """ `Albums > Check User's Saved Albums <https://developer.spotify.com/documentation/web-api/reference/ check-users-saved-albums>`_: Check if one or more albums is already saved in the current Spotify user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the albums. **Maximum**: 20 IDs. Returns ------- contains : `list` Array of booleans specifying whether the albums are found in the user's 'Your Library > Albums'. **Example**: :code:`[False, True]`. """ self._check_scope("check_saved_albums", "user-library-read") return self._get_json( f"{self.API_URL}/me/albums/contains", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )
[docs] def get_new_albums( self, *, country: str = None, limit: int = None, offset: int = None ) -> list[dict[str, Any]]: """ `Albums > Get New Releases <https://developer.spotify.com/ documentation/web-api/reference/ get-new-releases>`_: Get a list of new album releases featured in Spotify (shown, for example, on a Spotify player's "Browse" tab). Parameters ---------- country : `str`, keyword-only, optional A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want the list of returned items to be relevant to a particular country. If omitted, the returned items will be relevant to all countries. **Example**: :code:`"SE"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- albums : `list` A list containing Spotify catalog information for newly-released albums. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] } ] } """ return self._get_json( f"{self.API_URL}/browse/new-releases", params={"country": country, "limit": limit, "offset": offset}, )["albums"]
### ARTISTS ###############################################################
[docs] def get_artist(self, id: str) -> dict[str, Any]: """ `Artists > Get Artist <https://developer.spotify.com/ documentation/web-api/reference/get-an-artist>`_: Get Spotify catalog information for a single artist identified by their unique Spotify ID. Parameters ---------- id : `str` The Spotify ID of the artist. **Example**: :code:`"0TnOYISbd1XYRBk9myaseg"`. Returns ------- artist : `dict` Spotify catalog information for a single artist. .. admonition:: Sample response :class: dropdown .. code:: { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } """ return self._get_json(f"{self.API_URL}/artists/{id}")
[docs] def get_artists( self, ids: Union[int, str, list[Union[int, str]]] ) -> list[dict[str, Any]]: """ `Artists > Get Several Artists <https://developer.spotify.com/ documentation/web-api/reference/ get-multiple-artists>`_: Get Spotify catalog information for several artists based on their Spotify IDs. Parameters ---------- ids : `str` A (comma-separated) list of the Spotify IDs for the artists. **Maximum**: 50 IDs. **Example**: :code:`"2CIMQHirSU0MQqyYHq0eOx, 57dN52uHvrHOxijzpIgu3E, 1vCWHaC5f2uS3yhpwWbIA6"`. Returns ------- artists : `list` A list containing Spotify catalog information for multiple artists. .. admonition:: Sample response :class: dropdown .. code:: [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ] """ return self._get_json( f"{self.API_URL}/artists", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )["artists"]
[docs] def get_artist_albums( self, id: str, *, include_groups: Union[str, list[str]] = None, limit: int = None, market: str = None, offset: int = None, ) -> list[dict[str, Any]]: """ `Artist > Get Artist's Albums <https://developer.spotify.com/ documentation/web-api/reference/ get-an-artists-albums>`_: Get Spotify catalog information about an artist's albums. Parameters ---------- id : `str` The Spotify ID of the artist. **Example**: :code:`"0TnOYISbd1XYRBk9myaseg"`. include_groups : `str` or `list`, keyword-only, optional A comma-separated list of keywords that will be used to filter the response. If not supplied, all album types will be returned. .. container:: **Valid values**: * :code:`"album"` for albums. * :code:`"single"` for singles or promotional releases. * :code:`"appears_on"` for albums that `artist` appears on as a featured artist. * :code:`"compilation"` for compilations. **Examples**: * :code:`"album,single"` for albums and singles where `artist` is the main album artist. * :code:`"single,appears_on"` for singles and albums that `artist` appears on. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- albums : `list` A list containing Spotify catalog information for the artist's albums. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] } ] } """ return self._get_json( f"{self.API_URL}/artists/{id}/albums", params={ "include_groups": include_groups, "limit": limit, "market": market, "offset": offset, }, )
[docs] def get_artist_top_tracks( self, id: str, *, market: str = "US" ) -> list[dict[str, Any]]: """ `Artist > Get Artist's Top Tracks <https://developer.spotify.com/documentation/web-api/reference/ get-an-artists-top-tracks>`_: Get Spotify catalog information about an artist's top tracks by country. Parameters ---------- id : `str` The Spotify ID of the artist. **Example**: :code:`"0TnOYISbd1XYRBk9myaseg"`. market : `str`, keyword-only, default: :code:`"US"` An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- tracks : `list` A list containing Spotify catalog information for the artist's top tracks. .. admonition:: Sample response :class: dropdown .. code:: [ { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool>, "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } ] """ return self._get_json( f"{self.API_URL}/artists/{id}/top-tracks", params={"market": market}, )["tracks"]
### AUDIOBOOKS ############################################################
[docs] def get_audiobook(self, id: str, *, market: str = None) -> dict[str, Any]: """ `Audiobooks > Get an Audiobook <https://developer.spotify.com/documentation/web-api/reference/ get-an-audiobook>`_: Get Spotify catalog information for a single audiobook. .. note:: Audiobooks are only available for the US, UK, Ireland, New Zealand, and Australia markets. Parameters ---------- id : `str` The Spotify ID for the audiobook. **Example**: :code:`"7iHfbu1YPACw6oZPAFJtqe"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- audiobook : `dict` Spotify catalog information for a single audiobook. .. admonition:: Sample response :class: dropdown .. code:: { "authors": [ { "name": <str> } ], "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "edition": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "languages": [<str>], "media_type": <str>, "name": <str>, "narrators": [ { "name": <str> } ], "publisher": <str>, "type": "audiobook", "uri": <str>, "total_chapters": <int>, "chapters": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "audio_preview_url": <str>, "available_markets": [<str>], "chapter_number": <int>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_playable": <bool> "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> } } ] } } """ return self._get_json( f"{self.API_URL}/audiobooks/{id}", params={"market": market} )
[docs] def get_audiobooks( self, ids: Union[int, str, list[Union[int, str]]], *, market: str = None, ) -> list[dict[str, Any]]: """ `Audiobooks > Get Several Audiobooks <https://developer.spotify.com/documentation/web-api/reference/ get-multiple-audiobooks>`_: Get Spotify catalog information for several audiobooks identified by their Spotify IDs. .. note:: Audiobooks are only available for the US, UK, Ireland, New Zealand, and Australia markets. Parameters ---------- ids : `int`, `str`, or `list` A (comma-separated) list of the Spotify IDs for the audiobooks. **Maximum**: 50 IDs. **Example**: :code:`"18yVqkdbdRvS24c0Ilj2ci, 1HGw3J3NxZO1TP1BTtVhpZ, 7iHfbu1YPACw6oZPAFJtqe"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- audiobooks : `dict` or `list` A list containing Spotify catalog information for multiple audiobooks. .. admonition:: Sample response :class: dropdown .. code:: [ { "authors": [ { "name": <str> } ], "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "edition": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "languages": [<str>], "media_type": <str>, "name": <str>, "narrators": [ { "name": <str> } ], "publisher": <str>, "type": "audiobook", "uri": <str>, "total_chapters": <int>, "chapters": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "audio_preview_url": <str>, "available_markets": [<str>], "chapter_number": <int>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_playable": <bool> "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> } } ] } } ] """ return self._get_json( f"{self.API_URL}/audiobooks", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )["audiobooks"]
[docs] def get_audiobook_chapters( self, id: str, *, limit: int = None, market: str = None, offset: int = None, ) -> dict[str, Any]: """ `Audiobooks > Get Audiobook Chapters <https://developer.spotify.com/documentation/web-api/reference/ get-audiobook-chapters>`_: Get Spotify catalog information about an audiobook's chapters. .. note:: Audiobooks are only available for the US, UK, Ireland, New Zealand, and Australia markets. Parameters ---------- id : `str` The Spotify ID for the audiobook. **Example**: :code:`"7iHfbu1YPACw6oZPAFJtqe"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- audiobooks : `dict` A dictionary containing Spotify catalog information for an audiobook's chapters and the number of results returned. .. admonition:: Sample :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "audio_preview_url": <str>, "available_markets": [<str>], "chapter_number": <int>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_playable": <bool> "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> } } ] } """ return self._get_json( f"{self.API_URL}/audiobooks/{id}/chapters", params={"limit": limit, "market": market, "offset": offset}, )
[docs] def get_saved_audiobooks( self, *, limit: int = None, offset: int = None ) -> dict[str, Any]: """ `Audiobooks > Get User's Saved Audiobooks <https://developer.spotify.com/documentation/web-api/reference/ get-users-saved-audiobooks>`_: Get a list of the albums saved in the current Spotify user's audiobooks library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- audiobooks : `dict` A dictionary containing Spotify catalog information for a user's saved audiobooks and the number of results returned. .. admonition:: Sample :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "authors": [ { "name": <str> } ], "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "edition": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "languages": [<str>], "media_type": <str>, "name": <str>, "narrators": [ { "name": <str> } ], "publisher": <str>, "type": "audiobook", "uri": <str>, "total_chapters": <int> } ] } """ self._check_scope("get_saved_audiobooks", "user-library-read") return self._get_json( f"{self.API_URL}/me/audiobooks", params={"limit": limit, "offset": offset}, )
[docs] def save_audiobooks(self, ids: Union[str, list[str]]) -> None: """ `Audiobooks > Save Audiobooks for Current User <https://developer.spotify.com/documentation/web-api/reference/ save-audiobooks-user>`_: Save one or more audiobooks to current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the audiobooks. **Maximum**: 50 IDs. **Example**: :code:`"18yVqkdbdRvS24c0Ilj2ci, 1HGw3J3NxZO1TP1BTtVhpZ, 7iHfbu1YPACw6oZPAFJtqe"`. """ self._check_scope("save_audiobooks", "user-library-modify") self._request( "put", f"{self.API_URL}/me/audiobooks", params={ "ids": f"{ids if isinstance(ids, str) else ','.join(ids)}" }, )
[docs] def remove_saved_audiobooks(self, ids: Union[str, list[str]]) -> None: """ `Audiobooks > Remove User's Saved Audiobooks <https://developer.spotify.com/documentation/web-api/reference/ remove-audiobooks-user>`_: Delete one or more audiobooks from current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the audiobooks. **Maximum**: 50 IDs. **Example**: :code:`"18yVqkdbdRvS24c0Ilj2ci, 1HGw3J3NxZO1TP1BTtVhpZ, 7iHfbu1YPACw6oZPAFJtqe"`. """ self._check_scope("remove_saved_audiobooks", "user-library-modify") self._request( "delete", f"{self.API_URL}/me/audiobooks", params={ "ids": f"{ids if isinstance(ids, str) else ','.join(ids)}" }, )
[docs] def check_saved_audiobooks(self, ids: Union[str, list[str]]) -> list[bool]: """ `Audiobooks > Check User's Saved Audiobooks <https://developer.spotify.com/documentation/web-api/reference/ check-users-saved-audiobooks>`_: Check if one or more audiobooks are already saved in the current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the audiobooks. **Maximum**: 50 IDs. **Example**: :code:`"18yVqkdbdRvS24c0Ilj2ci, 1HGw3J3NxZO1TP1BTtVhpZ, 7iHfbu1YPACw6oZPAFJtqe"`. Returns ------- contains : `list` Array of booleans specifying whether the audiobooks are found in the user's saved audiobooks. **Example**: :code:`[False, True]`. """ self._check_scope("check_saved_audiobooks", "user-library-read") return self._get_json( f"{self.API_URL}/me/audiobooks/contains", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )
### CATEGORIES ############################################################
[docs] def get_category( self, category_id: str, *, country: str = None, locale: str = None ) -> dict[str, Any]: """ `Categories > Get Single Browse Category <https://developer.spotify.com/documentation/web-api/reference/ get-a-category>`_: Get a single category used to tag items in Spotify (on, for example, the Spotify player's "Browse" tab). Parameters ---------- category_id : `str` The Spotify category ID for the category. **Example**: :code:`"dinner"`. country : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. Provide this parameter to ensure that the category exists for a particular country. **Example**: :code:`"SE"`. locale : `str`, keyword-only, optional The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. Provide this parameter if you want the category strings returned in a particular language. .. note:: If `locale` is not supplied, or if the specified language is not available, the category strings returned will be in the Spotify default language (American English). **Example**: :code:`"es_MX"` for "Spanish (Mexico)". Returns ------- category : `dict` Information for a single browse category. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "icons": [ { "url": <str>, "height": <int>, "width": <int> } ], "id": <str>, "name": <str> } """ return self._get_json( f"{self.API_URL}/browse/categories/{category_id}", params={"country": country, "locale": locale}, )
[docs] def get_categories( self, *, country: str = None, limit: int = None, locale: str = None, offset: int = None, ) -> dict[str, Any]: """ `Categories > Get Several Browse Categories <https://developer.spotify.com/documentation/web-api/reference/ get-categories>`_: Get a list of categories used to tag items in Spotify (on, for example, the Spotify player's "Browse" tab). Parameters ---------- country : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. Provide this parameter to ensure that the category exists for a particular country. **Example**: :code:`"SE"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. locale : `str`, keyword-only, optional The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. Provide this parameter if you want the category strings returned in a particular language. .. note:: If locale is not supplied, or if the specified language is not available, the category strings returned will be in the Spotify default language (American English). **Example**: :code:`"es_MX"` for "Spanish (Mexico)". offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- categories : `dict` A dictionary containing nformation for the browse categories and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "items": [ { "href": <str>, "icons": [ { "height": <int>, "url": <str>, "width": <int> } ], "id": <str>, "name": <str> } ], "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int> } """ return self._get_json( f"{self.API_URL}/browse/categories", params={ "country": country, "limit": limit, "locale": locale, "offset": offset, }, )["categories"]
### CHAPTERS ##############################################################
[docs] def get_chapter(self, id: str, *, market: str = None) -> dict[str, Any]: """ `Chapters > Get a Chapter <https://developer.spotify.com/ documentation/web-api/reference/get-a-chapter>`_: Get Spotify catalog information for a single chapter. .. note:: Chapters are only available for the US, UK, Ireland, New Zealand, and Australia markets. Parameters ---------- id : `str` The Spotify ID for the chapter. **Example**: :code:`"0D5wENdkdwbqlrHoaJ9g29"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- chapter : `dict` Spotify catalog information for a single chapter. .. admonition:: Sample response :class: dropdown .. code:: { "audio_preview_url": <str>, "available_markets": [<str>], "chapter_number": <int>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_playable": <bool> "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> }, "audiobook": { "authors": [ { "name": <str> } ], "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "edition": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "languages": [<str>], "media_type": <str>, "name": <str>, "narrators": [ { "name": <str> } ], "publisher": <str>, "type": "audiobook", "uri": <str>, "total_chapters": <int> } } """ return self._get_json( f"{self.API_URL}/chapters/{id}", params={"market": market} )
[docs] def get_chapters( self, ids: Union[int, str, list[Union[int, str]]], *, market: str = None, ) -> list[dict[str, Any]]: """ `Chapters > Get Several Chapters <https://developer.spotify.com/ documentation/web-api/reference/ get-several-chapters>`_: Get Spotify catalog information for several chapters identified by their Spotify IDs. .. note:: Chapters are only available for the US, UK, Ireland, New Zealand, and Australia markets. Parameters ---------- ids : `int`, `str`, or `list` A (comma-separated) list of the Spotify IDs for the chapters. **Maximum**: 50 IDs. **Example**: :code:`"0IsXVP0JmcB2adSE338GkK, 3ZXb8FKZGU0EHALYX6uCzU, 0D5wENdkdwbqlrHoaJ9g29"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- chapters : `list` A list containing Spotify catalog information for multiple chapters. .. admonition:: Sample response :class: dropdown .. code:: [ { "audio_preview_url": <str>, "available_markets": [<str>], "chapter_number": <int>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_playable": <bool> "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> }, "audiobook": { "authors": [ { "name": <str> } ], "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "edition": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "languages": [<str>], "media_type": <str>, "name": <str>, "narrators": [ { "name": <str> } ], "publisher": <str>, "type": "audiobook", "uri": <str>, "total_chapters": <int> } } ] """ return self._get_json( f"{self.API_URL}/chapters", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )["chapters"]
### EPISODES ##############################################################
[docs] def get_episode(self, id: str, *, market: str = None) -> dict[str, Any]: """ `Episodes > Get Episode <https://developer.spotify.com/ documentation/web-api/reference/ get-an-episode>`_: Get Spotify catalog information for a single episode identified by its unique Spotify ID. Parameters ---------- id : `str` The Spotify ID for the episode. **Example**: :code:`"512ojhOuo1ktJprKbVcKyQ"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- episode : `dict` Spotify catalog information for a single episode. .. admonition:: Sample response :class: dropdown .. code:: { "audio_preview_url": <str>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool> "language": <str>, "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> }, "show": { "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [<str>], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int> } } """ return self._get_json( f"{self.API_URL}/episodes/{id}", params={"market": market} )
[docs] def get_episodes( self, ids: Union[int, str, list[Union[int, str]]], *, market: str = None, ) -> list[dict[str, Any]]: """ `Episodes > Get Several Episodes <https://developer.spotify.com/documentation/web-api/reference/ get-multiple-episodes>`_: Get Spotify catalog information for several episodes based on their Spotify IDs. Parameters ---------- ids : `int`, `str`, or `list` A (comma-separated) list of the Spotify IDs for the episodes. **Maximum**: 50 IDs. **Example**: :code:`"77o6BIVlYM3msb4MMIL1jH,0Q86acNRm6V9GYx55SXKwf"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- episodes : `list` A list containing Spotify catalog information for multiple episodes. .. admonition:: Sample :class: dropdown .. code:: [ { "audio_preview_url": <str>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool> "language": <str>, "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> }, "show": { "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [<str>], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int> } } ] """ return self._get_json( f"{self.API_URL}/episodes", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )["episodes"]
[docs] def get_saved_episodes( self, *, limit: int = None, market: str = None, offset: int = None ) -> dict[str, Any]: """ `Episodes > Get User's Saved Episodes <https://developer.spotify.com/documentation/web-api/reference/ get-users-saved-episodes>`_: Get a list of the episodes saved in the current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- episodes : `dict` A dictionary containing Spotify catalog information for a user's saved episodes and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "episode": { "audio_preview_url": <str>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool> "language": <str>, "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> }, "show": { "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [<str>], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int> } } } ] } """ self._check_scope("get_saved_episodes", "user-library-read") return self._get_json( f"{self.API_URL}/me/episodes", params={"limit": limit, "market": market, "offset": offset}, )
[docs] def save_episodes(self, ids: Union[str, list[str]]) -> None: """ `Episodes > Save Episodes for Current User <https://developer.spotify.com/documentation/web-api/reference/ save-episodes-user>`_: Save one or more episodes to the current user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the shows. **Maximum**: 50 IDs. **Example**: :code:`"77o6BIVlYM3msb4MMIL1jH,0Q86acNRm6V9GYx55SXKwf"`. """ self._check_scope("save_episodes", "user-library-modify") if isinstance(ids, str): self._request( "put", f"{self.API_URL}/me/episodes", params={"ids": ids} ) elif isinstance(ids, list): self._request( "put", f"{self.API_URL}/me/episodes", json={"ids": ids} )
[docs] def remove_saved_episodes(self, ids: Union[str, list[str]]) -> None: """ `Episodes > Remove User's Saved Episodes <https://developer.spotify.com/documentation/web-api/reference/ remove-episodes-user>`_: Remove one or more episodes from the current user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the episodes. **Maximum**: 50 IDs. **Example**: :code:`"77o6BIVlYM3msb4MMIL1jH,0Q86acNRm6V9GYx55SXKwf"`. """ self._check_scope("remove_saved_episodes", "user-library-modify") if isinstance(ids, str): self._request( "delete", f"{self.API_URL}/me/episodes", params={"ids": ids} ) elif isinstance(ids, list): self._request( "delete", f"{self.API_URL}/me/episodes", json={"ids": ids} )
[docs] def check_saved_episodes(self, ids: Union[str, list[str]]) -> list[bool]: """ `Episodes > Check User's Saved Episodes <https://developer.spotify.com/documentation/web-api/reference/ check-users-saved-episodes>`_: Check if one or more episodes is already saved in the current Spotify user's 'Your Episodes' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the episodes. **Maximum**: 50 IDs. **Example**: :code:`"77o6BIVlYM3msb4MMIL1jH,0Q86acNRm6V9GYx55SXKwf"`. Returns ------- contains : `list` Array of booleans specifying whether the episodes are found in the user's 'Liked Songs'. **Example**: :code:`[False, True]`. """ self._check_scope("check_saved_episodes", "user-library-read") return self._get_json( f"{self.API_URL}/me/episodes/contains", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )
### GENRES ################################################################
[docs] def get_genre_seeds(self) -> list[str]: """ `Genres > Get Available Genre Seeds <https://developer.spotify.com/documentation/web-api/reference/ get-recommendation-genres>`_: Retrieve a list of available genres seed parameter values for use in :meth:`get_recommendations`. Returns ------- genres : `list` Array of genres. **Example**: :code:`["acoustic", "afrobeat", ...]`. """ return self._get_json( f"{self.API_URL}/recommendations/available-genre-seeds" )["genres"]
### LIBRARY ###############################################################
[docs] def save_items(self, uris: Union[str, list[str]]) -> None: """ `Library > Save Items to Library <https://developer.spotify.com /documentation/web-api/reference/save-library-items>`_: Save one or more items to the current user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify`, :code:`user-follow-modify`, and :code:`playlist-modify-public` scopes. Parameters ---------- uris : `str` or `list` A (comma-separated) list of Spotify URIs for the items to save. Maximum: 40 URIs. """ self._check_scope("save_items", "user-library-modify") self._check_scope("save_items", "user-follow-modify") self._check_scope("save_items", "playlist-modify-public") self._request( "put", f"{self.API_URL}/me/library", params={"uris": ",".join(uris)}, )
[docs] def remove_saved_items(self, uris: Union[str, list[str]]) -> None: """ `Library > Remove Items from Library <https://developer.spotify.com/documentation/web-api/reference /remove-library-items>`_: Remove one or more items from the current user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify`, :code:`user-follow-modify`, and :code:`playlist-modify-public` scopes. Parameters ---------- uris : `str` or `list` A (comma-separated) list of Spotify URIs for the items to remove. Maximum: 40 URIs. """ self._check_scope("remove_saved_items", "user-library-modify") self._check_scope("remove_saved_items", "user-follow-modify") self._check_scope("remove_saved_items", "playlist-modify-public") self._request( "delete", f"{self.API_URL}/me/library", params={"uris": ",".join(uris)}, )
[docs] def are_items_saved(self, uris: Union[str, list[str]]) -> list[bool]: """ `Library > Check User's Saved Items <https://developer.spotify.com/documentation/web-api/reference /check-library-contains>`_: Check whether one or more items are saved in the current user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read`, :code:`user-follow-read`, and :code:`playlist-read-private` scopes. Parameters ---------- uris : `str` or `list` A (comma-separated) list of Spotify URIs for the items to check. Maximum: 40 URIs. Returns ------- contains : `list` Array of booleans specifying whether the items are found in the user's library. **Example**: :code:`[False, True]`. """ self._check_scope("are_items_saved", "user-library-read") self._check_scope("are_items_saved", "user-follow-read") self._check_scope("are_items_saved", "playlist-read-private") return self._get_json( f"{self.API_URL}/me/library/contains", params={"uris": ",".join(uris)}, )
### MARKETS ###############################################################
[docs] def get_markets(self) -> list[str]: """ `Markets > Get Available Markets <https://developer.spotify.com/ documentation/web-api/reference/ get-available-markets>`_: Get the list of markets where Spotify is available. Returns ------- markets : `list` Array of country codes. **Example**: :code:`["CA", "BR", "IT"]`. """ return self._get_json(f"{self.API_URL}/markets")["markets"]
### PLAYER ################################################################
[docs] def get_playback_state( self, *, market: str = None, additional_types: str = None ) -> dict[str, Any]: """ `Player > Get Playback State <https://developer.spotify.com/ documentation/web-api/reference/ get-information-about-the-users-current-playback>`_: Get information about the user's current playback state, including track or episode, progress, and active device. .. admonition:: Authorization scope :class: warning Requires the :code:`user-read-playback-state` scope. Parameters ---------- market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. additional_types : `str`, keyword-only, optional A comma-separated list of item types that your client supports besides the default track type. .. note:: This parameter was introduced to allow existing clients to maintain their current behavior and might be deprecated in the future. **Valid**: :code:`"track"` and :code:`"episode"`. Returns ------- state : `dict` Information about playback state. .. admonition:: Sample response :class: dropdown .. code:: { "device": { "id": <str>, "is_active": <bool>, "is_private_session": <bool>, "is_restricted": <bool>, "name": <str>, "type": <str>, "volume_percent": <int> }, "repeat_state": <str>, "shuffle_state": <bool>, "context": { "type": <str>, "href": <str>, "external_urls": { "spotify": <str> }, "uri": <str> }, "timestamp": <int>, "progress_ms": <int>, "is_playing": <bool>, "item": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> }, "currently_playing_type": <str>, "actions": { "interrupting_playback": <bool>, "pausing": <bool>, "resuming": <bool>, "seeking": <bool>, "skipping_next": <bool>, "skipping_prev": <bool>, "toggling_repeat_context": <bool>, "toggling_shuffle": <bool>, "toggling_repeat_track": <bool>, "transferring_playback": <bool> } } """ self._check_scope("get_playback_state", "user-read-playback-state") return self._get_json( f"{self.API_URL}/me/player", params={"market": market, "additional_types": additional_types}, )
[docs] def transfer_playback( self, device_ids: Union[str, list[str]], *, play: bool = None ) -> None: """ `Player > Transfer Playback <https://developer.spotify.com/ documentation/web-api/reference/transfer-a-users-playback>`_: Transfer playback to a new device and determine if it should start playing. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- device_ids : `str` or `list` The ID of the device on which playback should be started or transferred. .. note:: Although an array is accepted, only a single device ID is currently supported. Supplying more than one will return :code:`400 Bad Request`. **Example**: :code:`["74ASZWbe4lXaubB36ztrGX"]`. play : `bool` If :code:`True`, playback happens on the new device; if :code:`False` or not provided, the current playback state is kept. """ self._check_scope("transfer_playback", "user-modify-playback-state") json = { "device_ids": [device_ids] if isinstance(device_ids, str) else device_ids } if play is not None: json["play"] = play self._request("put", f"{self.API_URL}/me/player", json=json)
[docs] def get_devices(self) -> list[dict[str, Any]]: """ `Player > Get Available Devices <https://developer.spotify.com/ documentation/web-api/reference/ get-a-users-available-devices>`_: Get information about a user's available devices. .. admonition:: Authorization scope :class: warning Requires the :code:`user-read-playback-state` scope. Returns ------- devices : `list` A list containing information about the available devices. .. admonition:: Sample response :class: dropdown .. code:: [ { "devices": [ { "id": <str>, "is_active": <bool>, "is_private_session": <bool>, "is_restricted": <bool>, "name": <str>, "type": <str>, "volume_percent": <int> } ] } ] """ self._check_scope("get_devices", "user-read-playback-state") self._get_json(f"{self.API_URL}/me/player/devices")
[docs] def get_currently_playing( self, *, market: str = None, additional_types: str = None ) -> dict[str, Any]: """ `Player > Get Currently Playing Track <https://developer.spotify.com/documentation/web-api/reference/ get-the-users-currently-playing-track>`_: Get the object currently being played on the user's Spotify account. .. admonition:: Authorization scope :class: warning Requires the :code:`user-read-currently-playing` scope. Parameters ---------- market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. additional_types : `str`, keyword-only, optional A comma-separated list of item types that your client supports besides the default track type. .. note:: This parameter was introduced to allow existing clients to maintain their current behavior and might be deprecated in the future. **Valid**: :code:`"track"` and :code:`"episode"`. Returns ------- item : `dict` Information about the object currently being played. .. admonition:: Sample response :class: dropdown .. code:: { "device": { "id": <str>, "is_active": <bool>, "is_private_session": <bool>, "is_restricted": <bool>, "name": <str>, "type": <str>, "volume_percent": <int> }, "repeat_state": <str>, "shuffle_state": <bool>, "context": { "type": <str>, "href": <str>, "external_urls": { "spotify": <str> }, "uri": <str> }, "timestamp": <int>, "progress_ms": <int>, "is_playing": <bool>, "item": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> }, "currently_playing_type": <str>, "actions": { "interrupting_playback": <bool>, "pausing": <bool>, "resuming": <bool>, "seeking": <bool>, "skipping_next": <bool>, "skipping_prev": <bool>, "toggling_repeat_context": <bool>, "toggling_shuffle": <bool>, "toggling_repeat_track": <bool>, "transferring_playback": <bool> } } """ self._check_scope( "get_currently_playing_item", "user-read-currently-playing" ) self._get_json( f"{self.API_URL}/me/player/currently-playing", params={"market": market, "additional_types": additional_types}, )
[docs] def start_playback( self, *, device_id: str = None, context_uri: str = None, uris: list[str] = None, offset: dict[str, Any], position_ms: int = None, ) -> None: """ `Player > Start/Resume Playback <https://developer.spotify.com/ documentation/web-api/reference/start-a-users-playback>`_: Start a new context or resume current playback on the user's active device. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. context_uri : `str`, keyword-only, optional Spotify URI of the context to play. Only album, artist, and playlist contexts are valid. **Example**: :code:`"spotify:album:1Je1IMUlBXcx1Fz0WE7oPT"`. uris : `list`, keyword-only, optional A JSON array of the Spotify track URIs to play. **Example**: :code:`["spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M"]`. offset : `dict`, keyword-only, optional Indicates from where in the context playback should start. Only available when `context_uri` corresponds to an album or a playlist. .. container:: **Valid values**: * The value corresponding to the :code:`"position"` key is zero-based and can't be negative. * The value corresponding to the :code:`"uri"` key is a string representing the URI of the item to start at. **Examples**: * :code:`{"position": 5}` to start playback at the sixth item of the collection specified in `context_uri`. * :code:`{"uri": <str>}` to start playback at the item designated by the URI. position_ms : `int`, keyword-only, optional The position in milliseconds to seek to. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. **Valid values**: `position_ms` must be a positive number. """ self._check_scope("start_playback", "user-modify-playback-state") json = {} if context_uri is not None: json["context_uri"] = context_uri if uris is not None: json["uris"] = uris if offset is not None: json["offset"] = offset if position_ms is not None: json["position_ms"] = position_ms self._request( "put", f"{self.API_URL}/me/player/play", params={"device_id": device_id}, json=json, )
[docs] def pause_playback(self, *, device_id: str = None) -> None: """ `Player > Pause Playback <https://developer.spotify.com/ documentation/web-api/reference/pause-a-users-playback>`_: Pause playback on the user's account. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("pause_playback", "user-modify-playback-state") self._request( "put", f"{self.API_URL}/me/player/pause", params={"device_id": device_id}, )
[docs] def skip_to_next(self, *, device_id: str = None) -> None: """ `Player > Skip To Next <https://developer.spotify.com/ documentation/web-api/reference/ skip-users-playback-to-next-track>`_: Skips to next track in the user's queue. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("skip_to_next", "user-modify-playback-state") self._request( "post", f"{self.API_URL}/me/player/next", params={"device_id": device_id}, )
[docs] def skip_to_previous(self, *, device_id: str = None) -> None: """ `Player > Skip To Previous <https://developer.spotify.com/ documentation/web-api/reference/ skip-users-playback-to-previous-track>`_: Skips to previous track in the user's queue. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("skip_to_previous", "user-modify-playback-state") self._request( "post", f"{self.API_URL}/me/player/previous", params={"device_id": device_id}, )
[docs] def seek_to_position( self, position_ms: int, *, device_id: str = None ) -> None: """ `Player > Seek To Position <https://developer.spotify.com/ documentation/web-api/reference/ seek-to-position-in-currently-playing-track>`_: Seeks to the given position in the user's currently playing track. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- position_ms : `int` The position in milliseconds to seek to. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. **Valid values**: `position_ms` must be a positive number. **Example**: :code:`25000`. device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("seek_to_position", "user-modify-playback-state") self._request( "put", f"{self.API_URL}/me/player/seek", params={"position_ms": position_ms, "device_id": device_id}, )
[docs] def set_repeat_mode(self, state: str, *, device_id: str = None) -> None: """ `Player > Set Repeat Mode <https://developer.spotify.com/ documentation/web-api/reference/ set-repeat-mode-on-users-playback>`_: Set the repeat mode for the user's playback. Options are repeat-track, repeat-context, and off. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- state : `str` Repeat mode. .. container:: **Valid values**: * :code:`"track"` will repeat the current track. * :code:`"context"` will repeat the current context. * :code:`"off"` will turn repeat off. device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("set_repeat_mode", "user-modify-playback-state") self._request( "put", f"{self.API_URL}/me/player/repeat", params={"state": state, "device_id": device_id}, )
[docs] def set_playback_volume( self, volume_percent: int, *, device_id: str = None ) -> None: """ `Player > Set Playback Volume <https://developer.spotify.com/ documentation/web-api/reference/ set-volume-for-users-playback>`_: Set the volume for the user's current playback device. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- volume_percent : `int` The volume to set. **Valid values**: `volume_percent` must be a value from 0 to 100, inclusive. **Example**: :code:`50`. device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("set_playback_volume", "user-modify-playback-state") self._request( "put", f"{self.API_URL}/me/player/volume", params={"volume_percent": volume_percent, "device_id": device_id}, )
[docs] def toggle_playback_shuffle( self, state: bool, *, device_id: str = None ) -> None: """ `Player > Toggle Playback Shuffle <https://developer.spotify.com/documentation/web-api/reference/ toggle-shuffle-for-users-playback>`_: Toggle shuffle on or off for user's playback. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- state : `bool` Shuffle mode. If :code:`True`, shuffle the user's playback. device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope( "toggle_playback_shuffle", "user-modify-playback-state" ) self._request( "put", f"{self.API_URL}/me/player/shuffle", params={"state": state, "device_id": device_id}, )
[docs] def get_recently_played( self, *, limit: int = None, after: int = None, before: int = None ) -> dict[str, Any]: """ `Player > Get Recently Played Tracks <https://developer.spotify.com/documentation/web-api/reference/ get-recently-played>`_: Get tracks from the current user's recently played tracks. .. note:: Currently doesn't support podcast episodes. .. admonition:: Authorization scope :class: warning Requires the :code:`user-read-recently-played` scope. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. after : `int`, keyword-only, optional A Unix timestamp in milliseconds. Returns all items after (but not including) this cursor position. If `after` is specified, `before` must not be specified. **Example**: :code:`1484811043508`. before : `int`, keyword-only, optional A Unix timestamp in milliseconds. Returns all items before (but not including) this cursor position. If `before` is specified, `after` must not be specified. Returns ------- tracks : `dict` A dictionary containing Spotify catalog information for the recently played tracks and the number of results returned. .. admonition:: Sample :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "cursors": { "after": <str>, "before": <str> }, "total": <int>, "items": [ { "track": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> }, "played_at": <str>, "context": { "type": <str>, "href": <str>, "external_urls": { "spotify": <str> }, "uri": <str> } } ] } """ self._check_scope( "get_recently_played_tracks", "user-read-recently-played" ) return self._get_json( f"{self.API_URL}/me/player/recently-played", params={"limit": limit, "after": after, "before": before}, )
[docs] def get_queue(self) -> dict[str, Any]: """ `Player > Get the User's Queue <https://developer.spotify.com/ documentation/web-api/reference/get-queue>`_: Get the list of objects that make up the user's queue. .. admonition:: Authorization scope :class: warning Requires the :code:`user-read-playback-state` scope. Returns ------- queue : `dict` Information about the user's queue, such as the currently playing item and items in the queue. .. admonition:: Sample :class: dropdown .. code:: { "currently_playing": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> }, "queue": [ { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } ] } """ self._check_scope("get_user_queue", "user-read-playback-state") return self._get_json(f"{self.API_URL}/me/player/queue")
[docs] def add_to_queue(self, uri: str, *, device_id: str = None) -> None: """ `Player > Add Item to Playback Queue <https://developer.spotify.com/documentation/web-api/reference/ add-to-queue>`_: Add an item to the end of the user's current playback queue. .. admonition:: Authorization scope :class: warning Requires the :code:`user-modify-playback-state` scope. Parameters ---------- uri : `str` The URI of the item to add to the queue. Must be a track or an episode URL. device_id : `str`, keyword-only, optional The ID of the device this method is targeting. If not supplied, the user's currently active device is the target. **Example**: :code:`"0d1841b0976bae2a3a310dd74c0f3df354899bc8"`. """ self._check_scope("add_queue_item", "user-modify-playback-state") self._request( "post", f"{self.API_URL}/me/player/queue", params={"uri": uri, "device_id": device_id}, )
### PLAYLISTS #############################################################
[docs] def get_playlist( self, playlist_id: str, *, additional_types: Union[str, list[str]] = None, fields: str = None, market: str = None, ) -> dict[str, Any]: """ `Playlists > Get Playlist <https://developer.spotify.com/ documentation/web-api/reference/get-playlist>`_: Get a playlist owned by a Spotify user. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. additional_types : `str` or `list`, keyword-only, optional A (comma-separated) list of item types besides the default track type. .. note:: This parameter was introduced to allow existing clients to maintain their current behavior and might be deprecated in the future. **Valid values**: :code:`"track"` and :code:`"episode"`. fields : `str` or `list`, keyword-only, optional Filters for the query: a (comma-separated) list of the fields to return. If omitted, all fields are returned. A dot separator can be used to specify non-reoccurring fields, while parentheses can be used to specify reoccurring fields within objects. Use multiple parentheses to drill down into nested objects. Fields can be excluded by prefixing them with an exclamation mark. .. container:: **Examples**: * :code:`"description,uri"` to get just the playlist's description and URI, * :code:`"tracks.items(added_at,added_by.id)"` to get just the added date and user ID of the adder, * :code:`"tracks.items(track(name,href,album(name,href)))"` to drill down into the album, and * :code:`"tracks.items(track(name,href,album(!name,href)))"` to exclude the album name. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- playlist : `dict` Spotify catalog information for a single playlist. .. admonition:: Sample response :class: dropdown .. code:: { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "owner": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str>, "display_name": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "track": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } } ] }, "type": <str>, "uri": <str> } """ return self._get_json( f"{self.API_URL}/playlists/{playlist_id}", params={ "additional_types": ( additional_types if additional_types is None or isinstance(additional_types, str) else ",".join(additional_types) ), "fields": ( fields if fields is None or isinstance(fields, str) else ",".join(fields) ), "market": market, }, )
[docs] def change_playlist_details( self, playlist_id: str, *, name: str = None, public: bool = None, collaborative: bool = None, description: str = None, ) -> None: """ `Playlists > Change Playlist Details <https://developer.spotify.com/documentation/web-api/reference/ change-playlist-details>`_: Change a playlist's name and public/private state. (The user must, of course, own the playlist.) .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-public` or the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. name : `str`, keyword-only, optional The new name for the playlist. **Example**: :code:`"My New Playlist Title"`. public : `bool`, keyword-only, optional If :code:`True`, the playlist will be public. If :code:`False`, it will be private. collaborative : `bool`, keyword-only, optional If :code:`True`, the playlist will become collaborative and other users will be able to modify the playlist in their Spotify client. .. note:: You can only set :code:`collaborative=True` on non-public playlists. description : `str`, keyword-only, optional Value for playlist description as displayed in Spotify clients and in the Web API. """ self._check_scope( "change_playlist_details", "playlist-modify-" + ( "public" if self.get_playlist(playlist_id)["public"] else "private" ), ) json = {} if name is not None: json["name"] = name if public is not None: json["public"] = public if collaborative is not None: json["collaborative"] = collaborative if description is not None: json["description"] = description self._request( "put", f"{self.API_URL}/playlists/{playlist_id}", json=json )
[docs] def get_playlist_items( self, playlist_id: str, *, additional_types: Union[str, list[str]] = None, fields: str = None, limit: int = None, market: str = None, offset: int = None, ) -> dict[str, Any]: """ `Playlists > Get Playlist Items <https://developer.spotify.com/ documentation/web-api/reference/ get-playlists-tracks>`_: Get full details of the items of a playlist owned by a Spotify user. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. additional_types : `str` or `list`, keyword-only, optional A (comma-separated) list of item types besides the default track type. .. note:: This parameter was introduced to allow existing clients to maintain their current behavior and might be deprecated in the future. **Valid values**: :code:`"track"` and :code:`"episode"`. fields : `str` or `list`, keyword-only, optional Filters for the query: a (comma-separated) list of the fields to return. If omitted, all fields are returned. A dot separator can be used to specify non-reoccurring fields, while parentheses can be used to specify reoccurring fields within objects. Use multiple parentheses to drill down into nested objects. Fields can be excluded by prefixing them with an exclamation mark. .. container:: **Examples**: * :code:`"description,uri"` to get just the playlist's description and URI, * :code:`"tracks.items(added_at,added_by.id)"` to get just the added date and user ID of the adder, * :code:`"tracks.items(track(name,href,album(name,href)))"` to drill down into the album, and * :code:`"tracks.items(track(name,href,album(!name,href)))"` to exclude the album name. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- items : `dict` A dictionary containing Spotify catalog information for the playlist items and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "track": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } } ] } """ self._check_scope("get_playlist_item", "playlist-modify-private") return self._get_json( f"{self.API_URL}/playlists/{playlist_id}/items", params={ "additional_types": ( additional_types if additional_types is None or isinstance(additional_types, str) else ",".join(additional_types) ), "fields": ( fields if fields is None or isinstance(fields, str) else ",".join(fields) ), "limit": limit, "market": market, "offset": offset, }, )
[docs] def add_playlist_items( self, playlist_id: str, uris: Union[str, list[str]], *, position: int = None, ) -> str: """ `Playlists > Add Items to Playlist <https://developer.spotify.com/documentation/web-api/reference/ add-tracks-to-playlist>`_: Add one or more items to a user's playlist. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-public` or the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. uris : `str` or `list`, keyword-only, optional A (comma-separated) list of Spotify URIs to add; can be track or episode URIs. A maximum of 100 items can be added in one request. .. note:: It is likely that passing a large number of item URIs as a query parameter will exceed the maximum length of the request URI. When adding a large number of items, it is recommended to pass them in the request body (as a `list`). **Example**: :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7Rh, spotify:track:1301WleyT98MSxVHPZCA6M, spotify:episode:512ojhOuo1ktJprKbVcKyQ"`. position : `int`, keyword-only, optional The position to insert the items, a zero-based index. If omitted, the items will be appended to the playlist. Items are added in the order they are listed in the query string or request body. .. container:: **Examples**: * :code:`0` to insert the items in the first position. * :code:`2` to insert the items in the third position. Returns ------- snapshot_id : `str` The updated playlist's snapshot ID. """ self._check_scope( "add_playlist_details", "playlist-modify-" + ( "public" if self.get_playlist(playlist_id)["public"] else "private" ), ) if isinstance(uris, str): url = f"{self.API_URL}/playlists/{playlist_id}/items?{uris=}" if position is not None: url += f"{position=}" return self._request("post", url).json()["snapshot_id"] elif isinstance(uris, list): json = {"uris": uris} if position is not None: json["position"] = position self._request( "post", f"{self.API_URL}/playlists/{playlist_id}/items", json=json, ).json()["snapshot_id"]
[docs] def update_playlist_items( self, playlist_id: str, *, uris: Union[str, list[str]] = None, range_start: int = None, insert_before: int = None, range_length: int = 1, snapshot_id: str = None, ) -> str: """ `Playlists > Update Playlist Items <https://developer.spotify.com/documentation/web-api/reference/ reorder-or-replace-playlists-tracks>`_: Either reorder or replace items in a playlist depending on the request's parameters. To reorder items, include `range_start`, `insert_before`, `range_length`, and `snapshot_id` as keyword arguments. To replace items, include `uris` as a keyword argument. Replacing items in a playlist will overwrite its existing items. This operation can be used for replacing or clearing items in a playlist. .. note:: Replace and reorder are mutually exclusive operations which share the same endpoint, but have different parameters. These operations can't be applied together in a single request. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-public` or the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. uris : `str` or `list`, keyword-only, optional A (comma-separated) list of Spotify URIs to add; can be track or episode URIs. A maximum of 100 items can be added in one request. .. note:: It is likely that passing a large number of item URIs as a query parameter will exceed the maximum length of the request URI. When adding a large number of items, it is recommended to pass them in the request body (as a `list`). **Example**: :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7Rh, spotify:track:1301WleyT98MSxVHPZCA6M, spotify:episode:512ojhOuo1ktJprKbVcKyQ"`. range_start : `int`, keyword-only, optional The position of the first item to be reordered. insert_before : `int`, keyword-only, optional The position where the items should be inserted. To reorder the items to the end of the playlist, simply set `insert_before` to the position after the last item. .. container:: **Examples**: * :code:`range_start=0, insert_before=10` to reorder the first item to the last position in a playlist with 10 items, and * :code:`range_start=9, insert_before=0` to reorder the last item in a playlist with 10 items to the start of the playlist. range_length : `int`, keyword-only, default: :code:`1` The amount of items to be reordered. The range of items to be reordered begins from the `range_start` position, and includes the `range_length` subsequent items. **Example**: :code:`range_start=9, range_length=2` to move the items at indices 9–10 to the start of the playlist. snapshot_id : `str`, keyword-only, optional The playlist's snapshot ID against which you want to make the changes. Returns ------- snapshot_id : `str` The updated playlist's snapshot ID. """ self._check_scope( "update_playlist_details", "playlist-modify-" + ( "public" if self.get_playlist(playlist_id)["public"] else "private" ), ) json = {} if snapshot_id is not None: json["snapshot_id"] = snapshot_id if uris is None: if range_start is not None: json["range_start"] = range_start if insert_before is not None: json["insert_before"] = insert_before if range_length is not None: json["range_length"] = range_length return self._request( "put", f"{self.API_URL}/playlists/{playlist_id}/items", json=json, ).json()["snapshot_id"] elif isinstance(uris, str): return self._request( "put", f"{self.API_URL}/playlists/{playlist_id}/items?uris={uris}", json=json, ).json()["snapshot_id"] elif isinstance(uris, list): return self._request( "put", f"{self.API_URL}/playlists/{playlist_id}/items", json={"uris": uris} | json, ).json()["snapshot_id"]
[docs] def remove_playlist_items( self, playlist_id: str, tracks: list[str], *, snapshot_id: str = None ) -> str: """ `Playlists > Remove Playlist Items <https://developer.spotify.com/documentation/web-api/reference/ remove-tracks-playlist>`_: Remove one or more items from a user's playlist. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-public` or the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. tracks : `list` A (comma-separated) list containing Spotify URIs of the tracks or episodes to remove. **Maximum**: 100 items can be added in one request. **Example**: :code:`"spotify:track:4iV5W9uYEdYUVa79Axb7Rh, spotify:track:1301WleyT98MSxVHPZCA6M, spotify:episode:512ojhOuo1ktJprKbVcKyQ"`. snapshot_id : `str`, keyword-only, optional The playlist's snapshot ID against which you want to make the changes. The API will validate that the specified items exist and in the specified positions and make the changes, even if more recent changes have been made to the playlist. Returns ------- snapshot_id : `str` The updated playlist's snapshot ID. """ self._check_scope( "remove_playlist_items", "playlist-modify-" + ( "public" if self.get_playlist(playlist_id)["public"] else "private" ), ) json = {"tracks": tracks} if snapshot_id is not None: json["snapshot_id"] = snapshot_id return self._request( "delete", f"{self.API_URL}/playlists/{playlist_id}/items", json=json, ).json()["snapshot_id"]
[docs] def get_personal_playlists( self, *, limit: int = None, offset: int = None ) -> dict[str, Any]: """ `Playlist > Get Current User's Playlists <https://developer.spotify.com/documentation/web-api/reference/ get-a-list-of-current-users-playlists>`_: Get a list of the playlists owned or followed by the current Spotify user. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-read-private` and the :code:`playlist-read-collaborative` scopes. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- playlists : `dict` A dictionary containing the current user's playlists and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "owner": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str>, "display_name": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "total": <int> }, "type": <str>, "uri": <str> } ] } """ self._check_scope("get_personal_playlists", "playlist-read-private") self._check_scope( "get_personal_playlists", "playlist-read-collaborative" ) return self._get_json( f"{self.API_URL}/me/playlists", params={"limit": limit, "offset": offset}, )
[docs] def get_user_playlists( self, user_id: str, *, limit: int = None, offset: int = None ) -> dict[str, Any]: """ `Playlist > Get User's Playlists <https://developer.spotify.com/documentation/web-api/reference/ get-list-users-playlists>`_: Get a list of the playlists owned or followed by a Spotify user. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-read-private` and the :code:`playlist-read-collaborative` scopes. Parameters ---------- user_id : `str` The user's Spotify user ID. **Example**: :code:`"smedjan"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- playlists : `dict` A dictionary containing the user's playlists and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "owner": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str>, "display_name": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "total": <int> }, "type": <str>, "uri": <str> } ] } """ self._check_scope("get_user_playlists", "playlist-read-private") self._check_scope("get_user_playlists", "playlist-read-collaborative") return self._get_json( f"{self.API_URL}/users/{user_id}/playlists", params={"limit": limit, "offset": offset}, )
[docs] def create_playlist( self, name: str, *, public: bool = True, collaborative: bool = None, description: str = None, ) -> dict[str, Any]: """ `Playlists > Create Playlist <https://developer.spotify.com/ documentation/web-api/reference/create-playlist>`_: Create a playlist for a Spotify user. (The playlist will be empty until you add tracks.) .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-public` or the :code:`playlist-modify-private` scope. Parameters ---------- name : `str` The name for the new playlist. This name does not need to be unique; a user may have several playlists with the same name. **Example**: :code:`"Your Coolest Playlist"`. public : `bool`, keyword-only, default: `True` If :code:`True`, the playlist will be public; if :code:`False`, it will be private. .. note:: To be able to create private playlists, the user must have granted the :code:`playlist-modify-private` scope. collaborative : `bool`, keyword-only, optional If :code:`True`, the playlist will be collaborative. .. note:: To create a collaborative playlist, you must also set `public` to :code:`False`. To create collaborative playlists, you must have granted the :code:`playlist-modify-private` and :code:`playlist-modify-public` scopes. **Default**: :code:`False`. description : `str`, keyword-only, optional The playlist description, as displayed in Spotify Clients and in the Web API. Returns ------- playlist : `dict` Spotify catalog information for the newly created playlist. .. admonition:: Sample response :class: dropdown .. code:: { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "owner": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str>, "display_name": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "added_by": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str> }, "is_local": <bool>, "track": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } } ] }, "type": <str>, "uri": <str> } """ self._check_scope( "create_playlist", "playlist-modify-" + ("public" if public else "private"), ) json = {"name": name, "public": public} if collaborative is not None: json["collaborative"] = collaborative if description is not None: json["description"] = description return self._request( "post", f"{self.API_URL}/users/{self._user_id}/playlists", json=json, ).json()
[docs] def get_category_playlists( self, category_id: str, *, country: str = None, limit: int = None, offset: int = None, ) -> dict[str, Any]: """ `Playlists > Get Category's Playlists <https://developer.spotify.com/documentation/web-api/reference/ get-a-categories-playlists>`_: Get a list of Spotify playlists tagged with a particular category. Parameters ---------- category_id : `str` The Spotify category ID for the category. **Example**: :code:`"dinner"`. country : `str`, keyword-only, optional A country: an ISO 3166-1 alpha-2 country code. Provide this parameter to ensure that the category exists for a particular country. **Example**: :code:`"SE"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- playlists : `dict` A dictionary containing a message and a list of playlists in a particular category. .. admonition:: Sample response :class: dropdown .. code:: { "message": <str>, "playlists": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "owner": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str>, "display_name": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "total": <int> }, "type": <str>, "uri": <str> } ] } } """ return self._get_json( f"{self.API_URL}/browse/categories/{category_id}/playlists", params={"country": country, "limit": limit, "offset": offset}, )
[docs] def get_playlist_cover_image(self, playlist_id: str) -> dict[str, Any]: """ `Playlists > Get Playlist Cover Image <https://developer.spotify.com/documentation/web-api/reference/ get-playlist-cover>`_: Get the current image associated with a specific playlist. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. Returns ------- image : `dict` A dictionary containing the URL to and the dimensions of the playlist cover image. .. admonition:: Sample response :class: dropdown .. code:: { "url": <str>, "height": <int>, "width": <int> } """ return self._get_json( f"{self.API_URL}/playlists/{playlist_id}/images" )[0]
[docs] def add_playlist_cover_image(self, playlist_id: str, image: bytes) -> None: """ `Playlists > Add Custom Playlist Cover Image <https://developer.spotify.com/documentation/web-api/reference/ upload-custom-playlist-cover>`_: Replace the image used to represent a specific playlist. .. admonition:: Authorization scope :class: warning Requires the :code:`ugc-image-upload` and the :code:`playlist-modify-public` or :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. image : `bytes` Base64-encoded JPEG image data. The maximum payload size is 256 KB. """ self._check_scope("get_categories", "ugc-image-upload") self._check_scope( "get_categories", "playlist-modify-" + ( "public" if self.get_playlist(playlist_id)["public"] else "private" ), ) self._request( "put", f"{self.API_URL}/playlists/{playlist_id}/images", data=image, headers={"Content-Type": "image/jpeg"}, )
### SEARCH ################################################################
[docs] def search( self, q: str, type: Union[str, list[str]], *, limit: int = None, market: str = None, offset: int = None, ) -> dict[str, Any]: """ `Search > Search for Item <https://developer.spotify.com/ documentation/web-api/reference/search>`_: Get Spotify catalog information about albums, artists, playlists, tracks, shows, episodes or audiobooks that match a keyword string. Parameters ---------- q : `str` Your search query. .. note:: You can narrow down your search using field filters. The available filters are :code:`album`, :code:`artist`, :code:`track`, :code:`year`, :code:`upc`, :code:`tag:hipster`, :code:`tag:new`, :code:`isrc`, and :code:`genre`. Each field filter only applies to certain result types. The :code:`artist` and :code:`year` filters can be used while searching albums, artists and tracks. You can filter on a single :code:`year` or a range (e.g. 1955-1960). The :code:`album` filter can be used while searching albums and tracks. The :code:`genre` filter can be used while searching artists and tracks. The :code:`isrc` and :code:`track` filters can be used while searching tracks. The :code:`upc`, :code:`tag:new` and :code:`tag:hipster` filters can only be used while searching albums. The :code:`tag:new` filter will return albums released in the past two weeks and :code:`tag:hipster` can be used to return only albums with the lowest 10% popularity. **Example**: :code:`"remaster track:Doxy artist:Miles Davis"`. type : `str` or `list` A comma-separated list of item types to search across. Search results include hits from all the specified item types. **Valid values**: :code:`"album"`, :code:`"artist"`, :code:`"audiobook"`, :code:`"episode"`, :code:`"playlist"`, :code:`"show"`, and :code:`"track"`. .. container:: **Example**: * :code:`"track,artist"` returns both tracks and artists matching `query`. * :code:`type=album,track` returns both albums and tracks matching `query`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- results : `dict` The search results. .. admonition:: Sample :class: dropdown .. code:: { "tracks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } ] }, "artists": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ] }, "albums": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] } ] }, "playlists": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "collaborative": <bool>, "description": <str>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "owner": { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "type": "user", "uri": <str>, "display_name": <str> }, "public": <bool>, "snapshot_id": <str>, "tracks": { "href": <str>, "total": <int> }, "type": <str>, "uri": <str> } ] }, "shows": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [<str>], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int> } ] }, "episodes": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "audio_preview_url": <str>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool> "language": <str>, "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> } } ] }, "audiobooks": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "authors": [ { "name": <str> } ], "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "edition": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "languages": [<str>], "media_type": <str>, "name": <str>, "narrators": [ { "name": <str> } ], "publisher": <str>, "type": "audiobook", "uri": <str>, "total_chapters": <int> } ] } } """ return self._get_json( f"{self.API_URL}/search?q={urllib.parse.quote(q)}", params={ "type": type if isinstance(type, str) else ",".join(type), "limit": limit, "market": market, "offset": offset, }, )[f"{type}s"]
### SHOWS #################################################################
[docs] def get_show(self, id: str, *, market: str = None) -> dict[str, Any]: """ `Shows > Get Show <https://developer.spotify.com/documentation/ web-api/reference/get-a-show>`_: Get Spotify catalog information for a single show identified by its unique Spotify ID. Parameters ---------- id : `str` The Spotify ID for the show. **Example**: :code:`"38bS44xjbVVZ3No3ByF1dJ"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- show : `dict` Spotify catalog information for a single show. .. admonition:: Sample response :class: dropdown .. code:: { "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [<str>], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int>, "episodes": { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "audio_preview_url": <str>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool> "language": <str>, "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> } } ] } } """ return self._get_json( f"{self.API_URL}/shows/{id}", params={"market": market} )
[docs] def get_shows( self, ids: Union[str, list[str]], *, market: str = None ) -> list[dict[str, Any]]: """ `Shows > Get Several Shows <https://developer.spotify.com/ documentation/web-api/reference/ get-multiple-shows>`_: Get Spotify catalog information for several shows based on their Spotify IDs. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the shows. **Maximum**: 50 IDs. **Example**: :code:`"5CfCWKI5pZ28U0uOzXkDHe,5as3aKmN2k11yfDDDSrvaZ"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- shows : `list` A list containing Spotify catalog information for multiple shows. .. admonition:: Sample response :class: dropdown .. code:: [ { "available_markets": [<str>], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [<str>], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int> } ] """ return self._get_json( f"{self.API_URL}/shows", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )["shows"]
[docs] def get_show_episodes( self, id: str, *, limit: int = None, market: str = None, offset: int = None, ) -> dict[str, Any]: """ `Shows > Get Show Episodes <https://developer.spotify.com/ documentation/web-api/reference/ get-a-shows-episodes>`_: Get Spotify catalog information about an show's episodes. Optional parameters can be used to limit the number of episodes returned. Parameters ---------- id : `str` The Spotify ID for the show. **Example**: :code:`"38bS44xjbVVZ3No3ByF1dJ"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. Returns ------- episodes : `dict` A dictionary containing Spotify catalog information for a show's episodes and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "audio_preview_url": <str>, "description": <str>, "html_description": <str>, "duration_ms": <int>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "is_playable": <bool> "language": <str>, "languages": [<str>], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "resume_point": { "fully_played": <bool>, "resume_position_ms": <int> }, "type": "episode", "uri": <str>, "restrictions": { "reason": <str> } } ] } """ return self._get_json( f"{self.API_URL}/shows/{id}/episodes", params={"limit": limit, "market": market, "offset": offset}, )
[docs] def get_saved_shows( self, *, limit: int = None, offset: int = None ) -> dict[str, Any]: """ `Shows > Get User's Saved Shows <https://developer.spotify.com/ documentation/web-api/reference/ get-users-saved-shows>`_: Get a list of shows saved in the current Spotify user's library. Optional parameters can be used to limit the number of shows returned. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- shows : `dict` A dictionary containing Spotify catalog information for a user's saved shows and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "show": { "available_markets": [ <str> ], "copyrights": [ { "text": <str>, "type": <str> } ], "description": <str>, "html_description": <str>, "explicit": <bool>, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "is_externally_hosted": <bool>, "languages": [ <str> ], "media_type": <str>, "name": <str>, "publisher": <str>, "type": "show", "uri": <str>, "total_episodes": <int> } } ] } """ self._check_scope("get_saved_shows", "user-library-read") return self._get_json( f"{self.API_URL}/me/shows", params={"limit": limit, "offset": offset}, )
[docs] def save_shows(self, ids: Union[str, list[str]]) -> None: """ `Shows > Save Shows for Current User <https://developer.spotify.com/documentation/web-api/reference/ save-shows-user>`_: Save one or more shows to current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the shows. Maximum: 50 IDs. **Example**: :code:`"5CfCWKI5pZ28U0uOzXkDHe,5as3aKmN2k11yfDDDSrvaZ"`. """ self._check_scope("save_shows", "user-library-modify") self._request( "put", f"{self.API_URL}/me/shows", params={ "ids": f"{ids if isinstance(ids, str) else ','.join(ids)}" }, )
[docs] def remove_saved_shows( self, ids: Union[str, list[str]], *, market: str = None ) -> None: """ `Shows > Remove User's Saved Shows <https://developer.spotify.com/documentation/web-api/reference/ remove-shows-user>`_: Delete one or more shows from current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the shows. Maximum: 50 IDs. **Example**: :code:`"5CfCWKI5pZ28U0uOzXkDHe,5as3aKmN2k11yfDDDSrvaZ"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. """ self._check_scope("remove_saved_shows", "user-library-modify") self._request( "delete", f"{self.API_URL}/me/shows", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )
[docs] def check_saved_shows(self, ids: Union[str, list[str]]) -> list[bool]: """ `Shows > Check User's Saved Shows <https://developer.spotify.com/documentation/web-api/reference/ check-users-saved-shows>`_: Check if one or more shows is already saved in the current Spotify user's library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the shows. **Maximum**: 50 IDs. **Example**: :code:`"5CfCWKI5pZ28U0uOzXkDHe,5as3aKmN2k11yfDDDSrvaZ"`. Returns ------- contains : `list` Array of booleans specifying whether the shows are found in the user's saved shows. **Example**: :code:`[False, True]`. """ self._check_scope("check_saved_shows", "user-library-read") return self._get_json( f"{self.API_URL}/me/shows/contains", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )
### TRACKS ################################################################
[docs] def get_track(self, id: str, *, market: str = None) -> dict[str, Any]: """ `Tracks > Get Track <https://developer.spotify.com/ documentation/web-api/reference/get-track>`_: Get Spotify catalog information for a single track identified by its unique Spotify ID. Parameters ---------- id : `str` The Spotify ID for the track. **Example**: :code:`"11dFghVXANMlKmJXsNCbNl"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- track : `dict` Spotify catalog information for a single track. .. admonition:: Sample response :class: dropdown .. code:: { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } """ return self._get_json( f"{self.API_URL}/tracks/{id}", params={"market": market} )
[docs] def get_tracks( self, ids: Union[int, str, list[Union[int, str]]], *, market: str = None, ) -> list[dict[str, Any]]: """ `Tracks > Get Several Tracks <https://developer.spotify.com/ documentation/web-api/reference/ get-several-tracks>`_: Get Spotify catalog information for multiple tracks based on their Spotify IDs. Parameters ---------- ids : `int`, `str`, or `list` A (comma-separated) list of the Spotify IDs for the tracks. **Maximum**: 50 IDs. **Example**: :code:`"7ouMYWpwJ422jRcDASZB7P, 4VqPOruhp5EdPBeR92t6lQ, 2takcwOaAZWiXQijPHIx7B"`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. Returns ------- tracks : `dict` or `list` A list containing Spotify catalog information for multiple tracks. .. admonition:: Sample response :class: dropdown .. code:: [ { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } ] """ return self._get_json( f"{self.API_URL}/tracks", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "market": market, }, )["tracks"]
[docs] def get_saved_tracks( self, *, limit: int = None, market: str = None, offset: int = None ) -> dict[str, Any]: """ `Tracks > Get User's Saved Tracks <https://developer.spotify.com/documentation/web-api/reference/ get-users-saved-tracks>`_: Get a list of the songs saved in the current Spotify user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Valid values**: `offset` must be between 0 and 1,000. **Default**: :code:`0`. Returns ------- tracks : `dict` A dictionary containing Spotify catalog information for a user's saved tracks and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "added_at": <str>, "track": { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } } ] } """ self._check_scope("get_saved_tracks", "user-library-read") return self._get_json( f"{self.API_URL}/me/tracks", params={"limit": limit, "market": market, "offset": offset}, )
[docs] def save_tracks(self, ids: Union[str, list[str]]) -> None: """ `Tracks > Save Track for Current User <https://developer.spotify.com/documentation/web-api/reference/ save-tracks-user>`_: Save one or more tracks to the current user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the tracks. **Maximum**: 50 IDs. **Example**: :code:`"7ouMYWpwJ422jRcDASZB7P, 4VqPOruhp5EdPBeR92t6lQ, 2takcwOaAZWiXQijPHIx7B"`. """ self._check_scope("save_tracks", "user-library-modify") if isinstance(ids, str): self._request( "put", f"{self.API_URL}/me/tracks", params={"ids": ids} ) elif isinstance(ids, list): self._request( "put", f"{self.API_URL}/me/tracks", json={"ids": ids} )
[docs] def remove_saved_tracks(self, ids: Union[str, list[str]]) -> None: """ `Tracks > Remove User's Saved Tracks <https://developer.spotify.com/documentation/web-api/reference/ remove-tracks-user>`_: Remove one or more tracks from the current user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the tracks. **Maximum**: 50 IDs. **Example**: :code:`"7ouMYWpwJ422jRcDASZB7P, 4VqPOruhp5EdPBeR92t6lQ, 2takcwOaAZWiXQijPHIx7B"`. """ self._check_scope("remove_saved_tracks", "user-library-modify") if isinstance(ids, str): self._request( "delete", f"{self.API_URL}/me/tracks", params={"ids": ids} ) elif isinstance(ids, list): self._request( "delete", f"{self.API_URL}/me/tracks", json={"ids": ids} )
[docs] def check_saved_tracks(self, ids: Union[str, list[str]]) -> list[bool]: """ `Tracks > Check User's Saved Tracks <https://developer.spotify.com/documentation/web-api/reference/ check-users-saved-tracks>`_: Check if one or more tracks is already saved in the current Spotify user's 'Your Music' library. .. admonition:: Authorization scope :class: warning Requires the :code:`user-library-read` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the tracks. **Maximum**: 50 IDs. **Example**: :code:`"7ouMYWpwJ422jRcDASZB7P, 4VqPOruhp5EdPBeR92t6lQ, 2takcwOaAZWiXQijPHIx7B"`. Returns ------- contains : `list` Array of booleans specifying whether the tracks are found in the user's 'Liked Songs'. **Example**: :code:`[False, True]`. """ self._check_scope("check_saved_tracks", "user-library-read") return self._get_json( f"{self.API_URL}/me/tracks/contains", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )
[docs] def get_track_audio_features(self, id: str) -> dict[str, Any]: """ `Tracks > Get Track's Audio Features <https://developer.spotify.com/documentation/web-api/reference/ get-audio-features>`_: Get audio feature information for a single track identified by its unique Spotify ID. Parameters ---------- id : `str` The Spotify ID of the track. **Example**: :code:`"11dFghVXANMlKmJXsNCbNl"`. Returns ------- audio_features : `dict` The track's audio features. .. admonition:: Sample response :class: dropdown .. code:: { "acousticness": <float>, "analysis_url": <str>, "danceability": <float>, "duration_ms": <int>, "energy": <float>, "id": <str>, "instrumentalness": <float>, "key": <int>, "liveness": <float>, "loudness": <float>, "mode": <int>, "speechiness": <float>, "tempo": <float>, "time_signature": <int>, "track_href": <str>, "type": "audio_features", "uri": <str>, "valence": <float>, } """ return self._get_json(f"{self.API_URL}/audio-features/{id}")
[docs] def get_tracks_audio_features( self, ids: Union[str, list[str]] ) -> list[dict[str, Any]]: """ `Tracks > Get Tracks' Audio Features <https://developer.spotify.com/documentation/web-api/reference/ get-several-audio-features>`_: Get audio features for multiple tracks based on their Spotify IDs. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the Spotify IDs for the tracks. **Maximum**: 100 IDs. **Example**: :code:`"7ouMYWpwJ422jRcDASZB7P, 4VqPOruhp5EdPBeR92t6lQ, 2takcwOaAZWiXQijPHIx7B"`. Returns ------- audio_features : `dict` or `list` A list containing audio features for multiple tracks. .. admonition:: Sample response :class: dropdown .. code:: [ { "acousticness": <float>, "analysis_url": <str>, "danceability": <float>, "duration_ms": <int>, "energy": <float>, "id": <str>, "instrumentalness": <float>, "key": <int>, "liveness": <float>, "loudness": <float>, "mode": <int>, "speechiness": <float>, "tempo": <float>, "time_signature": <int>, "track_href": <str>, "type": "audio_features", "uri": <str>, "valence": <float>, } ] """ return self._get_json( f"{self.API_URL}/audio-features", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )["audio_features"]
[docs] def get_track_audio_analysis(self, id: str) -> dict[str, Any]: """ `Tracks > Get Track's Audio Analysis <https://developer.spotify.com/documentation/web-api/reference/ get-audio-analysis>`_: Get a low-level audio analysis for a track in the Spotify catalog. The audio analysis describes the track's structure and musical content, including rhythm, pitch, and timbre. Parameters ---------- id : `str` The Spotify ID of the track. **Example**: :code:`"11dFghVXANMlKmJXsNCbNl"`. Returns ------- audio_analysis : `dict` The track's audio analysis. .. admonition:: Sample response :class: dropdown .. code:: { "meta": { "analyzer_version": <str>, "platform": <str>, "detailed_status": <str>, "status_code": <int>, "timestamp": <int>, "analysis_time": <float>, "input_process": <str> }, "track": { "num_samples": <int>, "duration": <float>, "sample_md5": <str>, "offset_seconds": <int>, "window_seconds": <int>, "analysis_sample_rate": <int>, "analysis_channels": <int>, "end_of_fade_in": <int>, "start_of_fade_out": <float>, "loudness": <float>, "tempo": <float>, "tempo_confidence": <float>, "time_signature": <int>, "time_signature_confidence": <float>, "key": <int>, "key_confidence": <float>, "mode": <int>, "mode_confidence": <float>, "codestring": <str>, "code_version": <float>, "echoprintstring": <str>, "echoprint_version": <float>, "synchstring": <str>, "synch_version": <int>, "rhythmstring": <str>, "rhythm_version": <int> }, "bars": [ { "start": <float>, "duration": <float>, "confidence": <float> } ], "beats": [ { "start": <float>, "duration": <float>, "confidence": <float> } ], "sections": [ { "start": <float>, "duration": <float>, "confidence": <float>, "loudness": <float>, "tempo": <float>, "tempo_confidence": <float>, "key": <int>, "key_confidence": <float>, "mode": <int>, "mode_confidence": <float>, "time_signature": <int>, "time_signature_confidence": <float> } ], "segments": [ { "start": <float>, "duration": <float>, "confidence": <float>, "loudness_start": <float>, "loudness_max": <float>, "loudness_max_time": <float>, "loudness_end": <int>, "pitches": [<float>], "timbre": [<float>] } ], "tatums": [ { "start": <float>, "duration": <float>, "confidence": <float> } ] } """ return self._get_json(f"{self.API_URL}/audio-analysis/{id}")
[docs] def get_recommendations( self, seed_artists: Union[str, list[str]] = None, seed_genres: Union[str, list[str]] = None, seed_tracks: Union[str, list[str]] = None, *, limit: int = None, market: str = None, **kwargs, ) -> list[dict[str, Any]]: """ `Tracks > Get Recommendations <https://developer.spotify.com/ documentation/web-api/reference/ get-recommendations>`_: Recommendations are generated based on the available information for a given seed entity and matched against similar artists and tracks. If there is sufficient information about the provided seeds, a list of tracks will be returned together with pool size details. For artists and tracks that are very new or obscure, there might not be enough data to generate a list of tracks. .. important:: Spotify content may not be used to train machine learning or AI models. Parameters ---------- seed_artists : `str`, optional A comma separated list of Spotify IDs for seed artists. **Maximum**: Up to 5 seed values may be provided in any combination of `seed_artists`, `seed_tracks`, and `seed_genres`. **Example**: :code:`"4NHQUGzhtTLFvgF5SZesLK"`. seed_genres : `str`, optional A comma separated list of any genres in the set of available genre seeds. **Maximum**: Up to 5 seed values may be provided in any combination of `seed_artists`, `seed_tracks`, and `seed_genres`. **Example**: :code:`"classical,country"`. seed_tracks : `str`, optional A comma separated list of Spotify IDs for a seed track. **Maximum**: Up to 5 seed values may be provided in any combination of `seed_artists`, `seed_tracks`, and `seed_genres`. **Example**: :code:`"0c6xIDDpzE81m2q797ordA"`. limit : `int`, keyword-only, optional The target size of the list of recommended tracks. For seeds with unusually small pools or when highly restrictive filtering is applied, it may be impossible to generate the requested number of recommended tracks. Debugging information for such cases is available in the response. **Minimum**: :code:`1`. **Maximum**: :code:`100`. **Default**: :code:`20`. market : `str`, keyword-only, optional An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. .. note:: If neither market or user country are provided, the content is considered unavailable for the client. **Example**: :code:`"ES"`. **kwargs Tunable track attributes. For a list of available options, see the `Spotify Web API Reference page for this endpoint <https://developer.spotify.com/documentation/web-api/ reference/get-recommendations>`_. Returns ------- tracks : `dict` A dictionary containing Spotify catalog information for the recommended tracks. .. admonition:: Sample response :class: dropdown .. code:: { "seeds": [ { "afterFilteringSize": <int>, "afterRelinkingSize": <int>, "href": <str>, "id": <str>, "initialPoolSize": <int>, "type": <str> } ], "tracks": [ { "album": { "album_type": <str>, "total_tracks": <int>, "available_markets": [<str>], "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "release_date": <str>, "release_date_precision": <str>, "restrictions": { "reason": <str> }, "type": "album", "uri": <str>, "copyrights": [ { "text": <str>, "type": <str> } ], "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "genres": [<str>], "label": <str>, "popularity": <int>, "album_group": <str>, "artists": [ { "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "name": <str>, "type": "artist", "uri": <str> } ] }, "artists": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ], "available_markets": [<str>], "disc_number": <int>, "duration_ms": <int>, "explicit": <bool>, "external_ids": { "isrc": <str>, "ean": <str>, "upc": <str> }, "external_urls": { "spotify": <str> }, "href": <str>, "id": <str>, "is_playable": <bool> "linked_from": { }, "restrictions": { "reason": <str> }, "name": <str>, "popularity": <int>, "preview_url": <str>, "track_number": <int>, "type": "track", "uri": <str>, "is_local": <bool> } ] } """ return self._get_json( f"{self.API_URL}/recommendations", params={ "seed_artists": ( seed_artists if seed_artists is None or isinstance(seed_artists, str) else ",".join(seed_artists) ), "seed_genres": ( seed_genres if seed_genres is None or isinstance(seed_genres, str) else ",".join(seed_genres) ), "seed_tracks": ( seed_tracks if seed_tracks is None or isinstance(seed_tracks, str) else ",".join(seed_tracks) ), "limit": limit, "market": market, **kwargs, }, )
### USERS #################################################################
[docs] def get_profile(self) -> dict[str, Any]: """ `Users > Get Current User's Profile <https://developer.spotify.com/documentation/web-api/reference/ get-current-users-profile>`_: Get detailed profile information about the current user (including the current user's username). .. admonition:: Authorization scope :class: warning Requires the :code:`user-read-private` scope. Returns ------- user : `dict` A dictionary containing the current user's information. .. admonition:: Sample response :class: dropdown .. code:: { "country": <str>, "display_name": <str>, "email": <str>, "explicit_content": { "filter_enabled": <bool>, "filter_locked": <bool> }, "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "product": <str>, "type": <str>, "uri": <str> } """ self._check_scope("get_profile", "user-read-private") return self._get_json(f"{self.API_URL}/me")
[docs] def get_top_items( self, type: str, *, limit: int = None, offset: int = None, time_range: str = None, ) -> dict[str, Any]: """ `Users > Get User's Top Items <https://developer.spotify.com/ documentation/web-api/reference/ get-users-top-artists-and-tracks>`_: Get the current user's top artists or tracks based on calculated affinity. .. admonition:: Authorization scope :class: warning Requires the :code:`user-top-read` scope. Parameters ---------- type : `str` The type of entity to return. **Valid values**: :code:`"artists"` and :code:`"tracks"`. limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. offset : `int`, keyword-only, optional The index of the first result to return. Use with `limit` to get the next page of search results. **Default**: :code:`0`. time_range : `str`, keyword-only, optional Over what time frame the affinities are computed. .. container:: **Valid values**: * :code:`"long_term"` (calculated from several years of data and including all new data as it becomes available). * :code:`"medium_term"` (approximately last 6 months). * :code:`"short_term"` (approximately last 4 weeks). **Default**: :code:`"medium_term"`. Returns ------- items : `dict` A dictionary containing Spotify catalog information for a user's top items and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "offset": <int>, "previous": <str>, "total": <int>, "items": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": <str>, "uri": <str> } ] } """ if type not in (TYPES := {"artists", "tracks"}): raise ValueError( f"Invalid entity type ({type=}). " f"Valid values: {', '.join(TYPES)}." ) self._check_scope("get_top_items", "user-top-read") return self._get_json( f"{self.API_URL}/me/top/{type}", params={ "limit": limit, "offset": offset, "time_range": time_range, }, )
[docs] def get_user_profile(self, user_id: str) -> dict[str, Any]: """ `Users > Get User's Profile <https://developer.spotify.com/ documentation/web-api/reference/ get-users-profile>`_: Get public profile information about a Spotify user. Parameters ---------- user_id : `str` The user's Spotify user ID. **Example**: :code:`"smedjan"` Returns ------- user : `dict` A dictionary containing the user's information. .. admonition:: Sample response :class: dropdown .. code:: { "display_name": <str>, "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "type": "user", "uri": <str> } """ return self._get_json(f"{self.API_URL}/users/{user_id}")
[docs] def follow_playlist(self, playlist_id: str) -> None: """ `Users > Follow Playlist <https://developer.spotify.com/ documentation/web-api/reference/follow-playlist>`_: Add the current user as a follower of a playlist. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"` """ self._check_scope("follow_playlist", "playlist-modify-private") self._request( "put", f"{self.API_URL}/playlists/{playlist_id}/followers" )
[docs] def unfollow_playlist(self, playlist_id: str) -> None: """ `Users > Unfollow Playlist <https://developer.spotify.com/ documentation/web-api/reference/ unfollow-playlist>`_: Remove the current user as a follower of a playlist. .. admonition:: Authorization scope :class: warning Requires the :code:`playlist-modify-private` scope. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"` """ self._check_scope("unfollow_playlist", "playlist-modify-private") self._request( "delete", f"{self.API_URL}/playlists/{playlist_id}/followers" )
[docs] def get_followed_artists( self, *, after: str = None, limit: int = None ) -> dict[str, Any]: """ `Users > Get Followed Artists <https://developer.spotify.com/ documentation/web-api/reference/get-followed>`_: Get the current user's followed artists. .. admonition:: Authorization scope :class: warning Requires the :code:`user-follow-read` scope. Parameters ---------- after : `str`, keyword-only, optional The last artist ID retrieved from the previous request. **Example**: :code:`"0I2XqVXqHScXjHhk6AYYRe"` limit : `int`, keyword-only, optional The maximum number of results to return in each item type. **Valid values**: `limit` must be between 0 and 50. **Default**: :code:`20`. Returns ------- artists : `dict` A dictionary containing Spotify catalog information for a user's followed artists and the number of results returned. .. admonition:: Sample response :class: dropdown .. code:: { "href": <str>, "limit": <int>, "next": <str>, "cursors": { "after": <str>, "before": <str> }, "total": <int>, "items": [ { "external_urls": { "spotify": <str> }, "followers": { "href": <str>, "total": <int> }, "genres": [<str>], "href": <str>, "id": <str>, "images": [ { "url": <str>, "height": <int>, "width": <int> } ], "name": <str>, "popularity": <int>, "type": "artist", "uri": <str> } ] } """ self._check_scope("get_followed_artists", "user-follow-read") return self._get_json( f"{self.API_URL}/me/following", params={"type": "artist", "after": after, "limit": limit}, )["artists"]
[docs] def follow_people(self, ids: Union[str, list[str]], type: str) -> None: """ `Users > Follow Artists or Users <https://developer.spotify.com/ documentation/web-api/reference/ follow-artists-users>`_: Add the current user as a follower of one or more artists or other Spotify users. .. admonition:: Authorization scope :class: warning Requires the :code:`user-follow-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the artist or user Spotify IDs. **Maximum**: Up to 50 IDs can be sent in one request. **Example**: :code:`"2CIMQHirSU0MQqyYHq0eOx, 57dN52uHvrHOxijzpIgu3E, 1vCWHaC5f2uS3yhpwWbIA6"`. type : `str` The ID type. **Valid values**: :code:`"artist"` and :code:`"user"`. """ self._check_scope("follow_people", "user-follow-modify") if isinstance(ids, str): self._request( "put", f"{self.API_URL}/me/following", params={"ids": ids, "type": type}, ) elif isinstance(ids, list): self._request( "put", f"{self.API_URL}/me/following", json={"ids": ids}, params={"type": type}, )
[docs] def unfollow_people(self, ids: Union[str, list[str]], type: str) -> None: """ `Users > Unfollow Artists or Users <https://developer.spotify.com/documentation/web-api/reference/ unfollow-artists-users>`_: Remove the current user as a follower of one or more artists or other Spotify users. .. admonition:: Authorization scope :class: warning Requires the :code:`user-follow-modify` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the artist or user Spotify IDs. **Maximum**: Up to 50 IDs can be sent in one request. **Example**: :code:`"2CIMQHirSU0MQqyYHq0eOx, 57dN52uHvrHOxijzpIgu3E, 1vCWHaC5f2uS3yhpwWbIA6"`. type : `str` The ID type. **Valid values**: :code:`"artist"` and :code:`"user"`. """ self._check_scope("unfollow_people", "user-follow-modify") if isinstance(ids, str): self._request( "delete", f"{self.API_URL}/me/following", params={"ids": ids, "type": type}, ) elif isinstance(ids, list): self._request( "delete", f"{self.API_URL}/me/following", json={"ids": ids}, params={"type": type}, )
[docs] def check_followed_people( self, ids: Union[str, list[str]], type: str ) -> list[bool]: """ `Users > Check If User Follows Artists or Users <https://developer.spotify.com/documentation/web-api/reference/ check-current-user-follows>`_: Check to see if the current user is following one or more artists or other Spotify users. .. admonition:: Authorization scope :class: warning Requires the :code:`user-follow-read` scope. Parameters ---------- ids : `str` or `list` A (comma-separated) list of the artist or user Spotify IDs. **Maximum**: Up to 50 IDs can be sent in one request. **Example**: :code:`"2CIMQHirSU0MQqyYHq0eOx, 57dN52uHvrHOxijzpIgu3E, 1vCWHaC5f2uS3yhpwWbIA6"`. type : `str` The ID type. **Valid values**: :code:`"artist"` and :code:`"user"`. Returns ------- contains : `list` Array of booleans specifying whether the user follows the specified artists or Spotify users. **Example**: :code:`[False, True]`. """ self._check_scope("check_followed_people", "user-follow-read") return self._get_json( f"{self.API_URL}/me/following/contains", params={ "ids": ids if isinstance(ids, str) else ",".join(ids), "type": type, }, )
[docs] def check_playlist_followers( self, playlist_id: str, ids: Union[str, list[str]] ) -> list[bool]: """ `Users > Check If Users Follow Playlist <https://developer.spotify.com/documentation/web-api/reference/ check-if-user-follows-playlist>`_: Check to see if one or more Spotify users are following a specified playlist. Parameters ---------- playlist_id : `str` The Spotify ID of the playlist. **Example**: :code:`"3cEYpjA9oz9GiPac4AsH4n"`. ids : `str` or `list` A (comma-separated) list of Spotify user IDs; the IDs of the users that you want to check to see if they follow the playlist. **Maximum**: 5 IDs. **Example**: :code:`"jmperezperez,thelinmichael,wizzler"`. Returns ------- follows : `list` Array of booleans specifying whether the users follow the playlist. **Example**: :code:`[False, True]`. """ return self._get_json( f"{self.API_URL}/playlists/{playlist_id}/followers/contains", params={"ids": ids if isinstance(ids, str) else ",".join(ids)}, )