Handle non-JSON Tidal auth responses

Detect JSON decode-like exceptions from Tidal's token endpoint and return a safer, more actionable error message. Adds a _looks_like_json_decode_error helper and special-cases that error in check_device_auth to log the non-JSON response and advise disabling VPN/proxy/network filtering and restarting SoulSync. A test was added to ensure the user-facing message does not leak the raw exception text while still returning an error status. Other errors continue to fall back to the existing behavior.
This commit is contained in:
Broque Thomas 2026-05-20 14:04:45 -07:00
parent a3f0018b29
commit 3375b6c4bd
2 changed files with 42 additions and 0 deletions

View file

@ -95,6 +95,16 @@ HLS_MAP_TAG_RE = re.compile(r'#EXT-X-MAP:.*URI="([^"]+)"')
from core.download_plugins.base import DownloadSourcePlugin
def _looks_like_json_decode_error(exc: Exception) -> bool:
name = exc.__class__.__name__.lower()
message = str(exc).lower()
return (
"jsondecodeerror" in name
or "expecting value" in message
or "could not decode json" in message
)
class TidalDownloadClient(DownloadSourcePlugin):
"""
Tidal download client using tidalapi.
@ -230,6 +240,18 @@ class TidalDownloadClient(DownloadSourcePlugin):
return {'status': 'error', 'message': 'Auth completed but session invalid'}
except Exception as e:
if _looks_like_json_decode_error(e):
logger.error(
"Tidal device auth check received a non-JSON response from Tidal's token endpoint: %s",
e,
)
return {
'status': 'error',
'message': (
"Tidal returned an invalid auth response while SoulSync was finishing login. "
"Try again after disabling VPN/proxy/network filtering, then restart SoulSync if it repeats."
),
}
logger.error(f"Tidal device auth check error: {e}")
return {'status': 'error', 'message': str(e)}

View file

@ -61,6 +61,26 @@ def test_is_authenticated_false_when_session_check_login_raises(tidal_client_wit
assert client.is_authenticated() is False
def test_check_device_auth_returns_helpful_message_for_non_json_tidal_response(tidal_client_with_engine):
client, _ = tidal_client_with_engine
class FakeFuture:
def running(self):
return False
def result(self, timeout=0):
raise ValueError("Expecting value: line 1 column 1 (char 0)")
client._device_auth_future = FakeFuture()
client._device_auth_link = {'verification_uri': 'https://link.tidal.com', 'user_code': 'ABCD'}
result = client.check_device_auth()
assert result['status'] == 'error'
assert 'invalid auth response' in result['message']
assert 'Expecting value' not in result['message']
# ---------------------------------------------------------------------------
# download() — filename parsing + id contract
# ---------------------------------------------------------------------------