spotifywebapiPython 1.0.67__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.
- {spotifywebapiPython-1.0.67.dist-info → spotifywebapiPython-1.0.69.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.67.dist-info → spotifywebapiPython-1.0.69.dist-info}/RECORD +24 -24
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +16 -2
- spotifywebapipython/docs/index.html +2 -2
- spotifywebapipython/docs/search.js +1 -1
- spotifywebapipython/docs/spotifywebapiPython/const.html +3 -3
- spotifywebapipython/docs/spotifywebapiPython/saappmessages.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/sautils.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyapimessage.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyauthtoken.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifyclient.html +20 -14
- spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
- spotifywebapipython/models/userprofile.py +2 -2
- spotifywebapipython/models/zeroconfdiscoveryresult.py +3 -3
- spotifywebapipython/spotifyclient.py +14 -8
- spotifywebapipython/zeroconfapi/blobbuilder.py +3 -0
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +102 -68
- {spotifywebapiPython-1.0.67.dist-info → spotifywebapiPython-1.0.69.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.67.dist-info → spotifywebapiPython-1.0.69.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.67.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,21 +236,7 @@ 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
|
|
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
|
-
# otherwise, let the calling function process the returned status.
|
|
239
|
+
# let the calling function process the returned status.
|
|
257
240
|
return responseData
|
|
258
241
|
|
|
259
242
|
|
|
@@ -302,7 +285,7 @@ class ZeroconfConnect:
|
|
|
302
285
|
response. This public key is then used to submit another `addUser` request to connect to the device with the
|
|
303
286
|
newly returned public key. When this happens, a `ZeroconfResponse` object is returned with the result of
|
|
304
287
|
the final `addUser` request. If only one `addUser` request is processed, then a `ZeroconfGetInfo` object is
|
|
305
|
-
returned with the result of the `addUser` request. Note that `ZeroconfGetInfo
|
|
288
|
+
returned with the result of the `addUser` request. Note that `ZeroconfGetInfo` inherits from `ZeroconfResponse`,
|
|
306
289
|
so you can always treat the result of this method as a `ZeroconfResponse` object.
|
|
307
290
|
|
|
308
291
|
The login (on the device) is performed asynchronously, so the return result only indicates whether the library
|
|
@@ -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
|
-
#
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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.
|
|
@@ -374,14 +367,14 @@ class ZeroconfConnect:
|
|
|
374
367
|
# when this happens, the initial request will return a 203 status (ERROR-INVALID-PUBLICKEY), and
|
|
375
368
|
# return a valid public key in the response. we can then take this public key and try the
|
|
376
369
|
# `addUser` request again.
|
|
377
|
-
if (result.Status == 203) and (result.
|
|
370
|
+
if (result.Status == 203) and (result.StatusString == "ERROR-INVALID-PUBLICKEY"):
|
|
378
371
|
|
|
379
372
|
# trace.
|
|
380
373
|
_logsi.LogObject(SILevel.Verbose, '%s result (%s)' % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
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
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
|
|
446
|
-
blob = builder.build()
|
|
440
|
+
apiMethodName:str = '_ConnectAddUser'
|
|
441
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
447
442
|
|
|
448
|
-
|
|
449
|
-
endpoint:str = self.GetEndpoint('addUser')
|
|
443
|
+
try:
|
|
450
444
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
789
|
+
if self._Version is not None: msg = '%s\n Version="%s"' % (msg, str(self.Version))
|
|
756
790
|
return msg
|
|
File without changes
|
|
File without changes
|
|
File without changes
|