radiomanifest.js/calendar.js

267 lines
6.3 KiB
JavaScript
Raw Normal View History

2023-09-14 01:07:07 +02:00
import ICAL from "ical.js";
import shows from "./shows";
2022-01-30 02:23:11 +01:00
2022-02-02 10:16:12 +01:00
function max(a, b) {
2023-09-14 01:07:07 +02:00
if (a < b) return b;
return a;
2022-02-02 10:16:12 +01:00
}
2022-01-30 14:01:38 +01:00
/**
* Represent a schedule (ie: a .ics file)
*/
2022-01-30 02:23:11 +01:00
class RadioSchedule {
2023-09-14 01:07:07 +02:00
constructor(calendar, radio) {
this.calendar = calendar; // ICAL.Calendar
this.radio = radio; // radiomanifest.Radio
2022-01-30 02:23:11 +01:00
}
2022-01-30 14:01:38 +01:00
/**
* Get a list of all known {@link vEvent}s
*
* @returns {Array<external:ICAL~Component>}
* */
2022-01-30 02:23:11 +01:00
getEvents() {
2023-09-14 01:07:07 +02:00
return this.calendar.getAllSubcomponents("vevent");
2022-01-30 02:23:11 +01:00
}
2022-01-30 14:01:38 +01:00
2022-02-02 10:16:12 +01:00
/**
* @returns {RadioShow} tries to get a matching show, or create a new one
*/
getShowByEvent(ev) {
2023-09-14 01:07:07 +02:00
if (ev === null) return null;
2022-02-02 10:16:12 +01:00
if (this.radio !== undefined) {
2023-09-14 01:07:07 +02:00
const showid = RadioSchedule.veventGetShowID(ev);
var show = this.radio.getShowByName(showid);
2022-02-02 10:16:12 +01:00
if (show === null || show === undefined) {
2023-09-14 01:07:07 +02:00
return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
2022-02-02 10:16:12 +01:00
}
return show;
}
2023-09-14 01:07:07 +02:00
return new shows.RadioShow(RadioSchedule.veventGetSummary(ev));
2022-02-02 10:16:12 +01:00
}
2022-01-30 14:01:38 +01:00
/**
* @returns {external:ICAL~Component} if nothing is going on right now, `null` is returned
*/
2022-01-30 02:23:11 +01:00
getNowEvent(now) {
2023-09-14 01:07:07 +02:00
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];
2022-01-30 02:23:11 +01:00
}
2022-01-30 14:01:38 +01:00
/**
* @returns {RadioShow} if nothing is going on right now, `null` is returned
*/
2022-01-30 02:23:11 +01:00
getNowShow(now) {
2023-09-14 01:07:07 +02:00
const ev = this.getNowEvent(now);
return this.getShowByEvent(ev);
2022-01-30 02:23:11 +01:00
}
2022-01-30 14:01:38 +01:00
/**
2022-02-02 10:16:12 +01:00
* @returns {NextEvent} if there is none, `null` is returned
2022-01-30 14:01:38 +01:00
*/
2022-01-30 02:23:11 +01:00
getNextEvent(now) {
2023-09-14 01:07:07 +02:00
var nowEvent = this.getNowEvent(now);
let future_events = this.getEvents()
.filter((e) => {
return e != nowEvent;
})
2022-02-02 10:16:12 +01:00
.map((e) => {
2023-09-14 01:07:07 +02:00
const vEvent = new ICAL.Event(e);
return { event: e, time: getNext(vEvent, now) };
2022-02-02 10:16:12 +01:00
})
2023-09-14 01:07:07 +02:00
.filter((x) => {
return x.time !== null && x.time !== undefined;
});
2022-02-02 10:16:12 +01:00
// 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
2023-09-14 01:07:07 +02:00
future_events.sort(
(x1, x2) =>
this.veventGetPriority(x1.event) - this.veventGetPriority(x2.event),
);
future_events.sort((x, y) => x.time.toUnixTime() - y.time.toUnixTime());
2022-02-02 10:16:12 +01:00
if (future_events.length === 0) {
2023-09-14 01:07:07 +02:00
return null;
2022-02-02 10:16:12 +01:00
}
2023-09-14 01:07:07 +02:00
return future_events[0];
2022-01-30 02:23:11 +01:00
}
2022-01-30 14:01:38 +01:00
/**
2022-02-02 10:16:12 +01:00
* @returns {NextShow} if there is no next show, null will be returned
2022-01-30 14:01:38 +01:00
*/
2022-01-30 02:23:11 +01:00
getNextShow(now) {
2023-09-14 01:07:07 +02:00
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 };
2022-01-30 02:23:11 +01:00
}
static veventGetSummary(vevent) {
2023-09-14 01:07:07 +02:00
return vevent.getFirstProperty("summary").getFirstValue();
2022-01-30 02:23:11 +01:00
}
static veventGetShowID(vevent) {
2023-09-14 01:07:07 +02:00
return RadioSchedule.veventGetSummary(vevent); // XXX: X-Show-ID
2022-01-30 02:23:11 +01:00
}
2022-02-02 10:16:12 +01:00
/**
* @return {integer} a normalized version of priority, easier to compare
*/
veventGetPriority(ev) {
2023-09-14 01:07:07 +02:00
const prop = ev.getFirstProperty("priority");
2022-02-02 10:16:12 +01:00
let prio;
if (prop === null) {
2023-09-14 01:07:07 +02:00
prio = null;
2022-02-02 10:16:12 +01:00
} else {
2023-09-14 01:07:07 +02:00
prio = prop.getFirstValue();
2022-02-02 10:16:12 +01:00
}
if (prio === null || prio === 0) {
2023-09-14 01:07:07 +02:00
prio = 100;
2022-02-02 10:16:12 +01:00
}
2023-09-14 01:07:07 +02:00
return prio;
2022-02-02 10:16:12 +01:00
}
2022-01-30 02:23:11 +01:00
}
function isNow(vEvent, now) {
2023-09-14 01:07:07 +02:00
if (now === undefined) {
now = ICAL.Time.now();
}
if (vEvent.isRecurring()) {
return isNowRecurring(vEvent, now);
}
return now < vEvent.endDate && now > vEvent.startDate;
2022-01-30 02:23:11 +01:00
}
function isNowRecurring(vEvent, now) {
2023-09-14 01:07:07 +02:00
var expand = vEvent.iterator(vEvent.startDate);
2022-01-30 02:23:11 +01:00
var next, next_end;
while ((next = expand.next())) {
2023-09-14 01:07:07 +02:00
next_end = next.clone();
next_end.addDuration(vEvent.duration);
2022-01-30 02:23:11 +01:00
if (next_end > now) {
break;
}
}
2023-09-14 01:07:07 +02:00
return now < next_end && now > next;
2022-01-30 02:23:11 +01:00
}
2022-02-02 10:16:12 +01:00
/*
* @private
* @param {external:ICAL~Component} vEvent a _recurring_ vEvent
* @param {external:ICAL~Time} [now]
* @return {external:ICAL~Time} first future occurrence of this event
*/
function getNext(vEvent, now) {
if (now === undefined) {
2023-09-14 01:07:07 +02:00
now = ICAL.Time.now();
2022-02-02 10:16:12 +01:00
}
if (vEvent.isRecurring()) {
2023-09-14 01:07:07 +02:00
return getNextRecurring(vEvent, now);
2022-02-02 10:16:12 +01:00
}
if (vEvent.endDate > now) {
2023-09-14 01:07:07 +02:00
const val = max(now, vEvent.startDate);
return val;
2022-02-02 10:16:12 +01:00
}
2023-09-14 01:07:07 +02:00
return null;
2022-02-02 10:16:12 +01:00
}
/*
* @private
* @param {external:ICAL~Component} vEvent a _recurring_ vEvent
* @param {external:ICAL~Time} now
* @return {external:ICAL~Time} first future occurrence of this event
*/
function getNextRecurring(vEvent, now) {
2023-09-14 01:07:07 +02:00
var expand = vEvent.iterator(vEvent.startDate);
2022-02-02 10:16:12 +01:00
var next, next_end;
while ((next = expand.next())) {
2023-09-14 01:07:07 +02:00
const start = next.clone();
next_end = start.clone();
next_end.addDuration(vEvent.duration);
2022-02-02 10:16:12 +01:00
if (next_end <= now) {
2023-09-14 01:07:07 +02:00
continue;
2022-02-02 10:16:12 +01:00
}
2023-09-14 01:07:07 +02:00
return max(start, now);
2022-02-02 10:16:12 +01:00
}
2023-09-14 01:07:07 +02:00
return null;
2022-02-02 10:16:12 +01:00
}
2022-01-30 02:23:11 +01:00
async function get(manifest) {
if (manifest.scheduleURL) {
2023-09-14 01:07:07 +02:00
let resp = null;
2022-01-30 02:23:11 +01:00
try {
2023-09-14 01:07:07 +02:00
resp = await fetch(manifest.scheduleURL);
2022-01-30 02:23:11 +01:00
} catch (e) {
2023-09-14 01:07:07 +02:00
true;
2022-01-30 02:23:11 +01:00
}
if (resp !== null) {
try {
2023-09-14 01:07:07 +02:00
const text = await resp.text();
return parse(text);
2022-01-30 02:23:11 +01:00
} catch (e) {
2023-09-14 01:07:07 +02:00
console.error("Error while parsing schedule", e);
throw e;
2022-01-30 02:23:11 +01:00
}
}
}
}
2022-01-30 14:01:38 +01:00
/**
* Parse ICAL and get a RadioSchedule
*
* @param {string} text The text, in ICS format
* @returns {RadioSchedule}
*/
2022-01-30 02:23:11 +01:00
function parse(text) {
var jcalData = ICAL.parse(text);
var vcalendar = new ICAL.Component(jcalData);
2023-09-14 01:07:07 +02:00
return new RadioSchedule(vcalendar);
2022-01-30 02:23:11 +01:00
}
2023-01-23 12:42:25 +01:00
export default {
2022-01-30 02:23:11 +01:00
get: get,
parse: parse,
RadioSchedule: RadioSchedule,
2023-09-14 01:07:07 +02:00
};
2022-01-30 14:01:38 +01:00
/**
* @typedef {Object} NextShow
* @property {RadioShow} show The next show scheduled
* @property {external:ICAL~Time} time When it will start
*/
2022-02-02 10:16:12 +01:00
/**
* @typedef {Object} NextEvent
* @property {RadioShow} event The next show scheduled
* @property {external:ICAL~Time} time When it will start
*/
2022-01-30 14:01:38 +01:00
/**
* @external ICAL
* @see https://mozilla-comm.github.io/ical.js/api/index.html
*/
/**
* @class Component
* @memberof external:ICAL
* @inner
* @see https://mozilla-comm.github.io/ical.js/api/ICAL.Component.html
*/
/**
* @class Time
* @memberof external:ICAL
* @inner
* @see https://mozilla-comm.github.io/ical.js/api/ICAL.Time.html
*/