import db from '../../utils/infra/db.util.js'; import { SpotifyMetadata, Format } from '../../types/index.js'; interface RawMapping { url: string; title: string; artist: string; album: string; imageUrl: string; duration: number; isrc: string; previewUrl: string; youtubeUrl: string; formats: string; audioFormats: string; audioFeatures: string; year: string; timestamp: number; } // strip poisoned/volatile cached formats export function cleanFormats(raw: unknown): Format[] { let arr: unknown = raw; if (typeof raw === 'string') { try { arr = JSON.parse(raw); } catch { return []; } } if (!Array.isArray(arr)) return []; return arr.filter( (fmt): fmt is Format => !!fmt && typeof (fmt as Format).formatId === 'string' && (fmt as Format).formatId !== 'undefined' && !String((fmt as { url?: unknown }).url ?? '').includes('PENDING_DECIPHER') ); } // validated mapping or null to re-resolve export function parseCachedMapping(row: { youtubeUrl?: string | null; formats?: unknown; audioFormats?: unknown; }): { formats: Format[]; audioFormats: Format[] } | null { if (!row.youtubeUrl || !/^https?:\/\//u.test(row.youtubeUrl)) return null; const formats = cleanFormats(row.formats); if (formats.length === 0) return null; return { formats, audioFormats: cleanFormats(row.audioFormats) }; } if (db) { (async () => { try { await db.execute(` CREATE TABLE IF NOT EXISTS spotify_mappings ( url TEXT PRIMARY KEY, title TEXT, artist TEXT, album TEXT, imageUrl TEXT, duration INTEGER, isrc TEXT, previewUrl TEXT, youtubeUrl TEXT, formats TEXT, audioFormats TEXT, audioFeatures TEXT, year TEXT, timestamp INTEGER ) `); await db.execute( 'CREATE INDEX IF NOT EXISTS idx_spotify_isrc ON spotify_mappings(isrc)' ); await db.execute( 'CREATE INDEX IF NOT EXISTS idx_spotify_youtube ON spotify_mappings(youtubeUrl)' ); console.log('[Turso] Database initialized.'); } catch (err: unknown) { const error = err as Error; console.error('[Turso] Database bootstrap failed:', error.message); } })(); } export function saveToBrain(spotifyUrl: string, data: SpotifyMetadata): void { const activeDb = db; if (!activeDb) return; const isrc = data.isrc && data.isrc !== 'NONE' ? data.isrc : null; const isIsrcMatch = data.isIsrcMatch === true || (data.isrc && data.isrc !== 'NONE'); if (!isIsrcMatch || !isrc) { return; } const youtubeUrl = data.targetUrl || null; if (!youtubeUrl || !/^https?:\/\//u.test(youtubeUrl)) { return; } process.nextTick(() => { try { const cleanUrl = spotifyUrl.split('?')[0]; const args = [ cleanUrl, data.title || 'Unknown Title', data.artist || 'Unknown Artist', data.album || '', data.imageUrl || data.cover || data.thumbnail || null, data.duration || 0, isrc, data.previewUrl || null, youtubeUrl, JSON.stringify(cleanFormats(data.formats)), JSON.stringify(cleanFormats(data.audioFormats)), JSON.stringify(data.audioFeatures || null), data.year || 'Unknown', Date.now(), ]; activeDb .execute({ sql: 'INSERT OR REPLACE INTO spotify_mappings ' + '(url, title, artist, album, imageUrl, duration, isrc, previewUrl, youtubeUrl, formats, audioFormats, audioFeatures, year, timestamp) ' + 'VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', args, }) .catch((err: unknown) => { const error = err as Error; console.warn('[Turso] Failed to save to database:', error.message); }); if ( data.previewUrl && (data.previewUrl.includes('scdn.co') || data.previewUrl.includes('dzcdn.net') || data.previewUrl.includes('itunes.apple.com')) ) { activeDb .execute({ sql: 'INSERT OR REPLACE INTO volatile_links (url, expires_at, provider) VALUES (?, ?, ?)', args: [cleanUrl, Date.now() + 55 * 60 * 1000, 'spotify_preview'], }) .catch((err: Error) => { console.debug( '[Turso] Volatile link save failed:', (err as Error).message ); }); } } catch (err: unknown) { console.warn( '[Turso] Synchronous error preparing database save:', (err as Error).message ); } }); } export async function getFromBrain( cleanUrl: string ): Promise { if (!db) return null; try { const result = await db.execute({ sql: 'SELECT * FROM spotify_mappings WHERE url = ?', args: [cleanUrl], }); return (result.rows?.[0] as unknown as RawMapping) || null; } catch (err) { console.debug('[Turso] Brain lookup failed:', (err as Error).message); return null; } } export async function updatePreviewInBrain( cleanUrl: string, previewUrl: string ): Promise { const activeDb = db; if (!activeDb) return; try { await activeDb.execute({ sql: 'UPDATE spotify_mappings SET previewUrl = ? WHERE url = ?', args: [previewUrl, cleanUrl], }); if ( previewUrl && (previewUrl.includes('scdn.co') || previewUrl.includes('dzcdn.net') || previewUrl.includes('itunes.apple.com')) ) { await activeDb.execute({ sql: 'INSERT OR REPLACE INTO volatile_links (url, expires_at, provider) VALUES (?, ?, ?)', args: [cleanUrl, Date.now() + 55 * 60 * 1000, 'spotify_preview'], }); } } catch (err: unknown) { const error = err as Error; console.warn( '[Turso] Failed to update preview in database:', error.message ); } } if (db) { const activeDb = db; setInterval(() => { (async () => { try { const threshold = Date.now() + 5 * 60 * 1000; const result = await activeDb.execute({ sql: 'SELECT url, provider FROM volatile_links WHERE expires_at < ?', args: [threshold], }); if (result.rows && result.rows.length > 0) { const { refreshPreviewIfNeeded } = await import('./index.js'); let refreshed = 0; for (const row of result.rows) { if (row.provider === 'spotify_preview') { const url = row.url as string; const brainData = await getFromBrain(url); if (brainData) { await refreshPreviewIfNeeded( url, brainData as unknown as SpotifyMetadata ); refreshed++; } } } if (refreshed > 0) { console.log(`[JIT Worker] refreshed ${refreshed} preview link(s)`); } } } catch (err: unknown) { console.warn( '[JIT Worker] Error scanning volatile_links:', (err as Error).message ); } })(); }, 60000).unref(); }