const fetch = require('isomorphic-unfetch') function getStreaminfoUrl (siteurl) { return siteurl + '/streaminfo.json' // XXX: improve this logic } function getManifestUrl (siteurl) { return siteurl + '/radiomanifest.xml' // XXX: improve this logic } function getAttribute(el, attr, default_value) { if(el.hasAttribute(attr)) return el.getAttribute(attr); return default_value; } class Radio { constructor (sources, schedule, shows, feed) { this.streaming = new RadioStreaming(sources) this.schedule = schedule this.shows = shows this.feed = feed this.name = '' } getStreaming () { return this.streaming } setName (name) { this.name = name } getShowByName (showName) { } getSchedule () { } getShowAtTime () { } static fromDOM (xml) { const doc = xml.cloneNode(true) let res = doc.evaluate('/radio-manifest/streaming/source', doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null) const sources = [] for (let i = 0; i < res.snapshotLength; i++) { const src = res.snapshotItem(i) if (!src.hasAttribute('priority')) { src.setAttribute('priority', '0') } else if (parseInt(src.getAttribute('priority'), 10) < 0) { continue } sources.push(src) } res = doc.evaluate('/radio-manifest/schedule', doc) const scheduleEl = res.iterateNext() let schedule = null if (scheduleEl !== null) { schedule = scheduleEl.getAttribute('src') } res = xml.evaluate('/radio-manifest/shows', xml) const showsEl = res.iterateNext() let shows = null if (showsEl !== null) { shows = showsEl.getAttribute('src') } res = xml.evaluate('/radio-manifest/feed', xml) const feedEl = res.iterateNext() let feed = null if (feedEl !== null) { feed = feedEl.getAttribute('src') } const manifest = new Radio(sources, schedule, shows, feed) return manifest } } class RadioStreaming { constructor (sources) { this.sources = sources.sort( (a,b) => this.getPriority(a) < this.getPriority(a) ) } getOptions() { return this.sources.map(function (x) { return x.getAttribute('name') }) } // this is private getPriority(element) { return parseInt(getAttribute(element, 'priority', '1')) } // this is private getTopPrioritySources() { var topPriority = this.getPriority(this.sources[0]) return this.sources.filter( (src) => parseInt(src.getAttribute('priority'), 10) === topPriority ) } getSource(name) { if (name === undefined) { return this.getTopPrioritySources()[0] } const s = this.sources.find(function (x) { return x.getAttribute('name') === name }) if (s === undefined) return s return s.getAttribute('src') } async pickURLs() { var allSources = this.getTopPrioritySources() var allAudios = [] for(let src of allSources) { let url = src.getAttribute('src') let resp = await fetch(url) allAudios.unshift(... parseM3U(await resp.text())) } return allAudios } async pickURL() { var allAudios = await this.pickURLs() return allAudios[0] } } 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 = Radio.fromDOM(dom) resp = null try { resp = await fetch(getStreaminfoUrl(siteurl)) } catch (e) { true } if(resp !== null) { try { 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 SyntaxError) { true } else { console.error('Error', e) throw e } } } // XXX: in base alle options fai fetch anche di altra roba return manifest } function parseM3U (body) { return body.split('\n').filter((line) => { if (line.startsWith('#')) { return false } else { try { new URL(line); return true } catch { return false } } }) } module.exports = { get: get, objs: { Radio: Radio, RadioStreaming: RadioStreaming }, parsers: { M3U: parseM3U, radioManifest: Radio.fromDOM } }