UNPKG

@mildshield14/ical-booker

Version:

A lightweight, modern CalDAV client for Node.js - discover calendars, check availability, and create bookings

54 lines 2.56 kB
// src/lib/busy.ts import fetch from 'node-fetch'; import ical from 'node-ical'; import pkg from 'rrule'; const { RRule } = pkg; const fmt = (d) => `${new Date(d).toISOString().replace(/[-:]/g, '').split('.')[0]}Z`; export async function getBusyEvents(creds, calendars, startISO, endISO) { const range = { start: fmt(startISO), end: fmt(endISO) }; const AUTH = `Basic ${Buffer.from(`${creds.username}:${creds.password}`).toString('base64')}`; const report = `<?xml version="1.0"?> <C:calendar-query xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:D="DAV:"> <D:prop><C:calendar-data><C:expand start="${range.start}" end="${range.end}"/></C:calendar-data></D:prop> <C:filter><C:comp-filter name="VCALENDAR"><C:comp-filter name="VEVENT"> <C:time-range start="${range.start}" end="${range.end}"/> </C:comp-filter></C:comp-filter></C:filter> </C:calendar-query>`; // fetch every calendar in parallel const bodies = await Promise.all(calendars.map(({ url }) => fetch(url, { method: 'REPORT', headers: { Authorization: AUTH, Depth: '1', 'Content-Type': 'application/xml' }, body: report, }).then(r => (r.ok ? r.text() : '')))); const events = []; for (const xml of bodies) { for (const [, ics] of xml.matchAll(/<calendar-data[^>]*>(?:<!\[CDATA\[)?([\s\S]*?)(?:\]\]>)?<\/calendar-data>/gi)) { const parsed = ical.parseICS(ics); for (const ev of Object.values(parsed)) { if (!(ev && ev.type === 'VEVENT' && ev.start && ev.end)) continue; // one-off if (!ev.rrule) { events.push({ start: ev.start.toISOString(), end: ev.end.toISOString(), title: ev.summary || '(no title)', }); continue; } // recurring const rule = RRule.fromString(ev.rrule.toString()); rule.between(new Date(startISO), new Date(endISO), true).forEach(dt => { const duration = ev.end.getTime() - ev.start.getTime(); events.push({ start: dt.toISOString(), end: new Date(dt.getTime() + duration).toISOString(), title: ev.summary || '(no title)', }); }); } } } return events.sort((a, b) => a.start.localeCompare(b.start)); } //# sourceMappingURL=busy.js.map