Skip to content

Commit 8711f63

Browse files
authored
Merge pull request #397 from kingosticks/playback-access-token
Playback using access token
2 parents 2101b7f + 32f9e2d commit 8711f63

File tree

7 files changed

+32
-32
lines changed

7 files changed

+32
-32
lines changed

README.rst

+10-18
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ Status :warning:
2222
=================
2323

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

2829
Mopidy-Spotify currently has no support for the following:
2930

30-
- Playback
31-
3231
- Seeking
3332

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

4948
Working support for the following features is currently available:
5049

50+
- Playback
51+
5152
- Search
5253

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

66-
- A non-Facebook Spotify username and password. If you created your account
67-
through Facebook you'll need to create a "device password" to be able to use
68-
Mopidy-Spotify. Go to http://www.spotify.com/account/set-device-password/,
69-
login with your Facebook account, and follow the instructions. However,
70-
sometimes that process can fail for users with Facebook logins, in which case
71-
you can create an app-specific password on Facebook by going to facebook.com >
72-
Settings > Security > App passwords > Generate app passwords, and generate one
73-
to use with Mopidy-Spotify.
74-
7567
- ``Mopidy`` >= 3.4. The music server that Mopidy-Spotify extends.
7668

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

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

108102
[spotify]
109-
username = alice
110-
password = secret
111103
client_id = ... client_id value you got from mopidy.com ...
112104
client_secret = ... client_secret value you got from mopidy.com ...
113105

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

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

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

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

src/mopidy_spotify/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ def get_default_config(self):
1717
def get_config_schema(self):
1818
schema = super().get_config_schema()
1919

20-
schema["username"] = config.String()
21-
schema["password"] = config.Secret()
20+
schema["username"] = config.Deprecated() # since 5.0
21+
schema["password"] = config.Deprecated() # since 5.0
2222

2323
schema["client_id"] = config.String()
2424
schema["client_secret"] = config.Secret()

src/mopidy_spotify/backend.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ def __init__(self, *args, **kwargs):
4545
self._credentials_dir.mkdir(mode=0o700)
4646

4747
def on_source_setup(self, source):
48-
for prop in ["username", "password", "bitrate"]:
49-
source.set_property(prop, str(self._config[prop]))
48+
source.set_property("bitrate", str(self._config["bitrate"]))
5049
source.set_property("cache-credentials", self._credentials_dir)
50+
source.set_property("access-token", self.backend._web_client.token())
5151
if self._config["allow_cache"]:
5252
source.set_property("cache-files", self._cache_location)
5353
source.set_property("cache-max-size", self._config["cache_size"] * 1048576)

src/mopidy_spotify/ext.conf

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
[spotify]
22
enabled = true
3-
username =
4-
password =
53
client_id =
64
client_secret =
75
bitrate = 160

src/mopidy_spotify/web.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def __init__( # noqa: PLR0913
5050
self._auth = (client_id, client_secret)
5151
else:
5252
self._auth = None
53+
self._access_token = None
5354

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

73+
def token(self):
74+
with self._refresh_mutex:
75+
try:
76+
if self._should_refresh_token():
77+
self._refresh_token()
78+
except OAuthTokenRefreshError as e:
79+
logger.error(e) # noqa: TRY400
80+
return None
81+
else:
82+
return self._access_token
83+
7284
def get(self, path, cache=None, *args, **kwargs):
7385
if self._authorization_failed:
7486
logger.debug("Blocking request as previous authorization failed.")
@@ -149,7 +161,8 @@ def _refresh_token(self):
149161
f"wrong token_type: {result.get('token_type')}"
150162
)
151163

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

155168
if result.get("expires_in"):

tests/conftest.py

-2
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ def config(tmp_path):
2222
},
2323
"proxy": {},
2424
"spotify": {
25-
"username": "alice",
26-
"password": "password",
2725
"bitrate": 160,
2826
"volume_normalization": True,
2927
"timeout": 10,

tests/test_playback.py

+4-5
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from mopidy import audio
55
from mopidy import backend as backend_api
66

7-
from mopidy_spotify import backend
7+
from mopidy_spotify import backend, web
88

99

1010
@pytest.fixture
@@ -16,6 +16,7 @@ def audio_mock():
1616
def backend_mock(config):
1717
backend_mock = mock.Mock(spec=backend.SpotifyBackend)
1818
backend_mock._config = config
19+
backend_mock._web_client = mock.Mock(spec=web.OAuthClient)
1920
return backend_mock
2021

2122

@@ -36,10 +37,9 @@ def test_on_source_setup_sets_properties(config, provider):
3637
cred_dir = spotify_data_dir / "credentials-cache"
3738

3839
assert mock_source.set_property.mock_calls == [
39-
mock.call("username", "alice"),
40-
mock.call("password", "password"),
4140
mock.call("bitrate", "160"),
4241
mock.call("cache-credentials", cred_dir),
42+
mock.call("access-token", mock.ANY),
4343
mock.call("cache-files", spotify_cache_dir),
4444
mock.call("cache-max-size", 8589934592),
4545
]
@@ -53,10 +53,9 @@ def test_on_source_setup_without_caching(config, provider):
5353
cred_dir = spotify_data_dir / "credentials-cache"
5454

5555
assert mock_source.set_property.mock_calls == [
56-
mock.call("username", "alice"),
57-
mock.call("password", "password"),
5856
mock.call("bitrate", "160"),
5957
mock.call("cache-credentials", cred_dir),
58+
mock.call("access-token", mock.ANY),
6059
]
6160

6261

0 commit comments

Comments
 (0)