142 lines
4.5 KiB
JavaScript
142 lines
4.5 KiB
JavaScript
#!/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,
|
|
};
|
|
}
|