const fetch = require('isomorphic-unfetch') class RadioManifest { constructor (baseURL, options) { this.baseURL = baseURL this.options = options const radiomanifest = fetch(`${baseURL}/radiomanifest.xml`) return radiomanifest } getShowByName (showName) { } getStreaming () { } getSchedule () { } getShowAtTime () { } } function getStreaminfoUrl (siteurl) { return siteurl + '/streaminfo.json' // XXX: improve this logic } function getManifestUrl (siteurl) { return siteurl + '/radiomanifest.xml' // XXX: improve this logic } function parseRadioManifest (xml) { let res = xml.evaluate('/radio-manifest/streaming/source', xml) const sources = [] while (true) { const src = res.iterateNext() if (src === null) break if (!src.hasAttribute('priority')) { src.setAttribute('priority', '0') } else if (parseInt(src.getAttribute('priority'), 10) < 0) { continue } sources.push(src) } sources.sort(function cmp (a, b) { return parseInt(a.getAttribute('priority', 10)) < parseInt(b.getAttribute('priority', 10)) }) res = xml.evaluate('/radio-manifest/schedule', xml) const scheduleEl = res.iterateNext() let schedule = null if (scheduleEl !== null) { schedule = scheduleEl.getAttribute('url') } res = xml.evaluate('/radio-manifest/shows', xml) const showsEl = res.iterateNext() let shows = null if (showsEl !== null) { shows = showsEl.getAttribute('src') } const manifest = new Radio(sources, schedule, shows) return manifest } function Radio (sources, schedule, shows) { this.streaming = new RadioStreaming(sources) this.schedule = schedule this.shows = shows this.name = '' } Radio.prototype.getStreaming = function () { return this.streaming } Radio.prototype.setName = function (name) { this.name = name } function RadioStreaming (sources) { this.sources = sources } RadioStreaming.prototype.getOptions = function () { return this.sources.map(function (x) { return x.getAttribute('name') }) } RadioStreaming.prototype.getSource = function (name) { if (name === undefined) { } const s = this.sources.find(function (x) { return x.getAttribute('name') === name }) if (s === undefined) return s return s.getAttribute('src') } async function get (siteurl, options) { let resp = await fetch(getManifestUrl(siteurl)) let text = await resp.text() const parser = new DOMParser() const dom = parser.parseFromString(text, 'text/xml') const manifest = parseRadioManifest(dom) resp = null try { resp = await fetch(getStreaminfoUrl(siteurl)) text = await resp.text() const data = JSON.parse(text) const name = data['icy-name'] if (name !== undefined) { manifest.setName(name) } } catch (e) { if (e instanceof TypeError && e.message.startsWith('NetworkError')) { // the fetch has failed true } else if (e instanceof SyntaxError && e.message.startsWith('JSON.parse')) { true } else { console.error('Error', e) throw e } } // XXX: in base alle options fai fetch anche di altra roba return manifest } function parseM3U (body) { body.split('\n').filter((e) => { if (e.startsWith('#')) { return false } else { try { new URL(e); return true } catch { return false } } }) } module.exports = { get: get, objs: { Radio: Radio, RadioStreaming: RadioStreaming }, parsers: { M3U: parseM3U, radioManifest: parseRadioManifest } }