spotifywebapiPython 1.0.178__py3-none-any.whl → 1.0.180__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.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/RECORD +23 -23
- 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 +54 -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 +7 -6
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html +2 -2
- spotifywebapipython/saappmessages.py +15 -0
- spotifywebapipython/spotifyclient.py +92 -43
- spotifywebapipython/spotifywebplayertoken.py +59 -42
- {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/top_level.txt +0 -0
|
@@ -1164,6 +1164,7 @@ class SpotifyClient:
|
|
|
1164
1164
|
- 500 Internal Server Error. You should never receive this error because our clever coders catch them all ... but if you are unlucky enough to get one, please report it to us through a comment at the bottom of this page.
|
|
1165
1165
|
- 502 Bad Gateway - The server was acting as a gateway or proxy and received an invalid response from the upstream server.
|
|
1166
1166
|
- 503 Service Unavailable - The server is currently unable to handle the request due to a temporary condition which will be alleviated after some delay. You can choose to resend the request again.
|
|
1167
|
+
- 504 Gateway Timeout - The server is currently unable to handle the request due to a temporary condition which will be alleviated after some delay. You can choose to resend the request again.
|
|
1167
1168
|
"""
|
|
1168
1169
|
apiMethodName:str = 'MakeRequest'
|
|
1169
1170
|
apiMethodParms:SIMethodParmListContext = None
|
|
@@ -1228,49 +1229,86 @@ class SpotifyClient:
|
|
|
1228
1229
|
# in the logic below, ensure that ALL urllib3.request method calls conform to version 1.26.18.
|
|
1229
1230
|
# urllib3 version 2.0 is not supported! see internal developer notes for more details.
|
|
1230
1231
|
|
|
1231
|
-
#
|
|
1232
|
-
|
|
1232
|
+
# request retry loop for failed requests that are temporary in nature (504 Gateway Timeout, etc).
|
|
1233
|
+
loopTotalDelay:float = 0
|
|
1234
|
+
LOOP_DELAY:float = 0.200
|
|
1235
|
+
LOOP_TIMEOUT:float = 1.000
|
|
1236
|
+
while True:
|
|
1237
|
+
|
|
1238
|
+
# call the appropriate poolmanager request method.
|
|
1239
|
+
if msg.HasUrlParameters:
|
|
1233
1240
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
+
# add querystring parameters to url; if url already has a partial parm
|
|
1242
|
+
# string (e.g. has a '?xxx=...') then use the append separator (e.g. '...&xxx=...').
|
|
1243
|
+
urlQS:str = urlencode(msg.UrlParameters)
|
|
1244
|
+
urlParmSep:str = '?'
|
|
1245
|
+
if (url.find('?') > 0):
|
|
1246
|
+
urlParmSep = '&'
|
|
1247
|
+
url = url + urlParmSep + urlQS
|
|
1241
1248
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1249
|
+
_logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with urlparms)" % (url), msg.UrlParameters, prettyPrint=True)
|
|
1250
|
+
response = self._Manager.request_encode_url(method, url, headers=msg.RequestHeaders)
|
|
1244
1251
|
|
|
1245
|
-
|
|
1252
|
+
elif msg.HasRequestData:
|
|
1246
1253
|
|
|
1247
|
-
|
|
1254
|
+
if msg.IsRequestDataEncoded:
|
|
1248
1255
|
|
|
1249
|
-
|
|
1250
|
-
|
|
1256
|
+
_logsi.LogText(SILevel.Verbose, "SpotifyClient http request: '%s' (with body encoded)" % (url), msg.RequestData)
|
|
1257
|
+
response = self._Manager.request(method, url, body=msg.RequestData, headers=msg.RequestHeaders)
|
|
1251
1258
|
|
|
1252
|
-
|
|
1259
|
+
else:
|
|
1253
1260
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1261
|
+
_logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with body)" % (url), msg.RequestData, prettyPrint=True)
|
|
1262
|
+
response = self._Manager.request_encode_body(method, url, fields=msg.RequestData, headers=msg.RequestHeaders, encode_multipart=False)
|
|
1256
1263
|
|
|
1257
|
-
|
|
1264
|
+
elif msg.HasRequestJson:
|
|
1258
1265
|
|
|
1259
|
-
|
|
1266
|
+
_logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (with json body)" % (url), msg.RequestJson, prettyPrint=True)
|
|
1260
1267
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
+
# add content-type=json header and convert the dictionary to json format.
|
|
1269
|
+
if not ("content-type" in map(str.lower, msg.RequestHeaders.keys())):
|
|
1270
|
+
#headers = HTTPHeaderDict(headers)
|
|
1271
|
+
msg.RequestHeaders["Content-Type"] = "application/json"
|
|
1272
|
+
reqBody:str = json.dumps(msg.RequestJson, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
|
|
1273
|
+
_logsi.LogBinary(SILevel.Debug, "SpotifyClient http request JSON body", reqBody)
|
|
1274
|
+
response = self._Manager.request(method, url, body=reqBody, headers=msg.RequestHeaders)
|
|
1268
1275
|
|
|
1269
|
-
|
|
1276
|
+
else:
|
|
1277
|
+
|
|
1278
|
+
_logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (no body)" % (url), msg.RequestData, prettyPrint=True)
|
|
1279
|
+
response = self._Manager.request(method, url, headers=msg.RequestHeaders)
|
|
1280
|
+
|
|
1281
|
+
# TEST TODO - for testing retry logic.
|
|
1282
|
+
# if (response.status_code == 200) and (loopTotalDelay <= 0.200):
|
|
1283
|
+
# _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
|
|
1284
|
+
# response.status_code = 404
|
|
1285
|
+
# response.reason = "URL Not Found"
|
|
1286
|
+
# if (response.status_code == 200) and (loopTotalDelay <= 0.200):
|
|
1287
|
+
# _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
|
|
1288
|
+
# response.status_code = 504
|
|
1289
|
+
# response.reason = "Gateway Timeout"
|
|
1290
|
+
|
|
1291
|
+
# check for errors that are temporary in nature; for these errors, we will retry the
|
|
1292
|
+
# request for a specified number of tries with a small wait period in between.
|
|
1293
|
+
if (response.status == 504):
|
|
1294
|
+
|
|
1295
|
+
# only retry so many times before we give up;
|
|
1296
|
+
if (loopTotalDelay >= LOOP_TIMEOUT):
|
|
1297
|
+
raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_TIMEOUT % (loopTotalDelay), None, logsi=_logsi)
|
|
1298
|
+
|
|
1299
|
+
# trace.
|
|
1300
|
+
_logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_RESPONSE_STATUS % (response.status, response.reason), colorValue=SIColors.Red)
|
|
1301
|
+
|
|
1302
|
+
# wait just a bit between requests.
|
|
1303
|
+
_logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_REQUEST_DELAY % (LOOP_DELAY))
|
|
1304
|
+
time.sleep(LOOP_DELAY)
|
|
1305
|
+
loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
1306
|
+
|
|
1307
|
+
else:
|
|
1308
|
+
|
|
1309
|
+
# otherwise, break out of retry loop and process response.
|
|
1310
|
+
break
|
|
1270
1311
|
|
|
1271
|
-
_logsi.LogDictionary(SILevel.Verbose, "SpotifyClient http request: '%s' (no body)" % (url), msg.RequestData, prettyPrint=True)
|
|
1272
|
-
response = self._Manager.request(method, url, headers=msg.RequestHeaders)
|
|
1273
|
-
|
|
1274
1312
|
# process based upon response status code; some requests will not return response data.
|
|
1275
1313
|
# I know this could have been simplified, but I broke it down into possible return code ranges.
|
|
1276
1314
|
if response.status >= 200 and response.status <= 299:
|
|
@@ -6320,9 +6358,9 @@ class SpotifyClient:
|
|
|
6320
6358
|
deviceId:str|SpotifyConnectDevice|None=None,
|
|
6321
6359
|
) -> PlayerPlayState:
|
|
6322
6360
|
"""
|
|
6323
|
-
Get information about the current playback state
|
|
6324
|
-
|
|
6325
|
-
|
|
6361
|
+
Get information about the current playback state, including track or episode, and progress.
|
|
6362
|
+
If the Spotify Web API reports nothing as playing, then the device-specific playback state
|
|
6363
|
+
is returned (if one exists).
|
|
6326
6364
|
|
|
6327
6365
|
This method requires the `user-read-playback-state` scope.
|
|
6328
6366
|
|
|
@@ -6365,7 +6403,7 @@ class SpotifyClient:
|
|
|
6365
6403
|
# trace.
|
|
6366
6404
|
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
6367
6405
|
apiMethodParms.AppendKeyValue("deviceId", deviceId)
|
|
6368
|
-
_logsi.LogMethodParmList(SILevel.Verbose, "Get the current playback state
|
|
6406
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get the current Spotify playback state, with device fallback", apiMethodParms)
|
|
6369
6407
|
|
|
6370
6408
|
# resolve the device object; no need to activate it for what we want here.
|
|
6371
6409
|
# this will also set the currently active device in the devices cache, as well as
|
|
@@ -6375,15 +6413,20 @@ class SpotifyClient:
|
|
|
6375
6413
|
# get cached Spotify Web API playerstate (_ResolveDeviceObject method just refreshed it).
|
|
6376
6414
|
result = self.GetPlayerPlaybackState(refresh=False)
|
|
6377
6415
|
|
|
6378
|
-
#
|
|
6379
|
-
if (
|
|
6416
|
+
# did Spotify return a playstate?
|
|
6417
|
+
if (result.IsEmpty):
|
|
6418
|
+
|
|
6419
|
+
# no; we will now check for a device-specific playstate.
|
|
6420
|
+
|
|
6421
|
+
# is this a Sonos device?
|
|
6422
|
+
if (scDevice is not None) and (scDevice.IsSonos):
|
|
6380
6423
|
|
|
6381
|
-
|
|
6382
|
-
|
|
6424
|
+
# get Sonos playback state via Sonos Controller instance.
|
|
6425
|
+
playerStateSonos:PlayerPlayState = self.GetPlayerPlaybackStateSonos(scDevice)
|
|
6383
6426
|
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6427
|
+
# return the device playstate.
|
|
6428
|
+
_logsi.LogVerbose("Sonos device %s playstate will be returned" % (scDevice.Title))
|
|
6429
|
+
result = playerStateSonos
|
|
6387
6430
|
|
|
6388
6431
|
# trace.
|
|
6389
6432
|
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
@@ -13467,7 +13510,13 @@ class SpotifyClient:
|
|
|
13467
13510
|
# note that for other brands, the Spotify Web API transfer playback takes care of
|
|
13468
13511
|
# pausing the player since everything is under the Spotify players control.
|
|
13469
13512
|
if (scDeviceFrom is not None) and (scDeviceFrom.IsSonos):
|
|
13470
|
-
self.PlayerMediaPause(scDeviceFrom)
|
|
13513
|
+
#self.PlayerMediaPause(scDeviceFrom)
|
|
13514
|
+
try:
|
|
13515
|
+
self.PlayerMediaPause(scDeviceFrom)
|
|
13516
|
+
except:
|
|
13517
|
+
# ignore exceptions, since this is a convenience call to pause the from player.
|
|
13518
|
+
_logsi.LogVerbose("Ignoring previous exception, since this is a convenience call to pause the from player")
|
|
13519
|
+
pass
|
|
13471
13520
|
|
|
13472
13521
|
# resolve the device object from the device id; activate if it's dormant.
|
|
13473
13522
|
# for transfer playback, we will always activate the device.
|
|
@@ -7,6 +7,7 @@ import requests
|
|
|
7
7
|
import time
|
|
8
8
|
|
|
9
9
|
# our package imports.
|
|
10
|
+
from .saappmessages import SAAppMessages
|
|
10
11
|
from .spotifyapierror import SpotifyApiError
|
|
11
12
|
from .const import (
|
|
12
13
|
SPOTIFY_WEBUI_URL_BASE,
|
|
@@ -14,7 +15,7 @@ from .const import (
|
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
# get smartinspect logger reference; create a new session for this module name.
|
|
17
|
-
from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext
|
|
18
|
+
from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SIColors
|
|
18
19
|
import logging
|
|
19
20
|
_logsi:SISession = SIAuto.Si.GetSession(__name__)
|
|
20
21
|
if (_logsi == None):
|
|
@@ -276,17 +277,17 @@ class SpotifyWebPlayerToken:
|
|
|
276
277
|
"productType": "web_player",
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
#
|
|
280
|
+
# trace.
|
|
281
|
+
_logsi.LogVerbose("Exchanging Spotify Web Player cookie credentials for OAuth2 authorization access token")
|
|
282
|
+
_logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (cookies)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqCookies)
|
|
283
|
+
_logsi.LogDictionary(SILevel.Debug, "%s http request: \"%s\" (headers)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqHeaders)
|
|
284
|
+
_logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (parms)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqParms)
|
|
285
|
+
|
|
286
|
+
# request retry loop for failed requests that are temporary in nature (504 Gateway Timeout, etc).
|
|
280
287
|
loopTotalDelay:float = 0
|
|
281
|
-
LOOP_DELAY:float = 0.
|
|
282
|
-
LOOP_TIMEOUT:float =
|
|
288
|
+
LOOP_DELAY:float = 0.200
|
|
289
|
+
LOOP_TIMEOUT:float = 1.000
|
|
283
290
|
while True:
|
|
284
|
-
|
|
285
|
-
# trace.
|
|
286
|
-
_logsi.LogVerbose("Exchanging Spotify Web Player cookie credentials for OAuth2 authorization access token")
|
|
287
|
-
_logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (cookies)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqCookies)
|
|
288
|
-
_logsi.LogDictionary(SILevel.Debug, "%s http request: \"%s\" (headers)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqHeaders)
|
|
289
|
-
_logsi.LogDictionary(SILevel.Verbose, "%s http request: \"%s\" (parms)" % (tracePrefix, SPOTIFY_WEBUI_URL_GET_ACCESS_TOKEN), reqParms)
|
|
290
291
|
|
|
291
292
|
# convert the spotify web player cookie credentials to an access token.
|
|
292
293
|
response = session.get(
|
|
@@ -297,42 +298,58 @@ class SpotifyWebPlayerToken:
|
|
|
297
298
|
allow_redirects=False,
|
|
298
299
|
)
|
|
299
300
|
|
|
300
|
-
#
|
|
301
|
-
if
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
#
|
|
305
|
-
if
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
#
|
|
311
|
-
#
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
if response.status_code == 200:
|
|
318
|
-
_logsi.LogVerbose("Token exchange request was successful within %f seconds from initial request; processing results" % (loopTotalDelay))
|
|
319
|
-
break
|
|
301
|
+
# TEST TODO - for testing retry logic.
|
|
302
|
+
# if (response.status_code == 200) and (loopTotalDelay <= 0.200):
|
|
303
|
+
# _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
|
|
304
|
+
# response.status_code = 404
|
|
305
|
+
# response.reason = "URL Not Found"
|
|
306
|
+
# if (response.status_code == 200) and (loopTotalDelay <= 0.200):
|
|
307
|
+
# _logsi.LogWarning("TEST TODO - Testing Spotify Web API 504 status (Gateway timeout) condition ...", colorValue=SIColors.Red)
|
|
308
|
+
# response.status_code = 504
|
|
309
|
+
# response.reason = "Gateway Timeout"
|
|
310
|
+
|
|
311
|
+
# check for errors that are temporary in nature; for these errors, we will retry the
|
|
312
|
+
# request for a specified number of tries with a small wait period in between.
|
|
313
|
+
if (response.status_code == 504):
|
|
314
|
+
|
|
315
|
+
# only retry so many times before we give up.
|
|
316
|
+
if (loopTotalDelay >= LOOP_TIMEOUT):
|
|
317
|
+
raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_TIMEOUT % (loopTotalDelay), None, logsi=_logsi)
|
|
320
318
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
raise SpotifyApiError("Token exchange request failed: %s - %s" % (response.status_code, response.reason), None, logsi=_logsi)
|
|
319
|
+
# trace.
|
|
320
|
+
_logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_RESPONSE_STATUS % (response.status_code, response.reason), colorValue=SIColors.Red)
|
|
324
321
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
322
|
+
# wait just a bit between requests.
|
|
323
|
+
_logsi.LogVerbose(SAAppMessages.MSG_SPOTIFY_WEB_API_RETRY_REQUEST_DELAY % (LOOP_DELAY))
|
|
324
|
+
time.sleep(LOOP_DELAY)
|
|
325
|
+
loopTotalDelay = loopTotalDelay + LOOP_DELAY
|
|
328
326
|
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
else:
|
|
328
|
+
|
|
329
|
+
# otherwise, break out of retry loop and process response.
|
|
330
|
+
break
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
# trace.
|
|
333
|
+
if _logsi.IsOn(SILevel.Debug):
|
|
334
|
+
_logsi.LogObject(SILevel.Debug, "%s http response object - type=\"%s\", module=\"%s\"" % (tracePrefix, type(response).__name__, type(response).__module__), response)
|
|
335
|
+
|
|
336
|
+
# trace.
|
|
337
|
+
if _logsi.IsOn(SILevel.Debug):
|
|
338
|
+
_logsi.LogObject(SILevel.Debug, "%s http response [%s-%s] (response)" % (tracePrefix, response.status_code, response.reason), response)
|
|
339
|
+
if (response.headers):
|
|
340
|
+
_logsi.LogCollection(SILevel.Debug, "%s http response [%s-%s] (headers)" % (tracePrefix, response.status_code, response.reason), response.headers.items())
|
|
341
|
+
|
|
342
|
+
# raise exception if we could not get the access token, or if a redirect occured.
|
|
343
|
+
# the redirect usually indicates the sp_dc and sp_key are expired, and it's redirecting to a login page.
|
|
344
|
+
respHdrLocation:str = response.headers.get("Location", None)
|
|
345
|
+
if (response.status_code == 302 and respHdrLocation == "/get_access_token?reason=transport&productType=web_player&_authfailed=1"):
|
|
346
|
+
raise SpotifyApiError("Unsuccessful token request, received status 302 and Location header \"%s\". sp_dc and sp_key could be expired. Please update values in token storage file." % (respHdrLocation), None, logsi=_logsi)
|
|
347
|
+
|
|
348
|
+
# if successful request, then break out of the retry loop.
|
|
349
|
+
if (response.status_code == 200):
|
|
350
|
+
_logsi.LogVerbose("Token exchange request was successful within %f seconds from initial request; processing results" % (loopTotalDelay))
|
|
351
|
+
else:
|
|
352
|
+
raise SpotifyApiError("Token exchange request failed: %s - %s" % (response.status_code, response.reason), None, logsi=_logsi)
|
|
336
353
|
|
|
337
354
|
# load request response.
|
|
338
355
|
data = response.content.decode('utf-8')
|
|
File without changes
|
|
File without changes
|
{spotifywebapiPython-1.0.178.dist-info → spotifywebapiPython-1.0.180.dist-info}/top_level.txt
RENAMED
|
File without changes
|