New Audio Key flow for both Premium/Free plans
Some checks failed
CodeQL / Analyze (python) (push) Has been cancelled
Some checks failed
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
@@ -321,12 +321,6 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
packet.cmd, len(packet.payload)))
|
packet.cmd, len(packet.payload)))
|
||||||
|
|
||||||
def _is_premium_user(self) -> bool:
|
def _is_premium_user(self) -> bool:
|
||||||
"""Best-effort check whether the current Spotify account is Premium.
|
|
||||||
|
|
||||||
We rely on Session user attributes populated from the AP product_info packet.
|
|
||||||
Historically, the attribute named "type" contains values like "premium", "free",
|
|
||||||
"open", etc.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
raw = (
|
raw = (
|
||||||
self.__session.get_user_attribute("type")
|
self.__session.get_user_attribute("type")
|
||||||
@@ -355,16 +349,6 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
retry: bool = True,
|
retry: bool = True,
|
||||||
_attempt: int = 1,
|
_attempt: int = 1,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
"""Request the audio key directly from Spotify (Premium accounts).
|
|
||||||
|
|
||||||
This mirrors the working logic from the standalone AudioKeyManager
|
|
||||||
helper in temp.py: it sends a request_key packet with the payload
|
|
||||||
[file_id][gid][seq][zero_short], waits for a response, and retries
|
|
||||||
a limited number of times with simple backoff.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Allocate a new sequence number for this request and register
|
|
||||||
# the callback that will receive the response.
|
|
||||||
with self.__seq_holder_lock:
|
with self.__seq_holder_lock:
|
||||||
seq = AudioKeyManager.__seq_holder
|
seq = AudioKeyManager.__seq_holder
|
||||||
AudioKeyManager.__seq_holder += 1
|
AudioKeyManager.__seq_holder += 1
|
||||||
@@ -389,18 +373,15 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
if key is not None:
|
if key is not None:
|
||||||
return key
|
return key
|
||||||
|
|
||||||
# No key returned; treat this like a transient failure.
|
|
||||||
last_err = RuntimeError("Audio key request returned no key")
|
last_err = RuntimeError("Audio key request returned no key")
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
last_err = exc
|
last_err = exc
|
||||||
finally:
|
finally:
|
||||||
# Ensure we don't leak callbacks if anything goes wrong.
|
|
||||||
try:
|
try:
|
||||||
AudioKeyManager.__callbacks.pop(seq, None)
|
AudioKeyManager.__callbacks.pop(seq, None)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Decide whether to retry or give up.
|
|
||||||
if retry and _attempt < self.max_spotify_audio_key_retries:
|
if retry and _attempt < self.max_spotify_audio_key_retries:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
"Spotify audio key request failed (attempt %d/%d): %s",
|
"Spotify audio key request failed (attempt %d/%d): %s",
|
||||||
@@ -408,7 +389,6 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
self.max_spotify_audio_key_retries,
|
self.max_spotify_audio_key_retries,
|
||||||
last_err,
|
last_err,
|
||||||
)
|
)
|
||||||
# Simple linear backoff to avoid hammering the server.
|
|
||||||
time.sleep(5 * _attempt)
|
time.sleep(5 * _attempt)
|
||||||
return self._get_spotify_audio_key(
|
return self._get_spotify_audio_key(
|
||||||
gid,
|
gid,
|
||||||
@@ -417,7 +397,6 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
_attempt=_attempt + 1,
|
_attempt=_attempt + 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Give up after the configured number of attempts.
|
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
"Giving up fetching audio key from Spotify after %d attempts; gid=%s fileId=%s (last error: %s)",
|
"Giving up fetching audio key from Spotify after %d attempts; gid=%s fileId=%s (last error: %s)",
|
||||||
_attempt,
|
_attempt,
|
||||||
@@ -433,14 +412,27 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_audio_key(self,
|
def get_audio_key(
|
||||||
|
self,
|
||||||
gid: bytes,
|
gid: bytes,
|
||||||
file_id: bytes,
|
file_id: bytes,
|
||||||
retry: bool = True) -> bytes:
|
retry: bool = True,
|
||||||
# If the user is Premium, Spotify will return audio keys directly.
|
) -> bytes:
|
||||||
# In that case, do not use the SpotiClub API.
|
|
||||||
if self._is_premium_user():
|
is_premium = self._is_premium_user()
|
||||||
|
|
||||||
|
if is_premium:
|
||||||
|
try:
|
||||||
return self._get_spotify_audio_key(gid, file_id, retry=retry)
|
return self._get_spotify_audio_key(gid, file_id, retry=retry)
|
||||||
|
except Exception as exc: # noqa: BLE001
|
||||||
|
self.logger.warning(
|
||||||
|
"Spotify audio key fetch failed for premium user; falling back to SpotiClub API: %s",
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"\n[AudioKey] Spotify refused or failed to provide the audio key. "
|
||||||
|
"Falling back to SpotiClub API...\n"
|
||||||
|
)
|
||||||
|
|
||||||
global spoticlub_user, spoticlub_password, spoticlub_client_serial, spoticlub_loaded_logged
|
global spoticlub_user, spoticlub_password, spoticlub_client_serial, spoticlub_loaded_logged
|
||||||
if not spoticlub_user or not spoticlub_password or spoticlub_user == "anonymous":
|
if not spoticlub_user or not spoticlub_password or spoticlub_user == "anonymous":
|
||||||
@@ -499,9 +491,6 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
|
|
||||||
resp = requests.post(server_url, json=payload, timeout=AudioKeyManager.audio_key_request_timeout)
|
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:
|
if resp.status_code == 423:
|
||||||
try:
|
try:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
@@ -549,7 +538,6 @@ class AudioKeyManager(PacketsReceiver, Closeable):
|
|||||||
if len(key_bytes) != 16:
|
if len(key_bytes) != 16:
|
||||||
raise RuntimeError("[SpotiClub API] Woops, received Audio Key must be 16 bytes long")
|
raise RuntimeError("[SpotiClub API] Woops, received Audio Key must be 16 bytes long")
|
||||||
|
|
||||||
# After the first successful SpotiClub key fetch, enable the loader for future calls.
|
|
||||||
with _spoticlub_audio_key_loader_lock:
|
with _spoticlub_audio_key_loader_lock:
|
||||||
_spoticlub_audio_key_loader_enabled = True
|
_spoticlub_audio_key_loader_enabled = True
|
||||||
return key_bytes
|
return key_bytes
|
||||||
|
|||||||
Reference in New Issue
Block a user