diff --git a/scripts/migrate-release-years.mjs b/scripts/migrate-release-years.mjs index 78f1046..ad9b03b 100644 --- a/scripts/migrate-release-years.mjs +++ b/scripts/migrate-release-years.mjs @@ -12,13 +12,13 @@ const prisma = new PrismaClient(); const MUSICBRAINZ_API_BASE = 'https://musicbrainz.org/ws/2'; const USER_AGENT = 'hoerdle/0.1.0 ( elpatron@mailbox.org )'; -const RATE_LIMIT_DELAY = 100; // 100ms between requests (conservative) +const RATE_LIMIT_DELAY = 250; // 250ms between requests (very conservative) function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -async function fetchWithRetry(url, maxRetries = 5) { +async function fetchWithRetry(url, maxRetries = 10) { let lastError = null; for (let attempt = 0; attempt < maxRetries; attempt++) { @@ -52,29 +52,40 @@ async function fetchWithRetry(url, maxRetries = 5) { } async function getReleaseYear(artist, title) { - try { - const query = `artist:"${artist}" AND recording:"${title}"`; - const url = `${MUSICBRAINZ_API_BASE}/recording?query=${encodeURIComponent(query)}&fmt=json&limit=10`; - + const search = async (query, type) => { + const url = `${MUSICBRAINZ_API_BASE}/recording?query=${encodeURIComponent(query)}&fmt=json&limit=5`; await sleep(RATE_LIMIT_DELAY); - const response = await fetchWithRetry(url); + if (!response.ok) throw new Error(`API Error ${response.status}: ${response.statusText}`); + return response.json(); + }; - if (!response.ok) { - console.error(`MusicBrainz API error: ${response.status} ${response.statusText}`); - return null; + try { + // 1. Strict Search + let data = await search(`artist:"${artist}" AND recording:"${title}"`, 'strict'); + + // 2. Fallback: Fuzzy Search if no recordings found + if (!data.recordings || data.recordings.length === 0) { + // Remove special chars and quotes for fuzzy search + const cleanArtist = artist.replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim(); + const cleanTitle = title.replace(/[^\w\s]/g, ' ').replace(/\s+/g, ' ').trim(); + + // Only try fuzzy if the cleaned strings are valid + if (cleanArtist && cleanTitle) { + console.log(` Trying fuzzy search for: ${cleanTitle} by ${cleanArtist}`); + data = await search(`artist:${cleanArtist} AND recording:${cleanTitle}`, 'fuzzy'); + } } - const data = await response.json(); - if (!data.recordings || data.recordings.length === 0) { - // console.log(`No recordings found for "${title}" by "${artist}"`); + console.log(` ❌ No recordings found for "${title}" by "${artist}"`); return null; } let earliestYear = null; for (const recording of data.recordings) { + // Check releases linked to recording if (recording.releases && recording.releases.length > 0) { for (const release of recording.releases) { if (release.date) { @@ -86,6 +97,7 @@ async function getReleaseYear(artist, title) { } } + // Check first-release-date on recording itself if (recording['first-release-date']) { const year = parseInt(recording['first-release-date'].split('-')[0]); if (!isNaN(year) && (earliestYear === null || year < earliestYear)) { @@ -94,9 +106,15 @@ async function getReleaseYear(artist, title) { } } + if (earliestYear) { + // console.log(` ✅ Found year: ${earliestYear}`); + } else { + console.log(` ⚠️ Recordings found but NO YEAR for "${title}" by "${artist}"`); + } + return earliestYear; } catch (error) { - console.error(`Error fetching release year for "${title}" by "${artist}":`, error.message); + console.error(` ❌ Error fetching release year for "${title}" by "${artist}":`, error.message); return null; } }