diff --git a/CHANGELOG.md b/CHANGELOG.md index b455251..090cbc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -// Add your changes here and then delete this line +### Added + +- `open_browser` can be passed to the constructors of `SpotifyOAuth` and `SpotifyPKCE` to make it easier to authorize in browserless environments ## [2.15.0] - 2020-09-08 diff --git a/FAQ.md b/FAQ.md index b0cdf3a..9388f5b 100644 --- a/FAQ.md +++ b/FAQ.md @@ -40,4 +40,11 @@ Problem: you can see a track on the Spotify app but searching for it using the A Solution: by default `search("abba")` works in the US market. To search for in your current country, the [country indicator](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) -must be specified: `search("abba", market="DE")`. \ No newline at end of file +must be specified: `search("abba", market="DE")`. + +### How do I obtain authorization in a headless/browserless environment? + +If you cannot open a browser, set `open_browser=False` when instantiating SpotifyOAuth or SpotifyPKCE. You will be +prompted to open the authorization URI manually. + +See the [headless auth example](examples/headless.py). \ No newline at end of file diff --git a/examples/headless.py b/examples/headless.py new file mode 100644 index 0000000..4dc9b21 --- /dev/null +++ b/examples/headless.py @@ -0,0 +1,8 @@ +import spotipy + +from spotipy.oauth2 import SpotifyOAuth + +# set open_browser=False to prevent Spotipy from attempting to open the default browser +spotify = spotipy.Spotify(auth_manager=SpotifyOAuth(open_browser=False)) + +print(spotify.me()) \ No newline at end of file diff --git a/spotipy/oauth2.py b/spotipy/oauth2.py index 8a37774..643a913 100644 --- a/spotipy/oauth2.py +++ b/spotipy/oauth2.py @@ -234,18 +234,19 @@ class SpotifyOAuth(SpotifyAuthBase): OAUTH_TOKEN_URL = "https://accounts.spotify.com/api/token" def __init__( - self, - client_id=None, - client_secret=None, - redirect_uri=None, - state=None, - scope=None, - cache_path=None, - username=None, - proxies=None, - show_dialog=False, - requests_session=True, - requests_timeout=None + self, + client_id=None, + client_secret=None, + redirect_uri=None, + state=None, + scope=None, + cache_path=None, + username=None, + proxies=None, + show_dialog=False, + requests_session=True, + requests_timeout=None, + open_browser=True ): """ Creates a SpotifyOAuth object @@ -264,6 +265,7 @@ class SpotifyOAuth(SpotifyAuthBase): * show_dialog: Interpreted as boolean * requests_timeout: Tell Requests to stop waiting for a response after a given number of seconds + * open_browser: Whether or not the web browser should be opened to authorize a user """ super(SpotifyOAuth, self).__init__(requests_session) @@ -280,6 +282,7 @@ class SpotifyOAuth(SpotifyAuthBase): self.proxies = proxies self.requests_timeout = requests_timeout self.show_dialog = show_dialog + self.open_browser = open_browser def get_cached_token(self): """ Gets a cached auth token @@ -294,7 +297,7 @@ class SpotifyOAuth(SpotifyAuthBase): # if scopes don't match, then bail if "scope" not in token_info or not self._is_scope_subset( - self.scope, token_info["scope"] + self.scope, token_info["scope"] ): return None @@ -382,7 +385,7 @@ class SpotifyOAuth(SpotifyAuthBase): except webbrowser.Error: logger.error("Please navigate here: %s", auth_url) - def _get_auth_response_interactive(self, open_browser=True): + def _get_auth_response_interactive(self, open_browser=False): if open_browser: self._open_auth_url() prompt = "Enter the URL you were redirected to: " @@ -413,7 +416,7 @@ class SpotifyOAuth(SpotifyAuthBase): else: raise SpotifyOauthError("Server listening on localhost has not been accessed") - def get_auth_response(self, open_browser=True): + def get_auth_response(self, open_browser=None): logger.info('User authentication requires interaction with your ' 'web browser. Once you enter your credentials and ' 'give authorization, you will be redirected to ' @@ -423,10 +426,13 @@ class SpotifyOAuth(SpotifyAuthBase): redirect_info = urlparse(self.redirect_uri) redirect_host, redirect_port = get_host_port(redirect_info.netloc) + if open_browser is None: + open_browser = self.open_browser + if ( - open_browser - and redirect_host in ("127.0.0.1", "localhost") - and redirect_info.scheme == "http" + (open_browser or self.open_browser) + and redirect_host in ("127.0.0.1", "localhost") + and redirect_info.scheme == "http" ): # Only start a local http server if a port is specified if redirect_port: @@ -584,7 +590,8 @@ class SpotifyPKCE(SpotifyAuthBase): username=None, proxies=None, requests_timeout=None, - requests_session=True,): + requests_session=True, + open_browser=True): """ Creates Auth Manager with the PKCE Auth flow. @@ -602,6 +609,7 @@ class SpotifyPKCE(SpotifyAuthBase): * proxies: Proxy for the requests library to route through * requests_timeout: Tell Requests to stop waiting for a response after a given number of seconds + * open_browser: Whether or not the web browser should be opened to authorize a user """ super(SpotifyPKCE, self).__init__(requests_session) @@ -620,6 +628,7 @@ class SpotifyPKCE(SpotifyAuthBase): self.code_verifier = None self.code_challenge = None self.authorization_code = None + self.open_browser = open_browser def _normalize_scope(self, scope): if scope: @@ -642,7 +651,7 @@ class SpotifyPKCE(SpotifyAuthBase): try: import secrets verifier = secrets.token_urlsafe(length) - except ImportError: # For python 3.5 support + except ImportError: # For python 3.5 support import os import base64 rand_bytes = os.urandom(length) @@ -688,7 +697,7 @@ class SpotifyPKCE(SpotifyAuthBase): except webbrowser.Error: logger.error("Please navigate here: %s", auth_url) - def _get_auth_response(self, open_browser=True): + def _get_auth_response(self, open_browser=None): logger.info('User authentication requires interaction with your ' 'web browser. Once you enter your credentials and ' 'give authorization, you will be redirected to ' @@ -698,10 +707,13 @@ class SpotifyPKCE(SpotifyAuthBase): redirect_info = urlparse(self.redirect_uri) redirect_host, redirect_port = get_host_port(redirect_info.netloc) + if open_browser is None: + open_browser = self.open_browser + if ( - open_browser - and redirect_host in ("127.0.0.1", "localhost") - and redirect_info.scheme == "http" + open_browser + and redirect_host in ("127.0.0.1", "localhost") + and redirect_info.scheme == "http" ): # Only start a local http server if a port is specified if redirect_port: @@ -730,8 +742,8 @@ class SpotifyPKCE(SpotifyAuthBase): else: raise SpotifyOauthError("Server listening on localhost has not been accessed") - def _get_auth_response_interactive(self, open_browser=True): - if open_browser: + def _get_auth_response_interactive(self, open_browser=False): + if open_browser or self.open_browser: self._open_auth_url() prompt = "Enter the URL you were redirected to: " else: @@ -764,7 +776,7 @@ class SpotifyPKCE(SpotifyAuthBase): # if scopes don't match, then bail if "scope" not in token_info or not self._is_scope_subset( - self.scope, token_info["scope"] + self.scope, token_info["scope"] ): return None @@ -1011,7 +1023,7 @@ class SpotifyImplicitGrant(SpotifyAuthBase): # if scopes don't match, then bail if "scope" not in token_info or not self._is_scope_subset( - self.scope, token_info["scope"] + self.scope, token_info["scope"] ): return None