spotifywebapiPython 1.0.50__py3-none-any.whl → 1.0.52__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.50.dist-info → spotifywebapiPython-1.0.52.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.50.dist-info → spotifywebapiPython-1.0.52.dist-info}/RECORD +27 -25
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +14 -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 +111 -3
- spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
- spotifywebapipython/models/__init__.py +2 -0
- spotifywebapipython/models/device.py +50 -0
- spotifywebapipython/models/spotifyconnectdevice.py +93 -0
- spotifywebapipython/models/spotifyconnectdevices.py +240 -0
- spotifywebapipython/models/zeroconfdiscoveryresult.py +13 -5
- spotifywebapipython/spotifyclient.py +144 -36
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +32 -0
- spotifywebapipython/zeroconfapi/zeroconfresponse.py +24 -0
- {spotifywebapiPython-1.0.50.dist-info → spotifywebapiPython-1.0.52.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.50.dist-info → spotifywebapiPython-1.0.52.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.50.dist-info → spotifywebapiPython-1.0.52.dist-info}/top_level.txt +0 -0
|
@@ -64,6 +64,24 @@ class Device:
|
|
|
64
64
|
return self.ToString()
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
# implement sorting support.
|
|
68
|
+
def __eq__(self, other):
|
|
69
|
+
try:
|
|
70
|
+
return self.Name == other.Name
|
|
71
|
+
except Exception as ex:
|
|
72
|
+
if (isinstance(self, Device )) and (isinstance(other, Device )):
|
|
73
|
+
return self.Name == other.Name
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def __lt__(self, other):
|
|
77
|
+
try:
|
|
78
|
+
return self.Name < other.Name
|
|
79
|
+
except Exception as ex:
|
|
80
|
+
if (isinstance(self, Device )) and (isinstance(other, Device )):
|
|
81
|
+
return self.Name < other.Name
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
|
|
67
85
|
@property
|
|
68
86
|
def Id(self) -> str:
|
|
69
87
|
"""
|
|
@@ -74,6 +92,14 @@ class Device:
|
|
|
74
92
|
"""
|
|
75
93
|
return self._Id
|
|
76
94
|
|
|
95
|
+
@Id.setter
|
|
96
|
+
def Id(self, value:str):
|
|
97
|
+
"""
|
|
98
|
+
Sets the Id property value.
|
|
99
|
+
"""
|
|
100
|
+
if isinstance(value, str):
|
|
101
|
+
self._Id = value
|
|
102
|
+
|
|
77
103
|
|
|
78
104
|
@property
|
|
79
105
|
def IsActive(self) -> bool:
|
|
@@ -82,6 +108,14 @@ class Device:
|
|
|
82
108
|
"""
|
|
83
109
|
return self._IsActive
|
|
84
110
|
|
|
111
|
+
@IsActive.setter
|
|
112
|
+
def IsActive(self, value:bool):
|
|
113
|
+
"""
|
|
114
|
+
Sets the IsActive property value.
|
|
115
|
+
"""
|
|
116
|
+
if isinstance(value, bool):
|
|
117
|
+
self._IsActive = value
|
|
118
|
+
|
|
85
119
|
|
|
86
120
|
@property
|
|
87
121
|
def IsMuted(self) -> bool:
|
|
@@ -122,6 +156,14 @@ class Device:
|
|
|
122
156
|
"""
|
|
123
157
|
return self._Name
|
|
124
158
|
|
|
159
|
+
@Name.setter
|
|
160
|
+
def Name(self, value:str):
|
|
161
|
+
"""
|
|
162
|
+
Sets the Name property value.
|
|
163
|
+
"""
|
|
164
|
+
if isinstance(value, str):
|
|
165
|
+
self._Name = value
|
|
166
|
+
|
|
125
167
|
|
|
126
168
|
@property
|
|
127
169
|
def SelectItemNameAndId(self) -> str:
|
|
@@ -151,6 +193,14 @@ class Device:
|
|
|
151
193
|
"""
|
|
152
194
|
return self._Type
|
|
153
195
|
|
|
196
|
+
@Type.setter
|
|
197
|
+
def Type(self, value:str):
|
|
198
|
+
"""
|
|
199
|
+
Sets the Type property value.
|
|
200
|
+
"""
|
|
201
|
+
if isinstance(value, str):
|
|
202
|
+
self._Type = value
|
|
203
|
+
|
|
154
204
|
|
|
155
205
|
@property
|
|
156
206
|
def VolumePercent(self) -> int:
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
from zeroconf import ServiceInfo
|
|
3
|
+
|
|
4
|
+
# our package imports.
|
|
5
|
+
from ..sautils import export
|
|
6
|
+
from .zeroconfdiscoveryresult import ZeroconfDiscoveryResult
|
|
7
|
+
from spotifywebapipython.zeroconfapi.zeroconfgetinfo import ZeroconfGetInfo
|
|
8
|
+
|
|
9
|
+
@export
|
|
10
|
+
class SpotifyConnectDevice():
|
|
11
|
+
"""
|
|
12
|
+
Spotify Connect Device object.
|
|
13
|
+
|
|
14
|
+
Information about the Spotify Connect device, which is a combination of the
|
|
15
|
+
`ZeroconfDiscoveryResult` and `ZeroconfGetInfo` classes.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Initializes a new instance of the class.
|
|
21
|
+
"""
|
|
22
|
+
self._DiscoveryResult:ZeroconfDiscoveryResult = None
|
|
23
|
+
self._DeviceInfo:ZeroconfGetInfo = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def __repr__(self) -> str:
|
|
27
|
+
return self.ToString()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def __str__(self) -> str:
|
|
31
|
+
return self.ToString()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def DiscoveryResult(self) -> ZeroconfDiscoveryResult:
|
|
36
|
+
"""
|
|
37
|
+
Information about the Zeroconf entry for a SpotifyConnect device as found by Zeroconf (mDNS).
|
|
38
|
+
"""
|
|
39
|
+
return self._DiscoveryResult
|
|
40
|
+
|
|
41
|
+
@DiscoveryResult.setter
|
|
42
|
+
def DiscoveryResult(self, value:ZeroconfDiscoveryResult):
|
|
43
|
+
"""
|
|
44
|
+
Sets the DiscoveryResult property value.
|
|
45
|
+
"""
|
|
46
|
+
if isinstance(value, ZeroconfDiscoveryResult):
|
|
47
|
+
self._DiscoveryResult = value
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def DeviceInfo(self) -> ZeroconfGetInfo:
|
|
52
|
+
"""
|
|
53
|
+
Spotify Zeroconf API GetInfo response object.
|
|
54
|
+
"""
|
|
55
|
+
return self._DeviceInfo
|
|
56
|
+
|
|
57
|
+
@DeviceInfo.setter
|
|
58
|
+
def DeviceInfo(self, value:ZeroconfGetInfo):
|
|
59
|
+
"""
|
|
60
|
+
Sets the DeviceInfo property value.
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(value, ZeroconfGetInfo):
|
|
63
|
+
self._DeviceInfo = value
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ToDictionary(self) -> dict:
|
|
67
|
+
"""
|
|
68
|
+
Returns a dictionary representation of the class.
|
|
69
|
+
"""
|
|
70
|
+
result:dict = \
|
|
71
|
+
{
|
|
72
|
+
'DeviceInfo': self._DeviceInfo.ToDictionary(),
|
|
73
|
+
'DiscoveryResult': self._DiscoveryResult.ToDictionary(),
|
|
74
|
+
}
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def ToString(self, includeTitle:bool=True) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Returns a displayable string representation of the class.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
includeTitle (str):
|
|
84
|
+
True to include the class name title prefix.
|
|
85
|
+
"""
|
|
86
|
+
msg:str = ''
|
|
87
|
+
if includeTitle:
|
|
88
|
+
msg = 'Device: "%s" (%s)' % (self._DiscoveryResult.DeviceName, self._DeviceInfo.DeviceId)
|
|
89
|
+
|
|
90
|
+
# build result.
|
|
91
|
+
msg = '%s\n %s\n' % (msg, str(self._DeviceInfo.ToString(True)))
|
|
92
|
+
msg = '%s\n %s\n' % (msg, str(self._DiscoveryResult.ToString(True)))
|
|
93
|
+
return msg
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# external package imports.
|
|
2
|
+
from typing import Iterator
|
|
3
|
+
|
|
4
|
+
# our package imports.
|
|
5
|
+
from ..sautils import export
|
|
6
|
+
from .device import Device
|
|
7
|
+
from .spotifyconnectdevice import SpotifyConnectDevice
|
|
8
|
+
from .zeroconfdiscoveryresult import ZeroconfDiscoveryResult
|
|
9
|
+
from spotifywebapipython.zeroconfapi import ZeroconfGetInfo, ZeroconfGetInfoAlias
|
|
10
|
+
|
|
11
|
+
@export
|
|
12
|
+
class SpotifyConnectDevices():
|
|
13
|
+
"""
|
|
14
|
+
Spotify Connect Devices collection.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Initializes a new instance of the class.
|
|
20
|
+
"""
|
|
21
|
+
self._Items:list[SpotifyConnectDevice] = []
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def __getitem__(self, key) -> SpotifyConnectDevice:
|
|
25
|
+
return self._Presets[key]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def __iter__(self) -> Iterator:
|
|
29
|
+
return iter(self._Items)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def __len__(self) -> int:
|
|
33
|
+
return len(self._Items)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return self.ToString()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def __str__(self) -> str:
|
|
41
|
+
return self.ToString()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def Items(self) -> list[SpotifyConnectDevice]:
|
|
46
|
+
"""
|
|
47
|
+
Array of `SpotifyConnectDevice` objects.
|
|
48
|
+
"""
|
|
49
|
+
return self._Items
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def ItemsCount(self) -> int:
|
|
54
|
+
"""
|
|
55
|
+
Number of objects in the `Items` property array.
|
|
56
|
+
"""
|
|
57
|
+
if self._Items is not None:
|
|
58
|
+
return len(self._Items)
|
|
59
|
+
return 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def AddDynamicDevice(self, device:Device, activeUser:str) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Adds a new dynamic device entry to the `Items` collection.
|
|
65
|
+
|
|
66
|
+
Dynamic devices are Spotify Connect devices that are not found in Zeroconf discovery
|
|
67
|
+
process, but still exist in the player device list. These are usually devices in
|
|
68
|
+
a Spotify Connect web or mobile player.
|
|
69
|
+
"""
|
|
70
|
+
# validations.
|
|
71
|
+
if (device is None) or (not isinstance(device, Device)):
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# build zeroconf discovery result with what we can.
|
|
75
|
+
discoverResult:ZeroconfDiscoveryResult = ZeroconfDiscoveryResult()
|
|
76
|
+
discoverResult.DeviceName = device.Name
|
|
77
|
+
discoverResult.HostIpv4Address = '127.0.0.1'
|
|
78
|
+
discoverResult.HostIpPort = 0
|
|
79
|
+
discoverResult.Name = device.Name
|
|
80
|
+
discoverResult.SpotifyConnectCPath = '/zc'
|
|
81
|
+
discoverResult.SpotifyConnectVersion = '1.0'
|
|
82
|
+
|
|
83
|
+
# build zeroconf getinfo result with what we can.
|
|
84
|
+
info:ZeroconfGetInfo = ZeroconfGetInfo()
|
|
85
|
+
info.ActiveUser = activeUser
|
|
86
|
+
info.DeviceId = device.Id
|
|
87
|
+
info.DeviceType = device.Type
|
|
88
|
+
info.RemoteName = device.Name
|
|
89
|
+
info.SpotifyError = 0
|
|
90
|
+
info.Status = 101
|
|
91
|
+
info.StatusString = 'OK'
|
|
92
|
+
|
|
93
|
+
# add the device.
|
|
94
|
+
scDevice:SpotifyConnectDevice = SpotifyConnectDevice()
|
|
95
|
+
scDevice.DeviceInfo = info
|
|
96
|
+
scDevice.DiscoveryResult = discoverResult
|
|
97
|
+
self._Items.append(scDevice)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def ContainsDeviceId(self, value:str) -> bool:
|
|
101
|
+
"""
|
|
102
|
+
Returns True if the `Items` collection contains the specified device id value;
|
|
103
|
+
otherwise, False.
|
|
104
|
+
|
|
105
|
+
Alias entries (if any) are also compared.
|
|
106
|
+
"""
|
|
107
|
+
result:bool = False
|
|
108
|
+
if value is None:
|
|
109
|
+
return result
|
|
110
|
+
|
|
111
|
+
# convert case for comparison.
|
|
112
|
+
value = value.lower()
|
|
113
|
+
|
|
114
|
+
# process all discovered devices.
|
|
115
|
+
scDevice:SpotifyConnectDevice
|
|
116
|
+
for scDevice in self._Items:
|
|
117
|
+
if (scDevice.DeviceInfo.HasAliases):
|
|
118
|
+
scAlias:ZeroconfGetInfoAlias
|
|
119
|
+
for scAlias in scDevice.DeviceInfo.Aliases:
|
|
120
|
+
if (scAlias.Id.lower() == value):
|
|
121
|
+
result = True
|
|
122
|
+
break
|
|
123
|
+
if result:
|
|
124
|
+
break
|
|
125
|
+
else:
|
|
126
|
+
if (scDevice.DeviceInfo.DeviceId.lower() == value):
|
|
127
|
+
result = True
|
|
128
|
+
break
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def ContainsDeviceName(self, value:str) -> bool:
|
|
133
|
+
"""
|
|
134
|
+
Returns True if the `Items` collection contains the specified device name value;
|
|
135
|
+
otherwise, False.
|
|
136
|
+
|
|
137
|
+
Alias entries (if any) are also compared.
|
|
138
|
+
"""
|
|
139
|
+
result:bool = False
|
|
140
|
+
if value is None:
|
|
141
|
+
return result
|
|
142
|
+
|
|
143
|
+
# convert case for comparison.
|
|
144
|
+
value = value.lower()
|
|
145
|
+
|
|
146
|
+
# process all discovered devices.
|
|
147
|
+
scDevice:SpotifyConnectDevice
|
|
148
|
+
for scDevice in self._Items:
|
|
149
|
+
if (scDevice.DeviceInfo.HasAliases):
|
|
150
|
+
scAlias:ZeroconfGetInfoAlias
|
|
151
|
+
for scAlias in scDevice.DeviceInfo.Aliases:
|
|
152
|
+
if (scAlias.Name.lower() == value):
|
|
153
|
+
result = True
|
|
154
|
+
break
|
|
155
|
+
if result:
|
|
156
|
+
break
|
|
157
|
+
else:
|
|
158
|
+
if (scDevice.DeviceInfo.RemoteName.lower() == value):
|
|
159
|
+
result = True
|
|
160
|
+
break
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def GetDeviceList(self) -> list[Device]:
|
|
165
|
+
"""
|
|
166
|
+
Returns a list of `Device` objects that can be used to build a selection list
|
|
167
|
+
of available devices.
|
|
168
|
+
|
|
169
|
+
Note that the `Device` object has the following properties populated:
|
|
170
|
+
Id, Name, IsActive, Type.
|
|
171
|
+
"""
|
|
172
|
+
result:list[Device] = []
|
|
173
|
+
|
|
174
|
+
# process all discovered devices.
|
|
175
|
+
scDevice:SpotifyConnectDevice
|
|
176
|
+
for scDevice in self._Items:
|
|
177
|
+
|
|
178
|
+
# map device information details.
|
|
179
|
+
info:ZeroconfGetInfo = scDevice.DeviceInfo
|
|
180
|
+
|
|
181
|
+
# create new mock device.
|
|
182
|
+
device = Device()
|
|
183
|
+
device.IsActive = info.HasActiveUser
|
|
184
|
+
device.Type = info.DeviceType
|
|
185
|
+
|
|
186
|
+
# are aliases being used (RemoteName is null if so)?
|
|
187
|
+
if info.RemoteName is None:
|
|
188
|
+
|
|
189
|
+
# if aliases are defined, then use the alias details.
|
|
190
|
+
infoAlias:ZeroconfGetInfoAlias
|
|
191
|
+
for infoAlias in info.Aliases:
|
|
192
|
+
device.Id = infoAlias.Id
|
|
193
|
+
device.Name = infoAlias.Name
|
|
194
|
+
|
|
195
|
+
else:
|
|
196
|
+
|
|
197
|
+
# if no aliases then use the remote name and id.
|
|
198
|
+
device.Id = info.DeviceId
|
|
199
|
+
device.Name = info.RemoteName
|
|
200
|
+
|
|
201
|
+
# append device to results.
|
|
202
|
+
result.append(device)
|
|
203
|
+
|
|
204
|
+
# sort items on Name property, ascending order.
|
|
205
|
+
if len(result) > 0:
|
|
206
|
+
result.sort(key=lambda x: (x.Name or "").lower(), reverse=False)
|
|
207
|
+
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def ToDictionary(self) -> dict:
|
|
212
|
+
"""
|
|
213
|
+
Returns a dictionary representation of the class.
|
|
214
|
+
"""
|
|
215
|
+
result:dict = \
|
|
216
|
+
{
|
|
217
|
+
'ItemsCount': self.ItemsCount,
|
|
218
|
+
'Items': [ item.ToDictionary() for item in self._Items ],
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def ToString(self, includeItems:bool=False) -> str:
|
|
225
|
+
"""
|
|
226
|
+
Returns a displayable string representation of the class.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
includeItems (bool):
|
|
230
|
+
True to include the Items collection of objects; otherwise, False
|
|
231
|
+
to only return base properties.
|
|
232
|
+
"""
|
|
233
|
+
msg:str = 'Items Count=%s' % (str(self.ItemsCount))
|
|
234
|
+
|
|
235
|
+
if (includeItems):
|
|
236
|
+
item:SpotifyConnectDevice
|
|
237
|
+
for item in self._Items:
|
|
238
|
+
msg = "%s\n- %s" % (msg, item.ToString(True))
|
|
239
|
+
|
|
240
|
+
return msg
|
|
@@ -16,11 +16,6 @@ class ZeroconfDiscoveryResult:
|
|
|
16
16
|
def __init__(self) -> None:
|
|
17
17
|
"""
|
|
18
18
|
Initializes a new instance of the class.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
root (dict):
|
|
22
|
-
Spotify Web API JSON response in dictionary format, used to load object
|
|
23
|
-
attributes; otherwise, None to not load attributes.
|
|
24
19
|
"""
|
|
25
20
|
self._DeviceName:str = None
|
|
26
21
|
self._Domain:str = '.'
|
|
@@ -132,6 +127,19 @@ class ZeroconfDiscoveryResult:
|
|
|
132
127
|
self._HostTTL = value
|
|
133
128
|
|
|
134
129
|
|
|
130
|
+
@property
|
|
131
|
+
def IsDynamicDevice(self) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Returns True if the device is a dynamic device;
|
|
134
|
+
otherwise, False.
|
|
135
|
+
|
|
136
|
+
Dynamic devices are Spotify Connect devices that are not found in Zeroconf discovery
|
|
137
|
+
process, but still exist in the player device list. These are usually Spotify Connect
|
|
138
|
+
web or mobile players with temporary device id's.
|
|
139
|
+
"""
|
|
140
|
+
return (self._HostIpPort == 0)
|
|
141
|
+
|
|
142
|
+
|
|
135
143
|
@property
|
|
136
144
|
def Key(self) -> str:
|
|
137
145
|
"""
|