spotifywebapiPython 1.0.99__py3-none-any.whl → 1.0.101__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.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/RECORD +23 -23
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +16 -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 +17 -14
- spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
- spotifywebapipython/spotifyclient.py +51 -48
- spotifywebapipython/zeroconfapi/credentials.py +4 -1
- spotifywebapipython/zeroconfapi/helpers.py +7 -0
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +126 -2
- {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/top_level.txt +0 -0
|
@@ -745,6 +745,7 @@ class SpotifyClient:
|
|
|
745
745
|
|
|
746
746
|
# trace.
|
|
747
747
|
if (msg.HasRequestHeaders):
|
|
748
|
+
# "User-Agent: Spotify/8.9.76 iOS/18.1 (iPhone17,1)"
|
|
748
749
|
_logsi.LogCollection(SILevel.Verbose, "SpotifyClient http request: '%s' (headers)" % (url), msg.RequestHeaders.items())
|
|
749
750
|
|
|
750
751
|
# *** IMPORTANT ***
|
|
@@ -1022,7 +1023,7 @@ class SpotifyClient:
|
|
|
1022
1023
|
Example: `5AC9ZXA7nJ7oGWO911FuDG`
|
|
1023
1024
|
uris (str):
|
|
1024
1025
|
A comma-separated list of Spotify URIs to add; can be track or episode URIs.
|
|
1025
|
-
Example: `spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:episode:
|
|
1026
|
+
Example: `spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:episode:26c0zVyOv1lzfYpBXdh1zC`.
|
|
1026
1027
|
A maximum of 100 items can be specified in one request.
|
|
1027
1028
|
If null, the currently playing context uri value is used.
|
|
1028
1029
|
position (int):
|
|
@@ -5420,7 +5421,7 @@ class SpotifyClient:
|
|
|
5420
5421
|
Args:
|
|
5421
5422
|
episodeId (str):
|
|
5422
5423
|
The Spotify ID for the episode.
|
|
5423
|
-
Example: `
|
|
5424
|
+
Example: `26c0zVyOv1lzfYpBXdh1zC`
|
|
5424
5425
|
If null, the currently playing episode uri id value is used; a Spotify Free or Premium account
|
|
5425
5426
|
is required to correctly read the currently playing context.
|
|
5426
5427
|
market (str):
|
|
@@ -6436,10 +6437,12 @@ class SpotifyClient:
|
|
|
6436
6437
|
tokenStorageDir=self.TokenStorageDir,
|
|
6437
6438
|
tokenStorageFile=self.TokenStorageFile)
|
|
6438
6439
|
|
|
6439
|
-
#
|
|
6440
|
-
#
|
|
6441
|
-
|
|
6442
|
-
|
|
6440
|
+
# disconnect the device.
|
|
6441
|
+
# we will bypass disconnects for librespot devices (e.g. spotifyd, etc), as the
|
|
6442
|
+
# Spotify Connect Zeroconf `resetUsers` endpoint is not implemented and will
|
|
6443
|
+
# generate a 404 request response.
|
|
6444
|
+
if (info.BrandDisplayName != 'librespot'):
|
|
6445
|
+
_logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for current user context '%s'" % (scDevice.Title, deviceActiveUser))
|
|
6443
6446
|
zcfResult = zconn.Disconnect(delay)
|
|
6444
6447
|
status = status + 'disconnected from user context "%s";' % deviceActiveUser
|
|
6445
6448
|
|
|
@@ -8650,7 +8653,8 @@ class SpotifyClient:
|
|
|
8650
8653
|
True to refresh the Spotify Connect device list; otherwise, False to use the
|
|
8651
8654
|
Spotify Connect device list cache.
|
|
8652
8655
|
activateDevice (bool):
|
|
8653
|
-
True to activate the device if necessary; otherwise, False.
|
|
8656
|
+
True to activate the device if necessary; otherwise, False.
|
|
8657
|
+
Default is True.
|
|
8654
8658
|
delay (float):
|
|
8655
8659
|
Time delay (in seconds) to wait AFTER issuing any command to the device.
|
|
8656
8660
|
This delay will give the spotify zeroconf api time to process the change before
|
|
@@ -8679,14 +8683,12 @@ class SpotifyClient:
|
|
|
8679
8683
|
|
|
8680
8684
|
The `SpotifyConnectDevice` object is returned if a match is found; otherwise, a null value is
|
|
8681
8685
|
returned.
|
|
8682
|
-
|
|
8683
|
-
A user context switch will also be performed if a device id is resolved AND the active user of the
|
|
8684
|
-
device does not match the `SpotifyConnectUsername` argument specified on the class constructor.
|
|
8685
|
-
The user context switch is bypassed if the `verifyUserContext` argument is False, or if the
|
|
8686
|
-
`SpotifyConnectUsername` was not supplied on the class constructor. If the user context is to be
|
|
8687
|
-
switched, then a Disconnect will be issued if a user context is active on the device followed by
|
|
8688
|
-
a Connect to the user context specified on the class constructor.
|
|
8689
8686
|
|
|
8687
|
+
A Spotify Connect Zeroconf Disconnect (e.g. `resetUsers`) will be issued if the `activateDevice`
|
|
8688
|
+
argument is True, AND the device is not a dynamic device. This will temporarily remove the device
|
|
8689
|
+
from the Spotify Connect device list, but the device will be re-added when the Connect
|
|
8690
|
+
call is issued.
|
|
8691
|
+
|
|
8690
8692
|
<details>
|
|
8691
8693
|
<summary>Sample Code</summary>
|
|
8692
8694
|
```python
|
|
@@ -8779,7 +8781,8 @@ class SpotifyClient:
|
|
|
8779
8781
|
# did we resolve the device id?
|
|
8780
8782
|
if deviceResult is not None:
|
|
8781
8783
|
|
|
8782
|
-
# at this point, we will try to activate the device if it requires it
|
|
8784
|
+
# at this point, we will try to activate the device if it requires it
|
|
8785
|
+
# of if the caller forces us to do so (e.g. `activateDevice=True`).
|
|
8783
8786
|
|
|
8784
8787
|
# reset device reconnected flag.
|
|
8785
8788
|
scDevice.WasReConnected = False
|
|
@@ -8824,34 +8827,31 @@ class SpotifyClient:
|
|
|
8824
8827
|
# store the currently active user of the device, in case we need to switch users later on.
|
|
8825
8828
|
deviceActiveUser = info.ActiveUser.lower()
|
|
8826
8829
|
|
|
8827
|
-
# was device activation requested? if not, then we are done.
|
|
8828
|
-
if activateDevice == False:
|
|
8829
|
-
_logsi.LogVerbose("Activation not requested for Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8830
|
-
break
|
|
8831
|
-
|
|
8832
8830
|
# is this a dynamic device? if so, then there is no need to activate it as it's already active.
|
|
8833
8831
|
if (discoverResult.IsDynamicDevice):
|
|
8834
8832
|
_logsi.LogVerbose("Activation not required for dynamic Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8835
8833
|
break
|
|
8836
8834
|
|
|
8837
|
-
#
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
_logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as user chose to bypass user context switch" % (deviceActiveUser, deviceResult.Title))
|
|
8835
|
+
# was device activation requested? if not, then we are done.
|
|
8836
|
+
if (not activateDevice):
|
|
8837
|
+
_logsi.LogVerbose("Activation not requested for Spotify Connect device: '%s'" % (deviceResult.Title))
|
|
8841
8838
|
break
|
|
8842
8839
|
|
|
8840
|
+
zcfResult:ZeroconfResponse
|
|
8841
|
+
|
|
8843
8842
|
# was a Spotify Connect user account specified on the class constructor?
|
|
8844
|
-
# if not, then
|
|
8843
|
+
# if not, then we cannot re-activate the device!
|
|
8845
8844
|
if (self._SpotifyConnectUsername is None):
|
|
8846
|
-
|
|
8845
|
+
raise SpotifyApiError("A SpotifyConnectUsername parameter was not supplied; could not reactivate Spotify Connect device '%s'" % (deviceResult.Title), logsi=_logsi)
|
|
8846
|
+
#_logsi.LogVerbose("A SpotifyConnectUsername parameter was not supplied; could not reactivate Spotify Connect device '%s'" % (deviceResult.Title))
|
|
8847
8847
|
break
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
#
|
|
8852
|
-
#
|
|
8853
|
-
if (info.
|
|
8854
|
-
_logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for user context '%s'" % (deviceResult.Title, deviceActiveUser))
|
|
8848
|
+
|
|
8849
|
+
# disconnect the device.
|
|
8850
|
+
# we will bypass disconnects for librespot devices (e.g. spotifyd, etc), as the
|
|
8851
|
+
# Spotify Connect Zeroconf `resetUsers` endpoint is not implemented and will
|
|
8852
|
+
# generate a 404 request response.
|
|
8853
|
+
if (info.BrandDisplayName != 'librespot'):
|
|
8854
|
+
_logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for current user context '%s'" % (deviceResult.Title, deviceActiveUser))
|
|
8855
8855
|
zcfResult = zconn.Disconnect(delay)
|
|
8856
8856
|
|
|
8857
8857
|
# connect the device to OUR Spotify Connect user context.
|
|
@@ -10896,13 +10896,12 @@ class SpotifyClient:
|
|
|
10896
10896
|
# has the device id been resolved?
|
|
10897
10897
|
if (resolveDeviceId):
|
|
10898
10898
|
|
|
10899
|
-
# ensure the specified device id / name is active and available, and
|
|
10899
|
+
# no - ensure the specified device id / name is active and available, and
|
|
10900
10900
|
# the user context is set correctly.
|
|
10901
10901
|
scDevice = self.GetSpotifyConnectDevice(
|
|
10902
10902
|
deviceId,
|
|
10903
10903
|
refreshDeviceList=False,
|
|
10904
|
-
activateDevice=True
|
|
10905
|
-
verifyUserContext=True)
|
|
10904
|
+
activateDevice=True)
|
|
10906
10905
|
|
|
10907
10906
|
# did we find the device?
|
|
10908
10907
|
# if so, then use the found device id in case a device name was specified.
|
|
@@ -11038,13 +11037,12 @@ class SpotifyClient:
|
|
|
11038
11037
|
# has the device id been resolved?
|
|
11039
11038
|
if (resolveDeviceId):
|
|
11040
11039
|
|
|
11041
|
-
# ensure the specified device id / name is active and available, and
|
|
11040
|
+
# no - ensure the specified device id / name is active and available, and
|
|
11042
11041
|
# the user context is set correctly.
|
|
11043
11042
|
scDevice = self.GetSpotifyConnectDevice(
|
|
11044
11043
|
deviceId,
|
|
11045
11044
|
refreshDeviceList=False,
|
|
11046
|
-
activateDevice=True
|
|
11047
|
-
verifyUserContext=True)
|
|
11045
|
+
activateDevice=True)
|
|
11048
11046
|
|
|
11049
11047
|
# did we find the device?
|
|
11050
11048
|
# if so, then use the found device id in case a device name was specified.
|
|
@@ -11166,13 +11164,12 @@ class SpotifyClient:
|
|
|
11166
11164
|
# has the device id been resolved?
|
|
11167
11165
|
if (resolveDeviceId):
|
|
11168
11166
|
|
|
11169
|
-
# ensure the specified device id / name is active and available, and
|
|
11167
|
+
# no - ensure the specified device id / name is active and available, and
|
|
11170
11168
|
# the user context is set correctly.
|
|
11171
11169
|
scDevice = self.GetSpotifyConnectDevice(
|
|
11172
11170
|
deviceId,
|
|
11173
11171
|
refreshDeviceList=False,
|
|
11174
|
-
activateDevice=True
|
|
11175
|
-
verifyUserContext=True)
|
|
11172
|
+
activateDevice=True)
|
|
11176
11173
|
|
|
11177
11174
|
# did we find the device?
|
|
11178
11175
|
# if so, then use the found device id in case a device name was specified.
|
|
@@ -11696,7 +11693,6 @@ class SpotifyClient:
|
|
|
11696
11693
|
# call newer method to resolve the device id.
|
|
11697
11694
|
scDevice:SpotifyConnectDevice = self.GetSpotifyConnectDevice(
|
|
11698
11695
|
deviceValue=deviceValue,
|
|
11699
|
-
verifyUserContext=verifyUserContext,
|
|
11700
11696
|
verifyTimeout=verifyTimeout,
|
|
11701
11697
|
refreshDeviceList=True,
|
|
11702
11698
|
activateDevice=True,
|
|
@@ -12040,6 +12036,7 @@ class SpotifyClient:
|
|
|
12040
12036
|
play:bool=True,
|
|
12041
12037
|
delay:float=0.50,
|
|
12042
12038
|
refreshDeviceList:bool=True,
|
|
12039
|
+
forceActivateDevice:bool=True,
|
|
12043
12040
|
) -> None:
|
|
12044
12041
|
"""
|
|
12045
12042
|
Transfer playback to a new Spotify Connect device and optionally begin playback.
|
|
@@ -12065,6 +12062,11 @@ class SpotifyClient:
|
|
|
12065
12062
|
True to refresh the Spotify Connect device list; otherwise, False to use the
|
|
12066
12063
|
Spotify Connect device list cache.
|
|
12067
12064
|
Default is True.
|
|
12065
|
+
forceActivateDevice (bool):
|
|
12066
|
+
True to issue a Spotify Connect Disconnect call prior to transfer, which will
|
|
12067
|
+
force the device to reconnect to Spotify Connect; otherwise, False to not
|
|
12068
|
+
disconnect.
|
|
12069
|
+
Default is True.
|
|
12068
12070
|
|
|
12069
12071
|
Raises:
|
|
12070
12072
|
SpotifyWebApiError:
|
|
@@ -12096,6 +12098,7 @@ class SpotifyClient:
|
|
|
12096
12098
|
apiMethodParms.AppendKeyValue("play", play)
|
|
12097
12099
|
apiMethodParms.AppendKeyValue("delay", delay)
|
|
12098
12100
|
apiMethodParms.AppendKeyValue("refreshDeviceList", refreshDeviceList)
|
|
12101
|
+
apiMethodParms.AppendKeyValue("forceActivateDevice", forceActivateDevice)
|
|
12099
12102
|
_logsi.LogMethodParmList(SILevel.Verbose, "Transfer playback to a new Spotify Connect device", apiMethodParms)
|
|
12100
12103
|
|
|
12101
12104
|
# validations.
|
|
@@ -12105,9 +12108,8 @@ class SpotifyClient:
|
|
|
12105
12108
|
# the user context is set correctly.
|
|
12106
12109
|
scDevice = self.GetSpotifyConnectDevice(
|
|
12107
12110
|
deviceId,
|
|
12108
|
-
verifyUserContext=True,
|
|
12109
12111
|
refreshDeviceList=refreshDeviceList,
|
|
12110
|
-
activateDevice=
|
|
12112
|
+
activateDevice=forceActivateDevice)
|
|
12111
12113
|
|
|
12112
12114
|
# did we find the device?
|
|
12113
12115
|
# if so, then use the found device id in case a device name was specified.
|
|
@@ -12117,7 +12119,8 @@ class SpotifyClient:
|
|
|
12117
12119
|
|
|
12118
12120
|
# is this a Sonos device?
|
|
12119
12121
|
# if so, then control was automatically transferred by activating the device.
|
|
12120
|
-
#
|
|
12122
|
+
# note that we also cannot stop nor start play on the device either, as it's
|
|
12123
|
+
# considered a restricted device and the Spotify Web API cannot control it!
|
|
12121
12124
|
if scDevice.DeviceInfo.IsBrandSonos:
|
|
12122
12125
|
_logsi.LogVerbose('Sonos device detected; bypassing call to Spotify Web API Transfer Playback endpoint')
|
|
12123
12126
|
return
|
|
@@ -12608,7 +12611,7 @@ class SpotifyClient:
|
|
|
12608
12611
|
Example: `5AC9ZXA7nJ7oGWO911FuDG`
|
|
12609
12612
|
uris (str):
|
|
12610
12613
|
A comma-separated list of Spotify URIs to remove; can be track or episode URIs.
|
|
12611
|
-
Example: `spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:episode:
|
|
12614
|
+
Example: `spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:episode:26c0zVyOv1lzfYpBXdh1zC`.
|
|
12612
12615
|
A maximum of 100 items can be specified in one request.
|
|
12613
12616
|
If null, the currently playing context uri value is used.
|
|
12614
12617
|
snapshotId (str):
|
|
@@ -13015,7 +13018,7 @@ class SpotifyClient:
|
|
|
13015
13018
|
Example: `5AC9ZXA7nJ7oGWO911FuDG`
|
|
13016
13019
|
uris (str):
|
|
13017
13020
|
A comma-separated list of Spotify URIs to replace; can be track or episode URIs.
|
|
13018
|
-
Example: `spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:episode:
|
|
13021
|
+
Example: `spotify:track:4iV5W9uYEdYUVa79Axb7Rh,spotify:episode:26c0zVyOv1lzfYpBXdh1zC`.
|
|
13019
13022
|
A maximum of 100 items can be specified in one request.
|
|
13020
13023
|
|
|
13021
13024
|
Returns:
|
|
@@ -29,6 +29,9 @@ class Credentials:
|
|
|
29
29
|
|
|
30
30
|
# initialize storage.
|
|
31
31
|
self.username: bytes = bytes(username, 'ascii')
|
|
32
|
-
|
|
32
|
+
if (isinstance(password, bytes)):
|
|
33
|
+
self.password: bytes = password
|
|
34
|
+
else:
|
|
35
|
+
self.password: bytes = bytes(password, 'ascii')
|
|
33
36
|
self.auth_type: AuthenticationTypes = authenticationType
|
|
34
37
|
|
|
@@ -43,6 +43,13 @@ def b64_to_int(value: str):
|
|
|
43
43
|
return int.from_bytes(b64decode(value), 'big')
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def b64str_to_bytes(value: str):
|
|
47
|
+
"""
|
|
48
|
+
Decodes a base64-encoded string to a byte array.
|
|
49
|
+
"""
|
|
50
|
+
return b64decode(value)
|
|
51
|
+
|
|
52
|
+
|
|
46
53
|
def string_to_int(value):
|
|
47
54
|
"""
|
|
48
55
|
Converts a string to a big integer.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# external package imports.
|
|
2
2
|
import json
|
|
3
|
+
import os.path
|
|
3
4
|
import requests
|
|
4
5
|
from requests.models import Response
|
|
5
6
|
import time
|
|
@@ -7,7 +8,7 @@ import time
|
|
|
7
8
|
# our package imports.
|
|
8
9
|
from .blobbuilder import BlobBuilder
|
|
9
10
|
from .credentials import Credentials, AuthenticationTypes
|
|
10
|
-
from .helpers import int_to_b64str
|
|
11
|
+
from .helpers import int_to_b64str, b64str_to_bytes
|
|
11
12
|
from .spotifyzeroconfapierror import SpotifyZeroconfApiError
|
|
12
13
|
from .zeroconfresponse import ZeroconfResponse
|
|
13
14
|
from .zeroconfgetinfo import ZeroconfGetInfo
|
|
@@ -379,6 +380,12 @@ class ZeroconfConnect:
|
|
|
379
380
|
.. include:: ../../docs/include/samplecode/ZeroconfConnect/Connect.py
|
|
380
381
|
```
|
|
381
382
|
</details>
|
|
383
|
+
<details>
|
|
384
|
+
<summary>Sample Code - spotifyD</summary>
|
|
385
|
+
```python
|
|
386
|
+
.. include:: ../../docs/include/samplecode/ZeroconfConnect/Connect_SPOTIFYD.py
|
|
387
|
+
```
|
|
388
|
+
</details>
|
|
382
389
|
"""
|
|
383
390
|
apiMethodName:str = 'Connect'
|
|
384
391
|
apiMethodParms:SIMethodParmListContext = None
|
|
@@ -559,8 +566,37 @@ class ZeroconfConnect:
|
|
|
559
566
|
if (info.PublicKey == 'SU5WQUxJRA==') and (info.Availability == 'NOT-LOADED'):
|
|
560
567
|
includeOriginDeviceInfo = True
|
|
561
568
|
|
|
569
|
+
# special processing for librespot, spotifyd.
|
|
570
|
+
if info.ModelDisplayName == 'librespot':
|
|
571
|
+
|
|
572
|
+
# we will use credentials from a librespot credentials.json file.
|
|
573
|
+
# this requires that the user copy the "credentials.json" file from the
|
|
574
|
+
# librespot cache location (e.g. "/home/user/.cache/spotifyd/credentials.json", etc)
|
|
575
|
+
# and place it in the same folder as the token storage file with a new name
|
|
576
|
+
# of "SpotifyWebApiPython_librespot_credentials.json".
|
|
577
|
+
|
|
578
|
+
# get credentials from librespot credentials file.
|
|
579
|
+
credentials:dict = self._LoadLibrespotCredentials(loginId)
|
|
580
|
+
|
|
581
|
+
# create credentials and builder objects.
|
|
582
|
+
librespot_username = credentials.get('username','')
|
|
583
|
+
librespot_password = b64str_to_bytes(credentials.get('auth_data',''))
|
|
584
|
+
credentials:Credentials = Credentials(librespot_username, librespot_password, AuthenticationTypes.STORED_SPOTIFY_CREDENTIALS)
|
|
585
|
+
builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
|
|
586
|
+
|
|
587
|
+
# set defaults used by this token type:
|
|
588
|
+
# - clientKey should be the getInfo response PublicKey (base64 encoded).
|
|
589
|
+
# - include origin device information in the addUser request.
|
|
590
|
+
# - tokenType should be set to '' (not used by librespot).
|
|
591
|
+
clientKey:str = builder.dh_keys.PublicKeyBase64String
|
|
592
|
+
includeOriginDeviceInfo = True
|
|
593
|
+
tokenType = ''
|
|
594
|
+
|
|
595
|
+
# formulate the blob.
|
|
596
|
+
blob = builder.build()
|
|
597
|
+
|
|
562
598
|
# special processing for tokenType "authorization_code":
|
|
563
|
-
|
|
599
|
+
elif info.TokenType == 'authorization_code':
|
|
564
600
|
|
|
565
601
|
# trace.
|
|
566
602
|
_logsi.LogVerbose("Spotify Connect token type is '%s' - using Spotify Desktop Client OAuth2 token to connect: '%s' (ip=%s)" % (info.TokenType, info.RemoteName, self._HostIpAddress))
|
|
@@ -819,6 +855,90 @@ class ZeroconfConnect:
|
|
|
819
855
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
820
856
|
|
|
821
857
|
|
|
858
|
+
def _LoadLibrespotCredentials(self, loginId:str) -> dict:
|
|
859
|
+
"""
|
|
860
|
+
Loads spotify user credentials from a librespot credentials.json file.
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
loginId (str):
|
|
864
|
+
Spotify canonical login id of the authenticating user.
|
|
865
|
+
Example = `3758dfdsfjk435hjk6k79lm0n3c4`
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
A credentials dictionary, if one was found in the credentials storage file;
|
|
869
|
+
otherwise, null.
|
|
870
|
+
"""
|
|
871
|
+
apiMethodName:str = '_LoadLibrespotCredentials'
|
|
872
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
873
|
+
|
|
874
|
+
try:
|
|
875
|
+
|
|
876
|
+
# trace.
|
|
877
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
878
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Loading librespot credentials from storage", apiMethodParms)
|
|
879
|
+
|
|
880
|
+
# librespot credntials.json file format as of 2024/09/28
|
|
881
|
+
# {
|
|
882
|
+
# "username": "31l77xxxxxxxxxxxxxxxxxxxxxxx",
|
|
883
|
+
# "auth_type": 1,
|
|
884
|
+
# "auth_data": "QVFET2h6 ... aFp3OHg="
|
|
885
|
+
# }
|
|
886
|
+
|
|
887
|
+
# formulate the credentials file path.
|
|
888
|
+
filePath:str = os.path.join(self._TokenStorageDir, "SpotifyWebApiPython_librespot_credentials.json")
|
|
889
|
+
|
|
890
|
+
# does the file exist? if not, then it's an error as we are trying to authenticate!
|
|
891
|
+
if (not os.path.exists(filePath)):
|
|
892
|
+
raise SpotifyApiError("Could not find librespot authorization credentials file (%s). This file is required in order to authenticate to a device or service that utilizes the librespot application." % (filePath), logsi=_logsi)
|
|
893
|
+
|
|
894
|
+
# open the credentials file, and load its contents.
|
|
895
|
+
_logsi.LogVerbose('Opening librespot credentials file: "%s"' % (filePath))
|
|
896
|
+
with open(filePath, 'r') as f:
|
|
897
|
+
credentials:dict = json.load(f)
|
|
898
|
+
_logsi.LogDictionary(SILevel.Verbose, 'librespot credentials were loaded successfully', credentials, prettyPrint=True)
|
|
899
|
+
|
|
900
|
+
# prepare for comparison.
|
|
901
|
+
loginId = loginId.lower()
|
|
902
|
+
credential:dict = None
|
|
903
|
+
|
|
904
|
+
# check for multiple credential format.
|
|
905
|
+
if (isinstance(credentials, list)):
|
|
906
|
+
|
|
907
|
+
# file contents are an array of librespot credentials.
|
|
908
|
+
for credential in credentials:
|
|
909
|
+
if (credential.get('username','').lower() == loginId):
|
|
910
|
+
break
|
|
911
|
+
|
|
912
|
+
else:
|
|
913
|
+
|
|
914
|
+
# file contents are a single librespot credential.
|
|
915
|
+
credential = credentials
|
|
916
|
+
|
|
917
|
+
# does the found credential match the requested login? if not, then reset the credential.
|
|
918
|
+
if (credential is not None):
|
|
919
|
+
if (credential.get('username','').lower() != loginId):
|
|
920
|
+
credential = None
|
|
921
|
+
|
|
922
|
+
# if no credentials then it's an error.
|
|
923
|
+
if (credential is None):
|
|
924
|
+
raise SpotifyApiError("librespot authorization credentials file (%s) did not contain credentials for canonical loginid (%s)." % (filePath, loginId), logsi=_logsi)
|
|
925
|
+
|
|
926
|
+
# return credentials to caller.
|
|
927
|
+
return credential
|
|
928
|
+
|
|
929
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
930
|
+
except Exception as ex:
|
|
931
|
+
|
|
932
|
+
# trace.
|
|
933
|
+
_logsi.LogException('Could not load librespot authorization credentials file (%s).' % (filePath), ex)
|
|
934
|
+
raise
|
|
935
|
+
|
|
936
|
+
finally:
|
|
937
|
+
|
|
938
|
+
# trace.
|
|
939
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
940
|
+
|
|
941
|
+
|
|
822
942
|
def Disconnect(
|
|
823
943
|
self,
|
|
824
944
|
delay:float=0.50,
|
|
@@ -848,6 +968,10 @@ class ZeroconfConnect:
|
|
|
848
968
|
|
|
849
969
|
The currently logged in user (if any) will be logged out of Spotify Connect, and the
|
|
850
970
|
device id removed from the active Spotify Connect device list.
|
|
971
|
+
|
|
972
|
+
This method should not be attempted for devices or services that utilize `librespot`,
|
|
973
|
+
such as `spotifyd`. The `librespot` library does not implement the `resetUsers`
|
|
974
|
+
Spotify Connect Zeroconf endpoint, and will fail with a 404 error.
|
|
851
975
|
|
|
852
976
|
<details>
|
|
853
977
|
<summary>Sample Code</summary>
|
|
File without changes
|
|
File without changes
|
{spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/top_level.txt
RENAMED
|
File without changes
|