From 99481a02324e9618969fd1ad5072aceb1b60e573 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 19 Mar 2026 08:23:19 -0700 Subject: [PATCH] Fix Track Match search ignoring Track/Artist fields and low result limit - Frontend was concatenating Track and Artist inputs into a single query string, causing Spotify to return mixed results matching either word in any field. Now sends track and artist as separate params; backend builds field-filtered query (track:X artist:Y). - Result limit was silently capped at 10 in spotify_client.search_tracks via min(limit, 10). Raised to respect requested limit up to Spotify's API max of 50. - iTunes fallback endpoint updated with same field-specific params. - Legacy ?query= param still supported for backward compatibility. Fixes #194 --- core/spotify_client.py | 7 ++++--- web_server.py | 33 +++++++++++++++++++++++++++++---- webui/static/script.js | 24 ++++++++++++++++++------ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/core/spotify_client.py b/core/spotify_client.py index f06ff955..aee617a5 100644 --- a/core/spotify_client.py +++ b/core/spotify_client.py @@ -955,7 +955,8 @@ class SpotifyClient: if use_spotify: # Check Spotify cache - cached_results = cache.get_search_results('spotify', 'track', query, min(limit, 10)) + effective_limit = min(limit, 50) # Spotify API max is 50 + cached_results = cache.get_search_results('spotify', 'track', query, effective_limit) if cached_results is not None: tracks = [] for raw in cached_results: @@ -967,7 +968,7 @@ class SpotifyClient: return tracks try: - results = self.sp.search(q=query, type='track', limit=min(limit, 10)) + results = self.sp.search(q=query, type='track', limit=effective_limit) tracks = [] raw_items = results['tracks']['items'] @@ -979,7 +980,7 @@ class SpotifyClient: entries = [(td.get('id'), td) for td in raw_items if td.get('id')] if entries: cache.store_entities_bulk('spotify', 'track', entries) - cache.store_search_results('spotify', 'track', query, min(limit, 10), + cache.store_search_results('spotify', 'track', query, effective_limit, [td.get('id') for td in raw_items if td.get('id')]) return tracks diff --git a/web_server.py b/web_server.py index 0f5ad099..d89a706a 100644 --- a/web_server.py +++ b/web_server.py @@ -25069,10 +25069,23 @@ def search_spotify_tracks(): return jsonify({"error": "Spotify not authenticated."}), 401 try: - query = request.args.get('query', '').strip() + # Support field-specific search params (track, artist) or legacy combined query + track_q = request.args.get('track', '').strip() + artist_q = request.args.get('artist', '').strip() + legacy_query = request.args.get('query', '').strip() limit = int(request.args.get('limit', 20)) - if not query: + # Build Spotify field-filtered query + if track_q or artist_q: + parts = [] + if track_q: + parts.append(f'track:{track_q}') + if artist_q: + parts.append(f'artist:{artist_q}') + query = ' '.join(parts) + elif legacy_query: + query = legacy_query + else: return jsonify({"error": "Query parameter is required"}), 400 if use_hydrabase: @@ -25101,10 +25114,22 @@ def search_spotify_tracks(): def search_itunes_tracks(): """Search for tracks on iTunes - used by discovery fix modal when iTunes is the source""" try: - query = request.args.get('query', '').strip() + # Support field-specific search params or legacy combined query + track_q = request.args.get('track', '').strip() + artist_q = request.args.get('artist', '').strip() + legacy_query = request.args.get('query', '').strip() limit = int(request.args.get('limit', 20)) - if not query: + if track_q or artist_q: + parts = [] + if track_q: + parts.append(track_q) + if artist_q: + parts.append(artist_q) + query = ' '.join(parts) + elif legacy_query: + query = legacy_query + else: return jsonify({"error": "Query parameter is required"}), 400 use_hydrabase = _is_hydrabase_active() diff --git a/webui/static/script.js b/webui/static/script.js index 8991af5b..803abeeb 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -15392,12 +15392,19 @@ async function searchDiscoveryFix() { resultsContainer.innerHTML = `
🔍 Searching ${sourceLabel}...
`; try { - // Build search query - const query = `${artistInput} ${trackInput}`.trim(); + // Build search params — send track and artist separately for field-specific filtering + const params = new URLSearchParams(); + if (trackInput) params.set('track', trackInput); + if (artistInput) params.set('artist', artistInput); + if (!trackInput && !artistInput) { + resultsContainer.innerHTML = '
Enter a track name or artist.
'; + return; + } + params.set('limit', '50'); // Call appropriate search API based on discovery source const searchEndpoint = useFallback ? '/api/itunes/search_tracks' : '/api/spotify/search_tracks'; - const response = await fetch(`${searchEndpoint}?query=${encodeURIComponent(query)}&limit=20`); + const response = await fetch(`${searchEndpoint}?${params.toString()}`); const data = await response.json(); if (data.error) { @@ -55496,8 +55503,9 @@ async function searchPoolFix() { const resultsContainer = document.getElementById('pool-fix-results'); if (!trackInput || !resultsContainer) return; - const query = `${artistInput.value.trim()} ${trackInput.value.trim()}`.trim(); - if (!query) { + const trackVal = trackInput.value.trim(); + const artistVal = artistInput.value.trim(); + if (!trackVal && !artistVal) { resultsContainer.innerHTML = '
Enter a search term
'; return; } @@ -55505,7 +55513,11 @@ async function searchPoolFix() { resultsContainer.innerHTML = '
Searching...
'; try { - const res = await fetch(`/api/spotify/search_tracks?query=${encodeURIComponent(query)}&limit=20`); + const params = new URLSearchParams(); + if (trackVal) params.set('track', trackVal); + if (artistVal) params.set('artist', artistVal); + params.set('limit', '50'); + const res = await fetch(`/api/spotify/search_tracks?${params.toString()}`); const data = await res.json(); const tracks = data.tracks || [];