Fix: Final working v0.3.1 with fixed function mapping
All checks were successful
Build SilverBullet Plug / build (push) Successful in 25s

This commit is contained in:
2026-02-17 13:55:31 -08:00
parent a7180995b0
commit ced95d2a7a
4 changed files with 35 additions and 76 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,13 +3,16 @@ version: 0.3.1
author: sstent
functions:
syncCalendars:
path: icalendar.ts:syncCalendars
command: "iCalendar: Sync"
forceSync:
path: icalendar.ts:forceSync
command: "iCalendar: Force Sync"
clearCache:
path: icalendar.ts:clearCache
command: "iCalendar: Clear Cache"
showVersion:
path: icalendar.ts:showVersion
command: "iCalendar: Show Version"
# Grant permissions to fetch from anywhere
permissions:
- http
- http

View File

@@ -3,7 +3,6 @@ import { convertIcsCalendar, type IcsCalendar, type IcsEvent, type IcsDateObject
const VERSION = "0.3.1";
const CACHE_KEY = "icalendar:lastSync";
const DEFAULT_CACHE_DURATION_SECONDS = 21600; // 6 hours
// Mapping of common Windows/Outlook timezones to their standard offsets (in hours)
const TIMEZONE_OFFSETS: Record<string, number> = {
@@ -65,30 +64,20 @@ function toLocalISO(d: Date): string {
function processIcsDate(obj: any, manualShift = 0): string {
if (!obj) return "";
let wallTimeStr = "";
if (obj.local && typeof obj.local.date === "string") {
wallTimeStr = obj.local.date;
} else if (typeof obj.date === "string") {
wallTimeStr = obj.date;
} else if (obj.date instanceof Date) {
wallTimeStr = obj.date.toISOString();
} else if (obj instanceof Date) {
wallTimeStr = obj.toISOString();
}
if (obj.local && typeof obj.local.date === "string") wallTimeStr = obj.local.date;
else if (typeof obj.date === "string") wallTimeStr = obj.date;
else if (obj.date instanceof Date) wallTimeStr = obj.date.toISOString();
else if (obj instanceof Date) wallTimeStr = obj.toISOString();
if (!wallTimeStr) return "";
// 1. Extract the "Wall Time" from the string (ignoring Z if present)
const baseDate = new Date(wallTimeStr.replace("Z", "") + "Z");
// 2. Determine Source Timezone Offset
const baseDate = new Date(wallTimeStr.replace("Z", "") + "Z");
const tzName = obj.local?.timezone || obj.timezone || "UTC";
const sourceOffset = TIMEZONE_OFFSETS[tzName] ?? 0;
// 3. Calculate True UTC: WallTime - SourceOffset
const utcMillis = baseDate.getTime() - (sourceOffset * 3600000);
const finalDate = new Date(utcMillis + (manualShift * 3600000));
// 4. Apply User's Manual Shift and Localize
return toLocalISO(new Date(utcMillis + (manualShift * 3600000)));
return toLocalISO(finalDate);
}
function isIcsDateObjects(obj: any): obj is IcsDateObjects {
@@ -96,11 +85,8 @@ function isIcsDateObjects(obj: any): obj is IcsDateObjects {
}
async function sha256Hash(str: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(str);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
const hashBuffer = await crypto.subtle.digest("SHA-256", (new TextEncoder()).encode(str));
return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, "0")).join("");
}
function convertDatesToStrings<T>(obj: T, hourShift = 0): DateToString<T> {
@@ -111,41 +97,26 @@ function convertDatesToStrings<T>(obj: T, hourShift = 0): DateToString<T> {
if (typeof obj === 'object') {
const result: any = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = convertDatesToStrings((obj as any)[key], hourShift);
}
if (Object.prototype.hasOwnProperty.call(obj, key)) result[key] = convertDatesToStrings((obj as any)[key], hourShift);
}
return result as DateToString<T>;
}
return obj as DateToString<T>;
}
// ============================================================================
// Configuration & Commands
// ============================================================================
async function getSources(): Promise<{ sources: Source[], tzShift: number }> {
try {
const rawConfig = await config.get<PlugConfig>("icalendar", { sources: [] });
let sources: Source[] = [];
let tzShift = rawConfig.tzShift ?? 0;
let rawSources = rawConfig.sources;
if (rawSources && typeof rawSources === "object") {
if (rawSources.tzShift !== undefined && tzShift === 0) tzShift = rawSources.tzShift;
if (Array.isArray(rawSources)) {
sources = rawSources.filter(s => s && typeof s.url === "string");
} else if (rawSources.url) {
sources = [rawSources];
} else {
for (const key in rawSources) {
if (rawSources[key] && typeof rawSources[key].url === "string") sources.push(rawSources[key]);
}
}
if (Array.isArray(rawSources)) sources = rawSources.filter(s => s && typeof s.url === "string");
else if (rawSources.url) sources = [rawSources];
}
return { sources, tzShift };
} catch (e) {
console.error("Failed to load configuration", e);
return { sources: [], tzShift: 0 };
}
}
@@ -153,56 +124,39 @@ async function getSources(): Promise<{ sources: Source[], tzShift: number }> {
async function fetchAndParseCalendar(source: Source, hourShift = 0): Promise<CalendarEvent[]> {
let url = source.url.trim();
if (url.includes(" ")) url = encodeURI(url);
const response = await fetch(url, {
headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }
});
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const icsData = await response.text();
const calendar: IcsCalendar = convertIcsCalendar(undefined, icsData);
const calendar = convertIcsCalendar(undefined, await response.text());
if (!calendar.events) return [];
return await Promise.all(calendar.events.map(async (icsEvent: IcsEvent): Promise<CalendarEvent> => {
const uniqueKey = `${icsEvent.start?.date || ''}${icsEvent.uid || icsEvent.summary || ''}`;
const ref = await sha256Hash(uniqueKey);
return convertDatesToStrings({
...icsEvent,
ref,
tag: "ical-event" as const,
sourceName: source.name,
}, hourShift);
return convertDatesToStrings({ ...icsEvent, ref, tag: "ical-event" as const, sourceName: source.name }, hourShift);
}));
}
// ============================================================================
// Public Commands
// ============================================================================
export async function syncCalendars() {
try {
const { sources, tzShift } = await getSources();
if (sources.length === 0) {
console.log("[iCalendar] No sources configured.");
return;
}
if (sources.length === 0) return;
await editor.flashNotification("Syncing calendars...", "info");
const allEvents: CalendarEvent[] = [];
for (const source of sources) {
try {
const events = await fetchAndParseCalendar(source, tzShift);
allEvents.push(...events);
} catch (err) {
console.error(`[iCalendar] Failed to sync ${source.name}:`, err);
await editor.flashNotification(`Failed to sync ${source.name}`, "error");
console.error(`Failed to sync ${source.name}:`, err);
}
}
await index.indexObjects("$icalendar", allEvents);
await editor.flashNotification(`Synced ${allEvents.length} events`, "info");
} catch (err) {
console.error("[iCalendar] Sync failed:", err);
console.error("Sync failed:", err);
}
}
@@ -226,4 +180,4 @@ export async function clearCache() {
export async function showVersion() {
await editor.flashNotification(`iCalendar Plug ${VERSION}`, "info");
}
}