I was building an app that helps me move tracks from my library to another playlist which requires me to go through the Spotify Authorisation workflow. I m fairly certain the scopes are correct and I ve managed to return the correct access and refresh tokens, but I cannot figure out how to only get the tracks from the user s library after the user has logged in and authorised their account for access.
I tried passing the authorisation flow into a function only to be called before the app gets the tracks but that didn t seem to work.
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}`));
Running the code, I keep receiving this message:
[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)
Of course, after running the code, I browse to the localhost:3030
address and it logs in successfully with my account. The console logging this:
{
access_token: BQBC1CAN2Wv3PIR1XdwTuQwgrHjQ1eCgQJqAZ0PWBNAiHGk6OKqsJFeafJEqBXBWfg1qpOvVxfEJ4SF77OHgxn9OvxS8Lg9Na0NSFlz1iWR26xztSJEq4Or-hwUKB2yE_Y-X6yPvzaScar7HDFADSQtVMxOx1Z8wq3hbi498i0bGTTnYccFTijopoSxbwfKvbfMTRxNrdUJt0z8u_w ,
token_type: Bearer ,
expires_in: 3600,
refresh_token: AQC3bMXEM23qjQqOOXrC5Tcsvt6ijfp2umMyz466u1DCi9nNN2J9jsU0Q4ilYq2cu19xA80fhrljQSutWrFGyBzOUV3i1mytO4UBEjbbKOHuKXFXwEYV83Rxzo-7ic_-YFA ,
scope: playlist-modify-private
}