spotifywebapiPython 1.0.59__py3-none-any.whl → 1.0.61__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.59.dist-info → spotifywebapiPython-1.0.61.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.59.dist-info → spotifywebapiPython-1.0.61.dist-info}/RECORD +25 -25
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +18 -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 +4 -3
- spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +5 -5
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
- spotifywebapipython/models/spotifyconnectdevices.py +2 -1
- spotifywebapipython/models/zeroconfdiscoveryresult.py +36 -12
- spotifywebapipython/spotifyclient.py +39 -9
- spotifywebapipython/spotifydiscovery.py +52 -56
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +38 -50
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +26 -0
- {spotifywebapiPython-1.0.59.dist-info → spotifywebapiPython-1.0.61.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.59.dist-info → spotifywebapiPython-1.0.61.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.59.dist-info → spotifywebapiPython-1.0.61.dist-info}/top_level.txt +0 -0
|
@@ -94,9 +94,9 @@ class SpotifyDiscovery:
|
|
|
94
94
|
"""
|
|
95
95
|
A dictionary of discovered device names that were detected by the discovery process.
|
|
96
96
|
|
|
97
|
-
Dictionary keys will be in the form of "address:port", where "
|
|
98
|
-
|
|
99
|
-
is listening on.
|
|
97
|
+
Dictionary keys will be in the form of "'name' (address:port)", where "name" is the
|
|
98
|
+
device name, "address" is the device server address and the "port" is the ipv4 port
|
|
99
|
+
number the Spotify Connect device is listening on.
|
|
100
100
|
|
|
101
101
|
Dictionary values will be the device names (e.g. "Web Player (Chrome)", etc.). This SHOULD
|
|
102
102
|
match the name of the device as displayed in the Spotify App, but is not guaranteed.
|
|
@@ -152,60 +152,56 @@ class SpotifyDiscovery:
|
|
|
152
152
|
if (ipAddressList is None):
|
|
153
153
|
return
|
|
154
154
|
|
|
155
|
-
# create
|
|
156
|
-
|
|
155
|
+
# create new result instance.
|
|
156
|
+
result:ZeroconfDiscoveryResult = ZeroconfDiscoveryResult()
|
|
157
|
+
result.DeviceName = deviceName
|
|
158
|
+
result.Domain = '.local'
|
|
159
|
+
for item in ipAddressList:
|
|
160
|
+
result.HostIpAddresses.append(item)
|
|
161
|
+
result.HostIpPort = serviceInfo.port
|
|
162
|
+
result.HostTTL = serviceInfo.host_ttl
|
|
163
|
+
result.Key = serviceInfo.key
|
|
164
|
+
result.Name = serviceInfo.name
|
|
165
|
+
result.OtherTTL = serviceInfo.other_ttl
|
|
166
|
+
result.Priority = serviceInfo.priority
|
|
167
|
+
result.Server = serviceInfo.server
|
|
168
|
+
result.ServerKey = serviceInfo.server_key
|
|
169
|
+
result.ServiceInfo = serviceInfo
|
|
170
|
+
result.ServiceType = serviceType
|
|
171
|
+
result.Weight = serviceInfo.weight
|
|
157
172
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
result.
|
|
163
|
-
|
|
164
|
-
result.HostTTL = serviceInfo.host_ttl
|
|
165
|
-
result.Key = serviceInfo.key
|
|
166
|
-
result.Name = serviceInfo.name
|
|
167
|
-
result.OtherTTL = serviceInfo.other_ttl
|
|
168
|
-
result.Priority = serviceInfo.priority
|
|
169
|
-
result.Server = serviceInfo.server
|
|
170
|
-
result.ServerKey = serviceInfo.server_key
|
|
171
|
-
result.ServiceInfo = serviceInfo
|
|
172
|
-
result.ServiceType = serviceType
|
|
173
|
-
result.Weight = serviceInfo.weight
|
|
173
|
+
# formulate a unique key for this device.
|
|
174
|
+
deviceKey:str = "'%s' (%s:%i)" % (deviceName, result.HostIpAddress, result.HostIpPort)
|
|
175
|
+
_logsi.LogVerbose("Discovered Spotify Connect device: %s %s" % (deviceKey, result.HostIpAddresses))
|
|
176
|
+
if (self._PrintToConsole == True):
|
|
177
|
+
print("Discovered Spotify Connect device: %s [%s]" % (deviceKey, result.HostIpAddresses))
|
|
178
|
+
_logsi.LogObject(SILevel.Verbose, "Discovered Spotify Connect device ServiceInfo: %s" % (deviceKey), serviceInfo)
|
|
174
179
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if deviceKey not in self._DiscoveredDeviceNames.keys():
|
|
201
|
-
self._DiscoveredDeviceNames[deviceKey] = deviceName
|
|
202
|
-
|
|
203
|
-
# add the device result to the list.
|
|
204
|
-
self._DiscoveryResults.append(result)
|
|
205
|
-
|
|
206
|
-
# trace.
|
|
207
|
-
_logsi.LogObject(SILevel.Verbose, "Discovered Spotify Connect device result: '%s' (%s:%i)" % (deviceName, deviceIpAddress, devicePort), result, excludeNonPublic=True)
|
|
208
|
-
#_logsi.LogArray(SILevel.Verbose, "Discovered Spotify Connect device result.Properties: '%s' (%s:%i)" % (deviceName, deviceIpAddress, devicePort), result.Properties)
|
|
180
|
+
# process service info propertys.
|
|
181
|
+
# note that the property keys and values must first be decoded to utf-8 encoding.
|
|
182
|
+
if serviceInfo.properties is not None:
|
|
183
|
+
dspProperties:dict = {}
|
|
184
|
+
for key, value in serviceInfo.properties.items():
|
|
185
|
+
keyStr = key.decode('utf-8')
|
|
186
|
+
valueStr = value.decode('utf-8')
|
|
187
|
+
result.Properties.append(ZeroconfProperty(keyStr, valueStr))
|
|
188
|
+
dspProperties[keyStr] = valueStr
|
|
189
|
+
# check for Spotify Connect common properties.
|
|
190
|
+
if keyStr.lower() == 'cpath':
|
|
191
|
+
result.SpotifyConnectCPath = valueStr
|
|
192
|
+
elif keyStr.lower() == 'version':
|
|
193
|
+
result.SpotifyConnectVersion = valueStr
|
|
194
|
+
_logsi.LogDictionary(SILevel.Verbose, "Discovered Spotify Connect device ServiceInfo.Properties: %s" % (deviceKey), dspProperties)
|
|
195
|
+
|
|
196
|
+
# add the device name to the list (if not already added).
|
|
197
|
+
if deviceKey not in self._DiscoveredDeviceNames.keys():
|
|
198
|
+
self._DiscoveredDeviceNames[deviceKey] = deviceName
|
|
199
|
+
|
|
200
|
+
# add the device result to the list.
|
|
201
|
+
self._DiscoveryResults.append(result)
|
|
202
|
+
|
|
203
|
+
# trace.
|
|
204
|
+
_logsi.LogObject(SILevel.Verbose, "Discovered Spotify Connect device result: %s" % (deviceKey), result, excludeNonPublic=True)
|
|
209
205
|
|
|
210
206
|
elif (serviceStateChange is ServiceStateChange.Removed):
|
|
211
207
|
_logsi.LogVerbose("Discovered Spotify Connect device removal (ignored): '%s' (%s:%s)", serviceName, serviceType, serviceStateChange)
|
|
@@ -272,6 +268,6 @@ class SpotifyDiscovery:
|
|
|
272
268
|
if includeItems == True:
|
|
273
269
|
|
|
274
270
|
for key, deviceName in self._DiscoveredDeviceNames.items():
|
|
275
|
-
msg = "%s\n- %s
|
|
271
|
+
msg = "%s\n- %s" % (msg, key)
|
|
276
272
|
|
|
277
273
|
return msg
|
|
@@ -14,6 +14,7 @@ from .zeroconfgetinfo import ZeroconfGetInfo
|
|
|
14
14
|
from ..saappmessages import SAAppMessages
|
|
15
15
|
from ..sautils import export, validateDelay
|
|
16
16
|
from ..spotifyapierror import SpotifyApiError
|
|
17
|
+
from ..spotifywebapierror import SpotifyWebApiError
|
|
17
18
|
from ..const import (
|
|
18
19
|
TRACE_MSG_DELAY_DEVICE,
|
|
19
20
|
)
|
|
@@ -41,8 +42,8 @@ class ZeroconfConnect:
|
|
|
41
42
|
"""
|
|
42
43
|
|
|
43
44
|
def __init__(self,
|
|
44
|
-
|
|
45
|
-
hostIpPort:
|
|
45
|
+
hostIpAddress:str,
|
|
46
|
+
hostIpPort:int,
|
|
46
47
|
cpath:str,
|
|
47
48
|
version:str='1.0',
|
|
48
49
|
useSSL:bool=False
|
|
@@ -51,9 +52,9 @@ class ZeroconfConnect:
|
|
|
51
52
|
Initializes a new instance of the class.
|
|
52
53
|
|
|
53
54
|
Args:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
on the Spotify Connect device (e.g. "192.168.1.81").
|
|
55
|
+
hostIpAddress (str):
|
|
56
|
+
IP address or alias at which the Spotify Connect Zeroconf API can be reached
|
|
57
|
+
on the Spotify Connect device (e.g. "192.168.1.81", "Bose-SM2-341513fbeeae.local.", etc).
|
|
57
58
|
hostIpPort (int):
|
|
58
59
|
Port number (as an integer) at which the Spotify Connect Zeroconf API can be reached
|
|
59
60
|
on the Spotify Connect device (e.g. "8200").
|
|
@@ -80,7 +81,7 @@ class ZeroconfConnect:
|
|
|
80
81
|
# initialize storage.
|
|
81
82
|
self._CPath:str = cpath
|
|
82
83
|
self._HostIpPort:int = hostIpPort
|
|
83
|
-
self.
|
|
84
|
+
self._HostIpAddress:str = hostIpAddress
|
|
84
85
|
self._UseSSL:bool = useSSL
|
|
85
86
|
self._Version:str = version
|
|
86
87
|
|
|
@@ -97,12 +98,12 @@ class ZeroconfConnect:
|
|
|
97
98
|
|
|
98
99
|
|
|
99
100
|
@property
|
|
100
|
-
def
|
|
101
|
+
def HostIpAddress(self) -> str:
|
|
101
102
|
"""
|
|
102
|
-
|
|
103
|
-
on the Spotify Connect device (e.g. "192.168.1.81").
|
|
103
|
+
IP address or alias at which the Spotify Connect Zeroconf API can be reached
|
|
104
|
+
on the Spotify Connect device (e.g. "192.168.1.81", "Bose-SM2-341513fbeeae.local.", etc).
|
|
104
105
|
"""
|
|
105
|
-
return self.
|
|
106
|
+
return self._HostIpAddress
|
|
106
107
|
|
|
107
108
|
|
|
108
109
|
@property
|
|
@@ -191,43 +192,30 @@ class ZeroconfConnect:
|
|
|
191
192
|
if (response.headers):
|
|
192
193
|
_logsi.LogCollection(SILevel.Debug, "ZeroconfConnect http response [%s-%s]: '%s' (headers)" % (response.status_code, response.reason, responseUrl), response.headers.items())
|
|
193
194
|
|
|
194
|
-
|
|
195
|
+
# do we have response data?
|
|
196
|
+
if (response.content is not None) and (len(response.content) > 0):
|
|
195
197
|
|
|
196
|
-
#
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
# do we have response data?
|
|
203
|
-
if len(response.content) == 0:
|
|
204
|
-
|
|
205
|
-
# some requests will not return a response, which is ok.
|
|
206
|
-
responseData = None
|
|
207
|
-
_logsi.LogVerbose("ZeroconfConnect http response [%s-%s]: '%s' (no data)" % (response.status_code, response.reason, responseUrl))
|
|
208
|
-
|
|
209
|
-
elif (contentType is not None) and (contentType.find('json') > -1):
|
|
210
|
-
|
|
211
|
-
# response is json.
|
|
212
|
-
# do not use the "response.json()" method to parse JSON responses, as it is unreliable!
|
|
213
|
-
data = response.content.decode('utf-8')
|
|
214
|
-
responseData = json.loads(data)
|
|
215
|
-
|
|
216
|
-
if _logsi.IsOn(SILevel.Verbose):
|
|
217
|
-
if isinstance(responseData, dict):
|
|
218
|
-
_logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json dict)" % (response.status_code, response.reason, responseUrl), responseData)
|
|
219
|
-
elif isinstance(responseData, list):
|
|
220
|
-
_logsi.LogArray(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json array)" % (response.status_code, response.reason, responseUrl), responseData)
|
|
221
|
-
else:
|
|
222
|
-
_logsi.LogObject(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json object)" % (response.status_code, response.reason, responseUrl), responseData)
|
|
223
|
-
|
|
224
|
-
else:
|
|
198
|
+
# convert response to JSON object.
|
|
199
|
+
# ALL Spotify Connect Zeroconf API responses SHOULD be JSON format!
|
|
200
|
+
# do not use the "response.json()" method to parse JSON responses, as it is unreliable!
|
|
201
|
+
data = response.content.decode('utf-8')
|
|
202
|
+
responseData = json.loads(data)
|
|
225
203
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
responseData
|
|
230
|
-
|
|
204
|
+
if _logsi.IsOn(SILevel.Verbose):
|
|
205
|
+
if isinstance(responseData, dict):
|
|
206
|
+
_logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json dict)" % (response.status_code, response.reason, responseUrl), responseData)
|
|
207
|
+
elif isinstance(responseData, list):
|
|
208
|
+
_logsi.LogArray(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json array)" % (response.status_code, response.reason, responseUrl), responseData)
|
|
209
|
+
else:
|
|
210
|
+
_logsi.LogObject(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json object)" % (response.status_code, response.reason, responseUrl), responseData)
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
else:
|
|
214
|
+
|
|
215
|
+
# raise an exception, as we are expecting a JSON-formatted response!
|
|
216
|
+
errMessage = response.content.decode('utf-8')
|
|
217
|
+
raise SpotifyWebApiError(response.status_code, errMessage, methodName, response.reason, _logsi)
|
|
218
|
+
|
|
231
219
|
except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
|
|
232
220
|
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
233
221
|
except Exception as ex:
|
|
@@ -325,7 +313,7 @@ class ZeroconfConnect:
|
|
|
325
313
|
|
|
326
314
|
# trace.
|
|
327
315
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
328
|
-
apiMethodParms.AppendKeyValue("
|
|
316
|
+
apiMethodParms.AppendKeyValue("HostIpAddress", self._HostIpAddress)
|
|
329
317
|
apiMethodParms.AppendKeyValue("HostIpPort", self._HostIpPort)
|
|
330
318
|
apiMethodParms.AppendKeyValue("CPath", self._CPath)
|
|
331
319
|
apiMethodParms.AppendKeyValue("Version", self._Version)
|
|
@@ -440,7 +428,7 @@ class ZeroconfConnect:
|
|
|
440
428
|
|
|
441
429
|
# trace.
|
|
442
430
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
443
|
-
apiMethodParms.AppendKeyValue("
|
|
431
|
+
apiMethodParms.AppendKeyValue("HostIpAddress", self._HostIpAddress)
|
|
444
432
|
apiMethodParms.AppendKeyValue("HostIpPort", self._HostIpPort)
|
|
445
433
|
apiMethodParms.AppendKeyValue("CPath", self._CPath)
|
|
446
434
|
apiMethodParms.AppendKeyValue("Version", self._Version)
|
|
@@ -512,7 +500,7 @@ class ZeroconfConnect:
|
|
|
512
500
|
|
|
513
501
|
return "{protocol}://{ip}:{port}{cpath}".format(
|
|
514
502
|
protocol=protocol,
|
|
515
|
-
ip=self.
|
|
503
|
+
ip=self._HostIpAddress,
|
|
516
504
|
port=self._HostIpPort,
|
|
517
505
|
cpath=self._CPath,
|
|
518
506
|
)
|
|
@@ -546,7 +534,7 @@ class ZeroconfConnect:
|
|
|
546
534
|
|
|
547
535
|
# trace.
|
|
548
536
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
549
|
-
apiMethodParms.AppendKeyValue("
|
|
537
|
+
apiMethodParms.AppendKeyValue("HostIpAddress", self._HostIpAddress)
|
|
550
538
|
apiMethodParms.AppendKeyValue("HostIpPort", self._HostIpPort)
|
|
551
539
|
apiMethodParms.AppendKeyValue("CPath", self._CPath)
|
|
552
540
|
apiMethodParms.AppendKeyValue("Version", self._Version)
|
|
@@ -573,7 +561,7 @@ class ZeroconfConnect:
|
|
|
573
561
|
result = ZeroconfGetInfo(root=responseData)
|
|
574
562
|
|
|
575
563
|
# trace.
|
|
576
|
-
_logsi.LogObject(SILevel.Verbose, '%s result -
|
|
564
|
+
_logsi.LogObject(SILevel.Verbose, '%s result (%s) - "%s" (%s)' % (apiMethodName, type(result).__name__, result.RemoteName, result.DeviceId), result, excludeNonPublic=True)
|
|
577
565
|
return result
|
|
578
566
|
|
|
579
567
|
except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
|
|
@@ -47,6 +47,9 @@ class ZeroconfGetInfo(ZeroconfResponse):
|
|
|
47
47
|
self._Version:str = None
|
|
48
48
|
self._VoiceSupport:str = None
|
|
49
49
|
|
|
50
|
+
# non-Spotify properties.
|
|
51
|
+
self._IsInDeviceList:bool = None
|
|
52
|
+
|
|
50
53
|
if (root is None):
|
|
51
54
|
|
|
52
55
|
pass
|
|
@@ -343,6 +346,27 @@ class ZeroconfGetInfo(ZeroconfResponse):
|
|
|
343
346
|
return self._VoiceSupport
|
|
344
347
|
|
|
345
348
|
|
|
349
|
+
# non-Spotify properties.
|
|
350
|
+
|
|
351
|
+
@property
|
|
352
|
+
def IsInDeviceList(self) -> bool:
|
|
353
|
+
"""
|
|
354
|
+
Returns the status of active device list verification:
|
|
355
|
+
* None - device list verification has not been performed for this device.
|
|
356
|
+
* True - device is a member of the active device list.
|
|
357
|
+
* False - device is NOT a member of the active device list.
|
|
358
|
+
"""
|
|
359
|
+
return self._IsInDeviceList
|
|
360
|
+
|
|
361
|
+
@IsInDeviceList.setter
|
|
362
|
+
def IsInDeviceList(self, value:bool):
|
|
363
|
+
"""
|
|
364
|
+
Sets the IsInDeviceList property value.
|
|
365
|
+
"""
|
|
366
|
+
if isinstance(value, bool):
|
|
367
|
+
self._IsInDeviceList = value
|
|
368
|
+
|
|
369
|
+
|
|
346
370
|
def ToDictionary(self) -> dict:
|
|
347
371
|
"""
|
|
348
372
|
Returns a dictionary representation of the class.
|
|
@@ -374,6 +398,7 @@ class ZeroconfGetInfo(ZeroconfResponse):
|
|
|
374
398
|
'TokenType': self._TokenType,
|
|
375
399
|
'Version': self._Version,
|
|
376
400
|
'VoiceSupport': self._VoiceSupport,
|
|
401
|
+
'IsInDeviceList': self._IsInDeviceList,
|
|
377
402
|
}
|
|
378
403
|
|
|
379
404
|
# combine base class results with these results.
|
|
@@ -420,4 +445,5 @@ class ZeroconfGetInfo(ZeroconfResponse):
|
|
|
420
445
|
if self._TokenType is not None: msg = '%s\n TokenType="%s"' % (msg, str(self._TokenType))
|
|
421
446
|
if self._Version is not None: msg = '%s\n Version="%s"' % (msg, str(self._Version))
|
|
422
447
|
if self._VoiceSupport is not None: msg = '%s\n VoiceSupport="%s"' % (msg, str(self._VoiceSupport))
|
|
448
|
+
if self._IsInDeviceList is not None: msg = '%s\n IsInDeviceList="%s"' % (msg, str(self._IsInDeviceList))
|
|
423
449
|
return msg
|
|
File without changes
|
|
File without changes
|
|
File without changes
|