import { Router } from 'express' import axios from 'axios' import crypto from 'crypto' const router = Router() let cachedToken: string | null = null let tokenExpiry = 0 let tokenPromise: Promise | null = null async function getToken(host: string, user: string, pass: string): Promise { if (cachedToken && Date.now() < tokenExpiry) return cachedToken // Deduplicate concurrent login attempts if (tokenPromise) return tokenPromise tokenPromise = axios.post(`${host}/auth/login`, { username: user, password: pass }) .then(res => { cachedToken = res.data.token tokenExpiry = Date.now() + 55 * 60 * 1000 tokenPromise = null return cachedToken! }) .catch(err => { tokenPromise = null throw err }) return tokenPromise } router.get('/status', async (_req, res) => { const host = process.env.NAVIDROME_HOST const user = process.env.NAVIDROME_USER const pass = process.env.NAVIDROME_PASSWORD if (!host || !user || !pass) return res.status(503).json({ error: 'Navidrome not configured' }) try { const jwtToken = await getToken(host, user, pass) const headers = { 'X-ND-Authorization': `Bearer ${jwtToken}` } // Use Subsonic API for now playing (standard endpoint) const salt = 'sycoDash' const md5Token = crypto.createHash('md5').update(pass + salt).digest('hex') const subParams = { u: user, t: md5Token, s: salt, v: '1.16.1', c: 'syco-dashboard', f: 'json' } const [artistRes, albumRes, songRes, nowPlayingRes] = await Promise.all([ axios.get(`${host}/api/artist`, { headers, params: { _start: 0, _end: 1 } }), axios.get(`${host}/api/album`, { headers, params: { _start: 0, _end: 1 } }), axios.get(`${host}/api/song`, { headers, params: { _start: 0, _end: 1 } }), axios.get(`${host}/rest/getNowPlaying.view`, { params: subParams }).catch(() => null), ]) const artistCount = Number(artistRes.headers['x-total-count'] ?? 0) const albumCount = Number(albumRes.headers['x-total-count'] ?? 0) const songCount = Number(songRes.headers['x-total-count'] ?? 0) // eslint-disable-next-line @typescript-eslint/no-explicit-any const nowPlayingEntries: any[] = nowPlayingRes?.data?.['subsonic-response']?.nowPlaying?.entry ?? [] const entries = Array.isArray(nowPlayingEntries) ? nowPlayingEntries : [nowPlayingEntries] const nowPlaying = entries.filter(Boolean).map((e: any) => ({ user: e.username ?? 'Unknown', title: e.title ?? '', artist: e.artist ?? '', album: e.album ?? '', })) res.json({ artistCount, albumCount, songCount, nowPlaying }) } catch (err: any) { res.status(502).json({ error: err.message ?? 'Navidrome error' }) } }) export default router