import { listEventsAcrossCalendars } from 'kody:@kody/google/calendar' import { dayBounds, skippedSection, timeOnly } from '../format.ts' import { googleCalendarSetupError } from '../google-setup.ts' import type { BriefingInput, BriefingItem, BriefingSection } from '../types.ts' type CalendarDateTime = { date?: string; dateTime?: string } type CalendarEvent = { summary?: string start?: CalendarDateTime end?: CalendarDateTime htmlLink?: string recurringEventId?: string recurrence?: string[] calendarSummary?: string calendarId?: string accountLabel?: string } type CalendarListResult = { items?: CalendarEvent[] calendars?: unknown[] failures?: string[] truncated?: boolean truncatedReason?: 'budget' | null } function isRecurring(event: CalendarEvent): boolean { return Boolean(event.recurringEventId) || Boolean(event.recurrence?.length) } function eventSortKey(event: CalendarEvent): string { return event.start?.dateTime || event.start?.date || '' } function formatWhen(event: CalendarEvent, timezone: string): string { if (event.start?.date) return 'All day' const start = timeOnly(event.start?.dateTime, timezone) const end = timeOnly(event.end?.dateTime, timezone) return end ? `${start}–${end}` : start } export async function calendarSection( input: BriefingInput, date: string, timezone: string, ): Promise { const accounts = input.calendar?.accounts?.length ? input.calendar.accounts.map((account) => String(account).trim()).filter(Boolean) : ['personal'] if (!accounts.length) { return skippedSection( 'calendar', 'Pass calendar.accounts (for example ["personal"]) after connecting Google OAuth.', ) } const { start, end } = dayBounds(date, timezone) const events: CalendarEvent[] = [] const failures: string[] = [] const results = await Promise.all( accounts.map(async (account) => { try { const result = (await listEventsAcrossCalendars({ account, timeMin: start, timeMax: end, singleEvents: true, orderBy: 'startTime', maxResults: 100, calendarMaxResults: 100, showHiddenCalendars: true, })) as CalendarListResult return { account, result, error: null as unknown } } catch (error) { return { account, result: null, error: googleCalendarSetupError(error, account) } } }), ) for (const { account, result, error } of results) { if (!result) { failures.push(error instanceof Error ? error.message : String(error)) continue } for (const failure of result.failures || []) { failures.push(`${account}: ${failure}`) } if (result.truncated) { failures.push( `${account}: calendar scan stopped early (${result.truncatedReason ?? 'budget'}); this section may be missing later calendars`, ) } for (const event of result.items || []) { events.push({ ...event, accountLabel: account }) } } const unusual = events .filter((event) => !isRecurring(event)) .sort((a, b) => eventSortKey(a).localeCompare(eventSortKey(b))) const items: BriefingItem[] = unusual.slice(0, 10).map((event) => ({ title: event.summary || 'Calendar event', detail: `${event.accountLabel || 'calendar'} · ${event.calendarSummary || 'Primary'} · ${formatWhen(event, timezone)}`, label: event.start?.date ? 'all-day' : timeOnly(event.start?.dateTime, timezone), severity: 'notice', href: event.htmlLink, })) for (const failure of failures) { items.push({ title: 'Calendar source warning', detail: failure, severity: 'warning', }) } const status = failures.length && !unusual.length ? 'warning' : unusual.length ? 'ok' : failures.length ? 'warning' : 'empty' return { id: 'calendar', title: 'Calendar', status, summary: unusual.length ? `${unusual.length} non-recurring event${unusual.length === 1 ? '' : 's'} today` : failures.length ? 'Calendar auth or listing failed' : 'No non-recurring events today', items, } }