Skip to content

Commit

Permalink
Use access token for gst-plugin-spotify login
Browse files Browse the repository at this point in the history
  • Loading branch information
kingosticks committed Sep 23, 2024
1 parent e088c33 commit cddade5
Show file tree
Hide file tree
Showing 7 changed files with 31 additions and 31 deletions.
29 changes: 11 additions & 18 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ Status :warning:
=================

Spotify have recently disabled username and password login for playback
(`#394 <https://github.com/mopidy/mopidy-spotify/issues/394>`_).
Alternate authentication methods are possible but not yet supported.
(`#394 <https://github.com/mopidy/mopidy-spotify/issues/394>`_) and we
now utilise access-token login. You no longer need to provide your
Spotify account username or password.

Mopidy-Spotify currently has no support for the following:

- Playback

- Seeking

- Gapless playback
Expand All @@ -48,6 +47,8 @@ Mopidy-Spotify currently has no support for the following:

Working support for the following features is currently available:

- Playback

- Search

- Playlists (read-only)
Expand All @@ -63,18 +64,9 @@ Dependencies
- A Spotify Premium subscription. Mopidy-Spotify **will not** work with Spotify
Free, just Spotify Premium.

- A non-Facebook Spotify username and password. If you created your account
through Facebook you'll need to create a "device password" to be able to use
Mopidy-Spotify. Go to http://www.spotify.com/account/set-device-password/,
login with your Facebook account, and follow the instructions. However,
sometimes that process can fail for users with Facebook logins, in which case
you can create an app-specific password on Facebook by going to facebook.com >
Settings > Security > App passwords > Generate app passwords, and generate one
to use with Mopidy-Spotify.

- ``Mopidy`` >= 3.4. The music server that Mopidy-Spotify extends.

- ``gst-plugins-spotify`` >= 0.10. The `GStreamer Rust Plugin
- A *custom* version of ``gst-plugins-spotify``. The `GStreamer Rust Plugin
<https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs>`_ to stream Spotify
audio, based on `librespot <https://github.com/librespot-org/librespot/>`_.
**This plugin is not yet available from apt.mopidy.com**. It must be either
Expand All @@ -83,6 +75,9 @@ Dependencies
or `Debian packages are available
<https://github.com/kingosticks/gst-plugins-rs-build/releases/latest>`_
for some platforms.
**We currently require a forked version of ``gst-plugins-spotify`` which supports
token-based login. This can be found `here
<https://gitlab.freedesktop.org/kingosticks/gst-plugins-rs/-/tree/0.13-librespot-dev>`_.
Verify the GStreamer spotify plugin is correctly installed::

Expand All @@ -106,8 +101,6 @@ https://mopidy.com/ext/spotify/#authentication
to authorize this extension against your Spotify account::

[spotify]
username = alice
password = secret
client_id = ... client_id value you got from mopidy.com ...
client_secret = ... client_secret value you got from mopidy.com ...

Expand All @@ -116,9 +109,9 @@ The following configuration values are available:
- ``spotify/enabled``: If the Spotify extension should be enabled or not.
Defaults to ``true``.

- ``spotify/username``: Your Spotify Premium username. You *must* provide this.
- ``spotify/username``: Your Spotify Premium username. Obsolete.

- ``spotify/password``: Your Spotify Premium password. You *must* provide this.
- ``spotify/password``: Your Spotify Premium password. Obsolete.

- ``spotify/client_id``: Your Spotify application client id. You *must* provide this.

Expand Down
4 changes: 2 additions & 2 deletions src/mopidy_spotify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ def get_default_config(self):
def get_config_schema(self):
schema = super().get_config_schema()

schema["username"] = config.String()
schema["password"] = config.Secret()
schema["username"] = config.Deprecated() # since 5.0
schema["password"] = config.Deprecated() # since 5.0

schema["client_id"] = config.String()
schema["client_secret"] = config.Secret()
Expand Down
4 changes: 2 additions & 2 deletions src/mopidy_spotify/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ def __init__(self, *args, **kwargs):
self._credentials_dir.mkdir(mode=0o700)

def on_source_setup(self, source):
for prop in ["username", "password", "bitrate"]:
source.set_property(prop, str(self._config[prop]))
source.set_property("bitrate", str(self._config["bitrate"]))
source.set_property("cache-credentials", self._credentials_dir)
source.set_property("access-token", self.backend._web_client.token())
if self._config["allow_cache"]:
source.set_property("cache-files", self._cache_location)
source.set_property("cache-max-size", self._config["cache_size"] * 1048576)
2 changes: 0 additions & 2 deletions src/mopidy_spotify/ext.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
[spotify]
enabled = true
username =
password =
client_id =
client_secret =
bitrate = 160
Expand Down
15 changes: 14 additions & 1 deletion src/mopidy_spotify/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ def __init__( # noqa: PLR0913
self._auth = (client_id, client_secret)
else:
self._auth = None
self._access_token = None

self._base_url = base_url
self._refresh_url = refresh_url
Expand All @@ -69,6 +70,17 @@ def __init__( # noqa: PLR0913
self._cache_mutex = threading.Lock() # Protects get() cache param.
self._refresh_mutex = threading.Lock() # Protects _headers and _expires.

def token(self):
with self._refresh_mutex:
try:
if self._should_refresh_token():
self._refresh_token()
logger.info(f"Providing access token: {self._access_token}")
return self._access_token
except OAuthTokenRefreshError as e:
logger.error(e) # noqa: TRY400
return None

def get(self, path, cache=None, *args, **kwargs):
if self._authorization_failed:
logger.debug("Blocking request as previous authorization failed.")
Expand Down Expand Up @@ -149,7 +161,8 @@ def _refresh_token(self):
f"wrong token_type: {result.get('token_type')}"
)

self._headers["Authorization"] = f"Bearer {result['access_token']}"
self._access_token = result['access_token']
self._headers["Authorization"] = f"Bearer {self._access_token}"
self._expires = time.time() + result.get("expires_in", float("Inf"))

if result.get("expires_in"):
Expand Down
2 changes: 0 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ def config(tmp_path):
},
"proxy": {},
"spotify": {
"username": "alice",
"password": "password",
"bitrate": 160,
"volume_normalization": True,
"timeout": 10,
Expand Down
6 changes: 2 additions & 4 deletions tests/test_playback.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ def test_on_source_setup_sets_properties(config, provider):
cred_dir = spotify_data_dir / "credentials-cache"

assert mock_source.set_property.mock_calls == [
mock.call("username", "alice"),
mock.call("password", "password"),
mock.call("access-token", mock.ANY),
mock.call("bitrate", "160"),
mock.call("cache-credentials", cred_dir),
mock.call("cache-files", spotify_cache_dir),
Expand All @@ -52,8 +51,7 @@ def test_on_source_setup_without_caching(config, provider):
cred_dir = spotify_data_dir / "credentials-cache"

assert mock_source.set_property.mock_calls == [
mock.call("username", "alice"),
mock.call("password", "password"),
mock.call("access-token", mock.ANY),
mock.call("bitrate", "160"),
mock.call("cache-credentials", cred_dir),
]
Expand Down

0 comments on commit cddade5

Please sign in to comment.