feat: Spotify Importer — link → yt-dlp download + optional Navidrome playlist

- New 'Spotify Importer' page under Entdecken (sidebar + mobile nav)
- Backend: POST /api/spotify/resolve — resolves track/album/playlist URLs via Spotify API
- Backend: new 'playlist' download type accepting pre-resolved track list (no iTunes re-fetch)
- Frontend: paste Spotify link → preview with cover, type badge, track list
  - Track: single download or full album via iTunes fallback
  - Album: download all tracks with Spotify metadata
  - Playlist: download all tracks + optional 'Als Navidrome-Playlist speichern' checkbox
- Navidrome playlist creation: searches library by title/artist after download, creates playlist
- Spotify Client ID + Secret stored in app settings (setup screen + settings modal)
- iTunes metadata tags kept for all downloaded files (existing pipeline unchanged)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-27 00:56:11 +02:00
1 parent 9f68deea94
commit f7a5247f17
6 files changed
+398 -18

No files matched your search

+58
View File
@@ -46,6 +46,15 @@
<input id="backend-url" type="url" placeholder="http://192.168.1.x:3700">
<div class="hint">Für Download-to-Server Funktion. Backend separat starten (novasonic-server.js)</div>
</div>
<div class="field">
<label>Spotify Client ID <span style="color:var(--text3);font-weight:400;text-transform:none">(optional)</span></label>
<input id="spotify-client-id" type="text" placeholder="Für Spotify Importer">
<div class="hint">Kostenlos unter developer.spotify.com/dashboard</div>
</div>
<div class="field">
<label>Spotify Client Secret <span style="color:var(--text3);font-weight:400;text-transform:none">(optional)</span></label>
<input id="spotify-client-secret" type="password" placeholder="••••••••••••••••">
</div>
</div>
<button class="btn-primary" id="btn-connect" onclick="doConnect()">Verbinden</button>
<div class="setup-error" id="setup-error"></div>
@@ -111,6 +120,9 @@
<div class="nav-item" onclick="navigate('random')" data-page="random">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>Zufällig
</div>
<div class="nav-item" onclick="navigate('spotify-importer')" data-page="spotify-importer">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 11.973c2.5-1.473 5.5-.973 7.5.527M9.5 14.473c1.5-.973 3.5-.973 5 .027M7 9c3-1.5 6.5-1 9 1"/></svg>Spotify Importer
</div>
</div>
<div class="nav-section">
<div class="nav-section-title">Player</div>
@@ -320,6 +332,39 @@
</div>
</div>
<!-- SPOTIFY IMPORTER -->
<div class="page" id="page-spotify-importer">
<div class="page-header">
<div class="page-title">Spotify <em>Importer</em></div>
<span style="background:rgba(29,185,84,.15);color:#1DB954;border:1px solid rgba(29,185,84,.3);font-size:11px;padding:4px 10px;border-radius:20px;font-weight:600;flex-shrink:0">Spotify</span>
</div>
<div class="search-wrap" style="padding:0 0 20px">
<div style="display:flex;gap:8px">
<div class="search-box" style="background:var(--surface);border-color:var(--border2);flex:1">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 11.973c2.5-1.473 5.5-.973 7.5.527M9.5 14.473c1.5-.973 3.5-.973 5 .027M7 9c3-1.5 6.5-1 9 1"/></svg>
<input id="spotify-link-input" type="url" placeholder="Spotify Link einfügen (Track, Album oder Playlist)…" style="font-size:15px" oninput="clearSpotifyPreview()">
</div>
<button class="btn-primary" style="padding:0 18px;font-size:14px;white-space:nowrap;flex-shrink:0" onclick="resolveSpotifyLink()">Importieren</button>
</div>
</div>
<div id="spotify-importer-hint" style="background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:16px;font-size:13px;color:var(--text2);line-height:1.8">
<div style="font-weight:700;color:var(--text);margin-bottom:8px">So funktioniert's</div>
<div>1. Spotify Link für Track, Album oder Playlist einfügen</div>
<div>2. Metadaten werden direkt von Spotify geladen</div>
<div>3. Songs via yt-dlp herunterladen → direkt in Navidrome Library</div>
<div style="margin-top:10px;color:var(--text3);font-size:12px">Spotify Client ID + Secret in den Einstellungen → Discover APIs eintragen.<br>Kostenlos unter <strong style="color:var(--text2)">developer.spotify.com/dashboard</strong></div>
</div>
<div id="spotify-preview" style="display:none;margin-top:20px">
<div id="spotify-preview-inner"></div>
<div id="spotify-tracks-section" style="display:none">
<div class="section-header" style="margin-top:8px">
<span class="section-title" id="spotify-tracks-title">Tracks</span>
</div>
<div id="spotify-tracks-list" class="song-list"></div>
</div>
</div>
</div>
</main>
</div>
@@ -473,6 +518,15 @@
<input id="s-backend" type="url" placeholder="http://192.168.x.x:3700">
<div class="hint">Für Download-to-Server via yt-dlp</div>
</div>
<div class="field">
<label>Spotify Client ID</label>
<input id="s-spotify-id" type="text" placeholder="Für Spotify Importer">
<div class="hint">Kostenlos unter developer.spotify.com/dashboard</div>
</div>
<div class="field">
<label>Spotify Client Secret</label>
<input id="s-spotify-secret" type="password" placeholder="(unverändert lassen)">
</div>
<div class="settings-row" style="margin-top:8px;border-radius:9px">
<div>
<div style="font-size:13px;font-weight:500">Backend Status</div>
@@ -708,6 +762,10 @@
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 3 21 3 21 8"/><line x1="4" y1="20" x2="21" y2="3"/><polyline points="21 16 21 21 16 21"/><line x1="15" y1="15" x2="21" y2="21"/></svg>
Zufällig
</button>
<button class="bnav-item" onclick="navigate('spotify-importer')" data-page="spotify-importer">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 11.973c2.5-1.473 5.5-.973 7.5.527M9.5 14.473c1.5-.973 3.5-.973 5 .027M7 9c3-1.5 6.5-1 9 1"/></svg>
Importer
</button>
</div>
</div>
</nav>