Initial Commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user