From 5132c9e02322707840483835cec29c98339aff50 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Fri, 21 May 2021 03:40:40 +0000 Subject: [PATCH] Restyled by black --- examples/player.py | 61 ++- librespot/audio/AbsChunkedInputStream.py | 31 +- librespot/audio/cdn/CdnFeedHelper.py | 64 ++- librespot/audio/cdn/CdnManager.py | 149 ++++-- librespot/core/Session.py | 640 ++++++++++++++++------- 5 files changed, 648 insertions(+), 297 deletions(-) diff --git a/examples/player.py b/examples/player.py index 7a25647..cd506eb 100644 --- a/examples/player.py +++ b/examples/player.py @@ -31,14 +31,18 @@ def client(): return if (args[0] == "p" or args[0] == "play") and len(args) == 2: track_uri_search = re.search( - r"^spotify:track:(?P[0-9a-zA-Z]{22})$", args[1]) + r"^spotify:track:(?P[0-9a-zA-Z]{22})$", args[1] + ) track_url_search = re.search( r"^(https?://)?open.spotify.com/track/(?P[0-9a-zA-Z]{22})(\?si=.+?)?$", - args[1]) + args[1], + ) if track_uri_search is not None or track_url_search is not None: - track_id_str = (track_uri_search - if track_uri_search is not None else - track_url_search).group("TrackID") + track_id_str = ( + track_uri_search + if track_uri_search is not None + else track_url_search + ).group("TrackID") play(track_id_str) wait() if args[0] == "q" or args[0] == "quality": @@ -56,18 +60,22 @@ def client(): wait() if (args[0] == "s" or args[0] == "search") and len(args) <= 2: token = session.tokens().get("user-read-email") - resp = requests.get("https://api.spotify.com/v1/search", { - "limit": "5", - "offset": "0", - "q": cmd[2:], - "type": "track" - }, - headers={"Authorization": "Bearer %s" % token}) + resp = requests.get( + "https://api.spotify.com/v1/search", + {"limit": "5", "offset": "0", "q": cmd[2:], "type": "track"}, + headers={"Authorization": "Bearer %s" % token}, + ) i = 1 tracks = resp.json()["tracks"]["items"] for track in tracks: - print("%d, %s | %s" % (i, track["name"], ",".join( - [artist["name"] for artist in track["artists"]]))) + print( + "%d, %s | %s" + % ( + i, + track["name"], + ",".join([artist["name"] for artist in track["artists"]]), + ) + ) i += 1 position = -1 while True: @@ -105,11 +113,14 @@ def login(): def play(track_id_str: str): track_id = TrackId.from_base62(track_id_str) stream = session.content_feeder().load( - track_id, VorbisOnlyAudioQuality(AudioQuality.VERY_HIGH), False, None) - ffplay = subprocess.Popen(["ffplay", "-"], - stdin=subprocess.PIPE, - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL) + track_id, VorbisOnlyAudioQuality(AudioQuality.VERY_HIGH), False, None + ) + ffplay = subprocess.Popen( + ["ffplay", "-"], + stdin=subprocess.PIPE, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) while True: byte = stream.input_stream.stream().read() if byte == -1: @@ -119,11 +130,13 @@ def play(track_id_str: str): def splash(): - print("=================================\n" - "| Librespot-Python Player |\n" - "| |\n" - "| by kokarare1212 |\n" - "=================================\n\n\n") + print( + "=================================\n" + "| Librespot-Python Player |\n" + "| |\n" + "| by kokarare1212 |\n" + "=================================\n\n\n" + ) def main(): diff --git a/librespot/audio/AbsChunkedInputStream.py b/librespot/audio/AbsChunkedInputStream.py index c2e867b..53efa7e 100644 --- a/librespot/audio/AbsChunkedInputStream.py +++ b/librespot/audio/AbsChunkedInputStream.py @@ -21,9 +21,7 @@ class AbsChunkedInputStream(InputStream, HaltListener): _decoded_length: int = 0 def __init__(self, retry_on_chunk_error: bool): - self.retries: typing.Final[typing.List[int]] = [ - 0 for _ in range(self.chunks()) - ] + self.retries: typing.Final[typing.List[int]] = [0 for _ in range(self.chunks())] self.retry_on_chunk_error = retry_on_chunk_error def is_closed(self) -> bool: @@ -108,10 +106,13 @@ class AbsChunkedInputStream(InputStream, HaltListener): self.request_chunk_from_stream(chunk) self.requested_chunks()[chunk] = True - for i in range(chunk + 1, - min(self.chunks() - 1, chunk + self.preload_ahead) + 1): - if self.requested_chunks( - )[i] and self.retries[i] < self.preload_chunk_retries: + for i in range( + chunk + 1, min(self.chunks() - 1, chunk + self.preload_ahead) + 1 + ): + if ( + self.requested_chunks()[i] + and self.retries[i] < self.preload_chunk_retries + ): self.request_chunk_from_stream(i) self.requested_chunks()[chunk] = True @@ -145,10 +146,7 @@ class AbsChunkedInputStream(InputStream, HaltListener): self.check_availability(chunk, True, True) - def read(self, - b: bytearray = None, - offset: int = None, - length: int = None) -> int: + def read(self, b: bytearray = None, offset: int = None, length: int = None) -> int: if b is None and offset is None and length is None: return self.internal_read() if not (b is not None and offset is not None and length is not None): @@ -158,8 +156,9 @@ class AbsChunkedInputStream(InputStream, HaltListener): raise IOError("Stream is closed!") if offset < 0 or length < 0 or length > len(b) - offset: - raise IndexError("offset: {}, length: {}, buffer: {}".format( - offset, length, len(b))) + raise IndexError( + "offset: {}, length: {}, buffer: {}".format(offset, length, len(b)) + ) elif length == 0: return 0 @@ -174,8 +173,7 @@ class AbsChunkedInputStream(InputStream, HaltListener): self.check_availability(chunk, True, False) copy = min(len(self.buffer()[chunk]) - chunk_off, length - i) - b[offset + 0:copy] = self.buffer()[chunk][chunk_off:chunk_off + - copy] + b[offset + 0 : copy] = self.buffer()[chunk][chunk_off : chunk_off + copy] i += copy self._pos += copy @@ -223,4 +221,5 @@ class AbsChunkedInputStream(InputStream, HaltListener): @staticmethod def from_stream_error(stream_error: int): return AbsChunkedInputStream.ChunkException( - "Failed due to stream error, code: {}".format(stream_error)) + "Failed due to stream error, code: {}".format(stream_error) + ) diff --git a/librespot/audio/cdn/CdnFeedHelper.py b/librespot/audio/cdn/CdnFeedHelper.py index f9163ed..7a4efb0 100644 --- a/librespot/audio/cdn/CdnFeedHelper.py +++ b/librespot/audio/cdn/CdnFeedHelper.py @@ -17,10 +17,14 @@ class CdnFeedHelper: return random.choice(resp.cdnurl) @staticmethod - def load_track(session: Session, track: Metadata.Track, file: Metadata.AudioFile, - resp_or_url: typing.Union[StorageResolve.StorageResolveResponse, str], - preload: bool, halt_listener: HaltListener)\ - -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream: + def load_track( + session: Session, + track: Metadata.Track, + file: Metadata.AudioFile, + resp_or_url: typing.Union[StorageResolve.StorageResolveResponse, str], + preload: bool, + halt_listener: HaltListener, + ) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream: if type(resp_or_url) is str: url = resp_or_url else: @@ -31,19 +35,21 @@ class CdnFeedHelper: streamer = session.cdn().stream_file(file, key, url, halt_listener) input_stream = streamer.stream() - normalization_data = NormalizationData.NormalizationData.read( - input_stream) - if input_stream.skip(0xa7) != 0xa7: + normalization_data = NormalizationData.NormalizationData.read(input_stream) + if input_stream.skip(0xA7) != 0xA7: raise IOError("Couldn't skip 0xa7 bytes!") return PlayableContentFeeder.PlayableContentFeeder.LoadedStream( - track, streamer, normalization_data, + track, + streamer, + normalization_data, PlayableContentFeeder.PlayableContentFeeder.Metrics( - file.file_id, preload, -1 if preload else audio_key_time)) + file.file_id, preload, -1 if preload else audio_key_time + ), + ) @staticmethod def load_episode_external( - session: Session, episode: Metadata.Episode, - halt_listener: HaltListener + session: Session, episode: Metadata.Episode, halt_listener: HaltListener ) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream: resp = session.client().head(episode.external_url) @@ -51,21 +57,27 @@ class CdnFeedHelper: CdnFeedHelper._LOGGER.warning("Couldn't resolve redirect!") url = resp.url - CdnFeedHelper._LOGGER.debug("Fetched external url for {}: {}".format( - Utils.Utils.bytes_to_hex(episode.gid), url)) + CdnFeedHelper._LOGGER.debug( + "Fetched external url for {}: {}".format( + Utils.Utils.bytes_to_hex(episode.gid), url + ) + ) - streamer = session.cdn().stream_external_episode( - episode, url, halt_listener) + streamer = session.cdn().stream_external_episode(episode, url, halt_listener) return PlayableContentFeeder.PlayableContentFeeder.LoadedStream( - episode, streamer, None, - PlayableContentFeeder.PlayableContentFeeder.Metrics( - None, False, -1)) + episode, + streamer, + None, + PlayableContentFeeder.PlayableContentFeeder.Metrics(None, False, -1), + ) @staticmethod def load_episode( - session: Session, episode: Metadata.Episode, file: Metadata.AudioFile, - resp_or_url: typing.Union[StorageResolve.StorageResolveResponse, - str], halt_listener: HaltListener + session: Session, + episode: Metadata.Episode, + file: Metadata.AudioFile, + resp_or_url: typing.Union[StorageResolve.StorageResolveResponse, str], + halt_listener: HaltListener, ) -> PlayableContentFeeder.PlayableContentFeeder.LoadedStream: if type(resp_or_url) is str: url = resp_or_url @@ -78,9 +90,13 @@ class CdnFeedHelper: streamer = session.cdn().stream_file(file, key, url, halt_listener) input_stream = streamer.stream() normalization_data = NormalizationData.read(input_stream) - if input_stream.skip(0xa7) != 0xa7: + if input_stream.skip(0xA7) != 0xA7: raise IOError("Couldn't skip 0xa7 bytes!") return PlayableContentFeeder.PlayableContentFeeder.LoadedStream( - episode, streamer, normalization_data, + episode, + streamer, + normalization_data, PlayableContentFeeder.PlayableContentFeeder.Metrics( - file.file_id, False, audio_key_time)) + file.file_id, False, audio_key_time + ), + ) diff --git a/librespot/audio/cdn/CdnManager.py b/librespot/audio/cdn/CdnManager.py index d4b589c..c582033 100644 --- a/librespot/audio/cdn/CdnManager.py +++ b/librespot/audio/cdn/CdnManager.py @@ -30,9 +30,11 @@ class CdnManager: self._session = session def get_head(self, file_id: bytes): - resp = self._session.client() \ - .get(self._session.get_user_attribute("head-files-url", "https://heads-fa.spotify.com/head/{file_id}") - .replace("{file_id}", Utils.bytes_to_hex(file_id))) + resp = self._session.client().get( + self._session.get_user_attribute( + "head-files-url", "https://heads-fa.spotify.com/head/{file_id}" + ).replace("{file_id}", Utils.bytes_to_hex(file_id)) + ) if resp.status_code != 200: raise IOError("{}".format(resp.status_code)) @@ -43,27 +45,45 @@ class CdnManager: return body - def stream_external_episode(self, episode: Metadata.Episode, - external_url: str, - halt_listener: HaltListener): - return CdnManager.Streamer(self._session, StreamId(episode), - SuperAudioFormat.MP3, - CdnManager.CdnUrl(self, None, external_url), - self._session.cache(), NoopAudioDecrypt(), - halt_listener) + def stream_external_episode( + self, episode: Metadata.Episode, external_url: str, halt_listener: HaltListener + ): + return CdnManager.Streamer( + self._session, + StreamId(episode), + SuperAudioFormat.MP3, + CdnManager.CdnUrl(self, None, external_url), + self._session.cache(), + NoopAudioDecrypt(), + halt_listener, + ) - def stream_file(self, file: Metadata.AudioFile, key: bytes, url: str, - halt_listener: HaltListener): - return CdnManager.Streamer(self._session, StreamId.StreamId(file), - SuperAudioFormat.get(file.format), - CdnManager.CdnUrl(self, file.file_id, url), - self._session.cache(), AesAudioDecrypt(key), - halt_listener) + def stream_file( + self, + file: Metadata.AudioFile, + key: bytes, + url: str, + halt_listener: HaltListener, + ): + return CdnManager.Streamer( + self._session, + StreamId.StreamId(file), + SuperAudioFormat.get(file.format), + CdnManager.CdnUrl(self, file.file_id, url), + self._session.cache(), + AesAudioDecrypt(key), + halt_listener, + ) def get_audio_url(self, file_id: bytes): resp = self._session.api().send( - "GET", "/storage-resolve/files/audio/interactive/{}".format( - Utils.bytes_to_hex(file_id)), None, None) + "GET", + "/storage-resolve/files/audio/interactive/{}".format( + Utils.bytes_to_hex(file_id) + ), + None, + None, + ) if resp.status_code != 200: raise IOError(resp.status_code) @@ -76,11 +96,13 @@ class CdnManager: proto.ParseFromString(body) if proto.result == StorageResolve.StorageResolveResponse.Result.CDN: url = random.choice(proto.cdnurl) - self._LOGGER.debug("Fetched CDN url for {}: {}".format( - Utils.bytes_to_hex(file_id), url)) + self._LOGGER.debug( + "Fetched CDN url for {}: {}".format(Utils.bytes_to_hex(file_id), url) + ) return url raise CdnManager.CdnException( - "Could not retrieve CDN url! result: {}".format(proto.result)) + "Could not retrieve CDN url! result: {}".format(proto.result) + ) class CdnException(Exception): pass @@ -140,7 +162,8 @@ class CdnManager: if expire_at is None: self._expiration = -1 self._cdnManager._LOGGER.warning( - "Invalid __token__ in CDN url: {}".format(url)) + "Invalid __token__ in CDN url: {}".format(url) + ) return self._expiration = expire_at * 1000 @@ -150,8 +173,10 @@ class CdnManager: except ValueError: self._expiration = -1 self._cdnManager._LOGGER.warning( - "Couldn't extract expiration, invalid parameter in CDN url: " - .format(url)) + "Couldn't extract expiration, invalid parameter in CDN url: ".format( + url + ) + ) return self._expiration = int(token_url.query[:i]) * 1000 @@ -159,8 +184,10 @@ class CdnManager: else: self._expiration = -1 - class Streamer(GeneralAudioStream.GeneralAudioStream, - GeneralWritableStream.GeneralWritableStream): + class Streamer( + GeneralAudioStream.GeneralAudioStream, + GeneralWritableStream.GeneralWritableStream, + ): _session: Session = None _streamId: StreamId = None _executorService = concurrent.futures.ThreadPoolExecutor() @@ -175,10 +202,16 @@ class CdnManager: _internalStream: CdnManager.Streamer.InternalStream = None _haltListener: HaltListener = None - def __init__(self, session: Session, stream_id: StreamId, - audio_format: SuperAudioFormat, cdn_url, - cache: CacheManager, audio_decrypt: AudioDecrypt, - halt_listener: HaltListener): + def __init__( + self, + session: Session, + stream_id: StreamId, + audio_format: SuperAudioFormat, + cdn_url, + cache: CacheManager, + audio_decrypt: AudioDecrypt, + halt_listener: HaltListener, + ): self._session = session self._streamId = stream_id self._audioFormat = audio_format @@ -186,39 +219,38 @@ class CdnManager: self._cdnUrl = cdn_url self._haltListener = halt_listener - resp = self.request(range_start=0, - range_end=ChannelManager.CHUNK_SIZE - 1) + resp = self.request(range_start=0, range_end=ChannelManager.CHUNK_SIZE - 1) content_range = resp._headers.get("Content-Range") if content_range is None: raise IOError("Missing Content-Range header!") split = Utils.split(content_range, "/") self._size = int(split[1]) - self._chunks = int( - math.ceil(self._size / ChannelManager.CHUNK_SIZE)) + self._chunks = int(math.ceil(self._size / ChannelManager.CHUNK_SIZE)) first_chunk = resp._buffer self._available = [False for _ in range(self._chunks)] self._requested = [False for _ in range(self._chunks)] self._buffer = [bytearray() for _ in range(self._chunks)] - self._internalStream = CdnManager.Streamer.InternalStream( - self, False) + self._internalStream = CdnManager.Streamer.InternalStream(self, False) self._requested[0] = True self.write_chunk(first_chunk, 0, False) - def write_chunk(self, chunk: bytes, chunk_index: int, - cached: bool) -> None: + def write_chunk(self, chunk: bytes, chunk_index: int, cached: bool) -> None: if self._internalStream.is_closed(): return self._session._LOGGER.debug( "Chunk {}/{} completed, cached: {}, stream: {}".format( - chunk_index + 1, self._chunks, cached, self.describe())) + chunk_index + 1, self._chunks, cached, self.describe() + ) + ) self._buffer[chunk_index] = self._audioDecrypt.decrypt_chunk( - chunk_index, chunk) + chunk_index, chunk + ) self._internalStream.notify_chunk_available(chunk_index) def stream(self) -> AbsChunkedInputStream: @@ -229,8 +261,7 @@ class CdnManager: def describe(self) -> str: if self._streamId.is_episode(): - return "episode_gid: {}".format( - self._streamId.get_episode_gid()) + return "episode_gid: {}".format(self._streamId.get_episode_gid()) return "file_id: {}".format(self._streamId.get_file_id()) def decrypt_time_ms(self) -> int: @@ -240,10 +271,9 @@ class CdnManager: resp = self.request(index) self.write_chunk(resp._buffer, index, False) - def request(self, - chunk: int = None, - range_start: int = None, - range_end: int = None) -> CdnManager.InternalResponse: + def request( + self, chunk: int = None, range_start: int = None, range_end: int = None + ) -> CdnManager.InternalResponse: if chunk is None and range_start is None and range_end is None: raise TypeError() @@ -251,12 +281,10 @@ class CdnManager: range_start = ChannelManager.CHUNK_SIZE * chunk range_end = (chunk + 1) * ChannelManager.CHUNK_SIZE - 1 - resp = self._session.client().get(self._cdnUrl._url, - headers={ - "Range": - "bytes={}-{}".format( - range_start, range_end) - }) + resp = self._session.client().get( + self._cdnUrl._url, + headers={"Range": "bytes={}-{}".format(range_start, range_end)}, + ) if resp.status_code != 206: raise IOError(resp.status_code) @@ -291,16 +319,21 @@ class CdnManager: def request_chunk_from_stream(self, index: int) -> None: self.streamer._executorService.submit( - lambda: self.streamer.request_chunk(index)) + lambda: self.streamer.request_chunk(index) + ) def stream_read_halted(self, chunk: int, _time: int) -> None: if self.streamer._haltListener is not None: self.streamer._executorService.submit( lambda: self.streamer._haltListener.stream_read_halted( - chunk, _time)) + chunk, _time + ) + ) def stream_read_resumed(self, chunk: int, _time: int) -> None: if self.streamer._haltListener is not None: self.streamer._executorService.submit( - lambda: self.streamer._haltListener. - stream_read_resumed(chunk, _time)) + lambda: self.streamer._haltListener.stream_read_resumed( + chunk, _time + ) + ) diff --git a/librespot/core/Session.py b/librespot/core/Session.py index b8c3437..5bba924 100644 --- a/librespot/core/Session.py +++ b/librespot/core/Session.py @@ -31,30 +31,266 @@ import typing class Session(Closeable, SubListener, DealerClient.MessageListener): _LOGGER: logging = logging.getLogger(__name__) - _serverKey: bytes = bytes([ - 0xac, 0xe0, 0x46, 0x0b, 0xff, 0xc2, 0x30, 0xaf, 0xf4, 0x6b, 0xfe, 0xc3, - 0xbf, 0xbf, 0x86, 0x3d, 0xa1, 0x91, 0xc6, 0xcc, 0x33, 0x6c, 0x93, 0xa1, - 0x4f, 0xb3, 0xb0, 0x16, 0x12, 0xac, 0xac, 0x6a, 0xf1, 0x80, 0xe7, 0xf6, - 0x14, 0xd9, 0x42, 0x9d, 0xbe, 0x2e, 0x34, 0x66, 0x43, 0xe3, 0x62, 0xd2, - 0x32, 0x7a, 0x1a, 0x0d, 0x92, 0x3b, 0xae, 0xdd, 0x14, 0x02, 0xb1, 0x81, - 0x55, 0x05, 0x61, 0x04, 0xd5, 0x2c, 0x96, 0xa4, 0x4c, 0x1e, 0xcc, 0x02, - 0x4a, 0xd4, 0xb2, 0x0c, 0x00, 0x1f, 0x17, 0xed, 0xc2, 0x2f, 0xc4, 0x35, - 0x21, 0xc8, 0xf0, 0xcb, 0xae, 0xd2, 0xad, 0xd7, 0x2b, 0x0f, 0x9d, 0xb3, - 0xc5, 0x32, 0x1a, 0x2a, 0xfe, 0x59, 0xf3, 0x5a, 0x0d, 0xac, 0x68, 0xf1, - 0xfa, 0x62, 0x1e, 0xfb, 0x2c, 0x8d, 0x0c, 0xb7, 0x39, 0x2d, 0x92, 0x47, - 0xe3, 0xd7, 0x35, 0x1a, 0x6d, 0xbd, 0x24, 0xc2, 0xae, 0x25, 0x5b, 0x88, - 0xff, 0xab, 0x73, 0x29, 0x8a, 0x0b, 0xcc, 0xcd, 0x0c, 0x58, 0x67, 0x31, - 0x89, 0xe8, 0xbd, 0x34, 0x80, 0x78, 0x4a, 0x5f, 0xc9, 0x6b, 0x89, 0x9d, - 0x95, 0x6b, 0xfc, 0x86, 0xd7, 0x4f, 0x33, 0xa6, 0x78, 0x17, 0x96, 0xc9, - 0xc3, 0x2d, 0x0d, 0x32, 0xa5, 0xab, 0xcd, 0x05, 0x27, 0xe2, 0xf7, 0x10, - 0xa3, 0x96, 0x13, 0xc4, 0x2f, 0x99, 0xc0, 0x27, 0xbf, 0xed, 0x04, 0x9c, - 0x3c, 0x27, 0x58, 0x04, 0xb6, 0xb2, 0x19, 0xf9, 0xc1, 0x2f, 0x02, 0xe9, - 0x48, 0x63, 0xec, 0xa1, 0xb6, 0x42, 0xa0, 0x9d, 0x48, 0x25, 0xf8, 0xb3, - 0x9d, 0xd0, 0xe8, 0x6a, 0xf9, 0x48, 0x4d, 0xa1, 0xc2, 0xba, 0x86, 0x30, - 0x42, 0xea, 0x9d, 0xb3, 0x08, 0x6c, 0x19, 0x0e, 0x48, 0xb3, 0x9d, 0x66, - 0xeb, 0x00, 0x06, 0xa2, 0x5a, 0xee, 0xa1, 0x1b, 0x13, 0x87, 0x3c, 0xd7, - 0x19, 0xe6, 0x55, 0xbd - ]) + _serverKey: bytes = bytes( + [ + 0xAC, + 0xE0, + 0x46, + 0x0B, + 0xFF, + 0xC2, + 0x30, + 0xAF, + 0xF4, + 0x6B, + 0xFE, + 0xC3, + 0xBF, + 0xBF, + 0x86, + 0x3D, + 0xA1, + 0x91, + 0xC6, + 0xCC, + 0x33, + 0x6C, + 0x93, + 0xA1, + 0x4F, + 0xB3, + 0xB0, + 0x16, + 0x12, + 0xAC, + 0xAC, + 0x6A, + 0xF1, + 0x80, + 0xE7, + 0xF6, + 0x14, + 0xD9, + 0x42, + 0x9D, + 0xBE, + 0x2E, + 0x34, + 0x66, + 0x43, + 0xE3, + 0x62, + 0xD2, + 0x32, + 0x7A, + 0x1A, + 0x0D, + 0x92, + 0x3B, + 0xAE, + 0xDD, + 0x14, + 0x02, + 0xB1, + 0x81, + 0x55, + 0x05, + 0x61, + 0x04, + 0xD5, + 0x2C, + 0x96, + 0xA4, + 0x4C, + 0x1E, + 0xCC, + 0x02, + 0x4A, + 0xD4, + 0xB2, + 0x0C, + 0x00, + 0x1F, + 0x17, + 0xED, + 0xC2, + 0x2F, + 0xC4, + 0x35, + 0x21, + 0xC8, + 0xF0, + 0xCB, + 0xAE, + 0xD2, + 0xAD, + 0xD7, + 0x2B, + 0x0F, + 0x9D, + 0xB3, + 0xC5, + 0x32, + 0x1A, + 0x2A, + 0xFE, + 0x59, + 0xF3, + 0x5A, + 0x0D, + 0xAC, + 0x68, + 0xF1, + 0xFA, + 0x62, + 0x1E, + 0xFB, + 0x2C, + 0x8D, + 0x0C, + 0xB7, + 0x39, + 0x2D, + 0x92, + 0x47, + 0xE3, + 0xD7, + 0x35, + 0x1A, + 0x6D, + 0xBD, + 0x24, + 0xC2, + 0xAE, + 0x25, + 0x5B, + 0x88, + 0xFF, + 0xAB, + 0x73, + 0x29, + 0x8A, + 0x0B, + 0xCC, + 0xCD, + 0x0C, + 0x58, + 0x67, + 0x31, + 0x89, + 0xE8, + 0xBD, + 0x34, + 0x80, + 0x78, + 0x4A, + 0x5F, + 0xC9, + 0x6B, + 0x89, + 0x9D, + 0x95, + 0x6B, + 0xFC, + 0x86, + 0xD7, + 0x4F, + 0x33, + 0xA6, + 0x78, + 0x17, + 0x96, + 0xC9, + 0xC3, + 0x2D, + 0x0D, + 0x32, + 0xA5, + 0xAB, + 0xCD, + 0x05, + 0x27, + 0xE2, + 0xF7, + 0x10, + 0xA3, + 0x96, + 0x13, + 0xC4, + 0x2F, + 0x99, + 0xC0, + 0x27, + 0xBF, + 0xED, + 0x04, + 0x9C, + 0x3C, + 0x27, + 0x58, + 0x04, + 0xB6, + 0xB2, + 0x19, + 0xF9, + 0xC1, + 0x2F, + 0x02, + 0xE9, + 0x48, + 0x63, + 0xEC, + 0xA1, + 0xB6, + 0x42, + 0xA0, + 0x9D, + 0x48, + 0x25, + 0xF8, + 0xB3, + 0x9D, + 0xD0, + 0xE8, + 0x6A, + 0xF9, + 0x48, + 0x4D, + 0xA1, + 0xC2, + 0xBA, + 0x86, + 0x30, + 0x42, + 0xEA, + 0x9D, + 0xB3, + 0x08, + 0x6C, + 0x19, + 0x0E, + 0x48, + 0xB3, + 0x9D, + 0x66, + 0xEB, + 0x00, + 0x06, + 0xA2, + 0x5A, + 0xEE, + 0xA1, + 0x1B, + 0x13, + 0x87, + 0x3C, + 0xD7, + 0x19, + 0xE6, + 0x55, + 0xBD, + ] + ) _keys: DiffieHellman = None _inner: Session.Inner = None _scheduler: sched.scheduler = sched.scheduler(time.time) @@ -92,8 +328,9 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self._conn = Session.ConnectionHolder.create(addr, inner.conf) self._client = Session._create_client(self._inner.conf) - self._LOGGER.info("Created new session! device_id: {}, ap: {}".format( - inner.device_id, addr)) + self._LOGGER.info( + "Created new session! device_id: {}, ap: {}".format(inner.device_id, addr) + ) @staticmethod def _create_client(conf: Session.Configuration) -> requests.Session: @@ -101,14 +338,16 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): if conf.proxyAuth and conf.proxyType is not Proxy.Type.DIRECT: if conf.proxyAuth: proxy_setting = [ - conf.proxyUsername, conf.proxyPassword, conf.proxyAddress, - conf.proxyPort + conf.proxyUsername, + conf.proxyPassword, + conf.proxyAddress, + conf.proxyPort, ] else: proxy_setting = [conf.proxyAddress, conf.proxyPort] client.proxies = { "http": "{}:{}@{}:{}".format(*proxy_setting), - "https": "{}:{}@{}:{}".format(*proxy_setting) + "https": "{}:{}@{}:{}".format(*proxy_setting), } return client @@ -119,7 +358,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): if (lo & 0x80) == 0: return lo hi = buffer[1] - return lo & 0x7f | hi << 7 + return lo & 0x7F | hi << 7 def client(self) -> requests.Session: return self._client @@ -133,14 +372,15 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): client_hello = Keyexchange.ClientHello( build_info=Version.standard_build_info(), - 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_array(), server_keys_known=1), ), + gc=self._keys.public_key_array(), server_keys_known=1 + ), + ), client_nonce=nonce, - padding=bytes([0x1e])) + padding=bytes([0x1E]), + ) client_hello_bytes = client_hello.SerializeToString() length = 2 + 4 + len(client_hello_bytes) @@ -166,20 +406,23 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): ap_response_message.ParseFromString(buffer) shared_key = Utils.to_byte_array( self._keys.compute_shared_key( - ap_response_message.challenge.login_crypto_challenge. - diffie_hellman.gs)) + ap_response_message.challenge.login_crypto_challenge.diffie_hellman.gs + ) + ) # Check gs_signature rsa = RSA.construct((int.from_bytes(self._serverKey, "big"), 65537)) pkcs1_v1_5 = PKCS1_v1_5.new(rsa) sha1 = SHA1.new() - sha1.update(ap_response_message.challenge.login_crypto_challenge. - diffie_hellman.gs) + sha1.update( + ap_response_message.challenge.login_crypto_challenge.diffie_hellman.gs + ) # noinspection PyTypeChecker if not pkcs1_v1_5.verify( - sha1, ap_response_message.challenge.login_crypto_challenge. - diffie_hellman.gs_signature): + sha1, + ap_response_message.challenge.login_crypto_challenge.diffie_hellman.gs_signature, + ): raise RuntimeError("Failed signature check!") # Solve challenge @@ -201,12 +444,14 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): client_response_plaintext = Keyexchange.ClientResponsePlaintext( login_crypto_response=Keyexchange.LoginCryptoResponseUnion( diffie_hellman=Keyexchange.LoginCryptoDiffieHellmanResponse( - hmac=challenge)), + hmac=challenge + ) + ), pow_response=Keyexchange.PoWResponseUnion(), - crypto_response=Keyexchange.CryptoResponseUnion()) - - client_response_plaintext_bytes = client_response_plaintext.SerializeToString( + crypto_response=Keyexchange.CryptoResponseUnion(), ) + + client_response_plaintext_bytes = client_response_plaintext.SerializeToString() length = 4 + len(client_response_plaintext_bytes) self._conn.write_int(length) self._conn.write(client_response_plaintext_bytes) @@ -216,8 +461,12 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self._conn.set_timeout(1) scrap = self._conn.read(4) if 4 == len(scrap): - length = (scrap[0] << 24) | (scrap[1] << 16) | ( - scrap[2] << 8) | (scrap[3] & 0xff) + length = ( + (scrap[0] << 24) + | (scrap[1] << 16) + | (scrap[2] << 8) + | (scrap[3] & 0xFF) + ) payload = self._conn.read(length - 4) failed = Keyexchange.APResponseMessage() failed.ParseFromString(payload) @@ -234,8 +483,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self._LOGGER.info("Connection successfully!") - def _authenticate(self, - credentials: Authentication.LoginCredentials) -> None: + def _authenticate(self, credentials: Authentication.LoginCredentials) -> None: self._authenticate_partial(credentials, False) with self._authLock: @@ -245,8 +493,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self._channelManager = ChannelManager(self) self._api = ApiClient.ApiClient(self) self._cdnManager = CdnManager(self) - self._contentFeeder = PlayableContentFeeder.PlayableContentFeeder( - self) + self._contentFeeder = PlayableContentFeeder.PlayableContentFeeder(self) self._cacheManager = CacheManager(self) self._dealer = DealerClient(self) self._search = SearchManager.SearchManager(self) @@ -259,15 +506,15 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): # TimeProvider.init(self) self._dealer.connect() - self._LOGGER.info("Authenticated as {}!".format( - self._apWelcome.canonical_username)) + self._LOGGER.info( + "Authenticated as {}!".format(self._apWelcome.canonical_username) + ) self.mercury().interested_in("spotify:user:attributes:update", self) - self.dealer().add_message_listener( - self, "hm://connect-state/v1/connect/logout") + self.dealer().add_message_listener(self, "hm://connect-state/v1/connect/logout") - def _authenticate_partial(self, - credentials: Authentication.LoginCredentials, - remove_lock: bool) -> None: + def _authenticate_partial( + self, credentials: Authentication.LoginCredentials, remove_lock: bool + ) -> None: if self._cipherPair is None: raise RuntimeError("Connection not established!") @@ -277,11 +524,14 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): os=Authentication.Os.OS_UNKNOWN, cpu_family=Authentication.CpuFamily.CPU_UNKNOWN, system_information_string=Version.system_info_string(), - device_id=self._inner.device_id), - version_string=Version.version_string()) + device_id=self._inner.device_id, + ), + version_string=Version.version_string(), + ) - self._send_unchecked(Packet.Type.login, - client_response_encrypted.SerializeToString()) + self._send_unchecked( + Packet.Type.login, client_response_encrypted.SerializeToString() + ) packet = self._cipherPair.receive_encoded(self._conn) if packet.is_cmd(Packet.Type.ap_welcome): @@ -297,8 +547,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): preferred_locale += bytes([0x00, 0x00, 0x10, 0x00, 0x02]) preferred_locale += "preferred-locale".encode() preferred_locale += self._inner.preferred_locale.encode() - self._send_unchecked(Packet.Type.preferred_locale, - preferred_locale) + self._send_unchecked(Packet.Type.preferred_locale, preferred_locale) if remove_lock: with self._authLock: @@ -308,7 +557,8 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): if self._inner.conf.store_credentials: reusable = self._apWelcome.reusable_auth_credentials reusable_type = Authentication.AuthenticationType.Name( - self._apWelcome.reusable_auth_credentials_type) + self._apWelcome.reusable_auth_credentials_type + ) if self._inner.conf.stored_credentials_file is None: raise TypeError() @@ -318,8 +568,10 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): { "username": self._apWelcome.canonical_username, "credentials": base64.b64encode(reusable).decode(), - "type": reusable_type - }, f) + "type": reusable_type, + }, + f, + ) elif packet.is_cmd(Packet.Type.auth_failure): ap_login_failed = Keyexchange.APLoginFailed() @@ -329,8 +581,9 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): raise RuntimeError("Unknown CMD 0x" + packet.cmd.hex()) def close(self) -> None: - 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 @@ -383,11 +636,9 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): listener.on_closed() self._closeListeners: typing.List[Session.CloseListener] = [] - self._reconnectionListeners: typing.List[ - Session.ReconnectionListener] = [] + self._reconnectionListeners: typing.List[Session.ReconnectionListener] = [] - 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 _send_unchecked(self, cmd: bytes, payload: bytes) -> None: self._cipherPair.send_encoded(self._conn, cmd, payload) @@ -530,16 +781,21 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self._receiver.stop() self._conn = Session.ConnectionHolder.create( - ApResolver.get_random_accesspoint(), self._inner.conf) + ApResolver.get_random_accesspoint(), self._inner.conf + ) self._connect() self._authenticate_partial( Authentication.LoginCredentials( typ=self._apWelcome.reusable_auth_credentials_type, username=self._apWelcome.canonical_username, - auth_data=self._apWelcome.reusable_auth_credentials), True) + auth_data=self._apWelcome.reusable_auth_credentials, + ), + True, + ) - self._LOGGER.info("Re-authenticated as {}!".format( - self._apWelcome.canonical_username)) + self._LOGGER.info( + "Re-authenticated as {}!".format(self._apWelcome.canonical_username) + ) with self._reconnectionListenersLock: for listener in self._reconnectionListeners: @@ -549,13 +805,11 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): if listener not in self._closeListeners: self._closeListeners.append(listener) - def add_reconnection_listener(self, - listener: ReconnectionListener) -> None: + def add_reconnection_listener(self, listener: ReconnectionListener) -> None: if listener not in self._reconnectionListeners: self._reconnectionListeners.append(listener) - def remove_reconnection_listener(self, - listener: ReconnectionListener) -> None: + def remove_reconnection_listener(self, listener: ReconnectionListener) -> None: self._reconnectionListeners.remove(listener) def _parse_product_info(self, data) -> None: @@ -572,12 +826,14 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): for i in range(len(product)): self._userAttributes[product[i].tag] = product[i].text - self._LOGGER.debug("Parsed product info: {}".format( - self._userAttributes)) + self._LOGGER.debug("Parsed product info: {}".format(self._userAttributes)) def get_user_attribute(self, key: str, fallback: str = None) -> str: - return self._userAttributes.get(key) if self._userAttributes.get( - key) is not None else fallback + return ( + self._userAttributes.get(key) + if self._userAttributes.get(key) is not None + else fallback + ) def event(self, resp: MercuryClient.Response) -> None: if resp.uri == "spotify:user:attributes:update": @@ -586,11 +842,13 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): for pair in attributes_update.pairs_list: self._userAttributes[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 on_message(self, uri: str, headers: typing.Dict[str, str], - payload: bytes) -> None: + def on_message( + self, uri: str, headers: typing.Dict[str, str], payload: bytes + ) -> None: if uri == "hm://connect-state/v1/connect/logout": self.close() @@ -612,18 +870,21 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): conf = None preferred_locale: str = None - def __init__(self, - device_type: Connect.DeviceType, - device_name: str, - preferred_locale: str, - conf: Session.Configuration, - device_id: str = None): + def __init__( + self, + device_type: Connect.DeviceType, + device_name: str, + preferred_locale: str, + conf: Session.Configuration, + device_id: str = None, + ): self.preferred_locale = preferred_locale 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 Utils.random_hex_string( - 40) + self.device_id = ( + device_id if device_id is not None else Utils.random_hex_string(40) + ) class AbsBuilder: conf = None @@ -657,7 +918,8 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): 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 @@ -667,8 +929,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): def stored(self): pass - def stored_file(self, - stored_credentials: str = None) -> Session.Builder: + def stored_file(self, stored_credentials: str = None) -> Session.Builder: if stored_credentials is None: stored_credentials = self.conf.stored_credentials_file if os.path.isfile(stored_credentials): @@ -680,10 +941,10 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): 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"])) + auth_data=base64.b64decode(obj["credentials"]), + ) except KeyError: pass @@ -693,7 +954,8 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self.login_credentials = Authentication.LoginCredentials( username=username, typ=Authentication.AuthenticationType.AUTHENTICATION_USER_PASS, - auth_data=password.encode()) + auth_data=password.encode(), + ) return self def create(self) -> Session: @@ -701,10 +963,15 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): raise RuntimeError("You must select an authentication method.") session = Session( - Session.Inner(self.device_type, self.device_name, - self.preferred_locale, self.conf, - self.device_id), - ApResolver.get_random_accesspoint()) + Session.Inner( + self.device_type, + self.device_name, + self.preferred_locale, + self.conf, + self.device_id, + ), + ApResolver.get_random_accesspoint(), + ) session._connect() session._authenticate(self.login_credentials) return session @@ -731,12 +998,22 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): # Fetching retry_on_chunk_error: bool - def __init__(self, proxy_enabled: bool, proxy_type: Proxy.Type, - proxy_address: str, proxy_port: int, proxy_auth: bool, - proxy_username: str, proxy_password: str, - cache_enabled: bool, cache_dir: str, - do_cache_clean_up: bool, store_credentials: bool, - stored_credentials_file: str, retry_on_chunk_error: bool): + def __init__( + self, + proxy_enabled: bool, + proxy_type: Proxy.Type, + proxy_address: str, + proxy_port: int, + proxy_auth: bool, + proxy_username: str, + proxy_password: str, + cache_enabled: bool, + cache_dir: str, + do_cache_clean_up: bool, + store_credentials: bool, + stored_credentials_file: str, + retry_on_chunk_error: bool, + ): self.proxyEnabled = proxy_enabled self.proxyType = proxy_type self.proxyAddress = proxy_address @@ -768,93 +1045,99 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): # 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 = None def set_proxy_enabled( - self, - proxy_enabled: bool) -> Session.Configuration.Builder: + self, proxy_enabled: bool + ) -> Session.Configuration.Builder: self.proxyEnabled = proxy_enabled return self def set_proxy_type( - self, - proxy_type: Proxy.Type) -> Session.Configuration.Builder: + self, proxy_type: Proxy.Type + ) -> Session.Configuration.Builder: self.proxyType = proxy_type return self def set_proxy_address( - self, proxy_address: str) -> Session.Configuration.Builder: + self, proxy_address: str + ) -> Session.Configuration.Builder: self.proxyAddress = proxy_address return self - def set_proxy_auth( - self, proxy_auth: bool) -> Session.Configuration.Builder: + def set_proxy_auth(self, proxy_auth: bool) -> Session.Configuration.Builder: self.proxyAuth = proxy_auth return self def set_proxy_username( - self, - proxy_username: str) -> Session.Configuration.Builder: + self, proxy_username: str + ) -> Session.Configuration.Builder: self.proxyUsername = proxy_username return self def set_proxy_password( - self, - proxy_password: str) -> Session.Configuration.Builder: + self, proxy_password: str + ) -> Session.Configuration.Builder: self.proxyPassword = proxy_password return self def set_cache_enabled( - self, - cache_enabled: bool) -> Session.Configuration.Builder: + self, cache_enabled: bool + ) -> Session.Configuration.Builder: 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: self.cache_dir = cache_dir 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: self.do_cache_clean_up = do_cache_clean_up return self def set_store_credentials( - self, - store_credentials: bool) -> Session.Configuration.Builder: + self, store_credentials: bool + ) -> Session.Configuration.Builder: self.store_credentials = store_credentials return self def set_stored_credential_file( - self, stored_credential_file: str + self, stored_credential_file: str ) -> Session.Configuration.Builder: self.stored_credentials_file = stored_credential_file return self def set_retry_on_chunk_error( - self, retry_on_chunk_error: bool + self, retry_on_chunk_error: bool ) -> Session.Configuration.Builder: self.retry_on_chunk_error = retry_on_chunk_error return self def build(self) -> Session.Configuration: return Session.Configuration( - self.proxyEnabled, self.proxyType, self.proxyAddress, - self.proxyPort, self.proxyAuth, self.proxyUsername, - self.proxyPassword, self.cache_enabled, self.cache_dir, - self.do_cache_clean_up, self.store_credentials, - self.stored_credentials_file, self.retry_on_chunk_error) + self.proxyEnabled, + self.proxyType, + self.proxyAddress, + self.proxyPort, + self.proxyAuth, + self.proxyUsername, + self.proxyPassword, + self.cache_enabled, + self.cache_dir, + self.do_cache_clean_up, + self.store_credentials, + self.stored_credentials_file, + self.retry_on_chunk_error, + ) 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 Accumulator: buffer: bytes = bytes() @@ -881,8 +1164,7 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): self.sock = sock @staticmethod - def create(addr: str, - conf: Session.Configuration) -> Session.ConnectionHolder: + def create(addr: str, conf: Session.Configuration) -> Session.ConnectionHolder: ap_addr = addr.split(":")[0] ap_port = int(addr.split(":")[1]) if not conf.proxyEnabled or conf.proxyType is Proxy.Type.DIRECT: @@ -894,11 +1176,9 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): sock = socket.socket() sock.connect((conf.proxyAddress, conf.proxyPort)) - sock.send("CONNECT {}:{} HTTP/1.0\n".format(ap_addr, - ap_port).encode()) + sock.send("CONNECT {}:{} HTTP/1.0\n".format(ap_addr, ap_port).encode()) if conf.proxyAuth: - sock.send( - "Proxy-Authorization: {}\n".format(None).encode()) + sock.send("Proxy-Authorization: {}\n".format(None).encode()) sock.send(b"\n") @@ -963,18 +1243,21 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): try: # noinspection PyProtectedMember packet = self.session._cipherPair.receive_encoded( - self.session._conn) + self.session._conn + ) cmd = Packet.Type.parse(packet.cmd) if cmd is None: self.session._LOGGER.info( - "Skipping unknown command cmd: 0x{}, payload: {}". - format(Utils.bytes_to_hex(packet.cmd), - packet.payload)) + "Skipping unknown command cmd: 0x{}, payload: {}".format( + Utils.bytes_to_hex(packet.cmd), packet.payload + ) + ) continue except RuntimeError as ex: if self.running: self.session._LOGGER.fatal( - "Failed reading packet! {}".format(ex)) + "Failed reading packet! {}".format(ex) + ) # noinspection PyProtectedMember self.session._reconnect() break @@ -985,17 +1268,18 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): # noinspection PyProtectedMember if self.session._scheduledReconnect is not None: # noinspection PyProtectedMember - self.session._scheduler.cancel( - self.session._scheduledReconnect) + self.session._scheduler.cancel(self.session._scheduledReconnect) def anonymous(): self.session._LOGGER.warning( - "Socket timed out. Reconnecting...") + "Socket timed out. Reconnecting..." + ) self.session._reconnect() # noinspection PyProtectedMember 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) continue if cmd == Packet.Type.pong_ack: @@ -1003,8 +1287,8 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): if cmd == Packet.Type.country_code: self.session.country_code = packet.payload.decode() self.session._LOGGER.info( - "Received country_code: {}".format( - self.session.country_code)) + "Received country_code: {}".format(self.session.country_code) + ) continue if cmd == Packet.Type.license_version: license_version = BytesInputStream(packet.payload) @@ -1013,34 +1297,40 @@ class Session(Closeable, SubListener, DealerClient.MessageListener): 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) + ) continue if cmd == Packet.Type.unknown_0x10: - self.session._LOGGER.debug("Received 0x10: {}".format( - Utils.bytes_to_hex(packet.payload))) + self.session._LOGGER.debug( + "Received 0x10: {}".format(Utils.bytes_to_hex(packet.payload)) + ) continue - if cmd == Packet.Type.mercury_sub or \ - cmd == Packet.Type.mercury_unsub or \ - cmd == Packet.Type.mercury_event or \ - cmd == Packet.Type.mercury_req: + if ( + cmd == Packet.Type.mercury_sub + or cmd == Packet.Type.mercury_unsub + or cmd == Packet.Type.mercury_event + or cmd == Packet.Type.mercury_req + ): self.session.mercury().dispatch(packet) continue - if cmd == Packet.Type.aes_key or \ - cmd == Packet.Type.aes_key_error: + if cmd == Packet.Type.aes_key or cmd == Packet.Type.aes_key_error: self.session.audio_key().dispatch(packet) continue - if cmd == Packet.Type.channel_error or \ - cmd == Packet.Type.stream_chunk_res: + if ( + cmd == Packet.Type.channel_error + or cmd == Packet.Type.stream_chunk_res + ): self.session.channel().dispatch(packet) continue if cmd == Packet.Type.product_info: # noinspection PyProtectedMember self.session._parse_product_info(packet.payload) continue - self.session._LOGGER.info("Skipping {}".format( - Utils.bytes_to_hex(cmd))) + self.session._LOGGER.info("Skipping {}".format(Utils.bytes_to_hex(cmd))) self.session._LOGGER.debug("Session.Receiver stopped")