From 41e2ec1495e4e579924b8842474c60b4c673f0b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=B6rdle=20Bot?= Date: Mon, 24 Nov 2025 18:47:25 +0100 Subject: [PATCH] feat: Add rate limiting and request serialization to iTunes API calls. --- lib/itunes.ts | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/lib/itunes.ts b/lib/itunes.ts index 2539f8e..f6a6548 100644 --- a/lib/itunes.ts +++ b/lib/itunes.ts @@ -19,6 +19,15 @@ interface ItunesResponse { results: ItunesResult[]; } +// Rate limiting state +let lastRequestTime = 0; +let blockedUntil = 0; +const MIN_INTERVAL = 2000; // 2 seconds = 30 requests per minute +const BLOCK_DURATION = 60000; // 60 seconds pause after 403 + +// Mutex for serializing requests +let requestQueue = Promise.resolve(null); + /** * Get the earliest release year for a song from iTunes * @param artist Artist name @@ -26,10 +35,33 @@ interface ItunesResponse { * @returns Release year or null if not found */ export async function getReleaseYearFromItunes(artist: string, title: string): Promise { + // Queue the request to ensure sequential execution and rate limiting + const result = requestQueue.then(() => executeRequest(artist, title)); + + // Update queue to wait for this request + requestQueue = result.catch(() => null); + + return result; +} + +async function executeRequest(artist: string, title: string): Promise { try { + // Check if blocked + const now = Date.now(); + if (now < blockedUntil) { + const waitTime = blockedUntil - now; + console.log(`iTunes API blocked (403/429). Waiting ${Math.ceil(waitTime / 1000)}s before next request...`); + await new Promise(resolve => setTimeout(resolve, waitTime)); + } + + // Enforce rate limit (min interval) + const timeSinceLast = Date.now() - lastRequestTime; + if (timeSinceLast < MIN_INTERVAL) { + const delay = MIN_INTERVAL - timeSinceLast; + await new Promise(resolve => setTimeout(resolve, delay)); + } + // Construct search URL - // entity=song ensures we get individual tracks - // limit=10 to get a good range of potential matches (originals, remasters, best ofs) const term = encodeURIComponent(`${artist} ${title}`); const url = `https://itunes.apple.com/search?term=${term}&entity=song&limit=10`; @@ -41,6 +73,14 @@ export async function getReleaseYearFromItunes(artist: string, title: string): P } }); + lastRequestTime = Date.now(); + + if (response.status === 403 || response.status === 429) { + console.warn(`iTunes API rate limit hit (${response.status}). Pausing for 60s.`); + blockedUntil = Date.now() + BLOCK_DURATION; + return null; + } + if (!response.ok) { console.error(`iTunes API error: ${response.status} ${response.statusText}`); return null;