spotifywebapiPython 1.0.294__py3-none-any.whl → 1.0.296__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.
Files changed (26) hide show
  1. spotifywebapipython/const.py +1 -1
  2. spotifywebapipython/docs/__init__.html +16 -2
  3. spotifywebapipython/docs/index.html +2 -2
  4. spotifywebapipython/docs/search.js +1 -1
  5. spotifywebapipython/docs/spotifywebapipython/const.html +3 -3
  6. spotifywebapipython/docs/spotifywebapipython/saappmessages.html +2 -2
  7. spotifywebapipython/docs/spotifywebapipython/sautils.html +2 -2
  8. spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html +2 -2
  9. spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html +2 -2
  10. spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html +2 -2
  11. spotifywebapipython/docs/spotifywebapipython/spotifyclient.html +2 -2
  12. spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
  13. spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html +2 -2
  14. spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.html +2 -2
  15. spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
  16. spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
  17. spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
  18. spotifywebapipython/spotifyclient.py +1 -1
  19. spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py +126 -104
  20. spotifywebapipython/zeroconfapi/zeroconfconnect.py +128 -0
  21. spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +25 -0
  22. {spotifywebapipython-1.0.294.dist-info → spotifywebapipython-1.0.296.dist-info}/METADATA +1 -1
  23. {spotifywebapipython-1.0.294.dist-info → spotifywebapipython-1.0.296.dist-info}/RECORD +26 -26
  24. {spotifywebapipython-1.0.294.dist-info → spotifywebapipython-1.0.296.dist-info}/LICENSE +0 -0
  25. {spotifywebapipython-1.0.294.dist-info → spotifywebapipython-1.0.296.dist-info}/WHEEL +0 -0
  26. {spotifywebapipython-1.0.294.dist-info → spotifywebapipython-1.0.296.dist-info}/top_level.txt +0 -0
@@ -692,6 +692,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
692
692
  transferPlayback:bool=False,
693
693
  timeoutActivation:float=15.0,
694
694
  timeoutTransfer:float=10.0,
695
+ activateAppMediaReceiverOnGroupMembers:bool=False,
695
696
  ) -> str:
696
697
  """
697
698
  Activates the Spotify Cast Application on the specified Chromecast device.
@@ -712,18 +713,22 @@ class SpotifyConnectDirectoryTask(threading.Thread):
712
713
 
713
714
  Args:
714
715
  deviceName (str):
715
- Chromecast device friendly name (or device id) to activate.
716
+ Chromecast device friendly name (or device id) to activate.
716
717
  transferPlayback (bool):
717
718
  True to transfer playback to the device; otherwise, False to just activate
718
- the Spotify Cast App on the device.
719
+ the Spotify Cast App on the device.
719
720
  timeoutActivation (float):
720
721
  Amount of time to wait (in seconds) for the Spotify Cast App to be fully activated,
721
722
  the user logged in, and ready for transfer of playback.
722
- Default is 15 seconds.
723
+ Default is 15 seconds.
723
724
  timeoutTransfer (float):
724
725
  If `transferPlayback` argument is True, the amount of time to wait (in seconds)
725
726
  for the transfer to complete; otherwise, argument is ignored.
726
- Default is 10 seconds.
727
+ Default is 10 seconds.
728
+ activateAppMediaReceiverOnGroupMembers (bool):
729
+ True to force start the APP_MEDIA_RECEIVER on each group member cast device prior
730
+ to activating the Spotify Cast App; otherwise, False (default) to not start the
731
+ APP_MEDIA_RECEIVER on any group devices.
727
732
 
728
733
  Returns:
729
734
  The deviceId that was activated.
@@ -739,6 +744,7 @@ class SpotifyConnectDirectoryTask(threading.Thread):
739
744
  apiMethodParms.AppendKeyValue("transferPlayback", transferPlayback)
740
745
  apiMethodParms.AppendKeyValue("timeoutActivation", timeoutActivation)
741
746
  apiMethodParms.AppendKeyValue("timeoutTransfer", timeoutTransfer)
747
+ apiMethodParms.AppendKeyValue("activateAppMediaReceiverOnGroupMembers", activateAppMediaReceiverOnGroupMembers)
742
748
  _logsi.LogMethodParmList(SILevel.Verbose, "Activating Spotify App on Chromecast device: \"%s\"" % (deviceName), apiMethodParms)
743
749
 
744
750
  # validations.
@@ -829,13 +835,13 @@ class SpotifyConnectDirectoryTask(threading.Thread):
829
835
 
830
836
  # for groups, the SpotifyAppTask must be activated on the group leader (e.g. coordinator).
831
837
  # the cast zeroconf info may not contain the group leader host info, as it is constantly
832
- # being updated as devices are added / removed from the group. this can lead to the
833
- # SpotifyAppTask being started on a member of the group that is not the leader, which will
834
- # cause other devices in the group not to play.
835
- #
836
- # to combat this, we will call the `get_multizone_status` function that gives us the
837
- # current group leader host info for each group. we will then create a cast device to
838
- # the group leader host address, and start the SpotifyCastApp on that specific host.
838
+ # being updated as devices are added / removed from the group. the resolution of the group
839
+ # leader should have been handled by the `_VerifyMultizoneGroupLeader` call above.
840
+ #
841
+ # if this the the second attempt at activating the Spotify Cast App, then we will try
842
+ # starting the APP_MEDIA_RECEIVER on each group member device, as well as calling the
843
+ # `get_multizone_status` function again to verify the status of the zone before trying to
844
+ # start the Spotify Cast App.
839
845
 
840
846
  # trace.
841
847
  if (_logsi.IsOn(SILevel.Verbose)):
@@ -845,102 +851,109 @@ class SpotifyConnectDirectoryTask(threading.Thread):
845
851
  groupHost:str = scDevice.DiscoveryResult.HostIpAddress
846
852
  groupPort:int = scDevice.DiscoveryResult.HostIpPort
847
853
 
848
- try:
854
+ # are we starting the APP_MEDIA_RECEIVER on each group member?
855
+ if (activateAppMediaReceiverOnGroupMembers != True):
849
856
 
850
- # request a multizone group member update.
851
- # note that this will just send a request to the cast group device for its
852
- # status, which will then update the members list via event listener callbacks.
853
- castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(castInfo.uuid), None)
854
- if (castMultizoneController) and (castMultizoneController._socket_client):
855
- castMultizoneController.update_members()
856
- time.sleep(2.0) # give controller some time to update member list.
857
-
858
- # activate media receiver app for each member in the group.
859
- # process all device members in the group / zone.
860
- for zone_member_uuid in castMultizoneController.members:
861
-
862
- # get castInfo instance so we can connect to the device.
863
- zone_member_castInfo = self._CastBrowser.devices.get(UUID(zone_member_uuid))
864
- if zone_member_castInfo is not None:
865
-
866
- # get cast device instance.
867
- zone_member_castDevice = get_chromecast_from_cast_info(
868
- cast_info=zone_member_castInfo,
869
- zconf=self._ZeroconfInstance,
870
- tries=2,
871
- retry_wait=0.5,
872
- timeout=5)
873
-
874
- # wait for the cast device to provide an initial status.
875
- zone_member_castDevice.wait(10)
876
-
877
- # formulate title for trace messages.
878
- trc_zone_member_title:str = "\"%s\" [ip=%s:%s]" % (zone_member_castDevice.cast_info.friendly_name, zone_member_castDevice.socket_client.host, zone_member_castDevice.socket_client.port)
879
-
880
- # trace.
881
- _logsi.LogObject(SILevel.Verbose, "%s - Cast group \"%s\" member Chromecast device status: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), zone_member_castDevice.status, colorValue=SIColors.Coral)
882
-
883
- # if current app is APP_SPOTIFY, then stop the app before starting the media receiver.
884
- if (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY):
885
- _logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
886
- zone_member_castDevice.quit_app(timeout=5.0)
887
- elif (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY_CONNECT):
888
- _logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY_CONNECT) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
889
- zone_member_castDevice.quit_app(timeout=5.0)
890
-
891
- # start the media receiver app.
892
- # this will stabilize routing for the group using the Default Media Receiver.
893
- _logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
894
- zone_member_castDevice.start_app(APP_MEDIA_RECEIVER, force_launch=True, timeout=10.0)
895
-
896
- # start the connection worker thread, if needed.
897
- # failure to do this will result in the following exception being thrown when
898
- # the `launch_app()` method is called:
899
- # - `pychromecast.error.NotConnected: Chromecast unknown:8009 is connecting...`
900
- if (zone_member_castDevice.is_idle):
901
- if (zone_member_castDevice.socket_client is not None) and (zone_member_castDevice.socket_client.first_connection):
902
- _logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER connection worker thread for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
903
- zone_member_castDevice.start()
904
- zone_member_castDevice.wait(timeout=5.0)
905
-
906
- # media receiver app needs time to start.
907
- time.sleep(2.0)
857
+ _logsi.LogVerbose("Bypassing start of APP_MEDIA_RECEIVER for group: %s" % (scDevice.Title), colorValue=SIColors.Coral)
908
858
 
909
- except Exception as ex:
859
+ else:
860
+
861
+ try:
862
+
863
+ # request a multizone group member update.
864
+ # note that this will just send a request to the cast group device for its
865
+ # status, which will then update the members list via event listener callbacks.
866
+ castMultizoneController:MultizoneController = self._CastMultiZoneControllers.get(str(castInfo.uuid), None)
867
+ if (castMultizoneController) and (castMultizoneController._socket_client):
868
+ castMultizoneController.update_members()
869
+ time.sleep(2.0) # give controller some time to update member list.
870
+
871
+ # activate media receiver app for each member in the group.
872
+ # process all device members in the group / zone.
873
+ for zone_member_uuid in castMultizoneController.members:
874
+
875
+ # get castInfo instance so we can connect to the device.
876
+ zone_member_castInfo = self._CastBrowser.devices.get(UUID(zone_member_uuid))
877
+ if zone_member_castInfo is not None:
878
+
879
+ # get cast device instance.
880
+ zone_member_castDevice = get_chromecast_from_cast_info(
881
+ cast_info=zone_member_castInfo,
882
+ zconf=self._ZeroconfInstance,
883
+ tries=2,
884
+ retry_wait=0.5,
885
+ timeout=5)
886
+
887
+ # wait for the cast device to provide an initial status.
888
+ zone_member_castDevice.wait(10)
889
+
890
+ # formulate title for trace messages.
891
+ trc_zone_member_title:str = "\"%s\" [ip=%s:%s]" % (zone_member_castDevice.cast_info.friendly_name, zone_member_castDevice.socket_client.host, zone_member_castDevice.socket_client.port)
892
+
893
+ # trace.
894
+ _logsi.LogObject(SILevel.Verbose, "%s - Cast group \"%s\" member Chromecast device status: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), zone_member_castDevice.status, colorValue=SIColors.Coral)
895
+
896
+ # if current app is APP_SPOTIFY, then stop the app before starting the media receiver.
897
+ if (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY):
898
+ _logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
899
+ zone_member_castDevice.quit_app(timeout=5.0)
900
+ elif (zone_member_castDevice.status) and (zone_member_castDevice.status.app_id == APP_SPOTIFY_CONNECT):
901
+ _logsi.LogVerbose("%s - Issuing quit_app (APP_SPOTIFY_CONNECT) for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
902
+ zone_member_castDevice.quit_app(timeout=5.0)
903
+
904
+ # start the media receiver app.
905
+ # this will stabilize routing for the group using the Default Media Receiver.
906
+ _logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
907
+ zone_member_castDevice.start_app(APP_MEDIA_RECEIVER, force_launch=True, timeout=10.0)
908
+
909
+ # start the connection worker thread, if needed.
910
+ # failure to do this will result in the following exception being thrown when
911
+ # the `launch_app()` method is called:
912
+ # - `pychromecast.error.NotConnected: Chromecast unknown:8009 is connecting...`
913
+ if (zone_member_castDevice.is_idle):
914
+ if (zone_member_castDevice.socket_client is not None) and (zone_member_castDevice.socket_client.first_connection):
915
+ _logsi.LogVerbose("%s - Starting Chromecast default APP_MEDIA_RECEIVER connection worker thread for group \"%s\" member device: %s" % (self.name, castInfo.friendly_name, trc_zone_member_title), colorValue=SIColors.Coral)
916
+ zone_member_castDevice.start()
917
+ zone_member_castDevice.wait(timeout=5.0)
918
+
919
+ # media receiver app needs time to start.
920
+ time.sleep(2.0)
921
+
922
+ except Exception as ex:
910
923
 
911
- # trace.
912
- _logsi.LogException("Chromecast Multizone group update_members exception: %s" % (str(ex)), ex, logToSystemLogger=False)
913
- # ignore exception, as nothing can be done about it.
914
-
915
- # get current multizone status for all groups.
916
- castMultiZoneStatus:MultizoneStatus = get_multizone_status(castInfo.host, castInfo.services, self.ZeroconfInstance, 5)
917
- _logsi.LogObject(SILevel.Verbose, "%s - Chromecast group multizone status for device: %s [ip=%s:%s]" % (self.name, scDevice.Title, castInfo.host, castInfo.port), castMultiZoneStatus, colorValue=SIColors.Coral)
918
-
919
- # ensure group device is still on the network;
920
- # if group device dropped off the network, then we can't query it!
921
- if (castMultiZoneStatus is not None):
922
-
923
- # find the group coordinator for the selected device.
924
- zoneInfo:MultizoneInfo = None
925
- for zoneInfo in castMultiZoneStatus.groups:
926
-
927
- # is this our group?
928
- if (str(zoneInfo.uuid) == scDevice.DiscoveryResult.Key):
929
-
930
- # yes - get host info to use for the device.
931
- # we will use the device discovery result HostIpAddress instead of the zoneInfo.host
932
- # value here, as the zoneInfo MAY contain an IPV6 formatted address, which was causing
933
- # issues if the device was originally activated using an IPV4 address!
934
- groupHost = scDevice.DiscoveryResult.HostIpAddress
935
- groupPort = scDevice.DiscoveryResult.HostIpPort
936
- break
924
+ # trace.
925
+ _logsi.LogException("Chromecast Multizone group update_members exception: %s" % (str(ex)), ex, logToSystemLogger=False)
926
+ # ignore exception, as nothing can be done about it.
927
+
928
+ # get current multizone status for all groups.
929
+ castMultiZoneStatus:MultizoneStatus = get_multizone_status(castInfo.host, castInfo.services, self.ZeroconfInstance, 5)
930
+ _logsi.LogObject(SILevel.Verbose, "%s - Chromecast group multizone status for device: %s [ip=%s:%s]" % (self.name, scDevice.Title, castInfo.host, castInfo.port), castMultiZoneStatus, colorValue=SIColors.Coral)
931
+
932
+ # ensure group device is still on the network;
933
+ # if group device dropped off the network, then we can't query it!
934
+ if (castMultiZoneStatus is not None):
935
+
936
+ # find the group coordinator for the selected device.
937
+ zoneInfo:MultizoneInfo = None
938
+ for zoneInfo in castMultiZoneStatus.groups:
939
+
940
+ # is this our group?
941
+ if (str(zoneInfo.uuid) == scDevice.DiscoveryResult.Key):
942
+
943
+ # yes - get host info to use for the device.
944
+ # we will use the device discovery result HostIpAddress instead of the zoneInfo.host
945
+ # value here, as the zoneInfo MAY contain an IPV6 formatted address, which was causing
946
+ # issues if the device was originally activated using an IPV4 address!
947
+ groupHost = scDevice.DiscoveryResult.HostIpAddress
948
+ groupPort = scDevice.DiscoveryResult.HostIpPort
949
+ break
937
950
 
938
- # connect to the device and build a Chromecast instance from group coordinator host info.
939
- castDevice = get_chromecast_from_host(
940
- host=(groupHost, groupPort, castInfo.uuid, castInfo.model_name, castInfo.friendly_name),
941
- tries=2,
942
- retry_wait=0.5,
943
- timeout=10)
951
+ # connect to the device and build a Chromecast instance from group coordinator host info.
952
+ castDevice = get_chromecast_from_host(
953
+ host=(groupHost, groupPort, castInfo.uuid, castInfo.model_name, castInfo.friendly_name),
954
+ tries=2,
955
+ retry_wait=0.5,
956
+ timeout=10)
944
957
 
945
958
  # trace.
946
959
  _logsi.LogVerbose("%s - Waiting %d seconds max for Chromecast group multizone device to activate: %s [ip=%s:%s]" % (self.name, deviceWaitTimeoutSecs, scDevice.Title, groupHost, groupPort), colorValue=SIColors.Coral)
@@ -1535,12 +1548,21 @@ class SpotifyConnectDirectoryTask(threading.Thread):
1535
1548
  returnCoordinator = True
1536
1549
 
1537
1550
  # get Sonos Controller instance for device name.
1538
- sonosPlayer:SoCo = self._SonosPlayers.get(device.DiscoveryResult.HostIpAddress, None)
1551
+ hostIpAddress:str = device.DiscoveryResult.HostIpAddress
1552
+ sonosPlayer:SoCo = self._SonosPlayers.get(hostIpAddress, None)
1539
1553
 
1540
1554
  # if not found then it's an error.
1541
1555
  if (sonosPlayer is None):
1542
1556
  raise SpotifyApiError("Could not find Sonos Controller instance for device: %s" % (device.Title), None, logsi=_logsi)
1543
1557
 
1558
+ # Another integration (e.g. Home Assistant Sonos) can call soco_reset().
1559
+ # Our cached reference then outlives the SoCo singleton registry, and
1560
+ # topology updates only reach the current instance. Reacquire it before
1561
+ # inspecting its coordinator role or group. Without a reset this returns
1562
+ # the same instance, preserving the normal SoCo topology cache.
1563
+ sonosPlayer = SoCo(hostIpAddress)
1564
+ self._SonosPlayers[hostIpAddress] = sonosPlayer
1565
+
1544
1566
  # trace.
1545
1567
  _logsi.LogDictionary(SILevel.Verbose, "Sonos Controller instance for device: %s (speaker_info)" % (device.Title), sonosPlayer.speaker_info)
1546
1568
 
@@ -648,6 +648,134 @@ class ZeroconfConnect:
648
648
  # formulate the blob.
649
649
  blob = builder.build()
650
650
 
651
+ # special processing for Spotify Soloist client:
652
+ elif (infoModelDisplayName in ["soloist"]):
653
+
654
+ # have not figured out how the Spotify Soloist initial session is created.
655
+ raise SpotifyWebApiError(404, "Spotify Soloist initial session has not been created for loginId \"%s\"; use an authorized Spotify App to connect to Soloist for this loginId first" % (loginId), apiMethodName, "Soloist Initial Session Not Created", _logsi)
656
+
657
+ # The following Soloist log info is generated by a successful connection from an authorized Spotify App:
658
+ # 20:30:15.371 I [f:379] getAuthInfo: success
659
+ # 20:30:15.371 I [f:302] zeroconf: received getInfo
660
+ # 20:30:18.700 I [f:379] getAuthInfo: success
661
+ # 20:30:18.700 I [f:302] zeroconf: received getInfo
662
+ # 20:30:18.939 I [f:186] zeroconf: received addUser
663
+ # 20:30:18.939 I [f:280] loginAuthToken
664
+ # 20:30:18.939 I [f:273] SpeakerAuth: Login state changed: LOGGED_OUT -> LOGGING_IN
665
+ # 20:30:18.939 I [f:53 ] Creating session via Connect
666
+ # 20:30:19.275 I [f:263] High request latency: https://accounts.spotify.com/api took 185 ms
667
+ # 20:30:19.275 I [f:218] Auth code grant token exchange succeeded
668
+ # 20:30:19.277 I [f:72 ] Session created via Connect, session: [session:31l77y2al5lnn7mxfrmd4bpfhqke]
669
+ # 20:30:19.278 I [f:808] Connect session created successfully, received AuthenticatedSession
670
+
671
+ # left the following code in for now - still trying to work out how to create the
672
+ # initial Soloist session if one has not been established yet.
673
+
674
+ # # special processing for Spotify Soloist client:
675
+ # elif (infoModelDisplayName in ["soloist"]):
676
+
677
+ # # trace.
678
+ # _logsi.LogVerbose("Spotify Connect token type is '%s' - using soloist authorization credentials to connect: '%s' (ip=%s)" % (info.TokenType, info.RemoteName, self._HostIpAddress))
679
+
680
+ # # validations.
681
+ # # note that userid and password are not needed by soloist, as both of those
682
+ # # are contained in the soloist credentials object. we only need the loginId
683
+ # # value for the credentials lookup.
684
+ # if (loginId is None) or (not isinstance(loginId,str)):
685
+ # raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectLoginId"), logsi=_logsi)
686
+
687
+ # # we will use credentials from a librespot credentials.json file.
688
+ # # this requires that the user copy the "credentials.json" file from the
689
+ # # librespot cache location (e.g. "/home/user/.cache/spotifyd/credentials.json", etc)
690
+ # # and place it in the same folder as the token storage file with a new name
691
+ # # of "SpotifyWebApiPython_librespot_credentials.json".
692
+
693
+ # # get credentials from librespot credentials file.
694
+ # credentials:dict = self._LoadLibrespotCredentials(loginId)
695
+
696
+ # # create credentials and builder objects.
697
+ # librespot_username = credentials.get('username','')
698
+ # librespot_password = b64str_to_bytes(credentials.get('auth_data',''))
699
+ # credentials:Credentials = Credentials(librespot_username, librespot_password, AuthenticationTypes.STORED_SPOTIFY_CREDENTIALS)
700
+ # builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
701
+
702
+ # # set defaults used by this token type:
703
+ # # - clientKey should be the getInfo response PublicKey (base64 encoded).
704
+ # # - include origin device information in the addUser request.
705
+ # # - tokenType should be set to '' (not used by librespot).
706
+ # clientKey:str = ''
707
+ # includeOriginDeviceInfo = True
708
+ # tokenType = 'auth_token'
709
+
710
+ # # formulate the blob.
711
+ # blob = builder.build()
712
+
713
+ # The following Soloist log errors are generated for this auth method if an authorized Spotify App
714
+ # has not yet connected to the Soloist instance.
715
+ # The above code passes pre-configured LIBRESPOT credentials to the Spotify Connect zeroconf process.
716
+ # Note that playback transfer is successful if an authorized Spotify App HAS connected to
717
+ # the Soloist instance, as the device is in the Spotify Web API device list (dynamic device); in
718
+ # this instance, the `_ConnectAddUser` method is not even called!
719
+ # 20:18:17.491 I [f:379] getAuthInfo: success
720
+ # 20:18:17.491 I [f:302] zeroconf: received getInfo
721
+ # 20:18:44.731 I [f:379] getAuthInfo: success
722
+ # 20:18:44.731 I [f:302] zeroconf: received getInfo
723
+ # 20:18:47.458 I [f:186] zeroconf: received addUser
724
+ # 20:18:47.458 I [f:280] loginAuthToken
725
+ # 20:18:47.459 I [f:273] SpeakerAuth: Login state changed: LOGGED_OUT -> LOGGING_IN
726
+ # 20:18:47.459 I [f:53 ] Creating session via Connect
727
+ # 20:18:47.459 E [f:78 ] Session creation via Connect failed, error: session_invalid_connect_auth_token
728
+ # 20:18:47.459 E [f:813] Connect session creation failed: session_invalid_connect_auth_token
729
+ # 20:18:47.459 I [f:273] SpeakerAuth: Login state changed: LOGGING_IN -> LOGGED_OUT
730
+
731
+ # # special processing for Spotify Soloist client:
732
+ # elif (infoModelDisplayName in ["soloist"]):
733
+
734
+ # # trace.
735
+ # _logsi.LogVerbose("Spotify Connect token type is '%s' - using Spotify Desktop Client OAuth2 token to connect to Soloist: '%s' (ip=%s)" % (info.TokenType, info.RemoteName, self._HostIpAddress))
736
+
737
+ # # validations.
738
+ # if (loginId is None) or (not isinstance(loginId,str)):
739
+ # raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectLoginId"), logsi=_logsi)
740
+ # if (username is None) or (not isinstance(username,str)):
741
+ # raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectUsername"), logsi=_logsi)
742
+ # if (password is None) or (not isinstance(password,str)):
743
+ # raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectPassword"), logsi=_logsi)
744
+
745
+ # # create credentials and builder objects.
746
+ # credentials:Credentials = Credentials(loginId, password, AuthenticationTypes.SPOTIFY_TOKEN)
747
+ # #credentials:Credentials = Credentials(username, password, AuthenticationTypes.SPOTIFY_TOKEN)
748
+ # builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
749
+
750
+ # # set defaults used by Soloist client.
751
+ # # - clientKey not required.
752
+ # # - include origin device information in the addUser request.
753
+ # # - use tokenType of "auth_token".
754
+ # clientKey = ''
755
+ # includeOriginDeviceInfo = True
756
+ # tokenType = 'auth_token'
757
+
758
+ # # get access token.
759
+ # blob:str = self._GetSpotifyConnectAccessToken(info, loginId=loginId)
760
+
761
+ # The following Soloist log errors are generated for this auth method if an authorized Spotify App
762
+ # has not yet connected to the Soloist instance.
763
+ # The above code passes pre-configured Spotify Desktop App credentials to the Spotify Connect zeroconf process.
764
+ # Note that playback transfer is successful if an authorized Spotify App HAS connected to
765
+ # the Soloist instance, as the device is in the Spotify Web API device list (dynamic device); in
766
+ # this instance, the `_ConnectAddUser` method is not even called!
767
+ # 20:14:33.640 I [f:379] getAuthInfo: success
768
+ # 20:14:33.640 I [f:302] zeroconf: received getInfo
769
+ # 20:14:36.579 I [f:186] zeroconf: received addUser
770
+ # 20:14:36.579 I [f:280] loginAuthToken
771
+ # 20:14:36.579 I [f:273] SpeakerAuth: Login state changed: LOGGED_OUT -> LOGGING_IN
772
+ # 20:14:36.579 I [f:53 ] Creating session via Connect
773
+ # 20:14:36.579 E [f:78 ] Session creation via Connect failed, error: session_invalid_connect_auth_token
774
+ # 20:14:36.579 E [f:813] Connect session creation failed: session_invalid_connect_auth_token
775
+ # 20:14:36.579 I [f:273] SpeakerAuth: Login state changed: LOGGING_IN -> LOGGED_OUT
776
+ # 20:14:36.579 W [f:552] login failed while API key is configured (action_id=1)
777
+ # 20:14:36.579 W [f:553] login failed: make sure --api-key is valid
778
+
651
779
  # special processing for tokenType "authorization_code":
652
780
  elif info.TokenType == 'authorization_code':
653
781
 
@@ -28,6 +28,7 @@ class ZeroconfGetInfo(ZeroconfResponse):
28
28
  self._AccountReq:str = None
29
29
  self._ActiveUser:str = ""
30
30
  self._Aliases:list[ZeroconfGetInfoAlias] = []
31
+ self._AuthInfo:str = ""
31
32
  self._Availability:str = ""
32
33
  self._BrandDisplayName:str = None
33
34
  self._ClientId:str = None
@@ -55,6 +56,7 @@ class ZeroconfGetInfo(ZeroconfResponse):
55
56
 
56
57
  self._AccountReq = root.get('accountReq', None)
57
58
  self._ActiveUser = root.get('activeUser', "")
59
+ self._AuthInfo = root.get('authInfo', None)
58
60
  self._Availability = root.get('availability', "")
59
61
  self._BrandDisplayName = root.get('brandDisplayName', None)
60
62
  self._ClientId = root.get('clientID', None)
@@ -137,6 +139,27 @@ class ZeroconfGetInfo(ZeroconfResponse):
137
139
  return self._Aliases
138
140
 
139
141
 
142
+ @property
143
+ def AuthInfo(self) -> str:
144
+ """
145
+ This variable is not documented by the Spotify Web API.
146
+
147
+ It appears to be structured authentication information associated with the Soloist
148
+ client/device, in base64-encoded / protobuf format (e.g. "CiQyZjA2M ... BVE9NWmU5RQ==").
149
+
150
+ The maximum length of the auth Info string is unknown.
151
+ """
152
+ return self._AuthInfo
153
+
154
+ @AuthInfo.setter
155
+ def AuthInfo(self, value:str):
156
+ """
157
+ Sets the AuthInfo property value.
158
+ """
159
+ if (isinstance(value, str)) or (value is None):
160
+ self._AuthInfo = value
161
+
162
+
140
163
  @property
141
164
  def Availability(self) -> str:
142
165
  """
@@ -526,6 +549,7 @@ class ZeroconfGetInfo(ZeroconfResponse):
526
549
  'AccountReq': self._AccountReq,
527
550
  'ActiveUser': self._ActiveUser,
528
551
  'Aliases': [ item.ToDictionary() for item in self._Aliases ],
552
+ 'AuthInfo': self._AuthInfo,
529
553
  'Availability': self._Availability,
530
554
  'BrandDisplayName': self._BrandDisplayName,
531
555
  'ClientId': self._ClientId,
@@ -577,6 +601,7 @@ class ZeroconfGetInfo(ZeroconfResponse):
577
601
  if self._AccountReq is not None: msg = '%s\n AccountReq="%s"' % (msg, str(self._AccountReq))
578
602
  if self._ActiveUser is not None: msg = '%s\n ActiveUser="%s"' % (msg, str(self._ActiveUser))
579
603
  if self._Aliases is not None: msg = '%s\n Aliases=%s' % (msg, str(self._Aliases))
604
+ if self._AuthInfo is not None: msg = '%s\n AuthInfo="%s"' % (msg, str(self._AuthInfo))
580
605
  if self._Availability is not None: msg = '%s\n Availability="%s"' % (msg, str(self._Availability))
581
606
  if self._BrandDisplayName is not None: msg = '%s\n BrandDisplayName="%s"' % (msg, str(self._BrandDisplayName))
582
607
  if self._ClientId is not None: msg = '%s\n ClientId="%s"' % (msg, str(self._ClientId))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: spotifywebapipython
3
- Version: 1.0.294
3
+ Version: 1.0.296
4
4
  Summary: Spotify Web API Python3 Library
5
5
  Author: Todd Lucas
6
6
  Author-email: <thlucas@yahoo.com>
@@ -1,36 +1,36 @@
1
1
  spotifywebapipython/__init__.py,sha256=ka7F2AsT-UU7KSYKBnfgl5Trojcep5aQspLutg8D-2k,1306
2
- spotifywebapipython/const.py,sha256=oj5dY2U2uxxMLiZWij3jeY2VwZzmbqSiHdm1twX9ATw,4100
2
+ spotifywebapipython/const.py,sha256=50BHoLMJDRh4KnIBHjbpaYqfixgOHxMMl8lF_qZlktA,4100
3
3
  spotifywebapipython/saappmessages.py,sha256=mVprjpd5CdD8kYeAkLzRvMvIQPmaYtH4UCP5my3QhSk,5065
4
4
  spotifywebapipython/sautils.py,sha256=LZF3UvxaBK35WIg1hO37nbW8j1y-M-oOxRbi1dPR39w,8746
5
5
  spotifywebapipython/spotifyapierror.py,sha256=23_CYlxWN0BceYYCLpBpunl55vEwPr_wFf34l74xzeA,1855
6
6
  spotifywebapipython/spotifyapimessage.py,sha256=wCnW8jYMLS3x9kjyXPgXXZqaf1HeMTTXzU8u_Dm5pfA,8447
7
7
  spotifywebapipython/spotifyauthtoken.py,sha256=iVg2Pa5mfMqYe4qgc5H8td6JzJlaDqk4vMEhH5LmtfI,9289
8
- spotifywebapipython/spotifyclient.py,sha256=jkC2ewbsamh_YNfc0rhWLDk3Z1eaw9rmspVkxmdhH_Y,928610
8
+ spotifywebapipython/spotifyclient.py,sha256=hya2_fpOpP9Qyg6_wuWlUmlPwquTT_8-KcDF3Eiwx08,928655
9
9
  spotifywebapipython/spotifydiscovery.py,sha256=cyysxl8PQj9yGHVbaqhSF3SStlrp-NvfGAudM-dDmg0,20290
10
10
  spotifywebapipython/spotifymediatypes.py,sha256=hMjkxhbfTtks0_P8BKmY7qg3EmB2zhH3RT0DbEpb_2s,1628
11
11
  spotifywebapipython/spotifytypeprefixes.py,sha256=3yfpxKF7jF694qBuo-PbKYvm70YwwdBoGj9enOpWsAg,1375
12
12
  spotifywebapipython/spotifywebapiauthenticationerror.py,sha256=5A0MKvYo3Ypv0ZeSSfLHuFLwzoUUu044bBoUgMfNXNw,3940
13
13
  spotifywebapipython/spotifywebapierror.py,sha256=ctYFvkBPlFFEoM_geP6hzwTyC2mPcrYR_zCCNvsmpwc,3870
14
14
  spotifywebapipython/spotifywebplayertoken.py,sha256=axj1j1lM7COiiZfpElkJRdnk4AmE6bAQcjKshUIz1Nk,35813
15
- spotifywebapipython/docs/__init__.html,sha256=FsnO64WP9HsImXs-AonCUUmUWEOlBajpYqhBYVcjNiE,199713
15
+ spotifywebapipython/docs/__init__.html,sha256=2F9ndL51ND0rePvMs3q1AOaJB6jksyMo82YoBePSgOQ,200803
16
16
  spotifywebapipython/docs/codecopy.js,sha256=ryuoZwLIL03YitjjFzn49UO5ta7-OBos6LNA3qHhFzs,3120
17
- spotifywebapipython/docs/index.html,sha256=rNcPZUg3v7qJZSaXOcuFERqwC5CEm260Kc91BEbXoSg,48779
18
- spotifywebapipython/docs/search.js,sha256=xdiBYN4bciyd6hIKcrC6l9ySbFg0NjJ3bl6aAFns-7E,9277464
17
+ spotifywebapipython/docs/index.html,sha256=OGv36bUHX26oGcS2HmgUEpMVSUOVAVSNhqfYOeDcfRw,48779
18
+ spotifywebapipython/docs/search.js,sha256=wdg_LJOjbdrkNQchItK23zLj9CiU-3uVBqFjue7v4k4,9289882
19
19
  spotifywebapipython/docs/spotifywebapipython.ico,sha256=YImj39fqqSPSuB2-sMDlUYDd6YwRfQeJRtq4FjwWglg,16410
20
20
  spotifywebapipython/docs/spotifywebapipython_OLD.ico,sha256=YIv5LjcD4bTIJQSOr8jjwbWaeB3QkElRwkTOePiGbZo,14904
21
- spotifywebapipython/docs/spotifywebapipython/const.html,sha256=IsLvA9DVCXJm0ahAFiFqH5fCP_LDErWOknP4_-fIP0w,60537
22
- spotifywebapipython/docs/spotifywebapipython/saappmessages.html,sha256=5Q_EVCeeIak4bjbiBmzJt2f1P2u6dpRNq_mKWu9JEIs,62358
23
- spotifywebapipython/docs/spotifywebapipython/sautils.html,sha256=-yv1l4IM__IVWHIIbzRUjCUC7rQJeR2OmDHQY64fH6U,62031
24
- spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html,sha256=S_mHY212fy_lMKx_xakhFaNaOAI8nJclAnPpKnbSGLM,48054
25
- spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html,sha256=eiFt3S4N5FtErC-lEnU39m3sA_ISEMr2YOU6EIDahTA,59210
26
- spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html,sha256=ALxUxbPz9EUt6GxF3C1wghH3iKj-OCvOi1y5cdasaeE,59256
27
- spotifywebapipython/docs/spotifywebapipython/spotifyclient.html,sha256=bKgbh_ERKgTKypnQH6Gk-M-YTZgk0ihitAhPJpZkaeo,1677607
28
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html,sha256=Nj2t95BtEXLAoVyzI-unE22CgqbPVzfhu3LEGrnJ5ug,57662
29
- spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html,sha256=_Ntks24sCD3968HJtNHkN8IEnpgYW64hH69bY0EN7h0,51532
30
- spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.html,sha256=VccKsPzaqeN00S8Oz91yTetc4VKCfrAxHdm0C3T4ozg,52401
31
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html,sha256=Z6TRDfiZ2ylfCkKaOGDEk5yoAE8-sc7OaX-GPIjeATM,52174
32
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html,sha256=z16KGA-IX21EQBfVLw4woYLCMjJe837_mD3jRyYkwkE,51484
33
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html,sha256=RtDWlyO3C3FlR7us4euXW29Spc99HRAw_JTLhJ52axc,62204
21
+ spotifywebapipython/docs/spotifywebapipython/const.html,sha256=lHX8fMlg1Wd6MOAj9QABn9UtA3AWYFf6WTC2YOU6dsI,60537
22
+ spotifywebapipython/docs/spotifywebapipython/saappmessages.html,sha256=8Z2OMN1jlwhsYyORHZysY-ECDOfhPCiS91huz43ceHQ,62358
23
+ spotifywebapipython/docs/spotifywebapipython/sautils.html,sha256=BERvm-lHcFYo4-MIXhi_dfOpvlF4GHr8Ke_QGqx0Ecs,62031
24
+ spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html,sha256=8vXg0qZ5kH9nAfKJeAjrv9JLR2t5E_DMDQsbguIX1ZQ,48054
25
+ spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html,sha256=2UlqgTeZKQzT-wFuCjLq5zvSpcimnd5ojMYvDYhNt3M,59210
26
+ spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html,sha256=MdmU34FdYdsn4h4glGd7cZJ70TSPTkjMq2LoI-0PmZk,59256
27
+ spotifywebapipython/docs/spotifywebapipython/spotifyclient.html,sha256=oikPy0hqH3rbmcWoVHMCbcnFpvzjfie7yAoACBYedMM,1677607
28
+ spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html,sha256=lcs4bqdzMxbptqGL8ElmPNvsGhFCgoNVdCmWnqblMOY,57662
29
+ spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html,sha256=2VUSFazpMxjWW7xBSqo2zE0ncMBPttNGrZqlmf5-sMA,51532
30
+ spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.html,sha256=Fgypo08rN1oCBeSxlIBlOKYK6la14WUv78KgT_ri7jc,52401
31
+ spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html,sha256=POs3Fp9loNh4Vpp7IHu3VUGcMS8XD0UH__39bZtzJdI,52174
32
+ spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html,sha256=hNHWZzM2MUf4J6KEmpuNXRCbkfOx-GVeh6EnCSkbL8M,51484
33
+ spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html,sha256=z8FBtuJ5ytOuNBvcRG5Nq9b9A-ZXHdHyl9P00eiEbjc,62204
34
34
  spotifywebapipython/models/__init__.py,sha256=RwRQhUX-6EUs8XvStVFpR8EPwoqIAz9K6ref-28qHKI,4423
35
35
  spotifywebapipython/models/album.py,sha256=U3Pdw0-EL33LdkeiN31fjZ23rlLk6DNGV8jRmZU7EyQ,5170
36
36
  spotifywebapipython/models/albumpagesaved.py,sha256=SBYMhkh5ZsMrRAXl-cm9zsxMDh8gO190ENq6Py2TyGY,2417
@@ -110,7 +110,7 @@ spotifywebapipython/oauthcli/__init__.py,sha256=K37XTxYAKT0n8otCNzWbjgJ10fAPN4WL
110
110
  spotifywebapipython/oauthcli/authclient.py,sha256=qGKa4FLBHHZQyP5ho1sIM_mNmyshQd720YwKcUFQJAw,56207
111
111
  spotifywebapipython/spotifyconnect/__init__.py,sha256=9Xl5KiJFvWLbsdb4YQ0xmF67i36ybpXCCXrWeNsKEvc,1108
112
112
  spotifywebapipython/spotifyconnect/spotifyconnectdeviceeventargs.py,sha256=35hKa0FAaPgy7W8D6MFXmga667pjwXITo8jSj1TDfE8,1988
113
- spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py,sha256=Ha5Ht0PDaXNQJQxCcZEbu5SpDvETHcPgqbkr9l8WfhE,168043
113
+ spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py,sha256=RUXj875jstz_zRuZHn0TAn-gN59x4eA8yRRgmwTnK6Y,169688
114
114
  spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py,sha256=jArxsjAGntTcQD9L9yVuey1L8pw0KvshlNXIBA7PqiI,23367
115
115
  spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py,sha256=e2wLmFq4393IdVa51w8X5UQl4KfnHRh34DMlxZfl9-g,34218
116
116
  spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py,sha256=e7vKob2xim8H3BkxbgPj-eymftKvWgT71tzpskd0sn0,16720
@@ -132,13 +132,13 @@ spotifywebapipython/zeroconfapi/credentials.py,sha256=MIXqZvFNDjoICpZGNXPveHNl9G
132
132
  spotifywebapipython/zeroconfapi/cryptodiffiehellman.py,sha256=k-nMR68GtzODYsQrwCpvFE2XNxv-XUFbMemplldtwp4,3340
133
133
  spotifywebapipython/zeroconfapi/helpers.py,sha256=9lJFOuKYrk-e7SbU94WmFv5oiBq4k5gMS8wE1it4Wbc,1429
134
134
  spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py,sha256=rTwIrr1qV0UiQS_zBufboyZ3A3yGrdCsTAOiVA5dvzY,3031
135
- spotifywebapipython/zeroconfapi/zeroconfconnect.py,sha256=JzAKVc7XC_HrTH1IwsuXpcgW0rFlRYocHL8likoYYvQ,79023
136
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py,sha256=gTr3J3iVDR_ueP1tg-WHuCOke3eP864WGiESmOOYFUw,22243
135
+ spotifywebapipython/zeroconfapi/zeroconfconnect.py,sha256=8SxKNnzBiDtL7Mjrs5K7v67j-qr20ixS-2jwpvsiutw,88286
136
+ spotifywebapipython/zeroconfapi/zeroconfgetinfo.py,sha256=J8Oab4JJJ_OuNR5Q4cywTAYHUPUtarTGmmBjAzcUdq4,23135
137
137
  spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py,sha256=FZJM23HIPJSfhXIC3-EuS1XhawheoG5maSp7__k4yBo,2932
138
138
  spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py,sha256=c5uk8ljsIuBK3w2jahHKQuoWvm245LOjUBitetXx5Mo,2347
139
139
  spotifywebapipython/zeroconfapi/zeroconfresponse.py,sha256=mAPIDj5MG53x8peiScrp7OJblwy_o_Sto4wqE0rGujU,6445
140
- spotifywebapipython-1.0.294.dist-info/LICENSE,sha256=KGcc9g0zwqNLcWnSn_ezv1UtKyhMENFhvtixpxUPNsE,1177
141
- spotifywebapipython-1.0.294.dist-info/METADATA,sha256=aNEmx_UG_r6U79DD-SDB6Fnswiu-1OQll8IW_BGmr6c,11644
142
- spotifywebapipython-1.0.294.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
143
- spotifywebapipython-1.0.294.dist-info/top_level.txt,sha256=jTBLFlijKTesYh2dteAz4vb17qjaqWKeDjpnnwTcrjs,20
144
- spotifywebapipython-1.0.294.dist-info/RECORD,,
140
+ spotifywebapipython-1.0.296.dist-info/LICENSE,sha256=KGcc9g0zwqNLcWnSn_ezv1UtKyhMENFhvtixpxUPNsE,1177
141
+ spotifywebapipython-1.0.296.dist-info/METADATA,sha256=RgTFqUYbGNKScjkYOnpf66c1p-DFjUk9QMMyLBvTzaw,11644
142
+ spotifywebapipython-1.0.296.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
143
+ spotifywebapipython-1.0.296.dist-info/top_level.txt,sha256=jTBLFlijKTesYh2dteAz4vb17qjaqWKeDjpnnwTcrjs,20
144
+ spotifywebapipython-1.0.296.dist-info/RECORD,,