English 中文(简体)
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 !== 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 
}
问题回答

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

Using express for REST server, axios for Spotify REST POST call.

Using Authorization Code Flow for getting access token

Spotify API calls

Get Playlist Items

GET /playlists/{playlist_id}/tracks

enter image description here

Get User s Saved Tracks

GET /me/tracks

enter image description here

Add Items to Playlist

POST /playlists/{playlist_id}/tracks

Demo code

Save as 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
            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

enter image description here

Previous Playlist songs: total 771 songs

Before adding songs (previous playlist songs)

enter image description here

enter image description here After adding songs: total 1,591 songs

enter image description here

enter image description here

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}`)
})




相关问题
selected text in iframe

How to get a selected text inside a iframe. I my page i m having a iframe which is editable true. So how can i get the selected text in that iframe.

How to fire event handlers on the link using javascript

I would like to click a link in my page using javascript. I would like to Fire event handlers on the link without navigating. How can this be done? This has to work both in firefox and Internet ...

How to Add script codes before the </body> tag ASP.NET

Heres the problem, In Masterpage, the google analytics code were pasted before the end of body tag. In ASPX page, I need to generate a script (google addItem tracker) using codebehind ClientScript ...

Clipboard access using Javascript - sans Flash?

Is there a reliable way to access the client machine s clipboard using Javascript? I continue to run into permissions issues when attempting to do this. How does Google Docs do this? Do they use ...

javascript debugging question

I have a large javascript which I didn t write but I need to use it and I m slowely going trough it trying to figure out what does it do and how, I m using alert to print out what it does but now I ...

Parsing date like twitter

I ve made a little forum and I want parse the date on newest posts like twitter, you know "posted 40 minutes ago ","posted 1 hour ago"... What s the best way ? Thanx.

热门标签