From 616183a6cadc88a1f692db53d5dda06b09349ebf Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Tue, 5 Sep 2023 07:21:09 +0000 Subject: [PATCH] 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