import * as ical from 'node-ical'; import type { AgendaEvent } from '../todayview'; export async function fetchICSAgenda( urls: string[] ): Promise { const now = new Date(); const startOfDay = new Date(now); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(now); endOfDay.setHours(23, 59, 59, 999); const allEvents = ( await Promise.all(urls.map((url) => fetchOne(url, startOfDay, endOfDay))) ).flat(); allEvents.sort((a, b) => a.sortKey - b.sortKey); return allEvents.map(({ sortKey, ...event }) => event); } interface RawAgendaEvent extends AgendaEvent { sortKey: number; } async function fetchOne( url: string, start: Date, end: Date ): Promise { const resp = await fetch(url, { headers: [['User-Agent', 'private TRMNL fetcher']], }); const data = await resp.text(); const parsed = Object.values(await ical.async.parseICS(data)).filter( (a) => a?.type === 'VEVENT' ); const events: RawAgendaEvent[] = []; for (const event of parsed) { for (const instance of ical.expandRecurringEvent(event, { from: start, to: end, excludeExdates: true, expandOngoing: true, includeOverrides: true, })) { if (instance.isFullDay) continue; const startTime = formatTime(instance.start); const endTime = formatTime(instance.end); const location = (instance.event as any).location ?? ''; events.push({ start: startTime, end: endTime, title: (instance.summary as string) ?? '', where: location, kind: '', sortKey: instance.start.valueOf(), }); } } return events; } function formatTime(d: Date): string { return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`; }