spotifywebapiPython 1.0.175__py3-none-any.whl → 1.0.177__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of spotifywebapiPython might be problematic. Click here for more details.
- {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/RECORD +23 -23
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +14 -2
- spotifywebapipython/docs/index.html +2 -2
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +4 -4
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
- spotifywebapipython/spotifyclient.py +17 -33
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +50 -25
- spotifywebapipython/spotifywebplayertoken.py +70 -51
- {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/top_level.txt +0 -0
|
@@ -1048,37 +1048,45 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
1048
1048
|
if (sonosPlayer is None):
|
|
1049
1049
|
raise SpotifyApiError("Could not find Sonos Controller instance for device: %s" % (device.Title), None, logsi=_logsi)
|
|
1050
1050
|
|
|
1051
|
+
# trace.
|
|
1052
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
|
|
1053
|
+
|
|
1051
1054
|
# does caller want the group coordinator?
|
|
1052
1055
|
if (returnCoordinator):
|
|
1053
1056
|
|
|
1054
1057
|
# TEST TODO - use to test Sonos coordinator functionality.
|
|
1055
1058
|
# _logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer, colorValue=SIColors.Red)
|
|
1056
|
-
#
|
|
1059
|
+
#sonosPlayer._is_coordinator = False
|
|
1057
1060
|
|
|
1058
|
-
#
|
|
1061
|
+
# is Sonos Controller instance the group coordinator?
|
|
1059
1062
|
if (sonosPlayer.is_coordinator):
|
|
1060
|
-
if _logsi.IsOn(SILevel.Verbose):
|
|
1061
|
-
_logsi.LogVerbose("Sonos Controller instance is a group coordinator for device: %s" % (device.Title))
|
|
1062
|
-
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (device.Title), sonosPlayer)
|
|
1063
|
-
_logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
|
|
1064
|
-
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
|
|
1065
|
-
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (device.Title), sonosPlayer.zone_group_state)
|
|
1066
|
-
return sonosPlayer
|
|
1067
1063
|
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
|
|
1073
|
-
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (device.Title), sonosPlayer.zone_group_state)
|
|
1074
|
-
|
|
1075
|
-
# is the device part of a group? if so, then use the group coordinator.
|
|
1076
|
-
if (sonosPlayer.group is not None):
|
|
1077
|
-
if (sonosPlayer.group.coordinator is not None):
|
|
1078
|
-
sonosPlayerCoordinator:SoCo = sonosPlayer.group.coordinator
|
|
1079
|
-
_logsi.LogVerbose("Sonos Controller group coordinator \"%s\" (%s) will be used for device: %s" % (sonosPlayerCoordinator.player_name, sonosPlayerCoordinator.ip_address, device.Title))
|
|
1080
|
-
sonosPlayer = sonosPlayerCoordinator
|
|
1064
|
+
# yes - nothing else to do since it's already a group coordinator.
|
|
1065
|
+
_logsi.LogVerbose("Sonos Controller instance is a group coordinator")
|
|
1066
|
+
|
|
1067
|
+
else:
|
|
1081
1068
|
|
|
1069
|
+
# no - try to determine the group coordinator.
|
|
1070
|
+
_logsi.LogVerbose("Sonos Controller instance is NOT a group coordinator for device: %s" % (device.Title))
|
|
1071
|
+
|
|
1072
|
+
# is the device part of a group? if so, then use the `group.coordinator` value (if present).
|
|
1073
|
+
sonosPlayerCoordinator:SoCo = None
|
|
1074
|
+
if (sonosPlayer.group is not None):
|
|
1075
|
+
if (sonosPlayer.group.coordinator is not None):
|
|
1076
|
+
sonosPlayerCoordinator = sonosPlayer.group.coordinator
|
|
1077
|
+
sonosPlayer = sonosPlayerCoordinator
|
|
1078
|
+
_logsi.LogVerbose("Sonos Controller group coordinator \"%s\" (%s) will be used for device: %s" % (sonosPlayerCoordinator.player_name, sonosPlayerCoordinator.ip_address, device.Title))
|
|
1079
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayerCoordinator.speaker_info)
|
|
1080
|
+
|
|
1081
|
+
# if group coordinator could not be found, then log a trace message indicating
|
|
1082
|
+
# this and that subsequent Sonos Controller operations will probably fail.
|
|
1083
|
+
# this only seems to happen for "orphaned" Sonos devices (e.g. uid="")
|
|
1084
|
+
# zone_group_state.groups = [ZoneGroup(uid='RINCON_5CAAFDF4E8DE01400:orphan', coordinator=None, members={SoCo("192.168.50.79")}), etc
|
|
1085
|
+
if (sonosPlayerCoordinator is None):
|
|
1086
|
+
_logsi.LogVerbose("Sonos Controller group coordinator could not be determined for device: %s; subsequent Sonos Controller API functions will probably fail!" % (device.Title), colorValue=SIColors.Red)
|
|
1087
|
+
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (device.Title), sonosPlayer.group)
|
|
1088
|
+
|
|
1089
|
+
# return Sonos Controller instance to caller.
|
|
1082
1090
|
return sonosPlayer
|
|
1083
1091
|
|
|
1084
1092
|
|
|
@@ -1882,11 +1890,28 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
1882
1890
|
|
|
1883
1891
|
try:
|
|
1884
1892
|
|
|
1885
|
-
#
|
|
1886
|
-
# use device ip address as the key, as Spotify Web API reports id=null for restricted devices in playerstate!
|
|
1893
|
+
# is this a Sonos device?
|
|
1887
1894
|
if (scDevice.IsSonos):
|
|
1895
|
+
|
|
1896
|
+
# create a Sonos Controller instance for the device, retrieve the Sonos speaker information,
|
|
1897
|
+
# and add it to the Sonos players collection. use device ip address as the key, as the
|
|
1898
|
+
# Spotify Web API reports "id=null" for restricted devices in playerstate!
|
|
1888
1899
|
_logsi.LogVerbose("Sonos device detected; creating Sonos Controller instance for device: %s (ip=%s)" % (scDevice.Title, zeroconfDiscoveryResult.HostIpAddress))
|
|
1889
|
-
|
|
1900
|
+
sonosPlayer:SoCo = SoCo(zeroconfDiscoveryResult.HostIpAddress)
|
|
1901
|
+
self._SonosPlayers[zeroconfDiscoveryResult.HostIpAddress] = sonosPlayer
|
|
1902
|
+
|
|
1903
|
+
try:
|
|
1904
|
+
sonosPlayer.get_speaker_info()
|
|
1905
|
+
except Exception as ex:
|
|
1906
|
+
_logsi.LogException("Could not get speaker info for Sonos Controller instance: %s" % (str(ex)), ex, logToSystemLogger=False)
|
|
1907
|
+
|
|
1908
|
+
# trace.
|
|
1909
|
+
if (_logsi.IsOn(SILevel.Verbose)):
|
|
1910
|
+
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s" % (scDevice.Title), sonosPlayer)
|
|
1911
|
+
_logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (scDevice.Title), sonosPlayer.speaker_info)
|
|
1912
|
+
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (group)" % (scDevice.Title), sonosPlayer.group)
|
|
1913
|
+
_logsi.LogObject(SILevel.Verbose, "Sonos Controller instance for device: %s (zone_group_state)" % (scDevice.Title), sonosPlayer.zone_group_state)
|
|
1914
|
+
_logsi.LogEnumerable(SILevel.Verbose, "Sonos Controller instance for device: %s (all_zones)" % (scDevice.Title), sonosPlayer.all_zones)
|
|
1890
1915
|
|
|
1891
1916
|
except:
|
|
1892
1917
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# external package imports.
|
|
2
|
-
from datetime import datetime
|
|
2
|
+
from datetime import datetime
|
|
3
3
|
import json
|
|
4
4
|
import os.path
|
|
5
5
|
import platformdirs
|
|
6
6
|
import requests
|
|
7
7
|
import time
|
|
8
|
-
from tokenize import TokenError
|
|
9
8
|
|
|
10
9
|
# our package imports.
|
|
10
|
+
from .spotifyapierror import SpotifyApiError
|
|
11
11
|
from .const import (
|
|
12
12
|
SPOTIFY_WEBUI_URL_BASE,
|
|
13
13
|
SPOTIFYWEBAPIPYTHON_TOKEN_CACHE_FILE,
|
|
@@ -224,28 +224,22 @@ class SpotifyWebPlayerToken:
|
|
|
224
224
|
|
|
225
225
|
# validation.
|
|
226
226
|
if (self._sp_dc is None):
|
|
227
|
-
raise
|
|
227
|
+
raise SpotifyApiError("Cookie credentials storage key \"%s\" did not contain an \"sp_dc\" key value" % (tokenKey), None, logsi=_logsi)
|
|
228
228
|
if (self._sp_key is None):
|
|
229
|
-
raise
|
|
229
|
+
raise SpotifyApiError("Cookie credentials storage key \"%s\" did not contain an \"sp_key\" key value" % (tokenKey), None, logsi=_logsi)
|
|
230
230
|
|
|
231
231
|
# return token to caller.
|
|
232
232
|
return tokens[tokenKey]
|
|
233
233
|
|
|
234
234
|
# if we make it here, then it denotes that a token was not found
|
|
235
235
|
# for the specified key.
|
|
236
|
-
raise
|
|
236
|
+
raise SpotifyApiError("Cookie credentials storage key not found: \"%s\"" % (tokenKey), None, logsi=_logsi)
|
|
237
237
|
|
|
238
|
-
except
|
|
239
|
-
|
|
240
|
-
# trace.
|
|
241
|
-
_logsi.LogException(str(ex), ex, logToSystemLogger=False)
|
|
242
|
-
raise
|
|
243
|
-
|
|
238
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
244
239
|
except Exception as ex:
|
|
245
240
|
|
|
246
241
|
# trace.
|
|
247
|
-
|
|
248
|
-
raise TokenError(str(ex))
|
|
242
|
+
raise SpotifyApiError("Could not load Spotify Web Player cookie credentials", ex, logsi=_logsi)
|
|
249
243
|
|
|
250
244
|
finally:
|
|
251
245
|
|
|
@@ -282,39 +276,63 @@ class SpotifyWebPlayerToken:
|
|
|
282
276
|
"productType": "web_player",
|
|
283
277
|
}
|
|
284
278
|
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
279
|
+
# retry if gateway timeout error is returned.
|
|
280
|
+
loopTotalDelay:float = 0
|
|
281
|
+
LOOP_DELAY:float = 0.100
|
|
282
|
+
LOOP_TIMEOUT:float = 0.500
|
|
283
|
+
while True:
|
|
284
|
+
|
|
285
|
+
# trace.
|
|
286
|
+
_logsi.LogVerbose("Exchanging Spotify Web Player cookie credentials for OAuth2 authorization access token")
|
|
287
|
+
_logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (cookies)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqCookies)
|
|
288
|
+
_logsi.LogDictionary(SILevel.Debug, "%s http request: \"%s\" (headers)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqHeaders)
|
|
289
|
+
_logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (parms)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqParms)
|
|
290
290
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
291
|
+
# convert the spotify web player cookie credentials to an access token.
|
|
292
|
+
response = session.get(
|
|
293
|
+
SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN,
|
|
294
|
+
cookies=reqCookies,
|
|
295
|
+
headers=reqHeaders,
|
|
296
|
+
params=reqParms,
|
|
297
|
+
allow_redirects=False,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# trace.
|
|
301
|
+
if _logsi.IsOn(SILevel.Debug):
|
|
302
|
+
_logsi.LogObject(SILevel.Debug, "%s http response object - type=\"%s\", module=\"%s\"" % (tracePrefix, type(response).__name__, type(response).__module__), response)
|
|
303
|
+
|
|
304
|
+
# trace.
|
|
305
|
+
if _logsi.IsOn(SILevel.Debug):
|
|
306
|
+
_logsi.LogObject(SILevel.Debug, "%s http response [%s-%s] (response)" % (tracePrefix, response.status_code, response.reason), response)
|
|
307
|
+
if (response.headers):
|
|
308
|
+
_logsi.LogCollection(SILevel.Debug, "%s http response [%s-%s] (headers)" % (tracePrefix, response.status_code, response.reason), response.headers.items())
|
|
309
|
+
|
|
310
|
+
# raise exception if we could not get the access token, or if a redirect occured.
|
|
311
|
+
# the redirect usually indicates the sp_dc and sp_key are expired, and it's redirecting to a login page.
|
|
312
|
+
respHdrLocation:str = response.headers.get("Location", None)
|
|
313
|
+
if (response.status_code == 302 and respHdrLocation == "/get_access_token?reason=transport&productType=web_player&_authfailed=1"):
|
|
314
|
+
raise SpotifyApiError("Unsuccessful token request, received status 302 and Location header \"%s\". sp_dc and sp_key could be expired. Please update values in token storage file." % (respHdrLocation), None, logsi=_logsi)
|
|
315
|
+
|
|
316
|
+
# if successful request, then break out of the retry loop.
|
|
317
|
+
if response.status_code == 200:
|
|
318
|
+
_logsi.LogVerbose("Token exchange request was successful within %f seconds from initial request; processing results" % (loopTotalDelay))
|
|
319
|
+
break
|
|
320
|
+
|
|
321
|
+
# any other status code besides 504 (gateway timeout) is an exception event!
|
|
322
|
+
if response.status_code != 504:
|
|
323
|
+
raise SpotifyApiError("Token exchange request failed: %s - %s" % (response.status_code, response.reason), None, logsi=_logsi)
|
|
324
|
+
|
|
325
|
+
# only retry so many times before we give up;
|
|
326
|
+
if (loopTotalDelay > LOOP_TIMEOUT):
|
|
327
|
+
raise SpotifyApiError("Timed out waiting for Spotify Gateway to become available; gave up after %f seconds from initial request" % (loopTotalDelay), None, logsi=_logsi)
|
|
328
|
+
|
|
329
|
+
# trace.
|
|
330
|
+
_logsi.LogVerbose("Spotify Web Player cookie credentials token exchange request returned with a 504 status (Gateway timeout); request will be retried")
|
|
331
|
+
|
|
332
|
+
# wait just a bit between requests.
|
|
333
|
+
_logsi.LogVerbose("Delaying for %s seconds to allow Spotify Gateway to become available" % (LOOP_DELAY))
|
|
334
|
+
time.sleep(LOOP_DELAY)
|
|
335
|
+
loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
318
336
|
|
|
319
337
|
# load request response.
|
|
320
338
|
data = response.content.decode('utf-8')
|
|
@@ -347,15 +365,16 @@ class SpotifyWebPlayerToken:
|
|
|
347
365
|
# trace.
|
|
348
366
|
_logsi.LogObject(SILevel.Verbose, "Spotify Web Player access token object", self, excludeNonPublic=True)
|
|
349
367
|
|
|
368
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
350
369
|
except requests.TooManyRedirects as ex:
|
|
351
370
|
|
|
352
|
-
|
|
353
|
-
raise
|
|
371
|
+
# trace.
|
|
372
|
+
raise SpotifyApiError("Could not get Spotify Web Player access token; sp_dc and sp_key could be expired; Please update values in token storage file", ex, logsi=_logsi)
|
|
354
373
|
|
|
355
|
-
except Exception as ex:
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
raise
|
|
374
|
+
except Exception as ex:
|
|
375
|
+
|
|
376
|
+
# trace.
|
|
377
|
+
raise SpotifyApiError("Could not get Spotify Web Player access token", ex, logsi=_logsi)
|
|
359
378
|
|
|
360
379
|
finally:
|
|
361
380
|
|
|
File without changes
|
|
File without changes
|
{spotifywebapiPython-1.0.175.dist-info → spotifywebapiPython-1.0.177.dist-info}/top_level.txt
RENAMED
|
File without changes
|