Added rate limiter

master
oabrivard 3 months ago
parent 2b64a9aa17
commit a89f278c24

@ -5,7 +5,8 @@
"Bash(node /Users/oabrivard/Projects/javascript/spotify/get_popularity.js \"Radiohead\")",
"Bash(npm init:*)",
"Bash(npm install:*)",
"Bash(node /Users/oabrivard/Projects/javascript/spotify/get_popularity.js --url \"https://www.rockenseine.com/programmation\")"
"Bash(node /Users/oabrivard/Projects/javascript/spotify/get_popularity.js --url \"https://www.rockenseine.com/programmation\")",
"WebFetch(domain:developer.spotify.com)"
]
}
}

@ -7,6 +7,29 @@ const CLIENT_ID = "44e13265ab164d209a8665e7d0ae2237";
const CLIENT_SECRET = "cbd639db2c6b4c01981e8dd0cb00569b";
const REDIRECT_URI = "http://127.0.0.1:8888/callback";
// --- Rate Limiter ---
function createRateLimiter(maxPerSecond) {
const minInterval = 1000 / maxPerSecond;
let lastCall = 0;
return async function throttle() {
const now = Date.now();
const elapsed = now - lastCall;
if (elapsed < minInterval) {
await new Promise((resolve) => setTimeout(resolve, minInterval - elapsed));
}
lastCall = Date.now();
};
}
const spotifyThrottle = createRateLimiter(2);
async function spotifyFetch(url, options = {}) {
await spotifyThrottle();
return fetch(url, options);
}
// --- LLM Providers ---
const EXTRACTION_PROMPT = `Extract all music artist or band names from this web page text. The page is about concerts, festivals, or music venues.
@ -120,11 +143,12 @@ async function getUserAccessToken() {
// --- Spotify API ---
async function searchArtist(token, artistName) {
const response = await fetch(
const response = await spotifyFetch(
`https://api.spotify.com/v1/search?q=${encodeURIComponent(artistName)}&type=artist&limit=1`,
{ headers: { Authorization: `Bearer ${token}` } }
);
//console.log(`Server response for "${artistName}" on Spotify:`, response.headers, response.status, response.statusText, await response.text());
const data = await response.json();
const artist = data.artists.items[0];
if (!artist) {
@ -134,8 +158,8 @@ async function searchArtist(token, artistName) {
}
async function getTracksByArtist(token, artistName) {
const response = await fetch(
`https://api.spotify.com/v1/search?q=${encodeURIComponent(`artist:${artistName}`)}&type=track&limit=10`,
const response = await spotifyFetch(
`https://api.spotify.com/v1/search?q=${encodeURIComponent(`artist:${artistName}`)}&type=track%2Cshow&limit=10`,
{ headers: { Authorization: `Bearer ${token}` } }
);
@ -146,19 +170,6 @@ async function getTracksByArtist(token, artistName) {
return data.tracks.items;
}
async function getTrack(token, trackId) {
const response = await fetch(
`https://api.spotify.com/v1/tracks/${trackId}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const data = await response.json();
if (!response.ok) {
throw new Error(`Spotify API error: ${JSON.stringify(data)}`);
}
return data;
}
async function getTopTracks(token, artistName, topN = 3) {
const artist = await searchArtist(token, artistName);
if (!artist) {
@ -166,8 +177,7 @@ async function getTopTracks(token, artistName, topN = 3) {
return [];
}
const genres = artist.genres?.length ? artist.genres.join(", ") : "N/A";
console.log(`\nArtist: ${artist.name} (${genres})`);
console.log(`\nArtist: ${artist.name}`);
const tracks = await getTracksByArtist(token, artistName);
const topTracks = tracks
@ -179,19 +189,13 @@ async function getTopTracks(token, artistName, topN = 3) {
return [];
}
const topTracksWithDetails = await Promise.all(
topTracks.map((t) => getTrack(token, t.id))
);
topTracksWithDetails.forEach((track, i) => {
const popularity =
track.popularity !== undefined ? ` - Popularity: ${track.popularity}` : "";
topTracks.forEach((track, i) => {
console.log(
` ${i + 1}. "${track.name}"${popularity} (Album: ${track.album.name})`
` ${i + 1}. "${track.name}" (Album: ${track.album.name})`
);
});
return topTracksWithDetails.map((t) => t.uri);
return topTracks.map((t) => t.uri);
}
// --- Playlist Creation ---
@ -204,20 +208,10 @@ function playlistNameFromUrl(url) {
.replace(/^_|_$/g, "");
}
async function getCurrentUserId(token) {
const response = await fetch("https://api.spotify.com/v1/me", {
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
if (!response.ok) {
throw new Error(`Failed to get user profile: ${JSON.stringify(data)}`);
}
return data.id;
}
async function createPlaylist(token, userId, name, trackUris) {
const response = await fetch(
`https://api.spotify.com/v1/users/${userId}/playlists`,
async function createPlaylist(token, name, trackUris) {
// Use POST /me/playlists (Feb 2026: /users/{id}/playlists removed)
const response = await spotifyFetch(
`https://api.spotify.com/v1/me/playlists`,
{
method: "POST",
headers: {
@ -237,11 +231,12 @@ async function createPlaylist(token, userId, name, trackUris) {
throw new Error(`Failed to create playlist: ${JSON.stringify(playlist)}`);
}
// Use POST /playlists/{id}/items (Feb 2026: /playlists/{id}/tracks removed)
// Spotify allows max 100 tracks per request
for (let i = 0; i < trackUris.length; i += 100) {
const batch = trackUris.slice(i, i + 100);
const addResponse = await fetch(
`https://api.spotify.com/v1/playlists/${playlist.id}/tracks`,
const addResponse = await spotifyFetch(
`https://api.spotify.com/v1/playlists/${playlist.id}/items`,
{
method: "POST",
headers: {
@ -380,9 +375,8 @@ async function main() {
if (shouldCreatePlaylist && allTrackUris.length > 0) {
console.log(`\nCreating playlist with ${allTrackUris.length} tracks...`);
const userToken = await getUserAccessToken();
const userId = await getCurrentUserId(userToken);
const playlistName = playlistNameFromUrl(url);
const playlist = await createPlaylist(userToken, userId, playlistName, allTrackUris);
const playlist = await createPlaylist(userToken, playlistName, allTrackUris);
console.log(`\nPlaylist created: "${playlist.name}"`);
console.log(`URL: ${playlist.external_urls.spotify}`);
}

Loading…
Cancel
Save