From 04165151d72067706ede0818cf26b4011d7167ab Mon Sep 17 00:00:00 2001 From: "d.rathmer" Date: Tue, 5 Sep 2023 09:20:43 +0200 Subject: [PATCH 1/8] fix(apresolve): Better semantics on error --- librespot/core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index 1ba9564..afc92b8 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -235,9 +235,7 @@ class ApResolver: service_type)) if response.status_code != 200: if response.status_code == 502: - raise RuntimeError("ApResolve request failed: maybe rate limited?") - else: - raise RuntimeError("ApResolve request failed") + raise RuntimeError(f"ApResolve request failed with the following return value: {response.content}. Servers might be down!") return response.json() @staticmethod From 20501cdd58e3b2914d9a1cab7c390ce36dbc9e78 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:04 +0000 Subject: [PATCH 2/8] Restyled by autopep8 --- librespot/core.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index afc92b8..4639f00 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -54,7 +54,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 @@ -194,11 +195,11 @@ class ApiClient(Closeable): ) resp = requests.post("https://clienttoken.spotify.com/v1/clienttoken", - proto_req.SerializeToString(), - headers={ - "Accept": "application/x-protobuf", - "Content-Encoding": "", - }) + proto_req.SerializeToString(), + headers={ + "Accept": "application/x-protobuf", + "Content-Encoding": "", + }) ApiClient.StatusCodeException.check_status(resp) @@ -235,7 +236,8 @@ class ApResolver: service_type)) if response.status_code != 200: if response.status_code == 502: - raise RuntimeError(f"ApResolve request failed with the following return value: {response.content}. Servers might be down!") + raise RuntimeError( + f"ApResolve request failed with the following return value: {response.content}. Servers might be down!") return response.json() @staticmethod From 4a6b43ef3375ddbbaa0f9d8f7edff67c00f4facd Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:07 +0000 Subject: [PATCH 3/8] Restyled by black --- librespot/core.py | 527 ++++++++++++++++++++++++++-------------------- 1 file changed, 296 insertions(+), 231 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index 4639f00..ebe01a4 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -12,7 +12,15 @@ from librespot.cache import CacheManager from librespot.crypto import CipherPair, DiffieHellman, Packet from librespot.mercury import MercuryClient, MercuryRequests, RawMercuryRequest from librespot.metadata import AlbumId, ArtistId, EpisodeId, ShowId, TrackId, PlaylistId -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, Playlist4External_pb2 as Playlist4External +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, + Playlist4External_pb2 as Playlist4External, +) from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate from librespot.structure import Closeable, MessageListener, RequestListener, SubListener import base64 @@ -48,14 +56,18 @@ class ApiClient(Closeable): self.__base_url = "https://{}".format(ApResolver.get_random_spclient()) def build_request( - self, method: str, suffix: str, - headers: typing.Union[None, typing.Dict[str, str]], - body: typing.Union[None, bytes]) -> requests.PreparedRequest: + self, + method: str, + suffix: str, + headers: typing.Union[None, typing.Dict[str, str]], + body: typing.Union[None, bytes], + ) -> requests.PreparedRequest: 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 @@ -64,41 +76,53 @@ 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 - def send(self, method: str, suffix: str, - headers: typing.Union[None, typing.Dict[str, str]], - body: typing.Union[None, bytes]) -> requests.Response: + def send( + self, + method: str, + suffix: str, + headers: typing.Union[None, typing.Dict[str, str]], + body: typing.Union[None, bytes], + ) -> requests.Response: 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: response = self.send( "PUT", "/connect-state/v1/devices/{}".format(self.__session.device_id()), { "Content-Type": "application/protobuf", - "X-Spotify-Connection-Id": connection_id + "X-Spotify-Connection-Id": connection_id, }, proto.SerializeToString(), ) 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: - 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: @@ -108,9 +132,9 @@ class ApiClient(Closeable): return proto def get_metadata_4_episode(self, episode: EpisodeId) -> Metadata.Episode: - 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: @@ -120,9 +144,9 @@ class ApiClient(Closeable): return proto def get_metadata_4_album(self, album: AlbumId) -> Metadata.Album: - 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 @@ -133,9 +157,9 @@ class ApiClient(Closeable): return proto def get_metadata_4_artist(self, artist: ArtistId) -> Metadata.Artist: - 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: @@ -145,9 +169,9 @@ class ApiClient(Closeable): return proto def get_metadata_4_show(self, show: ShowId) -> Metadata.Show: - 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: @@ -157,9 +181,9 @@ class ApiClient(Closeable): return proto def get_playlist(self, _id: PlaylistId) -> Playlist4External.SelectedListContent: - 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: @@ -194,12 +218,14 @@ class ApiClient(Closeable): ), ) - resp = requests.post("https://clienttoken.spotify.com/v1/clienttoken", - proto_req.SerializeToString(), - headers={ - "Accept": "application/x-protobuf", - "Content-Encoding": "", - }) + resp = requests.post( + "https://clienttoken.spotify.com/v1/clienttoken", + proto_req.SerializeToString(), + headers={ + "Accept": "application/x-protobuf", + "Content-Encoding": "", + }, + ) ApiClient.StatusCodeException.check_status(resp) @@ -232,12 +258,12 @@ 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( - f"ApResolve request failed with the following return value: {response.content}. Servers might be down!") + f"ApResolve request failed with the following return value: {response.content}. Servers might be down!" + ) return response.json() @staticmethod @@ -298,12 +324,12 @@ 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: 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() @@ -311,7 +337,8 @@ 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() @@ -320,9 +347,13 @@ class DealerClient(Closeable): def connect(self) -> None: self.__connection = DealerClient.ConnectionHolder( - self.__session, self, "wss://{}/?access_token={}".format( + self.__session, + self, + "wss://{}/?access_token={}".format( ApResolver.get_random_dealer(), - self.__session.tokens().get("playlist-read"))) + self.__session.tokens().get("playlist-read"), + ), + ) def connection_invalided(self) -> None: self.__connection = None @@ -332,8 +363,7 @@ 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: uri = obj.get("uri") @@ -380,8 +410,10 @@ 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: @@ -394,8 +426,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: @@ -435,8 +467,7 @@ 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 @@ -453,7 +484,8 @@ 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): @@ -469,16 +501,18 @@ 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): 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(): self.send_ping() @@ -496,20 +530,20 @@ 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): - self.__ws.send("{\"type\":\"ping\"}") + self.__ws.send('{"type":"ping"}') def send_reply(self, key: str, 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): UNKNOWN_SEND_COMMAND_RESULT = 0 @@ -534,19 +568,21 @@ 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]): if type(event_or_builder) is EventService.GenericEvent: builder = event_or_builder.build() elif type(event_or_builder) is EventService.EventBuilder: @@ -595,9 +631,7 @@ 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: if c is None and s is None or c is not None and s is not None: raise TypeError() if c is not None: @@ -661,21 +695,23 @@ 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 = {} @@ -685,8 +721,11 @@ 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: self.__wait_auth_lock() @@ -706,8 +745,7 @@ 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 Args: @@ -729,11 +767,13 @@ 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: self.__wait_auth_lock() @@ -760,8 +800,9 @@ class Session(Closeable, MessageListener, SubListener): """ 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() @@ -787,8 +828,7 @@ 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: """ @@ -800,12 +840,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() @@ -819,24 +859,26 @@ 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 buffer = io.BytesIO() @@ -853,10 +895,13 @@ 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_bytes = ( + client_response_plaintext_proto.SerializeToString() ) self.connection.write_int(4 + len(client_response_plaintext_bytes)) self.connection.write(client_response_plaintext_bytes) @@ -865,8 +910,7 @@ 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) @@ -912,12 +956,16 @@ 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: - 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: if self.__closed: @@ -931,8 +979,7 @@ 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): if uri == "hm://connect-state/v1/connect/logout": self.close() @@ -950,8 +997,7 @@ 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: return self.__inner.preferred_locale @@ -964,7 +1010,8 @@ 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( @@ -974,8 +1021,9 @@ 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: return not self.__closing and not self.__closed and self.connection is None @@ -1015,9 +1063,9 @@ class Session(Closeable, MessageListener, SubListener): def stored(self): 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: @@ -1036,8 +1084,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() @@ -1046,11 +1094,12 @@ 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 @@ -1058,22 +1107,28 @@ 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") - self.__stored_str = base64.b64encode(json.dumps({ - "username": self.__ap_welcome.canonical_username, - "credentials": base64.b64encode(reusable).decode(), - "type": reusable_type, - }).encode()).decode() + 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() with open(self.__inner.conf.stored_credentials_file, "w") as f: json.dump( { "username": self.__ap_welcome.canonical_username, "credentials": base64.b64encode(reusable).decode(), "type": reusable_type, - }, f) + }, + f, + ) elif packet.is_cmd(Packet.Type.auth_failure): ap_login_failed = Keyexchange.APLoginFailed() @@ -1126,7 +1181,8 @@ 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: self.device_type = device_type return self @@ -1178,22 +1234,19 @@ class Session(Closeable, MessageListener, SubListener): def blob(self, username: str, blob: bytes) -> Session.Builder: 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: encrypted_blob = base64.b64decode(encrypted_blob) 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" @@ -1211,8 +1264,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) @@ -1227,7 +1280,7 @@ class Session(Closeable, MessageListener, SubListener): if (int(lo[0]) & 0x80) == 0: return int(lo[0]) hi = buffer.read(1) - return int(lo[0]) & 0x7f | int(hi[0]) << 7 + return int(lo[0]) & 0x7F | int(hi[0]) << 7 def stored(self, stored_credentials_str: str): """ @@ -1246,8 +1299,7 @@ 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"]), ) @@ -1255,8 +1307,7 @@ 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 Args: @@ -1275,8 +1326,7 @@ 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"]), ) @@ -1391,8 +1441,7 @@ 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 @@ -1432,8 +1481,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 Args: @@ -1444,8 +1493,7 @@ 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 Args: @@ -1457,8 +1505,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 Args: @@ -1470,8 +1518,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 Args: @@ -1483,7 +1531,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 @@ -1496,7 +1544,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 @@ -1539,8 +1587,7 @@ class Session(Closeable, MessageListener, SubListener): self.__socket = sock @staticmethod - def create(address: str, conf) \ - -> Session.ConnectionHolder: + def create(address: str, conf) -> Session.ConnectionHolder: """ Create the ConnectionHolder instance Args: @@ -1649,8 +1696,9 @@ 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: __session: Session @@ -1677,18 +1725,21 @@ 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: @@ -1696,15 +1747,18 @@ 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 @@ -1712,43 +1766,48 @@ 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: @@ -1766,8 +1825,11 @@ 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) @@ -1827,8 +1889,7 @@ 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: self.__image_size = image_size return self @@ -1855,7 +1916,8 @@ 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]: for token in self.__tokens: if token.has_scopes(scopes): return token @@ -1875,15 +1937,19 @@ 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 @@ -1900,10 +1966,9 @@ class TokenProvider: self.scopes = obj["scope"] 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: for s in self.scopes: From 4241ecb3ab548fcfa1706efc43360c799cbb1598 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:08 +0000 Subject: [PATCH 4/8] Restyled by isort --- librespot/core.py | 55 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index ebe01a4..0ae7b3a 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -1,32 +1,8 @@ from __future__ import annotations -from Cryptodome import Random -from Cryptodome.Cipher import AES -from Cryptodome.Hash import HMAC, SHA1 -from Cryptodome.Protocol.KDF import PBKDF2 -from Cryptodome.PublicKey import RSA -from Cryptodome.Signature import PKCS1_v1_5 -from librespot import util, Version -from librespot.audio import AudioKeyManager, CdnManager, PlayableContentFeeder -from librespot.audio.storage import ChannelManager -from librespot.cache import CacheManager -from librespot.crypto import CipherPair, DiffieHellman, Packet -from librespot.mercury import MercuryClient, MercuryRequests, RawMercuryRequest -from librespot.metadata import AlbumId, ArtistId, EpisodeId, ShowId, TrackId, PlaylistId -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, - Playlist4External_pb2 as Playlist4External, -) -from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate -from librespot.structure import Closeable, MessageListener, RequestListener, SubListener + import base64 import binascii import concurrent.futures -import defusedxml.ElementTree import enum import gzip import io @@ -34,7 +10,6 @@ import json import logging import os import random -import requests import sched import socket import struct @@ -42,7 +17,35 @@ import threading import time import typing import urllib.parse + +import defusedxml.ElementTree +import requests import websocket +from Cryptodome import Random +from Cryptodome.Cipher import AES +from Cryptodome.Hash import HMAC, SHA1 +from Cryptodome.Protocol.KDF import PBKDF2 +from Cryptodome.PublicKey import RSA +from Cryptodome.Signature import PKCS1_v1_5 + +from librespot import Version, util +from librespot.audio import AudioKeyManager, CdnManager, PlayableContentFeeder +from librespot.audio.storage import ChannelManager +from librespot.cache import CacheManager +from librespot.crypto import CipherPair, DiffieHellman, Packet +from librespot.mercury import MercuryClient, MercuryRequests, RawMercuryRequest +from librespot.metadata import (AlbumId, ArtistId, EpisodeId, PlaylistId, + ShowId, TrackId) +from librespot.proto import Authentication_pb2 as Authentication +from librespot.proto import ClientToken_pb2 as ClientToken +from librespot.proto import Connect_pb2 as Connect +from librespot.proto import Connectivity_pb2 as Connectivity +from librespot.proto import Keyexchange_pb2 as Keyexchange +from librespot.proto import Metadata_pb2 as Metadata +from librespot.proto import Playlist4External_pb2 as Playlist4External +from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate +from librespot.structure import (Closeable, MessageListener, RequestListener, + SubListener) class ApiClient(Closeable): From 616183a6cadc88a1f692db53d5dda06b09349ebf Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:09 +0000 Subject: [PATCH 5/8] Restyled by pyment --- librespot/core.py | 679 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 508 insertions(+), 171 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index 0ae7b3a..120f438 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -49,6 +49,7 @@ from librespot.structure import (Closeable, MessageListener, RequestListener, class ApiClient(Closeable): + """ """ logger = logging.getLogger("Librespot:ApiClient") __base_url: str __client_token_str: str = None @@ -65,6 +66,17 @@ class ApiClient(Closeable): headers: typing.Union[None, typing.Dict[str, str]], body: typing.Union[None, bytes], ) -> requests.PreparedRequest: + """ + + :param method: str: + :param suffix: str: + :param headers: typing.Union[None: + :param typing.Dict[str: + :param str]]: + :param body: typing.Union[None: + :param bytes]: + + """ if self.__client_token_str is None: resp = self.__client_token() self.__client_token_str = resp.granted_token.token @@ -92,6 +104,17 @@ class ApiClient(Closeable): headers: typing.Union[None, typing.Dict[str, str]], body: typing.Union[None, bytes], ) -> requests.Response: + """ + + :param method: str: + :param suffix: str: + :param headers: typing.Union[None: + :param typing.Dict[str: + :param str]]: + :param body: typing.Union[None: + :param bytes]: + + """ response = self.__session.client().send( self.build_request(method, suffix, headers, body) ) @@ -100,6 +123,12 @@ class ApiClient(Closeable): def put_connect_state( self, connection_id: str, proto: Connect.PutStateRequest ) -> None: + """ + + :param connection_id: str: + :param proto: Connect.PutStateRequest: + + """ response = self.send( "PUT", "/connect-state/v1/devices/{}".format(self.__session.device_id()), @@ -123,6 +152,11 @@ class ApiClient(Closeable): ) def get_metadata_4_track(self, track: TrackId) -> Metadata.Track: + """ + + :param track: TrackId: + + """ response = self.send( "GET", "/metadata/4/track/{}".format(track.hex_id()), None, None ) @@ -135,6 +169,11 @@ class ApiClient(Closeable): return proto def get_metadata_4_episode(self, episode: EpisodeId) -> Metadata.Episode: + """ + + :param episode: EpisodeId: + + """ response = self.send( "GET", "/metadata/4/episode/{}".format(episode.hex_id()), None, None ) @@ -147,6 +186,11 @@ class ApiClient(Closeable): return proto def get_metadata_4_album(self, album: AlbumId) -> Metadata.Album: + """ + + :param album: AlbumId: + + """ response = self.send( "GET", "/metadata/4/album/{}".format(album.hex_id()), None, None ) @@ -160,6 +204,11 @@ class ApiClient(Closeable): return proto def get_metadata_4_artist(self, artist: ArtistId) -> Metadata.Artist: + """ + + :param artist: ArtistId: + + """ response = self.send( "GET", "/metadata/4/artist/{}".format(artist.hex_id()), None, None ) @@ -172,6 +221,11 @@ class ApiClient(Closeable): return proto def get_metadata_4_show(self, show: ShowId) -> Metadata.Show: + """ + + :param show: ShowId: + + """ response = self.send( "GET", "/metadata/4/show/{}".format(show.hex_id()), None, None ) @@ -184,6 +238,11 @@ class ApiClient(Closeable): return proto def get_playlist(self, _id: PlaylistId) -> Playlist4External.SelectedListContent: + """ + + :param _id: PlaylistId: + + """ response = self.send( "GET", "/playlist/v2/playlist/{}".format(_id.id()), None, None ) @@ -196,6 +255,11 @@ class ApiClient(Closeable): return proto def set_client_token(self, client_token): + """ + + :param client_token: + + """ self.__client_token_str = client_token def __client_token(self): @@ -237,6 +301,7 @@ class ApiClient(Closeable): return proto_resp class StatusCodeException(IOError): + """ """ code: int def __init__(self, response: requests.Response): @@ -245,21 +310,26 @@ class ApiClient(Closeable): @staticmethod def check_status(response: requests.Response) -> None: + """ + + :param response: requests.Response: + + """ if response.status_code != 200: raise ApiClient.StatusCodeException(response) class ApResolver: + """ """ base_url = "https://apresolve.spotify.com/" @staticmethod def request(service_type: str) -> typing.Any: - """ - Gets the specified ApResolve - Args: - service_type: Unique ID for service name - Returns: - The resulting object will be returned + """Gets the specified ApResolve + + :param service_type: str: + :returns: The resulting object will be returned + """ response = requests.get("{}?type={}".format(ApResolver.base_url, service_type)) if response.status_code != 200: @@ -271,12 +341,11 @@ class ApResolver: @staticmethod def get_random_of(service_type: str) -> str: - """ - Gets the specified random ApResolve url - Args: - service_type: Unique ID for service name - Returns: - A random ApResolve url will be returned + """Gets the specified random ApResolve url + + :param service_type: str: + :returns: A random ApResolve url will be returned + """ pool = ApResolver.request(service_type) urls = pool.get(service_type) @@ -286,33 +355,37 @@ class ApResolver: @staticmethod def get_random_dealer() -> str: - """ - Get dealer endpoint url - Returns: - dealer endpoint url + """Get dealer endpoint url + + + :returns: dealer endpoint url + """ return ApResolver.get_random_of("dealer") @staticmethod def get_random_spclient() -> str: - """ - Get spclient endpoint url - Returns: - spclient endpoint url + """Get spclient endpoint url + + + :returns: spclient endpoint url + """ return ApResolver.get_random_of("spclient") @staticmethod def get_random_accesspoint() -> str: - """ - Get accesspoint endpoint url - Returns: - accesspoint endpoint url + """Get accesspoint endpoint url + + + :returns: accesspoint endpoint url + """ return ApResolver.get_random_of("accesspoint") class DealerClient(Closeable): + """ """ logger = logging.getLogger("Librespot:DealerClient") __connection: typing.Union[ConnectionHolder, None] __last_scheduled_reconnection: typing.Union[sched.Event, None] @@ -328,6 +401,12 @@ class DealerClient(Closeable): self.__session = session def add_message_listener(self, listener: MessageListener, uris: list[str]) -> None: + """ + + :param listener: MessageListener: + :param uris: list[str]: + + """ with self.__message_listeners_lock: if listener in self.__message_listeners: raise TypeError( @@ -337,6 +416,12 @@ class DealerClient(Closeable): self.__message_listeners_lock.notify_all() def add_request_listener(self, listener: RequestListener, uri: str): + """ + + :param listener: RequestListener: + :param uri: str: + + """ with self.__request_listeners_lock: if uri in self.__request_listeners: raise TypeError( @@ -346,9 +431,11 @@ class DealerClient(Closeable): self.__request_listeners_lock.notify_all() def close(self) -> None: + """ """ self.__worker.shutdown() def connect(self) -> None: + """ """ self.__connection = DealerClient.ConnectionHolder( self.__session, self, @@ -359,16 +446,23 @@ class DealerClient(Closeable): ) def connection_invalided(self) -> None: + """ """ self.__connection = None self.logger.debug("Scheduled reconnection attempt in 10 seconds...") def anonymous(): + """ """ self.__last_scheduled_reconnection = None self.connect() self.__last_scheduled_reconnection = self.__scheduler.enter(10, 1, anonymous) def handle_message(self, obj: typing.Any) -> None: + """ + + :param obj: typing.Any: + + """ uri = obj.get("uri") headers = self.__get_headers(obj) payloads = obj.get("payloads") @@ -394,6 +488,7 @@ class DealerClient(Closeable): interesting = True def anonymous(): + """ """ listener.on_message(uri, headers, decoded_payloads) self.__worker.submit(anonymous) @@ -402,6 +497,11 @@ class DealerClient(Closeable): self.logger.debug("Couldn't dispatch message: {}".format(uri)) def handle_request(self, obj: typing.Any) -> None: + """ + + :param obj: typing.Any: + + """ mid = obj.get("message_ident") key = obj.get("key") headers = self.__get_headers(obj) @@ -425,6 +525,7 @@ class DealerClient(Closeable): interesting = True def anonymous(): + """ """ result = listener.on_request(mid, pid, sender, command) if self.__connection is not None: self.__connection.send_reply(key, result) @@ -437,10 +538,20 @@ class DealerClient(Closeable): self.logger.debug("Couldn't dispatch request: {}".format(mid)) def remove_message_listener(self, listener: MessageListener) -> None: + """ + + :param listener: MessageListener: + + """ with self.__message_listeners_lock: self.__message_listeners.pop(listener) def remove_request_listener(self, listener: RequestListener) -> None: + """ + + :param listener: RequestListener: + + """ with self.__request_listeners_lock: request_listeners = {} for key, value in self.__request_listeners.items(): @@ -449,6 +560,7 @@ class DealerClient(Closeable): self.__request_listeners = request_listeners def wait_for_listener(self) -> None: + """ """ with self.__message_listeners_lock: if self.__message_listeners == {}: return @@ -461,6 +573,7 @@ class DealerClient(Closeable): return headers class ConnectionHolder(Closeable): + """ """ __closed = False __dealer_client: DealerClient __last_scheduled_ping: sched.Event @@ -477,6 +590,7 @@ class DealerClient(Closeable): self.__ws = websocket.WebSocketApp(url) def close(self): + """ """ if not self.__closed: self.__ws.close() self.__closed = True @@ -484,6 +598,12 @@ class DealerClient(Closeable): self.__scheduler.cancel(self.__last_scheduled_ping) def on_failure(self, ws: websocket.WebSocketApp, error): + """ + + :param ws: websocket.WebSocketApp: + :param error: + + """ if self.__closed: return self.__dealer_client.logger.warning( @@ -492,6 +612,12 @@ class DealerClient(Closeable): self.close() def on_message(self, ws: websocket.WebSocketApp, text: str): + """ + + :param ws: websocket.WebSocketApp: + :param text: str: + + """ obj = json.loads(text) self.__dealer_client.wait_for_listener() typ = MessageType.parse(obj.get("type")) @@ -507,6 +633,11 @@ class DealerClient(Closeable): raise RuntimeError("Unknown message type for {}".format(typ.value)) def on_open(self, ws: websocket.WebSocketApp): + """ + + :param ws: websocket.WebSocketApp: + + """ if self.__closed: self.__dealer_client.logger.fatal( "I wonder what happened here... Terminating. [closed: {}]".format( @@ -518,10 +649,12 @@ class DealerClient(Closeable): ) def anonymous(): + """ """ self.send_ping() self.__received_pong = False def anonymous2(): + """ """ if self.__last_scheduled_ping is None: return if not self.__received_pong: @@ -538,9 +671,16 @@ class DealerClient(Closeable): self.__last_scheduled_ping = self.__scheduler.enter(30, 1, anonymous) def send_ping(self): + """ """ self.__ws.send('{"type":"ping"}') def send_reply(self, key: str, result: DealerClient.RequestResult): + """ + + :param key: str: + :param result: DealerClient.RequestResult: + + """ success = ( "true" if result == DealerClient.RequestResult.SUCCESS else "false" ) @@ -549,6 +689,7 @@ class DealerClient(Closeable): ) class RequestResult(enum.Enum): + """ """ UNKNOWN_SEND_COMMAND_RESULT = 0 SUCCESS = 1 DEVICE_NOT_FOUND = 2 @@ -560,6 +701,7 @@ class DealerClient(Closeable): class EventService(Closeable): + """ """ logger = logging.getLogger("Librespot:EventService") __session: Session __worker = concurrent.futures.ThreadPoolExecutor() @@ -586,6 +728,12 @@ class EventService(Closeable): self.logger.error("Failed sending event: {} {}".format(event_builder, ex)) def send_event(self, event_or_builder: typing.Union[GenericEvent, EventBuilder]): + """ + + :param event_or_builder: typing.Union[GenericEvent: + :param EventBuilder]: + + """ if type(event_or_builder) is EventService.GenericEvent: builder = event_or_builder.build() elif type(event_or_builder) is EventService.EventBuilder: @@ -595,13 +743,20 @@ class EventService(Closeable): self.__worker.submit(lambda: self.__worker_callback(builder)) def language(self, lang: str): + """ + + :param lang: str: + + """ event = EventService.EventBuilder(EventService.Type.LANGUAGE) event.append(s=lang) def close(self): + """ """ self.__worker.shutdown() class Type(enum.Enum): + """ """ LANGUAGE = ("812", 1) FETCHED_FILE_ID = ("274", 3) NEW_SESSION_ID = ("557", 3) @@ -618,10 +773,13 @@ class EventService(Closeable): self.unknown = unknown class GenericEvent: + """ """ def build(self) -> EventService.EventBuilder: + """ """ raise NotImplementedError class EventBuilder: + """ """ body: io.BytesIO def __init__(self, event_type: EventService.Type): @@ -630,11 +788,22 @@ class EventService(Closeable): self.append(event_type.value[1]) def append_no_delimiter(self, s: str = None) -> None: + """ + + :param s: str: (Default value = None) + + """ if s is None: s = "" self.body.write(s.encode()) def append(self, c: int = None, s: str = None) -> EventService.EventBuilder: + """ + + :param c: int: (Default value = None) + :param s: str: (Default value = None) + + """ if c is None and s is None or c is not None and s is not None: raise TypeError() if c is not None: @@ -647,6 +816,7 @@ class EventService(Closeable): return self def to_array(self) -> bytes: + """ """ pos = self.body.tell() self.body.seek(0) data = self.body.read() @@ -655,6 +825,7 @@ class EventService(Closeable): class MessageType(enum.Enum): + """ """ MESSAGE = "message" PING = "ping" PONG = "pong" @@ -662,6 +833,11 @@ class MessageType(enum.Enum): @staticmethod def parse(_typ: str): + """ + + :param _typ: str: + + """ if _typ == MessageType.MESSAGE.value: return MessageType.MESSAGE if _typ == MessageType.PING.value: @@ -674,6 +850,7 @@ class MessageType(enum.Enum): class Session(Closeable, MessageListener, SubListener): + """ """ cipher_pair: typing.Union[CipherPair, None] country_code: str = "EN" connection: typing.Union[ConnectionHolder, None] @@ -731,28 +908,32 @@ class Session(Closeable, MessageListener, SubListener): ) def api(self) -> ApiClient: + """ """ self.__wait_auth_lock() if self.__api is None: raise RuntimeError("Session isn't authenticated!") return self.__api def ap_welcome(self): + """ """ self.__wait_auth_lock() if self.__ap_welcome is None: raise RuntimeError("Session isn't authenticated!") return self.__ap_welcome def audio_key(self) -> AudioKeyManager: + """ """ self.__wait_auth_lock() if self.__audio_key_manager is None: raise RuntimeError("Session isn't authenticated!") return self.__audio_key_manager def authenticate(self, credential: Authentication.LoginCredentials) -> None: - """ - Log in to Spotify - Args: - credential: Spotify account login information + """Log in to Spotify + + :param credential: Spotify account login information + :param credential: Authentication.LoginCredentials: + """ self.__authenticate_partial(credential, False) with self.__auth_lock: @@ -779,30 +960,32 @@ class Session(Closeable, MessageListener, SubListener): ) def cache(self) -> CacheManager: + """ """ self.__wait_auth_lock() if self.__cache_manager is None: raise RuntimeError("Session isn't authenticated!") return self.__cache_manager def cdn(self) -> CdnManager: + """ """ self.__wait_auth_lock() if self.__cdn_manager is None: raise RuntimeError("Session isn't authenticated!") return self.__cdn_manager def channel(self) -> ChannelManager: + """ """ self.__wait_auth_lock() if self.__channel_manager is None: raise RuntimeError("Session isn't authenticated!") return self.__channel_manager def client(self) -> requests.Session: + """ """ return self.__client def close(self) -> None: - """ - Close instance - """ + """Close instance""" self.logger.info( "Closing session. device_id: {}".format(self.__inner.device_id) ) @@ -834,9 +1017,7 @@ class Session(Closeable, MessageListener, SubListener): self.logger.info("Closed session. device_id: {}".format(self.__inner.device_id)) def connect(self) -> None: - """ - Connect to the Spotify Server - """ + """Connect to the Spotify Server""" acc = Session.Accumulator() # Send ClientHello nonce = Random.get_random_bytes(0x10) @@ -928,6 +1109,7 @@ class Session(Closeable, MessageListener, SubListener): self.logger.info("Connection successfully!") def content_feeder(self) -> PlayableContentFeeder: + """ """ self.__wait_auth_lock() if self.__content_feeder is None: raise RuntimeError("Session isn't authenticated!") @@ -935,25 +1117,39 @@ class Session(Closeable, MessageListener, SubListener): @staticmethod def create_client(conf: Configuration) -> requests.Session: + """ + + :param conf: Configuration: + + """ client = requests.Session() return client def dealer(self) -> DealerClient: + """ """ self.__wait_auth_lock() if self.__dealer_client is None: raise RuntimeError("Session isn't authenticated!") return self.__dealer_client def device_id(self) -> str: + """ """ return self.__inner.device_id def device_name(self) -> str: + """ """ return self.__inner.device_name def device_type(self) -> Connect.DeviceType: + """ """ return self.__inner.device_type def event(self, resp: MercuryClient.Response) -> None: + """ + + :param resp: MercuryClient.Response: + + """ if resp.uri == "spotify:user:attributes:update": attributes_update = UserAttributesUpdate() attributes_update.ParseFromString(resp.payload) @@ -964,6 +1160,12 @@ class Session(Closeable, MessageListener, SubListener): ) def get_user_attribute(self, key: str, fallback: str = None) -> str: + """ + + :param key: str: + :param fallback: str: (Default value = None) + + """ return ( self.__user_attributes.get(key) if self.__user_attributes.get(key) is not None @@ -971,26 +1173,36 @@ class Session(Closeable, MessageListener, SubListener): ) def is_valid(self) -> bool: + """ """ if self.__closed: return False self.__wait_auth_lock() return self.__ap_welcome is not None and self.connection is not None def mercury(self) -> MercuryClient: + """ """ self.__wait_auth_lock() if self.__mercury_client is None: raise RuntimeError("Session isn't authenticated!") return self.__mercury_client def on_message(self, uri: str, headers: typing.Dict[str, str], payload: bytes): + """ + + :param uri: str: + :param headers: typing.Dict[str: + :param str]: + :param payload: bytes: + + """ if uri == "hm://connect-state/v1/connect/logout": self.close() def parse_product_info(self, data) -> None: - """ - Parse product information - Args: - data: Raw product information + """Parse product information + + :param data: Raw product information + """ products = defusedxml.ElementTree.fromstring(data) if products is None: @@ -1003,12 +1215,11 @@ class Session(Closeable, MessageListener, SubListener): self.logger.debug("Parsed product info: {}".format(self.__user_attributes)) def preferred_locale(self) -> str: + """ """ return self.__inner.preferred_locale def reconnect(self) -> None: - """ - Reconnect to the Spotify Server - """ + """Reconnect to the Spotify Server""" if self.connection is not None: self.connection.close() self.__receiver.stop() @@ -1029,20 +1240,24 @@ class Session(Closeable, MessageListener, SubListener): ) def reconnecting(self) -> bool: + """ """ return not self.__closing and not self.__closed and self.connection is None def search(self) -> SearchManager: + """ """ self.__wait_auth_lock() if self.__search is None: raise RuntimeError("Session isn't authenticated!") return self.__search def send(self, cmd: bytes, payload: bytes): - """ - Send data to socket using send_unchecked - Args: - cmd: Command - payload: Payload + """Send data to socket using send_unchecked + + :param cmd: Command + :param payload: Payload + :param cmd: bytes: + :param payload: bytes: + """ if self.__closing and self.connection is None: self.logger.debug("Connection was broken while closing.") @@ -1055,15 +1270,18 @@ class Session(Closeable, MessageListener, SubListener): self.__send_unchecked(cmd, payload) def tokens(self) -> TokenProvider: + """ """ self.__wait_auth_lock() if self.__token_provider is None: raise RuntimeError("Session isn't authenticated!") return self.__token_provider def username(self): + """ """ return self.__ap_welcome.canonical_username def stored(self): + """ """ return self.__stored_str def __authenticate_partial( @@ -1155,6 +1373,7 @@ class Session(Closeable, MessageListener, SubListener): self.__auth_lock.wait() class AbsBuilder: + """ """ conf = None device_id = None device_name = "librespot-python" @@ -1168,16 +1387,31 @@ class Session(Closeable, MessageListener, SubListener): self.conf = conf def set_preferred_locale(self, locale: str) -> Session.AbsBuilder: + """ + + :param locale: str: + + """ if len(locale) != 2: raise TypeError("Invalid locale: {}".format(locale)) self.preferred_locale = locale return self def set_device_name(self, device_name: str) -> Session.AbsBuilder: + """ + + :param device_name: str: + + """ self.device_name = device_name return self def set_device_id(self, device_id: str) -> Session.AbsBuilder: + """ + + :param device_id: str: + + """ if self.device_id is not None and len(device_id) != 40: raise TypeError("Device ID must be 40 chars long.") self.device_id = device_id @@ -1186,20 +1420,27 @@ class Session(Closeable, MessageListener, SubListener): def set_device_type( self, device_type: Connect.DeviceType ) -> Session.AbsBuilder: + """ + + :param device_type: Connect.DeviceType: + + """ self.device_type = device_type return self class Accumulator: + """ """ __buffer: io.BytesIO def __init__(self): self.__buffer = io.BytesIO() def read(self) -> bytes: - """ - Read all buffer - Returns: - All buffer + """Read all buffer + + + :returns: All buffer + """ pos = self.__buffer.tell() self.__buffer.seek(0) @@ -1208,33 +1449,43 @@ class Session(Closeable, MessageListener, SubListener): return data def write(self, data: bytes) -> None: - """ - Write data to buffer - Args: - data: Bytes to be written + """Write data to buffer + + :param data: Bytes to be written + :param data: bytes: + """ self.__buffer.write(data) def write_int(self, data: int) -> None: - """ - Write data to buffer - Args: - data: Integer to be written + """Write data to buffer + + :param data: Integer to be written + :param data: int: + """ self.write(struct.pack(">i", data)) def write_short(self, data: int) -> None: - """ - Write data to buffer - Args: - data: Short integer to be written + """Write data to buffer + + :param data: Short integer to be written + :param data: int: + """ self.write(struct.pack(">h", data)) class Builder(AbsBuilder): + """ """ login_credentials: Authentication.LoginCredentials = None def blob(self, username: str, blob: bytes) -> Session.Builder: + """ + + :param username: str: + :param blob: bytes: + + """ 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) @@ -1243,6 +1494,13 @@ class Session(Closeable, MessageListener, SubListener): def decrypt_blob( self, device_id: str, username: str, encrypted_blob: bytes ) -> Authentication.LoginCredentials: + """ + + :param device_id: str: + :param username: str: + :param encrypted_blob: bytes: + + """ encrypted_blob = base64.b64decode(encrypted_blob) sha1 = SHA1.new() sha1.update(device_id.encode()) @@ -1279,6 +1537,11 @@ class Session(Closeable, MessageListener, SubListener): ) def read_blob_int(self, buffer: io.BytesIO) -> int: + """ + + :param buffer: io.BytesIO: + + """ lo = buffer.read(1) if (int(lo[0]) & 0x80) == 0: return int(lo[0]) @@ -1286,12 +1549,11 @@ class Session(Closeable, MessageListener, SubListener): return int(lo[0]) & 0x7F | int(hi[0]) << 7 def stored(self, stored_credentials_str: str): - """ - Create credential from stored string - Args: - stored_credentials: credential string - Returns: - Builder + """Create credential from stored string + + :param stored_credentials_str: str: + :returns: Builder + """ try: obj = json.loads(base64.b64decode(stored_credentials_str)) @@ -1311,12 +1573,11 @@ class Session(Closeable, MessageListener, SubListener): return self def stored_file(self, stored_credentials: str = None) -> Session.Builder: - """ - Create credential from stored file - Args: - stored_credentials: credential file path - Returns: - Builder + """Create credential from stored file + + :param stored_credentials: str: (Default value = None) + :returns: Builder + """ if stored_credentials is None: stored_credentials = self.conf.stored_credentials_file @@ -1338,13 +1599,13 @@ class Session(Closeable, MessageListener, SubListener): return self def user_pass(self, username: str, password: str) -> Session.Builder: - """ - Create credential from username and password - Args: - username: Spotify's account username - password: Spotify's account password - Returns: - Builder + """Create credential from username and password + + :param username: Spotify's account username + :param username: str: + :param password: str: + :returns: Builder + """ self.login_credentials = Authentication.LoginCredentials( username=username, @@ -1354,10 +1615,11 @@ class Session(Closeable, MessageListener, SubListener): return self def create(self) -> Session: - """ - Create the Session instance - Returns: - Session instance + """Create the Session instance + + + :returns: Session instance + """ if self.login_credentials is None: raise RuntimeError("You must select an authentication method.") @@ -1376,6 +1638,7 @@ class Session(Closeable, MessageListener, SubListener): return session class Configuration: + """ """ # Proxy # proxyEnabled: bool # proxyType: Proxy.Type @@ -1428,6 +1691,7 @@ class Session(Closeable, MessageListener, SubListener): self.retry_on_chunk_error = retry_on_chunk_error class Builder: + """ """ # Proxy # proxyEnabled: bool = False # proxyType: Proxy.Type = Proxy.Type.DIRECT @@ -1486,23 +1750,21 @@ class Session(Closeable, MessageListener, SubListener): def set_cache_enabled( self, cache_enabled: bool ) -> Session.Configuration.Builder: - """ - Set cache_enabled - Args: - cache_enabled: Cache enabled - Returns: - Builder + """Set cache_enabled + + :param cache_enabled: bool: + :returns: Builder + """ self.cache_enabled = cache_enabled return self def set_cache_dir(self, cache_dir: str) -> Session.Configuration.Builder: - """ - Set cache_dir - Args: - cache_dir: Cache directory - Returns: - Builder + """Set cache_dir + + :param cache_dir: str: + :returns: Builder + """ self.cache_dir = cache_dir return self @@ -1510,12 +1772,11 @@ class Session(Closeable, MessageListener, SubListener): def set_do_cache_clean_up( self, do_cache_clean_up: bool ) -> Session.Configuration.Builder: - """ - Set do_cache_clean_up - Args: - do_cache_clean_up: Do cache clean up - Returns: - Builder + """Set do_cache_clean_up + + :param do_cache_clean_up: bool: + :returns: Builder + """ self.do_cache_clean_up = do_cache_clean_up return self @@ -1523,12 +1784,11 @@ class Session(Closeable, MessageListener, SubListener): def set_store_credentials( self, store_credentials: bool ) -> Session.Configuration.Builder: - """ - Set store_credentials - Args: - store_credentials: Store credentials - Returns: - Builder + """Set store_credentials + + :param store_credentials: bool: + :returns: Builder + """ self.store_credentials = store_credentials return self @@ -1536,12 +1796,11 @@ class Session(Closeable, MessageListener, SubListener): def set_stored_credential_file( self, stored_credential_file: str ) -> Session.Configuration.Builder: - """ - Set stored_credential_file - Args: - stored_credential_file: Stored credential file - Returns: - Builder + """Set stored_credential_file + + :param stored_credential_file: str: + :returns: Builder + """ self.stored_credentials_file = stored_credential_file return self @@ -1549,21 +1808,21 @@ class Session(Closeable, MessageListener, SubListener): def set_retry_on_chunk_error( self, retry_on_chunk_error: bool ) -> Session.Configuration.Builder: - """ - Set retry_on_chunk_error - Args: - retry_on_chunk_error: Retry on chunk error - Returns: - Builder + """Set retry_on_chunk_error + + :param retry_on_chunk_error: bool: + :returns: Builder + """ self.retry_on_chunk_error = retry_on_chunk_error return self def build(self) -> Session.Configuration: - """ - Build Configuration instance - Returns: - Session.Configuration + """Build Configuration instance + + + :returns: Session.Configuration + """ return Session.Configuration( # self.proxyEnabled, @@ -1582,6 +1841,7 @@ class Session(Closeable, MessageListener, SubListener): ) class ConnectionHolder: + """ """ __buffer: io.BytesIO __socket: socket.socket @@ -1591,13 +1851,13 @@ class Session(Closeable, MessageListener, SubListener): @staticmethod def create(address: str, conf) -> Session.ConnectionHolder: - """ - Create the ConnectionHolder instance - Args: - address: Address to connect - conf: Configuration - Returns: - ConnectionHolder instance + """Create the ConnectionHolder instance + + :param address: Address to connect + :param address: str: + :param conf: + :returns: ConnectionHolder instance + """ ap_address = address.split(":")[0] ap_port = int(address.split(":")[1]) @@ -1606,15 +1866,11 @@ class Session(Closeable, MessageListener, SubListener): return Session.ConnectionHolder(sock) def close(self) -> None: - """ - Close the connection - """ + """Close the connection""" self.__socket.close() def flush(self) -> None: - """ - Flush data to socket - """ + """Flush data to socket""" try: self.__buffer.seek(0) self.__socket.send(self.__buffer.read()) @@ -1623,64 +1879,70 @@ class Session(Closeable, MessageListener, SubListener): pass def read(self, length: int) -> bytes: - """ - Read data from socket - Args: - length: Reading length - Returns: - Bytes data from socket + """Read data from socket + + :param length: int: + :returns: Bytes data from socket + """ return self.__socket.recv(length) def read_int(self) -> int: - """ - Read integer from socket - Returns: - integer from socket + """Read integer from socket + + + :returns: integer from socket + """ return struct.unpack(">i", self.read(4))[0] def read_short(self) -> int: - """ - Read short integer from socket - Returns: - short integer from socket + """Read short integer from socket + + + :returns: short integer from socket + """ return struct.unpack(">h", self.read(2))[0] def set_timeout(self, seconds: float) -> None: - """ - Set socket's timeout - Args: - seconds: Number of seconds until timeout + """Set socket's timeout + + :param seconds: Number of seconds until timeout + :param seconds: float: + """ self.__socket.settimeout(None if seconds == 0 else seconds) def write(self, data: bytes) -> None: - """ - Write data to buffer - Args: - data: Bytes to be written + """Write data to buffer + + :param data: Bytes to be written + :param data: bytes: + """ self.__buffer.write(data) def write_int(self, data: int) -> None: - """ - Write data to buffer - Args: - data: Integer to be written + """Write data to buffer + + :param data: Integer to be written + :param data: int: + """ self.write(struct.pack(">i", data)) def write_short(self, data: int) -> None: - """ - Write data to buffer - Args: - data: Short integer to be written + """Write data to buffer + + :param data: Short integer to be written + :param data: int: + """ self.write(struct.pack(">h", data)) class Inner: + """ """ device_type: Connect.DeviceType = None device_name: str device_id: str @@ -1704,6 +1966,7 @@ class Session(Closeable, MessageListener, SubListener): ) class Receiver: + """ """ __session: Session __thread: threading.Thread __running: bool = True @@ -1716,12 +1979,11 @@ class Session(Closeable, MessageListener, SubListener): self.__thread.start() def stop(self) -> None: + """ """ self.__running = False def run(self) -> None: - """ - Receive Packet thread function - """ + """Receive Packet thread function""" self.__session.logger.info("Session.Receiver started") while self.__running: packet: Packet @@ -1754,6 +2016,7 @@ class Session(Closeable, MessageListener, SubListener): ) def anonymous(): + """ """ self.__session.logger.warning( "Socket timed out. Reconnecting..." ) @@ -1809,11 +2072,13 @@ class Session(Closeable, MessageListener, SubListener): ) class SpotifyAuthenticationException(Exception): + """ """ def __init__(self, login_failed: Keyexchange.APLoginFailed): super().__init__(Keyexchange.ErrorCode.Name(login_failed.error_code)) class SearchManager: + """ """ base_url = "hm://searchview/km/v4/search/" __session: Session @@ -1821,6 +2086,11 @@ class SearchManager: self.__session = session def request(self, request: SearchRequest) -> typing.Any: + """ + + :param request: SearchRequest: + + """ if request.get_username() == "": request.set_username(self.__session.username()) if request.get_country() == "": @@ -1838,10 +2108,12 @@ class SearchManager: return json.loads(response.payload) class SearchException(Exception): + """ """ def __init__(self, status_code: int): super().__init__("Search failed with code {}.".format(status_code)) class SearchRequest: + """ """ query: typing.Final[str] __catalogue = "" __country = "" @@ -1856,6 +2128,7 @@ class SearchManager: raise TypeError def build_url(self) -> str: + """ """ url = SearchManager.base_url + urllib.parse.quote(self.query) url += "?entityVersion=2" url += "&catalogue=" + urllib.parse.quote(self.__catalogue) @@ -1867,49 +2140,86 @@ class SearchManager: return url def get_catalogue(self) -> str: + """ """ return self.__catalogue def get_country(self) -> str: + """ """ return self.__country def get_image_size(self) -> str: + """ """ return self.__image_size def get_limit(self) -> int: + """ """ return self.__limit def get_locale(self) -> str: + """ """ return self.__locale def get_username(self) -> str: + """ """ return self.__username def set_catalogue(self, catalogue: str) -> SearchManager.SearchRequest: + """ + + :param catalogue: str: + + """ self.__catalogue = catalogue return self def set_country(self, country: str) -> SearchManager.SearchRequest: + """ + + :param country: str: + + """ self.__country = country return self def set_image_size(self, image_size: str) -> SearchManager.SearchRequest: + """ + + :param image_size: str: + + """ self.__image_size = image_size return self def set_limit(self, limit: int) -> SearchManager.SearchRequest: + """ + + :param limit: int: + + """ self.__limit = limit return self def set_locale(self, locale: str) -> SearchManager.SearchRequest: + """ + + :param locale: str: + + """ self.__locale = locale return self def set_username(self, username: str) -> SearchManager.SearchRequest: + """ + + :param username: str: + + """ self.__username = username return self class TokenProvider: + """ """ logger = logging.getLogger("Librespot:TokenProvider") token_expire_threshold = 10 __session: Session @@ -1921,15 +2231,30 @@ class TokenProvider: def find_token_with_all_scopes( self, scopes: typing.List[str] ) -> typing.Union[StoredToken, None]: + """ + + :param scopes: typing.List[str]: + + """ for token in self.__tokens: if token.has_scopes(scopes): return token return None def get(self, scope: str) -> str: + """ + + :param scope: str: + + """ return self.get_token(scope).access_token def get_token(self, *scopes) -> StoredToken: + """ + + :param *scopes: + + """ scopes = list(scopes) if len(scopes) == 0: raise RuntimeError("The token doesn't have any scope") @@ -1957,6 +2282,7 @@ class TokenProvider: return token class StoredToken: + """ """ expires_in: int access_token: str scopes: typing.List[str] @@ -1969,17 +2295,28 @@ class TokenProvider: self.scopes = obj["scope"] def expired(self) -> bool: + """ """ return self.timestamp + ( self.expires_in - TokenProvider.token_expire_threshold ) * 1000 < int(time.time_ns() / 1000) def has_scope(self, scope: str) -> bool: + """ + + :param scope: str: + + """ for s in self.scopes: if s == scope: return True return False def has_scopes(self, sc: typing.List[str]) -> bool: + """ + + :param sc: typing.List[str]: + + """ for s in sc: if not self.has_scope(s): return False From 907634a754027b9bad233771363228abbd397c97 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:10 +0000 Subject: [PATCH 6/8] Restyled by reorder-python-imports --- librespot/core.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index 120f438..19fe9e4 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -23,19 +23,31 @@ import requests import websocket from Cryptodome import Random from Cryptodome.Cipher import AES -from Cryptodome.Hash import HMAC, SHA1 +from Cryptodome.Hash import HMAC +from Cryptodome.Hash import SHA1 from Cryptodome.Protocol.KDF import PBKDF2 from Cryptodome.PublicKey import RSA from Cryptodome.Signature import PKCS1_v1_5 -from librespot import Version, util -from librespot.audio import AudioKeyManager, CdnManager, PlayableContentFeeder +from librespot import util +from librespot import Version +from librespot.audio import AudioKeyManager +from librespot.audio import CdnManager +from librespot.audio import PlayableContentFeeder from librespot.audio.storage import ChannelManager from librespot.cache import CacheManager -from librespot.crypto import CipherPair, DiffieHellman, Packet -from librespot.mercury import MercuryClient, MercuryRequests, RawMercuryRequest -from librespot.metadata import (AlbumId, ArtistId, EpisodeId, PlaylistId, - ShowId, TrackId) +from librespot.crypto import CipherPair +from librespot.crypto import DiffieHellman +from librespot.crypto import Packet +from librespot.mercury import MercuryClient +from librespot.mercury import MercuryRequests +from librespot.mercury import RawMercuryRequest +from librespot.metadata import AlbumId +from librespot.metadata import ArtistId +from librespot.metadata import EpisodeId +from librespot.metadata import PlaylistId +from librespot.metadata import ShowId +from librespot.metadata import TrackId from librespot.proto import Authentication_pb2 as Authentication from librespot.proto import ClientToken_pb2 as ClientToken from librespot.proto import Connect_pb2 as Connect @@ -44,8 +56,10 @@ from librespot.proto import Keyexchange_pb2 as Keyexchange from librespot.proto import Metadata_pb2 as Metadata from librespot.proto import Playlist4External_pb2 as Playlist4External from librespot.proto.ExplicitContentPubsub_pb2 import UserAttributesUpdate -from librespot.structure import (Closeable, MessageListener, RequestListener, - SubListener) +from librespot.structure import Closeable +from librespot.structure import MessageListener +from librespot.structure import RequestListener +from librespot.structure import SubListener class ApiClient(Closeable): From 12621ef31c253831654082d7841d6e65091d17a0 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:10 +0000 Subject: [PATCH 7/8] Restyled by whitespace --- librespot/core.py | 192 +++++++++++++++++++++++----------------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index 19fe9e4..fd13f70 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -82,13 +82,13 @@ class ApiClient(Closeable): ) -> requests.PreparedRequest: """ - :param method: str: - :param suffix: str: - :param headers: typing.Union[None: - :param typing.Dict[str: - :param str]]: - :param body: typing.Union[None: - :param bytes]: + :param method: str: + :param suffix: str: + :param headers: typing.Union[None: + :param typing.Dict[str: + :param str]]: + :param body: typing.Union[None: + :param bytes]: """ if self.__client_token_str is None: @@ -120,13 +120,13 @@ class ApiClient(Closeable): ) -> requests.Response: """ - :param method: str: - :param suffix: str: - :param headers: typing.Union[None: - :param typing.Dict[str: - :param str]]: - :param body: typing.Union[None: - :param bytes]: + :param method: str: + :param suffix: str: + :param headers: typing.Union[None: + :param typing.Dict[str: + :param str]]: + :param body: typing.Union[None: + :param bytes]: """ response = self.__session.client().send( @@ -139,8 +139,8 @@ class ApiClient(Closeable): ) -> None: """ - :param connection_id: str: - :param proto: Connect.PutStateRequest: + :param connection_id: str: + :param proto: Connect.PutStateRequest: """ response = self.send( @@ -168,7 +168,7 @@ class ApiClient(Closeable): def get_metadata_4_track(self, track: TrackId) -> Metadata.Track: """ - :param track: TrackId: + :param track: TrackId: """ response = self.send( @@ -185,7 +185,7 @@ class ApiClient(Closeable): def get_metadata_4_episode(self, episode: EpisodeId) -> Metadata.Episode: """ - :param episode: EpisodeId: + :param episode: EpisodeId: """ response = self.send( @@ -202,7 +202,7 @@ class ApiClient(Closeable): def get_metadata_4_album(self, album: AlbumId) -> Metadata.Album: """ - :param album: AlbumId: + :param album: AlbumId: """ response = self.send( @@ -220,7 +220,7 @@ class ApiClient(Closeable): def get_metadata_4_artist(self, artist: ArtistId) -> Metadata.Artist: """ - :param artist: ArtistId: + :param artist: ArtistId: """ response = self.send( @@ -237,7 +237,7 @@ class ApiClient(Closeable): def get_metadata_4_show(self, show: ShowId) -> Metadata.Show: """ - :param show: ShowId: + :param show: ShowId: """ response = self.send( @@ -254,7 +254,7 @@ class ApiClient(Closeable): def get_playlist(self, _id: PlaylistId) -> Playlist4External.SelectedListContent: """ - :param _id: PlaylistId: + :param _id: PlaylistId: """ response = self.send( @@ -271,7 +271,7 @@ class ApiClient(Closeable): def set_client_token(self, client_token): """ - :param client_token: + :param client_token: """ self.__client_token_str = client_token @@ -326,7 +326,7 @@ class ApiClient(Closeable): def check_status(response: requests.Response) -> None: """ - :param response: requests.Response: + :param response: requests.Response: """ if response.status_code != 200: @@ -341,7 +341,7 @@ class ApResolver: def request(service_type: str) -> typing.Any: """Gets the specified ApResolve - :param service_type: str: + :param service_type: str: :returns: The resulting object will be returned """ @@ -357,7 +357,7 @@ class ApResolver: def get_random_of(service_type: str) -> str: """Gets the specified random ApResolve url - :param service_type: str: + :param service_type: str: :returns: A random ApResolve url will be returned """ @@ -417,8 +417,8 @@ class DealerClient(Closeable): def add_message_listener(self, listener: MessageListener, uris: list[str]) -> None: """ - :param listener: MessageListener: - :param uris: list[str]: + :param listener: MessageListener: + :param uris: list[str]: """ with self.__message_listeners_lock: @@ -432,8 +432,8 @@ class DealerClient(Closeable): def add_request_listener(self, listener: RequestListener, uri: str): """ - :param listener: RequestListener: - :param uri: str: + :param listener: RequestListener: + :param uri: str: """ with self.__request_listeners_lock: @@ -474,7 +474,7 @@ class DealerClient(Closeable): def handle_message(self, obj: typing.Any) -> None: """ - :param obj: typing.Any: + :param obj: typing.Any: """ uri = obj.get("uri") @@ -513,7 +513,7 @@ class DealerClient(Closeable): def handle_request(self, obj: typing.Any) -> None: """ - :param obj: typing.Any: + :param obj: typing.Any: """ mid = obj.get("message_ident") @@ -554,7 +554,7 @@ class DealerClient(Closeable): def remove_message_listener(self, listener: MessageListener) -> None: """ - :param listener: MessageListener: + :param listener: MessageListener: """ with self.__message_listeners_lock: @@ -563,7 +563,7 @@ class DealerClient(Closeable): def remove_request_listener(self, listener: RequestListener) -> None: """ - :param listener: RequestListener: + :param listener: RequestListener: """ with self.__request_listeners_lock: @@ -614,8 +614,8 @@ class DealerClient(Closeable): def on_failure(self, ws: websocket.WebSocketApp, error): """ - :param ws: websocket.WebSocketApp: - :param error: + :param ws: websocket.WebSocketApp: + :param error: """ if self.__closed: @@ -628,8 +628,8 @@ class DealerClient(Closeable): def on_message(self, ws: websocket.WebSocketApp, text: str): """ - :param ws: websocket.WebSocketApp: - :param text: str: + :param ws: websocket.WebSocketApp: + :param text: str: """ obj = json.loads(text) @@ -649,7 +649,7 @@ class DealerClient(Closeable): def on_open(self, ws: websocket.WebSocketApp): """ - :param ws: websocket.WebSocketApp: + :param ws: websocket.WebSocketApp: """ if self.__closed: @@ -691,8 +691,8 @@ class DealerClient(Closeable): def send_reply(self, key: str, result: DealerClient.RequestResult): """ - :param key: str: - :param result: DealerClient.RequestResult: + :param key: str: + :param result: DealerClient.RequestResult: """ success = ( @@ -744,8 +744,8 @@ class EventService(Closeable): def send_event(self, event_or_builder: typing.Union[GenericEvent, EventBuilder]): """ - :param event_or_builder: typing.Union[GenericEvent: - :param EventBuilder]: + :param event_or_builder: typing.Union[GenericEvent: + :param EventBuilder]: """ if type(event_or_builder) is EventService.GenericEvent: @@ -759,7 +759,7 @@ class EventService(Closeable): def language(self, lang: str): """ - :param lang: str: + :param lang: str: """ event = EventService.EventBuilder(EventService.Type.LANGUAGE) @@ -849,7 +849,7 @@ class MessageType(enum.Enum): def parse(_typ: str): """ - :param _typ: str: + :param _typ: str: """ if _typ == MessageType.MESSAGE.value: @@ -946,7 +946,7 @@ class Session(Closeable, MessageListener, SubListener): """Log in to Spotify :param credential: Spotify account login information - :param credential: Authentication.LoginCredentials: + :param credential: Authentication.LoginCredentials: """ self.__authenticate_partial(credential, False) @@ -1133,7 +1133,7 @@ class Session(Closeable, MessageListener, SubListener): def create_client(conf: Configuration) -> requests.Session: """ - :param conf: Configuration: + :param conf: Configuration: """ client = requests.Session() @@ -1161,7 +1161,7 @@ class Session(Closeable, MessageListener, SubListener): def event(self, resp: MercuryClient.Response) -> None: """ - :param resp: MercuryClient.Response: + :param resp: MercuryClient.Response: """ if resp.uri == "spotify:user:attributes:update": @@ -1176,7 +1176,7 @@ class Session(Closeable, MessageListener, SubListener): def get_user_attribute(self, key: str, fallback: str = None) -> str: """ - :param key: str: + :param key: str: :param fallback: str: (Default value = None) """ @@ -1203,10 +1203,10 @@ class Session(Closeable, MessageListener, SubListener): def on_message(self, uri: str, headers: typing.Dict[str, str], payload: bytes): """ - :param uri: str: - :param headers: typing.Dict[str: - :param str]: - :param payload: bytes: + :param uri: str: + :param headers: typing.Dict[str: + :param str]: + :param payload: bytes: """ if uri == "hm://connect-state/v1/connect/logout": @@ -1269,8 +1269,8 @@ class Session(Closeable, MessageListener, SubListener): :param cmd: Command :param payload: Payload - :param cmd: bytes: - :param payload: bytes: + :param cmd: bytes: + :param payload: bytes: """ if self.__closing and self.connection is None: @@ -1403,7 +1403,7 @@ class Session(Closeable, MessageListener, SubListener): def set_preferred_locale(self, locale: str) -> Session.AbsBuilder: """ - :param locale: str: + :param locale: str: """ if len(locale) != 2: @@ -1414,7 +1414,7 @@ class Session(Closeable, MessageListener, SubListener): def set_device_name(self, device_name: str) -> Session.AbsBuilder: """ - :param device_name: str: + :param device_name: str: """ self.device_name = device_name @@ -1423,7 +1423,7 @@ class Session(Closeable, MessageListener, SubListener): def set_device_id(self, device_id: str) -> Session.AbsBuilder: """ - :param device_id: str: + :param device_id: str: """ if self.device_id is not None and len(device_id) != 40: @@ -1436,7 +1436,7 @@ class Session(Closeable, MessageListener, SubListener): ) -> Session.AbsBuilder: """ - :param device_type: Connect.DeviceType: + :param device_type: Connect.DeviceType: """ self.device_type = device_type @@ -1466,7 +1466,7 @@ class Session(Closeable, MessageListener, SubListener): """Write data to buffer :param data: Bytes to be written - :param data: bytes: + :param data: bytes: """ self.__buffer.write(data) @@ -1475,7 +1475,7 @@ class Session(Closeable, MessageListener, SubListener): """Write data to buffer :param data: Integer to be written - :param data: int: + :param data: int: """ self.write(struct.pack(">i", data)) @@ -1484,7 +1484,7 @@ class Session(Closeable, MessageListener, SubListener): """Write data to buffer :param data: Short integer to be written - :param data: int: + :param data: int: """ self.write(struct.pack(">h", data)) @@ -1496,8 +1496,8 @@ class Session(Closeable, MessageListener, SubListener): def blob(self, username: str, blob: bytes) -> Session.Builder: """ - :param username: str: - :param blob: bytes: + :param username: str: + :param blob: bytes: """ if self.device_id is None: @@ -1510,9 +1510,9 @@ class Session(Closeable, MessageListener, SubListener): ) -> Authentication.LoginCredentials: """ - :param device_id: str: - :param username: str: - :param encrypted_blob: bytes: + :param device_id: str: + :param username: str: + :param encrypted_blob: bytes: """ encrypted_blob = base64.b64decode(encrypted_blob) @@ -1553,7 +1553,7 @@ class Session(Closeable, MessageListener, SubListener): def read_blob_int(self, buffer: io.BytesIO) -> int: """ - :param buffer: io.BytesIO: + :param buffer: io.BytesIO: """ lo = buffer.read(1) @@ -1565,7 +1565,7 @@ class Session(Closeable, MessageListener, SubListener): def stored(self, stored_credentials_str: str): """Create credential from stored string - :param stored_credentials_str: str: + :param stored_credentials_str: str: :returns: Builder """ @@ -1616,8 +1616,8 @@ class Session(Closeable, MessageListener, SubListener): """Create credential from username and password :param username: Spotify's account username - :param username: str: - :param password: str: + :param username: str: + :param password: str: :returns: Builder """ @@ -1766,7 +1766,7 @@ class Session(Closeable, MessageListener, SubListener): ) -> Session.Configuration.Builder: """Set cache_enabled - :param cache_enabled: bool: + :param cache_enabled: bool: :returns: Builder """ @@ -1776,7 +1776,7 @@ class Session(Closeable, MessageListener, SubListener): def set_cache_dir(self, cache_dir: str) -> Session.Configuration.Builder: """Set cache_dir - :param cache_dir: str: + :param cache_dir: str: :returns: Builder """ @@ -1788,7 +1788,7 @@ class Session(Closeable, MessageListener, SubListener): ) -> Session.Configuration.Builder: """Set do_cache_clean_up - :param do_cache_clean_up: bool: + :param do_cache_clean_up: bool: :returns: Builder """ @@ -1800,7 +1800,7 @@ class Session(Closeable, MessageListener, SubListener): ) -> Session.Configuration.Builder: """Set store_credentials - :param store_credentials: bool: + :param store_credentials: bool: :returns: Builder """ @@ -1812,7 +1812,7 @@ class Session(Closeable, MessageListener, SubListener): ) -> Session.Configuration.Builder: """Set stored_credential_file - :param stored_credential_file: str: + :param stored_credential_file: str: :returns: Builder """ @@ -1824,7 +1824,7 @@ class Session(Closeable, MessageListener, SubListener): ) -> Session.Configuration.Builder: """Set retry_on_chunk_error - :param retry_on_chunk_error: bool: + :param retry_on_chunk_error: bool: :returns: Builder """ @@ -1868,8 +1868,8 @@ class Session(Closeable, MessageListener, SubListener): """Create the ConnectionHolder instance :param address: Address to connect - :param address: str: - :param conf: + :param address: str: + :param conf: :returns: ConnectionHolder instance """ @@ -1895,7 +1895,7 @@ class Session(Closeable, MessageListener, SubListener): def read(self, length: int) -> bytes: """Read data from socket - :param length: int: + :param length: int: :returns: Bytes data from socket """ @@ -1923,7 +1923,7 @@ class Session(Closeable, MessageListener, SubListener): """Set socket's timeout :param seconds: Number of seconds until timeout - :param seconds: float: + :param seconds: float: """ self.__socket.settimeout(None if seconds == 0 else seconds) @@ -1932,7 +1932,7 @@ class Session(Closeable, MessageListener, SubListener): """Write data to buffer :param data: Bytes to be written - :param data: bytes: + :param data: bytes: """ self.__buffer.write(data) @@ -1941,7 +1941,7 @@ class Session(Closeable, MessageListener, SubListener): """Write data to buffer :param data: Integer to be written - :param data: int: + :param data: int: """ self.write(struct.pack(">i", data)) @@ -1950,7 +1950,7 @@ class Session(Closeable, MessageListener, SubListener): """Write data to buffer :param data: Short integer to be written - :param data: int: + :param data: int: """ self.write(struct.pack(">h", data)) @@ -2102,7 +2102,7 @@ class SearchManager: def request(self, request: SearchRequest) -> typing.Any: """ - :param request: SearchRequest: + :param request: SearchRequest: """ if request.get_username() == "": @@ -2180,7 +2180,7 @@ class SearchManager: def set_catalogue(self, catalogue: str) -> SearchManager.SearchRequest: """ - :param catalogue: str: + :param catalogue: str: """ self.__catalogue = catalogue @@ -2189,7 +2189,7 @@ class SearchManager: def set_country(self, country: str) -> SearchManager.SearchRequest: """ - :param country: str: + :param country: str: """ self.__country = country @@ -2198,7 +2198,7 @@ class SearchManager: def set_image_size(self, image_size: str) -> SearchManager.SearchRequest: """ - :param image_size: str: + :param image_size: str: """ self.__image_size = image_size @@ -2207,7 +2207,7 @@ class SearchManager: def set_limit(self, limit: int) -> SearchManager.SearchRequest: """ - :param limit: int: + :param limit: int: """ self.__limit = limit @@ -2216,7 +2216,7 @@ class SearchManager: def set_locale(self, locale: str) -> SearchManager.SearchRequest: """ - :param locale: str: + :param locale: str: """ self.__locale = locale @@ -2225,7 +2225,7 @@ class SearchManager: def set_username(self, username: str) -> SearchManager.SearchRequest: """ - :param username: str: + :param username: str: """ self.__username = username @@ -2247,7 +2247,7 @@ class TokenProvider: ) -> typing.Union[StoredToken, None]: """ - :param scopes: typing.List[str]: + :param scopes: typing.List[str]: """ for token in self.__tokens: @@ -2258,7 +2258,7 @@ class TokenProvider: def get(self, scope: str) -> str: """ - :param scope: str: + :param scope: str: """ return self.get_token(scope).access_token @@ -2266,7 +2266,7 @@ class TokenProvider: def get_token(self, *scopes) -> StoredToken: """ - :param *scopes: + :param *scopes: """ scopes = list(scopes) @@ -2317,7 +2317,7 @@ class TokenProvider: def has_scope(self, scope: str) -> bool: """ - :param scope: str: + :param scope: str: """ for s in self.scopes: @@ -2328,7 +2328,7 @@ class TokenProvider: def has_scopes(self, sc: typing.List[str]) -> bool: """ - :param sc: typing.List[str]: + :param sc: typing.List[str]: """ for s in sc: From ecd0006874c958fc84223a6c5278ea0061471e0d Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:12 +0000 Subject: [PATCH 8/8] Restyled by yapf --- librespot/core.py | 476 ++++++++++++++++++++++------------------------ 1 file changed, 223 insertions(+), 253 deletions(-) diff --git a/librespot/core.py b/librespot/core.py index fd13f70..f7d602d 100644 --- a/librespot/core.py +++ b/librespot/core.py @@ -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: """