spotifywebapiPython 1.0.111__py3-none-any.whl → 1.0.113__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 (25) hide show
  1. {spotifywebapiPython-1.0.111.dist-info → spotifywebapiPython-1.0.113.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.111.dist-info → spotifywebapiPython-1.0.113.dist-info}/RECORD +25 -25
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +17 -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 +62 -4
  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/models/artistinfotourevent.py +2 -1
  18. spotifywebapipython/models/pageobject.py +28 -0
  19. spotifywebapipython/models/playerqueueinfo.py +34 -4
  20. spotifywebapipython/models/trackpage.py +40 -0
  21. spotifywebapipython/spotifyclient.py +215 -19
  22. spotifywebapipython/zeroconfapi/zeroconfconnect.py +23 -23
  23. {spotifywebapiPython-1.0.111.dist-info → spotifywebapiPython-1.0.113.dist-info}/LICENSE +0 -0
  24. {spotifywebapiPython-1.0.111.dist-info → spotifywebapiPython-1.0.113.dist-info}/WHEEL +0 -0
  25. {spotifywebapiPython-1.0.111.dist-info → spotifywebapiPython-1.0.113.dist-info}/top_level.txt +0 -0
@@ -58,6 +58,46 @@ class TrackPage(PageObject):
58
58
  return self._Items
59
59
 
60
60
 
61
+ def GetUris(self, afterUri:str=None) -> str:
62
+ """
63
+ Gets a comma-delimited list of uri's contained in the underlying `Items` list.
64
+
65
+ args:
66
+ afterUri (str):
67
+ If specified, the process will look for this uri before adding subsequent
68
+ uri's to the returned results. This allows you to skip uri's that have
69
+ recently been played.
70
+
71
+ This is a convenience method so one does not have to loop through the `Items`
72
+ array of PlayHistory objects to get the list of uri's.
73
+ """
74
+ result:str = ""
75
+ addUri:bool = False
76
+ item:Track
77
+
78
+ # add uri's from the beginning if no `afterUri` argument was passed.
79
+ if (afterUri is None) or (len(afterUri.strip()) == 0):
80
+ addUri = True
81
+
82
+ for item in self._Items:
83
+
84
+ if (not addUri):
85
+
86
+ # check for `afterUri` match; if found, we will start adding uri's
87
+ # to the list with the next item.
88
+ if (afterUri == item.Uri):
89
+ addUri = True
90
+
91
+ elif (addUri):
92
+
93
+ # add item uri to the list.
94
+ if (len(result) > 0):
95
+ result += ","
96
+ result += item.Uri
97
+
98
+ return result
99
+
100
+
61
101
  def ToString(self, includeItems:bool=False) -> str:
62
102
  """
63
103
  Returns a displayable string representation of the class.
@@ -9,7 +9,8 @@ import time
9
9
  from typing import Tuple, Callable
10
10
  from urllib3 import PoolManager, Timeout, HTTPResponse
11
11
  from urllib.parse import urlencode
12
- from lxml.etree import fromstring, Element
12
+ from lxml.etree import fromstring, Element, tostring
13
+ from lxml import etree
13
14
  from zeroconf import Zeroconf
14
15
 
15
16
  # our package imports.
@@ -838,6 +839,8 @@ class SpotifyClient:
838
839
  deviceId:str=None
839
840
  ) -> None:
840
841
  """
842
+ DEPRECATED - use `AddPlayerQueueItems` method instead.
843
+
841
844
  Add an item to the end of the user's current playback queue.
842
845
 
843
846
  This method requires the `user-modify-playback-state` scope.
@@ -862,13 +865,6 @@ class SpotifyClient:
862
865
  This API only works for users who have Spotify Premium.
863
866
 
864
867
  The order of execution is not guaranteed when you use this API with other Player API endpoints.
865
-
866
- <details>
867
- <summary>Sample Code</summary>
868
- ```python
869
- .. include:: ../docs/include/samplecode/SpotifyClient/AddPlayerQueueItem.py
870
- ```
871
- </details>
872
868
  """
873
869
  apiMethodName:str = 'AddPlayerQueueItem'
874
870
  apiMethodParms:SIMethodParmListContext = None
@@ -920,6 +916,139 @@ class SpotifyClient:
920
916
  _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
921
917
 
922
918
 
919
+ def AddPlayerQueueItems(
920
+ self,
921
+ uris:str,
922
+ deviceId:str=None,
923
+ verifyDeviceId:bool=True,
924
+ delay:float=0.15,
925
+ ) -> None:
926
+ """
927
+ Add one or more items to the end of the user's current playback queue.
928
+
929
+ This method requires the `user-modify-playback-state` scope.
930
+
931
+ Args:
932
+ uris (str):
933
+ A list of Spotify track or episode URIs to add to the queue; can be track or episode URIs.
934
+ All URIs must be of the same type - you cannot mix and match tracks and episodes!
935
+ Example: [`spotify:track:6zd8T1PBe9JFHmuVnurdRp` ,`spotify:track:1kWUud3vY5ij5r62zxpTRy`].
936
+ It can also be specified as a comma-delimited string.
937
+ Example: `spotify:track:6zd8T1PBe9JFHmuVnurdRp,spotify:track:1kWUud3vY5ij5r62zxpTRy`.
938
+ A maximum of 50 items can be added in one request.
939
+ deviceId (str):
940
+ The id or name of the device this command is targeting.
941
+ If not supplied, the user's currently active device is the target.
942
+ Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
943
+ Example: `Web Player (Chrome)`
944
+ verifyDeviceId (bool):
945
+ True to verify a device id is active; otherwise, false to assume that a
946
+ device id is already active.
947
+ Default is True.
948
+ delay (float):
949
+ Time delay (in seconds) to wait AFTER issuing the add request. This delay will give the
950
+ Spotify web api time to process the queue change before another command is issued.
951
+ Default is 0.15; value range is 0 - 10.
952
+
953
+ Raises:
954
+ SpotifyWebApiError:
955
+ If the Spotify Web API request was for a non-authorization service
956
+ and the response contains error information.
957
+ SpotifApiError:
958
+ If the method fails for any other reason.
959
+
960
+ This API only works for users who have Spotify Premium.
961
+
962
+ The order of execution is not guaranteed when you use this API with other Player API endpoints.
963
+
964
+ The Spotify Web API endpoint is called to add each item individually.
965
+ The Spotify Web API does not currently support adding more than 1 item to the queue at a time.
966
+
967
+ <details>
968
+ <summary>Sample Code</summary>
969
+ ```python
970
+ .. include:: ../docs/include/samplecode/SpotifyClient/AddPlayerQueueItems.py
971
+ ```
972
+ </details>
973
+ """
974
+ apiMethodName:str = 'AddPlayerQueueItems'
975
+ apiMethodParms:SIMethodParmListContext = None
976
+
977
+ try:
978
+
979
+ # trace.
980
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
981
+ apiMethodParms.AppendKeyValue("uris", uris)
982
+ apiMethodParms.AppendKeyValue("deviceId", deviceId)
983
+ apiMethodParms.AppendKeyValue("verifyDeviceId", verifyDeviceId)
984
+ apiMethodParms.AppendKeyValue("delay", delay)
985
+ _logsi.LogMethodParmList(SILevel.Verbose, "Add items to playback queue", apiMethodParms)
986
+
987
+ # validations.
988
+ delay = validateDelay(delay, 0.15, 10)
989
+ if (verifyDeviceId is None):
990
+ verifyDeviceId = True
991
+
992
+ # build a list of all item uri's.
993
+ # remove any leading / trailing spaces in case user put a space between the items.
994
+ arrUris:list[str] = None
995
+ if isinstance(uris, list):
996
+ arrUris = uris
997
+ else:
998
+ arrUris = uris.split(',')
999
+ for idx in range(0, len(arrUris)):
1000
+ arrUris[idx] = arrUris[idx].strip()
1001
+
1002
+ # if deviceId was not specified, then verify that there is an active player device.
1003
+ if verifyDeviceId:
1004
+ if deviceId is None:
1005
+ deviceId:str = self.VerifyPlayerActiveDeviceId()
1006
+
1007
+ # check for device name; convert to an id if a name was supplied.
1008
+ if verifyDeviceId:
1009
+ deviceId = self.PlayerConvertDeviceNameToId(deviceId)
1010
+
1011
+ # process all uri's.
1012
+ for idx in range(0, len(arrUris)):
1013
+
1014
+ # build spotify web api request parameters.
1015
+ urlParms:dict = \
1016
+ {
1017
+ 'uri': arrUris[idx]
1018
+ }
1019
+ if deviceId is not None:
1020
+ urlParms['device_id'] = deviceId
1021
+
1022
+ # execute spotify web api request.
1023
+ msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/me/player/queue')
1024
+ msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
1025
+ msg.UrlParameters = urlParms
1026
+ self.MakeRequest('POST', msg)
1027
+
1028
+ # give spotify web api time to process the change.
1029
+ if delay > 0:
1030
+ _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
1031
+ time.sleep(delay)
1032
+
1033
+
1034
+ # process results.
1035
+ # no results to process - this is pass or fail.
1036
+ return
1037
+
1038
+ except SpotifyApiError: raise # pass handled exceptions on thru
1039
+ except SpotifyWebApiError: raise # pass handled exceptions on thru
1040
+ except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
1041
+ except Exception as ex:
1042
+
1043
+ # format unhandled exception.
1044
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
1045
+
1046
+ finally:
1047
+
1048
+ # trace.
1049
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
1050
+
1051
+
923
1052
  def AddPlaylistCoverImage(self,
924
1053
  playlistId:str,
925
1054
  imagePath:str
@@ -2727,7 +2856,8 @@ class SpotifyClient:
2727
2856
 
2728
2857
  # update result object with final paging details.
2729
2858
  result.Total = pageObj.Total
2730
-
2859
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
2860
+
2731
2861
  # sort result items.
2732
2862
  if (len(result.Items) > 0):
2733
2863
  if (sortResult is True):
@@ -2894,6 +3024,7 @@ class SpotifyClient:
2894
3024
 
2895
3025
  # update result object with final paging details.
2896
3026
  result.Total = pageObj.Total
3027
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
2897
3028
 
2898
3029
  # sort result items.
2899
3030
  if (len(result.Items) > 0):
@@ -3161,6 +3292,7 @@ class SpotifyClient:
3161
3292
 
3162
3293
  # update result object with final paging details.
3163
3294
  result.Total = pageObj.Total
3295
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
3164
3296
 
3165
3297
  # do not sort items, as they are in playable order.
3166
3298
 
@@ -3428,6 +3560,7 @@ class SpotifyClient:
3428
3560
 
3429
3561
  # update result object with final paging details.
3430
3562
  result.Total = pageObj.Total
3563
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
3431
3564
 
3432
3565
  # sort result items.
3433
3566
  if (len(result.Items) > 0):
@@ -3638,12 +3771,21 @@ class SpotifyClient:
3638
3771
  for elm in attrElements:
3639
3772
  innerText = _xmlGetInnerText(elm)
3640
3773
  if innerText is not None:
3641
- if innerText.lower() == 'see all':
3774
+ innerTextLCase = innerText.lower()
3775
+ if innerTextLCase == 'see all':
3642
3776
  break
3643
- _logsi.LogObject(SILevel.Verbose, "Element match: '%s' (innerText=%s)" % (attrXPath, innerText), elm)
3777
+ if innerTextLCase.startswith('view all upcoming concerts'):
3778
+ break;
3779
+
3780
+ if (_logsi.IsOn(SILevel.Verbose)):
3781
+ xml_string = etree.tostring(elm, pretty_print=True, encoding="utf-8")
3782
+ _logsi.LogXml(SILevel.Verbose, "Element match: '%s' (html)" % (attrXPath), xml_string.decode("utf-8"))
3783
+
3644
3784
  event:ArtistInfoTourEvent = ArtistInfoTourEvent()
3645
- elmChild = elm.xpath('.//time/@dateTime')
3646
- if elmChild is not None:
3785
+
3786
+ attrXPath = './/time/@dateTime'
3787
+ elmChild = elm.xpath(attrXPath)
3788
+ if (elmChild is not None) and (len(elmChild) > 0):
3647
3789
  try:
3648
3790
  attrValue = str(elmChild[0])
3649
3791
  idx:int = attrValue.rfind('-')
@@ -3652,12 +3794,17 @@ class SpotifyClient:
3652
3794
  except Exception as ex:
3653
3795
  _logsi.LogException("Element value '%s' (%s) could not be parsed to a datetime object" % (elmChild[0], attrValue), ex, logToSystemLogger=False)
3654
3796
  event.EventDateTime = None
3655
- elmChild = elm.xpath('.//h3/text()')
3656
- if elmChild is not None:
3797
+
3798
+ attrXPath = './/h3/text()'
3799
+ elmChild = elm.xpath(attrXPath)
3800
+ if (elmChild is not None) and (len(elmChild) > 0):
3657
3801
  event.Title = elmChild[0]
3658
- elmChild = elm.xpath('.//p/text()')
3659
- if elmChild is not None:
3802
+
3803
+ attrXPath = './/p/text()'
3804
+ elmChild = elm.xpath(attrXPath)
3805
+ if (elmChild is not None) and (len(elmChild) > 0):
3660
3806
  event.VenueName = elmChild[0]
3807
+
3661
3808
  result.TourEvents.append(event)
3662
3809
  _logsi.LogObject(SILevel.Verbose, '%s: "%s" (%s)' % (type(event).__name__, event.Title, event.EventDateTime), event, excludeNonPublic=True)
3663
3810
 
@@ -4006,6 +4153,7 @@ class SpotifyClient:
4006
4153
  # update result object with final paging details.
4007
4154
  result.Total = pageObj.Total
4008
4155
  result.CursorAfter = pageObj.CursorAfter
4156
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
4009
4157
 
4010
4158
  # sort result items.
4011
4159
  if (len(result.Items) > 0):
@@ -4386,6 +4534,7 @@ class SpotifyClient:
4386
4534
 
4387
4535
  # update result object with final paging details.
4388
4536
  result.Total = pageObj.Total
4537
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
4389
4538
 
4390
4539
  # no sorting, as chapters are in playable order.
4391
4540
 
@@ -4538,6 +4687,7 @@ class SpotifyClient:
4538
4687
 
4539
4688
  # update result object with final paging details.
4540
4689
  result.Total = pageObj.Total
4690
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
4541
4691
 
4542
4692
  # sort result items.
4543
4693
  if (len(result.Items) > 0):
@@ -4931,6 +5081,7 @@ class SpotifyClient:
4931
5081
 
4932
5082
  # update result object with final paging details.
4933
5083
  result.Total = pageObj.Total
5084
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
4934
5085
 
4935
5086
  # sort result items.
4936
5087
  if (len(result.Items) > 0):
@@ -5216,6 +5367,7 @@ class SpotifyClient:
5216
5367
 
5217
5368
  # update result object with final paging details.
5218
5369
  result.Total = pageObj.Total
5370
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
5219
5371
 
5220
5372
  # sort result items.
5221
5373
  if (len(result.Items) > 0):
@@ -5677,6 +5829,7 @@ class SpotifyClient:
5677
5829
 
5678
5830
  # update result object with final paging details.
5679
5831
  result.Total = pageObj.Total
5832
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
5680
5833
 
5681
5834
  # sort result items.
5682
5835
  if (len(result.Items) > 0):
@@ -5969,6 +6122,7 @@ class SpotifyClient:
5969
6122
 
5970
6123
  # update result object with final paging details.
5971
6124
  result.Total = pageObj.Total
6125
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
5972
6126
 
5973
6127
  # sort result items.
5974
6128
  if (len(result.Items) > 0):
@@ -7376,6 +7530,7 @@ class SpotifyClient:
7376
7530
  result.Total = result.ItemsCount
7377
7531
  result.CursorAfter = pageObj.CursorAfter
7378
7532
  result.CursorBefore = pageObj.CursorBefore
7533
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
7379
7534
 
7380
7535
  # trace.
7381
7536
  _logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
@@ -7799,6 +7954,7 @@ class SpotifyClient:
7799
7954
 
7800
7955
  # update result object with final paging details.
7801
7956
  result.Total = pageObj.Total
7957
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
7802
7958
 
7803
7959
  # do not sort this list, as it is in play order.
7804
7960
 
@@ -7951,6 +8107,7 @@ class SpotifyClient:
7951
8107
 
7952
8108
  # update result object with final paging details.
7953
8109
  result.Total = pageObj.Total
8110
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
7954
8111
 
7955
8112
  # sort result items.
7956
8113
  if (len(result.Items) > 0):
@@ -8112,6 +8269,7 @@ class SpotifyClient:
8112
8269
 
8113
8270
  # update result object with final paging details.
8114
8271
  result.Total = pageObj.Total
8272
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
8115
8273
 
8116
8274
  # sort result items.
8117
8275
  if (len(result.Items) > 0):
@@ -8392,6 +8550,7 @@ class SpotifyClient:
8392
8550
 
8393
8551
  # update result object with final paging details.
8394
8552
  result.Total = pageObj.Total
8553
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
8395
8554
 
8396
8555
  # no sort, as items are in playable order.
8397
8556
 
@@ -8571,6 +8730,7 @@ class SpotifyClient:
8571
8730
 
8572
8731
  # update result object with final paging details.
8573
8732
  result.Total = pageObj.Total
8733
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
8574
8734
 
8575
8735
  # sort result items.
8576
8736
  if (len(result.Items) > 0):
@@ -8927,9 +9087,16 @@ class SpotifyClient:
8927
9087
  # we will bypass disconnects for librespot devices (e.g. spotifyd, etc), as the
8928
9088
  # Spotify Connect Zeroconf `resetUsers` endpoint is not implemented and will
8929
9089
  # generate a 404 request response.
8930
- if (info.BrandDisplayName != 'librespot'):
9090
+ if (info.BrandDisplayName != 'librespot'):
9091
+
8931
9092
  _logsi.LogVerbose("Issuing Disconnect to Spotify Connect device '%s' for current user context '%s'" % (deviceResult.Title, deviceActiveUser))
8932
9093
  zcfResult = zconn.Disconnect(delay)
9094
+
9095
+ # delay just a little to give the device time to process the disconnect.
9096
+ # some devices only support a single connection on the spotify connect
9097
+ # zeroconf webserver, so we have to give it a little time to process the
9098
+ # disconnect command before we try to call the Connect method.
9099
+ time.sleep(0.350)
8933
9100
 
8934
9101
  # connect the device to OUR Spotify Connect user context.
8935
9102
  # note that the result here only indicates that the connect was submitted - NOT that it was successful!
@@ -9540,6 +9707,7 @@ class SpotifyClient:
9540
9707
 
9541
9708
  # update result object with final paging details.
9542
9709
  result.Total = pageObj.Total
9710
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
9543
9711
 
9544
9712
  # sort result items.
9545
9713
  if (len(result.Items) > 0):
@@ -10468,6 +10636,7 @@ class SpotifyClient:
10468
10636
 
10469
10637
  # update result object with final paging details.
10470
10638
  result.Total = pageObj.Total
10639
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
10471
10640
 
10472
10641
  # sort result items.
10473
10642
  if (len(result.Items) > 0):
@@ -10634,6 +10803,7 @@ class SpotifyClient:
10634
10803
 
10635
10804
  # update result object with final paging details.
10636
10805
  result.Total = pageObj.Total
10806
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
10637
10807
 
10638
10808
  # sort result items.
10639
10809
  if (len(result.Items) > 0):
@@ -11262,6 +11432,25 @@ class SpotifyClient:
11262
11432
  # if so, then use the found device id in case a device name was specified.
11263
11433
  if scDevice is not None:
11264
11434
  deviceId = scDevice.Id
11435
+
11436
+ # left this here in case we want to implement it in the future.
11437
+
11438
+ # # are we backfilling the player queue? if so, we can only have 50 uris total.
11439
+ # if (backfillUrisPageOffset > -1) and (len(arrUris) < 50):
11440
+
11441
+ # # are we playing tracks?
11442
+ # if (len(arrUris) > 0) and (SpotifyClient.GetTypeFromUri(arrUris[0]) == 'track'):
11443
+
11444
+ # _logsi.LogVerbose("Backfilling player queue items from users top tracks")
11445
+
11446
+ # # get list of users top tracks (50 max items).
11447
+ # pageObj:TrackPage = self.GetUsersTopTracks(timeRange='medium_term', offset=backfillUrisPageOffset, limit=(50 - len(arrUris)), sortResult=False)
11448
+
11449
+ # # add them to the player queue; no need to validate the device id, since we
11450
+ # # know there is an active device id by this point.
11451
+ # item:Track
11452
+ # for item in pageObj.Items:
11453
+ # arrUris.append(item.Uri)
11265
11454
 
11266
11455
  # build spotify web api request parameters.
11267
11456
  reqData:dict = \
@@ -11286,7 +11475,7 @@ class SpotifyClient:
11286
11475
  if delay > 0:
11287
11476
  _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % delay)
11288
11477
  time.sleep(delay)
11289
-
11478
+
11290
11479
  # process results.
11291
11480
  # no results to process - this is pass or fail.
11292
11481
  return
@@ -14007,6 +14196,7 @@ class SpotifyClient:
14007
14196
 
14008
14197
  # update result object with final paging details.
14009
14198
  result.Total = pageObj.Total
14199
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
14010
14200
 
14011
14201
  # do not sort, as spotify uses intelligent AI to return results in its order.
14012
14202
 
@@ -14199,6 +14389,7 @@ class SpotifyClient:
14199
14389
 
14200
14390
  # update result object with final paging details.
14201
14391
  result.Total = pageObj.Total
14392
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
14202
14393
 
14203
14394
  # do not sort, as spotify uses intelligent AI to return results in its order.
14204
14395
 
@@ -14393,6 +14584,7 @@ class SpotifyClient:
14393
14584
 
14394
14585
  # update result object with final paging details.
14395
14586
  result.Total = pageObj.Total
14587
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
14396
14588
 
14397
14589
  # do not sort, as spotify uses intelligent AI to return results in its order.
14398
14590
 
@@ -14585,6 +14777,7 @@ class SpotifyClient:
14585
14777
 
14586
14778
  # update result object with final paging details.
14587
14779
  result.Total = pageObj.Total
14780
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
14588
14781
 
14589
14782
  # do not sort, as spotify uses intelligent AI to return results in its order.
14590
14783
 
@@ -14777,6 +14970,7 @@ class SpotifyClient:
14777
14970
 
14778
14971
  # update result object with final paging details.
14779
14972
  result.Total = pageObj.Total
14973
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
14780
14974
 
14781
14975
  # do not sort, as spotify uses intelligent AI to return results in its order.
14782
14976
 
@@ -14969,6 +15163,7 @@ class SpotifyClient:
14969
15163
 
14970
15164
  # update result object with final paging details.
14971
15165
  result.Total = pageObj.Total
15166
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
14972
15167
 
14973
15168
  # do not sort, as spotify uses intelligent AI to return results in its order.
14974
15169
 
@@ -15161,6 +15356,7 @@ class SpotifyClient:
15161
15356
 
15162
15357
  # update result object with final paging details.
15163
15358
  result.Total = pageObj.Total
15359
+ result.DateLastRefreshed = datetime.utcnow().timestamp()
15164
15360
 
15165
15361
  # do not sort, as spotify uses intelligent AI to return results in its order.
15166
15362
 
@@ -543,8 +543,8 @@ class ZeroconfConnect:
543
543
 
544
544
  # set request headers.
545
545
  reqHeaders:dict = {
546
- 'Content-Type': 'application/x-www-form-urlencoded',
547
- 'Connection': 'close'
546
+ "Content-Type": "application/x-www-form-urlencoded",
547
+ "Connection": "close"
548
548
  }
549
549
 
550
550
  # set defaults used by most manufacturers - we will tailor these in the
@@ -552,14 +552,14 @@ class ZeroconfConnect:
552
552
  # - exclude origin device information from the addUser request.
553
553
  # - tokenType should be set to 'default'.
554
554
  includeOriginDeviceInfo:bool = False
555
- tokenType:str = 'default'
555
+ tokenType:str = "default"
556
556
 
557
557
  # include the origin device information if PublicKey="INVALID" (base64 encoded) and availability="NOT-LOADED".
558
- if (info.PublicKey == 'SU5WQUxJRA==') and (info.Availability == 'NOT-LOADED'):
558
+ if (info.PublicKey == "SU5WQUxJRA==") and (info.Availability == "NOT-LOADED"):
559
559
  includeOriginDeviceInfo = True
560
560
 
561
561
  # special processing for librespot, spotifyd.
562
- if info.ModelDisplayName == 'librespot':
562
+ if info.ModelDisplayName == "librespot":
563
563
 
564
564
  # trace.
565
565
  _logsi.LogVerbose("Spotify Connect token type is '%s' - using librespot authorization credentials to connect: '%s' (ip=%s)" % (info.TokenType, info.RemoteName, self._HostIpAddress))
@@ -649,20 +649,20 @@ class ZeroconfConnect:
649
649
  # - tokenType should be set to 'default'.
650
650
  clientKey:str = builder.dh_keys.PublicKeyBase64String
651
651
  includeOriginDeviceInfo = False
652
- tokenType = 'default'
652
+ tokenType = "default"
653
653
 
654
654
  # formulate the blob.
655
655
  blob = builder.build()
656
656
 
657
657
  # set request parameters.
658
658
  reqData={
659
- 'action': 'addUser',
660
- 'version': self.Version, # info.Version,
661
- 'tokenType': tokenType,
662
- 'clientKey': clientKey,
663
- 'loginId': loginId or '', # canonical login id (e.g. "31l77fd87g8h9j00k89f07jf87ge")
664
- 'userName': credentials.username.decode('ascii'), # canonical user name (e.g. "31l77fd87g8h9j00k89f07jf87ge")
665
- 'blob': blob,
659
+ "action": "addUser",
660
+ "version": self.Version, # info.Version,
661
+ "tokenType": tokenType,
662
+ "clientKey": clientKey,
663
+ "loginId": loginId or '', # canonical login id (e.g. "31l77fd87g8h9j00k89f07jf87ge")
664
+ "userName": credentials.username.decode("ascii"), # canonical user name (e.g. "31l77fd87g8h9j00k89f07jf87ge")
665
+ "blob": blob,
666
666
  }
667
667
 
668
668
  # are we including origin device information (e.g. deviceName, deviceId)?
@@ -1028,21 +1028,21 @@ class ZeroconfConnect:
1028
1028
  delay = validateDelay(delay, 0.50, 10)
1029
1029
 
1030
1030
  # set request endpoint.
1031
- endpoint:str = self.GetEndpoint('resetUsers')
1031
+ endpoint:str = self.GetEndpoint("resetUsers")
1032
1032
  if ignoreStatusResult is None:
1033
1033
  ignoreStatusResult = False
1034
1034
 
1035
1035
  # set request headers.
1036
1036
  reqHeaders:dict = {
1037
- 'Content-Type': 'application/x-www-form-urlencoded',
1038
- 'Connection': 'close'
1037
+ "Content-Type": "application/x-www-form-urlencoded",
1038
+ "Connection": "close"
1039
1039
  }
1040
1040
  _logsi.LogDictionary(SILevel.Debug, "ZeroconfConnect http request: '%s' (headers)" % (endpoint), reqHeaders)
1041
1041
 
1042
1042
  # set request parameters.
1043
1043
  reqData={
1044
- 'action': 'resetUsers',
1045
- 'version': self.Version,
1044
+ "action": "resetUsers",
1045
+ "version": self.Version,
1046
1046
  }
1047
1047
  _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (data)" % (endpoint), reqData)
1048
1048
 
@@ -1195,19 +1195,19 @@ class ZeroconfConnect:
1195
1195
  _logsi.LogMethodParmList(SILevel.Verbose, "Get information from Spotify Connect Zeroconf API (ip=%s)" % self._HostIpAddress, apiMethodParms)
1196
1196
 
1197
1197
  # set request endpoint.
1198
- endpoint:str = self.GetEndpoint('getInfo')
1198
+ endpoint:str = self.GetEndpoint("getInfo")
1199
1199
 
1200
1200
  # set request headers.
1201
1201
  reqHeaders:dict = {
1202
- 'Content-Type': 'application/x-www-form-urlencoded',
1203
- 'Connection': 'close'
1202
+ "Content-Type": "application/x-www-form-urlencoded",
1203
+ "Connection": "close"
1204
1204
  }
1205
1205
  _logsi.LogDictionary(SILevel.Debug, "ZeroconfConnect http request: '%s' (headers)" % (endpoint), reqHeaders)
1206
1206
 
1207
1207
  # set request parameters.
1208
1208
  reqParams={
1209
- 'action': 'getInfo',
1210
- 'version': self.Version,
1209
+ "action": "getInfo",
1210
+ "version": self.Version,
1211
1211
  }
1212
1212
  _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (params)" % (endpoint), reqParams)
1213
1213