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(node /Users/oabrivard/Projects/javascript/spotify/get_popularity.js \"Radiohead\")",
"Bash(npm init:*)", "Bash(npm init:*)",
"Bash(npm install:*)", "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 CLIENT_SECRET = "cbd639db2c6b4c01981e8dd0cb00569b";
const REDIRECT_URI = "http://127.0.0.1:8888/callback"; 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 --- // --- 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. 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 --- // --- Spotify API ---
async function searchArtist(token, artistName) { 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`, `https://api.spotify.com/v1/search?q=${encodeURIComponent(artistName)}&type=artist&limit=1`,
{ headers: { Authorization: `Bearer ${token}` } } { 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 data = await response.json();
const artist = data.artists.items[0]; const artist = data.artists.items[0];
if (!artist) { if (!artist) {
@ -134,8 +158,8 @@ async function searchArtist(token, artistName) {
} }
async function getTracksByArtist(token, artistName) { async function getTracksByArtist(token, artistName) {
const response = await fetch( const response = await spotifyFetch(
`https://api.spotify.com/v1/search?q=${encodeURIComponent(`artist:${artistName}`)}&type=track&limit=10`, `https://api.spotify.com/v1/search?q=${encodeURIComponent(`artist:${artistName}`)}&type=track%2Cshow&limit=10`,
{ headers: { Authorization: `Bearer ${token}` } } { headers: { Authorization: `Bearer ${token}` } }
); );
@ -146,19 +170,6 @@ async function getTracksByArtist(token, artistName) {
return data.tracks.items; 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) { async function getTopTracks(token, artistName, topN = 3) {
const artist = await searchArtist(token, artistName); const artist = await searchArtist(token, artistName);
if (!artist) { if (!artist) {
@ -166,8 +177,7 @@ async function getTopTracks(token, artistName, topN = 3) {
return []; return [];
} }
const genres = artist.genres?.length ? artist.genres.join(", ") : "N/A"; console.log(`\nArtist: ${artist.name}`);
console.log(`\nArtist: ${artist.name} (${genres})`);
const tracks = await getTracksByArtist(token, artistName); const tracks = await getTracksByArtist(token, artistName);
const topTracks = tracks const topTracks = tracks
@ -179,19 +189,13 @@ async function getTopTracks(token, artistName, topN = 3) {
return []; return [];
} }
const topTracksWithDetails = await Promise.all( topTracks.forEach((track, i) => {
topTracks.map((t) => getTrack(token, t.id))
);
topTracksWithDetails.forEach((track, i) => {
const popularity =
track.popularity !== undefined ? ` - Popularity: ${track.popularity}` : "";
console.log( 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 --- // --- Playlist Creation ---
@ -204,20 +208,10 @@ function playlistNameFromUrl(url) {
.replace(/^_|_$/g, ""); .replace(/^_|_$/g, "");
} }
async function getCurrentUserId(token) { async function createPlaylist(token, name, trackUris) {
const response = await fetch("https://api.spotify.com/v1/me", { // Use POST /me/playlists (Feb 2026: /users/{id}/playlists removed)
headers: { Authorization: `Bearer ${token}` }, const response = await spotifyFetch(
}); `https://api.spotify.com/v1/me/playlists`,
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`,
{ {
method: "POST", method: "POST",
headers: { headers: {
@ -237,11 +231,12 @@ async function createPlaylist(token, userId, name, trackUris) {
throw new Error(`Failed to create playlist: ${JSON.stringify(playlist)}`); 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 // Spotify allows max 100 tracks per request
for (let i = 0; i < trackUris.length; i += 100) { for (let i = 0; i < trackUris.length; i += 100) {
const batch = trackUris.slice(i, i + 100); const batch = trackUris.slice(i, i + 100);
const addResponse = await fetch( const addResponse = await spotifyFetch(
`https://api.spotify.com/v1/playlists/${playlist.id}/tracks`, `https://api.spotify.com/v1/playlists/${playlist.id}/items`,
{ {
method: "POST", method: "POST",
headers: { headers: {
@ -380,9 +375,8 @@ async function main() {
if (shouldCreatePlaylist && allTrackUris.length > 0) { if (shouldCreatePlaylist && allTrackUris.length > 0) {
console.log(`\nCreating playlist with ${allTrackUris.length} tracks...`); console.log(`\nCreating playlist with ${allTrackUris.length} tracks...`);
const userToken = await getUserAccessToken(); const userToken = await getUserAccessToken();
const userId = await getCurrentUserId(userToken);
const playlistName = playlistNameFromUrl(url); 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(`\nPlaylist created: "${playlist.name}"`);
console.log(`URL: ${playlist.external_urls.spotify}`); console.log(`URL: ${playlist.external_urls.spotify}`);
} }

Loading…
Cancel
Save