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.

Files changed (23) hide show
  1. {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/RECORD +23 -23
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +16 -2
  5. spotifywebapipython/docs/index.html +2 -2
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapiPython/const.html +3 -3
  8. spotifywebapipython/docs/spotifywebapiPython/saappmessages.html +2 -2
  9. spotifywebapipython/docs/spotifywebapiPython/sautils.html +2 -2
  10. spotifywebapipython/docs/spotifywebapiPython/spotifyapierror.html +2 -2
  11. spotifywebapipython/docs/spotifywebapiPython/spotifyapimessage.html +2 -2
  12. spotifywebapipython/docs/spotifywebapiPython/spotifyauthtoken.html +2 -2
  13. spotifywebapipython/docs/spotifywebapiPython/spotifyclient.html +17 -14
  14. spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -2
  15. spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
  16. spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
  17. spotifywebapipython/spotifyclient.py +51 -48
  18. spotifywebapipython/zeroconfapi/credentials.py +4 -1
  19. spotifywebapipython/zeroconfapi/helpers.py +7 -0
  20. spotifywebapipython/zeroconfapi/zeroconfconnect.py +126 -2
  21. {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/LICENSE +0 -0
  22. {spotifywebapiPython-1.0.99.dist-info → spotifywebapiPython-1.0.101.dist-info}/WHEEL +0 -0
  23. {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:512ojhOuo1ktJprKbVcKyQ`.
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: `512ojhOuo1ktJprKbVcKyQ`
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
- # if a different user context has control of the device then we need to disconnect
6440
- # the current user context before connecting a different user.
6441
- if (info.HasActiveUser):
6442
- _logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for user context '%s'" % (scDevice.Title, deviceActiveUser))
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
- # did caller request user context switch bypass?
8838
- # if so, then just return the device.
8839
- if (not verifyUserContext):
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 do not switch the user context.
8843
+ # if not, then we cannot re-activate the device!
8845
8844
  if (self._SpotifyConnectUsername is None):
8846
- _logsi.LogVerbose("User context '%s' will be used for Spotify Connect device '%s', as a SpotifyConnectUsername parameter was not supplied" % (deviceActiveUser, deviceResult.Title))
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
- zcfResult:ZeroconfResponse
8850
-
8851
- # if a different user context has control of the device then we need to disconnect
8852
- # the current user context before connecting a different user.
8853
- if (info.HasActiveUser):
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=True)
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
- # it's a restricted device, so the Spotify Web API cannot control it!
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:512ojhOuo1ktJprKbVcKyQ`.
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:512ojhOuo1ktJprKbVcKyQ`.
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
- self.password: bytes = bytes(password, 'ascii')
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
- if info.TokenType == 'authorization_code':
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>