spotifywebapiPython 1.0.290__py3-none-any.whl → 1.0.292__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.
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +8 -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 +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html +2 -2
- spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.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/spotifyconnect/spotifyconnectdirectorytask.py +84 -23
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +165 -9
- spotifywebapipython/zeroconfapi/zeroconfgetinfo.py +26 -0
- {spotifywebapipython-1.0.290.dist-info → spotifywebapipython-1.0.292.dist-info}/METADATA +1 -1
- {spotifywebapipython-1.0.290.dist-info → spotifywebapipython-1.0.292.dist-info}/RECORD +25 -25
- {spotifywebapipython-1.0.290.dist-info → spotifywebapipython-1.0.292.dist-info}/LICENSE +0 -0
- {spotifywebapipython-1.0.290.dist-info → spotifywebapipython-1.0.292.dist-info}/WHEEL +0 -0
- {spotifywebapipython-1.0.290.dist-info → spotifywebapipython-1.0.292.dist-info}/top_level.txt +0 -0
|
@@ -2312,6 +2312,8 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
2312
2312
|
# syncronize access via lock, as we are accessing the collection.
|
|
2313
2313
|
with self._SpotifyConnectDevices_RLock:
|
|
2314
2314
|
|
|
2315
|
+
zconn:ZeroconfConnect = None
|
|
2316
|
+
|
|
2315
2317
|
try:
|
|
2316
2318
|
|
|
2317
2319
|
# trace.
|
|
@@ -2374,14 +2376,12 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
2374
2376
|
# retrieve initial spotify connect device information (by ip address).
|
|
2375
2377
|
scDevice.DeviceInfo = zconn.GetInformation()
|
|
2376
2378
|
scDevice.Id = scDevice.DeviceInfo.DeviceId
|
|
2377
|
-
scDevice.Name = scDevice.DeviceInfo.
|
|
2379
|
+
scDevice.Name = scDevice.DeviceInfo.RemoteNameOrAlias
|
|
2378
2380
|
|
|
2379
|
-
# if
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
_logsi.LogVerbose("Spotify Connect Zeroconf GetInformation alias name will be utilized for Zeroconf Discovery Result: \"%s\" (%s)" % (zeroconfDiscoveryResult.DeviceName, zeroconfDiscoveryResult.Name))
|
|
2384
|
-
scDevice.Name = scDevice.DeviceInfo.Aliases[0].Name
|
|
2381
|
+
# if service discovery version not set, then use the getInfo version.
|
|
2382
|
+
if (zeroconfDiscoveryResult.SpotifyConnectVersion is None):
|
|
2383
|
+
if (scDevice.DeviceInfo.Version is not None):
|
|
2384
|
+
zeroconfDiscoveryResult.SpotifyConnectVersion = scDevice.DeviceInfo.Version
|
|
2385
2385
|
|
|
2386
2386
|
except Exception as ex:
|
|
2387
2387
|
|
|
@@ -2408,14 +2408,12 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
2408
2408
|
# retrieve initial spotify connect device information (by dns alias).
|
|
2409
2409
|
scDevice.DeviceInfo = zconn.GetInformation()
|
|
2410
2410
|
scDevice.Id = scDevice.DeviceInfo.DeviceId
|
|
2411
|
-
scDevice.Name = scDevice.DeviceInfo.
|
|
2411
|
+
scDevice.Name = scDevice.DeviceInfo.RemoteNameOrAlias
|
|
2412
2412
|
|
|
2413
|
-
# if
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
_logsi.LogVerbose("Spotify Connect Zeroconf GetInformation alias name will be utilized for Zeroconf Discovery Result: \"%s\" (%s)" % (zeroconfDiscoveryResult.DeviceName, zeroconfDiscoveryResult.Name))
|
|
2418
|
-
scDevice.Name = scDevice.DeviceInfo.Aliases[0].Name
|
|
2413
|
+
# if service discovery version not set, then use the getInfo version.
|
|
2414
|
+
if (zeroconfDiscoveryResult.SpotifyConnectVersion is None):
|
|
2415
|
+
if (scDevice.DeviceInfo.Version is not None):
|
|
2416
|
+
zeroconfDiscoveryResult.SpotifyConnectVersion = scDevice.DeviceInfo.Version
|
|
2419
2417
|
|
|
2420
2418
|
# update HostIpAddress in discovery result so it knows to use the dns alias
|
|
2421
2419
|
# instead of the ip address.
|
|
@@ -2516,25 +2514,84 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
2516
2514
|
scDevice = self._SpotifyConnectDevices.Items[idx]
|
|
2517
2515
|
|
|
2518
2516
|
# were any changes made to the zeroconf discovery results?
|
|
2517
|
+
# this can happen when the Spotify Connect zeroconf server on the device has
|
|
2518
|
+
# been restarted (e.g. "resetUsers" called, etc). if so, then we need to update
|
|
2519
|
+
# the DiscoveryResult since it could possibly point to a different host address
|
|
2520
|
+
# (or port number) for the zeroconf endpoints (e.g. getInfo, addUser, etc). we will
|
|
2521
|
+
# also call "getInfo" to update any Spotify Connect changes (e.g. RemoteName, Id, etc).
|
|
2519
2522
|
if (not scDevice.DiscoveryResult.Equals(zeroconfDiscoveryResult)):
|
|
2520
2523
|
|
|
2521
2524
|
# trace.
|
|
2525
|
+
_logsi.LogVerbose("Spotify Connect zeroconf DiscoveryResult change detected for device: %s" % (scDevice.Title))
|
|
2522
2526
|
_logsi.LogObject(SILevel.Debug, "SpotifyConnectDevice info: \"%s\" (OLD DeviceInfo / getInfo)" % (scDevice.Title), scDevice.DeviceInfo, excludeNonPublic=True)
|
|
2523
2527
|
_logsi.LogObject(SILevel.Debug, "SpotifyConnectDevice info: \"%s\" (OLD DiscoveryResult) [%s]" % (scDevice.Title, scDevice.DiscoveryResult.HostIpTitle), scDevice.DiscoveryResult, excludeNonPublic=True)
|
|
2524
2528
|
|
|
2525
|
-
#
|
|
2529
|
+
# preserve various old property values for comparison later.
|
|
2530
|
+
oldDeviceId:str = scDevice.DeviceInfo.DeviceId
|
|
2531
|
+
oldRemoteName:str = scDevice.DeviceInfo.RemoteNameOrAlias
|
|
2532
|
+
oldHostIpPort:int = scDevice.DiscoveryResult.HostIpPort
|
|
2533
|
+
newRemoteName:str = None
|
|
2534
|
+
newDeviceId:str = None
|
|
2535
|
+
|
|
2536
|
+
try:
|
|
2537
|
+
|
|
2538
|
+
# trace.
|
|
2539
|
+
_logsi.LogVerbose("Retrieving Spotify Connect device information: %s [DiscoveryResult change]" % (zeroconfDiscoveryResult.Id))
|
|
2540
|
+
|
|
2541
|
+
# create connection object to retrieve spotify connect device information (via direct ip address).
|
|
2542
|
+
zconn:ZeroconfConnect = ZeroconfConnect(
|
|
2543
|
+
zeroconfDiscoveryResult.HostIpAddress,
|
|
2544
|
+
zeroconfDiscoveryResult.HostIpPort,
|
|
2545
|
+
zeroconfDiscoveryResult.SpotifyConnectCPath,
|
|
2546
|
+
zeroconfDiscoveryResult.SpotifyConnectVersion,
|
|
2547
|
+
useSSL=False,
|
|
2548
|
+
tokenStorageDir=self.SpotifyClientInstance.TokenStorageDir,
|
|
2549
|
+
tokenStorageFile=self.SpotifyClientInstance.TokenStorageFile
|
|
2550
|
+
)
|
|
2551
|
+
|
|
2552
|
+
# retrieve spotify connect device information (by ip address).
|
|
2553
|
+
getInfo:ZeroconfGetInfo = zconn.GetInformation()
|
|
2554
|
+
|
|
2555
|
+
# if service discovery version not set, then use the getInfo version.
|
|
2556
|
+
if (zeroconfDiscoveryResult.SpotifyConnectVersion is None):
|
|
2557
|
+
if (getInfo.Version is not None):
|
|
2558
|
+
zeroconfDiscoveryResult.SpotifyConnectVersion = getInfo.Version
|
|
2559
|
+
|
|
2560
|
+
# update base device info from getInfo details.
|
|
2561
|
+
scDevice.DeviceInfo = getInfo
|
|
2562
|
+
|
|
2563
|
+
# set device name and id values from getInfo response values.
|
|
2564
|
+
newRemoteName = getInfo.RemoteNameOrAlias
|
|
2565
|
+
newDeviceId = getInfo.DeviceId
|
|
2566
|
+
|
|
2567
|
+
except Exception as ex:
|
|
2568
|
+
|
|
2569
|
+
# trace.
|
|
2570
|
+
_logsi.LogVerbose("Could not retrieve Spotify Connect device information; defaulting DeviceID to GetSpotifyDeviceIDFromName \"%s\"" % (zeroconfDiscoveryResult.DeviceName))
|
|
2571
|
+
|
|
2572
|
+
# at this point we don't know for sure what the device name is since getInfo failed.
|
|
2573
|
+
# set device name and id values from serviceinfo discovery values.
|
|
2574
|
+
newRemoteName:str = zeroconfDiscoveryResult.DeviceName
|
|
2575
|
+
newDeviceId:str = self.GetSpotifyDeviceIDFromName(newRemoteName)
|
|
2576
|
+
|
|
2577
|
+
# update zeroconf discovery result properties in the base device object.
|
|
2526
2578
|
scDevice.DiscoveryResult = zeroconfDiscoveryResult
|
|
2527
2579
|
|
|
2528
|
-
# did the device name change?
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2580
|
+
# did the device remote name change? if so, then update base device properties.
|
|
2581
|
+
if (oldRemoteName != newRemoteName):
|
|
2582
|
+
_logsi.LogVerbose("Spotify Connect Zeroconf device RemoteNameAlias changed from \"%s\" to \"%s\"" % (oldRemoteName, newRemoteName))
|
|
2583
|
+
scDevice.Name = newRemoteName
|
|
2584
|
+
scDevice.DeviceInfo.RemoteName = newRemoteName
|
|
2585
|
+
|
|
2586
|
+
# did the device id change?
|
|
2587
|
+
if (oldDeviceId != newDeviceId):
|
|
2588
|
+
_logsi.LogVerbose("Spotify Connect Zeroconf device Id changed from \"%s\" to \"%s\"" % (oldDeviceId, newDeviceId))
|
|
2535
2589
|
scDevice.Id = newDeviceId
|
|
2536
2590
|
scDevice.DeviceInfo.DeviceId = newDeviceId
|
|
2537
|
-
|
|
2591
|
+
|
|
2592
|
+
# did the device zeroconf server listener port change?
|
|
2593
|
+
if (oldHostIpPort != zeroconfDiscoveryResult.HostIpPort):
|
|
2594
|
+
_logsi.LogVerbose("Spotify Connect Zeroconf device HostIpPort changed from \"%s\" to \"%s\"" % (oldHostIpPort, zeroconfDiscoveryResult.HostIpPort))
|
|
2538
2595
|
|
|
2539
2596
|
# update existing Spotify Connect Device in devices collection.
|
|
2540
2597
|
self._SpotifyConnectDevices.Items[idx] = scDevice
|
|
@@ -2566,6 +2623,10 @@ class SpotifyConnectDirectoryTask(threading.Thread):
|
|
|
2566
2623
|
|
|
2567
2624
|
finally:
|
|
2568
2625
|
|
|
2626
|
+
# free resources.
|
|
2627
|
+
if (zconn is not None):
|
|
2628
|
+
zconn = None
|
|
2629
|
+
|
|
2569
2630
|
# trace.
|
|
2570
2631
|
_logsi.LeaveMethod(SILevel.Debug)
|
|
2571
2632
|
|
|
@@ -586,7 +586,7 @@ class ZeroconfConnect:
|
|
|
586
586
|
_logsi.LogMethodParmList(SILevel.Verbose, "Issuing Spotify Connect Zeroconf addUser request (ip=%s:%s)" % (self._HostIpAddress, self._HostIpPort), apiMethodParms)
|
|
587
587
|
|
|
588
588
|
# set request endpoint.
|
|
589
|
-
endpoint:str = self.GetEndpoint('addUser')
|
|
589
|
+
endpoint:str = self.GetEndpoint('addUser', info.Version)
|
|
590
590
|
|
|
591
591
|
# set request headers.
|
|
592
592
|
reqHeaders:dict = {
|
|
@@ -677,6 +677,35 @@ class ZeroconfConnect:
|
|
|
677
677
|
# get authorization_code access token.
|
|
678
678
|
blob:str = self._GetSpotifyConnectAuthorizationCodeToken(info, loginId=loginId)
|
|
679
679
|
|
|
680
|
+
# special processing for tokenType "accesstoken":
|
|
681
|
+
elif info.TokenType == 'accesstoken':
|
|
682
|
+
|
|
683
|
+
# trace.
|
|
684
|
+
_logsi.LogVerbose("Spotify Connect token type is '%s' - using Spotify Desktop Client OAuth2 token to connect: '%s' (ip=%s)" % (info.TokenType, info.RemoteName, self._HostIpAddress))
|
|
685
|
+
|
|
686
|
+
# validations.
|
|
687
|
+
if (loginId is None) or (not isinstance(loginId,str)):
|
|
688
|
+
raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectLoginId"), logsi=_logsi)
|
|
689
|
+
if (username is None) or (not isinstance(username,str)):
|
|
690
|
+
raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectUsername"), logsi=_logsi)
|
|
691
|
+
if (password is None) or (not isinstance(password,str)):
|
|
692
|
+
raise SpotifyApiError(SAAppMessages.MSG_SPOTIFY_ACTIVATE_CREDENTIAL_REQUIRED % ("SpotifyConnectPassword"), logsi=_logsi)
|
|
693
|
+
|
|
694
|
+
# create credentials and builder objects.
|
|
695
|
+
credentials:Credentials = Credentials(username, password, AuthenticationTypes.SPOTIFY_TOKEN)
|
|
696
|
+
builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
|
|
697
|
+
|
|
698
|
+
# set defaults used by token type "accesstoken".
|
|
699
|
+
# - use clientKey from getInfo response.
|
|
700
|
+
# - exclude origin device information from the addUser request.
|
|
701
|
+
# - use tokenType from getInfo response.
|
|
702
|
+
clientKey = info.ClientId or ''
|
|
703
|
+
includeOriginDeviceInfo = False
|
|
704
|
+
tokenType = info.TokenType
|
|
705
|
+
|
|
706
|
+
# get access token.
|
|
707
|
+
blob:str = self._GetSpotifyConnectAccessToken(info, loginId=loginId)
|
|
708
|
+
|
|
680
709
|
else:
|
|
681
710
|
|
|
682
711
|
# trace.
|
|
@@ -713,7 +742,7 @@ class ZeroconfConnect:
|
|
|
713
742
|
# set request parameters.
|
|
714
743
|
reqData={
|
|
715
744
|
"action": "addUser",
|
|
716
|
-
"version":
|
|
745
|
+
"version": info.Version or self.Version,
|
|
717
746
|
"tokenType": tokenType,
|
|
718
747
|
"clientKey": clientKey,
|
|
719
748
|
"loginId": loginId or '', # canonical login id (e.g. "31l77fd87g8h9j00k89f07jf87ge")
|
|
@@ -740,7 +769,7 @@ class ZeroconfConnect:
|
|
|
740
769
|
|
|
741
770
|
# trace.
|
|
742
771
|
_logsi.LogDictionary(SILevel.Debug, "ZeroconfConnect http request: '%s' (headers)" % (endpoint), reqHeaders)
|
|
743
|
-
_logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (
|
|
772
|
+
_logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (json dict)" % (endpoint), reqData, prettyPrint=True)
|
|
744
773
|
|
|
745
774
|
# The 10 second connect timeout prevents the application from hanging when
|
|
746
775
|
# attempting to connect to an unreachable IP address.
|
|
@@ -771,6 +800,129 @@ class ZeroconfConnect:
|
|
|
771
800
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
772
801
|
|
|
773
802
|
|
|
803
|
+
def _GetSpotifyConnectAccessToken(
|
|
804
|
+
self,
|
|
805
|
+
info:ZeroconfGetInfo,
|
|
806
|
+
loginId:str,
|
|
807
|
+
) -> str:
|
|
808
|
+
"""
|
|
809
|
+
Get the Spotify OAuth2 access token value.
|
|
810
|
+
The resulting token will be used as the `blob` argument for the Spotify Connect Zeroconf `addUser` request.
|
|
811
|
+
"""
|
|
812
|
+
apiMethodName:str = '_GetSpotifyConnectAccessToken'
|
|
813
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
814
|
+
tracePrefix:str = 'SpotifyConnectAccessToken exchange'
|
|
815
|
+
|
|
816
|
+
# the access token is loaded from the token storage file (e.g. `spotifyplus_tokens.json`, etc).
|
|
817
|
+
# if not found, then an exception will be raised to indicate there is no access token stored.
|
|
818
|
+
# if found, the token is then checked to see if it has expired, and is automatically refreshed if so.
|
|
819
|
+
# the "access_token" value is then returned, and passed on the Spotify Connect Zeroconf "adduser"
|
|
820
|
+
# call as the "blob" parameter (e.g. `AuthenticationTypes.SPOTIFY_TOKEN`).
|
|
821
|
+
|
|
822
|
+
try:
|
|
823
|
+
|
|
824
|
+
# trace.
|
|
825
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
826
|
+
apiMethodParms.AppendKeyValue("loginId", loginId)
|
|
827
|
+
apiMethodParms.AppendKeyValue("info.DeviceId", info.DeviceId)
|
|
828
|
+
apiMethodParms.AppendKeyValue("info.RemoteName", info.RemoteName)
|
|
829
|
+
apiMethodParms.AppendKeyValue("info.BrandDisplayName", info.BrandDisplayName)
|
|
830
|
+
apiMethodParms.AppendKeyValue("info.ModelDisplayName", info.ModelDisplayName)
|
|
831
|
+
apiMethodParms.AppendKeyValue("self.TokenAuthInBrowser", self.TokenAuthInBrowser)
|
|
832
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Preparing to generate a new access token for Spotify Connect device: '%s' (%s)" % (info.RemoteName, info.DeviceId), apiMethodParms)
|
|
833
|
+
|
|
834
|
+
# set variables for spotify access token generation.
|
|
835
|
+
# the redirect uri variables were obtained via Fiddler trace using Spotify Desktop App to initiate Spotify Connect to device.
|
|
836
|
+
redirectUriHost:str = '127.0.0.1'
|
|
837
|
+
redirectUriPort:str = 4381
|
|
838
|
+
redirectUriPath:str = '/login'
|
|
839
|
+
authorizationType:str = 'Authorization Code PKCE'
|
|
840
|
+
|
|
841
|
+
# Spotify Desktop App scopes requested for Spotify Connect (streaming only as of 2024/08/13)
|
|
842
|
+
SPOTIFY_SCOPES:list = \
|
|
843
|
+
[
|
|
844
|
+
'streaming',
|
|
845
|
+
]
|
|
846
|
+
|
|
847
|
+
# create oauth provider for spotify authentication code with pkce.
|
|
848
|
+
_logsi.LogVerbose('creating OAuth2 provider for Spotify Authentication Code with PKCE')
|
|
849
|
+
authClient:AuthClient = AuthClient(
|
|
850
|
+
authorizationType=authorizationType,
|
|
851
|
+
authorizationUrl=SPOTIFY_API_AUTHORIZE_URL,
|
|
852
|
+
tokenUrl=SPOTIFY_API_TOKEN_URL,
|
|
853
|
+
scope=SPOTIFY_SCOPES,
|
|
854
|
+
clientId=SPOTIFY_DESKTOP_APP_CLIENT_ID,
|
|
855
|
+
tokenStorageDir=self.TokenStorageDir,
|
|
856
|
+
tokenStorageFile=self.TokenStorageFile,
|
|
857
|
+
tokenProviderId='SpotifyWebApiAuthCodePkce',
|
|
858
|
+
tokenProfileId=loginId,
|
|
859
|
+
)
|
|
860
|
+
|
|
861
|
+
# raise an exception if the authorization token is not present, or the scope has changed.
|
|
862
|
+
# the user must create the authorization token outside of this process, as the Spotify
|
|
863
|
+
# authorization token is driven by responding to a web request for access using OAuth.
|
|
864
|
+
# as this process is running on a server, there is no way for the user to respond to the
|
|
865
|
+
# request (e.g. via browser nor command-line).
|
|
866
|
+
isAuthorized = authClient.IsAuthorized
|
|
867
|
+
_logsi.LogVerbose('Checking OAuth2 authorization status: IsAuthorized=%s' % isAuthorized)
|
|
868
|
+
|
|
869
|
+
if (isAuthorized == False):
|
|
870
|
+
|
|
871
|
+
# can the user interact with a local default browser to authorize the request?
|
|
872
|
+
if self.TokenAuthInBrowser:
|
|
873
|
+
|
|
874
|
+
# at this point, we need a new authorization token.
|
|
875
|
+
# the user has up to 2 minutes to respond to the request by copying / pasting the
|
|
876
|
+
# message / auth approval url from the system log into a browser window that is
|
|
877
|
+
# running on the local machine.
|
|
878
|
+
_logsi.LogVerbose('Preparing to retrieve a new OAuth2 authorization access token')
|
|
879
|
+
authClient.AuthorizeWithServer(
|
|
880
|
+
host=redirectUriHost,
|
|
881
|
+
port=redirectUriPort,
|
|
882
|
+
redirect_uri_path=redirectUriPath,
|
|
883
|
+
open_browser=True,
|
|
884
|
+
timeout_seconds=120
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
else:
|
|
888
|
+
|
|
889
|
+
# user cannot approve the request - raise an exception.
|
|
890
|
+
raise SpotifyZeroconfApiError(401, 'Spotify Desktop Client Application access token was not authorized', apiMethodName, 'Token Not Authorized', _logsi)
|
|
891
|
+
|
|
892
|
+
else:
|
|
893
|
+
|
|
894
|
+
_logsi.LogVerbose('OAuth2 authorization token has previously been authorized')
|
|
895
|
+
|
|
896
|
+
# process results.
|
|
897
|
+
oauth2token:dict = authClient.Session.token
|
|
898
|
+
authToken = SpotifyAuthToken(authClient.AuthorizationType, authClient.TokenProfileId, root=oauth2token)
|
|
899
|
+
|
|
900
|
+
# does token need to be refreshed?
|
|
901
|
+
if authToken.IsExpired:
|
|
902
|
+
|
|
903
|
+
# refresh the token.
|
|
904
|
+
# this will also store the refreshed token to disk to be used later if required.
|
|
905
|
+
_logsi.LogVerbose('OAuth2 authorization token has expired, or is about to; token will be refreshed')
|
|
906
|
+
oauth2token:dict = authClient.RefreshToken()
|
|
907
|
+
authToken = SpotifyAuthToken(authClient.AuthorizationType, authClient.TokenProfileId, root=oauth2token)
|
|
908
|
+
_logsi.LogObject(SILevel.Verbose, TRACE_METHOD_RESULT % apiMethodName, authToken, excludeNonPublic=True)
|
|
909
|
+
|
|
910
|
+
else:
|
|
911
|
+
|
|
912
|
+
_logsi.LogVerbose('OAuth2 authorization token has not expired')
|
|
913
|
+
|
|
914
|
+
# get access token value.
|
|
915
|
+
result = oauth2token.get("access_token", None)
|
|
916
|
+
|
|
917
|
+
# return access token to caller.
|
|
918
|
+
return result
|
|
919
|
+
|
|
920
|
+
finally:
|
|
921
|
+
|
|
922
|
+
# trace.
|
|
923
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
924
|
+
|
|
925
|
+
|
|
774
926
|
def _GetSpotifyConnectAuthorizationCodeToken(
|
|
775
927
|
self,
|
|
776
928
|
info:ZeroconfGetInfo,
|
|
@@ -803,7 +955,7 @@ class ZeroconfConnect:
|
|
|
803
955
|
_logsi.LogMethodParmList(SILevel.Verbose, "Preparing to generate a new authorization_code access token for Spotify Connect device: '%s' (%s)" % (info.RemoteName, info.DeviceId), apiMethodParms)
|
|
804
956
|
|
|
805
957
|
# set variables for spotify authorization_code token generation.
|
|
806
|
-
# the
|
|
958
|
+
# the redirect uri variables were obtained via Fiddler trace using Spotify Desktop App to initiate Spotify Connect to device.
|
|
807
959
|
redirectUriHost:str = '127.0.0.1'
|
|
808
960
|
redirectUriPort:str = 4381
|
|
809
961
|
redirectUriPath:str = '/login'
|
|
@@ -918,7 +1070,7 @@ class ZeroconfConnect:
|
|
|
918
1070
|
# trace.
|
|
919
1071
|
_logsi.LogVerbose('Exchanging OAuth2 authorization access token for an authorization_code access token')
|
|
920
1072
|
_logsi.LogDictionary(SILevel.Debug, "%s http request: '%s' (headers)" % (tracePrefix, SPOTIFY_API_TOKEN_URL), tokHeaders)
|
|
921
|
-
_logsi.LogDictionary(SILevel.Verbose, "%s http request: '%s' (
|
|
1073
|
+
_logsi.LogDictionary(SILevel.Verbose, "%s http request: '%s' (json dict)" % (tracePrefix, SPOTIFY_API_TOKEN_URL), tokData, prettyPrint=True)
|
|
922
1074
|
|
|
923
1075
|
# execute spotify access token request.
|
|
924
1076
|
response = requests.post(
|
|
@@ -1109,7 +1261,7 @@ class ZeroconfConnect:
|
|
|
1109
1261
|
"action": "resetUsers",
|
|
1110
1262
|
"version": self.Version,
|
|
1111
1263
|
}
|
|
1112
|
-
_logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (
|
|
1264
|
+
_logsi.LogDictionary(SILevel.Verbose, "ZeroconfConnect http request: '%s' (json dict)" % (endpoint), reqData, prettyPrint=True)
|
|
1113
1265
|
|
|
1114
1266
|
# execute spotify zeroconf api request.
|
|
1115
1267
|
response = requests.post(
|
|
@@ -1202,7 +1354,8 @@ class ZeroconfConnect:
|
|
|
1202
1354
|
|
|
1203
1355
|
def GetEndpoint(
|
|
1204
1356
|
self,
|
|
1205
|
-
action:str
|
|
1357
|
+
action:str,
|
|
1358
|
+
version:str|None=None,
|
|
1206
1359
|
) -> str:
|
|
1207
1360
|
"""
|
|
1208
1361
|
Gets a Spotify Zeroconf API endpoint uri.
|
|
@@ -1210,6 +1363,9 @@ class ZeroconfConnect:
|
|
|
1210
1363
|
Args:
|
|
1211
1364
|
action (str):
|
|
1212
1365
|
Action parameter value (e.g. "getInfo", "addUser", "resetUsers", etc).
|
|
1366
|
+
version (str):
|
|
1367
|
+
Version parameter value (e.g. "2.7.1", "2.9.0", etc).
|
|
1368
|
+
For some devices, this is important and must match what is returned on the getInfo request!
|
|
1213
1369
|
|
|
1214
1370
|
Returns:
|
|
1215
1371
|
A string containing the endpoint.
|
|
@@ -1217,7 +1373,7 @@ class ZeroconfConnect:
|
|
|
1217
1373
|
return "{uri}?action={action}&version={version}".format(
|
|
1218
1374
|
uri=self._Uri,
|
|
1219
1375
|
action=action,
|
|
1220
|
-
version=self.Version,
|
|
1376
|
+
version=version or self.Version,
|
|
1221
1377
|
)
|
|
1222
1378
|
|
|
1223
1379
|
|
|
@@ -1341,7 +1497,7 @@ class ZeroconfConnect:
|
|
|
1341
1497
|
result = ZeroconfGetInfo(root=responseData)
|
|
1342
1498
|
|
|
1343
1499
|
# trace.
|
|
1344
|
-
_logsi.LogObject(SILevel.Verbose, '%s result (%s) - "%s" (%s)' % (apiMethodName, type(result).__name__, result.
|
|
1500
|
+
_logsi.LogObject(SILevel.Verbose, '%s result (%s) - "%s" (%s)' % (apiMethodName, type(result).__name__, result.RemoteNameOrAlias, result.DeviceId), result, excludeNonPublic=True)
|
|
1345
1501
|
|
|
1346
1502
|
# if result status is not ok, then raise an exception.
|
|
1347
1503
|
if (result.Status != 101):
|
|
@@ -373,6 +373,32 @@ class ZeroconfGetInfo(ZeroconfResponse):
|
|
|
373
373
|
if (isinstance(value, str)) or (value is None):
|
|
374
374
|
self._RemoteName = value
|
|
375
375
|
|
|
376
|
+
@property
|
|
377
|
+
def RemoteNameOrAlias(self) -> str:
|
|
378
|
+
"""
|
|
379
|
+
Name to be displayed for the device (e.g. "BOSE-ST10-1").
|
|
380
|
+
|
|
381
|
+
If a RemoteName value is not defined, then the first alias name will be
|
|
382
|
+
returned if one is defined.
|
|
383
|
+
|
|
384
|
+
This value will be null if the response does not contain a RemoteName value
|
|
385
|
+
and there are no Aliases defined.
|
|
386
|
+
"""
|
|
387
|
+
if (self._RemoteName is not None) and ((self._RemoteName + "").strip() != ""):
|
|
388
|
+
|
|
389
|
+
# if remote name is defined, then return it.
|
|
390
|
+
return self._RemoteName
|
|
391
|
+
|
|
392
|
+
elif (self.HasAliases):
|
|
393
|
+
|
|
394
|
+
# if aliases are defined then return the first alias name.
|
|
395
|
+
return self._Aliases[0].Name
|
|
396
|
+
|
|
397
|
+
else:
|
|
398
|
+
|
|
399
|
+
# otherwise return null.
|
|
400
|
+
return None
|
|
401
|
+
|
|
376
402
|
|
|
377
403
|
@property
|
|
378
404
|
def ResolverVersion(self) -> str:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
spotifywebapipython/__init__.py,sha256=ka7F2AsT-UU7KSYKBnfgl5Trojcep5aQspLutg8D-2k,1306
|
|
2
|
-
spotifywebapipython/const.py,sha256=
|
|
2
|
+
spotifywebapipython/const.py,sha256=A1cvmzruhkRRN8tqgeYtpfLus8rUoD446j7HfFSodJ0,4100
|
|
3
3
|
spotifywebapipython/saappmessages.py,sha256=mVprjpd5CdD8kYeAkLzRvMvIQPmaYtH4UCP5my3QhSk,5065
|
|
4
4
|
spotifywebapipython/sautils.py,sha256=LZF3UvxaBK35WIg1hO37nbW8j1y-M-oOxRbi1dPR39w,8746
|
|
5
5
|
spotifywebapipython/spotifyapierror.py,sha256=23_CYlxWN0BceYYCLpBpunl55vEwPr_wFf34l74xzeA,1855
|
|
@@ -12,25 +12,25 @@ spotifywebapipython/spotifytypeprefixes.py,sha256=3yfpxKF7jF694qBuo-PbKYvm70Ywwd
|
|
|
12
12
|
spotifywebapipython/spotifywebapiauthenticationerror.py,sha256=5A0MKvYo3Ypv0ZeSSfLHuFLwzoUUu044bBoUgMfNXNw,3940
|
|
13
13
|
spotifywebapipython/spotifywebapierror.py,sha256=ctYFvkBPlFFEoM_geP6hzwTyC2mPcrYR_zCCNvsmpwc,3870
|
|
14
14
|
spotifywebapipython/spotifywebplayertoken.py,sha256=axj1j1lM7COiiZfpElkJRdnk4AmE6bAQcjKshUIz1Nk,35813
|
|
15
|
-
spotifywebapipython/docs/__init__.html,sha256=
|
|
15
|
+
spotifywebapipython/docs/__init__.html,sha256=Q07ibHoLeCq6xMDsnscQoKKKsBiVYqZRpBtppolBXHo,198988
|
|
16
16
|
spotifywebapipython/docs/codecopy.js,sha256=ryuoZwLIL03YitjjFzn49UO5ta7-OBos6LNA3qHhFzs,3120
|
|
17
|
-
spotifywebapipython/docs/index.html,sha256=
|
|
18
|
-
spotifywebapipython/docs/search.js,sha256=
|
|
17
|
+
spotifywebapipython/docs/index.html,sha256=2i080Y3zkBfhdjzzhh1_62RO2ffkOa5dIP-8wv-PAsU,48779
|
|
18
|
+
spotifywebapipython/docs/search.js,sha256=q4VZ2SMeeGus1p2MMdusjmjfymik-xpnyR1ZQu8v91o,9276225
|
|
19
19
|
spotifywebapipython/docs/spotifywebapipython.ico,sha256=YImj39fqqSPSuB2-sMDlUYDd6YwRfQeJRtq4FjwWglg,16410
|
|
20
20
|
spotifywebapipython/docs/spotifywebapipython_OLD.ico,sha256=YIv5LjcD4bTIJQSOr8jjwbWaeB3QkElRwkTOePiGbZo,14904
|
|
21
|
-
spotifywebapipython/docs/spotifywebapipython/const.html,sha256=
|
|
22
|
-
spotifywebapipython/docs/spotifywebapipython/saappmessages.html,sha256=
|
|
23
|
-
spotifywebapipython/docs/spotifywebapipython/sautils.html,sha256=
|
|
24
|
-
spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html,sha256=
|
|
25
|
-
spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html,sha256=
|
|
26
|
-
spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html,sha256=
|
|
27
|
-
spotifywebapipython/docs/spotifywebapipython/spotifyclient.html,sha256=
|
|
28
|
-
spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html,sha256=
|
|
29
|
-
spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html,sha256=
|
|
30
|
-
spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.html,sha256=
|
|
31
|
-
spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html,sha256=
|
|
32
|
-
spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html,sha256=
|
|
33
|
-
spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html,sha256=
|
|
21
|
+
spotifywebapipython/docs/spotifywebapipython/const.html,sha256=VROpTx214OhAX50Ke5eWWgzg7H1Oj32bRJ-BClJJ1sM,60537
|
|
22
|
+
spotifywebapipython/docs/spotifywebapipython/saappmessages.html,sha256=L8oF4rwosC3z_vKhIfumlt4H-q0Mto4BU4vkHMEe5BI,62358
|
|
23
|
+
spotifywebapipython/docs/spotifywebapipython/sautils.html,sha256=AhrCNFPtfqyRZsWCIxWrGxyVlnfq136ybUxuQLj-_vQ,62031
|
|
24
|
+
spotifywebapipython/docs/spotifywebapipython/spotifyapierror.html,sha256=43spTeZ5re5j6GO-MNJOcJc8R-9nIXKkDp-caVza6yY,48054
|
|
25
|
+
spotifywebapipython/docs/spotifywebapipython/spotifyapimessage.html,sha256=KdbQPIL0TwVux1RElR9Dc-ProvihgWWNCzkFYsUcTqg,59210
|
|
26
|
+
spotifywebapipython/docs/spotifywebapipython/spotifyauthtoken.html,sha256=toVCb95bEAvbNxOMpVXIediauVBNl_6NU5h4rdi1a0U,59256
|
|
27
|
+
spotifywebapipython/docs/spotifywebapipython/spotifyclient.html,sha256=_DlyOletxlhhE2Xdo5RH0stbdU4x5FSIcHQQPhBdR5E,1677607
|
|
28
|
+
spotifywebapipython/docs/spotifywebapipython/spotifydiscovery.html,sha256=vihtraJlGUn4kp3A4tx7-GOqh4livctWGQ8H5rUCayU,57662
|
|
29
|
+
spotifywebapipython/docs/spotifywebapipython/spotifymediatypes.html,sha256=ss7-LPAqEixRTws6SHYEQFhAstSMdRdP1eDcBlHJhNA,51532
|
|
30
|
+
spotifywebapipython/docs/spotifywebapipython/spotifytypeprefixes.html,sha256=8WBQbIfh_i2SDlelt123pOARpKxdBQYQrJH4SoakYOA,52401
|
|
31
|
+
spotifywebapipython/docs/spotifywebapipython/spotifywebapiauthenticationerror.html,sha256=1Ka6gyOAeSMOGefzL_RVhTu0lbnFjqGUx4TPNPFa0ns,52174
|
|
32
|
+
spotifywebapipython/docs/spotifywebapipython/spotifywebapierror.html,sha256=koUkSqk89KM8jihcZM0kB9tD6Q4XiqvIG5qqDBz62Nw,51484
|
|
33
|
+
spotifywebapipython/docs/spotifywebapipython/spotifywebplayertoken.html,sha256=id1JsjfuL1D6KcPhrCiLw1dF__fKRSF6rGsZMFVQfbA,62204
|
|
34
34
|
spotifywebapipython/models/__init__.py,sha256=RwRQhUX-6EUs8XvStVFpR8EPwoqIAz9K6ref-28qHKI,4423
|
|
35
35
|
spotifywebapipython/models/album.py,sha256=U3Pdw0-EL33LdkeiN31fjZ23rlLk6DNGV8jRmZU7EyQ,5170
|
|
36
36
|
spotifywebapipython/models/albumpagesaved.py,sha256=SBYMhkh5ZsMrRAXl-cm9zsxMDh8gO190ENq6Py2TyGY,2417
|
|
@@ -110,7 +110,7 @@ spotifywebapipython/oauthcli/__init__.py,sha256=K37XTxYAKT0n8otCNzWbjgJ10fAPN4WL
|
|
|
110
110
|
spotifywebapipython/oauthcli/authclient.py,sha256=qGKa4FLBHHZQyP5ho1sIM_mNmyshQd720YwKcUFQJAw,56207
|
|
111
111
|
spotifywebapipython/spotifyconnect/__init__.py,sha256=9Xl5KiJFvWLbsdb4YQ0xmF67i36ybpXCCXrWeNsKEvc,1108
|
|
112
112
|
spotifywebapipython/spotifyconnect/spotifyconnectdeviceeventargs.py,sha256=35hKa0FAaPgy7W8D6MFXmga667pjwXITo8jSj1TDfE8,1988
|
|
113
|
-
spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py,sha256=
|
|
113
|
+
spotifywebapipython/spotifyconnect/spotifyconnectdirectorytask.py,sha256=GY8t6nH4aEA77yd9ysTYE-5DUZE0_rpCtNn5aKmjQsA,157385
|
|
114
114
|
spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastapptask.py,sha256=tCjqU7ubtfzBENdvqwVB0pMDGHRY_0u6N5OYlI5hbj8,23368
|
|
115
115
|
spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastcontroller.py,sha256=e2wLmFq4393IdVa51w8X5UQl4KfnHRh34DMlxZfl9-g,34218
|
|
116
116
|
spotifywebapipython/spotifyconnect/spotifyconnectzeroconfcastlistener.py,sha256=k_Lw52CqNliCrfqMP6s_BHynqHYBOf_NwdrJru6qDFA,16707
|
|
@@ -132,13 +132,13 @@ spotifywebapipython/zeroconfapi/credentials.py,sha256=MIXqZvFNDjoICpZGNXPveHNl9G
|
|
|
132
132
|
spotifywebapipython/zeroconfapi/cryptodiffiehellman.py,sha256=k-nMR68GtzODYsQrwCpvFE2XNxv-XUFbMemplldtwp4,3340
|
|
133
133
|
spotifywebapipython/zeroconfapi/helpers.py,sha256=9lJFOuKYrk-e7SbU94WmFv5oiBq4k5gMS8wE1it4Wbc,1429
|
|
134
134
|
spotifywebapipython/zeroconfapi/spotifyzeroconfapierror.py,sha256=rTwIrr1qV0UiQS_zBufboyZ3A3yGrdCsTAOiVA5dvzY,3031
|
|
135
|
-
spotifywebapipython/zeroconfapi/zeroconfconnect.py,sha256=
|
|
136
|
-
spotifywebapipython/zeroconfapi/zeroconfgetinfo.py,sha256=
|
|
135
|
+
spotifywebapipython/zeroconfapi/zeroconfconnect.py,sha256=JzAKVc7XC_HrTH1IwsuXpcgW0rFlRYocHL8likoYYvQ,79023
|
|
136
|
+
spotifywebapipython/zeroconfapi/zeroconfgetinfo.py,sha256=gTr3J3iVDR_ueP1tg-WHuCOke3eP864WGiESmOOYFUw,22243
|
|
137
137
|
spotifywebapipython/zeroconfapi/zeroconfgetinfoalias.py,sha256=FZJM23HIPJSfhXIC3-EuS1XhawheoG5maSp7__k4yBo,2932
|
|
138
138
|
spotifywebapipython/zeroconfapi/zeroconfgetinfodrmmediaformat.py,sha256=c5uk8ljsIuBK3w2jahHKQuoWvm245LOjUBitetXx5Mo,2347
|
|
139
139
|
spotifywebapipython/zeroconfapi/zeroconfresponse.py,sha256=mAPIDj5MG53x8peiScrp7OJblwy_o_Sto4wqE0rGujU,6445
|
|
140
|
-
spotifywebapipython-1.0.
|
|
141
|
-
spotifywebapipython-1.0.
|
|
142
|
-
spotifywebapipython-1.0.
|
|
143
|
-
spotifywebapipython-1.0.
|
|
144
|
-
spotifywebapipython-1.0.
|
|
140
|
+
spotifywebapipython-1.0.292.dist-info/LICENSE,sha256=KGcc9g0zwqNLcWnSn_ezv1UtKyhMENFhvtixpxUPNsE,1177
|
|
141
|
+
spotifywebapipython-1.0.292.dist-info/METADATA,sha256=XYS1qt1LwEsJDG61IbX34sMh5I0ZpQcWoZ0U4w0uMRI,11644
|
|
142
|
+
spotifywebapipython-1.0.292.dist-info/WHEEL,sha256=yQN5g4mg4AybRjkgi-9yy4iQEFibGQmlz78Pik5Or-A,92
|
|
143
|
+
spotifywebapipython-1.0.292.dist-info/top_level.txt,sha256=jTBLFlijKTesYh2dteAz4vb17qjaqWKeDjpnnwTcrjs,20
|
|
144
|
+
spotifywebapipython-1.0.292.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{spotifywebapipython-1.0.290.dist-info → spotifywebapipython-1.0.292.dist-info}/top_level.txt
RENAMED
|
File without changes
|