|
159 | 159 | #!/usr/bin/env python3 |
160 | 160 | """ |
161 | 161 | Author: Michal Szymanski <misiektoja-github@rm-rf.ninja> |
162 | | -v2.9.1 |
| 162 | +v2.9.2 |
163 | 163 |
|
164 | 164 | Tool implementing real-time tracking of Spotify friends music activity: |
165 | 165 | https://github.com/misiektoja/spotify_monitor/ |
|
175 | 175 | spotipy (required since v2.7 due to new Spotify restrictions introduced on 22 Dec 2025) |
176 | 176 | """ |
177 | 177 |
|
178 | | -VERSION = "2.9.1" |
| 178 | +VERSION = "2.9.2" |
179 | 179 |
|
180 | 180 | # API 401 error means sp_dc cookie has expired. Lasts one year. 03/15/2025 |
181 | 181 |
|
@@ -2602,16 +2602,24 @@ def fetch_server_time(session: req.Session, ua: str) -> int: |
2602 | 2602 | return int(parsedate_to_datetime(date_hdr).timestamp()) |
2603 | 2603 |
|
2604 | 2604 |
|
| 2605 | +# Resolves the effective TOTP version, falling back to the highest available when the configured TOTP_VER is missing from SECRET_CIPHER_DICT |
| 2606 | +def resolve_totp_ver() -> int: |
| 2607 | + if not SECRET_CIPHER_DICT: |
| 2608 | + raise SecretsUnavailableError("resolve_totp_ver(): SECRET_CIPHER_DICT is empty") |
| 2609 | + if TOTP_VER and str(TOTP_VER) in SECRET_CIPHER_DICT: |
| 2610 | + return TOTP_VER |
| 2611 | + available = sorted(map(int, SECRET_CIPHER_DICT)) |
| 2612 | + fallback = available[-1] |
| 2613 | + if TOTP_VER: |
| 2614 | + print(f"Warning: configured TOTP_VER ({TOTP_VER}) is missing from SECRET_CIPHER_DICT (available: {available}); falling back to auto-selected version {fallback}") |
| 2615 | + return fallback |
| 2616 | + |
| 2617 | + |
2605 | 2618 | # Creates a TOTP object using a secret derived from transformed cipher bytes |
2606 | 2619 | def generate_totp(): |
2607 | 2620 | import pyotp |
2608 | 2621 |
|
2609 | | - if not SECRET_CIPHER_DICT: |
2610 | | - raise SecretsUnavailableError("generate_totp(): SECRET_CIPHER_DICT is empty") |
2611 | | - |
2612 | | - ver = TOTP_VER or max(map(int, SECRET_CIPHER_DICT)) |
2613 | | - if str(ver) not in SECRET_CIPHER_DICT: |
2614 | | - raise SecretsUnavailableError(f"generate_totp(): Defined TOTP_VER ({ver}) is missing in SECRET_CIPHER_DICT") |
| 2622 | + ver = resolve_totp_ver() |
2615 | 2623 |
|
2616 | 2624 | secret_cipher_bytes = SECRET_CIPHER_DICT[str(ver)] |
2617 | 2625 |
|
@@ -2704,7 +2712,7 @@ def refresh_access_token_from_sp_dc(sp_dc: str) -> dict: |
2704 | 2712 | client_time = int(time_ns() / 1000 / 1000) |
2705 | 2713 | otp_value = totp_obj.at(server_time) |
2706 | 2714 |
|
2707 | | - totp_ver = TOTP_VER or max(map(int, SECRET_CIPHER_DICT)) |
| 2715 | + totp_ver = resolve_totp_ver() |
2708 | 2716 |
|
2709 | 2717 | params = { |
2710 | 2718 | "reason": "transport", |
@@ -2829,7 +2837,10 @@ def spotify_get_access_token_from_sp_dc(sp_dc: str): |
2829 | 2837 | last_error = str(e) |
2830 | 2838 | debug_print(f"TOTP secrets unavailable: {e}") |
2831 | 2839 | if fetch_and_update_secrets(): |
2832 | | - debug_print("TOTP secrets updated, retrying token refresh immediately") |
| 2840 | + debug_print("TOTP secrets updated, retrying token refresh") |
| 2841 | + retry += 1 |
| 2842 | + if retry < max_retries: |
| 2843 | + time.sleep(TOKEN_RETRY_TIMEOUT) |
2833 | 2844 | continue |
2834 | 2845 | raise RuntimeError(f"Failed to obtain TOTP secrets for token refresh: {e}") |
2835 | 2846 | except Exception as e: |
|
0 commit comments