Skip to content

Anonymous access to API #878

Description

@dieser-niko

Is your feature request related to a problem? Please describe.
This feature would allow the script to make anonymous API calls without login or registered application.

Describe the solution you'd like
It's actually very easy to obtain an anonymous token: https://open.spotify.com/get_access_token

I wrote a little script, but it may not be the best implementation (basically copied SpotifyClientCredentials and changed the class to my liking):

spotipy_anon.py

import warnings
import logging

import requests
from spotipy.oauth2 import SpotifyAuthBase
from spotipy.cache_handler import CacheFileHandler, CacheHandler

logger = logging.getLogger(__name__)


class SpotifyAnon(SpotifyAuthBase):
    """
    Implements anonymous access to the Spotify API.
    """
    client_id: str  # was suggested by PyCharm

    TOKEN_URL = "https://open.spotify.com/get_access_token"

    def __init__(
            self,
            proxies=None,
            requests_session=True,
            requests_timeout=None,
            cache_handler=None
    ):
        """
        Creates a SpotifyAnon object
        
        Parameters:
        * proxies: Optional, interpreted as boolean
        * requests_session: A Requests session
        * requests_timeout: Optional, tell Requests to stop waiting for a response after
                            a given number of seconds
        * cache_handler: An instance of the `CacheHandler` class to handle
                         getting and saving cached authorization tokens.
                         Optional, will otherwise use `CacheFileHandler`.
                         (takes precedence over `cache_path` and `username`)
        """

        super(SpotifyAnon, self).__init__(requests_session)

        self.proxies = proxies
        self.requests_session = requests_session
        self.requests_timeout = requests_timeout
        self.cache_handler = cache_handler

        if cache_handler:
            assert issubclass(cache_handler.__class__, CacheHandler), \
                "cache_handler must be a subclass of CacheHandler: " + str(type(cache_handler)) \
                + " != " + str(CacheHandler)
            self.cache_handler = cache_handler
        else:
            self.cache_handler = CacheFileHandler()

    def get_access_token(self, as_dict=False, check_cache=True):
        """
        If a valid access token is in memory, returns it
        Else fetches a new token and returns it
            Parameters:
            - as_dict - a boolean indicating if returning the access token
                as a token_info dictionary, otherwise it will be returned
                as a string.
        """
        if as_dict:
            warnings.warn(
                "You're using 'as_dict = True'."
                "get_access_token will return the token string directly in future "
                "versions. Please adjust your code accordingly, or use "
                "get_cached_token instead.",
                DeprecationWarning,
                stacklevel=2,
            )

        if check_cache:
            token_info = self.cache_handler.get_cached_token()
            if token_info and not self.is_token_expired(token_info):
                return token_info if as_dict else token_info["access_token"]

        token_info = self._request_access_token()
        # token_info = self._add_custom_values_to_token_info(token_info)
        self.cache_handler.save_token_to_cache(token_info)
        self.client_id = token_info["client_id"]
        return token_info if as_dict else token_info["access_token"]

    def _request_access_token(self):
        """Gets client credentials access token """
        logger.debug("sending GET request to %s", self.TOKEN_URL)

        try:
            response = self._session.get(
                self.TOKEN_URL,
                verify=True,
                proxies=self.proxies,
                timeout=self.requests_timeout,
            )
            response.raise_for_status()
            token_info = response.json()
            return {"client_id": token_info["clientId"],
                    "access_token": token_info["accessToken"],
                    "expires_at": int(token_info["accessTokenExpirationTimestampMs"] / 1000)
                    }
        except requests.exceptions.HTTPError as http_error:
            self._handle_oauth_error(http_error)

main.py

import spotipy, spotipy_anon
sp = spotipy.Spotify(auth_manager=spotipy_anon.SpotifyAnon())

results = sp.search(q='weezer', limit=20)
for idx, track in enumerate(results['tracks']['items']):
    print(idx, track['name'])

Describe alternatives you've considered
None.

Additional context
Not really relevant for this suggestion or even for this repo, but there's also the endpoint api-partner.spotify.com which uses the same token, but much harder to implement. This is how I found the anonymous token. It is possible to get even more stuff like background colors and things relevant to the front end, but you need the hash of the definition, which can be changed (and was already changed while I was testing it)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions