Expose Hydrabase as a configurable metadata source (no dev mode needed)

Add Hydrabase section to Settings → Connections with enable toggle,
WebSocket URL, API key, auto-connect, and connect/disconnect button.
_is_hydrabase_active() now checks hydrabase.enabled config in addition
to dev_mode — either path activates it. Default disabled, zero change
for existing users. Dev admin page stays behind dev mode password.
This commit is contained in:
Broque Thomas 2026-03-20 13:41:02 -07:00
parent 3c51f27e97
commit 2f9491c71b
4 changed files with 108 additions and 7 deletions

View file

@ -444,7 +444,8 @@ class ConfigManager:
"hydrabase": { "hydrabase": {
"url": "", "url": "",
"api_key": "", "api_key": "",
"auto_connect": False "auto_connect": False,
"enabled": False
}, },
"content_filter": { "content_filter": {
"allow_explicit": True "allow_explicit": True

View file

@ -4560,12 +4560,16 @@ _comparison_lock = threading.Lock()
def _is_hydrabase_active(): def _is_hydrabase_active():
"""Check if Hydrabase should be used as the primary metadata source. """Check if Hydrabase should be used as the primary metadata source.
Returns False when dev mode is off no behavior change for normal users.""" Active when: (dev_mode OR hydrabase.enabled config) AND client connected."""
try: try:
return (dev_mode_enabled if hydrabase_client is None or not hydrabase_client.is_connected():
and hydrabase_client is not None return False
and hydrabase_client.is_connected()) # Dev mode always enables Hydrabase (legacy behavior)
except NameError: if dev_mode_enabled:
return True
# Config toggle: user enabled Hydrabase as metadata source
return config_manager.get('hydrabase.enabled', False)
except (NameError, Exception):
return False return False
def _run_background_comparison(query, hydrabase_counts=None): def _run_background_comparison(query, hydrabase_counts=None):
@ -40759,7 +40763,10 @@ try:
timeout=10 timeout=10
) )
_hydrabase_ws = _auto_ws _hydrabase_ws = _auto_ws
dev_mode_enabled = True # Enable dev mode only if Hydrabase was previously in dev mode
# The config toggle (hydrabase.enabled) handles non-dev usage
if not _hydra_cfg.get('enabled'):
dev_mode_enabled = True
print(f"✅ Hydrabase auto-connected to {_hydra_cfg['url']}") print(f"✅ Hydrabase auto-connected to {_hydra_cfg['url']}")
except Exception as e: except Exception as e:
print(f"⚠️ Hydrabase auto-reconnect failed: {e}") print(f"⚠️ Hydrabase auto-reconnect failed: {e}")

View file

@ -3677,6 +3677,38 @@
</div> </div>
</div> </div>
<!-- Hydrabase P2P Metadata -->
<div class="api-service-frame" data-stg="connections">
<h4 class="service-title" style="color: #00b4d8;">Hydrabase</h4>
<div class="form-group" style="margin-bottom: 8px;">
<label class="checkbox-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 0;">
<input type="checkbox" id="hydrabase-enabled" style="width: 16px; height: 16px;">
<span>Enable Hydrabase as metadata source</span>
</label>
</div>
<div class="form-group">
<label>WebSocket URL:</label>
<input type="text" id="hydrabase-url" placeholder="ws://localhost:4545">
</div>
<div class="form-group">
<label>API Key:</label>
<input type="password" id="hydrabase-api-key" placeholder="Hydrabase API Key">
</div>
<div class="form-group" style="margin-bottom: 8px;">
<label class="checkbox-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 0;">
<input type="checkbox" id="hydrabase-auto-connect" style="width: 16px; height: 16px;">
<span>Auto-connect on startup</span>
</label>
</div>
<div class="callback-info">
<div class="callback-help">P2P metadata network. When enabled and connected, replaces Spotify/iTunes as the primary metadata source for searches.</div>
</div>
<div class="form-actions">
<button class="test-button" id="hydrabase-connect-btn" onclick="toggleHydrabaseFromSettings()">Connect</button>
<span id="hydrabase-settings-status" style="font-size: 0.82em; color: rgba(255,255,255,0.4); margin-left: 8px;"></span>
</div>
</div>
<!-- Test Connection Buttons --> <!-- Test Connection Buttons -->
<div class="api-test-buttons"> <div class="api-test-buttons">
<button class="test-button" onclick="testConnection('spotify')">Test <button class="test-button" onclick="testConnection('spotify')">Test

View file

@ -5467,6 +5467,13 @@ async function loadSettingsData() {
// Populate Metadata source setting // Populate Metadata source setting
document.getElementById('metadata-fallback-source').value = settings.metadata?.fallback_source || 'itunes'; document.getElementById('metadata-fallback-source').value = settings.metadata?.fallback_source || 'itunes';
// Populate Hydrabase settings
const hbConfig = settings.hydrabase || {};
document.getElementById('hydrabase-enabled').checked = hbConfig.enabled || false;
document.getElementById('hydrabase-url').value = hbConfig.url || '';
document.getElementById('hydrabase-api-key').value = hbConfig.api_key || '';
document.getElementById('hydrabase-auto-connect').checked = hbConfig.auto_connect || false;
// Populate Download settings (right column) // Populate Download settings (right column)
document.getElementById('download-path').value = settings.soulseek?.download_path || './downloads'; document.getElementById('download-path').value = settings.soulseek?.download_path || './downloads';
document.getElementById('transfer-path').value = settings.soulseek?.transfer_path || './Transfer'; document.getElementById('transfer-path').value = settings.soulseek?.transfer_path || './Transfer';
@ -6077,6 +6084,54 @@ async function saveQualityProfile() {
// END QUALITY PROFILE FUNCTIONS // END QUALITY PROFILE FUNCTIONS
// =============================== // ===============================
async function toggleHydrabaseFromSettings() {
const statusEl = document.getElementById('hydrabase-settings-status');
const btn = document.getElementById('hydrabase-connect-btn');
const url = document.getElementById('hydrabase-url').value.trim();
const apiKey = document.getElementById('hydrabase-api-key').value.trim();
if (!url || !apiKey) {
if (statusEl) statusEl.textContent = 'URL and API Key required';
return;
}
// Save settings first
await saveSettings(true);
try {
// Check current status
const statusRes = await fetch('/api/hydrabase/status');
const statusData = await statusRes.json();
if (statusData.connected) {
// Disconnect
await fetch('/api/hydrabase/disconnect', { method: 'POST' });
if (btn) btn.textContent = 'Connect';
if (statusEl) statusEl.textContent = 'Disconnected';
showToast('Hydrabase disconnected', 'info');
} else {
// Connect
const res = await fetch('/api/hydrabase/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url, api_key: apiKey })
});
const data = await res.json();
if (data.success) {
if (btn) btn.textContent = 'Disconnect';
if (statusEl) statusEl.textContent = 'Connected';
showToast('Hydrabase connected', 'success');
} else {
if (statusEl) statusEl.textContent = data.error || 'Connection failed';
showToast('Hydrabase connection failed', 'error');
}
}
} catch (e) {
if (statusEl) statusEl.textContent = 'Error';
showToast('Hydrabase connection error', 'error');
}
}
async function activateDevMode() { async function activateDevMode() {
const password = document.getElementById('dev-mode-password').value; const password = document.getElementById('dev-mode-password').value;
try { try {
@ -6384,6 +6439,12 @@ async function saveSettings(quiet = false) {
metadata: { metadata: {
fallback_source: document.getElementById('metadata-fallback-source').value || 'itunes' fallback_source: document.getElementById('metadata-fallback-source').value || 'itunes'
}, },
hydrabase: {
enabled: document.getElementById('hydrabase-enabled').checked,
url: document.getElementById('hydrabase-url').value,
api_key: document.getElementById('hydrabase-api-key').value,
auto_connect: document.getElementById('hydrabase-auto-connect').checked
},
download_source: { download_source: {
mode: document.getElementById('download-source-mode').value, mode: document.getElementById('download-source-mode').value,
hybrid_primary: document.getElementById('hybrid-primary-source').value, hybrid_primary: document.getElementById('hybrid-primary-source').value,