import crypto from 'crypto' const NAVIDROME_URL = process.env.NAVIDROME_URL || 'https://music.jefflix.lol' const NAVIDROME_USER = process.env.NAVIDROME_USER || '' const NAVIDROME_PASS = process.env.NAVIDROME_PASS || '' function subsonicAuth() { const salt = crypto.randomBytes(6).toString('hex') const token = crypto.createHash('md5').update(NAVIDROME_PASS + salt).digest('hex') return { u: NAVIDROME_USER, t: token, s: salt, v: '1.16.1', c: 'jefflix', f: 'json' } } function buildUrl(path: string, params: Record = {}) { const auth = subsonicAuth() const url = new URL(`/rest/${path}`, NAVIDROME_URL) for (const [k, v] of Object.entries({ ...auth, ...params })) { url.searchParams.set(k, v) } return url.toString() } /** JSON response from Subsonic API (parsed) */ export async function navidromeGet>( path: string, params: Record = {} ): Promise { const url = buildUrl(path, params) const res = await fetch(url, { cache: 'no-store' }) if (!res.ok) throw new Error(`Navidrome ${path} returned ${res.status}`) const json = await res.json() const sub = json['subsonic-response'] if (sub?.status !== 'ok') { throw new Error(sub?.error?.message || `Navidrome error on ${path}`) } return sub as T } /** Raw binary response (for streaming audio, cover art) */ export async function navidromeFetch( path: string, params: Record = {} ): Promise { const url = buildUrl(path, params) const res = await fetch(url, { cache: 'no-store' }) if (!res.ok) throw new Error(`Navidrome ${path} returned ${res.status}`) return res }