import { S } from './state.js'; import { md5 } from './utils.js'; // ─── SUBSONIC ─── export function apiUrl(method, extra='') { const salt = Math.random().toString(36).slice(2,10); const token = md5(S.cfg.pass + salt); return `${S.cfg.url}/rest/${method}?u=${encodeURIComponent(S.cfg.user)}&t=${token}&s=${salt}&v=1.16.1&c=NovaSonic&f=json${extra}`; } export async function api(method, extra='') { const res = await fetch(apiUrl(method, extra)); if(!res.ok) throw new Error(`HTTP ${res.status}`); const data = await res.json(); const r = data['subsonic-response']; if(r.status !== 'ok') throw new Error(r.error?.message || 'API Error'); return r; } export function streamUrl(id) { return apiUrl('stream', `&id=${id}&format=raw`); } export async function getLyricsBySongId(songId) { try { const r = await api('getLyricsBySongId', `&id=${songId}`); return r.lyricsList?.structuredLyrics ?? []; } catch { return []; } } export function downloadUrl(id) { return apiUrl('download', `&id=${id}`); } export function coverUrl(id, size=300) { return id ? apiUrl('getCoverArt', `&id=${id}&size=${size}`) : null; } // ─── NAVIDROME REST AUTH ─── export async function ndAuth() { if(S._ndToken) return S._ndToken; const res = await fetch(`${S.cfg.url}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username: S.cfg.user, password: S.cfg.pass }), }); if(!res.ok) throw new Error('Navidrome login fehlgeschlagen'); const d = await res.json(); S._ndToken = d.token; return S._ndToken; } // ─── ITUNES ─── const ITUNES = 'https://itunes.apple.com'; export async function itunesSearch(query, entity='musicTrack', limit=25) { try { const r = await fetch(`${ITUNES}/search?term=${encodeURIComponent(query)}&media=music&entity=${entity}&limit=${limit}&country=us&explicit=Yes`); const text = await r.text(); if (!text) return []; return JSON.parse(text).results || []; } catch { return []; } } export async function itunesLookupAlbum(collectionId) { const r = await fetch(`${ITUNES}/lookup?id=${collectionId}&entity=song&explicit=Yes`); const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'track'); } // Returns actual explicit tracks for an artist via lookup (search endpoint only returns cleaned versions) export async function itunesLookupArtistSongs(artistId, limit=15) { const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`); const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'track'); } export async function itunesLookupArtistAlbums(artistId, limit=200) { const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=album&limit=${limit}&country=us`); const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'collection' && x.collectionType === 'Album'); } export async function itunesTopCharts() { const urls = [ 'https://rss.applemarketingtools.com/api/v2/de/music/most-played/50/albums.json', 'https://rss.applemarketingtools.com/api/v2/us/music/most-played/50/albums.json', ]; for(const url of urls) { try { const r = await fetch(url, { signal: AbortSignal.timeout(6000) }); if(!r.ok) continue; const d = await r.json(); if(d.feed?.results?.length) return d.feed.results; } catch {} } try { const r = await fetch('https://itunes.apple.com/search?term=new+music&media=music&entity=album&limit=50&country=de'); const d = await r.json(); return (d.results||[]).map(a => ({ name: a.collectionName, artistName: a.artistName, artworkUrl: a.artworkUrl100?.replace('100x100','300x300'), artworkUrl100: a.artworkUrl100, })); } catch { return []; } } export async function itunesTopSongs() { const urls = [ 'https://rss.applemarketingtools.com/api/v2/de/music/most-played/50/songs.json', 'https://rss.applemarketingtools.com/api/v2/us/music/most-played/50/songs.json', ]; for(const url of urls) { try { const r = await fetch(url, { signal: AbortSignal.timeout(6000) }); if(!r.ok) continue; const d = await r.json(); if(d.feed?.results?.length) return d.feed.results; } catch {} } try { const r = await fetch('https://itunes.apple.com/search?term=top+hits&media=music&entity=musicTrack&limit=50&country=de'); const d = await r.json(); return (d.results||[]).map(t => ({ name: t.trackName, artistName: t.artistName, artworkUrl: t.artworkUrl100 })); } catch { return []; } }