spotifywebapiPython 1.0.45__py3-none-any.whl → 1.0.47__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 (34) hide show
  1. {spotifywebapiPython-1.0.45.dist-info → spotifywebapiPython-1.0.47.dist-info}/METADATA +6 -6
  2. {spotifywebapiPython-1.0.45.dist-info → spotifywebapiPython-1.0.47.dist-info}/RECORD +34 -28
  3. spotifywebapipython/const.py +1 -1
  4. spotifywebapipython/docs/__init__.html +21 -3
  5. spotifywebapipython/docs/index.html +20 -2
  6. spotifywebapipython/docs/search.js +1 -1
  7. spotifywebapipython/docs/spotifywebapiPython/const.html +3 -3
  8. spotifywebapipython/docs/spotifywebapiPython/saappmessages.html +22 -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 +2 -200
  14. spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -28
  15. spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
  16. spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
  17. spotifywebapipython/models/__init__.py +0 -10
  18. spotifywebapipython/saappmessages.py +7 -1
  19. spotifywebapipython/spotifyclient.py +0 -249
  20. spotifywebapipython/spotifydiscovery.py +0 -46
  21. spotifywebapipython/zeroconfapi/__init__.py +21 -0
  22. spotifywebapipython/{models/zeroconfblobbuilder.py → zeroconfapi/blobbuilder.py} +10 -58
  23. spotifywebapipython/zeroconfapi/credentials.py +18 -0
  24. spotifywebapipython/zeroconfapi/cryptodiffiehellman.py +24 -0
  25. spotifywebapipython/zeroconfapi/helpers.py +30 -0
  26. spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py +108 -0
  27. spotifywebapipython/zeroconfapi/zeroconfconnect.py +562 -0
  28. spotifywebapipython/{models → zeroconfapi}/zeroconfgetinfo.py +21 -7
  29. spotifywebapipython/{models → zeroconfapi}/zeroconfgetinfoalias.py +1 -1
  30. spotifywebapipython/{models → zeroconfapi}/zeroconfgetinfodrmmediaformat.py +1 -1
  31. spotifywebapipython/{models → zeroconfapi}/zeroconfresponse.py +3 -3
  32. {spotifywebapiPython-1.0.45.dist-info → spotifywebapiPython-1.0.47.dist-info}/LICENSE +0 -0
  33. {spotifywebapiPython-1.0.45.dist-info → spotifywebapiPython-1.0.47.dist-info}/WHEEL +0 -0
  34. {spotifywebapiPython-1.0.45.dist-info → spotifywebapiPython-1.0.47.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,108 @@
1
+ # external package imports.
2
+ from smartinspectpython.sisession import SISession
3
+
4
+ # our package imports.
5
+ from ..sautils import export
6
+ from ..saappmessages import SAAppMessages
7
+
8
+
9
+ @export
10
+ class SpotifyZeroconfApiError(Exception):
11
+ """
12
+ Exception thrown when a Spotify ZeroConf API Request Error occurs.
13
+ """
14
+
15
+ def __init__(self, status:int, message:str, methodName:str, reason:str, logsi:SISession=None) -> None:
16
+ """
17
+ Initializes a new instance of the class.
18
+
19
+ Args:
20
+ status (int):
21
+ HTTP status code that is also returned in the response header.
22
+ For further information, see Response Status Codes.
23
+ message (str):
24
+ A short description of the cause of the error.
25
+ methodName (str):
26
+ Name of the client method that executed the request.
27
+ reason (str):
28
+ HTTP reason code for the error.
29
+ logsi (SISession):
30
+ Trace session object that this exception will be logged to, or null to bypass trace logging.
31
+ Default is None.
32
+ """
33
+
34
+ # initialize base class.
35
+ super().__init__(message)
36
+
37
+ # initialize class instance.
38
+ self._HttpReason:str = reason
39
+ self._Message:str = message
40
+ self._MethodName:str = methodName
41
+ self._Status:int = status
42
+
43
+ # trace.
44
+ if logsi is not None:
45
+ if (isinstance(logsi, SISession)):
46
+ logsi.LogException(str(self), self, logToSystemLogger=False)
47
+
48
+
49
+ def __repr__(self) -> str:
50
+ return self.ToString()
51
+
52
+
53
+ def __str__(self) -> str:
54
+ return self.ToString()
55
+
56
+
57
+ @property
58
+ def HttpReason(self) -> str:
59
+ """
60
+ HTTP reason code for the error.
61
+
62
+ Example: `Bad Request`.
63
+ """
64
+ return self._HttpReason
65
+
66
+
67
+ @property
68
+ def Message(self) -> str:
69
+ """
70
+ A short description of the cause of the error.
71
+
72
+ Example: `invalid id`.
73
+ """
74
+ return self._Message
75
+
76
+
77
+ @property
78
+ def MethodName(self) -> str:
79
+ """
80
+ The method being executed when the error occured.
81
+
82
+ Example: `getInfo`.
83
+ """
84
+ return self._MethodName
85
+
86
+
87
+ @property
88
+ def Status(self) -> int:
89
+ """
90
+ HTTP status code that is also returned in the response header.
91
+ For further information, see Response Status Codes.
92
+
93
+ Example: `400`
94
+ """
95
+ return self._Status
96
+
97
+
98
+ def ToString(self) -> str:
99
+ """
100
+ Returns a UI-friendly string representation of the class.
101
+ """
102
+ msg:str = SAAppMessages.MSG_SPOTIFY_ZEROCONF_API_ERROR.format(
103
+ methodname=self._MethodName,
104
+ status=self._Status,
105
+ httpreason=self._HttpReason,
106
+ message=self._Message
107
+ )
108
+ return msg
@@ -0,0 +1,562 @@
1
+ # external package imports.
2
+ import json
3
+ import requests
4
+ from requests.models import Response
5
+ import time
6
+
7
+ # our package imports.
8
+ from .blobbuilder import BlobBuilder
9
+ from .credentials import Credentials
10
+ from .helpers import int_to_b64str
11
+ from .spotifyzeroconfapierror import SpotifyZeroconfApiError
12
+ from .zeroconfresponse import ZeroconfResponse
13
+ from .zeroconfgetinfo import ZeroconfGetInfo
14
+ from ..saappmessages import SAAppMessages
15
+ from ..sautils import export
16
+ from ..spotifyapierror import SpotifyApiError
17
+
18
+ # get smartinspect logger reference; create a new session for this module name.
19
+ from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SISourceId
20
+ import logging
21
+ _logsi:SISession = SIAuto.Si.GetSession(__name__)
22
+ if (_logsi == None):
23
+ _logsi = SIAuto.Si.AddSession(__name__, True)
24
+ _logsi.SystemLogger = logging.getLogger(__name__)
25
+
26
+
27
+ @export
28
+ class ZeroconfConnect:
29
+ """
30
+ Interfaces with the Spotify Connect Zeroconf API to add and remove a device to / from the
31
+ Spotify Connect active device list.
32
+
33
+ More information about Spotify Connect Zeroconf API can be found here:
34
+ https://developer.spotify.com/documentation/commercial-hardware/implementation/guides/zeroconf
35
+
36
+ Inspiration and some of the blob encryption encoding was derived from the [zerospot](https://github.com/maraid/zerospot)
37
+ GitHub repository.
38
+ """
39
+
40
+ def __init__(self,
41
+ hostIpv4Address:str,
42
+ hostIpPort:str,
43
+ cpath:str,
44
+ version:str='1.0',
45
+ useSSL:bool=False
46
+ ) -> None:
47
+ """
48
+ Initializes a new instance of the class.
49
+
50
+ Args:
51
+ hostIpv4Address (str):
52
+ IPV4 address (as a string) at which the Spotify Connect Zeroconf API can be reached
53
+ on the Spotify Connect device (e.g. "192.168.1.81").
54
+ hostIpPort (int):
55
+ Port number (as an integer) at which the Spotify Connect Zeroconf API can be reached
56
+ on the Spotify Connect device (e.g. "8200").
57
+ cpath (str):
58
+ Spotify Connect Zeroconf API CPath property value (e.g. "/zc").
59
+ version (str):
60
+ Spotify Connect Zeroconf API version number that the device supports (e.g. "2.10.0").
61
+ Default is '1.0'.
62
+ useSSL (bool):
63
+ True if the host device utilizes HTTPS Secure Sockets Layer (SSL) support;
64
+ otherwise, False to utilize HTTP.
65
+ Default is False (HTTP).
66
+
67
+ The URI value consists of an IP Address, port, CPath, and version value that are used to send
68
+ requests to / receive responses from a headless Spotify Connect device. These values can be
69
+ obtained from a Zeroconf discovery process.
70
+ """
71
+ # validations.
72
+ if (version is None):
73
+ version = '1.0'
74
+
75
+ # initialize storage.
76
+ self._CPath:str = cpath
77
+ self._HostIpPort:int = hostIpPort
78
+ self._HostIpv4Address:str = hostIpv4Address
79
+ self._UseSSL:bool = useSSL
80
+ self._Version:str = version
81
+
82
+ # get uri used to access the Spotify Zeroconf API for the device.
83
+ self._Uri:str = self.GetEndpointUri()
84
+
85
+
86
+ @property
87
+ def CPath(self) -> str:
88
+ """
89
+ Spotify Connect Zeroconf API CPath property value (e.g. "/zc").
90
+ """
91
+ return self._CPath
92
+
93
+
94
+ @property
95
+ def HostIpv4Address(self) -> str:
96
+ """
97
+ IPV4 address (as a string) at which the Spotify Connect Zeroconf API can be reached
98
+ on the Spotify Connect device (e.g. "192.168.1.81").
99
+ """
100
+ return self._HostIpv4Address
101
+
102
+
103
+ @property
104
+ def HostIpPort(self) -> int:
105
+ """
106
+ Port number (as an integer) at which the Spotify Connect Zeroconf API can be reached
107
+ on the Spotify Connect device (e.g. "8200").
108
+ """
109
+ return self._HostIpPort
110
+
111
+
112
+ @property
113
+ def Uri(self) -> str:
114
+ """
115
+ Spotify Connect Zeroconf API URI that will be used to access the device (e.g. "http://192.168.1.81:8200/zc").
116
+ """
117
+ return self._Uri
118
+
119
+
120
+ @property
121
+ def UseSSL(self) -> str:
122
+ """
123
+ True if the host device utilizes HTTPS Secure Sockets Layer (SSL) support;
124
+ otherwise, False to utilize HTTP.
125
+
126
+ Default is False (HTTP).
127
+ """
128
+ return self._UseSSL
129
+
130
+
131
+ @property
132
+ def Version(self) -> str:
133
+ """
134
+ Spotify Connect Zeroconf API version number that the device supports (e.g. "2.10.0").
135
+ """
136
+ return self._Version
137
+
138
+
139
+ def _CheckResponseForErrors(self, response:Response, methodName:str) -> None:
140
+ """
141
+ Checks the Spotify Zeroconf API response for errors.
142
+
143
+ If no errors were found, then the request response data is converted to a JSON dictionary
144
+ and returned to the caller.
145
+
146
+ If an error result is found, then a `SpotifyZeroconfApiError` is raised to inform the
147
+ user of the error.
148
+
149
+ Args:
150
+ response (Response):
151
+ Spotify Web API http response object.
152
+ methodName (str):
153
+ method name that made the request (for trace purposes).
154
+
155
+ Raises:
156
+ SpotifyZeroconfApiError:
157
+ If the Spotify Zeroconf API request response contains error information.
158
+ """
159
+ responseData:dict = None
160
+ responseUTF8:str = None
161
+ contentType:str = None
162
+
163
+ try:
164
+
165
+ # trace.
166
+ if _logsi.IsOn(SILevel.Debug):
167
+ _logsi.LogObject(SILevel.Debug, 'ZeroconfConnect http response object - type="%s", module="%s"' % (type(response).__name__, type(response).__module__), response)
168
+
169
+ # safely get the response url value.
170
+ # for some reason, the 'url' attribute is not present sometimes if a redirect occurs on the request.
171
+ responseUrl:str = None
172
+ if hasattr(response, 'url'):
173
+ responseUrl = response.url
174
+ elif hasattr(response, '_request_url'):
175
+ responseUrl = response._request_url
176
+ else:
177
+ try:
178
+ responseUrl = response.geturl()
179
+ except Exception:
180
+ _logsi.LogWarning('HTTPResponse method "geturl()" could not be called - defaulting to "unknown response url"')
181
+ responseUrl = 'Unknown response url'
182
+
183
+ # trace.
184
+ if _logsi.IsOn(SILevel.Debug):
185
+ _logsi.LogObject(SILevel.Debug, "ZeroconfConnect http response [%s-%s]: '%s' (response)" % (response.status_code, response.reason, responseUrl), response)
186
+ if (response.headers):
187
+ _logsi.LogCollection(SILevel.Debug, "ZeroconfConnect http response [%s-%s]: '%s' (headers)" % (response.status_code, response.reason, responseUrl), response.headers.items())
188
+
189
+ if response.content is not None:
190
+
191
+ # do response headers contain a content-type value?
192
+ # if so, we will use it to determine how to convert the response data.
193
+ if response.headers:
194
+ if 'content-type' in response.headers:
195
+ contentType = response.headers['content-type']
196
+
197
+ # do we have response data?
198
+ if len(response.content) == 0:
199
+
200
+ # some requests will not return a response, which is ok.
201
+ responseData = None
202
+ _logsi.LogVerbose("ZeroconfConnect http response [%s-%s]: '%s' (no data)" % (response.status_code, response.reason, responseUrl))
203
+
204
+ elif (contentType is not None) and (contentType.find('json') > -1):
205
+
206
+ # response is json.
207
+ # do not use the "response.json()" method to parse JSON responses, as it is unreliable!
208
+ data = response.content.decode('utf-8')
209
+ responseData = json.loads(data)
210
+
211
+ if _logsi.IsOn(SILevel.Verbose):
212
+ if isinstance(responseData, dict):
213
+ _logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json dict)" % (response.status_code, response.reason, responseUrl), responseData)
214
+ elif isinstance(responseData, list):
215
+ _logsi.LogArray(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json array)" % (response.status_code, response.reason, responseUrl), responseData)
216
+ else:
217
+ _logsi.LogObject(SILevel.Verbose, "ZeroconfConnect http response [%s-%s]: '%s' (json object)" % (response.status_code, response.reason, responseUrl), responseData)
218
+
219
+ else:
220
+
221
+ # no - treat it as utf-8 encoded data.
222
+ responseUTF8 = response.content.decode('utf-8')
223
+ _logsi.LogText(SILevel.Error, "ZeroconfConnect http response [%s-%s]: '%s' (utf-8)" % (response.status_code, response.reason, responseUrl), responseUTF8)
224
+ responseData = responseUTF8
225
+
226
+ except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
227
+ except SpotifyApiError: raise # pass handled exceptions on thru
228
+ except Exception as ex:
229
+
230
+ _logsi.LogException("ZeroconfConnect http response could not be converted to JSON and will be converted to utf-8.\nConversion exception returned was:\n{ex}".format(ex=str(ex)), ex, logToSystemLogger=False)
231
+
232
+ # if json conversion failed, then convert to utf-8 response.
233
+ if response.content is not None:
234
+ responseUTF8 = response.content.decode('utf-8')
235
+ _logsi.LogText(SILevel.Error, "ZeroconfConnect http response [%s-%s]: '%s' (utf-8)" % (response.status_code, response.reason, responseUrl), responseUTF8)
236
+
237
+ # at this point we don't know what Spotify Web Api returned, so let's
238
+ # just raise a new exception with the non-JSON response data.
239
+ raise SpotifyZeroconfApiError(response.status_code, responseUTF8, methodName, response.reason, _logsi)
240
+
241
+ errCode:str = None
242
+ errMessage:str = None
243
+
244
+ # if response is not in the 2xx range then it's an error, even
245
+ # though no json 'error' response body was returned! this is
246
+ # usually due to a '405 - method not allowed' or '403-Forbidden',
247
+ # or '503 Server Error' response.
248
+ if response.status_code > 299:
249
+
250
+ errCode = response.status_code
251
+ errMessage = response.reason
252
+
253
+ if responseUTF8 is not None:
254
+ errMessage = responseUTF8
255
+
256
+ raise SpotifyZeroconfApiError(errCode, errMessage, methodName, response.reason, _logsi)
257
+
258
+ # no errors found - set message object response data.
259
+ return responseData
260
+
261
+
262
+ def Connect(self,
263
+ username:str,
264
+ password:str,
265
+ ) -> ZeroconfResponse:
266
+ """
267
+ Calls the `addUser` Spotify Zeroconf API endpoint to issue a call to SpConnectionLoginBlob. If successful,
268
+ the associated device id is added to the Spotify Connect active device list for the specified user account.
269
+
270
+
271
+ Args:
272
+ username (str):
273
+ Spotify Connect user name to login with.
274
+ password (str):
275
+ Spotify Connect user password to login with.
276
+
277
+ Returns:
278
+ A `ZeroconfResponse` object that indicates success or failure (see notes below).
279
+
280
+ Raises:
281
+ SpotifyWebApiError:
282
+ If the Spotify Zeroconf API request response contains error information.
283
+
284
+ This will first issue a call to the `getInfo` Spotify Zeroconf API endpoint to retrieve the Spotify
285
+ Connect device id associated with the device. It will then issue a call to the `addUser` Spotify Zeroconf
286
+ API endpoint to add the user to the device.
287
+
288
+ The login (on the device) is performed asynchronously, so the return result only indicates whether the library
289
+ is able to perform the login attempt. You should issue a call to the Spotify Web API `Get Available Devices`
290
+ endpoint to check the current device list to ensure that the device id was successfully added or not.
291
+
292
+ <details>
293
+ <summary>Sample Code</summary>
294
+ ```python
295
+ .. include:: ../../docs/include/samplecode/ZeroconfConnect/Connect.py
296
+ ```
297
+ </details>
298
+ """
299
+ apiMethodName:str = 'Connect'
300
+ apiMethodParms:SIMethodParmListContext = None
301
+ result:ZeroconfResponse = None
302
+ response:Response = None
303
+
304
+ try:
305
+
306
+ # trace.
307
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
308
+ apiMethodParms.AppendKeyValue("HostIpv4Address", self._HostIpv4Address)
309
+ apiMethodParms.AppendKeyValue("HostIpPort", self._HostIpPort)
310
+ apiMethodParms.AppendKeyValue("CPath", self._CPath)
311
+ apiMethodParms.AppendKeyValue("Version", self._Version)
312
+ apiMethodParms.AppendKeyValue("Uri", self._Uri)
313
+ apiMethodParms.AppendKeyValue("username", username)
314
+ _logsi.LogMethodParmList(SILevel.Verbose, "Connecting device to Spotify Connect using specified Username and Password", apiMethodParms)
315
+
316
+ # validations.
317
+ if (username is None) or (not isinstance(username,str)):
318
+ raise SpotifyApiError(SAAppMessages.ARGUMENT_REQUIRED_ERROR % (apiMethodName, 'username'), logsi=_logsi)
319
+ if (password is None) or (not isinstance(password,str)):
320
+ raise SpotifyApiError(SAAppMessages.ARGUMENT_REQUIRED_ERROR % (apiMethodName, 'password'), logsi=_logsi)
321
+
322
+ # get the current device id from the device via Spotify ZeroConf API `getInfo` endpoint.
323
+ info:ZeroconfGetInfo = self.GetInformation()
324
+
325
+ # formulate the blob.
326
+ credentials:Credentials = Credentials(username, password)
327
+ builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
328
+ blob = builder.build()
329
+
330
+ # execute spotify zeroconf api request.
331
+ response = requests.post(
332
+ self._Uri,
333
+ timeout=10,
334
+ headers={
335
+ 'Content-Type': 'application/x-www-form-urlencoded'
336
+ },
337
+ data={
338
+ 'action': 'addUser',
339
+ 'VERSION': self._Version,
340
+ 'userName': credentials.username.decode('ascii'),
341
+ 'clientKey': int_to_b64str(builder.dh_keys.public_key),
342
+ 'blob': blob
343
+ }
344
+ )
345
+
346
+ # check response for errors, and return json response.
347
+ responseData:dict = self._CheckResponseForErrors(response, apiMethodName)
348
+
349
+ # process results.
350
+ result = ZeroconfResponse(root=responseData)
351
+
352
+ # trace.
353
+ _logsi.LogObject(SILevel.Verbose, '%s result - type="%s"' % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
354
+ return result
355
+
356
+ except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
357
+ except SpotifyApiError: raise # pass handled exceptions on thru
358
+ except Exception as ex:
359
+
360
+ # format unhandled exception.
361
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
362
+
363
+ finally:
364
+
365
+ # close the response (if needed).
366
+ if response is not None:
367
+ response.close()
368
+
369
+ # trace.
370
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
371
+
372
+
373
+ def Disconnect(self) -> ZeroconfResponse:
374
+ """
375
+ Calls the `resetUsers` Spotify Zeroconf API endpoint to issue a call to SpConnectionLogout.
376
+
377
+ Returns:
378
+ A `ZeroconfResponse` object that indicates success or failure (see notes below).
379
+
380
+ Raises:
381
+ SpotifyWebApiError:
382
+ If the Spotify Zeroconf API request response contains error information.
383
+
384
+ The currently logged in user (if any) will be logged out of Spotify Connect, and the
385
+ device id removed from the active Spotify Connect device list.
386
+
387
+ <details>
388
+ <summary>Sample Code</summary>
389
+ ```python
390
+ .. include:: ../../docs/include/samplecode/ZeroconfConnect/Disconnect.py
391
+ ```
392
+ </details>
393
+ """
394
+ apiMethodName:str = 'Disconnect'
395
+ apiMethodParms:SIMethodParmListContext = None
396
+ result:ZeroconfResponse = None
397
+ response:Response = None
398
+
399
+ try:
400
+
401
+ # trace.
402
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
403
+ apiMethodParms.AppendKeyValue("HostIpv4Address", self._HostIpv4Address)
404
+ apiMethodParms.AppendKeyValue("HostIpPort", self._HostIpPort)
405
+ apiMethodParms.AppendKeyValue("CPath", self._CPath)
406
+ apiMethodParms.AppendKeyValue("Version", self._Version)
407
+ apiMethodParms.AppendKeyValue("Uri", self._Uri)
408
+ _logsi.LogMethodParmList(SILevel.Verbose, "Disconnecting device from Spotify Connect", apiMethodParms)
409
+
410
+ # execute spotify zeroconf api request.
411
+ response = requests.post(
412
+ self._Uri,
413
+ timeout=10,
414
+ headers={
415
+ 'Content-Type': 'application/x-www-form-urlencoded'
416
+ },
417
+ data={
418
+ 'action': 'resetUsers',
419
+ 'VERSION': self._Version,
420
+ }
421
+ )
422
+
423
+ # check response for errors, and return json response.
424
+ responseData:dict = self._CheckResponseForErrors(response, apiMethodName)
425
+
426
+ # process results.
427
+ result = ZeroconfResponse(root=responseData)
428
+
429
+ # just in case user tries to issue a connect immediately after this, give the device
430
+ # a little time to reset the connection so the subsequent connect does not fail.
431
+ time.sleep(0.5)
432
+
433
+ # trace.
434
+ _logsi.LogObject(SILevel.Verbose, '%s result - type="%s"' % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
435
+ return result
436
+
437
+ except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
438
+ except SpotifyApiError: raise # pass handled exceptions on thru
439
+ except Exception as ex:
440
+
441
+ # format unhandled exception.
442
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
443
+
444
+ finally:
445
+
446
+ # close the response (if needed).
447
+ if response is not None:
448
+ response.close()
449
+
450
+ # trace.
451
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
452
+
453
+
454
+ def GetEndpointUri(self) -> str:
455
+ """
456
+ Gets a Spotify Zeroconf API endpoint uri.
457
+
458
+ Returns:
459
+ A string containing the endpoint uri.
460
+ """
461
+ protocol:str = 'http'
462
+ if (self._UseSSL):
463
+ protocol = 'https'
464
+
465
+ return "{protocol}://{ip}:{port}{cpath}".format(
466
+ protocol=protocol,
467
+ ip=self._HostIpv4Address,
468
+ port=self._HostIpPort,
469
+ cpath=self._CPath,
470
+ )
471
+
472
+
473
+ def GetInformation(self) -> ZeroconfGetInfo:
474
+ """
475
+ Calls the `getInfo` Spotify Zeroconf API endpoint to return information about the device.
476
+
477
+ Returns:
478
+ A `ZeroconfGetInfo` object that indicates success or failure (see notes below), as well
479
+ as the device information.
480
+
481
+ Raises:
482
+ SpotifyWebApiError:
483
+ If the Spotify Zeroconf API request response contains error information.
484
+
485
+ <details>
486
+ <summary>Sample Code</summary>
487
+ ```python
488
+ .. include:: ../../docs/include/samplecode/ZeroconfConnect/GetInformation.py
489
+ ```
490
+ </details>
491
+ """
492
+ apiMethodName:str = 'GetInformation'
493
+ apiMethodParms:SIMethodParmListContext = None
494
+ result:ZeroconfGetInfo = None
495
+ response:Response = None
496
+
497
+ try:
498
+
499
+ # trace.
500
+ apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
501
+ apiMethodParms.AppendKeyValue("HostIpv4Address", self._HostIpv4Address)
502
+ apiMethodParms.AppendKeyValue("HostIpPort", self._HostIpPort)
503
+ apiMethodParms.AppendKeyValue("CPath", self._CPath)
504
+ apiMethodParms.AppendKeyValue("Version", self._Version)
505
+ apiMethodParms.AppendKeyValue("Uri", self._Uri)
506
+ _logsi.LogMethodParmList(SILevel.Verbose, "Get information from Spotify Connect for device", apiMethodParms)
507
+
508
+ # execute spotify zeroconf api request.
509
+ response = requests.get(
510
+ self._Uri,
511
+ timeout=10,
512
+ headers={
513
+ 'Content-Type': 'application/x-www-form-urlencoded'
514
+ },
515
+ params={
516
+ 'action': 'getInfo',
517
+ 'VERSION': self._Version,
518
+ })
519
+
520
+ # check response for errors, and return json response.
521
+ responseData:dict = self._CheckResponseForErrors(response, apiMethodName)
522
+
523
+ # process results.
524
+ result = ZeroconfGetInfo(root=responseData)
525
+
526
+ # trace.
527
+ _logsi.LogObject(SILevel.Verbose, '%s result - type="%s"' % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
528
+ return result
529
+
530
+ except SpotifyZeroconfApiError: raise # pass handled exceptions on thru
531
+ except SpotifyApiError: raise # pass handled exceptions on thru
532
+ except Exception as ex:
533
+
534
+ # format unhandled exception.
535
+ raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
536
+
537
+ finally:
538
+
539
+ # close the response (if needed).
540
+ if response is not None:
541
+ response.close()
542
+
543
+ # trace.
544
+ _logsi.LeaveMethod(SILevel.Debug, apiMethodName)
545
+
546
+
547
+ def ToString(self, includeTitle:bool=False) -> str:
548
+ """
549
+ Returns a displayable string representation of the class.
550
+
551
+ Args:
552
+ includeTitle (str):
553
+ True to include the class name title prefix.
554
+ """
555
+ msg:str = ''
556
+ if includeTitle:
557
+ msg = 'ZeroconfConnect:'
558
+
559
+ msg = '%s\n URI="%s"' % (msg, str(self._Uri))
560
+ if self._CPath is not None: msg = '%s\n CPath="%s"' % (msg, str(self._CPath))
561
+ if self._Version is not None: msg = '%s\n Version="%s"' % (msg, str(self._Version))
562
+ return msg