From 3d96752087795358b035208d8645bec63f3c167b Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 8 Apr 2026 10:37:38 -0700 Subject: [PATCH] Add Qobuz auth token login as CAPTCHA bypass alternative Qobuz added reCAPTCHA to their login endpoint, blocking automated email/password auth for new users. Token login lets users paste their X-User-Auth-Token from the browser DevTools after logging in manually. Added to both Connections and Downloads tabs with instructions. Existing email/password flow completely unchanged. Backend validates token via user/get API and saves the session identically to email/password login. --- core/qobuz_client.py | 58 ++++++++++++++++++++++++++++++ web_server.py | 22 ++++++++++++ webui/index.html | 36 +++++++++++++++++++ webui/static/script.js | 80 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) diff --git a/core/qobuz_client.py b/core/qobuz_client.py index 75c12123..61a550d8 100644 --- a/core/qobuz_client.py +++ b/core/qobuz_client.py @@ -446,6 +446,64 @@ class QobuzClient: traceback.print_exc() return {'status': 'error', 'message': self._auth_error} + def login_with_token(self, token: str) -> Dict[str, Any]: + """ + Login to Qobuz with a user_auth_token pasted from the browser. + Bypasses email/password login (and any CAPTCHA) entirely. + """ + self._auth_error = None + try: + # Step 1: Extract app credentials if we don't have them + if not self.app_id or not self.app_secret: + if not self._extract_app_credentials(): + self._auth_error = 'Could not extract Qobuz app credentials. Qobuz may have updated their web player.' + return {'status': 'error', 'message': self._auth_error} + + # Step 2: Set the token and validate it + self.user_auth_token = token.strip() + self.session.headers['X-App-Id'] = self.app_id + self.session.headers['X-User-Auth-Token'] = self.user_auth_token + + resp = self.session.get( + QOBUZ_API_BASE + 'user/get', + params={'user_id': 'me'}, + timeout=15, + ) + + if resp.status_code != 200: + self.user_auth_token = None + self.session.headers.pop('X-User-Auth-Token', None) + self._auth_error = f'Invalid token (HTTP {resp.status_code})' + return {'status': 'error', 'message': self._auth_error} + + data = resp.json() + self.user_info = data + + # Check subscription + subscription = data.get('credential', {}) + sub_label = subscription.get('label', 'Unknown') + + # Save session + self._save_session() + + display_name = data.get('display_name', data.get('email', 'unknown')) + logger.info(f"Qobuz token login successful: {display_name} (plan: {sub_label})") + + return { + 'status': 'success', + 'message': f'Logged in as {display_name}', + 'user': { + 'display_name': display_name, + 'subscription': sub_label, + 'email': data.get('email', ''), + }, + } + + except Exception as e: + self._auth_error = str(e) + logger.error(f"Qobuz token login failed: {e}") + return {'status': 'error', 'message': self._auth_error} + def logout(self): """Clear Qobuz session.""" self.user_auth_token = None diff --git a/web_server.py b/web_server.py index 0541f35a..d99476de 100644 --- a/web_server.py +++ b/web_server.py @@ -31465,6 +31465,28 @@ def qobuz_auth_login(): return jsonify({"success": False, "error": str(e)}), 500 +@app.route('/api/qobuz/auth/token', methods=['POST']) +def qobuz_auth_token(): + """Login to Qobuz with a pasted user_auth_token (bypasses CAPTCHA).""" + try: + data = request.get_json() + token = data.get('token', '').strip() + + if not token: + return jsonify({"success": False, "error": "Auth token required"}), 400 + + qobuz = soulseek_client.qobuz + result = qobuz.login_with_token(token) + + if result['status'] == 'success': + return jsonify({"success": True, **result}) + else: + return jsonify({"success": False, "error": result.get('message', 'Token login failed')}), 400 + + except Exception as e: + return jsonify({"success": False, "error": str(e)}), 500 + + @app.route('/api/qobuz/auth/status', methods=['GET']) def qobuz_auth_status(): """Check if Qobuz client is authenticated.""" diff --git a/webui/index.html b/webui/index.html index 301f3903..e3fb8e3a 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4232,6 +4232,25 @@
www.qobuz.com/api.json
+ → look in the request headers for X-User-Auth-Token → copy that value.www.qobuz.com/api.json
+ → look for X-User-Auth-Token in the request headers → copy that value.