Browse Source

apply prettier

boyska 7 months ago
parent
commit
4e19b6304e

+ 1 - 0
.prettierignore

@@ -0,0 +1 @@
+libs/

+ 2 - 3
README.md

@@ -13,6 +13,5 @@ npm run test
 
 ## Supported spec
 
- - [radiomanifest](https://radiomanifest.degenerazione.xyz/)
- - [stream-meta v2](https://www.stream-meta.info/version_2_files.html): name of the radio is extracted from `/streaminfo.json`
-
+- [radiomanifest](https://radiomanifest.degenerazione.xyz/)
+- [stream-meta v2](https://www.stream-meta.info/version_2_files.html): name of the radio is extracted from `/streaminfo.json`

+ 85 - 85
calendar.js

@@ -1,18 +1,18 @@
-import ICAL from 'ical.js'
-import shows from './shows'
+import ICAL from "ical.js";
+import shows from "./shows";
 
 function max(a, b) {
-  if (a<b) return b
-  return a
+  if (a < b) return b;
+  return a;
 }
 
 /**
  * Represent a schedule (ie: a .ics file)
  */
 class RadioSchedule {
-  constructor (calendar, radio) {
-    this.calendar = calendar  // ICAL.Calendar
-    this.radio = radio  // radiomanifest.Radio
+  constructor(calendar, radio) {
+    this.calendar = calendar; // ICAL.Calendar
+    this.radio = radio; // radiomanifest.Radio
   }
 
   /**
@@ -21,136 +21,138 @@ class RadioSchedule {
    * @returns {Array<external:ICAL~Component>}
    * */
   getEvents() {
-    return this.calendar.getAllSubcomponents('vevent');
-
+    return this.calendar.getAllSubcomponents("vevent");
   }
 
   /**
    * @returns {RadioShow} tries to get a matching show, or create a new one
    */
   getShowByEvent(ev) {
-    if (ev === null) return null
+    if (ev === null) return null;
     if (this.radio !== undefined) {
-      const showid = RadioSchedule.veventGetShowID(ev)
-      var show = this.radio.getShowByName(showid)
+      const showid = RadioSchedule.veventGetShowID(ev);
+      var show = this.radio.getShowByName(showid);
       if (show === null || show === undefined) {
-        return new shows.RadioShow(RadioSchedule.veventGetSummary(ev))
+        return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
       }
       return show;
     }
-    return new shows.RadioShow(RadioSchedule.veventGetSummary(ev))
+    return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
   }
 
   /**
    * @returns {external:ICAL~Component} if nothing is going on right now, `null` is returned
    */
   getNowEvent(now) {
-    var ev_now = this.getEvents().filter(function(vevent) {
-      const ev = new ICAL.Event(vevent)
-      return isNow(ev, now)
-    })
-    ev_now.sort((e1, e2) => { return this.veventGetPriority(e1) - this.veventGetPriority(e2) })
-    if(ev_now.length === 0)
-      return null
-    return ev_now[0]
+    var ev_now = this.getEvents().filter(function (vevent) {
+      const ev = new ICAL.Event(vevent);
+      return isNow(ev, now);
+    });
+    ev_now.sort((e1, e2) => {
+      return this.veventGetPriority(e1) - this.veventGetPriority(e2);
+    });
+    if (ev_now.length === 0) return null;
+    return ev_now[0];
   }
 
   /**
    * @returns {RadioShow} if nothing is going on right now, `null` is returned
    */
   getNowShow(now) {
-    const ev = this.getNowEvent(now)
-    return this.getShowByEvent(ev)
+    const ev = this.getNowEvent(now);
+    return this.getShowByEvent(ev);
   }
 
   /**
    * @returns {NextEvent} if there is none, `null` is returned
    */
   getNextEvent(now) {
-    var nowEvent = this.getNowEvent(now)
-    let future_events = this.getEvents().filter((e) => { return e != nowEvent })
+    var nowEvent = this.getNowEvent(now);
+    let future_events = this.getEvents()
+      .filter((e) => {
+        return e != nowEvent;
+      })
       .map((e) => {
-        const vEvent = new ICAL.Event(e)
-        return {event: e, time: getNext(vEvent, now)}
+        const vEvent = new ICAL.Event(e);
+        return { event: e, time: getNext(vEvent, now) };
       })
-      .filter((x) => { return x.time !== null && x.time !== undefined })
+      .filter((x) => {
+        return x.time !== null && x.time !== undefined;
+      });
     // since ".sort()" is guaranteed to be stable, we can sort by priority, then by date, so that two events
     // starting at the same time will be sorted observing priority
-    future_events.sort((x1, x2) => this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event))
-    future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime())
+    future_events.sort(
+      (x1, x2) =>
+        this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event),
+    );
+    future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime());
 
     if (future_events.length === 0) {
-      return null
+      return null;
     }
-    return future_events[0]
+    return future_events[0];
   }
 
-
   /**
    * @returns {NextShow} if there is no next show, null will be returned
    */
   getNextShow(now) {
-    const next = this.getNextEvent(now)
-    if (next === null) return null
-    const ev = next.event
-    const show = this.getShowByEvent(ev)
-    return {show: show, time: next.time}
+    const next = this.getNextEvent(now);
+    if (next === null) return null;
+    const ev = next.event;
+    const show = this.getShowByEvent(ev);
+    return { show: show, time: next.time };
   }
   static veventGetSummary(vevent) {
-      return vevent.getFirstProperty('summary').getFirstValue()
+    return vevent.getFirstProperty("summary").getFirstValue();
   }
   static veventGetShowID(vevent) {
-      return RadioSchedule.veventGetSummary(vevent)  // XXX: X-Show-ID
+    return RadioSchedule.veventGetSummary(vevent); // XXX: X-Show-ID
   }
 
   /**
    * @return {integer} a normalized version of priority, easier to compare
    */
   veventGetPriority(ev) {
-    const prop = ev.getFirstProperty('priority')
+    const prop = ev.getFirstProperty("priority");
     let prio;
     if (prop === null) {
-      prio = null
+      prio = null;
     } else {
-      prio = prop.getFirstValue()
+      prio = prop.getFirstValue();
     }
     if (prio === null || prio === 0) {
-      prio = 100
+      prio = 100;
     }
-    return prio
+    return prio;
   }
-
-
 }
 
-
 function isNow(vEvent, now) {
-    if (now === undefined) {
-          now = ICAL.Time.now()
-        }
-    if (vEvent.isRecurring()) {
-          return isNowRecurring(vEvent, now)
-        }
-    return (now < vEvent.endDate) && (now > vEvent.startDate);
+  if (now === undefined) {
+    now = ICAL.Time.now();
+  }
+  if (vEvent.isRecurring()) {
+    return isNowRecurring(vEvent, now);
+  }
+  return now < vEvent.endDate && now > vEvent.startDate;
 }
 
-
 function isNowRecurring(vEvent, now) {
-  var expand = vEvent.iterator(vEvent.startDate)
+  var expand = vEvent.iterator(vEvent.startDate);
   var next, next_end;
 
   while ((next = expand.next())) {
-    next_end = next.clone()
-    next_end.addDuration(vEvent.duration)
+    next_end = next.clone();
+    next_end.addDuration(vEvent.duration);
 
     if (next_end > now) {
       break;
     }
   }
-  return (now < next_end && now > next);
+  return now < next_end && now > next;
 }
 
-
 /*
  * @private
  * @param {external:ICAL~Component} vEvent a _recurring_ vEvent
@@ -159,16 +161,16 @@ function isNowRecurring(vEvent, now) {
  */
 function getNext(vEvent, now) {
   if (now === undefined) {
-    now = ICAL.Time.now()
+    now = ICAL.Time.now();
   }
   if (vEvent.isRecurring()) {
-    return getNextRecurring(vEvent, now)
+    return getNextRecurring(vEvent, now);
   }
   if (vEvent.endDate > now) {
-    const val = max(now, vEvent.startDate)
-    return val
+    const val = max(now, vEvent.startDate);
+    return val;
   }
-  return null
+  return null;
 }
 
 /*
@@ -178,37 +180,36 @@ function getNext(vEvent, now) {
  * @return {external:ICAL~Time} first future occurrence of this event
  */
 function getNextRecurring(vEvent, now) {
-  var expand = vEvent.iterator(vEvent.startDate)
+  var expand = vEvent.iterator(vEvent.startDate);
   var next, next_end;
 
   while ((next = expand.next())) {
-    const start = next.clone()
-    next_end = start.clone()
-    next_end.addDuration(vEvent.duration)
+    const start = next.clone();
+    next_end = start.clone();
+    next_end.addDuration(vEvent.duration);
     if (next_end <= now) {
-      continue
+      continue;
     }
-    return max(start, now)
+    return max(start, now);
   }
-  return null
+  return null;
 }
 
-
 async function get(manifest) {
   if (manifest.scheduleURL) {
-    let resp = null
+    let resp = null;
     try {
-      resp = await fetch(manifest.scheduleURL)
+      resp = await fetch(manifest.scheduleURL);
     } catch (e) {
-      true
+      true;
     }
     if (resp !== null) {
       try {
-        const text = await resp.text()
-        return parse(text)
+        const text = await resp.text();
+        return parse(text);
       } catch (e) {
-        console.error('Error while parsing schedule', e)
-        throw e
+        console.error("Error while parsing schedule", e);
+        throw e;
       }
     }
   }
@@ -224,15 +225,14 @@ function parse(text) {
   var jcalData = ICAL.parse(text);
   var vcalendar = new ICAL.Component(jcalData);
 
-  return new RadioSchedule(vcalendar)
+  return new RadioSchedule(vcalendar);
 }
 
-
 export default {
   get: get,
   parse: parse,
   RadioSchedule: RadioSchedule,
-}
+};
 
 /**
  * @typedef {Object} NextShow

+ 3 - 2
doc/home.md

@@ -2,5 +2,6 @@ Radiomanifest is a specification to make webradios express their offer in a stan
 library aiming at making the development of expressive webapps a breeze.
 
 You are strongly invited to start from:
- - {@tutorial quickstart} tutorial
- - {@link get} this is the first function you'll ever call, in 99% of user scenarios
+
+- {@tutorial quickstart} tutorial
+- {@link get} this is the first function you'll ever call, in 99% of user scenarios

+ 14 - 13
doc/tutorials/quickstart.md

@@ -1,9 +1,9 @@
 Using radiomanifest is pretty simple. In an ideal usecase, you can easily do this:
 
 ```javascript
-const radiomanifest = require('radiomanifest')
-const radio = radiomanifest.get('http://myradio.com/')
-console.log(radio.getName())
+const radiomanifest = require("radiomanifest");
+const radio = radiomanifest.get("http://myradio.com/");
+console.log(radio.getName());
 ```
 
 Now we have `radio`, a {@link Radio} object, which can be seen as the "center" of our data. From here, we can
@@ -11,13 +11,13 @@ get more data.
 
 ## Streaming
 
-The first thing we could want to do is just to *play* the radio. Let's use the {@link RadioStreaming#pickURLs
+The first thing we could want to do is just to _play_ the radio. Let's use the {@link RadioStreaming#pickURLs
 RadioStreaming.pickURLs} method then
 
 ```javascript
-var streaming = radio.getStreaming()
-var urls = await streaming.pickURLs()
-console.log(urls)
+var streaming = radio.getStreaming();
+var urls = await streaming.pickURLs();
+console.log(urls);
 ```
 
 and here we go! This is a list of URLs that the radio is indicating to us as their preferred ones. Why not a
@@ -30,17 +30,18 @@ Our {@link Radio} keeps track of those, and for each show we can have useful met
 for more details.
 
 ```javascript
-var shows = radio.getShows()
-console.log(shows.map(s => s.getName()))
+var shows = radio.getShows();
+console.log(shows.map((s) => s.getName()));
 ```
+
 ## Schedule
 
 ```javascript
-const show = radio.getShowAtTime()
+const show = radio.getShowAtTime();
 if (show !== null) {
-  console.log(show.getName())
+  console.log(show.getName());
 } else {
-  console.log("Nothing special going on right now, sorry")
+  console.log("Nothing special going on right now, sorry");
 }
 ```
 
@@ -51,4 +52,4 @@ now and {@link RadioSchedule#getNextShow what will go on next}
 ## Conclusions
 
 I hope this tutorial helped you get your feet wet. Hopefully, using radiomanifest you'd be able to create
-great webapps that work on *any* webradio (well, any webradio that supports radiomanifest, at least).
+great webapps that work on _any_ webradio (well, any webradio that supports radiomanifest, at least).

+ 12 - 16
karma.config.js

@@ -1,34 +1,30 @@
 module.exports = function (config) {
   config.set({
-    frameworks: ['mocha'],
+    frameworks: ["mocha"],
     // plugins: ['karma-webpack', 'karma-mocha', 'karma-chai-as-promised'],
     webpack: {
       // karma watches the test entry points
       // Do NOT specify the entry option
       // webpack watches dependencies
-
       // webpack configuration
     },
     preprocessors: {
-      'test/**/*.js': ['webpack'],
-      'radiomanifest.js': ['webpack']
+      "test/**/*.js": ["webpack"],
+      "radiomanifest.js": ["webpack"],
     },
-    files: [
-      'radiomanifest.js',
-      'test/**/*.js'
-    ],
-    reporters: ['progress'],
+    files: ["radiomanifest.js", "test/**/*.js"],
+    reporters: ["progress"],
     port: 9876, // karma web server port
     colors: true,
     logLevel: config.LOG_INFO,
-    browsers: ['ChromeHeadless', 'FirefoxHeadless'],
+    browsers: ["ChromeHeadless", "FirefoxHeadless"],
     autoWatch: false,
     concurrency: Infinity,
     customLaunchers: {
       FirefoxHeadless: {
-        base: 'Firefox',
-        flags: ['-headless']
-      }
-    }
-  })
-}
+        base: "Firefox",
+        flags: ["-headless"],
+      },
+    },
+  });
+};

+ 291 - 287
radiomanifest.js

@@ -1,174 +1,174 @@
-import fetch from 'isomorphic-unfetch'
-import shows from './shows.js'
-import calendar from './calendar.js'
+import fetch from "isomorphic-unfetch";
+import shows from "./shows.js";
+import calendar from "./calendar.js";
 
 function getStreaminfoUrl(siteurl) {
-    return siteurl + "/streaminfo.json" // XXX: improve this logic
+  return siteurl + "/streaminfo.json"; // XXX: improve this logic
 }
 function getManifestUrl(siteurl) {
-    return siteurl + "/radiomanifest.xml" // XXX: improve this logic
+  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
+  if (el.hasAttribute(attr)) return el.getAttribute(attr);
+  return default_value;
 }
 
 /**
  * Represents everything we know about a radio. This includes, but is not limited to, radiomanifest.xml.
  */
 class Radio {
-    /**
-     * @param {Array}  [sources] optional
-     * @param {string} [scheduleURL]
-     * @param {string} [showsURL]
-     * @param {string} [feed]
-     */
-    constructor(sources, scheduleURL, showsURL, feed) {
-        this.streaming = new RadioStreaming(sources)
-        this.scheduleURL = scheduleURL
-        this.showsURL = showsURL
-        this.feed = feed
-        this.name = ""
-        this.description = ""
-        this.logo = null
-        this.shows = []
-        this.schedule = null
+  /**
+   * @param {Array}  [sources] optional
+   * @param {string} [scheduleURL]
+   * @param {string} [showsURL]
+   * @param {string} [feed]
+   */
+  constructor(sources, scheduleURL, showsURL, feed) {
+    this.streaming = new RadioStreaming(sources);
+    this.scheduleURL = scheduleURL;
+    this.showsURL = showsURL;
+    this.feed = feed;
+    this.name = "";
+    this.description = "";
+    this.logo = null;
+    this.shows = [];
+    this.schedule = null;
+  }
+
+  /**
+   * @returns {RadioStreaming}
+   */
+  getStreaming() {
+    return this.streaming;
+  }
+
+  setName(name) {
+    this.name = name;
+  }
+
+  /**
+   * The radio name, as inferred by stream-meta
+   *
+   * @returns {string}
+   */
+  getName() {
+    return this.name;
+  }
+
+  setDescription(desc) {
+    this.description = desc;
+  }
+
+  /**
+   * The description of the radio, as inferred by stream-meta
+   *
+   * @returns {string}
+   */
+  getDescription() {
+    return this.description;
+  }
+
+  setLogo(logo) {
+    this.logo = logo;
+  }
+
+  /**
+   * @returns {string} the URL of the logo, or `null` if not found
+   */
+  getLogo() {
+    return this.logo;
+  }
+
+  /**
+   *
+   * @returns {Array<RadioShow>}
+   */
+  getShows() {
+    return this.shows;
+  }
+
+  /**
+   * @returns {RadioShow} The lookup is exact and case-sensitive. If no such show can be found, `null`
+   * is returned.
+   */
+  getShowByName(showName) {
+    if (this.shows === undefined) return null;
+    return this.shows.find((s) => s.name === showName);
+  }
+
+  /**
+   * @returns {RadioSchedule} If no schedule is present, `null` is returned.
+   */
+  getSchedule() {
+    return this.schedule;
+  }
+
+  /**
+   * Find if a show is running at the given moment. If there's none, `null` is returned.
+   * If possible, a complete {@link RadioShow} including full informations (from shows.xml) is returned.
+   * If, instead, we know from the `schedule` that there must be a show, but have no additional detail, a
+   * {@link RadioShow} object will be created on the fly.
+   *
+   * @param {ICAL.Time} [now] If omitted, the current time is used.
+   * @returns {RadioShow} If we don't know what's going on at the given moment, `null` is returned.
+   */
+  getShowAtTime(now) {
+    if (this.schedule === undefined || this.schedule === null) return null;
+    return this.getSchedule().getNowShow(now);
+  }
+
+  /**
+   * This static method can create a Radio object from a valid radiomanifest.xml file
+   *
+   * @param xml An already parsed xml block
+   * @returns {Radio}
+   */
+  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);
     }
 
-    /**
-     * @returns {RadioStreaming}
-     */
-    getStreaming() {
-        return this.streaming
+    res = doc.evaluate("/radio-manifest/schedule", doc);
+    const scheduleEl = res.iterateNext();
+    let scheduleURL = null;
+    if (scheduleEl !== null) {
+      scheduleURL = scheduleEl.getAttribute("src");
     }
 
-    setName(name) {
-        this.name = name
+    res = xml.evaluate("/radio-manifest/shows", xml);
+    const showsEl = res.iterateNext();
+    let showsURL = null;
+    if (showsEl !== null) {
+      showsURL = showsEl.getAttribute("src");
     }
 
-    /**
-     * The radio name, as inferred by stream-meta
-     *
-     * @returns {string}
-     */
-    getName() {
-        return this.name
+    res = xml.evaluate("/radio-manifest/feed", xml);
+    const feedEl = res.iterateNext();
+    let feed = null;
+    if (feedEl !== null) {
+      feed = feedEl.getAttribute("src");
     }
 
-    setDescription(desc) {
-        this.description = desc
-    }
-
-    /**
-     * The description of the radio, as inferred by stream-meta
-     *
-     * @returns {string}
-     */
-    getDescription() {
-        return this.description
-    }
-
-    setLogo(logo) {
-        this.logo = logo
-    }
-
-    /**
-     * @returns {string} the URL of the logo, or `null` if not found
-     */
-    getLogo() {
-        return this.logo
-    }
-
-    /**
-     *
-     * @returns {Array<RadioShow>}
-     */
-    getShows() {
-        return this.shows
-    }
-
-    /**
-     * @returns {RadioShow} The lookup is exact and case-sensitive. If no such show can be found, `null`
-     * is returned.
-     */
-    getShowByName(showName) {
-        if (this.shows === undefined) return null
-        return this.shows.find(s => s.name === showName)
-    }
-
-    /**
-     * @returns {RadioSchedule} If no schedule is present, `null` is returned.
-     */
-    getSchedule() {
-        return this.schedule
-    }
-
-    /**
-     * Find if a show is running at the given moment. If there's none, `null` is returned.
-     * If possible, a complete {@link RadioShow} including full informations (from shows.xml) is returned.
-     * If, instead, we know from the `schedule` that there must be a show, but have no additional detail, a
-     * {@link RadioShow} object will be created on the fly.
-     *
-     * @param {ICAL.Time} [now] If omitted, the current time is used.
-     * @returns {RadioShow} If we don't know what's going on at the given moment, `null` is returned.
-     */
-    getShowAtTime(now) {
-        if (this.schedule === undefined || this.schedule === null) return null
-        return this.getSchedule().getNowShow(now)
-    }
-
-    /**
-     * This static method can create a Radio object from a valid radiomanifest.xml file
-     *
-     * @param xml An already parsed xml block
-     * @returns {Radio}
-     */
-    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 scheduleURL = null
-        if (scheduleEl !== null) {
-            scheduleURL = scheduleEl.getAttribute("src")
-        }
-
-        res = xml.evaluate("/radio-manifest/shows", xml)
-        const showsEl = res.iterateNext()
-        let showsURL = null
-        if (showsEl !== null) {
-            showsURL = 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, scheduleURL, showsURL, feed)
-        return manifest
-    }
+    const manifest = new Radio(sources, scheduleURL, showsURL, feed);
+    return manifest;
+  }
 }
 
 /**
@@ -176,71 +176,75 @@ class Radio {
  * This has probably been derived from the <streaming> block in radiomanifest.xml
  */
 class RadioStreaming {
-    constructor(sources) {
-        this.sources = sources.sort((a, b) => this.getPriority(a) < this.getPriority(a))
-    }
-
-    /**
-     * Get the list of possible options that are provided to the user
-     * @returns {Array<string>}
-     */
-    getOptions() {
-        return this.sources.map(function (x) {
-            return x.getAttribute("name")
-        })
-    }
-
-    /**
-     * @private
-     */
-    getPriority(element) {
-        return parseInt(getAttribute(element, "priority", "1"))
-    }
-    /**
-     * @private
-     */
-    getTopPrioritySources() {
-        var topPriority = this.getPriority(this.sources[0])
-        return this.sources.filter(src => parseInt(src.getAttribute("priority"), 10) === topPriority)
-    }
-    /**
-     * @return {string} url of the source. Note that this is much probably a playlist, in M3U format
-     */
-    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")
-    }
-
-    /**
-     * This is your go-to function whenever you need a list of URLs to play.
-     * They will be picked honoring priorities, and expanding the playlist source
-     * @return {Array<string>}
-     */
-    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
+  constructor(sources) {
+    this.sources = sources.sort(
+      (a, b) => this.getPriority(a) < this.getPriority(a),
+    );
+  }
+
+  /**
+   * Get the list of possible options that are provided to the user
+   * @returns {Array<string>}
+   */
+  getOptions() {
+    return this.sources.map(function (x) {
+      return x.getAttribute("name");
+    });
+  }
+
+  /**
+   * @private
+   */
+  getPriority(element) {
+    return parseInt(getAttribute(element, "priority", "1"));
+  }
+  /**
+   * @private
+   */
+  getTopPrioritySources() {
+    var topPriority = this.getPriority(this.sources[0]);
+    return this.sources.filter(
+      (src) => parseInt(src.getAttribute("priority"), 10) === topPriority,
+    );
+  }
+  /**
+   * @return {string} url of the source. Note that this is much probably a playlist, in M3U format
+   */
+  getSource(name) {
+    if (name === undefined) {
+      return this.getTopPrioritySources()[0];
     }
-
-    /**
-     * Just like {@link RadioStreaming#pickURLs}, but get a single URL
-     * @return {string}
-     */
-    async pickURL() {
-        var allAudios = await this.pickURLs()
-        return allAudios[0]
+    const s = this.sources.find(function (x) {
+      return x.getAttribute("name") === name;
+    });
+    if (s === undefined) return s;
+    return s.getAttribute("src");
+  }
+
+  /**
+   * This is your go-to function whenever you need a list of URLs to play.
+   * They will be picked honoring priorities, and expanding the playlist source
+   * @return {Array<string>}
+   */
+  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;
+  }
+
+  /**
+   * Just like {@link RadioStreaming#pickURLs}, but get a single URL
+   * @return {string}
+   */
+  async pickURL() {
+    var allAudios = await this.pickURLs();
+    return allAudios[0];
+  }
 }
 
 /**
@@ -251,87 +255,87 @@ class RadioStreaming {
  * @return {Radio}
  */
 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)
-
-    try {
-        manifest.shows = await shows.get(manifest)
-    } catch (e) {
-        console.error("Error while fetching shows file", e)
-    }
-
-    try {
-        manifest.schedule = await calendar.get(manifest)
-        if (manifest.schedule !== undefined) manifest.schedule.radio = manifest
-    } catch (e) {
-        console.error("Error while fetching shows file", e)
-    }
-
-    resp = null
+  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);
+
+  try {
+    manifest.shows = await shows.get(manifest);
+  } catch (e) {
+    console.error("Error while fetching shows file", e);
+  }
+
+  try {
+    manifest.schedule = await calendar.get(manifest);
+    if (manifest.schedule !== undefined) manifest.schedule.radio = manifest;
+  } catch (e) {
+    console.error("Error while fetching shows file", e);
+  }
+
+  resp = null;
+  try {
+    resp = await fetch(getStreaminfoUrl(siteurl));
+  } catch (e) {
+    true;
+  }
+  if (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);
+      }
+      const desc = data["icy-description"];
+      if (desc !== undefined) {
+        manifest.setDescription(desc);
+      }
+
+      const logo = data["icy-logo"];
+      if (desc !== undefined) {
+        manifest.setLogo(logo);
+      }
     } 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)
-            }
-            const desc = data["icy-description"]
-            if (desc !== undefined) {
-                manifest.setDescription(desc)
-            }
-
-            const logo = data["icy-logo"]
-            if (desc !== undefined) {
-                manifest.setLogo(logo)
-            }
-        } catch (e) {
-            if (e instanceof SyntaxError) {
-                true
-            } else {
-                console.error("Error", e)
-                throw e
-            }
-        }
+      if (e instanceof SyntaxError) {
+        true;
+      } else {
+        console.error("Error", e);
+        throw e;
+      }
     }
+  }
 
-    return manifest
+  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
-            }
-        }
-    })
+  return body.split("\n").filter((line) => {
+    if (line.startsWith("#")) {
+      return false;
+    } else {
+      try {
+        new URL(line);
+        return true;
+      } catch {
+        return false;
+      }
+    }
+  });
 }
 
 export default {
-    get: get,
-    objs: {
-        Radio: Radio,
-        RadioStreaming: RadioStreaming
-    },
-    parsers: {
-        M3U: parseM3U,
-        radioManifest: Radio.fromDOM,
-        shows: shows.parse
-    }
-}
+  get: get,
+  objs: {
+    Radio: Radio,
+    RadioStreaming: RadioStreaming,
+  },
+  parsers: {
+    M3U: parseM3U,
+    radioManifest: Radio.fromDOM,
+    shows: shows.parse,
+  },
+};

+ 88 - 44
shows.js

@@ -3,30 +3,28 @@
  * schedule.
  */
 
-
 class RadioShow {
   constructor(name, description, website, feed, schedule, radio_calendar) {
-    this.name = name
-    this.description = description
-    this.website = website
-    this.feed = feed
-    this.schedule = schedule
-    this.radio_calendar = radio_calendar
+    this.name = name;
+    this.description = description;
+    this.website = website;
+    this.feed = feed;
+    this.schedule = schedule;
+    this.radio_calendar = radio_calendar;
   }
 
   getName() {
-    return this.name
+    return this.name;
   }
   getWebsite() {
-    return this.website
+    return this.website;
   }
   getFeed() {
-    return this.feed
+    return this.feed;
   }
   getSchedule() {
-    return this.schedule
+    return this.schedule;
   }
-
 }
 
 /**
@@ -34,51 +32,98 @@ class RadioShow {
  * @return {Array<RadioShow>}
  */
 function parseRadioShows(xml) {
-  const doc = xml.cloneNode(true)
-  const bookmarks = doc.evaluate('//bookmark', doc, showsNamespaceResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
-  const shows = []
+  const doc = xml.cloneNode(true);
+  const bookmarks = doc.evaluate(
+    "//bookmark",
+    doc,
+    showsNamespaceResolver,
+    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
+    null,
+  );
+  const shows = [];
   for (let i = 0; i < bookmarks.snapshotLength; i++) {
-    const bm = bookmarks.snapshotItem(i)
+    const bm = bookmarks.snapshotItem(i);
 
-    let name = doc.evaluate('./info/metadata/show:name', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
-    if (name === '') {
-      name = doc.evaluate('./title', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
+    let name = doc.evaluate(
+      "./info/metadata/show:name",
+      bm,
+      showsNamespaceResolver,
+      XPathResult.STRING_TYPE,
+    ).stringValue;
+    if (name === "") {
+      name = doc.evaluate(
+        "./title",
+        bm,
+        showsNamespaceResolver,
+        XPathResult.STRING_TYPE,
+      ).stringValue;
     }
-    let website = doc.evaluate('./info/metadata/show:website', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
-    if (website === '') {
-      website = bm.getAttribute('href')
+    let website = doc.evaluate(
+      "./info/metadata/show:website",
+      bm,
+      showsNamespaceResolver,
+      XPathResult.STRING_TYPE,
+    ).stringValue;
+    if (website === "") {
+      website = bm.getAttribute("href");
     }
-    const feed = doc.evaluate('./info/metadata/show:feed', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
-    const schedule = doc.evaluate('./info/metadata/show:schedule', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
-    let description = doc.evaluate('./info/metadata/show:description', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
-    if (description === '') {
-      description = doc.evaluate('./desc', bm, showsNamespaceResolver, XPathResult.STRING_TYPE).stringValue
+    const feed = doc.evaluate(
+      "./info/metadata/show:feed",
+      bm,
+      showsNamespaceResolver,
+      XPathResult.STRING_TYPE,
+    ).stringValue;
+    const schedule = doc.evaluate(
+      "./info/metadata/show:schedule",
+      bm,
+      showsNamespaceResolver,
+      XPathResult.STRING_TYPE,
+    ).stringValue;
+    let description = doc.evaluate(
+      "./info/metadata/show:description",
+      bm,
+      showsNamespaceResolver,
+      XPathResult.STRING_TYPE,
+    ).stringValue;
+    if (description === "") {
+      description = doc.evaluate(
+        "./desc",
+        bm,
+        showsNamespaceResolver,
+        XPathResult.STRING_TYPE,
+      ).stringValue;
     }
 
-    const show = new RadioShow(name, description || null, website || null, feed || null, schedule || null)
-    shows.push(show)
+    const show = new RadioShow(
+      name,
+      description || null,
+      website || null,
+      feed || null,
+      schedule || null,
+    );
+    shows.push(show);
   }
 
-  return shows
+  return shows;
 }
 
 async function getShows(manifest) {
   if (manifest.showsURL) {
-    let resp = null
+    let resp = null;
     try {
-      resp = await fetch(manifest.showsURL)
+      resp = await fetch(manifest.showsURL);
     } catch (e) {
-      true
+      true;
     }
     if (resp !== null) {
       try {
-        const text = await resp.text()
-        const parser = new DOMParser()
-        const showsDom = parser.parseFromString(text, 'text/xml')
-        return parseRadioShows(showsDom)
+        const text = await resp.text();
+        const parser = new DOMParser();
+        const showsDom = parser.parseFromString(text, "text/xml");
+        return parseRadioShows(showsDom);
       } catch (e) {
-        console.error('Error while parsing shows file', e)
-        throw e
+        console.error("Error while parsing shows file", e);
+        throw e;
       }
     }
   }
@@ -86,14 +131,13 @@ async function getShows(manifest) {
 
 function showsNamespaceResolver(prefix) {
   const prefixes = {
-    show: 'https://radiomanifest.degenerazione.xyz/shows/',
-  }
-  return prefixes[prefix] || null
+    show: "https://radiomanifest.degenerazione.xyz/shows/",
+  };
+  return prefixes[prefix] || null;
 }
 
-
 export default {
   get: getShows,
   parse: parseRadioShows,
   RadioShow: RadioShow,
-}
+};

+ 15 - 15
test/404.test.js

@@ -1,17 +1,17 @@
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const expect = chai.expect
-const testName = 'idontexist'
-const url = 'https://example.org/radiomanifest/examples/' + testName + '/'
+const expect = chai.expect;
+const testName = "idontexist";
+const url = "https://example.org/radiomanifest/examples/" + testName + "/";
 
-describe('radiomanifest.js supports example ' + testName, () => {
-  describe('Get empty radiomanifest', () => {
-    it('should throw', () => {
-      const p = radiomanifest.get(url)
-      expect(p).to.be.rejected
-    })
-  })
-})
+describe("radiomanifest.js supports example " + testName, () => {
+  describe("Get empty radiomanifest", () => {
+    it("should throw", () => {
+      const p = radiomanifest.get(url);
+      expect(p).to.be.rejected;
+    });
+  });
+});

+ 24 - 21
test/example-empty.test.js

@@ -1,26 +1,29 @@
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const expect = chai.expect
-const tests = ['empty', 'empty-no-streaminfo', 'empty-invalid-streaminfo']
+const expect = chai.expect;
+const tests = ["empty", "empty-no-streaminfo", "empty-invalid-streaminfo"];
 for (const exampleName of tests) {
-  let url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
+  let url =
+    "https://radiomanifest.degenerazione.xyz/v0.2/examples/" +
+    exampleName +
+    "/";
 
-  describe('examples/' + exampleName, () => {
-    describe('Get radiomanifest', () => {
-      it('should return a Promise', () => {
-        const p = radiomanifest.get(url)
-        expect(p instanceof Promise).to.be.eql(true)
-      })
-    })
+  describe("examples/" + exampleName, () => {
+    describe("Get radiomanifest", () => {
+      it("should return a Promise", () => {
+        const p = radiomanifest.get(url);
+        expect(p instanceof Promise).to.be.eql(true);
+      });
+    });
 
-    describe('streaming', () => {
-      it('shoud return no streaming option', async () => {
-        const p = await radiomanifest.get(url)
-        expect(p.getStreaming().getOptions().length).to.be.equal(0)
-      })
-    })
-  })
+    describe("streaming", () => {
+      it("shoud return no streaming option", async () => {
+        const p = await radiomanifest.get(url);
+        expect(p.getStreaming().getOptions().length).to.be.equal(0);
+      });
+    });
+  });
 }

+ 88 - 91
test/example-full-ondarossa.test.js

@@ -1,107 +1,104 @@
-const ICAL = require('ical.js')
+const ICAL = require("ical.js");
 
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const exampleName = 'full-ondarossa'
-const expect = chai.expect
-const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
+const exampleName = "full-ondarossa";
+const expect = chai.expect;
+const url =
+  "https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
 
-const testShowName = 'Entropia Massima'
-const testWebsite = "http://www.ondarossa.info/trx/entropia-massima"
-const testFeed = 'http://www.ondarossa.info/podcast/by-trx-id/10497/podcast.xml'
+const testShowName = "Entropia Massima";
+const testWebsite = "http://www.ondarossa.info/trx/entropia-massima";
+const testFeed =
+  "http://www.ondarossa.info/podcast/by-trx-id/10497/podcast.xml";
 
-describe('examples/' + exampleName, () => {
-  describe('shows', () => {
-    it('shoud find many shows', async () => {
-      const rm = await radiomanifest.get(url)
-      assert.isAbove(rm.getShows().length, 1)
-    })
+describe("examples/" + exampleName, () => {
+  describe("shows", () => {
+    it("shoud find many shows", async () => {
+      const rm = await radiomanifest.get(url);
+      assert.isAbove(rm.getShows().length, 1);
+    });
     it('one of which is called "Entropia Massima"', async () => {
-      const rm = await radiomanifest.get(url)
-      const show = rm.getShowByName(testShowName)
-      assert.equal(show.getName(), testShowName)
-      assert.equal(show.getWebsite(), testWebsite)
-      assert.equal(show.getSchedule(), null)
-    })
-  })
+      const rm = await radiomanifest.get(url);
+      const show = rm.getShowByName(testShowName);
+      assert.equal(show.getName(), testShowName);
+      assert.equal(show.getWebsite(), testWebsite);
+      assert.equal(show.getSchedule(), null);
+    });
+  });
 
-  describe('schedule', () => {
-    it('should find many event', async () => {
-      const rm = await radiomanifest.get(url)
-      assert.isAbove(rm.getSchedule().getEvents().length, 1)
-    })
+  describe("schedule", () => {
+    it("should find many event", async () => {
+      const rm = await radiomanifest.get(url);
+      assert.isAbove(rm.getSchedule().getEvents().length, 1);
+    });
 
-    it('At 1AM, nothing is going on', async () => {
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
+    it("At 1AM, nothing is going on", async () => {
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
       const now = new ICAL.Time({
-            year: 2022,
-            month: 1,
-            day: 31,
-            hour: 1,
-            minute: 0,
-            second: 0,
-            isDate: false
-        });
-      const ev = rs.getNowEvent(now)
-      assert.equal(ev, null)
-    })
+        year: 2022,
+        month: 1,
+        day: 31,
+        hour: 1,
+        minute: 0,
+        second: 0,
+        isDate: false,
+      });
+      const ev = rs.getNowEvent(now);
+      assert.equal(ev, null);
+    });
 
     it('monday at 8PM, "Entropia Massima" is going on', async () => {
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
       const now = new ICAL.Time({
-            year: 2022,
-            month: 1,
-            day: 31,
-            hour: 20,
-            minute: 10,
-            second: 0,
-            isDate: false
-        },
-      );
-      const vevent = rs.getNowEvent(now)
-      assert.notEqual(vevent, null)
-      const show = rs.getNowShow(now)
-      assert.notEqual(show, null)
-      assert.equal(show.getName(), testShowName)
+        year: 2022,
+        month: 1,
+        day: 31,
+        hour: 20,
+        minute: 10,
+        second: 0,
+        isDate: false,
+      });
+      const vevent = rs.getNowEvent(now);
+      assert.notEqual(vevent, null);
+      const show = rs.getNowShow(now);
+      assert.notEqual(show, null);
+      assert.equal(show.getName(), testShowName);
 
-      assert.equal(show.getFeed(), testFeed)
-    })
+      assert.equal(show.getFeed(), testFeed);
+    });
 
     it('monday at 7PM, "Entropia Massima" is next', async () => {
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
       const now = new ICAL.Time({
-            year: 2022,
-            month: 1,
-            day: 31,
-            hour: 19,
-            minute: 10,
-            second: 0,
-            isDate: false
-        },
-      );
-      const vevent = rs.getNowEvent(now)
-      assert.notEqual(vevent, null)
-      const show = rs.getNowShow(now)
-      assert.notEqual(show, null)
-      assert.equal(show.getName(), 'Baraonda')
-
-      const next_event = rs.getNextEvent(now)
-      assert.notEqual(next_event, null)
-      assert.notEqual(next_event.event, null)
-      const next_show = rs.getNextShow(now)
-      assert.notEqual(next_show, null)
-      assert.notEqual(next_show.show, null)
-      assert.equal(next_show.show.getName(), testShowName)
-      assert.equal(next_show.show.getFeed(), testFeed)
-    })
-
-  })
-
-})
+        year: 2022,
+        month: 1,
+        day: 31,
+        hour: 19,
+        minute: 10,
+        second: 0,
+        isDate: false,
+      });
+      const vevent = rs.getNowEvent(now);
+      assert.notEqual(vevent, null);
+      const show = rs.getNowShow(now);
+      assert.notEqual(show, null);
+      assert.equal(show.getName(), "Baraonda");
 
+      const next_event = rs.getNextEvent(now);
+      assert.notEqual(next_event, null);
+      assert.notEqual(next_event.event, null);
+      const next_show = rs.getNextShow(now);
+      assert.notEqual(next_show, null);
+      assert.notEqual(next_show.show, null);
+      assert.equal(next_show.show.getName(), testShowName);
+      assert.equal(next_show.show.getFeed(), testFeed);
+    });
+  });
+});

+ 64 - 68
test/example-full-spore.test.js

@@ -1,79 +1,75 @@
-const ICAL = require('ical.js')
+const ICAL = require("ical.js");
 
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const exampleName = 'full-spore'
-const expect = chai.expect
-const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
+const exampleName = "full-spore";
+const expect = chai.expect;
+const url =
+  "https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
 
-const testShowName = 'scaricomerci'
-const testNextShowName = 'nastrone notte'
+const testShowName = "scaricomerci";
+const testNextShowName = "nastrone notte";
 
-describe('examples/' + exampleName, () => {
-  describe('schedule.getNow', () => {
-    it('observes priority correctly', async () => {
+describe("examples/" + exampleName, () => {
+  describe("schedule.getNow", () => {
+    it("observes priority correctly", async () => {
       // tuesday, half past midnight
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
-      const now = new ICAL.Time({
-            year: 2022,
-            month: 1,
-            day: 30,
-            hour: 2,
-            minute: 20,
-            second: 0,
-            isDate: false
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
+      const now = new ICAL.Time(
+        {
+          year: 2022,
+          month: 1,
+          day: 30,
+          hour: 2,
+          minute: 20,
+          second: 0,
+          isDate: false,
         },
-        ICAL.Timezone.utcTimezone
+        ICAL.Timezone.utcTimezone,
       );
-      const vevent = rs.getNowEvent(now)
-      assert.notEqual(vevent, null)
-      const show = rs.getNowShow(now)
-      assert.notEqual(show, null)
-      assert.equal(show.getName(), testShowName)
-    })
-  })
-  describe('schedule.getNext', () => {
-
-    it('getNext observes priority correctly', async () => {
+      const vevent = rs.getNowEvent(now);
+      assert.notEqual(vevent, null);
+      const show = rs.getNowShow(now);
+      assert.notEqual(show, null);
+      assert.equal(show.getName(), testShowName);
+    });
+  });
+  describe("schedule.getNext", () => {
+    it("getNext observes priority correctly", async () => {
       // tuesday, half past midnight
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
       const now = new ICAL.Time({
-            year: 2022,
-            month: 2,
-            day: 2,
-            hour: 2,
-            minute: 20,
-            second: 0,
-            isDate: false
-        },
-      );
-      const vevent = rs.getNowEvent(now)
-      assert.notEqual(vevent, null)
-      const show = rs.getNowShow(now)
-      assert.notEqual(show, null)
-      assert.equal(show.getName(), testShowName)
-
-      const next_event = rs.getNextEvent(now)
-      assert.notEqual(next_event, null)
-      assert.isObject(next_event.event)
-      assert.isObject(next_event.time)
-      const next_show = rs.getNextShow(now)
-      assert.isObject(next_show)
-      assert.isObject(next_show.show)
-      assert.equal(next_show.show.getName(), testNextShowName)
-
-      const time = next_event.time.toJSDate()
-      assert.equal(time.getHours(), 2)
-      assert.equal(time.getMinutes(), 58)
-    })
-
-  })
-
-})
+        year: 2022,
+        month: 2,
+        day: 2,
+        hour: 2,
+        minute: 20,
+        second: 0,
+        isDate: false,
+      });
+      const vevent = rs.getNowEvent(now);
+      assert.notEqual(vevent, null);
+      const show = rs.getNowShow(now);
+      assert.notEqual(show, null);
+      assert.equal(show.getName(), testShowName);
 
+      const next_event = rs.getNextEvent(now);
+      assert.notEqual(next_event, null);
+      assert.isObject(next_event.event);
+      assert.isObject(next_event.time);
+      const next_show = rs.getNextShow(now);
+      assert.isObject(next_show);
+      assert.isObject(next_show.show);
+      assert.equal(next_show.show.getName(), testNextShowName);
 
+      const time = next_event.time.toJSDate();
+      assert.equal(time.getHours(), 2);
+      assert.equal(time.getMinutes(), 58);
+    });
+  });
+});

+ 52 - 53
test/example-onlyics.test.js

@@ -1,60 +1,59 @@
-const ICAL = require('ical.js')
+const ICAL = require("ical.js");
 
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const exampleName = 'onlyics'
-const expect = chai.expect
-const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
+const exampleName = "onlyics";
+const expect = chai.expect;
+const url =
+  "https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
 
-describe('examples/' + exampleName, () => {
-  describe('schedule', () => {
-    it('should find one event', async () => {
-      const rm = await radiomanifest.get(url)
-      assert.equal(rm.getSchedule().getEvents().length, 1)
-    })
-    it('with a specific name', async () => {
-      const rm = await radiomanifest.get(url)
-      const ev = rm.getSchedule().getEvents()[0]
-      const summary = ev.getFirstProperty('summary').getFirstValue()
-      assert.equal(summary, 'JavaScript show')
-    })
+describe("examples/" + exampleName, () => {
+  describe("schedule", () => {
+    it("should find one event", async () => {
+      const rm = await radiomanifest.get(url);
+      assert.equal(rm.getSchedule().getEvents().length, 1);
+    });
+    it("with a specific name", async () => {
+      const rm = await radiomanifest.get(url);
+      const ev = rm.getSchedule().getEvents()[0];
+      const summary = ev.getFirstProperty("summary").getFirstValue();
+      assert.equal(summary, "JavaScript show");
+    });
 
-    it('happens every monday at 6AM', async () => {
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
+    it("happens every monday at 6AM", async () => {
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
       const now = new ICAL.Time({
-            year: 2022,
-            month: 1,
-            day: 3,
-            hour: 6,
-            minute: 30,
-            second: 0,
-            isDate: false
-        });
-      const show = rs.getNowShow(now)
-      assert.notEqual(show, null)
-      assert.equal(show.getName(), 'JavaScript show')
-    })
+        year: 2022,
+        month: 1,
+        day: 3,
+        hour: 6,
+        minute: 30,
+        second: 0,
+        isDate: false,
+      });
+      const show = rs.getNowShow(now);
+      assert.notEqual(show, null);
+      assert.equal(show.getName(), "JavaScript show");
+    });
 
-    it('doesnt happen at any other time than 6AM', async () => {
-      const rm = await radiomanifest.get(url)
-      const rs = rm.getSchedule()
+    it("doesnt happen at any other time than 6AM", async () => {
+      const rm = await radiomanifest.get(url);
+      const rs = rm.getSchedule();
       const now = new ICAL.Time({
-            year: 2022,
-            month: 1,
-            day: 3,
-            hour: 9,
-            minute: 0,
-            second: 0,
-            isDate: false
-        });
-      const ev = rs.getNowEvent(now)
-      assert.equal(ev, null)
-    })
-  })
-})
-
-
+        year: 2022,
+        month: 1,
+        day: 3,
+        hour: 9,
+        minute: 0,
+        second: 0,
+        isDate: false,
+      });
+      const ev = rs.getNowEvent(now);
+      assert.equal(ev, null);
+    });
+  });
+});

+ 25 - 21
test/example-source404.test.js

@@ -1,23 +1,27 @@
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const exampleName = 'source404'
-const expect = chai.expect
-const url = 'https://radiomanifest.degenerazione.xyz/v0.2/examples/' + exampleName + '/'
+const exampleName = "source404";
+const expect = chai.expect;
+const url =
+  "https://radiomanifest.degenerazione.xyz/v0.2/examples/" + exampleName + "/";
 
-describe('examples/' + exampleName, () => {
-  describe('streaming', () => {
-    it('shoud return one streaming option', async () => {
-      const p = await radiomanifest.get(url)
-      assert.equal(p.getStreaming().getOptions().length, 1)
-    })
-    it('... whose url is stream.m3u', async () => {
-      const p = await radiomanifest.get(url)
-      assert.equal(p.getStreaming().getOptions()[0], 'try to find me')
-      const name = p.getStreaming().getOptions()[0]
-      assert.equal(p.getStreaming().getSource(name), 'https://www.radioexample.org/stream.m3u')
-    })
-  })
-})
+describe("examples/" + exampleName, () => {
+  describe("streaming", () => {
+    it("shoud return one streaming option", async () => {
+      const p = await radiomanifest.get(url);
+      assert.equal(p.getStreaming().getOptions().length, 1);
+    });
+    it("... whose url is stream.m3u", async () => {
+      const p = await radiomanifest.get(url);
+      assert.equal(p.getStreaming().getOptions()[0], "try to find me");
+      const name = p.getStreaming().getOptions()[0];
+      assert.equal(
+        p.getStreaming().getSource(name),
+        "https://www.radioexample.org/stream.m3u",
+      );
+    });
+  });
+});

+ 50 - 51
test/parser-M3U.test.js

@@ -1,53 +1,52 @@
-const parseM3U = require('../radiomanifest.js').parsers.M3U
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
-const assert = chai.assert
+const parseM3U = require("../radiomanifest.js").parsers.M3U;
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
+const assert = chai.assert;
 
-const expect = chai.expect
-
-describe('parseM3U parses basic M3U', () => {
-  describe('empty M3U', () => {
-    it('should return empty list', () => {
-      var r = parseM3U("")
-      assert.equal(r.length, 0)
-    })
-    it('should discard empty lines', () => {
-      var r = parseM3U("\n\n\n")
-      assert.equal(r.length, 0)
-    })
-  })
-  describe('just some lines', () => {
-    it('should return appropriate list', () => {
-      var r = parseM3U("http://foo")
-      assert.equal(r.length, 1)
-      assert.equal(r[0], "http://foo")
-    })
-    it('should work with longer list', () => {
-      var r = parseM3U("http://foo\nhttp://baz\nhttp://bar\n")
-      assert.equal(r.length, 3)
-      assert.equal(r[0], "http://foo")
-      assert.equal(r[1], "http://baz")
-      assert.equal(r[2], "http://bar")
-    })
-    it('should discard empty lines', () => {
-      var r = parseM3U("http://foo\n\nhttp://baz\nhttp://bar\n\n\n")
-      assert.equal(r.length, 3)
-      assert.equal(r[0], "http://foo")
-      assert.equal(r[1], "http://baz")
-      assert.equal(r[2], "http://bar")
-    })
-  })
-  describe('comments', () => {
-    it('comments should be ignored', () => {
-      var r = parseM3U("http://foo\n#asd")
-      assert.equal(r.length, 1)
-      assert.equal(r[0], "http://foo")
-    })
-    it('mid-line hash is not a comment', () => {
-      var r = parseM3U("http://foo#asd")
-      assert.equal(r.length, 1)
-      assert.equal(r[0], "http://foo#asd")
-    })
-  })
-})
+const expect = chai.expect;
 
+describe("parseM3U parses basic M3U", () => {
+  describe("empty M3U", () => {
+    it("should return empty list", () => {
+      var r = parseM3U("");
+      assert.equal(r.length, 0);
+    });
+    it("should discard empty lines", () => {
+      var r = parseM3U("\n\n\n");
+      assert.equal(r.length, 0);
+    });
+  });
+  describe("just some lines", () => {
+    it("should return appropriate list", () => {
+      var r = parseM3U("http://foo");
+      assert.equal(r.length, 1);
+      assert.equal(r[0], "http://foo");
+    });
+    it("should work with longer list", () => {
+      var r = parseM3U("http://foo\nhttp://baz\nhttp://bar\n");
+      assert.equal(r.length, 3);
+      assert.equal(r[0], "http://foo");
+      assert.equal(r[1], "http://baz");
+      assert.equal(r[2], "http://bar");
+    });
+    it("should discard empty lines", () => {
+      var r = parseM3U("http://foo\n\nhttp://baz\nhttp://bar\n\n\n");
+      assert.equal(r.length, 3);
+      assert.equal(r[0], "http://foo");
+      assert.equal(r[1], "http://baz");
+      assert.equal(r[2], "http://bar");
+    });
+  });
+  describe("comments", () => {
+    it("comments should be ignored", () => {
+      var r = parseM3U("http://foo\n#asd");
+      assert.equal(r.length, 1);
+      assert.equal(r[0], "http://foo");
+    });
+    it("mid-line hash is not a comment", () => {
+      var r = parseM3U("http://foo#asd");
+      assert.equal(r.length, 1);
+      assert.equal(r[0], "http://foo#asd");
+    });
+  });
+});

+ 19 - 21
test/radiomanifest.test.js

@@ -1,25 +1,23 @@
-const radiomanifest = require('../radiomanifest.js')
-const chai = require('chai')
-chai.use(require('chai-as-promised'))
+const radiomanifest = require("../radiomanifest.js");
+const chai = require("chai");
+chai.use(require("chai-as-promised"));
 
-const expect = chai.expect
+const expect = chai.expect;
 
-describe('radiomanifest.js', () => {
-  describe('Get a radiomanifest', () => {
-    it('should return a Promise', () => {
-      const p = radiomanifest.get('http://example.com/')
-      expect(p instanceof Promise).to.be.eql(true)
-    })
+describe("radiomanifest.js", () => {
+  describe("Get a radiomanifest", () => {
+    it("should return a Promise", () => {
+      const p = radiomanifest.get("http://example.com/");
+      expect(p instanceof Promise).to.be.eql(true);
+    });
 
-    it('should reject on invalid URL', () => {
-      const p = radiomanifest.get('http://example.com/')
-      expect(p).to.eventually.be.rejected
-    })
-  })
+    it("should reject on invalid URL", () => {
+      const p = radiomanifest.get("http://example.com/");
+      expect(p).to.eventually.be.rejected;
+    });
+  });
 
-  describe('streaming', () => {
-    it('shoud return a valid streaming URL', () => {
-
-    })
-  })
-})
+  describe("streaming", () => {
+    it("shoud return a valid streaming URL", () => {});
+  });
+});

+ 51 - 41
ui.js

@@ -1,55 +1,65 @@
 // const radiomanifest = require('radiomanifest.js')
 
 function updateNow(radio) {
-  var box = document.querySelector('#now-info')
-  const show = radio.getSchedule().getNowShow()
+  var box = document.querySelector("#now-info");
+  const show = radio.getSchedule().getNowShow();
   try {
-  var showText = show.getName() + '\nfeed: ' + show.getWebsite()
+    var showText = show.getName() + "\nfeed: " + show.getWebsite();
   } catch (e) {
-    var showText = String(show)
+    var showText = String(show);
   }
-  var text = radio.getName() + ' - ' + radio.getDescription() + ' -- ' + showText
-  box.textContent = text
+  var text =
+    radio.getName() + " - " + radio.getDescription() + " -- " + showText;
+  box.textContent = text;
 }
-async function fai () {
-  const radio = await radiomanifest.get('https://radiomanifest.degenerazione.xyz/v0.2/examples/full-ondarossa')
-  console.log('radio?', radio)
-  const s = radio.getStreaming()
-  console.log(s.sources)
-  console.log(s.getOptions())
-  console.log(s.getSource(s.getOptions()[0]))
-  console.log(s.getSource())
+async function fai() {
+  const radio = await radiomanifest.get(
+    "https://radiomanifest.degenerazione.xyz/v0.2/examples/full-ondarossa",
+  );
+  console.log("radio?", radio);
+  const s = radio.getStreaming();
+  console.log(s.sources);
+  console.log(s.getOptions());
+  console.log(s.getSource(s.getOptions()[0]));
+  console.log(s.getSource());
 
-  var audioEl = document.querySelector('#player audio')
-  var urls = await s.pickURLs()
-  console.log('audios', urls)
-  urls.forEach( function(url) {
-    var srcEl = document.createElement("source")
-    srcEl.setAttribute('src', url)
-    console.log('src', srcEl, url)
-    audioEl.appendChild(srcEl)
-  })
+  var audioEl = document.querySelector("#player audio");
+  var urls = await s.pickURLs();
+  console.log("audios", urls);
+  urls.forEach(function (url) {
+    var srcEl = document.createElement("source");
+    srcEl.setAttribute("src", url);
+    console.log("src", srcEl, url);
+    audioEl.appendChild(srcEl);
+  });
 
-  const showList = document.querySelector('#shows > ul')
+  const showList = document.querySelector("#shows > ul");
   for (const show of radio.getShows()) {
-    const item = document.createElement('li')
-    const link = document.createElement('a')
-    link.dataset['show'] = show.getName()
-    link.textContent = show.getName()
-    link.setAttribute('href', show.getWebsite())
-    item.appendChild(link)
-    showList.appendChild(item)
+    const item = document.createElement("li");
+    const link = document.createElement("a");
+    link.dataset["show"] = show.getName();
+    link.textContent = show.getName();
+    link.setAttribute("href", show.getWebsite());
+    item.appendChild(link);
+    showList.appendChild(item);
   }
-  showList.addEventListener('mouseenter', function (evt) {
-    if (evt.target.dataset['show'] === undefined)
-      return;
-    const info = document.querySelector('#show-info')
-    info.textContent = radio.getShowByName(evt.target.dataset['show']).getFeed()
-  }, true)
+  showList.addEventListener(
+    "mouseenter",
+    function (evt) {
+      if (evt.target.dataset["show"] === undefined) return;
+      const info = document.querySelector("#show-info");
+      info.textContent = radio
+        .getShowByName(evt.target.dataset["show"])
+        .getFeed();
+    },
+    true,
+  );
 
-  updateNow(radio)
-  console.log(radio.getSchedule())
-  setInterval(function() { updateNow(radio) }, 2000)
+  updateNow(radio);
+  console.log(radio.getSchedule());
+  setInterval(function () {
+    updateNow(radio);
+  }, 2000);
 }
 
-fai()
+fai();

+ 23 - 25
webpack.config.js

@@ -1,27 +1,25 @@
-const path = require('path')
+const path = require("path");
 
 const variants = [
-  { name: 'web', libtype: 'amd', target: 'web' },
-  { name: 'node', libtype: 'amd', target: 'node' },
-  { name: 'oldstyle', libtype: 'umd', target: 'web' }
-]
-module.exports = variants.map(
-  variant => {
-    const obj = {
-      name: variant.name,
-      mode: 'production',
-      entry: './radiomanifest.js',
-      target: [variant.target],
-      output: {
-        path: path.resolve(__dirname, 'dist'),
-        filename: 'radiomanifest-' + variant.name + '.bundle.js',
-        clean: false,
-        library: {
-          name: 'radiomanifest',
-          type: variant.libtype
-        }
-      }
-    }
-    return obj
-  }
-)
+  { name: "web", libtype: "amd", target: "web" },
+  { name: "node", libtype: "amd", target: "node" },
+  { name: "oldstyle", libtype: "umd", target: "web" },
+];
+module.exports = variants.map((variant) => {
+  const obj = {
+    name: variant.name,
+    mode: "production",
+    entry: "./radiomanifest.js",
+    target: [variant.target],
+    output: {
+      path: path.resolve(__dirname, "dist"),
+      filename: "radiomanifest-" + variant.name + ".bundle.js",
+      clean: false,
+      library: {
+        name: "radiomanifest",
+        type: variant.libtype,
+      },
+    },
+  };
+  return obj;
+});