From e1f7b8f5cc32f6fde92f4a008eacf74c3c80c3ff Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 8 Apr 2026 09:58:55 -0700 Subject: [PATCH] Add HiFi API instance health check to Settings New "Check All Instances" button in Settings > Downloads > HiFi shows each configured instance with color-coded status: green for fully working (can download), orange for search-only (downloads fail), red for offline/SSL error/timeout. Helps users understand why HiFi downloads aren't working when community instances go down. --- web_server.py | 41 +++++++++++++++++++++++++++++++++++++++++ webui/index.html | 5 +++++ webui/static/script.js | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) diff --git a/web_server.py b/web_server.py index 07c56898..0541f35a 100644 --- a/web_server.py +++ b/web_server.py @@ -31226,6 +31226,47 @@ def hifi_status(): return jsonify({"available": False, "error": str(e)}) +@app.route('/api/hifi/instances', methods=['GET']) +def hifi_instances(): + """Check availability of all HiFi API instances.""" + import requests as req + try: + hifi = soulseek_client.hifi + instances = list(hifi._instances) + results = [] + for url in instances: + entry = {'url': url, 'status': 'unknown', 'version': None, 'can_search': False, 'can_download': False} + try: + # Check root for version + r = req.get(f'{url}/', timeout=5, headers={'Accept': 'application/json'}) + if r.ok: + data = r.json() + entry['version'] = data.get('version') + entry['status'] = 'online' + # Check search + sr = req.get(f'{url}/search', params={'s': 'test', 'limit': 1}, timeout=5) + entry['can_search'] = sr.ok + # Check track (download capability) + tr = req.get(f'{url}/track', params={'id': '1550546', 'quality': 'LOSSLESS'}, timeout=5) + entry['can_download'] = tr.ok + if not tr.ok: + entry['download_error'] = f'HTTP {tr.status_code}' + else: + entry['status'] = f'error (HTTP {r.status_code})' + except req.exceptions.SSLError: + entry['status'] = 'ssl_error' + except req.exceptions.ConnectTimeout: + entry['status'] = 'timeout' + except req.exceptions.ConnectionError: + entry['status'] = 'offline' + except Exception as e: + entry['status'] = f'error ({type(e).__name__})' + results.append(entry) + return jsonify({'instances': results, 'active': hifi._get_instance()}) + except Exception as e: + return jsonify({'error': str(e)}), 500 + + # =================================================================== # DEEZER DOWNLOAD ENDPOINTS # =================================================================== diff --git a/webui/index.html b/webui/index.html index d4456799..301f3903 100644 --- a/webui/index.html +++ b/webui/index.html @@ -4825,6 +4825,11 @@ Free lossless downloads via community-run hifi-api instances. No authentication or subscription needed. +
+ + + +
diff --git a/webui/static/script.js b/webui/static/script.js index 419133df..63551b25 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -7738,6 +7738,45 @@ async function testHiFiConnection() { } } +async function checkHiFiInstances() { + const panel = document.getElementById('hifi-instances-panel'); + const btn = document.getElementById('hifi-instances-check-btn'); + if (!panel) return; + panel.style.display = 'block'; + panel.innerHTML = '
Checking instances...
'; + if (btn) { btn.disabled = true; btn.textContent = 'Checking...'; } + try { + const resp = await fetch('/api/hifi/instances'); + const data = await resp.json(); + if (!data.instances || data.instances.length === 0) { + panel.innerHTML = '
No instances configured.
'; + return; + } + const _statusIcon = (inst) => { + if (inst.can_download) return '● Download'; + if (inst.can_search) return '● Search only'; + if (inst.status === 'online') return '● Online (limited)'; + if (inst.status === 'ssl_error') return '● SSL error'; + if (inst.status === 'timeout') return '● Timeout'; + if (inst.status === 'offline') return '● Offline'; + return `● ${escapeHtml(inst.status)}`; + }; + panel.innerHTML = data.instances.map(inst => { + const isActive = inst.url === data.active; + const ver = inst.version ? ` v${inst.version}` : ''; + const activeTag = isActive ? ' (ACTIVE)' : ''; + return `
+ ${escapeHtml(inst.url)}${ver}${activeTag} + ${_statusIcon(inst)} +
`; + }).join(''); + } catch (e) { + panel.innerHTML = `
Error checking instances: ${escapeHtml(e.message)}
`; + } finally { + if (btn) { btn.disabled = false; btn.textContent = 'Check All Instances'; } + } +} + async function testDeezerDownloadConnection() { const statusEl = document.getElementById('deezer-download-status'); if (!statusEl) return;