spotifywebapiPython 1.0.68__py3-none-any.whl → 1.0.69__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 (24) hide show
  1. {spotifywebapiPython-1.0.68.dist-info → spotifywebapiPython-1.0.69.dist-info}/METADATA +1 -1
  2. {spotifywebapiPython-1.0.68.dist-info → spotifywebapiPython-1.0.69.dist-info}/RECORD +24 -24
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +10 -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 +20 -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/models/userprofile.py +2 -2
  18. spotifywebapipython/models/zeroconfdiscoveryresult.py +3 -3
  19. spotifywebapipython/spotifyclient.py +13 -7
  20. spotifywebapipython/zeroconfapi/blobbuilder.py +3 -0
  21. spotifywebapipython/zeroconfapi/zeroconfconnect.py +99 -65
  22. {spotifywebapiPython-1.0.68.dist-info → spotifywebapiPython-1.0.69.dist-info}/LICENSE +0 -0
  23. {spotifywebapiPython-1.0.68.dist-info → spotifywebapiPython-1.0.69.dist-info}/WHEEL +0 -0
  24. {spotifywebapiPython-1.0.68.dist-info → spotifywebapiPython-1.0.69.dist-info}/top_level.txt +0 -0
@@ -132,9 +132,9 @@ class ZeroconfConnect:
132
132
  @property
133
133
  def Version(self) -> str:
134
134
  """
135
- Spotify Connect Zeroconf API version number that the device supports (e.g. "2.10.0").
135
+ Spotify Connect Zeroconf API version number that the device supports (e.g. "", "1.0", "2.10.0", etc.).
136
136
  """
137
- return self._Version
137
+ return self._Version or ''
138
138
 
139
139
 
140
140
  def _CheckResponseForErrors(self, response:Response, methodName:str, endpoint:str) -> None:
@@ -190,7 +190,6 @@ class ZeroconfConnect:
190
190
  _logsi.LogArray(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json array)" % (response.status_code, response.reason, endpoint), responseData)
191
191
  else:
192
192
  _logsi.LogObject(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json object)" % (response.status_code, response.reason, endpoint), responseData)
193
- pass
194
193
 
195
194
  else:
196
195
 
@@ -213,8 +212,6 @@ class ZeroconfConnect:
213
212
  # just raise a new exception with the non-JSON response data.
214
213
  raise SpotifyZeroconfApiError(response.status_code, responseUTF8, methodName, response.reason, _logsi)
215
214
 
216
- errMessage:str = None
217
-
218
215
  # at this point we know that the response contains JSON data, as
219
216
  # we would have raised an exception before this if we could not
220
217
  # convert the response to JSON!
@@ -239,20 +236,6 @@ class ZeroconfConnect:
239
236
  # InvalidArguments 303 400 ERROR-INVALID-ARGUMENTS All Incorrect or insufficient arguments supplied for requested action
240
237
  # SpotifyError 402 200 ERROR-SPOTIFY-ERROR All A Spotify API call returned an error not covered by other error messages
241
238
 
242
- # # process results.
243
- # result = ZeroconfResponse(root=responseData)
244
-
245
- # # if result is ok, then just return the response data.
246
- # if (result.Status == 101) \
247
- # or (result.StatusString == 'OK') \
248
- # or (result.StatusString == 'ERROR-OK'):
249
- # return responseData
250
-
251
- # # was an error status string returned?
252
- # # if so, then we will raise an exception.
253
- # if (result.StatusString.startswith('ERROR-')):
254
- # raise SpotifyZeroconfApiError(result.Status, result.ToString(), methodName, result.StatusString, _logsi)
255
-
256
239
  # let the calling function process the returned status.
257
240
  return responseData
258
241
 
@@ -352,10 +335,18 @@ class ZeroconfConnect:
352
335
  # get the current device id from the device via Spotify ZeroConf API `getInfo` endpoint.
353
336
  info:ZeroconfGetInfo = self.GetInformation()
354
337
 
355
- # if the public key value is "INVALID" then issue a disconnect to reset the user context.
356
- if (info.PublicKey == 'SU5WQUxJRA=='): # base64 encoded "INVALID"
357
- _logsi.LogVerbose("Spotify Connect publicKey value is 'INVALID' for Device id '%s'; calling Disconnect to reset the user context" % (info.DeviceId))
358
- self.Disconnect(ignoreStatusResult=True)
338
+ # TODO - remove this comment when you get this working.
339
+ # following code did not help:
340
+ # # if the public key value is "INVALID" then issue a disconnect to reset the user context.
341
+ # if (info.PublicKey == 'SU5WQUxJRA=='): # base64 encoded "INVALID"
342
+ # _logsi.LogVerbose("Spotify Connect publicKey value is 'INVALID' for Device id '%s'; calling Disconnect to reset the user context" % (info.DeviceId))
343
+ # self.Disconnect(ignoreStatusResult=True)
344
+
345
+ # are we including origin device information (e.g. deviceName, deviceId)?
346
+ # TODO - maybe tailor this to info.BrandDisplayName and info.ModelDisplayName instead?
347
+ includeOriginDeviceInfo:bool = False
348
+ if (info.PublicKey == 'SU5WQUxJRA==') and (info.Availability == 'NOT-LOADED'):
349
+ includeOriginDeviceInfo = True
359
350
 
360
351
  # execute the Spotify Zeroconf API addUser request.
361
352
  responseData:dict = self._ConnectAddUser(
@@ -363,7 +354,9 @@ class ZeroconfConnect:
363
354
  username,
364
355
  password,
365
356
  loginId,
366
- apiMethodName)
357
+ apiMethodName,
358
+ includeOriginDeviceInfo=includeOriginDeviceInfo
359
+ )
367
360
 
368
361
  # process results.
369
362
  # we will process the results with a `ZeroconfGetInfo` object, in case a publicKey was returned.
@@ -381,7 +374,7 @@ class ZeroconfConnect:
381
374
  _logsi.LogVerbose("Spotify Connect addUser request returned an ERROR-INVALID-PUBLICKEY response for Device id '%s'; addUser will be retried with the returned publicKey" % (info.DeviceId))
382
375
 
383
376
  # give the device a little time to wake up.
384
- WAKEUP_DELAY:float = 0.25
377
+ WAKEUP_DELAY:float = 1.0
385
378
  _logsi.LogVerbose(TRACE_MSG_DELAY_DEVICE % WAKEUP_DELAY)
386
379
  time.sleep(WAKEUP_DELAY)
387
380
 
@@ -393,7 +386,9 @@ class ZeroconfConnect:
393
386
  username,
394
387
  password,
395
388
  loginId,
396
- apiMethodName)
389
+ apiMethodName,
390
+ includeOriginDeviceInfo=includeOriginDeviceInfo
391
+ )
397
392
 
398
393
  # process results.
399
394
  result = ZeroconfResponse(root=responseData)
@@ -437,48 +432,87 @@ class ZeroconfConnect:
437
432
  password:str,
438
433
  loginId:str,
439
434
  apiMethodName:str,
435
+ includeOriginDeviceInfo:bool=False,
440
436
  ) -> dict:
441
437
  """
438
+ Execute the `addUser` request.
442
439
  """
443
- # formulate the blob.
444
- credentials:Credentials = Credentials(username, password)
445
- builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
446
- blob = builder.build()
440
+ apiMethodName:str = '_ConnectAddUser'
441
+ apiMethodParms:SIMethodParmListContext = None
447
442
 
448
- # set request endpoint.
449
- endpoint:str = self.GetEndpoint('addUser')
443
+ try:
450
444
 
451
- # set request headers.
452
- reqHeaders:dict = {
453
- 'Content-Type': 'application/x-www-form-urlencoded',
454
- 'Connection': 'close'
455
- }
456
- _logsi.LogDictionary(SILevel.Debug, "ZeroconfConnect http request: '%s' (headers)" % (endpoint), reqHeaders)
445
+ # trace.
446
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
447
+ apiMethodParms.AppendKeyValue("includeOriginDeviceInfo", includeOriginDeviceInfo)
448
+ _logsi.LogMethodParmList(SILevel.Verbose, "Issuing Spotify Connect Zeroconf addUser request (ip=%s)" % self._HostIpAddress, apiMethodParms)
449
+
450
+ # validations.
451
+ if (includeOriginDeviceInfo is None):
452
+ includeOriginDeviceInfo = False
457
453
 
458
- # set request parameters.
459
- reqData={
460
- 'action': 'addUser',
461
- 'version': self._Version or '',
462
- 'userName': credentials.username.decode('ascii'), # user name (e.g. "youremail@mail.com")
463
- 'clientKey': int_to_b64str(builder.dh_keys.public_key),
464
- 'blob': blob,
465
- 'tokenType': 'default', # NOTE - not the same as info.TokenType!
466
- # not sure if this is required or not, so commenting out for now.
467
- #'loginId': loginId or '', # canonical login id (e.g. "31l77fd87g8h9j00k89f07jf87ge")
468
- }
469
- _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (data)" % (endpoint), reqData)
470
-
471
- # execute spotify zeroconf api request.
472
- response = requests.post(
473
- self._Uri,
474
- timeout=10,
475
- headers=reqHeaders,
476
- data=reqData # send data in POST request body
477
- )
454
+ # formulate the blob.
455
+ credentials:Credentials = Credentials(username, password)
456
+ builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
457
+ blob = builder.build()
478
458
 
479
- # check response for initial errors, and return json response.
480
- responseData:dict = self._CheckResponseForErrors(response, apiMethodName, endpoint)
481
- return responseData
459
+ # set request endpoint.
460
+ endpoint:str = self.GetEndpoint('addUser')
461
+
462
+ # set request headers.
463
+ reqHeaders:dict = {
464
+ 'Content-Type': 'application/x-www-form-urlencoded',
465
+ 'Connection': 'close'
466
+ }
467
+ _logsi.LogDictionary(SILevel.Debug, "ZeroconfConnect http request: '%s' (headers)" % (endpoint), reqHeaders)
468
+
469
+ # set request parameters.
470
+ reqData={
471
+ 'action': 'addUser',
472
+ 'version': self.Version,
473
+ 'tokenType': 'default', # NOTE - not the same as info.TokenType!
474
+ 'loginId': loginId or '', # canonical login id (e.g. "31l77fd87g8h9j00k89f07jf87ge")
475
+ 'userName': credentials.username.decode('ascii'), # user name (e.g. "youremail@mail.com")
476
+ 'clientKey': int_to_b64str(builder.dh_keys.public_key),
477
+ 'blob': blob,
478
+ }
479
+
480
+ # are we including origin device information (e.g. deviceName, deviceId)?
481
+ # the `deviceName` and `deviceId` keys are specified by some manufacturers for the
482
+ # `addUser` action. I am not sure if they are required or not, as they are not part
483
+ # of the Spotify Zeroconf API specification for the `addUser` action.
484
+ if (includeOriginDeviceInfo):
485
+ _logsi.LogVerbose("Origin deviceName and deviceId will be included with the addUser request for deviceId '%s'" % (info.DeviceId))
486
+ reqData['deviceName'] = builder._OriginDeviceName
487
+ reqData['deviceId'] = builder._OriginDeviceId
488
+
489
+ # if PublicKey="INVALID" and availability="NOT-LOADED" then adjust the addUser
490
+ # request to remove the blob and clientKey values. this will inform the device to
491
+ # send us back a PublicKey value, which can be used on a subsequent addUser request.
492
+ if (info.PublicKey == 'SU5WQUxJRA==') and (info.Availability == 'NOT-LOADED'):
493
+ _logsi.LogVerbose("Spotify Connect PublicKey='INVALID' and Availability='NOT-LOADED' detected for Device id '%s'; removing blob and clientKey values" % (info.DeviceId))
494
+ reqData['blob'] = ''
495
+ reqData['clientKey'] = ''
496
+
497
+ # trace.
498
+ _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (data)" % (endpoint), reqData)
499
+
500
+ # execute spotify zeroconf api request.
501
+ response = requests.post(
502
+ self._Uri,
503
+ timeout=10,
504
+ headers=reqHeaders,
505
+ data=reqData # send data in POST request body
506
+ )
507
+
508
+ # check response for initial errors, and return json response.
509
+ responseData:dict = self._CheckResponseForErrors(response, apiMethodName, endpoint)
510
+ return responseData
511
+
512
+ finally:
513
+
514
+ # trace.
515
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
482
516
 
483
517
 
484
518
  def Disconnect(
@@ -554,7 +588,7 @@ class ZeroconfConnect:
554
588
  # set request parameters.
555
589
  reqData={
556
590
  'action': 'resetUsers',
557
- 'version': self._Version or '',
591
+ 'version': self.Version,
558
592
  }
559
593
  _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (data)" % (endpoint), reqData)
560
594
 
@@ -641,7 +675,7 @@ class ZeroconfConnect:
641
675
  return "{uri}?action={action}&version={version}".format(
642
676
  uri=self._Uri,
643
677
  action=action,
644
- version=self._Version or '',
678
+ version=self.Version,
645
679
  )
646
680
 
647
681
 
@@ -693,7 +727,7 @@ class ZeroconfConnect:
693
727
  # set request parameters.
694
728
  reqParams={
695
729
  'action': 'getInfo',
696
- 'version': self._Version or '',
730
+ 'version': self.Version,
697
731
  }
698
732
  _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (params)" % (endpoint), reqParams)
699
733
 
@@ -752,5 +786,5 @@ class ZeroconfConnect:
752
786
 
753
787
  msg = '%s\n URI="%s"' % (msg, str(self._Uri))
754
788
  if self._CPath is not None: msg = '%s\n CPath="%s"' % (msg, str(self._CPath))
755
- if self._Version is not None: msg = '%s\n Version="%s"' % (msg, str(self._Version))
789
+ if self._Version is not None: msg = '%s\n Version="%s"' % (msg, str(self.Version))
756
790
  return msg