fix: use Spotify public web-player token — no Premium/API account needed
Replace Client Credentials flow with Spotify's public web-player token endpoint (open.spotify.com/get_access_token) which works without any account or Premium. Remove Spotify credential fields from UI entirely. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
dc1bec74a1
commit
cfa8a8cd49
4 files changed
+21
-41
No files matched your search
+19
-14
@@ -388,20 +388,26 @@ function enqueue(task) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ─── SPOTIFY ──────────────────────────────────────────────────────────────────
|
// ─── SPOTIFY ──────────────────────────────────────────────────────────────────
|
||||||
let _spTok = null, _spTokExp = 0, _spCachedId = '';
|
let _spTok = null, _spTokExp = 0;
|
||||||
|
|
||||||
async function getSpotifyToken(clientId, clientSecret) {
|
// Uses Spotify's public web-player token — no account or Premium required
|
||||||
if (_spTok && clientId === _spCachedId && Date.now() < _spTokExp - 30000) return _spTok;
|
async function getSpotifyToken() {
|
||||||
const creds = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
if (_spTok && Date.now() < _spTokExp - 60000) return _spTok;
|
||||||
const r = await fetch('https://accounts.spotify.com/api/token', {
|
const r = await fetch(
|
||||||
method: 'POST',
|
'https://open.spotify.com/get_access_token?reason=transport&productType=web_player',
|
||||||
headers: { Authorization: `Basic ${creds}`, 'Content-Type': 'application/x-www-form-urlencoded' },
|
{
|
||||||
body: 'grant_type=client_credentials',
|
headers: {
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
||||||
|
Accept: 'application/json',
|
||||||
|
},
|
||||||
signal: AbortSignal.timeout(8000),
|
signal: AbortSignal.timeout(8000),
|
||||||
});
|
}
|
||||||
if (!r.ok) throw new Error(`Spotify Auth fehlgeschlagen (${r.status}) — Client ID/Secret prüfen`);
|
);
|
||||||
|
if (!r.ok) throw new Error(`Spotify public token fehlgeschlagen (${r.status})`);
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
_spTok = d.access_token; _spTokExp = Date.now() + d.expires_in * 1000; _spCachedId = clientId;
|
if (!d.accessToken) throw new Error('Kein Spotify-Token erhalten — Spotify möglicherweise nicht erreichbar');
|
||||||
|
_spTok = d.accessToken;
|
||||||
|
_spTokExp = d.accessTokenExpirationTimestampMs || (Date.now() + 3600000);
|
||||||
return _spTok;
|
return _spTok;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,12 +424,11 @@ app.get('/api/health', (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/spotify/resolve', async (req, res) => {
|
app.post('/api/spotify/resolve', async (req, res) => {
|
||||||
const { url, clientId, clientSecret } = req.body;
|
const { url } = req.body;
|
||||||
if (!url) return res.status(400).json({ error: 'url required' });
|
if (!url) return res.status(400).json({ error: 'url required' });
|
||||||
if (!clientId || !clientSecret) return res.status(400).json({ error: 'Spotify credentials fehlen (clientId + clientSecret in Einstellungen eintragen)' });
|
|
||||||
try {
|
try {
|
||||||
const { type, id } = parseSpotifyUrl(url);
|
const { type, id } = parseSpotifyUrl(url);
|
||||||
const tok = await getSpotifyToken(clientId, clientSecret);
|
const tok = await getSpotifyToken();
|
||||||
const spH = { Authorization: `Bearer ${tok}` };
|
const spH = { Authorization: `Bearer ${tok}` };
|
||||||
const spFetch = (u) => fetch(u, { headers: spH, signal: AbortSignal.timeout(10000) });
|
const spFetch = (u) => fetch(u, { headers: spH, signal: AbortSignal.timeout(10000) });
|
||||||
|
|
||||||
|
|||||||
+1
-19
@@ -46,15 +46,6 @@
|
|||||||
<input id="backend-url" type="url" placeholder="http://192.168.1.x:3700">
|
<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 class="hint">Für Download-to-Server Funktion. Backend separat starten (novasonic-server.js)</div>
|
||||||
</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>
|
</div>
|
||||||
<button class="btn-primary" id="btn-connect" onclick="doConnect()">Verbinden</button>
|
<button class="btn-primary" id="btn-connect" onclick="doConnect()">Verbinden</button>
|
||||||
<div class="setup-error" id="setup-error"></div>
|
<div class="setup-error" id="setup-error"></div>
|
||||||
@@ -352,7 +343,7 @@
|
|||||||
<div>1. Spotify Link für Track, Album oder Playlist einfügen</div>
|
<div>1. Spotify Link für Track, Album oder Playlist einfügen</div>
|
||||||
<div>2. Metadaten werden direkt von Spotify geladen</div>
|
<div>2. Metadaten werden direkt von Spotify geladen</div>
|
||||||
<div>3. Songs via yt-dlp herunterladen → direkt in Navidrome Library</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 style="margin-top:10px;color:var(--text3);font-size:12px">Kein Spotify-Account erforderlich — funktioniert direkt über das Backend.</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="spotify-preview" style="display:none;margin-top:20px">
|
<div id="spotify-preview" style="display:none;margin-top:20px">
|
||||||
<div id="spotify-preview-inner"></div>
|
<div id="spotify-preview-inner"></div>
|
||||||
@@ -518,15 +509,6 @@
|
|||||||
<input id="s-backend" type="url" placeholder="http://192.168.x.x:3700">
|
<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 class="hint">Für Download-to-Server via yt-dlp</div>
|
||||||
</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 class="settings-row" style="margin-top:8px;border-radius:9px">
|
||||||
<div>
|
<div>
|
||||||
<div style="font-size:13px;font-weight:500">Backend Status</div>
|
<div style="font-size:13px;font-weight:500">Backend Status</div>
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ export function openSettings() {
|
|||||||
document.getElementById('s-pass').value = '';
|
document.getElementById('s-pass').value = '';
|
||||||
document.getElementById('s-lastfm').value = S.cfg.lastfmKey || '';
|
document.getElementById('s-lastfm').value = S.cfg.lastfmKey || '';
|
||||||
document.getElementById('s-backend').value = S.cfg.backendUrl || '';
|
document.getElementById('s-backend').value = S.cfg.backendUrl || '';
|
||||||
document.getElementById('s-spotify-id').value = S.cfg.spotifyClientId || '';
|
|
||||||
document.getElementById('s-spotify-secret').value = '';
|
|
||||||
document.getElementById('settings-modal').classList.add('open');
|
document.getElementById('settings-modal').classList.add('open');
|
||||||
checkBackend();
|
checkBackend();
|
||||||
}
|
}
|
||||||
@@ -74,9 +72,6 @@ export function saveSettings() {
|
|||||||
if(pass) S.cfg.pass = pass;
|
if(pass) S.cfg.pass = pass;
|
||||||
S.cfg.lastfmKey = document.getElementById('s-lastfm').value.trim();
|
S.cfg.lastfmKey = document.getElementById('s-lastfm').value.trim();
|
||||||
S.cfg.backendUrl = document.getElementById('s-backend').value.trim().replace(/\/$/,'');
|
S.cfg.backendUrl = document.getElementById('s-backend').value.trim().replace(/\/$/,'');
|
||||||
S.cfg.spotifyClientId = document.getElementById('s-spotify-id').value.trim();
|
|
||||||
const spSec = document.getElementById('s-spotify-secret').value;
|
|
||||||
if (spSec) S.cfg.spotifyClientSecret = spSec;
|
|
||||||
localStorage.setItem('ns_cfg', JSON.stringify(S.cfg));
|
localStorage.setItem('ns_cfg', JSON.stringify(S.cfg));
|
||||||
closeSettings(); toast('✅ Gespeichert');
|
closeSettings(); toast('✅ Gespeichert');
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-3
@@ -918,8 +918,6 @@ export async function resolveSpotifyLink() {
|
|||||||
const url = document.getElementById('spotify-link-input')?.value.trim();
|
const url = document.getElementById('spotify-link-input')?.value.trim();
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
if (!S.cfg.backendUrl) { toast('⚠ Backend URL in Einstellungen konfigurieren'); return; }
|
if (!S.cfg.backendUrl) { toast('⚠ Backend URL in Einstellungen konfigurieren'); return; }
|
||||||
if (!S.cfg.spotifyClientId) { toast('⚠ Spotify Client ID in Einstellungen eintragen'); return; }
|
|
||||||
if (!S.cfg.spotifyClientSecret) { toast('⚠ Spotify Client Secret in Einstellungen eintragen'); return; }
|
|
||||||
|
|
||||||
const hint = document.getElementById('spotify-importer-hint');
|
const hint = document.getElementById('spotify-importer-hint');
|
||||||
const preview = document.getElementById('spotify-preview');
|
const preview = document.getElementById('spotify-preview');
|
||||||
@@ -935,7 +933,7 @@ export async function resolveSpotifyLink() {
|
|||||||
const res = await fetch(`${S.cfg.backendUrl}/api/spotify/resolve`, {
|
const res = await fetch(`${S.cfg.backendUrl}/api/spotify/resolve`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ url, clientId: S.cfg.spotifyClientId, clientSecret: S.cfg.spotifyClientSecret }),
|
body: JSON.stringify({ url }),
|
||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const err = await res.json().catch(() => ({}));
|
const err = await res.json().catch(() => ({}));
|
||||||
|
|||||||
Reference in new issue
Block a user