为什么应用程序的主要功能代码在授权工作流之前开始运行?

huangapple go评论77阅读模式
英文:

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&#39;m fairly certain the scopes are correct and I&#39;ve managed to return the correct access and refresh tokens, but I cannot figure out how to only get the tracks from the user&#39;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&#39;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 = &quot;https://accounts.spotify.com/api/token&quot;; // Spotify Web API URL
const client_id = &#39;467fab359c114e719ecefafd6af299e5&#39;; // Client id
const client_secret = &#39;your_client_secret&#39; // temp client secret
// const client_secret = process.env.CLIENT_SECRET;
const redirect_uri = &#39;http://localhost:3030/callback/&#39;; // Callback URL

let AT, RT; // Stores access and refresh tokens
const scope = [
  &#39;user-read-private&#39;,
  &#39;user-read-email&#39;,
  &#39;user-library-read&#39;,
  &#39;playlist-read-private&#39;,
  &#39;playlist-modify-public&#39;,
  &#39;playlist-modify-private&#39;
];


/**
 * 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 = &#39;&#39;;
  let possible = &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&#39;;

  for (let i = 0; i &lt; length; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
};

let stateKey = &#39;spotify_auth_state&#39;;


const authorizeSpotify = () =&gt; {
  return new Promise((resolve, reject) =&gt; {
    app.get(&#39;/&#39;, function (req, res) {
      res.sendFile(__dirname + &quot;/index.html&quot;);
      res.redirect(&#39;/login&#39;);
    });
    
    app.use(Express.static(__dirname + &#39;/index.html&#39;))
      .use(cors())
      .use(cookieParser());
    
    app.get(&#39;/login&#39;, function (req, res) {
    
      let state = generateRandomString(16);
      res.cookie(stateKey, state);
    
      //  app requests authorization
      res.redirect(&#39;https://accounts.spotify.com/authorize?&#39; +
        querystring.stringify({
          response_type: &#39;code&#39;,
          client_id: client_id,
          scope: scope,
          redirect_uri: redirect_uri,
          state: state
        }));
    });
    
    app.get(&#39;/callback&#39;, 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(&#39;/#&#39; +
          querystring.stringify({
            error: &#39;state_mismatch&#39;
          }));
      } else {
        res.clearCookie(stateKey);
        let authOptions = {
          url: &#39;https://accounts.spotify.com/api/token&#39;,
          form: {
            code: code,
            redirect_uri: redirect_uri,
            grant_type: &#39;authorization_code&#39;
          },
          headers: {
            &#39;Authorization&#39;: &#39;Basic &#39; + (Buffer.from(client_id + &#39;:&#39; + client_secret).toString(&#39;base64&#39;))
          },
          json: true
        };
    
    
        request.post(authOptions, function (error, response, body) {
          if (!error &amp;&amp; response.statusCode === 200) {
    
            console.log(body);
    
            AT = body.access_token;
            RT = body.refresh_token;
    
            let options = {
              url: &#39;https://api.spotify.com/v1/me&#39;,
              headers: { &#39;Authorization&#39;: &#39;Bearer &#39; + AT },
              json: true
            };
    
            interval = setInterval(requestToken, body.expires_in * 1000 * 0.70);
    
            previousExpires = body.expires_in;
    
            res.send(&quot;Logged in!&quot;);
          }
        });
      }
    });
    
    
    
    let interval;
    let previousExpires = 0;
    
    const requestToken = () =&gt; {
    
      const authOptions = {
        url: &#39;https://accounts.spotify.com/api/token&#39;,
        headers: { &#39;Authorization&#39;: &#39;Basic &#39; + (Buffer.from(client_id + &#39;:&#39; + client_secret).toString(&#39;base64&#39;)) },
        form: {
          grant_type: &#39;refresh_token&#39;,
          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(&quot;Access Token refreshed!&quot;);
    
        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&#39;s library tracks
const getUserLibraryTracks = (AT) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    const options = {
      url: &#39;https://api.spotify.com/v1/me/tracks&#39;,
      headers: { &#39;Authorization&#39;: &#39;Bearer &#39; + AT },
      json: true
    };

    request.get(options, (error, response, body) =&gt; {
      if (error || response.statusCode !== 200) {
        console.log(&#39;Response:&#39;, body);
        reject(error || new Error(&#39;Failed to get user library tracks&#39;));
      } else {
        resolve(body.items.map(item =&gt; item.track));
      }
    });
  });
};

// Function to get the tracks in a playlist
const getPlaylistTracks = (AT, playlistId) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    const options = {
      url: `https://api.spotify.com/v1/playlists/${playlistId}/tracks`,
      headers: { &#39;Authorization&#39;: &#39;Bearer &#39; + AT },
      json: true
    };

    request.get(options, (error, response, body) =&gt; {
      if (error || response.statusCode !== 200) {
        reject(error || new Error(&#39;Failed to get playlist tracks&#39;));
      } else {
        resolve(body.items.map(item =&gt; item.track));
      }
    });
  });
};

// Function to add tracks to a playlist
const addTracksToPlaylist = (AT, playlistId, trackIds) =&gt; {
  return new Promise((resolve, reject) =&gt; {
    const options = {
      url: `https://api.spotify.com/v1/playlists/${playlistId}/tracks`,
      headers: { &#39;Authorization&#39;: &#39;Bearer &#39; + AT },
      json: true,
      body: { uris: trackIds }
    };

    request.post(options, (error, response, body) =&gt; {
      if (error || response.statusCode !== 201) {
        reject(error || new Error(&#39;Failed to add tracks to playlist&#39;));
      } else {
        resolve();
      }
    });
  });
};

// Function to update the playlist with new tracks
const updatePlaylist = async (playlistId) =&gt; {
  
  try {
    const {AT, RT } = await authorizeSpotify();
    const libraryTracks = await getUserLibraryTracks(AT);
    const playlistTracks = await getPlaylistTracks(AT, playlistId);

    const trackIdsToAdd = libraryTracks
      .filter(track =&gt; !playlistTracks.some(playlistTrack =&gt; playlistTrack.id === track.id))
      .map(track =&gt; track.uri);

    await addTracksToPlaylist(AT, playlistId, trackIdsToAdd);

    console.log(&#39;Playlist updated successfully&#39;);
  } catch (error) {
    console.error(&#39;Failed to update playlist:&#39;, error);
  }
};

// Call the updatePlaylist function to update the playlist
updatePlaylist(&#39;your_playlist_id&#39;);

app.listen(port, () =&gt; 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: &#39;Invalid access token&#39; } }
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.&lt;anonymous&gt; (/home/nero/Projects/Autoadd/node_modules/request/request.js:1154:10)
at Request.emit (events.js:314:20)
at IncomingMessage.&lt;anonymous&gt; (/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: &#39;BQBC1CAN2Wv3PIR1XdwTuQwgrHjQ1eCgQJqAZ0PWBNAiHGk6OKqsJFeafJEqBXBWfg1qpOvVxfEJ4SF77OHgxn9OvxS8Lg9Na0NSFlz1iWR26xztSJEq4Or-hwUKB2yE_Y-X6yPvzaScar7HDFADSQtVMxOx1Z8wq3hbi498i0bGTTnYccFTijopoSxbwfKvbfMTRxNrdUJt0z8u_w&#39;,
token_type: &#39;Bearer&#39;,
expires_in: 3600,
refresh_token: &#39;AQC3bMXEM23qjQqOOXrC5Tcsvt6ijfp2umMyz466u1DCi9nNN2J9jsU0Q4ilYq2cu19xA80fhrljQSutWrFGyBzOUV3i1mytO4UBEjbbKOHuKXFXwEYV83Rxzo-7ic_-YFA&#39;,
scope: &#39;playlist-modify-private&#39;
}

答案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(&quot;express&quot;)
const axios = require(&#39;axios&#39;)
const cors = require(&quot;cors&quot;);

const app = express()
app.use(cors())

CLIENT_ID = &quot;&lt;your client ID&gt;&quot;
CLIENT_SECRET = &quot;&lt;your client secret&gt;&quot;
PORT = 3030 // it is located in Spotify dashboard&#39;s Redirect URIs, my port is 3000
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is &#39;http://localhost:3000/callback&#39;
PLAYLIST_ID = &#39;your playlist ID&#39;
SCOPE = [
    &#39;user-read-private&#39;,
    &#39;user-read-email&#39;,
    &#39;user-library-read&#39;,
    &#39;playlist-read-private&#39;,
    &#39;playlist-modify-public&#39;,
    &#39;playlist-modify-private&#39;
]

const getToken = async (code) =&gt; {
    try {
        const resp = await axios.post(
            url = &#39;https://accounts.spotify.com/api/token&#39;,
            data = new URLSearchParams({
                &#39;grant_type&#39;: &#39;authorization_code&#39;,
                &#39;redirect_uri&#39;: REDIRECT_URI,
                &#39;code&#39;: code
            }),
            config = {
                headers: {
                    &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;
                },
                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) =&gt; {
    try {
        const uris = []
        for(const track of tracks) {
            if (track.new) {
                uris.push(track.uri)
            }
        }

        const chunkSize = 100;
        for (let i = 0; i &lt; 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 = {
                    &#39;uris&#39;: sub_uris
                },
                config = {
                    headers: {
                        &#39;Content-Type&#39;: &#39;application/json&#39;,
                        &#39;Authorization&#39;: `Bearer ${token}`,
                    }
                })
            }
        return Promise.resolve(&#39;OK&#39;);
    } catch (err) {
        console.error(err)
        return Promise.reject(err)
    }
}

const getPlaylistTracks = async (playlist, token) =&gt; {
    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: {
                        &#39;Accept-Encoding&#39;: &#39;application/json&#39;,
                        &#39;Authorization&#39;: `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) =&gt; {
    const { length } = arr;
    const id = length + 1;
    const found = arr.some(el =&gt; 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) =&gt; {
    try {
        new_tracks = previous_tracks.map(a =&gt; 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) =&gt; {
    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}&amp;offset=${offset}`,
                config = {
                    headers: {
                        &#39;Accept-Encoding&#39;: &#39;application/json&#39;,
                        &#39;Authorization&#39;: `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&#39;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(&quot;express&quot;)
const axios = require(&#39;axios&#39;)
const cors = require(&quot;cors&quot;);
const app = express()
app.use(cors())
CLIENT_ID = &quot;&lt;your client ID&gt;&quot;
CLIENT_SECRET = &quot;&lt;your client secret&gt;&quot;
PORT = 3030 // it is located in Spotify dashboard&#39;s Redirect URIs, my port is 3000
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is &#39;http://localhost:3000/callback&#39;
PLAYLIST_ID = &#39;your playlist ID&#39;
SCOPE = [
&#39;user-read-private&#39;,
&#39;user-read-email&#39;,
&#39;user-library-read&#39;,
&#39;playlist-read-private&#39;,
&#39;playlist-modify-public&#39;,
&#39;playlist-modify-private&#39;
]
const getToken = async (code) =&gt; {
try {
const resp = await axios.post(
url = &#39;https://accounts.spotify.com/api/token&#39;,
data = new URLSearchParams({
&#39;grant_type&#39;: &#39;authorization_code&#39;,
&#39;redirect_uri&#39;: REDIRECT_URI,
&#39;code&#39;: code
}),
config = {
headers: {
&#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;
},
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) =&gt; {
try {
const uris = []
for(const track of tracks) {
if (track.new) {
uris.push(track.uri)
}
}
const chunkSize = 100;
for (let i = 0; i &lt; 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 = {
&#39;uris&#39;: sub_uris
},
config = {
headers: {
&#39;Content-Type&#39;: &#39;application/json&#39;,
&#39;Authorization&#39;: `Bearer ${token}`,
}
})
}
return Promise.resolve(&#39;OK&#39;);
} catch (err) {
console.error(err)
return Promise.reject(err)
}
}
const getPlaylistTracks = async (playlist, token) =&gt; {
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: {
&#39;Accept-Encoding&#39;: &#39;application/json&#39;,
&#39;Authorization&#39;: `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) =&gt; {
const { length } = arr;
const id = length + 1;
const found = arr.some(el =&gt; 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) =&gt; {
try {
new_tracks = previous_tracks.map(a =&gt; 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) =&gt; {
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}&amp;offset=${offset}`,
config = {
headers: {
&#39;Accept-Encoding&#39;: &#39;application/json&#39;,
&#39;Authorization&#39;: `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(&quot;/login&quot;, (request, response) =&gt; {
const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&amp;client_id=${CLIENT_ID}&amp;scope=${SCOPE}&amp;state=123456&amp;redirect_uri=${REDIRECT_URI}&amp;prompt=consent`
response.redirect(redirect_url);
})
app.get(&quot;/callback&quot;, async (request, response) =&gt; {
const code = request.query[&quot;code&quot;]
getToken(code)
.then(access_token =&gt; {
getMyTracks(access_token)
.then(my_tracks =&gt; {
getPlaylistTracks(PLAY_LIST_ID, access_token)
.then(previous_tracks =&gt; {
updatePlaylistTracks(my_tracks, previous_tracks, access_token)
.then(new_tracks =&gt; {
addSongs(PLAY_LIST_ID, new_tracks, access_token)
.then(OK =&gt; {
return response.send({ 
&#39;my tracks Total:&#39;: my_tracks.length,
&#39;my tracks&#39;: my_tracks,
&#39;previous playlist Total:&#39;: previous_tracks.length,
&#39;previous playlist&#39;: previous_tracks,
&#39;new playlist Total:&#39;: new_tracks.length,
&#39;new playlist&#39;: new_tracks,
&#39;add song result&#39;: OK });
})
})
})
})
})
.catch(error =&gt; {
console.log(error.message);
})
})
app.listen(PORT, () =&gt; {
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(&quot;express&quot;)
const axios = require(&#39;axios&#39;)
const cors = require(&quot;cors&quot;);

const app = express()
app.use(cors())

CLIENT_ID = &quot;&lt;your client ID&gt;&quot;
CLIENT_SECRET = &quot;&lt;your client secret&gt;&quot;
PORT = 3030 // it is located in Spotify dashboard&#39;s Redirect URIs
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is &#39;http://localhost:3000/callback&#39;
SCOPE = [
    &#39;user-read-private&#39;,
    &#39;user-read-email&#39;,
    &#39;user-library-read&#39;,
    &#39;playlist-read-private&#39;,
    &#39;playlist-modify-public&#39;,
    &#39;playlist-modify-private&#39;
]

app.get(&quot;/login&quot;, (request, response) =&gt; {
    const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&amp;client_id=${CLIENT_ID}&amp;scope=${SCOPE}&amp;state=123456&amp;redirect_uri=${REDIRECT_URI}&amp;prompt=consent`
    response.redirect(redirect_url);
})

const getToken = async (code) =&gt; {
    try {
        const resp = await axios.post(
            &#39;https://accounts.spotify.com/api/token&#39;,
            new URLSearchParams({
                &#39;grant_type&#39;: &#39;authorization_code&#39;,
                &#39;redirect_uri&#39;: REDIRECT_URI,
                &#39;code&#39;: code
            }),
            {
                headers: {
                    &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;
                },
                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) =&gt; {
    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}&amp;offset=${offset}`,
                config = {
                    headers: {
                        &#39;Accept-Encoding&#39;: &#39;application/json&#39;,
                        &#39;Authorization&#39;: `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(&quot;/callback&quot;, async (request, response) =&gt; {
    const code = request.query[&quot;code&quot;]
    getToken(code)
        .then(access_token =&gt; {
            getMyTracks(access_token)
                .then(my_tracks =&gt; {
                    return response.send({ 
                        &#39;total:&#39; : my_tracks.length,
                        &#39;my tracks&#39;: my_tracks
                    });
                })
        })
        .catch(error =&gt; {
            console.log(error.message);
        })
    })

app.listen(PORT, () =&gt; {
    console.log(`Listening on :${PORT}`)
})

Update V3 for plylist only

Step By Step, this code can get playlist songs.

const express = require(&quot;express&quot;)
const axios = require(&#39;axios&#39;)
const cors = require(&quot;cors&quot;);

const app = express()
app.use(cors())

CLIENT_ID = &quot;&lt;your client ID&gt;&quot;
CLIENT_SECRET = &quot;&lt;your client secret&gt;&quot;
PORT = 3030 // it is located in Spotify dashboard&#39;s Redirect URIs
REDIRECT_URI = `http://localhost:${PORT}/callback` // my case is &#39;http://localhost:3000/callback&#39;
PLAYLIST_ID = &#39;your playlist ID&#39;
SCOPE = [
    &#39;user-read-private&#39;,
    &#39;user-read-email&#39;,
    &#39;user-library-read&#39;,
    &#39;playlist-read-private&#39;,
    &#39;playlist-modify-public&#39;,
    &#39;playlist-modify-private&#39;
]

app.get(&quot;/login&quot;, (request, response) =&gt; {
    const redirect_url = `https://accounts.spotify.com/authorize?response_type=code&amp;client_id=${CLIENT_ID}&amp;scope=${SCOPE}&amp;state=123456&amp;redirect_uri=${REDIRECT_URI}&amp;prompt=consent`
    response.redirect(redirect_url);
})

const getToken = async (code) =&gt; {
    try {
        const resp = await axios.post(
            &#39;https://accounts.spotify.com/api/token&#39;,
            new URLSearchParams({
                &#39;grant_type&#39;: &#39;authorization_code&#39;,
                &#39;redirect_uri&#39;: REDIRECT_URI,
                &#39;code&#39;: code
            }),
            {
                headers: {
                    &#39;Content-Type&#39;: &#39;application/x-www-form-urlencoded&#39;
                },
                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) =&gt; {
    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: {
                        &#39;Accept-Encoding&#39;: &#39;application/json&#39;,
                        &#39;Authorization&#39;: `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(&quot;/callback&quot;, async (request, response) =&gt; {
    const code = request.query[&quot;code&quot;]
    getToken(code)
        .then(access_token =&gt; {
            getPlaylistTracks(PLAYLIST_ID, access_token)
                .then(tracks =&gt; {
                    return response.send({ 
                        &#39;total:&#39; : tracks.length,
                        &#39;playlist tracks&#39;: tracks
                    });
                })
        })
        .catch(error =&gt; {
            console.log(error.message);
        })
    })

app.listen(PORT, () =&gt; {
    console.log(`Listening on :${PORT}`)
})

huangapple
  • 本文由 发表于 2023年7月4日 20:25:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76612617.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定