Skip to content

Commit b28e684

Browse files
committed
#135 Add ClientToken support
1 parent 1a91446 commit b28e684

7 files changed

Lines changed: 323 additions & 3 deletions

File tree

librespot/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
class Version:
9-
version_name = "0.0.1"
9+
version_name = "0.0.4"
1010

1111
@staticmethod
1212
def platform() -> Platform:

librespot/core.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from librespot.crypto import CipherPair, DiffieHellman, Packet
1313
from librespot.mercury import MercuryClient, MercuryRequests, RawMercuryRequest
1414
from librespot.metadata import AlbumId, ArtistId, EpisodeId, ShowId, TrackId
15-
from librespot.proto import Authentication_pb2 as Authentication, Connect_pb2 as Connect, Keyexchange_pb2 as Keyexchange, Metadata_pb2 as Metadata
15+
from librespot.proto import Authentication_pb2 as Authentication, ClientToken_pb2 as ClientToken, Connect_pb2 as Connect, Connectivity_pb2 as Connectivity, Keyexchange_pb2 as Keyexchange, Metadata_pb2 as Metadata
1616
from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate
1717
from librespot.structure import Closeable, MessageListener, RequestListener, SubListener
1818
import base64
@@ -39,6 +39,7 @@
3939
class ApiClient(Closeable):
4040
logger = logging.getLogger("Librespot:ApiClient")
4141
__base_url: str
42+
__client_token_str: str = None
4243
__session: Session
4344

4445
def __init__(self, session: Session):
@@ -49,6 +50,11 @@ def build_request(
4950
self, method: str, suffix: str,
5051
headers: typing.Union[None, typing.Dict[str, str]],
5152
body: typing.Union[None, bytes]) -> requests.PreparedRequest:
53+
if self.__client_token_str is None:
54+
resp = self.__client_token()
55+
self.__client_token_str = resp.granted_token.token
56+
self.logger.debug("Updated client token: {}".format(self.__client_token_str))
57+
5258
request = requests.PreparedRequest()
5359
request.method = method
5460
request.data = body
@@ -147,6 +153,45 @@ def get_metadata_4_show(self, show: ShowId) -> Metadata.Show:
147153
proto.ParseFromString(body)
148154
return proto
149155

156+
def set_client_token(self, client_token):
157+
self.__client_token_str = client_token
158+
159+
def __client_token(self):
160+
proto_req = ClientToken.ClientTokenRequest(
161+
request_type=ClientToken.ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST,
162+
client_data=ClientToken.ClientDataRequest(
163+
client_id=MercuryRequests.keymaster_client_id,
164+
client_version=Version.version_name,
165+
connectivity_sdk_data=Connectivity.ConnectivitySdkData(
166+
device_id=self.__session.device_id(),
167+
platform_specific_data=Connectivity.PlatformSpecificData(
168+
windows=Connectivity.NativeWindowsData(
169+
something1=10,
170+
something3=21370,
171+
something4=2,
172+
something6=9,
173+
something7=332,
174+
something8=33404,
175+
something10=True,
176+
),
177+
),
178+
),
179+
),
180+
)
181+
182+
resp = requests.post("https://clienttoken.spotify.com/v1/clienttoken",
183+
proto_req.SerializeToString(),
184+
headers={
185+
"Accept": "application/x-protobuf",
186+
"Content-Encoding": "",
187+
})
188+
189+
ApiClient.StatusCodeException.check_status(resp)
190+
191+
proto_resp = ClientToken.ClientTokenResponse()
192+
proto_resp.ParseFromString(resp.content)
193+
return proto_resp
194+
150195
class StatusCodeException(IOError):
151196
code: int
152197

librespot/proto/ClientToken_pb2.py

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

librespot/proto/Connectivity_pb2.py

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/client_token.proto

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
syntax = "proto3";
2+
3+
package spotify.clienttoken.http.v0;
4+
5+
import "connectivity.proto";
6+
7+
option optimize_for = CODE_SIZE;
8+
option java_package = "com.spotify.clienttoken.http.v0";
9+
10+
message ClientTokenRequest {
11+
ClientTokenRequestType request_type = 1;
12+
13+
oneof request {
14+
ClientDataRequest client_data = 2;
15+
ChallengeAnswersRequest challenge_answers = 3;
16+
}
17+
}
18+
19+
message ClientDataRequest {
20+
string client_version = 1;
21+
string client_id = 2;
22+
23+
oneof data {
24+
data.v0.ConnectivitySdkData connectivity_sdk_data = 3;
25+
}
26+
}
27+
28+
message ChallengeAnswersRequest {
29+
string state = 1;
30+
repeated ChallengeAnswer answers = 2;
31+
}
32+
33+
message ClientTokenResponse {
34+
ClientTokenResponseType response_type = 1;
35+
36+
oneof response {
37+
GrantedTokenResponse granted_token = 2;
38+
ChallengesResponse challenges = 3;
39+
}
40+
}
41+
42+
message TokenDomain {
43+
string domain = 1;
44+
}
45+
46+
message GrantedTokenResponse {
47+
string token = 1;
48+
int32 expires_after_seconds = 2;
49+
int32 refresh_after_seconds = 3;
50+
repeated TokenDomain domains = 4;
51+
}
52+
53+
message ChallengesResponse {
54+
string state = 1;
55+
repeated Challenge challenges = 2;
56+
}
57+
58+
message ClientSecretParameters {
59+
string salt = 1;
60+
}
61+
62+
message EvaluateJSParameters {
63+
string code = 1;
64+
repeated string libraries = 2;
65+
}
66+
67+
message HashCashParameters {
68+
int32 length = 1;
69+
string prefix = 2;
70+
}
71+
72+
message Challenge {
73+
ChallengeType type = 1;
74+
75+
oneof parameters {
76+
ClientSecretParameters client_secret_parameters = 2;
77+
EvaluateJSParameters evaluate_js_parameters = 3;
78+
HashCashParameters evaluate_hashcash_parameters = 4;
79+
}
80+
}
81+
82+
message ClientSecretHMACAnswer {
83+
string hmac = 1;
84+
}
85+
86+
message EvaluateJSAnswer {
87+
string result = 1;
88+
}
89+
90+
message HashCashAnswer {
91+
string suffix = 1;
92+
}
93+
94+
message ChallengeAnswer {
95+
ChallengeType ChallengeType = 1;
96+
97+
oneof answer {
98+
ClientSecretHMACAnswer client_secret = 2;
99+
EvaluateJSAnswer evaluate_js = 3;
100+
HashCashAnswer hash_cash = 4;
101+
}
102+
}
103+
104+
message ClientTokenBadRequest {
105+
string message = 1;
106+
}
107+
108+
enum ClientTokenRequestType {
109+
REQUEST_UNKNOWN = 0;
110+
REQUEST_CLIENT_DATA_REQUEST = 1;
111+
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
112+
}
113+
114+
enum ClientTokenResponseType {
115+
RESPONSE_UNKNOWN = 0;
116+
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
117+
RESPONSE_CHALLENGES_RESPONSE = 2;
118+
}
119+
120+
enum ChallengeType {
121+
CHALLENGE_UNKNOWN = 0;
122+
CHALLENGE_CLIENT_SECRET_HMAC = 1;
123+
CHALLENGE_EVALUATE_JS = 2;
124+
CHALLENGE_HASH_CASH = 3;
125+
}

proto/connectivity.proto

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
syntax = "proto3";
2+
3+
package spotify.clienttoken.data.v0;
4+
5+
option optimize_for = CODE_SIZE;
6+
option java_package = "com.spotify.clienttoken.data.v0";
7+
8+
message ConnectivitySdkData {
9+
PlatformSpecificData platform_specific_data = 1;
10+
string device_id = 2;
11+
}
12+
13+
message PlatformSpecificData {
14+
oneof data {
15+
NativeAndroidData android = 1;
16+
NativeIOSData ios = 2;
17+
NativeWindowsData windows = 4;
18+
}
19+
}
20+
21+
message NativeAndroidData {
22+
int32 major_sdk_version = 1;
23+
int32 minor_sdk_version = 2;
24+
int32 patch_sdk_version = 3;
25+
uint32 api_version = 4;
26+
Screen screen_dimensions = 5;
27+
}
28+
29+
message NativeIOSData {
30+
int32 user_interface_idiom = 1;
31+
bool target_iphone_simulator = 2;
32+
string hw_machine = 3;
33+
string system_version = 4;
34+
string simulator_model_identifier = 5;
35+
}
36+
37+
message NativeWindowsData {
38+
int32 something1 = 1;
39+
int32 something3 = 3;
40+
int32 something4 = 4;
41+
int32 something6 = 6;
42+
int32 something7 = 7;
43+
int32 something8 = 8;
44+
bool something10 = 10;
45+
}
46+
47+
message Screen {
48+
int32 width = 1;
49+
int32 height = 2;
50+
int32 density = 3;
51+
}

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import setuptools
22

33
setuptools.setup(name="librespot",
4-
version="0.0.3",
4+
version="0.0.4",
55
description="Open Source Spotify Client",
66
long_description=open("README.md").read(),
77
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)