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

+13 -10
View File
@@ -14,7 +14,7 @@ import {
navigate, _navGoBack, _navLoad, loadHomeData, onLocalSearch, onMobileSearch,
onGlobalSearch, s_goArtist, s_goAlbum, playerBarGoArtist, playerBarGoAlbum,
searchSimilarArtist, searchAndPreview, loadItunesAlbum, expandArtistBio,
loadRandom,
loadRandom, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
} from './views.js';
import {
openNowPlaying, closeNowPlaying, switchNpTab, npGoArtist, npGoAlbum,
@@ -22,23 +22,25 @@ import {
checkBackend, openSidebar, closeSidebar, switchNavMode, showCtxMenu,
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist,
createPlaylistPrompt, showSongInfo, closeInfoModal, openDlModal, closeDlModal,
selectDlType, executeDownload, openEditPlModal, closeEditPlModal,
selectDlType, executeDownload, executeSpotifyDownload, openEditPlModal, closeEditPlModal,
saveEditPlaylist, editPlUploadCover, editPlCoverSelected, editPlRemoveCover,
toggleMobileSearch, closeMobileSearch, switchSetupTab,
} from './ui.js';
// ─── CONNECT ───
async function doConnect() {
const url = document.getElementById('srv-url').value.trim().replace(/\/$/,'');
const user = document.getElementById('srv-user').value.trim();
const pass = document.getElementById('srv-pass').value;
const lfKey = document.getElementById('lastfm-key').value.trim();
const backUrl = document.getElementById('backend-url').value.trim().replace(/\/$/,'');
const url = document.getElementById('srv-url').value.trim().replace(/\/$/,'');
const user = document.getElementById('srv-user').value.trim();
const pass = document.getElementById('srv-pass').value;
const lfKey = document.getElementById('lastfm-key').value.trim();
const backUrl = document.getElementById('backend-url').value.trim().replace(/\/$/,'');
const spId = document.getElementById('spotify-client-id').value.trim();
const spSecret = document.getElementById('spotify-client-secret').value.trim();
if(!url || !user || !pass) { _showSetupError('Bitte Server-URL, Benutzernamen und Passwort ausfüllen.'); return; }
const btn = document.getElementById('btn-connect');
btn.disabled=true; btn.textContent='Verbinde…';
document.getElementById('setup-error').style.display='none';
S.cfg = {url, user, pass, lastfmKey:lfKey, backendUrl:backUrl};
S.cfg = {url, user, pass, lastfmKey:lfKey, backendUrl:backUrl, spotifyClientId:spId, spotifyClientSecret:spSecret};
try {
await api('ping');
localStorage.setItem('ns_cfg', JSON.stringify(S.cfg));
@@ -55,7 +57,7 @@ function disconnect() {
localStorage.removeItem('ns_cfg');
localStorage.removeItem('ns_last_played');
sessionStorage.removeItem('ns_nav');
S.cfg = {url:'', user:'', pass:'', lastfmKey:'', backendUrl:''};
S.cfg = {url:'', user:'', pass:'', lastfmKey:'', backendUrl:'', spotifyClientId:'', spotifyClientSecret:''};
S.queue=[]; S.qIdx=-1; S._navStack=[]; S._navCursor=-1;
S.audio.pause(); S.audio.src='';
document.getElementById('app').style.display='none';
@@ -183,7 +185,8 @@ Object.assign(window, {
openSettings, closeSettings, saveSettings, checkBackend,
openSidebar, closeSidebar, switchNavMode, showCtxMenu,
// Views
loadRandom,
loadRandom, loadSpotifyImporter, resolveSpotifyLink, clearSpotifyPreview, spotifyDlAll,
executeSpotifyDownload,
openPlModal, closePlModal, addSongsToPlaylist, createAndAddPlaylist, createPlaylistPrompt,
showSongInfo, closeInfoModal,
openDlModal, closeDlModal, selectDlType, executeDownload,