diff --git a/web_server.py b/web_server.py index 9b5664ae..84a881a2 100644 --- a/web_server.py +++ b/web_server.py @@ -19071,6 +19071,15 @@ def get_version_info(): "title": "What's New in SoulSync", "subtitle": f"Version {SOULSYNC_VERSION} — Latest Changes", "sections": [ + { + "title": "🔧 Add Qobuz to Connections Tab (#218)", + "description": "Qobuz credentials now available on the Connections tab for metadata enrichment", + "features": [ + "• New Qobuz section on Settings → Connections tab for enrichment auth", + "• Users can connect Qobuz for metadata enrichment regardless of download source", + "• Auth status syncs between Connections and Downloads tabs" + ] + }, { "title": "🔧 Fix Enrichment Widget Showing 'Running' When Rate Limited", "description": "Enrichment tooltip now shows Rate Limited or Daily Limit Reached instead of stuck on Running", diff --git a/webui/index.html b/webui/index.html index 49903477..7bf055d1 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3632,6 +3632,38 @@ + +
+

Qobuz (Metadata & Enrichment)

+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+ Connects Qobuz for metadata enrichment (ISRC, labels, copyright). Also used for downloads if Qobuz is your download source. +
+
+

ListenBrainz

diff --git a/webui/static/helper.js b/webui/static/helper.js index 1f0f0201..9d145aa1 100644 --- a/webui/static/helper.js +++ b/webui/static/helper.js @@ -3403,6 +3403,7 @@ function closeHelperSearch() { const WHATS_NEW = { '2.1': [ // Newest features first + { title: 'Qobuz on Connections Tab', desc: 'Qobuz credentials now on Settings → Connections for metadata enrichment without needing it as download source' }, { title: 'Fix Enrichment Status Widget', desc: 'Enrichment tooltip now shows Rate Limited or Daily Limit instead of stuck on Running' }, { title: 'Cache Maintenance', desc: 'Cache evictor now cleans junk entities, orphaned searches, and stale MusicBrainz nulls' }, { title: 'Fix Wishlist Download Selection', desc: 'Download Selection now only downloads checked tracks instead of the entire category' }, diff --git a/webui/static/script.js b/webui/static/script.js index 0255d9dc..cb7d1c39 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -7602,24 +7602,83 @@ async function checkQobuzAuthStatus() { try { const resp = await fetch('/api/qobuz/auth/status'); const data = await resp.json(); + + // Update downloads tab section const formEl = document.getElementById('qobuz-auth-form'); const loggedInEl = document.getElementById('qobuz-auth-logged-in'); const userInfoEl = document.getElementById('qobuz-auth-user-info'); + // Update connections tab section + const connFormEl = document.getElementById('qobuz-connection-form'); + const connLoggedInEl = document.getElementById('qobuz-connection-logged-in'); + const connUserInfoEl = document.getElementById('qobuz-connection-user-info'); + if (data.authenticated) { const user = data.user || {}; - userInfoEl.textContent = `Connected: ${user.display_name || 'Qobuz User'} (${user.subscription || 'Active'})`; - loggedInEl.style.display = 'flex'; - formEl.style.display = 'none'; + const label = `Connected: ${user.display_name || 'Qobuz User'} (${user.subscription || 'Active'})`; + + if (userInfoEl) { userInfoEl.textContent = label; } + if (loggedInEl) loggedInEl.style.display = 'flex'; + if (formEl) formEl.style.display = 'none'; + + if (connUserInfoEl) { connUserInfoEl.textContent = label; } + if (connLoggedInEl) connLoggedInEl.style.display = 'flex'; + if (connFormEl) connFormEl.style.display = 'none'; } else { - loggedInEl.style.display = 'none'; - formEl.style.display = 'block'; + if (loggedInEl) loggedInEl.style.display = 'none'; + if (formEl) formEl.style.display = 'block'; + + if (connLoggedInEl) connLoggedInEl.style.display = 'none'; + if (connFormEl) connFormEl.style.display = 'block'; } } catch (e) { console.error('Qobuz auth status check failed:', e); } } +async function loginQobuzFromConnections() { + const btn = document.getElementById('qobuz-connection-login-btn'); + const statusEl = document.getElementById('qobuz-connection-status'); + const email = document.getElementById('qobuz-connection-email').value.trim(); + const password = document.getElementById('qobuz-connection-password').value; + + if (!email || !password) { + showToast('Please enter your Qobuz email and password', 'warning'); + return; + } + + btn.disabled = true; + btn.textContent = 'Connecting...'; + statusEl.textContent = ''; + + try { + const resp = await fetch('/api/qobuz/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + const data = await resp.json(); + + if (data.success) { + showToast('Qobuz connected successfully!', 'success'); + document.getElementById('qobuz-connection-password').value = ''; + checkQobuzAuthStatus(); + } else { + statusEl.textContent = data.error || 'Login failed'; + statusEl.style.color = '#ff5555'; + showToast(data.error || 'Qobuz login failed', 'error'); + } + } catch (error) { + console.error('Qobuz login error:', error); + statusEl.textContent = 'Connection error'; + statusEl.style.color = '#ff5555'; + showToast('Failed to connect to Qobuz', 'error'); + } finally { + btn.disabled = false; + btn.textContent = 'Connect Qobuz'; + } +} + async function loginQobuz() { const btn = document.getElementById('qobuz-login-btn'); const statusEl = document.getElementById('qobuz-auth-status');