#!/usr/bin/env node import { readFile, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const DEFAULT_SOURCE_URL = 'https://data.radio.si/api/radiostations?857df78efd094abcb98c7bbb53303c3d'; const scriptDir = path.dirname(fileURLToPath(import.meta.url)); const repoRoot = path.resolve(scriptDir, '..'); const sourceUrlArg = process.argv[2]; const stationsPathArg = process.argv[3]; const sourceUrl = sourceUrlArg && !sourceUrlArg.startsWith('--') ? sourceUrlArg : DEFAULT_SOURCE_URL; const stationsPath = stationsPathArg && !stationsPathArg.startsWith('--') ? path.resolve(process.cwd(), stationsPathArg) : path.join(repoRoot, 'public', 'stations.json'); const [remoteStations, existingStations] = await Promise.all([ fetchJson(sourceUrl), readJsonFile(stationsPath).catch(() => []), ]); if (!Array.isArray(remoteStations)) { throw new Error('Expected the radio stations API to return an array.'); } const remoteStationsById = new Set(remoteStations.map((station) => station.id)); const existingStationsById = new Map( existingStations .filter((station) => station && typeof station === 'object' && typeof station.id === 'string') .map((station) => [station.id, station]), ); const updatedStations = remoteStations.map((remoteStation) => { const existingStation = existingStationsById.get(remoteStation.id); return mergeStation(existingStation, remoteStation); }).concat( existingStations .filter((station) => station && typeof station === 'object' && typeof station.id === 'string' && !remoteStationsById.has(station.id)) .map(normalizeStation), ); await writeFile(stationsPath, `${JSON.stringify(updatedStations, null, 2)}\n`, 'utf8'); console.log(`Updated ${updatedStations.length} stations in ${stationsPath}`); async function fetchJson(url) { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); } return response.json(); } async function readJsonFile(filePath) { const contents = await readFile(filePath, 'utf8'); return JSON.parse(contents); } function mergeStation(existingStation = {}, remoteStation) { const assets = { ...(existingStation.assets ?? {}) }; assets.logo = remoteStation.logo; if (remoteStation.poster) { assets.poster = remoteStation.poster; } else { delete assets.poster; } const streams = { ...(existingStation.streams ?? {}) }; streams.audio = remoteStation.liveAudio; if (remoteStation.liveVideo) { streams.video = remoteStation.liveVideo; } else { delete streams.video; } const metadata = { ...(existingStation.metadata ?? {}) }; if (remoteStation.lastSongs) { metadata.lastSongs = remoteStation.lastSongs; } return { id: remoteStation.id, name: remoteStation.title ?? existingStation.name ?? remoteStation.id, slogan: remoteStation.slogan ?? existingStation.slogan ?? '', category: existingStation.category ?? '', country: existingStation.country ?? 'SI', language: existingStation.language ?? 'sl', region: existingStation.region ?? 'National', tags: Array.isArray(existingStation.tags) ? existingStation.tags : [], website: remoteStation.www ?? existingStation.website ?? null, enabled: typeof remoteStation.enabled === 'boolean' ? remoteStation.enabled : Boolean(existingStation.enabled), assets, streams, metadata, }; } function normalizeStation(station) { const assets = { ...(station.assets ?? {}) }; const streams = { ...(station.streams ?? {}) }; const metadata = { ...(station.metadata ?? {}) }; if (!assets.logo) { assets.logo = station.logo ?? ''; } if (station.poster && !assets.poster) { assets.poster = station.poster; } if (!streams.audio) { streams.audio = station.streams?.audio ?? station.url ?? station.liveAudio ?? station.streamUrl ?? ''; } if (!streams.video && station.liveVideo) { streams.video = station.liveVideo; } return { id: station.id, name: station.name ?? station.title ?? station.id, slogan: station.slogan ?? '', category: station.category ?? '', country: station.country ?? '', language: station.language ?? '', region: station.region ?? '', tags: Array.isArray(station.tags) ? station.tags : [], website: station.website ?? station.homepage ?? station.www ?? null, enabled: typeof station.enabled === 'boolean' ? station.enabled : true, assets, streams, metadata, }; }