spotifywebapiPython 1.0.40__py3-none-any.whl → 1.0.41__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.40.dist-info → spotifywebapiPython-1.0.41.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.41.dist-info}/RECORD +24 -22
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +12 -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 +253 -2
- 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/artistinfo.py +263 -0
- spotifywebapipython/models/artistinfotourevent.py +107 -0
- spotifywebapipython/sautils.py +29 -0
- spotifywebapipython/spotifyclient.py +415 -7
- {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.41.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.41.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.40.dist-info → spotifywebapiPython-1.0.41.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# external package imports.
|
|
2
2
|
import base64
|
|
3
|
+
from datetime import datetime
|
|
3
4
|
import json
|
|
4
5
|
from io import BytesIO
|
|
5
6
|
from oauthlib.oauth2 import BackendApplicationClient, WebApplicationClient
|
|
@@ -7,12 +8,13 @@ import time
|
|
|
7
8
|
from typing import Tuple, Callable
|
|
8
9
|
from urllib3 import PoolManager, Timeout, HTTPResponse
|
|
9
10
|
from urllib.parse import urlencode
|
|
11
|
+
from lxml.etree import fromstring, Element
|
|
10
12
|
|
|
11
13
|
# our package imports.
|
|
12
14
|
from .oauthcli import AuthClient
|
|
13
15
|
from .models import *
|
|
14
16
|
from .models import UserProfile as UserProfileCurrentUser
|
|
15
|
-
from .sautils import export, GetUnixTimestampMSFromUtcNow
|
|
17
|
+
from .sautils import export, GetUnixTimestampMSFromUtcNow, _xmlGetInnerText
|
|
16
18
|
from .saappmessages import SAAppMessages
|
|
17
19
|
from .spotifyapierror import SpotifyApiError
|
|
18
20
|
from .spotifyapimessage import SpotifyApiMessage
|
|
@@ -39,7 +41,7 @@ CACHE_SOURCE_CACHED:str = "cached"
|
|
|
39
41
|
CACHE_SOURCE_CURRENT:str = "current"
|
|
40
42
|
|
|
41
43
|
# get smartinspect logger reference; create a new session for this module name.
|
|
42
|
-
from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext
|
|
44
|
+
from smartinspectpython.siauto import SIAuto, SILevel, SISession, SIMethodParmListContext, SISourceId
|
|
43
45
|
import logging
|
|
44
46
|
_logsi:SISession = SIAuto.Si.GetSession(__name__)
|
|
45
47
|
if (_logsi == None):
|
|
@@ -411,10 +413,7 @@ class SpotifyClient:
|
|
|
411
413
|
# no - treat it as utf-8 encoded data.
|
|
412
414
|
responseUTF8 = response.data.decode('utf-8')
|
|
413
415
|
_logsi.LogText(SILevel.Error, "SpotifyClient http response [%s-%s]: '%s' (utf-8)" % (response.status, response.reason, responseUrl), responseUTF8)
|
|
414
|
-
|
|
415
|
-
# at this point we don't know what Spotify Web Api returned, so let's
|
|
416
|
-
# just raise a new exception with the non-JSON response data.
|
|
417
|
-
#raise SpotifyWebApiError(response.status, responseUTF8, msg.MethodName, response.reason, _logsi)
|
|
416
|
+
responseData = responseUTF8
|
|
418
417
|
|
|
419
418
|
except SpotifyWebApiError: raise # pass handled exceptions on thru
|
|
420
419
|
except Exception as ex:
|
|
@@ -668,7 +667,9 @@ class SpotifyClient:
|
|
|
668
667
|
# formulate the request url.
|
|
669
668
|
url:str = None
|
|
670
669
|
uri:str = msg.Uri
|
|
671
|
-
if (uri == self.SpotifyApiTokenUrl)
|
|
670
|
+
if (uri == self.SpotifyApiTokenUrl) \
|
|
671
|
+
or (uri == self.SpotifyApiAuthorizeUrl) \
|
|
672
|
+
or (uri.startswith('https:')):
|
|
672
673
|
url = uri
|
|
673
674
|
else:
|
|
674
675
|
url = f'{self.SpotifyWebApiUrlBase}{uri}'
|
|
@@ -3196,6 +3197,254 @@ class SpotifyClient:
|
|
|
3196
3197
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
3197
3198
|
|
|
3198
3199
|
|
|
3200
|
+
def GetArtistInfo(self,
|
|
3201
|
+
artistId:str,
|
|
3202
|
+
) -> ArtistInfo:
|
|
3203
|
+
"""
|
|
3204
|
+
Get artist about information from the Spotify Artist Biography page for the
|
|
3205
|
+
specified Spotify artist ID.
|
|
3206
|
+
|
|
3207
|
+
Args:
|
|
3208
|
+
artistId (str):
|
|
3209
|
+
The Spotify ID of the artist.
|
|
3210
|
+
Example: `6APm8EjxOHSYM5B4i3vT3q`
|
|
3211
|
+
|
|
3212
|
+
Returns:
|
|
3213
|
+
An `ArtistInfo` object that contains the artist info details.
|
|
3214
|
+
|
|
3215
|
+
Raises:
|
|
3216
|
+
SpotifyWebApiError:
|
|
3217
|
+
If the Spotify Web API request was for a non-authorization service
|
|
3218
|
+
and the response contains error information.
|
|
3219
|
+
SpotifApiError:
|
|
3220
|
+
If the method fails for any other reason.
|
|
3221
|
+
|
|
3222
|
+
<details>
|
|
3223
|
+
<summary>Sample Code</summary>
|
|
3224
|
+
```python
|
|
3225
|
+
.. include:: ../docs/include/samplecode/SpotifyClient/GetArtistInfo.py
|
|
3226
|
+
```
|
|
3227
|
+
</details>
|
|
3228
|
+
"""
|
|
3229
|
+
apiMethodName:str = 'GetArtistInfo'
|
|
3230
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
3231
|
+
result:ArtistInfo = None
|
|
3232
|
+
|
|
3233
|
+
ABOUT_MONTHLY_LISTENERS:str = ' monthly listeners'
|
|
3234
|
+
|
|
3235
|
+
try:
|
|
3236
|
+
|
|
3237
|
+
# trace.
|
|
3238
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
3239
|
+
apiMethodParms.AppendKeyValue("artistId", artistId)
|
|
3240
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Get Spotify catalog information for a single artist", apiMethodParms)
|
|
3241
|
+
|
|
3242
|
+
# execute spotify web api request.
|
|
3243
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, '/artists/{id}'.format(id=artistId))
|
|
3244
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
3245
|
+
self.MakeRequest('GET', msg)
|
|
3246
|
+
|
|
3247
|
+
# process results.
|
|
3248
|
+
artist = Artist(root=msg.ResponseData)
|
|
3249
|
+
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(artist).__name__), artist, excludeNonPublic=True)
|
|
3250
|
+
|
|
3251
|
+
# create base result.
|
|
3252
|
+
result = ArtistInfo(artist.Id, artist.Name, artist.Type, artist.Uri, artist.ImageUrl)
|
|
3253
|
+
|
|
3254
|
+
# does artist have a bio information url? if not then we are done.
|
|
3255
|
+
if (artist.Href is None) or (len(artist.Href.strip()) == 0):
|
|
3256
|
+
_logsi.LogVerbose("No html returned for artist details page: '%s'" % (artist.ExternalUrls.Spotify))
|
|
3257
|
+
return result
|
|
3258
|
+
|
|
3259
|
+
# was a Spotify external url specified for the artist? if not then we are done.
|
|
3260
|
+
if artist.ExternalUrls is None or artist.ExternalUrls.Spotify is None:
|
|
3261
|
+
return artist
|
|
3262
|
+
|
|
3263
|
+
# retrieve artist details page.
|
|
3264
|
+
msg:SpotifyApiMessage = SpotifyApiMessage(apiMethodName, artist.ExternalUrls.Spotify)
|
|
3265
|
+
msg.RequestHeaders[self.AuthToken.HeaderKey] = self.AuthToken.HeaderValue
|
|
3266
|
+
self.MakeRequest('GET', msg)
|
|
3267
|
+
|
|
3268
|
+
# log html response.
|
|
3269
|
+
#_logsi.LogHtml(SILevel.Verbose, "GetArtistInfo response HTML", msg.ResponseData)
|
|
3270
|
+
_logsi.LogSource(SILevel.Verbose, "GetArtistInfo response Text", msg.ResponseData, SISourceId.Html)
|
|
3271
|
+
|
|
3272
|
+
# if no response data then we are done.
|
|
3273
|
+
if not msg.HasResponseData:
|
|
3274
|
+
_logsi.LogVerbose("No html returned for artist details page: '%s'" % (artist.ExternalUrls.Spotify))
|
|
3275
|
+
return result
|
|
3276
|
+
|
|
3277
|
+
# find the starting <html> tag.
|
|
3278
|
+
# ElementTree fromstring method will fail if initial tag is "<!doctype html>".
|
|
3279
|
+
html:str = msg.ResponseData
|
|
3280
|
+
if html is None:
|
|
3281
|
+
return result
|
|
3282
|
+
if not html.startswith('<html'):
|
|
3283
|
+
idx:int = html.find('<html')
|
|
3284
|
+
if idx == -1:
|
|
3285
|
+
_logsi.LogVerbose("<html> tag could not be found for artist details page: '%s'" % (artist.ExternalUrls.Spotify))
|
|
3286
|
+
return result
|
|
3287
|
+
html = html[idx:]
|
|
3288
|
+
_logsi.LogSource(SILevel.Verbose, "GetArtistInfo response Text (starting from '<html')", html, SISourceId.Html)
|
|
3289
|
+
|
|
3290
|
+
# log formatted html response.
|
|
3291
|
+
_logsi.LogXml(SILevel.Verbose, "GetArtistInfo response Text (formatted)", html, prettyPrint=True)
|
|
3292
|
+
|
|
3293
|
+
# load html element tree so we can parse it.
|
|
3294
|
+
doc:Element = fromstring(html)
|
|
3295
|
+
_logsi.LogObject(SILevel.Verbose, "doc Element object", doc)
|
|
3296
|
+
|
|
3297
|
+
attrXPath:str = None
|
|
3298
|
+
elm:Element = None
|
|
3299
|
+
elmChild:Element = None
|
|
3300
|
+
innerText:str = None
|
|
3301
|
+
|
|
3302
|
+
# about information - image (button / bio page):
|
|
3303
|
+
attrXPath = './/button[@type="button"]/descendant-or-self::img'
|
|
3304
|
+
_logsi.LogVerbose("Element search 'image button': '%s'" % attrXPath)
|
|
3305
|
+
attrElements:list[Element] = doc.xpath(attrXPath)
|
|
3306
|
+
for elm in attrElements:
|
|
3307
|
+
_logsi.LogObject(SILevel.Verbose, "Element match: '%s'" % attrXPath, elm)
|
|
3308
|
+
attrValue:str = elm.get('src', None)
|
|
3309
|
+
if attrValue is not None:
|
|
3310
|
+
result.ImageUrl = attrValue
|
|
3311
|
+
break
|
|
3312
|
+
|
|
3313
|
+
# about information - image (entity-id):
|
|
3314
|
+
# use the internal `_ImageUrl` attribute value to check, as the `ImageUrl` property
|
|
3315
|
+
# will return the `ImageUrlDefault` if `_ImageUrl` attribute was not set.
|
|
3316
|
+
# <img data-testid="artist-entity-image" src="https://i.scdn.co/image/ab6761610000517446196125b56397cd4e0d9c4b" />
|
|
3317
|
+
if result._ImageUrl is None:
|
|
3318
|
+
attrXPath = './/attribute::data-testid[contains(., "artist-entity-image")]/../@src'
|
|
3319
|
+
_logsi.LogVerbose("Element search 'image entity': '%s'" % attrXPath)
|
|
3320
|
+
elmChild = doc.xpath(attrXPath)
|
|
3321
|
+
if elmChild is not None:
|
|
3322
|
+
_logsi.LogVerbose("Element match: '%s'" % elmChild[0])
|
|
3323
|
+
result.ImageUrl = elmChild[0]
|
|
3324
|
+
|
|
3325
|
+
# about information - monthly listeners:
|
|
3326
|
+
# <div data-testid="monthly-listeners-label">2,707,252 monthly listeners</div>
|
|
3327
|
+
attrXPath = './/attribute::data-testid[contains(., "monthly-listeners-label")]/../text()'
|
|
3328
|
+
_logsi.LogVerbose("Element search 'monthly listeners': '%s'" % attrXPath)
|
|
3329
|
+
elmChild = doc.xpath(attrXPath)
|
|
3330
|
+
if elmChild is not None:
|
|
3331
|
+
_logsi.LogVerbose("Element match: '%s'" % elmChild[0])
|
|
3332
|
+
attrValue = elmChild[0].replace(ABOUT_MONTHLY_LISTENERS,'')
|
|
3333
|
+
attrValue = attrValue.replace(',','')
|
|
3334
|
+
attrValue = attrValue.strip()
|
|
3335
|
+
if attrValue.isnumeric:
|
|
3336
|
+
result.MonthlyListeners = int(attrValue)
|
|
3337
|
+
|
|
3338
|
+
# about information - bio:
|
|
3339
|
+
# <div data-testid="expandable-description">
|
|
3340
|
+
# <div>
|
|
3341
|
+
# <div>
|
|
3342
|
+
# <span>MercyMe is a contemporary Christian music band ...</span>
|
|
3343
|
+
# </div>
|
|
3344
|
+
# </div>
|
|
3345
|
+
# </div>
|
|
3346
|
+
attrXPath = './/attribute::data-testid[contains(., "expandable-description")]/..'
|
|
3347
|
+
_logsi.LogVerbose("Element search 'bio': '%s'" % attrXPath)
|
|
3348
|
+
attrElements:list[Element] = doc.xpath(attrXPath)
|
|
3349
|
+
for elm in attrElements:
|
|
3350
|
+
innerText = _xmlGetInnerText(elm)
|
|
3351
|
+
_logsi.LogObject(SILevel.Verbose, "Element match: '%s' (innerText=%s)" % (attrXPath, innerText), elm)
|
|
3352
|
+
result.Bio = innerText
|
|
3353
|
+
break
|
|
3354
|
+
|
|
3355
|
+
# about information - on tour dates:
|
|
3356
|
+
# <h2>On tour</h2>
|
|
3357
|
+
# <div><ul><li>
|
|
3358
|
+
# <a draggable="false" class="x" href="/concert/1plBKlFD04tlsD1ky081qL">
|
|
3359
|
+
# <time class="x" dateTime="2024-04-04T19:00-07:00">
|
|
3360
|
+
# <h5 class="x" data-encore-id="type">Apr</h5>
|
|
3361
|
+
# <h1 class="x" data-encore-id="type">5</h1>
|
|
3362
|
+
# </time>
|
|
3363
|
+
# <div class="x">
|
|
3364
|
+
# <h3 class="x" data-encore-id="type">MercyMe with David Leonard and Newsboys</h3>
|
|
3365
|
+
# <p class="x" data-encore-id="type"><time class="cR3tL5CgXmgwfJDDKQ2A">Fri, Apr 5</time>accesso ShoWare Center, Kent</p>
|
|
3366
|
+
# </div>
|
|
3367
|
+
# </a>
|
|
3368
|
+
# </li></ul></div>
|
|
3369
|
+
attrXPath = './/h2[text()="On tour"]/following-sibling::*/descendant-or-self::a'
|
|
3370
|
+
_logsi.LogVerbose("Element search 'On tour': '%s'" % attrXPath)
|
|
3371
|
+
attrElements:list[Element] = doc.xpath(attrXPath)
|
|
3372
|
+
for elm in attrElements:
|
|
3373
|
+
innerText = _xmlGetInnerText(elm)
|
|
3374
|
+
if innerText is not None:
|
|
3375
|
+
if innerText.lower() == 'see all':
|
|
3376
|
+
break
|
|
3377
|
+
_logsi.LogObject(SILevel.Verbose, "Element match: '%s' (innerText=%s)" % (attrXPath, innerText), elm)
|
|
3378
|
+
event:ArtistInfoTourEvent = ArtistInfoTourEvent()
|
|
3379
|
+
elmChild = elm.xpath('.//time/@dateTime')
|
|
3380
|
+
if elmChild is not None:
|
|
3381
|
+
try:
|
|
3382
|
+
attrValue = str(elmChild[0])
|
|
3383
|
+
idx:int = attrValue.rfind('-')
|
|
3384
|
+
if idx > 15: # drop timezone portion of the datetime.
|
|
3385
|
+
event.EventDateTime = datetime.fromisoformat(attrValue[:idx])
|
|
3386
|
+
except Exception as ex:
|
|
3387
|
+
_logsi.LogException("Element value '%s' (%s) could not be parsed to a datetime object" % (elmChild[0], attrValue), ex, logToSystemLogger=False)
|
|
3388
|
+
event.EventDateTime = None
|
|
3389
|
+
elmChild = elm.xpath('.//h3/text()')
|
|
3390
|
+
if elmChild is not None:
|
|
3391
|
+
event.Title = elmChild[0]
|
|
3392
|
+
elmChild = elm.xpath('.//p/text()')
|
|
3393
|
+
if elmChild is not None:
|
|
3394
|
+
event.VenueName = elmChild[0]
|
|
3395
|
+
result.TourEvents.append(event)
|
|
3396
|
+
_logsi.LogObject(SILevel.Verbose, '%s: "%s" (%s)' % (type(event).__name__, event.Title, event.EventDateTime), event, excludeNonPublic=True)
|
|
3397
|
+
|
|
3398
|
+
# about information - links.
|
|
3399
|
+
result.AboutUrlFacebook = self._GetArtistInfoAboutLink(doc,"Facebook")
|
|
3400
|
+
result.AboutUrlInstagram = self._GetArtistInfoAboutLink(doc,"Instagram")
|
|
3401
|
+
result.AboutUrlTwitter = self._GetArtistInfoAboutLink(doc,"Twitter")
|
|
3402
|
+
result.AboutUrlWikipedia = self._GetArtistInfoAboutLink(doc,"Wikipedia")
|
|
3403
|
+
|
|
3404
|
+
# trace.
|
|
3405
|
+
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT_TYPE % (apiMethodName, type(result).__name__), result, excludeNonPublic=True)
|
|
3406
|
+
return result
|
|
3407
|
+
|
|
3408
|
+
except SpotifyWebApiError: raise # pass handled exceptions on thru
|
|
3409
|
+
except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
|
|
3410
|
+
except Exception as ex:
|
|
3411
|
+
|
|
3412
|
+
# format unhandled exception.
|
|
3413
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
3414
|
+
|
|
3415
|
+
finally:
|
|
3416
|
+
|
|
3417
|
+
# trace.
|
|
3418
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
3419
|
+
|
|
3420
|
+
|
|
3421
|
+
def _GetArtistInfoAboutLink(self,
|
|
3422
|
+
doc:Element,
|
|
3423
|
+
linkTitle:str,
|
|
3424
|
+
) -> str:
|
|
3425
|
+
"""
|
|
3426
|
+
Searches artist about page for the specified link title and returns the HREF
|
|
3427
|
+
attribute value if found; otherwise, null is returned.
|
|
3428
|
+
"""
|
|
3429
|
+
result:str = None
|
|
3430
|
+
|
|
3431
|
+
# about information - link.
|
|
3432
|
+
# <a href="https://facebook.com/mercyme">
|
|
3433
|
+
# <div>Facebook</div>
|
|
3434
|
+
# </a>
|
|
3435
|
+
attrXPath:str = './/div[text()="%s"]/parent::a' % linkTitle
|
|
3436
|
+
_logsi.LogVerbose("Element search '%s link': '%s'" % (linkTitle, attrXPath))
|
|
3437
|
+
attrElements:list[Element] = doc.xpath(attrXPath)
|
|
3438
|
+
for elm in attrElements:
|
|
3439
|
+
_logsi.LogObject(SILevel.Verbose, "Element match: '%s'" % attrXPath, elm)
|
|
3440
|
+
attrValue:str = elm.get('href', None)
|
|
3441
|
+
if attrValue is not None:
|
|
3442
|
+
result = attrValue
|
|
3443
|
+
break
|
|
3444
|
+
|
|
3445
|
+
return result
|
|
3446
|
+
|
|
3447
|
+
|
|
3199
3448
|
def GetArtistRelatedArtists(self,
|
|
3200
3449
|
artistId:str,
|
|
3201
3450
|
) -> list[Artist]:
|
|
@@ -8833,6 +9082,97 @@ class SpotifyClient:
|
|
|
8833
9082
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
8834
9083
|
|
|
8835
9084
|
|
|
9085
|
+
def PlayerMediaPlayTrackFavorites(self,
|
|
9086
|
+
deviceId:str=None,
|
|
9087
|
+
shuffle:bool=True,
|
|
9088
|
+
delay:float=0.50
|
|
9089
|
+
) -> None:
|
|
9090
|
+
"""
|
|
9091
|
+
Get a list of the tracks saved in the current Spotify user's 'Your Library'
|
|
9092
|
+
and starts playing them.
|
|
9093
|
+
|
|
9094
|
+
Args:
|
|
9095
|
+
deviceId (str):
|
|
9096
|
+
The id of the device this command is targeting.
|
|
9097
|
+
If not supplied, the user's currently active device is the target.
|
|
9098
|
+
Example: `0d1841b0976bae2a3a310dd74c0f3df354899bc8`
|
|
9099
|
+
shuffle (bool):
|
|
9100
|
+
True to set player shuffle mode to on; otherwise, False for no shuffle.
|
|
9101
|
+
delay (float):
|
|
9102
|
+
Time delay (in seconds) to wait AFTER issuing the command to the player.
|
|
9103
|
+
This delay will give the spotify web api time to process the change before
|
|
9104
|
+
another command is issued.
|
|
9105
|
+
Default is 0.50; value range is 0 - 10.
|
|
9106
|
+
|
|
9107
|
+
Raises:
|
|
9108
|
+
SpotifyWebApiError:
|
|
9109
|
+
If the Spotify Web API request was for a non-authorization service
|
|
9110
|
+
and the response contains error information.
|
|
9111
|
+
SpotifApiError:
|
|
9112
|
+
If the method fails for any other reason.
|
|
9113
|
+
|
|
9114
|
+
This API only works for users who have Spotify Premium.
|
|
9115
|
+
|
|
9116
|
+
This method simply calls the `GetTrackFavorites` method to retrieve the current users
|
|
9117
|
+
favorite tracks (200 max), then calls the `PlayerMediaPlayTracks` method to play them.
|
|
9118
|
+
|
|
9119
|
+
<details>
|
|
9120
|
+
<summary>Sample Code</summary>
|
|
9121
|
+
```python
|
|
9122
|
+
.. include:: ../docs/include/samplecode/SpotifyClient/PlayerMediaPlayTrackFavorites.py
|
|
9123
|
+
```
|
|
9124
|
+
</details>
|
|
9125
|
+
"""
|
|
9126
|
+
apiMethodName:str = 'PlayerMediaPlayTrackFavorites'
|
|
9127
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
9128
|
+
|
|
9129
|
+
try:
|
|
9130
|
+
|
|
9131
|
+
# trace.
|
|
9132
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
9133
|
+
apiMethodParms.AppendKeyValue("deviceId", deviceId)
|
|
9134
|
+
apiMethodParms.AppendKeyValue("shuffle", shuffle)
|
|
9135
|
+
apiMethodParms.AppendKeyValue("delay", delay)
|
|
9136
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Spotify Connect device play track favorites", apiMethodParms)
|
|
9137
|
+
|
|
9138
|
+
# validations.
|
|
9139
|
+
delay = self._ValidateDelay(delay, 0.50, 10)
|
|
9140
|
+
|
|
9141
|
+
# get current users favorite tracks.
|
|
9142
|
+
tracks:TrackPageSaved = self.GetTrackFavorites(limitTotal=200)
|
|
9143
|
+
if (tracks.ItemsCount == 0):
|
|
9144
|
+
_logsi.LogVerbose("Current user has no favorite tracks; nothing to do")
|
|
9145
|
+
return
|
|
9146
|
+
|
|
9147
|
+
# build a list of all item uri's.
|
|
9148
|
+
arrUris:list[str] = []
|
|
9149
|
+
trackSaved:TrackSaved
|
|
9150
|
+
for trackSaved in tracks.Items:
|
|
9151
|
+
arrUris.append(trackSaved.Track.Uri)
|
|
9152
|
+
|
|
9153
|
+
# play the tracks.
|
|
9154
|
+
self.PlayerMediaPlayTracks(arrUris, deviceId=deviceId, delay=delay)
|
|
9155
|
+
|
|
9156
|
+
# set desired shuffle mode.
|
|
9157
|
+
self.PlayerSetShuffleMode(shuffle, deviceId)
|
|
9158
|
+
|
|
9159
|
+
# process results.
|
|
9160
|
+
# no results to process - this is pass or fail.
|
|
9161
|
+
return
|
|
9162
|
+
|
|
9163
|
+
except SpotifyWebApiError: raise # pass handled exceptions on thru
|
|
9164
|
+
except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
|
|
9165
|
+
except Exception as ex:
|
|
9166
|
+
|
|
9167
|
+
# format unhandled exception.
|
|
9168
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
9169
|
+
|
|
9170
|
+
finally:
|
|
9171
|
+
|
|
9172
|
+
# trace.
|
|
9173
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
9174
|
+
|
|
9175
|
+
|
|
8836
9176
|
def PlayerMediaPlayTracks(self,
|
|
8837
9177
|
uris:list[str],
|
|
8838
9178
|
positionMS:int=0,
|
|
@@ -10024,6 +10364,72 @@ class SpotifyClient:
|
|
|
10024
10364
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
10025
10365
|
|
|
10026
10366
|
|
|
10367
|
+
def RemovePlaylist(self,
|
|
10368
|
+
playlistId:str,
|
|
10369
|
+
) -> None:
|
|
10370
|
+
"""
|
|
10371
|
+
Remove a user's playlist (calls the `UnfollowPlaylist` method).
|
|
10372
|
+
|
|
10373
|
+
This method requires the `playlist-modify-public` and `playlist-modify-private` scope.
|
|
10374
|
+
|
|
10375
|
+
Args:
|
|
10376
|
+
|
|
10377
|
+
playlistId (str):
|
|
10378
|
+
The Spotify ID of the playlist.
|
|
10379
|
+
Example: `5AC9ZXA7nJ7oGWO911FuDG`
|
|
10380
|
+
|
|
10381
|
+
Raises:
|
|
10382
|
+
SpotifyWebApiError:
|
|
10383
|
+
If the Spotify Web API request was for a non-authorization service
|
|
10384
|
+
and the response contains error information.
|
|
10385
|
+
SpotifApiError:
|
|
10386
|
+
If the method fails for any other reason.
|
|
10387
|
+
|
|
10388
|
+
There is no Spotify Web API endpoint for deleting a playlist. The notion of deleting a playlist is
|
|
10389
|
+
not relevant within the Spotify playlist system. Even if you are the playlist owner and you choose
|
|
10390
|
+
to manually remove it from your own list of playlists, you are simply unfollowing it. Although this
|
|
10391
|
+
behavior may sound strange, it means that other users who are already following the playlist can keep
|
|
10392
|
+
enjoying it.
|
|
10393
|
+
|
|
10394
|
+
[Manually restoring a deleted playlist](https://www.spotify.com/us/account/recover-playlists/) through
|
|
10395
|
+
the Spotify Accounts Service is the same thing as following one of your own playlists that you have
|
|
10396
|
+
previously unfollowed.
|
|
10397
|
+
|
|
10398
|
+
<details>
|
|
10399
|
+
<summary>Sample Code</summary>
|
|
10400
|
+
```python
|
|
10401
|
+
.. include:: ../docs/include/samplecode/SpotifyClient/RemovePlaylist.py
|
|
10402
|
+
```
|
|
10403
|
+
</details>
|
|
10404
|
+
"""
|
|
10405
|
+
apiMethodName:str = 'RemovePlaylist'
|
|
10406
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
10407
|
+
result:str = None
|
|
10408
|
+
|
|
10409
|
+
try:
|
|
10410
|
+
|
|
10411
|
+
# trace.
|
|
10412
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
10413
|
+
apiMethodParms.AppendKeyValue("playlistId", playlistId)
|
|
10414
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Remove (by unfollowing) a user's playlist", apiMethodParms)
|
|
10415
|
+
|
|
10416
|
+
# unfollow the playlist for the specified user.
|
|
10417
|
+
return self.UnfollowPlaylist(playlistId)
|
|
10418
|
+
|
|
10419
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
10420
|
+
except SpotifyWebApiError: raise # pass handled exceptions on thru
|
|
10421
|
+
except SpotifyWebApiAuthenticationError: raise # pass handled exceptions on thru
|
|
10422
|
+
except Exception as ex:
|
|
10423
|
+
|
|
10424
|
+
# format unhandled exception.
|
|
10425
|
+
raise SpotifyApiError(SAAppMessages.UNHANDLED_EXCEPTION.format(apiMethodName, str(ex)), ex, logsi=_logsi)
|
|
10426
|
+
|
|
10427
|
+
finally:
|
|
10428
|
+
|
|
10429
|
+
# trace.
|
|
10430
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
10431
|
+
|
|
10432
|
+
|
|
10027
10433
|
def RemovePlaylistItems(self,
|
|
10028
10434
|
playlistId:str,
|
|
10029
10435
|
uris:str=None,
|
|
@@ -10049,6 +10455,7 @@ class SpotifyClient:
|
|
|
10049
10455
|
The API will validate that the specified items exist and in the specified positions and
|
|
10050
10456
|
make the changes, even if more recent changes have been made to the playlist.
|
|
10051
10457
|
If null, the current playlist is updated.
|
|
10458
|
+
Example: `MTk3LGEzMjUwZGYwODljNmI5ZjAxZTRjZThiOGI4NzZhM2U5M2IxOWUyMDQ`
|
|
10052
10459
|
Default is null.
|
|
10053
10460
|
|
|
10054
10461
|
Returns:
|
|
@@ -10336,6 +10743,7 @@ class SpotifyClient:
|
|
|
10336
10743
|
snapshotId (str):
|
|
10337
10744
|
The playlist's snapshot ID against which you want to make the changes.
|
|
10338
10745
|
If null, the current playlist is updated.
|
|
10746
|
+
Example: `MTk3LGEzMjUwZGYwODljNmI5ZjAxZTRjZThiOGI4NzZhM2U5M2IxOWUyMDQ`
|
|
10339
10747
|
Default is null.
|
|
10340
10748
|
|
|
10341
10749
|
Returns:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|