From 5d5924d331856f5a93d32eee1ca4d3bbdf89dde1 Mon Sep 17 00:00:00 2001 From: mcbabo Date: Wed, 14 Jan 2026 13:22:15 +0100 Subject: [PATCH] Add regex filter to .ics calendars --- src/services/ICSSubscriptionService.ts | 31 +++++++++++++++++++++----- src/settings/tabs/integrationsTab.ts | 6 +++++ src/types.ts | 1 + 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/services/ICSSubscriptionService.ts b/src/services/ICSSubscriptionService.ts index 7291564f..dd7ff131 100644 --- a/src/services/ICSSubscriptionService.ts +++ b/src/services/ICSSubscriptionService.ts @@ -236,7 +236,7 @@ export class ICSSubscriptionService extends EventEmitter { throw new Error("Unknown subscription type"); } - const events = this.parseICS(icsData, subscription.id); + const events = this.parseICS(icsData, subscription); // Update cache const cache: ICSCache = { @@ -296,7 +296,7 @@ export class ICSSubscriptionService extends EventEmitter { } } - private parseICS(icsData: string, subscriptionId: string): ICSEvent[] { + private parseICS(icsData: string, subscription: ICSSubscription): ICSEvent[] { try { const jcalData = ICAL.parse(icsData); const comp = new ICAL.Component(jcalData); @@ -331,6 +331,20 @@ export class ICSSubscriptionService extends EventEmitter { } }); + // Create Regex filter if user sets subscription.filter + let regExp: RegExp | null = null; + const filter = subscription.filter?.trim(); + + if (filter) { + try { + // Fix double escaping + const match = filter.match(/^\/(.+)\/([a-z]*)$/i); + regExp = match ? new RegExp(match[1], match[2]) : new RegExp(filter); + } catch { + regExp = null; + } + } + // Second pass: process events vevents.forEach((vevent: ICAL.Component) => { try { @@ -344,6 +358,11 @@ export class ICSSubscriptionService extends EventEmitter { // Extract basic properties const summary = event.summary || "Untitled Event"; + + if (regExp && !regExp.test(summary)) { + return; + } + const description = event.description || undefined; const location = event.location || undefined; @@ -360,12 +379,12 @@ export class ICSSubscriptionService extends EventEmitter { const endISO = endDate ? this.icalTimeToISOString(endDate) : undefined; // Generate unique ID - const uid = event.uid || `${subscriptionId}-${events.length}`; - const eventId = `${subscriptionId}-${uid}`; + const uid = event.uid || `${subscription.id}-${events.length}`; + const eventId = `${subscription.id}-${uid}`; const icsEvent: ICSEvent = { id: eventId, - subscriptionId: subscriptionId, + subscriptionId: subscription.id, title: summary, description: description, start: startISO, @@ -430,7 +449,7 @@ export class ICSSubscriptionService extends EventEmitter { if (modifiedStart) { events.push({ id: `${eventId}-${instanceCount}`, - subscriptionId: subscriptionId, + subscriptionId: subscription.id, title: modifiedEvent.summary || summary, description: modifiedEvent.description || description, start: this.icalTimeToISOString(modifiedStart), diff --git a/src/settings/tabs/integrationsTab.ts b/src/settings/tabs/integrationsTab.ts index 61b45a29..9db9ce37 100644 --- a/src/settings/tabs/integrationsTab.ts +++ b/src/settings/tabs/integrationsTab.ts @@ -1173,6 +1173,7 @@ export function renderIntegrationsTab( enabled: false, // Start disabled until user fills in details type: "remote" as const, refreshInterval: 60, + filter: "" }; if (!plugin.icsSubscriptionService) { @@ -1589,6 +1590,7 @@ function renderICSSubscriptionsList( const colorInput = createCardInput("color", "", subscription.color); const refreshInput = createCardNumberInput(5, 1440, 5, subscription.refreshInterval || 60); + const filterInput = createCardInput("text", "", subscription.filter); // Update handlers const updateSubscription = async (updates: Partial) => { @@ -1617,6 +1619,9 @@ function renderICSSubscriptionsList( const minutes = parseInt(refreshInput.value) || 60; updateSubscription({ refreshInterval: minutes }); }); + filterInput.addEventListener("change", () => + updateSubscription({ filter: filterInput.value }) + ); // Type change handler - re-render the subscription list to update input type typeSelect.addEventListener("change", async () => { @@ -1750,6 +1755,7 @@ function renderICSSubscriptionsList( }, { label: "Color:", input: colorInput }, { label: "Refresh (min):", input: refreshInput }, + { label: "Filter (regex):", input: filterInput } ]; createCard(container, { diff --git a/src/types.ts b/src/types.ts index d1dafd42..e6608344 100644 --- a/src/types.ts +++ b/src/types.ts @@ -761,6 +761,7 @@ export interface ICSSubscription { color: string; enabled: boolean; refreshInterval: number; // minutes (for remote) or check interval (for local) + filter: string; } export interface ICSEvent {