From e4aed25db86359d25d55208a35be4c537d355874 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 18 Dec 2025 20:31:56 +0100 Subject: [PATCH] Prepare V0.2 --- librespot/audio/__init__.py | 188 ++++++++++-------- .../__pycache__/__init__.cpython-314.pyc | Bin 78286 -> 79868 bytes 2 files changed, 105 insertions(+), 83 deletions(-) diff --git a/librespot/audio/__init__.py b/librespot/audio/__init__.py index e13e87c..22a263f 100644 --- a/librespot/audio/__init__.py +++ b/librespot/audio/__init__.py @@ -23,6 +23,7 @@ import urllib.parse import os import json import requests +import atexit if typing.TYPE_CHECKING: from librespot.core import Session @@ -52,6 +53,33 @@ spoticlub_client_serial: typing.Optional[str] = None spoticlub_loaded_logged: bool = False ######################################## +### SPOTICLUB CLIENT SERIAL TRACKING (DO NOT EDIT) ### +spoticlub_client_serial: typing.Optional[str] = None +spoticlub_loaded_logged: bool = False + +def _spoticlub_notify_session_done() -> None: + global spoticlub_user, spoticlub_password, spoticlub_client_serial + try: + if not server_url or not spoticlub_user or not spoticlub_client_serial: + return + base_url = server_url.rsplit("/", 1)[0] + url = base_url + "/client_done" + payload = { + "user": spoticlub_user, + "password": spoticlub_password, + "client_serial": spoticlub_client_serial, + } + requests.post(url, json=payload, timeout=5) + except Exception: + AudioKeyManager.logger.debug( + "[SpotiClub API] Failed to notify server of session completion", + exc_info=True, + ) + + +atexit.register(_spoticlub_notify_session_done) +######################################## + class LoadedStream(GeneralAudioStream): def __init__(self, data: bytes): super().__init__() @@ -327,17 +355,6 @@ class AudioKeyManager(PacketsReceiver, Closeable): spoticlub_loaded_logged = True print(f"\n[SpotiClub API] Plugin Loaded! Welcome {spoticlub_user}\n") - # Try to show a Zotify loader while we fetch the remote audio key. - # The import is done lazily here to avoid hard circular imports. - loader = None - try: - from zotify.loader import Loader # type: ignore - from zotify.termoutput import PrintChannel # type: ignore - loader = Loader(PrintChannel.PROGRESS_INFO, "Fetching audio key...") - loader.start() - except Exception: - loader = None - payload = { "gid": util.bytes_to_hex(gid), "file_id": util.bytes_to_hex(file_id), @@ -350,80 +367,85 @@ class AudioKeyManager(PacketsReceiver, Closeable): tries = 0 last_err: typing.Optional[Exception] = None - try: - while True: - tries += 1 + while True: + tries += 1 + audio_key_loader = None + try: try: - resp = requests.post(server_url, json=payload, timeout=AudioKeyManager.audio_key_request_timeout) - - # If another client instance is already active for this - # SpotiClub user, the server will reply with HTTP 423 and - # instruct this client to wait before retrying. - if resp.status_code == 423: - try: - data = resp.json() - except Exception: # noqa: BLE001 - data = {} - retry_after = data.get("retry_after", 60) - if not isinstance(retry_after, (int, float)): - retry_after = 10 - print( - f"[SpotiClub API] Another client is already using this account. Waiting {int(retry_after)}s before retrying..." - ) - self.logger.info( - "[SpotiClub API] Queued client for user %s; waiting %ds before retry", - spoticlub_user, - int(retry_after), - ) - time.sleep(float(retry_after)) - # Do NOT count this as a failure towards the max retries. - continue - - # Explicit handling for bad logins so we don't just retry. - if resp.status_code == 401: - print( - "[SpotiClub API][BAD_LOGIN] It seems your credentials aren't recognized by the API. Please ensure you have entered them correctly, or contact a DEV if you are absolutely certain of their validity." - ) - raise SystemExit(1) - - if resp.status_code != 200: - raise RuntimeError(f"[SpotiClub API] Sorry, the API returned the unexpected code {resp.status_code}: {resp.text}") - - data = resp.json() - key_hex = data.get("key") - if not isinstance(key_hex, str): - raise RuntimeError("[SpotiClub API] Sorry, API response missing 'key'") - - country = data.get("country") - if isinstance(country, str): - if AudioKeyManager._spoticlub_current_country != country: - AudioKeyManager._spoticlub_current_country = country - print(f"[SpotiClub API] Received {country} as the download country\n\n") - - new_serial = data.get("client_serial") - if isinstance(new_serial, str) and new_serial: - spoticlub_client_serial = new_serial - - key_bytes = util.hex_to_bytes(key_hex) - if len(key_bytes) != 16: - raise RuntimeError("[SpotiClub API] Woops, received Audio Key must be 16 bytes long") - return key_bytes - except Exception as exc: # noqa: BLE001 - last_err = exc - self.logger.warning("[SpotiClub API] Retrying the contact... (try %d): %s", tries, exc) - if not retry or tries >= 3: - break - time.sleep(5) - - raise RuntimeError( - "Failed fetching Audio Key from API for gid: {}, fileId: {} (last error: {})".format( - util.bytes_to_hex(gid), util.bytes_to_hex(file_id), last_err)) - finally: - if loader is not None: - try: - loader.stop() + from zotify.loader import Loader + from zotify.termoutput import PrintChannel + audio_key_loader = Loader(PrintChannel.PROGRESS_INFO, "Fetching audio key...").start() except Exception: - pass + audio_key_loader = None + + resp = requests.post(server_url, json=payload, timeout=AudioKeyManager.audio_key_request_timeout) + + # If another client instance is already active for this + # SpotiClub user, we will will reply with HTTP 423 and + # instruct this client to wait before retrying. + if resp.status_code == 423: + try: + data = resp.json() + except Exception: # noqa: BLE001 + data = {} + retry_after = data.get("retry_after", 60) + if not isinstance(retry_after, (int, float)): + retry_after = 10 + print( + f"\n[SpotiClub API] Another client is already using this account. Waiting {int(retry_after)}s before retrying...\n" + ) + self.logger.info( + "[SpotiClub API] Queued client for user %s; waiting %ds before retry", + spoticlub_user, + int(retry_after), + ) + time.sleep(float(retry_after)) + continue + + if resp.status_code == 401: + print( + "\n[SpotiClub API][BAD_LOGIN] It seems your credentials aren't recognized by the API. Please ensure you have entered them correctly, or contact a DEV if you are absolutely certain of their validity." + ) + raise SystemExit(1) + + if resp.status_code != 200: + raise RuntimeError(f"\n[SpotiClub API] Sorry, the API returned the unexpected code {resp.status_code}: {resp.text}\n") + + data = resp.json() + key_hex = data.get("key") + if not isinstance(key_hex, str): + raise RuntimeError("\n[SpotiClub API] Sorry, API response missing 'key'\n") + + country = data.get("country") + if isinstance(country, str): + if AudioKeyManager._spoticlub_current_country != country: + AudioKeyManager._spoticlub_current_country = country + print(f"\n\n[SpotiClub API] Received {country} as the download country\n\n") + + new_serial = data.get("client_serial") + if isinstance(new_serial, str) and new_serial: + spoticlub_client_serial = new_serial + + key_bytes = util.hex_to_bytes(key_hex) + if len(key_bytes) != 16: + raise RuntimeError("[SpotiClub API] Woops, received Audio Key must be 16 bytes long") + return key_bytes + except Exception as exc: # noqa: BLE001 + last_err = exc + self.logger.warning("[SpotiClub API] Retrying the contact... (try %d): %s", tries, exc) + if not retry or tries >= 3: + break + time.sleep(5) + finally: + if audio_key_loader is not None: + try: + audio_key_loader.stop() + except Exception: + pass + + raise RuntimeError( + "Failed fetching Audio Key from API for gid: {}, fileId: {} (last error: {})".format( + util.bytes_to_hex(gid), util.bytes_to_hex(file_id), last_err)) class Callback: diff --git a/librespot/audio/__pycache__/__init__.cpython-314.pyc b/librespot/audio/__pycache__/__init__.cpython-314.pyc index d81a74c0b132b47aa33e56b9d71ee10a3cfbc62b..f5ebab4f1dc5ad7d63e6c1ebf9efed482f952d46 100644 GIT binary patch delta 20387 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 delta 18990 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