import { Tracer } from "@luna/core"; import { settings } from "./Settings"; const { trace } = Tracer("[Radiant Lyrics]"); const sylTrace = (...args: unknown[]) => { if (settings.syllableLogging) trace.log(...args); }; export const RL_PLATFORM = "rl"; const RL_ACCESS_TOKEN_ID = "58hy4s86"; const RL_ACCESS_TOKEN = "xjehy2lfg5h5mjwotoxrcqugam"; // Yup that's right, plaintext token in a public Repo!! // The API does not return sensitive data & won't be plain text like this in the future <3 let cachedPublicIP: string | undefined; export async function ip(): Promise { if (cachedPublicIP) return cachedPublicIP; try { const res = await fetch("https://api.ipify.org?format=text"); if (res.ok) cachedPublicIP = (await res.text()).trim(); } catch {} return cachedPublicIP; } export async function auth(): Promise> { const clientIP = await ip(); return { "P-Access-Token-Id": RL_ACCESS_TOKEN_ID, "P-Access-Token": RL_ACCESS_TOKEN, "x-client-ip": clientIP ?? "null", }; } // Platform param (just for DX logging) const platformQs = `platform=${encodeURIComponent(RL_PLATFORM)}`; // Query string & params function query( title: string, artist: string, isrc: string | undefined, options?: { romanize?: boolean; flush?: boolean; synthesize?: boolean }, ): string { let q = `?title=${encodeURIComponent(title)}&artist=${encodeURIComponent(artist)}`; if (isrc) q += `&isrc=${encodeURIComponent(isrc)}`; if (options?.romanize) q += "&romanize=true"; if (options?.flush) q += "&flush=true"; if (options?.synthesize) q += "&synthesize=true"; q += `&${platformQs}`; return q; } // Response types export interface WordTiming { text: string; time: number; duration: number; isBackground: boolean; romanized?: string; /** 0..1 model confidence for synthesized timings; unset for provider data */ confidence?: number; } export interface WordLine { text: string; startTime: number; duration: number; endTime: number; syllabus: WordTiming[]; element: { key: string; songPart?: string; songPartIndex?: number; singer: string; }; translation: string | null; romanized?: string; } export interface ApiLine { text: string; startTime: number; duration: number; endTime: number; syllabus?: WordTiming[]; element?: { key: string; songPart?: string; songPartIndex?: number; singer?: string; }; translation?: string | null; romanized?: string; } export interface WordLyricsResponse { type: "Word"; data: WordLine[]; metadata: { source: string; title: string; language: string; totalDuration: string; agents?: Record; songParts?: Array<{ name: string; time: number; duration: number }>; }; _cached?: boolean; /** true when timings came from the hosted Radiant AI model (?synthesize=true) */ _synthesized?: boolean; /** original line-level data, kept so AI timings can be toggled off without a refetch */ lines?: ApiLine[]; } export interface LineLyricsResponse { type: "Line"; data: ApiLine[]; metadata: { source: string; title: string; language: string; totalDuration: string; agents?: Record; songParts?: Array<{ name: string; time: number; duration: number }>; }; _cached?: boolean; } export type LyricsApiResponse = WordLyricsResponse | LineLyricsResponse; type FetchOutcome = | { status: "ok"; data: LyricsApiResponse | null } | { status: "404" } | { status: "500" } | { status: "err" }; // Lyrics lookup (network) export async function fetchLyrics( title: string, artist: string, isrc: string | undefined, romanize: boolean, synthesize = false, ): Promise { const params = query(title, artist, isrc, { romanize, synthesize }); const atomixUrl = `https://api.atomix.one/rl-api${params}`; const fallbackUrl = `https://rl-api.kineticsand.net/lyrics${params}`; const rlApiHeaders = await auth(); const tryFetch = async (url: string, useAtomixAuth: boolean): Promise => { try { sylTrace(`RL API: Fetching lyrics: ${url}`); const res = await fetch(url, { headers: useAtomixAuth ? rlApiHeaders : undefined, }); if (!res.ok) { trace.log(`RL API: fetch failed: ${res.status} from ${url}`); if (res.status === 404) return { status: "404" }; return res.status === 500 ? { status: "500" } : { status: "err" }; } const data = (await res.json()) as LyricsApiResponse; if (!data?.data || !Array.isArray(data.data)) { trace.log("Lyrics API returned invalid payload"); return { status: "ok", data: null }; } if (data.type !== "Word" && data.type !== "Line") { trace.log("Lyrics not available in supported format"); return { status: "ok", data: null }; } return { status: "ok", data }; } catch (err) { trace.log(`RL API: fetch error from ${url}: ${err}`); return { status: "err" }; } }; const primary = await tryFetch(atomixUrl, true); if (primary.status === "ok") return primary.data; if (primary.status === "404") { trace.log("RL API: 404 — no API lyrics exist for this track"); return null; } if (primary.status === "500") { trace.log("RL API: 500 (Execution Timeout) — fallback"); } const fallback = await tryFetch(fallbackUrl, false); if (fallback.status === "ok") return fallback.data; if (fallback.status === "404") { trace.log("RL API: 404 from fallback — no API lyrics exist for this track"); return null; } if (fallback.status === "500") { trace.log("RL API: 500 from fallback — API IS ACTUALLY BORKED!"); return null; } trace.log("RL API: All Endpoints Failed"); return null; } export async function flushLyrics(track: { title: string; artist: string; isrc?: string; }): Promise< | { ok: true; data: LyricsApiResponse & { _flush?: string } } | { ok: false; status: number; notFound: boolean } > { const q = query(track.title, track.artist, track.isrc, { flush: true, }); const url = `https://api.atomix.one/rl-api${q}`; const headers = await auth(); const res = await fetch(url, { headers }); if (res.status === 404) { return { ok: false, status: 404, notFound: true }; } if (!res.ok) { return { ok: false, status: res.status, notFound: false }; } const data = (await res.json()) as LyricsApiResponse & { _flush?: string }; return { ok: true, data }; } // Romanize export async function romanizeLyrics( lineTexts: string[], ): Promise { if (lineTexts.length === 0) return null; const payload = { type: "Line" as const, data: lineTexts.map((text, idx) => ({ text, startTime: idx, duration: 0, endTime: idx, })), }; const romanizeQuery = `?${platformQs}`; const urls: { url: string; useAtomixAuth: boolean }[] = [ { url: `https://api.atomix.one/rl-api/romanize${romanizeQuery}`, useAtomixAuth: true, }, { url: `https://rl-api.kineticsand.net/romanize${romanizeQuery}`, useAtomixAuth: false, }, ]; for (const { url, useAtomixAuth } of urls) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); try { const romanizeHeaders: Record = { "content-type": "application/json", }; if (useAtomixAuth) { Object.assign(romanizeHeaders, await auth()); } const res = await fetch(url, { method: "POST", headers: romanizeHeaders, body: JSON.stringify(payload), signal: controller.signal, }); clearTimeout(timeout); if (!res.ok) { trace.log(`Romanize: request failed ${res.status} | ${url}`); continue; } const data = (await res.json()) as { type?: string; data?: Array<{ text?: string; romanized?: string }>; }; if (!Array.isArray(data?.data)) continue; return lineTexts.map((original, idx) => { const item = data.data?.[idx]; return item?.romanized ?? item?.text ?? original; }); } catch (err) { clearTimeout(timeout); if (err instanceof DOMException && err.name === "AbortError") { trace.log(`Romanize: request timed out | ${url}`); } else { trace.log(`Romanize: request error | ${url} | ${err}`); } } } return null; }