spotifywebapiPython 1.0.100__py3-none-any.whl → 1.0.102__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.100.dist-info → spotifywebapiPython-1.0.102.dist-info}/METADATA +1 -1
- {spotifywebapiPython-1.0.100.dist-info → spotifywebapiPython-1.0.102.dist-info}/RECORD +23 -23
- spotifywebapipython/const.py +1 -1
- spotifywebapipython/docs/__init__.html +15 -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 +11 -3
- spotifywebapipython/docs/spotifywebapiPython/spotifydiscovery.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapiauthenticationerror.html +2 -2
- spotifywebapipython/docs/spotifywebapiPython/spotifywebapierror.html +2 -2
- spotifywebapipython/spotifyclient.py +37 -6
- spotifywebapipython/zeroconfapi/credentials.py +4 -1
- spotifywebapipython/zeroconfapi/helpers.py +7 -0
- spotifywebapipython/zeroconfapi/zeroconfconnect.py +126 -2
- {spotifywebapiPython-1.0.100.dist-info → spotifywebapiPython-1.0.102.dist-info}/LICENSE +0 -0
- {spotifywebapiPython-1.0.100.dist-info → spotifywebapiPython-1.0.102.dist-info}/WHEEL +0 -0
- {spotifywebapiPython-1.0.100.dist-info → spotifywebapiPython-1.0.102.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# external package imports.
|
|
2
2
|
import json
|
|
3
|
+
import os.path
|
|
3
4
|
import requests
|
|
4
5
|
from requests.models import Response
|
|
5
6
|
import time
|
|
@@ -7,7 +8,7 @@ import time
|
|
|
7
8
|
# our package imports.
|
|
8
9
|
from .blobbuilder import BlobBuilder
|
|
9
10
|
from .credentials import Credentials, AuthenticationTypes
|
|
10
|
-
from .helpers import int_to_b64str
|
|
11
|
+
from .helpers import int_to_b64str, b64str_to_bytes
|
|
11
12
|
from .spotifyzeroconfapierror import SpotifyZeroconfApiError
|
|
12
13
|
from .zeroconfresponse import ZeroconfResponse
|
|
13
14
|
from .zeroconfgetinfo import ZeroconfGetInfo
|
|
@@ -379,6 +380,12 @@ class ZeroconfConnect:
|
|
|
379
380
|
.. include:: ../../docs/include/samplecode/ZeroconfConnect/Connect.py
|
|
380
381
|
```
|
|
381
382
|
</details>
|
|
383
|
+
<details>
|
|
384
|
+
<summary>Sample Code - spotifyD</summary>
|
|
385
|
+
```python
|
|
386
|
+
.. include:: ../../docs/include/samplecode/ZeroconfConnect/Connect_SPOTIFYD.py
|
|
387
|
+
```
|
|
388
|
+
</details>
|
|
382
389
|
"""
|
|
383
390
|
apiMethodName:str = 'Connect'
|
|
384
391
|
apiMethodParms:SIMethodParmListContext = None
|
|
@@ -559,8 +566,37 @@ class ZeroconfConnect:
|
|
|
559
566
|
if (info.PublicKey == 'SU5WQUxJRA==') and (info.Availability == 'NOT-LOADED'):
|
|
560
567
|
includeOriginDeviceInfo = True
|
|
561
568
|
|
|
569
|
+
# special processing for librespot, spotifyd.
|
|
570
|
+
if info.ModelDisplayName == 'librespot':
|
|
571
|
+
|
|
572
|
+
# we will use credentials from a librespot credentials.json file.
|
|
573
|
+
# this requires that the user copy the "credentials.json" file from the
|
|
574
|
+
# librespot cache location (e.g. "/home/user/.cache/spotifyd/credentials.json", etc)
|
|
575
|
+
# and place it in the same folder as the token storage file with a new name
|
|
576
|
+
# of "SpotifyWebApiPython_librespot_credentials.json".
|
|
577
|
+
|
|
578
|
+
# get credentials from librespot credentials file.
|
|
579
|
+
credentials:dict = self._LoadLibrespotCredentials(loginId)
|
|
580
|
+
|
|
581
|
+
# create credentials and builder objects.
|
|
582
|
+
librespot_username = credentials.get('username','')
|
|
583
|
+
librespot_password = b64str_to_bytes(credentials.get('auth_data',''))
|
|
584
|
+
credentials:Credentials = Credentials(librespot_username, librespot_password, AuthenticationTypes.STORED_SPOTIFY_CREDENTIALS)
|
|
585
|
+
builder = BlobBuilder(credentials, info.DeviceId, info.PublicKey)
|
|
586
|
+
|
|
587
|
+
# set defaults used by this token type:
|
|
588
|
+
# - clientKey should be the getInfo response PublicKey (base64 encoded).
|
|
589
|
+
# - include origin device information in the addUser request.
|
|
590
|
+
# - tokenType should be set to '' (not used by librespot).
|
|
591
|
+
clientKey:str = builder.dh_keys.PublicKeyBase64String
|
|
592
|
+
includeOriginDeviceInfo = True
|
|
593
|
+
tokenType = ''
|
|
594
|
+
|
|
595
|
+
# formulate the blob.
|
|
596
|
+
blob = builder.build()
|
|
597
|
+
|
|
562
598
|
# special processing for tokenType "authorization_code":
|
|
563
|
-
|
|
599
|
+
elif info.TokenType == 'authorization_code':
|
|
564
600
|
|
|
565
601
|
# trace.
|
|
566
602
|
_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))
|
|
@@ -819,6 +855,90 @@ class ZeroconfConnect:
|
|
|
819
855
|
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
820
856
|
|
|
821
857
|
|
|
858
|
+
def _LoadLibrespotCredentials(self, loginId:str) -> dict:
|
|
859
|
+
"""
|
|
860
|
+
Loads spotify user credentials from a librespot credentials.json file.
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
loginId (str):
|
|
864
|
+
Spotify canonical login id of the authenticating user.
|
|
865
|
+
Example = `3758dfdsfjk435hjk6k79lm0n3c4`
|
|
866
|
+
|
|
867
|
+
Returns:
|
|
868
|
+
A credentials dictionary, if one was found in the credentials storage file;
|
|
869
|
+
otherwise, null.
|
|
870
|
+
"""
|
|
871
|
+
apiMethodName:str = '_LoadLibrespotCredentials'
|
|
872
|
+
apiMethodParms:SIMethodParmListContext = None
|
|
873
|
+
|
|
874
|
+
try:
|
|
875
|
+
|
|
876
|
+
# trace.
|
|
877
|
+
apiMethodParms = _logsi.EnterMethodParmList(SILevel.Debug, apiMethodName)
|
|
878
|
+
_logsi.LogMethodParmList(SILevel.Verbose, "Loading librespot credentials from storage", apiMethodParms)
|
|
879
|
+
|
|
880
|
+
# librespot credntials.json file format as of 2024/09/28
|
|
881
|
+
# {
|
|
882
|
+
# "username": "31l77xxxxxxxxxxxxxxxxxxxxxxx",
|
|
883
|
+
# "auth_type": 1,
|
|
884
|
+
# "auth_data": "QVFET2h6 ... aFp3OHg="
|
|
885
|
+
# }
|
|
886
|
+
|
|
887
|
+
# formulate the credentials file path.
|
|
888
|
+
filePath:str = os.path.join(self._TokenStorageDir, "SpotifyWebApiPython_librespot_credentials.json")
|
|
889
|
+
|
|
890
|
+
# does the file exist? if not, then it's an error as we are trying to authenticate!
|
|
891
|
+
if (not os.path.exists(filePath)):
|
|
892
|
+
raise SpotifyApiError("Could not find librespot authorization credentials file (%s). This file is required in order to authenticate to a device or service that utilizes the librespot application." % (filePath), logsi=_logsi)
|
|
893
|
+
|
|
894
|
+
# open the credentials file, and load its contents.
|
|
895
|
+
_logsi.LogVerbose('Opening librespot credentials file: "%s"' % (filePath))
|
|
896
|
+
with open(filePath, 'r') as f:
|
|
897
|
+
credentials:dict = json.load(f)
|
|
898
|
+
_logsi.LogDictionary(SILevel.Verbose, 'librespot credentials were loaded successfully', credentials, prettyPrint=True)
|
|
899
|
+
|
|
900
|
+
# prepare for comparison.
|
|
901
|
+
loginId = loginId.lower()
|
|
902
|
+
credential:dict = None
|
|
903
|
+
|
|
904
|
+
# check for multiple credential format.
|
|
905
|
+
if (isinstance(credentials, list)):
|
|
906
|
+
|
|
907
|
+
# file contents are an array of librespot credentials.
|
|
908
|
+
for credential in credentials:
|
|
909
|
+
if (credential.get('username','').lower() == loginId):
|
|
910
|
+
break
|
|
911
|
+
|
|
912
|
+
else:
|
|
913
|
+
|
|
914
|
+
# file contents are a single librespot credential.
|
|
915
|
+
credential = credentials
|
|
916
|
+
|
|
917
|
+
# does the found credential match the requested login? if not, then reset the credential.
|
|
918
|
+
if (credential is not None):
|
|
919
|
+
if (credential.get('username','').lower() != loginId):
|
|
920
|
+
credential = None
|
|
921
|
+
|
|
922
|
+
# if no credentials then it's an error.
|
|
923
|
+
if (credential is None):
|
|
924
|
+
raise SpotifyApiError("librespot authorization credentials file (%s) did not contain credentials for canonical loginid (%s)." % (filePath, loginId), logsi=_logsi)
|
|
925
|
+
|
|
926
|
+
# return credentials to caller.
|
|
927
|
+
return credential
|
|
928
|
+
|
|
929
|
+
except SpotifyApiError: raise # pass handled exceptions on thru
|
|
930
|
+
except Exception as ex:
|
|
931
|
+
|
|
932
|
+
# trace.
|
|
933
|
+
_logsi.LogException('Could not load librespot authorization credentials file (%s).' % (filePath), ex)
|
|
934
|
+
raise
|
|
935
|
+
|
|
936
|
+
finally:
|
|
937
|
+
|
|
938
|
+
# trace.
|
|
939
|
+
_logsi.LeaveMethod(SILevel.Debug, apiMethodName)
|
|
940
|
+
|
|
941
|
+
|
|
822
942
|
def Disconnect(
|
|
823
943
|
self,
|
|
824
944
|
delay:float=0.50,
|
|
@@ -848,6 +968,10 @@ class ZeroconfConnect:
|
|
|
848
968
|
|
|
849
969
|
The currently logged in user (if any) will be logged out of Spotify Connect, and the
|
|
850
970
|
device id removed from the active Spotify Connect device list.
|
|
971
|
+
|
|
972
|
+
This method should not be attempted for devices or services that utilize `librespot`,
|
|
973
|
+
such as `spotifyd`. The `librespot` library does not implement the `resetUsers`
|
|
974
|
+
Spotify Connect Zeroconf endpoint, and will fail with a 404 error.
|
|
851
975
|
|
|
852
976
|
<details>
|
|
853
977
|
<summary>Sample Code</summary>
|
|
File without changes
|
|
File without changes
|
{spotifywebapiPython-1.0.100.dist-info → spotifywebapiPython-1.0.102.dist-info}/top_level.txt
RENAMED
|
File without changes
|