英文:
Why does the main function code for the app start running before the authorisation workflow?
问题
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
<details>
<summary>英文:</summary>
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.
```js
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'
}
答案1
得分: 1
Flow Overview
使用express
作为REST服务器,axios
用于Spotify REST POST调用。
使用授权码流
获取访问令牌
Spotify API调用
GET /playlists/{playlist_id}/tracks
GET /me/tracks
POST /playlists/{playlist_id}/tracks
演示代码
保存为update_songs.js
const express = require("express")
const axios = require('axios')
const cors = require("cors");
const app = express()
app.use(cors())
CLIENT_ID = "<your client ID>"
CLIENT_SECRET = "<your client secret>"
PORT = 3030 // it is located in Spotify dashboard's Redirect URIs, my port is 3000
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is 'http://localhost:3000/callback'
PLAYLIST_ID = 'your playlist ID'
SCOPE = [
'user-read-private',
'user-read-email',
'user-library-read',
'playlist-read-private',
'playlist-modify-public',
'playlist-modify-private'
]
const getToken = async (code) => {
try {
const resp = await axios.post(
url = 'https://accounts.spotify.com/api/token',
data = new URLSearchParams({
'grant_type': 'authorization_code',
'redirect_uri': REDIRECT_URI,
'code': code
}),
config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
})
return Promise.resolve(resp.data.access_token);
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const addSongs = async (playlist_id, tracks, token) => {
try {
const uris = []
for(const track of tracks) {
if (track.new) {
uris.push(track.uri)
}
}
const chunkSize = 100;
for (let i = 0; i < uris.length; i += chunkSize) {
const sub_uris = uris.slice(i, i + chunkSize);
const resp = await axios.post(
url = `https://api.spotify.com/v1/playlists/${playlist_id}/tracks`,
data = {
'uris': sub_uris
},
config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
}
})
}
return Promise.resolve('OK');
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getPlaylistTracks = async (playlist, token) => {
try {
let next = 1
const tracks = []
url = `https://api.spotify.com/v1/playlists/${playlist}`
while (next != null) {
const resp = await axios.get(
url,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
)
items = []
if (resp.data.items) {
items = resp.data.items
} else if (resp.data.tracks.items) {
items = resp.data.tracks.items
}
for(const item of items) {
if (item.track?.name != null) {
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false
})
}
}
if (resp.data.items) {
url = resp.data.next
} else if (resp.data.tracks.items) {
url = resp.data.tracks.next
} else {
break
}
next = url
}
return Promise.resolve(tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const update_track = (arr, track) => {
const { length } = arr;
const id = length + 1;
const found = arr.some(el => el.external_urls === track.external_urls);
if (!found) {
arr.push({ name : track.name, external_urls: track.external_urls, uri: track.uri, new: true })
};
return arr;
}
const updatePlaylistTracks = async (my_tracks, previous_tracks, token) => {
try {
new_tracks = previous_tracks.map(a => Object.assign({}, a));
// update new playlist with my_tracks and previous_tracks
for(const track of my_tracks) {
new_tracks = update_track(new_tracks, track)
}
return Promise.resolve(new_tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getMyTracks = async (token) => {
try {
let offset = 0
let next = 1
const limit = 50;
const tracks = [];
while (next != null) {
const resp = await axios.get(
url = `https://api.spotify.com/v1/me/tracks/?limit=${limit}&offset=${offset}`,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
);
for(const item of resp.data.items) {
if(item.track?.name != null) {
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false,
added_at: item.added_at
})
}
}
offset = offset + limit
<details>
<summary>英文:</summary>
My understanding is you want to get my tracks and adding into the playlist except for duplicated(already existing) songs.
Flow Overview
[![enter image description here][1]][1]
Using [`express`](https://expressjs.com/) for REST server, [`axios`](https://axios-http.com/docs/intro) for Spotify REST POST call.
Using [`Authorization Code Flow`](https://developer.spotify.com/documentation/web-api/tutorials/code-flow) for getting `access token`
#### Spotify API calls
[Get Playlist Items](https://developer.spotify.com/documentation/web-api/reference/get-playlists-tracks)
GET /playlists/{playlist_id}/tracks
[![enter image description here][2]][2]
[Get User's Saved Tracks](https://developer.spotify.com/documentation/web-api/reference/get-users-saved-tracks)
GET /me/tracks
[![enter image description here][3]][3]
[Add Items to Playlist](https://developer.spotify.com/documentation/web-api/reference/add-tracks-to-playlist)
POST /playlists/{playlist_id}/tracks
### Demo code
Save as `update_songs.js`
```node.js
const express = require("express")
const axios = require('axios')
const cors = require("cors");
const app = express()
app.use(cors())
CLIENT_ID = "<your client ID>"
CLIENT_SECRET = "<your client secret>"
PORT = 3030 // it is located in Spotify dashboard's Redirect URIs, my port is 3000
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is 'http://localhost:3000/callback'
PLAYLIST_ID = 'your playlist ID'
SCOPE = [
'user-read-private',
'user-read-email',
'user-library-read',
'playlist-read-private',
'playlist-modify-public',
'playlist-modify-private'
]
const getToken = async (code) => {
try {
const resp = await axios.post(
url = 'https://accounts.spotify.com/api/token',
data = new URLSearchParams({
'grant_type': 'authorization_code',
'redirect_uri': REDIRECT_URI,
'code': code
}),
config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
})
return Promise.resolve(resp.data.access_token);
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const addSongs = async (playlist_id, tracks, token) => {
try {
const uris = []
for(const track of tracks) {
if (track.new) {
uris.push(track.uri)
}
}
const chunkSize = 100;
for (let i = 0; i < uris.length; i += chunkSize) {
const sub_uris = uris.slice(i, i + chunkSize);
const resp = await axios.post(
url = `https://api.spotify.com/v1/playlists/${playlist_id}/tracks`,
data = {
'uris': sub_uris
},
config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
}
})
}
return Promise.resolve('OK');
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getPlaylistTracks = async (playlist, token) => {
try {
let next = 1
const tracks = []
url = `https://api.spotify.com/v1/playlists/${playlist}`
while (next != null) {
const resp = await axios.get(
url,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
)
items = []
if (resp.data.items) {
items = resp.data.items
} else if (resp.data.tracks.items) {
items = resp.data.tracks.items
}
for(const item of items) {
if (item.track?.name != null) {
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false
})
}
}
if (resp.data.items) {
url = resp.data.next
} else if (resp.data.tracks.items) {
url = resp.data.tracks.next
} else {
break
}
next = url
}
return Promise.resolve(tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const update_track = (arr, track) => {
const { length } = arr;
const id = length + 1;
const found = arr.some(el => el.external_urls === track.external_urls);
if (!found) {
arr.push({ name : track.name, external_urls: track.external_urls, uri: track.uri, new: true })
};
return arr;
}
const updatePlaylistTracks = async (my_tracks, previous_tracks, token) => {
try {
new_tracks = previous_tracks.map(a => Object.assign({}, a));
// update new playlist with my_tracks and previous_tracks
for(const track of my_tracks) {
new_tracks = update_track(new_tracks, track)
}
return Promise.resolve(new_tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getMyTracks = async (token) => {
try {
let offset = 0
let next = 1
const limit = 50;
const tracks = [];
while (next != null) {
const resp = await axios.get(
url = `https://api.spotify.com/v1/me/tracks/?limit=${limit}&offset=${offset}`,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
);
for(const item of resp.data.items) {
if(item.track?.name != null) {
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false,
added_at: item.added_at
})
}
}
offset = offset + limit
next = resp.data.next
}
return Promise.resolve(tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
app.get("/login", (request, response) => {
const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&client_id=${CLIENT_ID}&scope=${SCOPE}&state=123456&redirect_uri=${REDIRECT_URI}&prompt=consent`
response.redirect(redirect_url);
})
app.get("/callback", async (request, response) => {
const code = request.query["code"]
getToken(code)
.then(access_token => {
getMyTracks(access_token)
.then(my_tracks => {
getPlaylistTracks(PLAY_LIST_ID, access_token)
.then(previous_tracks => {
updatePlaylistTracks(my_tracks, previous_tracks, access_token)
.then(new_tracks => {
addSongs(PLAY_LIST_ID, new_tracks, access_token)
.then(OK => {
return response.send({
'my tracks Total:': my_tracks.length,
'my tracks': my_tracks,
'previous playlist Total:': previous_tracks.length,
'previous playlist': previous_tracks,
'new playlist Total:': new_tracks.length,
'new playlist': new_tracks,
'add song result': OK });
})
})
})
})
})
.catch(error => {
console.log(error.message);
})
})
app.listen(PORT, () => {
console.log(`Listening on :${PORT}`)
})
Install dependencies
npm install express axios cors
Run it
From terminal
node update_songs.js
By browser
http://locahost:3030/login
Result
From Browser
My library list songs: total 837 songs
Previous Playlist songs: total 771 songs
Before adding songs (previous playlist songs)
After adding songs: total 1,591 songs
Update for checking new songs
For checking if any new songs are in my library, can see when it added_at
You need to add one line of code into getMyTracks()
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false,
added_at: item.added_at
})
Update V3 for my tracks only
Step By Step, this code can get my library songs.
const express = require("express")
const axios = require('axios')
const cors = require("cors");
const app = express()
app.use(cors())
CLIENT_ID = "<your client ID>"
CLIENT_SECRET = "<your client secret>"
PORT = 3030 // it is located in Spotify dashboard's Redirect URIs
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is 'http://localhost:3000/callback'
SCOPE = [
'user-read-private',
'user-read-email',
'user-library-read',
'playlist-read-private',
'playlist-modify-public',
'playlist-modify-private'
]
app.get("/login", (request, response) => {
const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&client_id=${CLIENT_ID}&scope=${SCOPE}&state=123456&redirect_uri=${REDIRECT_URI}&prompt=consent`
response.redirect(redirect_url);
})
const getToken = async (code) => {
try {
const resp = await axios.post(
'https://accounts.spotify.com/api/token',
new URLSearchParams({
'grant_type': 'authorization_code',
'redirect_uri': REDIRECT_URI,
'code': code
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
})
return Promise.resolve(resp.data.access_token);
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getMyTracks = async (token) => {
try {
let offset = 0
let next = 1
const limit = 50;
const tracks = [];
while (next != null) {
const resp = await axios.get(
url = `https://api.spotify.com/v1/me/tracks/?limit=${limit}&offset=${offset}`,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
);
for(const item of resp.data.items) {
if(item.track?.name != null) {
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false,
added_at: item.added_at
})
}
}
offset = offset + limit
next = resp.data.next
}
return Promise.resolve(tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
};
app.get("/callback", async (request, response) => {
const code = request.query["code"]
getToken(code)
.then(access_token => {
getMyTracks(access_token)
.then(my_tracks => {
return response.send({
'total:' : my_tracks.length,
'my tracks': my_tracks
});
})
})
.catch(error => {
console.log(error.message);
})
})
app.listen(PORT, () => {
console.log(`Listening on :${PORT}`)
})
Update V3 for plylist only
Step By Step, this code can get playlist songs.
const express = require("express")
const axios = require('axios')
const cors = require("cors");
const app = express()
app.use(cors())
CLIENT_ID = "<your client ID>"
CLIENT_SECRET = "<your client secret>"
PORT = 3030 // it is located in Spotify dashboard's Redirect URIs
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is 'http://localhost:3000/callback'
PLAYLIST_ID = 'your playlist ID'
SCOPE = [
'user-read-private',
'user-read-email',
'user-library-read',
'playlist-read-private',
'playlist-modify-public',
'playlist-modify-private'
]
app.get("/login", (request, response) => {
const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&client_id=${CLIENT_ID}&scope=${SCOPE}&state=123456&redirect_uri=${REDIRECT_URI}&prompt=consent`
response.redirect(redirect_url);
})
const getToken = async (code) => {
try {
const resp = await axios.post(
'https://accounts.spotify.com/api/token',
new URLSearchParams({
'grant_type': 'authorization_code',
'redirect_uri': REDIRECT_URI,
'code': code
}),
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
auth: {
username: CLIENT_ID,
password: CLIENT_SECRET
}
})
return Promise.resolve(resp.data.access_token);
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getPlaylistTracks = async (playlist, token) => {
try {
let next = 1
const tracks = []
url = `https://api.spotify.com/v1/playlists/${playlist}`
while (next != null) {
const resp = await axios.get(
url,
config = {
headers: {
'Accept-Encoding': 'application/json',
'Authorization': `Bearer ${token}`,
}
}
)
items = []
if (resp.data.items) {
items = resp.data.items
} else if (resp.data.tracks.items) {
items = resp.data.tracks.items
}
for(const item of items) {
if (item.track?.name != null) {
tracks.push({
name: item.track.name,
external_urls: item.track.external_urls.spotify,
uri: item.track.uri,
new: false
})
}
}
if (resp.data.items) {
url = resp.data.next
} else if (resp.data.tracks.items) {
url = resp.data.tracks.next
} else {
break
}
next = url
}
return Promise.resolve(tracks)
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
app.get("/callback", async (request, response) => {
const code = request.query["code"]
getToken(code)
.then(access_token => {
getPlaylistTracks(PLAYLIST_ID, access_token)
.then(tracks => {
return response.send({
'total:' : tracks.length,
'playlist tracks': tracks
});
})
})
.catch(error => {
console.log(error.message);
})
})
app.listen(PORT, () => {
console.log(`Listening on :${PORT}`)
})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论