Spotify授权未完成时应用主功能提前执行致401错误
Spotify授权完成后才执行播放列表更新逻辑的问题
我开发了一款将Spotify音乐库歌曲迁移至其他播放列表的应用,需通过Spotify授权流程。已确认scope配置正确,也能获取到有效的access token和refresh token,但无法实现仅在用户登录授权完成后才获取用户音乐库的歌曲。尝试将授权流程封装为函数,在获取歌曲前调用但未生效。
运行代码时,应用启动后立即执行更新播放列表的逻辑,返回401 Invalid access token错误;而手动访问localhost:3030完成授权后,控制台能成功输出有效令牌信息。
相关代码
const __dirname = dirname(fileURLToPath(import.meta.url)); const app = Express(); const port = 3030; // CLIENT_SECRET stored in Config Vars // const apiUrl = "https://accounts.spotify.com/api/token"; // Spotify Web API URL const client_id = '467fab359c114e719ecefafd6af299e5'; // Client id const client_secret = 'your_client_secret' // temp client secret // const client_secret = process.env.CLIENT_SECRET; const redirect_uri = 'http://localhost:3030/callback/'; // Callback URL let AT, RT; // Stores access and refresh tokens const scope = [ 'user-read-private', 'user-read-email', 'user-library-read', 'playlist-read-private', 'playlist-modify-public', 'playlist-modify-private' ]; /** * Generates a random string containing numbers and letters * @param {number} length The length of the string * @return {string} The generated string */ let generateRandomString = function (length) { let text = ''; let possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < length; i++) { text += possible.charAt(Math.floor(Math.random() * possible.length)); } return text; }; let stateKey = 'spotify_auth_state'; const authorizeSpotify = () => { return new Promise((resolve, reject) => { app.get('/', function (req, res) { res.sendFile(__dirname + "/index.html"); res.redirect('/login'); }); app.use(Express.static(__dirname + '/index.html')) .use(cors()) .use(cookieParser()); app.get('/login', function (req, res) { let state = generateRandomString(16); res.cookie(stateKey, state); // app requests authorization res.redirect('https://accounts.spotify.com/authorize?' + querystring.stringify({ response_type: 'code', client_id: client_id, scope: scope, redirect_uri: redirect_uri, state: state })); }); app.get('/callback', function (req, res) { // app requests refresh and access tokens // after checking the state parameter let code = req.query.code || null; let state = req.query.state || null; let storedState = req.cookies ? req.cookies[stateKey] : null; console.log(state); console.log(storedState); if (state === null || state !== storedState) { res.redirect('/#' + querystring.stringify({ error: 'state_mismatch' })); } else { res.clearCookie(stateKey); let authOptions = { url: 'https://accounts.spotify.com/api/token', form: { code: code, redirect_uri: redirect_uri, grant_type: 'authorization_code' }, headers: { 'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64')) }, json: true }; request.post(authOptions, function (error, response, body) { if (!error && response.statusCode === 200) { console.log(body); AT = body.access_token; RT = body.refresh_token; let options = { url: 'https://api.spotify.com/v1/me', headers: { 'Authorization': 'Bearer ' + AT }, json: true }; interval = setInterval(requestToken, body.expires_in * 1000 * 0.70); previousExpires = body.expires_in; res.send("Logged in!"); } }); } }); let interval; let previousExpires = 0; const requestToken = () => { const authOptions = { url: 'https://accounts.spotify.com/api/token', headers: { 'Authorization': 'Basic ' + (Buffer.from(client_id + ':' + client_secret).toString('base64')) }, form: { grant_type: 'refresh_token', refresh_token: RT }, json: true }; request.post(authOptions, function (error, response, body) { if (error || response.statusCode !== 200) { console.error(error); return; } AT = body.access_token; if (body.refresh_token) { RT = body.refresh_token; } console.log("Access Token refreshed!"); if (previousExpires != body.expires_in) { clearInterval(interval); interval = setInterval(requestToken, body.expires_in * 1000 * 0.70); previousExpires = body.expires_in; } }); } resolve({AT, RT}); }); }; // Write code for app here // Function to get the user's library tracks const getUserLibraryTracks = (AT) => { return new Promise((resolve, reject) => { const options = { url: 'https://api.spotify.com/v1/me/tracks', headers: { 'Authorization': 'Bearer ' + AT }, json: true }; request.get(options, (error, response, body) => { if (error || response.statusCode !== 200) { console.log('Response:', body); reject(error || new Error('Failed to get user library tracks')); } else { resolve(body.items.map(item => item.track)); } }); }); }; // Function to get the tracks in a playlist const getPlaylistTracks = (AT, playlistId) => { return new Promise((resolve, reject) => { const options = { url: `https://api.spotify.com/v1/playlists/${playlistId}/tracks`, headers: { 'Authorization': 'Bearer ' + AT }, json: true }; request.get(options, (error, response, body) => { if (error || response.statusCode !== 200) { reject(error || new Error('Failed to get playlist tracks')); } else { resolve(body.items.map(item => item.track)); } }); }); }; // Function to add tracks to a playlist const addTracksToPlaylist = (AT, playlistId, trackIds) => { return new Promise((resolve, reject) => { const options = { url: `https://api.spotify.com/v1/playlists/${playlistId}/tracks`, headers: { 'Authorization': 'Bearer ' + AT }, json: true, body: { uris: trackIds } }; request.post(options, (error, response, body) => { if (error || response.statusCode !== 201) { reject(error || new Error('Failed to add tracks to playlist')); } else { resolve(); } }); }); }; // Function to update the playlist with new tracks const updatePlaylist = async (playlistId) => { try { const {AT, RT } = await authorizeSpotify(); const libraryTracks = await getUserLibraryTracks(AT); const playlistTracks = await getPlaylistTracks(AT, playlistId); const trackIdsToAdd = libraryTracks .filter(track => !playlistTracks.some(playlistTrack => playlistTrack.id === track.id)) .map(track => track.uri); await addTracksToPlaylist(AT, playlistId, trackIdsToAdd); console.log('Playlist updated successfully'); } catch (error) { console.error('Failed to update playlist:', error); } }; // Call the updatePlaylist function to update the playlist updatePlaylist('your_playlist_id'); app.listen(port, () => console.log(`Listening on port: ${port}`));
错误日志
[nodemon] starting `node autoadd.js` Listening on port: 3030 Response: { error: { status: 401, message: 'Invalid access token' } } Failed to update playlist: Error: Failed to get user library tracks at Request._callback (file:///home/nero/Projects/Autoadd/autoadd.js:195:25) at Request.self.callback (/home/nero/Projects/Autoadd/node_modules/request/request.js:185:22) at Request.emit (events.js:314:20) at Request.<anonymous> (/home/nero/Projects/Autoadd/node_modules/request/request.js:1154:10) at Request.emit (events.js:314:20) at IncomingMessage.<anonymous> (/home/nero/Projects/Autoadd/node_modules/request/request.js:1076:12) at Object.onceWrapper (events.js:420:28) at IncomingMessage.emit (events.js:326:22) at endReadableNT (_stream_readable.js:1241:12) at processTicksAndRejections (internal/process/task_queues.js:84:21)
授权成功日志
{ access_token: 'BQBC1CAN2Wv3PIR1XdwTuQwgrHjQ1eCgQJqAZ0PWBNAiHGk6OKqsJFeafJEqBXBWfg1qpOvVxfEJ4SF77OHgxn9OvxS8Lg9Na0NSFlz1iWR26xztSJEq4Or-hwUKB2yE_Y-X6yPvzaScar7HDFADSQtVMxOx1Z8wq3hbi498i0bGTTnYccFTijopoSxbwfKvbfMTRxNrdUJt0z8u_w', token_type: 'Bearer', expires_in: 3600, refresh_token: 'AQC3bMXEM23qjQqOOXrC5Tcsvt6ijfp2umMyz466u1DCi9nNN2J9jsU0Q4ilYq2cu19xA80fhrljQSutWrFGyBzOUV3i1mytO4UBEjbbKOHuKXFXwEYV83Rxzo-7ic_-YFA', scope: 'playlist-modify-private' }
内容的提问来源于stack exchange,提问作者Lazlo
相关产品推荐
相关产品推荐

