Restyled by yapf

This commit is contained in:
Restyled.io 2023-09-05 07:21:12 +00:00 committed by d.rathmer
parent 12621ef31c
commit ecd0006874
1 changed files with 223 additions and 253 deletions

View File

@ -94,9 +94,8 @@ class ApiClient(Closeable):
if self.__client_token_str is None:
resp = self.__client_token()
self.__client_token_str = resp.granted_token.token
self.logger.debug(
"Updated client token: {}".format(self.__client_token_str)
)
self.logger.debug("Updated client token: {}".format(
self.__client_token_str))
request = requests.PreparedRequest()
request.method = method
@ -105,8 +104,7 @@ class ApiClient(Closeable):
if headers is not None:
request.headers = headers
request.headers["Authorization"] = "Bearer {}".format(
self.__session.tokens().get("playlist-read")
)
self.__session.tokens().get("playlist-read"))
request.headers["client-token"] = self.__client_token_str
request.url = self.__base_url + suffix
return request
@ -130,13 +128,11 @@ class ApiClient(Closeable):
"""
response = self.__session.client().send(
self.build_request(method, suffix, headers, body)
)
self.build_request(method, suffix, headers, body))
return response
def put_connect_state(
self, connection_id: str, proto: Connect.PutStateRequest
) -> None:
def put_connect_state(self, connection_id: str,
proto: Connect.PutStateRequest) -> None:
"""
:param connection_id: str:
@ -154,16 +150,11 @@ class ApiClient(Closeable):
)
if response.status_code == 413:
self.logger.warning(
"PUT state payload is too large: {} bytes uncompressed.".format(
len(proto.SerializeToString())
)
)
"PUT state payload is too large: {} bytes uncompressed.".
format(len(proto.SerializeToString())))
elif response.status_code != 200:
self.logger.warning(
"PUT state returned {}. headers: {}".format(
response.status_code, response.headers
)
)
self.logger.warning("PUT state returned {}. headers: {}".format(
response.status_code, response.headers))
def get_metadata_4_track(self, track: TrackId) -> Metadata.Track:
"""
@ -171,9 +162,9 @@ class ApiClient(Closeable):
:param track: TrackId:
"""
response = self.send(
"GET", "/metadata/4/track/{}".format(track.hex_id()), None, None
)
response = self.send("GET",
"/metadata/4/track/{}".format(track.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
@ -188,9 +179,9 @@ class ApiClient(Closeable):
:param episode: EpisodeId:
"""
response = self.send(
"GET", "/metadata/4/episode/{}".format(episode.hex_id()), None, None
)
response = self.send("GET",
"/metadata/4/episode/{}".format(episode.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
@ -205,9 +196,9 @@ class ApiClient(Closeable):
:param album: AlbumId:
"""
response = self.send(
"GET", "/metadata/4/album/{}".format(album.hex_id()), None, None
)
response = self.send("GET",
"/metadata/4/album/{}".format(album.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
@ -223,9 +214,9 @@ class ApiClient(Closeable):
:param artist: ArtistId:
"""
response = self.send(
"GET", "/metadata/4/artist/{}".format(artist.hex_id()), None, None
)
response = self.send("GET",
"/metadata/4/artist/{}".format(artist.hex_id()),
None, None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
@ -240,9 +231,9 @@ class ApiClient(Closeable):
:param show: ShowId:
"""
response = self.send(
"GET", "/metadata/4/show/{}".format(show.hex_id()), None, None
)
response = self.send("GET",
"/metadata/4/show/{}".format(show.hex_id()), None,
None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
@ -251,15 +242,16 @@ class ApiClient(Closeable):
proto.ParseFromString(body)
return proto
def get_playlist(self, _id: PlaylistId) -> Playlist4External.SelectedListContent:
def get_playlist(self,
_id: PlaylistId) -> Playlist4External.SelectedListContent:
"""
:param _id: PlaylistId:
"""
response = self.send(
"GET", "/playlist/v2/playlist/{}".format(_id.id()), None, None
)
response = self.send("GET",
"/playlist/v2/playlist/{}".format(_id.id()), None,
None)
ApiClient.StatusCodeException.check_status(response)
body = response.content
if body is None:
@ -278,7 +270,8 @@ class ApiClient(Closeable):
def __client_token(self):
proto_req = ClientToken.ClientTokenRequest(
request_type=ClientToken.ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST,
request_type=ClientToken.ClientTokenRequestType.
REQUEST_CLIENT_DATA_REQUEST,
client_data=ClientToken.ClientDataRequest(
client_id=MercuryRequests.keymaster_client_id,
client_version=Version.version_name,
@ -293,8 +286,7 @@ class ApiClient(Closeable):
something7=332,
something8=33404,
something10=True,
),
),
), ),
),
),
)
@ -345,7 +337,8 @@ class ApResolver:
:returns: The resulting object will be returned
"""
response = requests.get("{}?type={}".format(ApResolver.base_url, service_type))
response = requests.get("{}?type={}".format(ApResolver.base_url,
service_type))
if response.status_code != 200:
if response.status_code == 502:
raise RuntimeError(
@ -414,7 +407,8 @@ class DealerClient(Closeable):
def __init__(self, session: Session):
self.__session = session
def add_message_listener(self, listener: MessageListener, uris: list[str]) -> None:
def add_message_listener(self, listener: MessageListener,
uris: list[str]) -> None:
"""
:param listener: MessageListener:
@ -424,8 +418,7 @@ class DealerClient(Closeable):
with self.__message_listeners_lock:
if listener in self.__message_listeners:
raise TypeError(
"A listener for {} has already been added.".format(uris)
)
"A listener for {} has already been added.".format(uris))
self.__message_listeners[listener] = uris
self.__message_listeners_lock.notify_all()
@ -439,8 +432,7 @@ class DealerClient(Closeable):
with self.__request_listeners_lock:
if uri in self.__request_listeners:
raise TypeError(
"A listener for '{}' has already been added.".format(uri)
)
"A listener for '{}' has already been added.".format(uri))
self.__request_listeners[uri] = listener
self.__request_listeners_lock.notify_all()
@ -469,7 +461,8 @@ class DealerClient(Closeable):
self.__last_scheduled_reconnection = None
self.connect()
self.__last_scheduled_reconnection = self.__scheduler.enter(10, 1, anonymous)
self.__last_scheduled_reconnection = self.__scheduler.enter(
10, 1, anonymous)
def handle_message(self, obj: typing.Any) -> None:
"""
@ -527,10 +520,8 @@ class DealerClient(Closeable):
sender = payload.get("sent_by_device_id")
command = payload.get("command")
self.logger.debug(
"Received request. [mid: {}, key: {}, pid: {}, sender: {}, command: {}]".format(
mid, key, pid, sender, command
)
)
"Received request. [mid: {}, key: {}, pid: {}, sender: {}, command: {}]"
.format(mid, key, pid, sender, command))
interesting = False
with self.__request_listeners_lock:
for mid_prefix in self.__request_listeners:
@ -544,8 +535,8 @@ class DealerClient(Closeable):
if self.__connection is not None:
self.__connection.send_reply(key, result)
self.logger.warning(
"Handled request. [key: {}, result: {}]".format(key, result)
)
"Handled request. [key: {}, result: {}]".format(
key, result))
self.__worker.submit(anonymous)
if not interesting:
@ -597,7 +588,8 @@ class DealerClient(Closeable):
__url: str
__ws: websocket.WebSocketApp
def __init__(self, session: Session, dealer_client: DealerClient, url: str):
def __init__(self, session: Session, dealer_client: DealerClient,
url: str):
self.__session = session
self.__dealer_client = dealer_client
self.__url = url
@ -621,8 +613,7 @@ class DealerClient(Closeable):
if self.__closed:
return
self.__dealer_client.logger.warning(
"An exception occurred. Reconnecting..."
)
"An exception occurred. Reconnecting...")
self.close()
def on_message(self, ws: websocket.WebSocketApp, text: str):
@ -644,7 +635,8 @@ class DealerClient(Closeable):
elif typ == MessageType.PING:
pass
else:
raise RuntimeError("Unknown message type for {}".format(typ.value))
raise RuntimeError("Unknown message type for {}".format(
typ.value))
def on_open(self, ws: websocket.WebSocketApp):
"""
@ -654,13 +646,10 @@ class DealerClient(Closeable):
"""
if self.__closed:
self.__dealer_client.logger.fatal(
"I wonder what happened here... Terminating. [closed: {}]".format(
self.__closed
)
)
"I wonder what happened here... Terminating. [closed: {}]".
format(self.__closed))
self.__dealer_client.logger.debug(
"Dealer connected! [url: {}]".format(self.__url)
)
"Dealer connected! [url: {}]".format(self.__url))
def anonymous():
""" """
@ -680,9 +669,11 @@ class DealerClient(Closeable):
self.__received_pong = False
self.__scheduler.enter(3, 1, anonymous2)
self.__last_scheduled_ping = self.__scheduler.enter(30, 1, anonymous)
self.__last_scheduled_ping = self.__scheduler.enter(
30, 1, anonymous)
self.__last_scheduled_ping = self.__scheduler.enter(30, 1, anonymous)
self.__last_scheduled_ping = self.__scheduler.enter(
30, 1, anonymous)
def send_ping(self):
""" """
@ -695,12 +686,11 @@ class DealerClient(Closeable):
:param result: DealerClient.RequestResult:
"""
success = (
"true" if result == DealerClient.RequestResult.SUCCESS else "false"
)
success = ("true" if result == DealerClient.RequestResult.SUCCESS
else "false")
self.__ws.send(
'{"type":"reply","key":"%s","payload":{"success":%s}' % (key, success)
)
'{"type":"reply","key":"%s","payload":{"success":%s}' %
(key, success))
class RequestResult(enum.Enum):
""" """
@ -727,21 +717,19 @@ class EventService(Closeable):
try:
body = event_builder.to_array()
resp = self.__session.mercury().send_sync(
RawMercuryRequest.Builder()
.set_uri("hm://event-service/v1/events")
.set_method("POST")
.add_user_field("Accept-Language", "en")
.add_user_field("X-ClientTimeStamp", int(time.time() * 1000))
.add_payload_part(body)
.build()
)
self.logger.debug(
"Event sent. body: {}, result: {}".format(body, resp.status_code)
)
RawMercuryRequest.Builder().set_uri(
"hm://event-service/v1/events").set_method("POST").
add_user_field("Accept-Language", "en").add_user_field(
"X-ClientTimeStamp",
int(time.time() * 1000)).add_payload_part(body).build())
self.logger.debug("Event sent. body: {}, result: {}".format(
body, resp.status_code))
except IOError as ex:
self.logger.error("Failed sending event: {} {}".format(event_builder, ex))
self.logger.error("Failed sending event: {} {}".format(
event_builder, ex))
def send_event(self, event_or_builder: typing.Union[GenericEvent, EventBuilder]):
def send_event(self, event_or_builder: typing.Union[GenericEvent,
EventBuilder]):
"""
:param event_or_builder: typing.Union[GenericEvent:
@ -788,6 +776,7 @@ class EventService(Closeable):
class GenericEvent:
""" """
def build(self) -> EventService.EventBuilder:
""" """
raise NotImplementedError
@ -811,7 +800,9 @@ class EventService(Closeable):
s = ""
self.body.write(s.encode())
def append(self, c: int = None, s: str = None) -> EventService.EventBuilder:
def append(self,
c: int = None,
s: str = None) -> EventService.EventBuilder:
"""
:param c: int: (Default value = None)
@ -889,23 +880,21 @@ class Session(Closeable, MessageListener, SubListener):
__mercury_client: MercuryClient
__receiver: typing.Union[Receiver, None] = None
__search: typing.Union[SearchManager, None]
__server_key = (
b"\xac\xe0F\x0b\xff\xc20\xaf\xf4k\xfe\xc3\xbf\xbf\x86="
b"\xa1\x91\xc6\xcc3l\x93\xa1O\xb3\xb0\x16\x12\xac\xacj"
b"\xf1\x80\xe7\xf6\x14\xd9B\x9d\xbe.4fC\xe3b\xd22z\x1a"
b"\r\x92;\xae\xdd\x14\x02\xb1\x81U\x05a\x04\xd5,\x96\xa4"
b"L\x1e\xcc\x02J\xd4\xb2\x0c\x00\x1f\x17\xed\xc2/\xc45"
b"!\xc8\xf0\xcb\xae\xd2\xad\xd7+\x0f\x9d\xb3\xc52\x1a*"
b"\xfeY\xf3Z\r\xach\xf1\xfab\x1e\xfb,\x8d\x0c\xb79-\x92"
b"G\xe3\xd75\x1am\xbd$\xc2\xae%[\x88\xff\xabs)\x8a\x0b"
b"\xcc\xcd\x0cXg1\x89\xe8\xbd4\x80xJ_\xc9k\x89\x9d\x95k"
b"\xfc\x86\xd7O3\xa6x\x17\x96\xc9\xc3-\r2\xa5\xab\xcd\x05'"
b"\xe2\xf7\x10\xa3\x96\x13\xc4/\x99\xc0'\xbf\xed\x04\x9c"
b"<'X\x04\xb6\xb2\x19\xf9\xc1/\x02\xe9Hc\xec\xa1\xb6B\xa0"
b"\x9dH%\xf8\xb3\x9d\xd0\xe8j\xf9HM\xa1\xc2\xba\x860B\xea"
b"\x9d\xb3\x08l\x19\x0eH\xb3\x9df\xeb\x00\x06\xa2Z\xee\xa1"
b"\x1b\x13\x87<\xd7\x19\xe6U\xbd"
)
__server_key = (b"\xac\xe0F\x0b\xff\xc20\xaf\xf4k\xfe\xc3\xbf\xbf\x86="
b"\xa1\x91\xc6\xcc3l\x93\xa1O\xb3\xb0\x16\x12\xac\xacj"
b"\xf1\x80\xe7\xf6\x14\xd9B\x9d\xbe.4fC\xe3b\xd22z\x1a"
b"\r\x92;\xae\xdd\x14\x02\xb1\x81U\x05a\x04\xd5,\x96\xa4"
b"L\x1e\xcc\x02J\xd4\xb2\x0c\x00\x1f\x17\xed\xc2/\xc45"
b"!\xc8\xf0\xcb\xae\xd2\xad\xd7+\x0f\x9d\xb3\xc52\x1a*"
b"\xfeY\xf3Z\r\xach\xf1\xfab\x1e\xfb,\x8d\x0c\xb79-\x92"
b"G\xe3\xd75\x1am\xbd$\xc2\xae%[\x88\xff\xabs)\x8a\x0b"
b"\xcc\xcd\x0cXg1\x89\xe8\xbd4\x80xJ_\xc9k\x89\x9d\x95k"
b"\xfc\x86\xd7O3\xa6x\x17\x96\xc9\xc3-\r2\xa5\xab\xcd\x05'"
b"\xe2\xf7\x10\xa3\x96\x13\xc4/\x99\xc0'\xbf\xed\x04\x9c"
b"<'X\x04\xb6\xb2\x19\xf9\xc1/\x02\xe9Hc\xec\xa1\xb6B\xa0"
b"\x9dH%\xf8\xb3\x9d\xd0\xe8j\xf9HM\xa1\xc2\xba\x860B\xea"
b"\x9d\xb3\x08l\x19\x0eH\xb3\x9df\xeb\x00\x06\xa2Z\xee\xa1"
b"\x1b\x13\x87<\xd7\x19\xe6U\xbd")
__stored_str: str = ""
__token_provider: typing.Union[TokenProvider, None]
__user_attributes = {}
@ -915,11 +904,8 @@ class Session(Closeable, MessageListener, SubListener):
self.connection = Session.ConnectionHolder.create(address, None)
self.__inner = inner
self.__keys = DiffieHellman()
self.logger.info(
"Created new session! device_id: {}, ap: {}".format(
inner.device_id, address
)
)
self.logger.info("Created new session! device_id: {}, ap: {}".format(
inner.device_id, address))
def api(self) -> ApiClient:
""" """
@ -942,7 +928,8 @@ class Session(Closeable, MessageListener, SubListener):
raise RuntimeError("Session isn't authenticated!")
return self.__audio_key_manager
def authenticate(self, credential: Authentication.LoginCredentials) -> None:
def authenticate(self,
credential: Authentication.LoginCredentials) -> None:
"""Log in to Spotify
:param credential: Spotify account login information
@ -965,13 +952,11 @@ class Session(Closeable, MessageListener, SubListener):
self.__auth_lock_bool = False
self.__auth_lock.notify_all()
self.dealer().connect()
self.logger.info(
"Authenticated as {}!".format(self.__ap_welcome.canonical_username)
)
self.logger.info("Authenticated as {}!".format(
self.__ap_welcome.canonical_username))
self.mercury().interested_in("spotify:user:attributes:update", self)
self.dealer().add_message_listener(
self, ["hm://connect-state/v1/connect/logout"]
)
self, ["hm://connect-state/v1/connect/logout"])
def cache(self) -> CacheManager:
""" """
@ -1000,9 +985,8 @@ class Session(Closeable, MessageListener, SubListener):
def close(self) -> None:
"""Close instance"""
self.logger.info(
"Closing session. device_id: {}".format(self.__inner.device_id)
)
self.logger.info("Closing session. device_id: {}".format(
self.__inner.device_id))
self.__closing = True
if self.__dealer_client is not None:
self.__dealer_client.close()
@ -1028,7 +1012,8 @@ class Session(Closeable, MessageListener, SubListener):
self.__ap_welcome = None
self.cipher_pair = None
self.__closed = True
self.logger.info("Closed session. device_id: {}".format(self.__inner.device_id))
self.logger.info("Closed session. device_id: {}".format(
self.__inner.device_id))
def connect(self) -> None:
"""Connect to the Spotify Server"""
@ -1038,12 +1023,12 @@ class Session(Closeable, MessageListener, SubListener):
client_hello_proto = Keyexchange.ClientHello(
build_info=Version.standard_build_info(),
client_nonce=nonce,
cryptosuites_supported=[Keyexchange.Cryptosuite.CRYPTO_SUITE_SHANNON],
cryptosuites_supported=[
Keyexchange.Cryptosuite.CRYPTO_SUITE_SHANNON
],
login_crypto_hello=Keyexchange.LoginCryptoHelloUnion(
diffie_hellman=Keyexchange.LoginCryptoDiffieHellmanHello(
gc=self.__keys.public_key_bytes(), server_keys_known=1
),
),
gc=self.__keys.public_key_bytes(), server_keys_known=1), ),
padding=b"\x1e",
)
client_hello_bytes = client_hello_proto.SerializeToString()
@ -1057,25 +1042,25 @@ class Session(Closeable, MessageListener, SubListener):
# Read APResponseMessage
ap_response_message_length = self.connection.read_int()
acc.write_int(ap_response_message_length)
ap_response_message_bytes = self.connection.read(ap_response_message_length - 4)
ap_response_message_bytes = self.connection.read(
ap_response_message_length - 4)
acc.write(ap_response_message_bytes)
ap_response_message_proto = Keyexchange.APResponseMessage()
ap_response_message_proto.ParseFromString(ap_response_message_bytes)
shared_key = util.int_to_bytes(
self.__keys.compute_shared_key(
ap_response_message_proto.challenge.login_crypto_challenge.diffie_hellman.gs
)
)
ap_response_message_proto.challenge.login_crypto_challenge.
diffie_hellman.gs))
# Check gs_signature
rsa = RSA.construct((int.from_bytes(self.__server_key, "big"), 65537))
pkcs1_v1_5 = PKCS1_v1_5.new(rsa)
sha1 = SHA1.new()
sha1.update(
ap_response_message_proto.challenge.login_crypto_challenge.diffie_hellman.gs
)
sha1.update(ap_response_message_proto.challenge.login_crypto_challenge.
diffie_hellman.gs)
if not pkcs1_v1_5.verify(
sha1,
ap_response_message_proto.challenge.login_crypto_challenge.diffie_hellman.gs_signature,
sha1,
ap_response_message_proto.challenge.login_crypto_challenge.
diffie_hellman.gs_signature,
):
raise RuntimeError("Failed signature check!")
# Solve challenge
@ -1093,14 +1078,11 @@ class Session(Closeable, MessageListener, SubListener):
crypto_response=Keyexchange.CryptoResponseUnion(),
login_crypto_response=Keyexchange.LoginCryptoResponseUnion(
diffie_hellman=Keyexchange.LoginCryptoDiffieHellmanResponse(
hmac=challenge
)
),
hmac=challenge)),
pow_response=Keyexchange.PoWResponseUnion(),
)
client_response_plaintext_bytes = (
client_response_plaintext_proto.SerializeToString()
)
client_response_plaintext_proto.SerializeToString())
self.connection.write_int(4 + len(client_response_plaintext_bytes))
self.connection.write(client_response_plaintext_bytes)
self.connection.flush()
@ -1108,7 +1090,8 @@ class Session(Closeable, MessageListener, SubListener):
self.connection.set_timeout(1)
scrap = self.connection.read(4)
if len(scrap) == 4:
payload = self.connection.read(struct.unpack(">i", scrap)[0] - 4)
payload = self.connection.read(
struct.unpack(">i", scrap)[0] - 4)
failed = Keyexchange.APResponseMessage()
failed.ParseFromString(payload)
raise RuntimeError(failed)
@ -1169,9 +1152,8 @@ class Session(Closeable, MessageListener, SubListener):
attributes_update.ParseFromString(resp.payload)
for pair in attributes_update.pairs_list:
self.__user_attributes[pair.key] = pair.value
self.logger.info(
"Updated user attribute: {} -> {}".format(pair.key, pair.value)
)
self.logger.info("Updated user attribute: {} -> {}".format(
pair.key, pair.value))
def get_user_attribute(self, key: str, fallback: str = None) -> str:
"""
@ -1180,11 +1162,8 @@ class Session(Closeable, MessageListener, SubListener):
:param fallback: str: (Default value = None)
"""
return (
self.__user_attributes.get(key)
if self.__user_attributes.get(key) is not None
else fallback
)
return (self.__user_attributes.get(key)
if self.__user_attributes.get(key) is not None else fallback)
def is_valid(self) -> bool:
""" """
@ -1200,7 +1179,8 @@ class Session(Closeable, MessageListener, SubListener):
raise RuntimeError("Session isn't authenticated!")
return self.__mercury_client
def on_message(self, uri: str, headers: typing.Dict[str, str], payload: bytes):
def on_message(self, uri: str, headers: typing.Dict[str, str],
payload: bytes):
"""
:param uri: str:
@ -1226,7 +1206,8 @@ class Session(Closeable, MessageListener, SubListener):
return
for i in range(len(product)):
self.__user_attributes[product[i].tag] = product[i].text
self.logger.debug("Parsed product info: {}".format(self.__user_attributes))
self.logger.debug("Parsed product info: {}".format(
self.__user_attributes))
def preferred_locale(self) -> str:
""" """
@ -1238,8 +1219,7 @@ class Session(Closeable, MessageListener, SubListener):
self.connection.close()
self.__receiver.stop()
self.connection = Session.ConnectionHolder.create(
ApResolver.get_random_accesspoint(), self.__inner.conf
)
ApResolver.get_random_accesspoint(), self.__inner.conf)
self.connect()
self.__authenticate_partial(
Authentication.LoginCredentials(
@ -1249,9 +1229,8 @@ class Session(Closeable, MessageListener, SubListener):
),
True,
)
self.logger.info(
"Re-authenticated as {}!".format(self.__ap_welcome.canonical_username)
)
self.logger.info("Re-authenticated as {}!".format(
self.__ap_welcome.canonical_username))
def reconnecting(self) -> bool:
""" """
@ -1298,9 +1277,9 @@ class Session(Closeable, MessageListener, SubListener):
""" """
return self.__stored_str
def __authenticate_partial(
self, credential: Authentication.LoginCredentials, remove_lock: bool
) -> None:
def __authenticate_partial(self,
credential: Authentication.LoginCredentials,
remove_lock: bool) -> None:
"""
Login to Spotify
Args:
@ -1319,8 +1298,8 @@ class Session(Closeable, MessageListener, SubListener):
version_string=Version.version_string(),
)
self.__send_unchecked(
Packet.Type.login, client_response_encrypted_proto.SerializeToString()
)
Packet.Type.login,
client_response_encrypted_proto.SerializeToString())
packet = self.cipher_pair.receive_encoded(self.connection)
if packet.is_cmd(Packet.Type.ap_welcome):
self.__ap_welcome = Authentication.APWelcome()
@ -1329,12 +1308,11 @@ class Session(Closeable, MessageListener, SubListener):
bytes0x0f = Random.get_random_bytes(0x14)
self.__send_unchecked(Packet.Type.unknown_0x0f, bytes0x0f)
preferred_locale = io.BytesIO()
preferred_locale.write(
b"\x00\x00\x10\x00\x02preferred-locale"
+ self.__inner.preferred_locale.encode()
)
preferred_locale.write(b"\x00\x00\x10\x00\x02preferred-locale" +
self.__inner.preferred_locale.encode())
preferred_locale.seek(0)
self.__send_unchecked(Packet.Type.preferred_locale, preferred_locale.read())
self.__send_unchecked(Packet.Type.preferred_locale,
preferred_locale.read())
if remove_lock:
with self.__auth_lock:
self.__auth_lock_bool = False
@ -1342,19 +1320,19 @@ class Session(Closeable, MessageListener, SubListener):
if self.__inner.conf.store_credentials:
reusable = self.__ap_welcome.reusable_auth_credentials
reusable_type = Authentication.AuthenticationType.Name(
self.__ap_welcome.reusable_auth_credentials_type
)
self.__ap_welcome.reusable_auth_credentials_type)
if self.__inner.conf.stored_credentials_file is None:
raise TypeError("The file path to be saved is not specified")
raise TypeError(
"The file path to be saved is not specified")
self.__stored_str = base64.b64encode(
json.dumps(
{
"username": self.__ap_welcome.canonical_username,
"credentials": base64.b64encode(reusable).decode(),
"type": reusable_type,
}
).encode()
).decode()
json.dumps({
"username":
self.__ap_welcome.canonical_username,
"credentials":
base64.b64encode(reusable).decode(),
"type":
reusable_type,
}).encode()).decode()
with open(self.__inner.conf.stored_credentials_file, "w") as f:
json.dump(
{
@ -1432,8 +1410,7 @@ class Session(Closeable, MessageListener, SubListener):
return self
def set_device_type(
self, device_type: Connect.DeviceType
) -> Session.AbsBuilder:
self, device_type: Connect.DeviceType) -> Session.AbsBuilder:
"""
:param device_type: Connect.DeviceType:
@ -1502,12 +1479,13 @@ class Session(Closeable, MessageListener, SubListener):
"""
if self.device_id is None:
raise TypeError("You must specify the device ID first.")
self.login_credentials = self.decrypt_blob(self.device_id, username, blob)
self.login_credentials = self.decrypt_blob(self.device_id,
username, blob)
return self
def decrypt_blob(
self, device_id: str, username: str, encrypted_blob: bytes
) -> Authentication.LoginCredentials:
self, device_id: str, username: str,
encrypted_blob: bytes) -> Authentication.LoginCredentials:
"""
:param device_id: str:
@ -1519,9 +1497,11 @@ class Session(Closeable, MessageListener, SubListener):
sha1 = SHA1.new()
sha1.update(device_id.encode())
secret = sha1.digest()
base_key = PBKDF2(
secret, username.encode(), 20, 0x100, hmac_hash_module=SHA1
)
base_key = PBKDF2(secret,
username.encode(),
20,
0x100,
hmac_hash_module=SHA1)
sha1 = SHA1.new()
sha1.update(base_key)
key = sha1.digest() + b"\x00\x00\x00\x14"
@ -1539,8 +1519,8 @@ class Session(Closeable, MessageListener, SubListener):
type_ = Authentication.AuthenticationType.Name(type_int)
if type_ is None:
raise IOError(
TypeError("Unknown AuthenticationType: {}".format(type_int))
)
TypeError(
"Unknown AuthenticationType: {}".format(type_int)))
blob.read(1)
l = self.read_blob_int(blob)
auth_data = blob.read(l)
@ -1578,7 +1558,8 @@ class Session(Closeable, MessageListener, SubListener):
else:
try:
self.login_credentials = Authentication.LoginCredentials(
typ=Authentication.AuthenticationType.Value(obj["type"]),
typ=Authentication.AuthenticationType.Value(
obj["type"]),
username=obj["username"],
auth_data=base64.b64decode(obj["credentials"]),
)
@ -1586,7 +1567,8 @@ class Session(Closeable, MessageListener, SubListener):
pass
return self
def stored_file(self, stored_credentials: str = None) -> Session.Builder:
def stored_file(self,
stored_credentials: str = None) -> Session.Builder:
"""Create credential from stored file
:param stored_credentials: str: (Default value = None)
@ -1604,7 +1586,8 @@ class Session(Closeable, MessageListener, SubListener):
else:
try:
self.login_credentials = Authentication.LoginCredentials(
typ=Authentication.AuthenticationType.Value(obj["type"]),
typ=Authentication.AuthenticationType.Value(
obj["type"]),
username=obj["username"],
auth_data=base64.b64decode(obj["credentials"]),
)
@ -1722,7 +1705,8 @@ class Session(Closeable, MessageListener, SubListener):
# Stored credentials
store_credentials: bool = True
stored_credentials_file: str = os.path.join(os.getcwd(), "credentials.json")
stored_credentials_file: str = os.path.join(
os.getcwd(), "credentials.json")
# Fetching
retry_on_chunk_error: bool = True
@ -1762,8 +1746,8 @@ class Session(Closeable, MessageListener, SubListener):
# return self
def set_cache_enabled(
self, cache_enabled: bool
) -> Session.Configuration.Builder:
self,
cache_enabled: bool) -> Session.Configuration.Builder:
"""Set cache_enabled
:param cache_enabled: bool:
@ -1773,7 +1757,8 @@ class Session(Closeable, MessageListener, SubListener):
self.cache_enabled = cache_enabled
return self
def set_cache_dir(self, cache_dir: str) -> Session.Configuration.Builder:
def set_cache_dir(self,
cache_dir: str) -> Session.Configuration.Builder:
"""Set cache_dir
:param cache_dir: str:
@ -1784,8 +1769,8 @@ class Session(Closeable, MessageListener, SubListener):
return self
def set_do_cache_clean_up(
self, do_cache_clean_up: bool
) -> Session.Configuration.Builder:
self,
do_cache_clean_up: bool) -> Session.Configuration.Builder:
"""Set do_cache_clean_up
:param do_cache_clean_up: bool:
@ -1796,8 +1781,8 @@ class Session(Closeable, MessageListener, SubListener):
return self
def set_store_credentials(
self, store_credentials: bool
) -> Session.Configuration.Builder:
self,
store_credentials: bool) -> Session.Configuration.Builder:
"""Set store_credentials
:param store_credentials: bool:
@ -1808,7 +1793,7 @@ class Session(Closeable, MessageListener, SubListener):
return self
def set_stored_credential_file(
self, stored_credential_file: str
self, stored_credential_file: str
) -> Session.Configuration.Builder:
"""Set stored_credential_file
@ -1820,7 +1805,7 @@ class Session(Closeable, MessageListener, SubListener):
return self
def set_retry_on_chunk_error(
self, retry_on_chunk_error: bool
self, retry_on_chunk_error: bool
) -> Session.Configuration.Builder:
"""Set retry_on_chunk_error
@ -1975,9 +1960,8 @@ class Session(Closeable, MessageListener, SubListener):
self.conf = conf
self.device_type = device_type
self.device_name = device_name
self.device_id = (
device_id if device_id is not None else util.random_hex_string(40)
)
self.device_id = (device_id if device_id is not None else
util.random_hex_string(40))
class Receiver:
""" """
@ -2004,21 +1988,18 @@ class Session(Closeable, MessageListener, SubListener):
cmd: bytes
try:
packet = self.__session.cipher_pair.receive_encoded(
self.__session.connection
)
self.__session.connection)
cmd = Packet.Type.parse(packet.cmd)
if cmd is None:
self.__session.logger.info(
"Skipping unknown command cmd: 0x{}, payload: {}".format(
util.bytes_to_hex(packet.cmd), packet.payload
)
)
"Skipping unknown command cmd: 0x{}, payload: {}".
format(util.bytes_to_hex(packet.cmd),
packet.payload))
continue
except (RuntimeError, ConnectionResetError) as ex:
if self.__running:
self.__session.logger.fatal(
"Failed reading packet! {}".format(ex)
)
"Failed reading packet! {}".format(ex))
self.__session.reconnect()
break
if not self.__running:
@ -2026,19 +2007,16 @@ class Session(Closeable, MessageListener, SubListener):
if cmd == Packet.Type.ping:
if self.__session.scheduled_reconnect is not None:
self.__session.scheduler.cancel(
self.__session.scheduled_reconnect
)
self.__session.scheduled_reconnect)
def anonymous():
""" """
self.__session.logger.warning(
"Socket timed out. Reconnecting..."
)
"Socket timed out. Reconnecting...")
self.__session.reconnect()
self.__session.scheduled_reconnect = self.__session.scheduler.enter(
2 * 60 + 5, 1, anonymous
)
2 * 60 + 5, 1, anonymous)
self.__session.send(Packet.Type.pong, packet.payload)
elif cmd == Packet.Type.pong_ack:
continue
@ -2046,49 +2024,47 @@ class Session(Closeable, MessageListener, SubListener):
self.__session.__country_code = packet.payload.decode()
self.__session.logger.info(
"Received country_code: {}".format(
self.__session.__country_code
)
)
self.__session.__country_code))
elif cmd == Packet.Type.license_version:
license_version = io.BytesIO(packet.payload)
license_id = struct.unpack(">h", license_version.read(2))[0]
license_id = struct.unpack(">h",
license_version.read(2))[0]
if license_id != 0:
buffer = license_version.read()
self.__session.logger.info(
"Received license_version: {}, {}".format(
license_id, buffer.decode()
)
)
license_id, buffer.decode()))
else:
self.__session.logger.info(
"Received license_version: {}".format(license_id)
)
"Received license_version: {}".format(license_id))
elif cmd == Packet.Type.unknown_0x10:
self.__session.logger.debug(
"Received 0x10: {}".format(util.bytes_to_hex(packet.payload))
)
self.__session.logger.debug("Received 0x10: {}".format(
util.bytes_to_hex(packet.payload)))
elif cmd in [
Packet.Type.mercury_sub,
Packet.Type.mercury_unsub,
Packet.Type.mercury_event,
Packet.Type.mercury_req,
Packet.Type.mercury_sub,
Packet.Type.mercury_unsub,
Packet.Type.mercury_event,
Packet.Type.mercury_req,
]:
self.__session.mercury().dispatch(packet)
elif cmd in [Packet.Type.aes_key, Packet.Type.aes_key_error]:
self.__session.audio_key().dispatch(packet)
elif cmd in [Packet.Type.channel_error, Packet.Type.stream_chunk_res]:
elif cmd in [
Packet.Type.channel_error, Packet.Type.stream_chunk_res
]:
self.__session.channel().dispatch(packet)
elif cmd == Packet.Type.product_info:
self.__session.parse_product_info(packet.payload)
else:
self.__session.logger.info(
"Skipping {}".format(util.bytes_to_hex(cmd))
)
self.__session.logger.info("Skipping {}".format(
util.bytes_to_hex(cmd)))
class SpotifyAuthenticationException(Exception):
""" """
def __init__(self, login_failed: Keyexchange.APLoginFailed):
super().__init__(Keyexchange.ErrorCode.Name(login_failed.error_code))
super().__init__(
Keyexchange.ErrorCode.Name(login_failed.error_code))
class SearchManager:
@ -2112,17 +2088,15 @@ class SearchManager:
if request.get_locale() == "":
request.set_locale(self.__session.preferred_locale())
response = self.__session.mercury().send_sync(
RawMercuryRequest.new_builder()
.set_method("GET")
.set_uri(request.build_url())
.build()
)
RawMercuryRequest.new_builder().set_method("GET").set_uri(
request.build_url()).build())
if response.status_code != 200:
raise SearchManager.SearchException(response.status_code)
return json.loads(response.payload)
class SearchException(Exception):
""" """
def __init__(self, status_code: int):
super().__init__("Search failed with code {}.".format(status_code))
@ -2195,7 +2169,8 @@ class SearchManager:
self.__country = country
return self
def set_image_size(self, image_size: str) -> SearchManager.SearchRequest:
def set_image_size(self,
image_size: str) -> SearchManager.SearchRequest:
"""
:param image_size: str:
@ -2243,8 +2218,7 @@ class TokenProvider:
self._session = session
def find_token_with_all_scopes(
self, scopes: typing.List[str]
) -> typing.Union[StoredToken, None]:
self, scopes: typing.List[str]) -> typing.Union[StoredToken, None]:
"""
:param scopes: typing.List[str]:
@ -2279,19 +2253,15 @@ class TokenProvider:
else:
return token
self.logger.debug(
"Token expired or not suitable, requesting again. scopes: {}, old_token: {}".format(
scopes, token
)
)
"Token expired or not suitable, requesting again. scopes: {}, old_token: {}"
.format(scopes, token))
response = self._session.mercury().send_sync_json(
MercuryRequests.request_token(self._session.device_id(), ",".join(scopes))
)
MercuryRequests.request_token(self._session.device_id(),
",".join(scopes)))
token = TokenProvider.StoredToken(response)
self.logger.debug(
"Updated token successfully! scopes: {}, new_token: {}".format(
scopes, token
)
)
scopes, token))
self.__tokens.append(token)
return token
@ -2310,9 +2280,9 @@ class TokenProvider:
def expired(self) -> bool:
""" """
return self.timestamp + (
self.expires_in - TokenProvider.token_expire_threshold
) * 1000 < int(time.time_ns() / 1000)
return self.timestamp + (self.expires_in - TokenProvider.
token_expire_threshold) * 1000 < int(
time.time_ns() / 1000)
def has_scope(self, scope: str) -> bool:
"""