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

@@ -1,6 +1,6 @@
---
name: Library/sstent/icalendar
version: "0.4.6"
version: "0.4.7"
tags: meta/library
files:
- icalendar.plug.js

View File

@@ -1,6 +1,6 @@
{
"name": "icalendar-plug",
"version": "0.4.6",
"version": "0.4.7",
"nodeModulesDir": "auto",
"tasks": {
"sync-version": "deno run -A scripts/sync-version.ts",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
name: icalendar
version: 0.4.6
version: 0.4.7
author: sstent
index: icalendar.ts
# Legacy SilverBullet permission name

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;