fix duplicates and filter canceled/declined events - v0.4.7 [skip-ci]
Some checks failed
Build SilverBullet Plug / build (push) Has been cancelled

This commit is contained in:
2026-02-25 05:49:05 -08:00
parent 139ab71db7
commit fc03f0e0de
6 changed files with 57 additions and 23 deletions

View File

@@ -3,7 +3,7 @@ import ICAL from "ical.js";
import { RRule, RRuleSet } from "rrule";
import { getUtcOffsetMs, resolveIanaName } from "./timezones.ts";
const VERSION = "0.4.6";
const VERSION = "0.4.7";
const CACHE_KEY = "icalendar:lastSync";
console.log(`[iCalendar] Plug script executing at top level (Version ${VERSION})`);
@@ -325,21 +325,55 @@ async function fetchAndParseCalendar(source: any, windowDays = 365, displayTimez
const vcalendar = new ICAL.Component(jcalData);
const vevents = vcalendar.getAllSubcomponents("vevent");
// First pass: map of UID -> Set of ISO strings for RECURRENCE-ID exceptions
const overrides = new Map<string, Set<string>>();
for (const vevent of vevents) {
const recId = vevent.getFirstPropertyValue("recurrence-id") as ICAL.Time | null;
const uid = vevent.getFirstPropertyValue("uid") as string | null;
if (recId && uid) {
if (!overrides.has(uid)) overrides.set(uid, new Set());
overrides.get(uid)!.add(recId.toJSDate().toISOString());
}
}
const events: any[] = [];
for (const vevent of vevents) {
const icsEvent = new ICAL.Event(vevent);
const status = vevent.getFirstPropertyValue("status") as string | null;
const summary = icsEvent.summary || "";
// 1. Skip explicitly cancelled events
if (status?.toUpperCase() === "CANCELLED") continue;
if (summary.toLowerCase().startsWith("canceled:") || summary.toLowerCase().startsWith("cancelled:")) continue;
// 2. Skip declined events (look for PARTSTAT=DECLINED in attendees)
const attendees = vevent.getAllProperties("attendee");
let declined = false;
for (const attendee of attendees) {
const partstat = attendee.getParameter("partstat");
if (partstat?.toUpperCase() === "DECLINED") {
// Note: In a multi-user environment, we'd need to check if this is *the* user's status.
// For a personal plug, we assume any DECLINED attendee means the user declined or the event is out.
declined = true;
break;
}
}
if (declined) continue;
// Extract raw properties for recurrence expansion
const summary = icsEvent.summary;
const uid = icsEvent.uid;
const description = icsEvent.description;
const location = icsEvent.location;
const rrule = vevent.getFirstPropertyValue("rrule");
const exdates = vevent.getAllProperties("exdate").map((p: any) => p.getFirstValue().toJSDate().toISOString());
// Add recurrence-id overrides to exdates so the Master doesn't expand them
if (uid && overrides.has(uid) && !vevent.getFirstPropertyValue("recurrence-id")) {
for (const overDate of overrides.get(uid)!) {
exdates.push(overDate);
}
}
// Resolve start/end times
const startDateUTC = icsEvent.startDate.toJSDate();
const endDateUTC = icsEvent.endDate ? icsEvent.endDate.toJSDate() : null;