Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
# calendar-parser
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Parses iCalendar (RFC 5545) / ICS payloads embedded in emails — these
arrive either as `text/calendar` MIME parts or as attachments with the
`application/ics` media type. Only the subset of fields meaningful to
email automation is extracted: summary, description, start/end,
location, organizer, attendees, UID, and METHOD (REQUEST / REPLY /
CANCEL). The parser is deliberately tolerant of folded continuation
lines and ICS escape sequences.
## Features
- Handles RFC 5545 line folding — both `CRLF + SP` and `CRLF + TAB`
continuations.
- Unescapes the four standard ICS escapes: `\\n`, `\\,`, `\\;`, `\\\\`.
- Extracts `mailto:` addresses from `ORGANIZER` and `ATTENDEE` lines
regardless of preceding `CN` parameters.
- Captures the calendar-level `METHOD` and surfaces it on every event
so consumers can branch on REQUEST vs CANCEL.
- Ignores lines outside `BEGIN:VEVENT` / `END:VEVENT` blocks so
`VCALENDAR` boilerplate does not leak into event fields.
## Usage: Basic
```ts
import { parseICalendar } from "@suaveplan/email-processing/calendar";
const events = parseICalendar(icsAttachment);
for (const event of events) {
if (event.method === "CANCEL") {
await cancelMeeting(event.uid);
}
}
```
## Usage: Advanced
```ts
import { parseICalendar } from "@suaveplan/email-processing/calendar";
const events = parseICalendar(icsString);
for (const event of events) {
const attendees = event.attendees.map((mailto) => mailto.toLowerCase());
await db.calendar.upsert({
where: { uid: event.uid ?? hash(event.summary ?? "") },
update: {
summary: event.summary ?? null,
dtStart: event.dtStart ?? null,
dtEnd: event.dtEnd ?? null,
location: event.location ?? null,
attendees,
},
create: { ... },
});
}
```
## API Reference
- `CalendarEvent` — `{ summary?, description?, dtStart?, dtEnd?,
location?, organizer?, attendees, uid?, method? }`.
- `parseICalendar(icsContent: string): CalendarEvent[]` — returns an
empty array when no `VEVENT` blocks are present. Throws
`EmailProcessingError(INVALID_INPUT)` for non-string input.
## Implementation Notes
DTSTART and DTEND are returned as the raw ICS date-time strings (e.g.
`20250101T120000Z`) without conversion. Consumers that need
`Date`/`Temporal` instances should call their preferred parser — the
package deliberately does not pick one, since `text/calendar` payloads
arrive in many timezone shapes (floating, UTC, TZID-suffixed) and
correct handling requires knowledge of the surrounding email context.
The unfolding regexes operate on `\r\n` first so that `\n`-only ICS
payloads still split correctly on the second `\r?\n` split call.