diff --git a/Dockerfile.build b/Dockerfile.build index 41e9ed3..e13f67b 100644 --- a/Dockerfile.build +++ b/Dockerfile.build @@ -2,4 +2,4 @@ FROM denoland/deno:latest WORKDIR /app COPY . . RUN deno run -A https://github.com/silverbulletmd/silverbullet/releases/download/2.4.1/plug-compile.js -c deno.json icalendar.plug.yaml -CMD ["cat", "Library/sstent/icalendar.plug.js"] +CMD ["cat", "icalendar.plug.js"] diff --git a/icalendar.ts b/icalendar.ts index 1b70717..3d20eb1 100644 --- a/icalendar.ts +++ b/icalendar.ts @@ -1,7 +1,7 @@ import { clientStore, config, datastore, editor, index } from "@silverbulletmd/silverbullet/syscalls"; import { convertIcsCalendar } from "https://esm.sh/ts-ics@2.4.0"; -const VERSION = "0.3.19"; +const VERSION = "0.3.20"; const CACHE_KEY = "icalendar:lastSync"; console.log(`[iCalendar] Plug script executing at top level (Version ${VERSION})`); @@ -23,70 +23,118 @@ const TIMEZONE_OFFSETS: Record = { async function getSources(): Promise<{ sources: any[], tzShift: number }> { try { const rawConfig = await config.get("icalendar", { sources: [] }); - const sources = rawConfig.sources || []; - const tzShift = rawConfig.tzShift || 0; + console.log("[iCalendar] Raw config retrieved:", JSON.stringify(rawConfig)); + + let sources = rawConfig.sources || []; + let tzShift = rawConfig.tzShift || 0; + + // Handle common Space Lua structure where tzShift might be inside the sources table + if (sources && typeof sources === "object" && !Array.isArray(sources)) { + if (sources.tzShift !== undefined && tzShift === 0) { + tzShift = sources.tzShift; + console.log("[iCalendar] Found tzShift inside sources object:", tzShift); + } + // Convert map-like sources to array + const sourceArray = []; + for (const key in sources) { + if (sources[key] && typeof sources[key].url === "string") { + sourceArray.push(sources[key]); + } + } + sources = sourceArray; + } + + console.log(`[iCalendar] Final Sources: ${sources.length}, tzShift: ${tzShift}`); return { sources, tzShift }; } catch (e) { + console.error("[iCalendar] Error in getSources:", e); return { sources: [], tzShift: 0 }; } } async function fetchAndParseCalendar(source: any, hourShift = 0): Promise { - const response = await fetch(source.url); - if (!response.ok) return []; - const text = await response.text(); - const calendar = convertIcsCalendar(undefined, text); - if (!calendar.events) return []; - - const events: any[] = []; - for (const icsEvent of calendar.events) { - const obj = icsEvent.start; - let wallTimeStr = ""; - if (obj.local && typeof obj.local.date === "string") wallTimeStr = obj.local.date; - else if (typeof obj.date === "string") wallTimeStr = obj.date; - if (!wallTimeStr) continue; - - const baseDate = new Date(wallTimeStr.replace("Z", "") + "Z"); - const tzName = obj.local?.timezone || obj.timezone || "UTC"; - const sourceOffset = TIMEZONE_OFFSETS[tzName] ?? 0; - const utcMillis = baseDate.getTime() - (sourceOffset * 3600000); - const finalDate = new Date(utcMillis + (hourShift * 3600000)); + console.log(`[iCalendar] Fetching from: ${source.url}`); + try { + const response = await fetch(source.url); + if (!response.ok) { + console.error(`[iCalendar] Fetch failed for ${source.name}: ${response.status} ${response.statusText}`); + return []; + } + const text = await response.text(); + console.log(`[iCalendar] Received ICS data (${text.length} chars) from ${source.name}`); - const pad = (n: number) => String(n).padStart(2, "0"); - const localIso = finalDate.getFullYear() + "-" + pad(finalDate.getMonth() + 1) + "-" + pad(finalDate.getDate()) + "T" + pad(finalDate.getHours()) + ":" + pad(finalDate.getMinutes()) + ":" + pad(finalDate.getSeconds()); + // Log a snippet of the ICS to verify content + console.log(`[iCalendar] ICS snippet: ${text.substring(0, 200).replace(/\n/g, "\\n")}`); - events.push({ - ...icsEvent, - start: localIso, - tag: "ical-event", - sourceName: source.name - }); + const calendar = convertIcsCalendar(undefined, text); + if (!calendar || !calendar.events) { + console.warn(`[iCalendar] No events found in ICS from ${source.name} using ts-ics`); + return []; + } + + console.log(`[iCalendar] ts-ics found ${calendar.events.length} events in ${source.name}`); + + const events: any[] = []; + for (const icsEvent of calendar.events) { + const obj = icsEvent.start; + if (!obj) continue; + + let wallTimeStr = ""; + if (obj.local && typeof obj.local.date === "string") wallTimeStr = obj.local.date; + else if (typeof obj.date === "string") wallTimeStr = obj.date; + + if (!wallTimeStr) { + console.warn("[iCalendar] Skipping event with no start date string:", icsEvent.summary); + continue; + } + + const baseDate = new Date(wallTimeStr.replace("Z", "") + "Z"); + const tzName = obj.local?.timezone || obj.timezone || "UTC"; + const sourceOffset = TIMEZONE_OFFSETS[tzName] ?? 0; + const utcMillis = baseDate.getTime() - (sourceOffset * 3600000); + const finalDate = new Date(utcMillis + (hourShift * 3600000)); + + const pad = (n: number) => String(n).padStart(2, "0"); + const localIso = finalDate.getFullYear() + "-" + pad(finalDate.getMonth() + 1) + "-" + pad(finalDate.getDate()) + "T" + pad(finalDate.getHours()) + ":" + pad(finalDate.getMinutes()) + ":" + pad(finalDate.getSeconds()); + + events.push({ + ...icsEvent, + start: localIso, + tag: "ical-event", + sourceName: source.name + }); + } + return events; + } catch (err) { + console.error(`[iCalendar] Error fetching/parsing ${source.name}:`, err); + return []; } - return events; } export async function syncCalendars() { + console.log("[iCalendar] syncCalendars() started"); try { const { sources, tzShift } = await getSources(); - if (sources.length === 0) return; + if (sources.length === 0) { + console.log("[iCalendar] No sources to sync"); + return; + } await editor.flashNotification("Syncing calendars...", "info"); const allEvents: any[] = []; for (const source of sources) { - try { - const events = await fetchAndParseCalendar(source, tzShift); - allEvents.push(...events); - } catch (err) { - console.error(`Failed to sync ${source.name}:`, err); - } + const events = await fetchAndParseCalendar(source, tzShift); + allEvents.push(...events); } + console.log(`[iCalendar] Total events to index: ${allEvents.length}`); await index.indexObjects("$icalendar", allEvents); await editor.flashNotification(`Synced ${allEvents.length} events`, "info"); } catch (err) { - console.error("Sync failed:", err); + console.error("[iCalendar] syncCalendars failed:", err); } } export async function forceSync() { + console.log("[iCalendar] forceSync() triggered"); await clientStore.del(CACHE_KEY); await syncCalendars(); } @@ -106,4 +154,4 @@ export async function clearCache() { export async function showVersion() { await editor.flashNotification(`iCalendar Plug ${VERSION}`, "info"); -} \ No newline at end of file +}