jefflix-website/lib/navidrome.ts

48 lines
1.6 KiB
TypeScript

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<string, string> = {}) {
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<T = Record<string, unknown>>(
path: string,
params: Record<string, string> = {}
): Promise<T> {
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<string, string> = {}
): Promise<Response> {
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
}