feat: switch Spotify auth to official Client Credentials Flow
Replaces the broken open.spotify.com/get_access_token scrape with accounts.spotify.com/api/token (Basic auth, client_credentials grant). Adds SPOTIFY_CLIENT_ID/SECRET to config, docker-compose, and .env.example. Health endpoint now reports spotify.configured; frontend disables the importer and shows a setup guide when credentials are missing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
7c1c444bf2
commit
468827571a
5 files changed
+77
-13
No files matched your search
+27
-12
@@ -72,23 +72,35 @@ let _spTokenExpiry = 0;
|
||||
|
||||
async function fetchSpotifyToken() {
|
||||
if (_spTokenCache && Date.now() < _spTokenExpiry) return _spTokenCache;
|
||||
// spotdl trick: send a random sp_t cookie — Spotify requires some value but doesn't validate it
|
||||
const sp_t = require('crypto').randomUUID();
|
||||
const r = await fetch('https://open.spotify.com/get_access_token?reason=transport&productType=web_player', {
|
||||
if (!CONFIG.spotifyClientId || !CONFIG.spotifyClientSecret) {
|
||||
throw new Error(
|
||||
'Spotify-Credentials fehlen. Bitte SPOTIFY_CLIENT_ID und ' +
|
||||
'SPOTIFY_CLIENT_SECRET in der Umgebung setzen. ' +
|
||||
'Setup-Anleitung: https://developer.spotify.com/dashboard'
|
||||
);
|
||||
}
|
||||
|
||||
const basic = Buffer.from(`${CONFIG.spotifyClientId}:${CONFIG.spotifyClientSecret}`).toString('base64');
|
||||
const r = await fetch('https://accounts.spotify.com/api/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
||||
'Accept': 'application/json',
|
||||
'Accept-Language': 'en-US,en;q=0.9',
|
||||
'Referer': 'https://open.spotify.com/',
|
||||
'Cookie': `sp_t=${sp_t}`,
|
||||
'Authorization': `Basic ${basic}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: 'grant_type=client_credentials',
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!r.ok) throw new Error(`Spotify-Token nicht abrufbar (${r.status})`);
|
||||
|
||||
if (!r.ok) {
|
||||
const errBody = await r.text().catch(() => '');
|
||||
throw new Error(`Spotify-Token-Fehler (${r.status}): ${errBody.slice(0, 200)}`);
|
||||
}
|
||||
|
||||
const d = await r.json();
|
||||
if (!d.accessToken) throw new Error('Kein Spotify-Token erhalten');
|
||||
_spTokenCache = d.accessToken;
|
||||
_spTokenExpiry = (d.accessTokenExpirationTimestampMs || (Date.now() + 55 * 60 * 1000)) - 30000;
|
||||
if (!d.access_token) throw new Error('Kein access_token von Spotify erhalten');
|
||||
|
||||
_spTokenCache = d.access_token;
|
||||
_spTokenExpiry = Date.now() + (d.expires_in * 1000) - 30000;
|
||||
return _spTokenCache;
|
||||
}
|
||||
|
||||
@@ -205,6 +217,9 @@ app.get('/api/health', (req, res) => {
|
||||
squidQuality: CONFIG.squidQuality,
|
||||
activeJobs: activeDownloads,
|
||||
queueLength: downloadQueue.length,
|
||||
spotify: {
|
||||
configured: !!(CONFIG.spotifyClientId && CONFIG.spotifyClientSecret),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user