import { createTrigger, AppConnectionValueForAuthProperty, Property, } from '@activepieces/pieces-framework'; import { TriggerStrategy } from '@activepieces/pieces-framework'; import { googleCalendarCommon, googleCalendarAuth, getAccessToken } from '../common'; import { GoogleCalendarEvent } from '../common/types'; import { DedupeStrategy, Polling, pollingHelper, } from '@activepieces/pieces-common'; import { AuthenticationType, httpClient, HttpMethod, HttpRequest, } from '@activepieces/pieces-common'; import { eventOutputSchema } from '../output-schemas'; interface GoogleCalendarEventList { items: GoogleCalendarEvent[]; } const polling: Polling< AppConnectionValueForAuthProperty, { calendar_id: string | undefined; search_term: string | undefined; event_types: string[] | undefined; search_fields: string[] | undefined; } > = { strategy: DedupeStrategy.TIMEBASED, items: async ({ auth, propsValue, lastFetchEpochMS }) => { const { calendar_id, search_term, event_types, search_fields } = propsValue; if (!calendar_id || !search_term) { return []; } let minUpdated: Date; if (lastFetchEpochMS === 0) { minUpdated = new Date(); minUpdated.setDate(minUpdated.getDate() - 1); } else { minUpdated = new Date(lastFetchEpochMS); } const queryParams: Record = { singleEvents: 'true', orderBy: 'updated', updatedMin: minUpdated.toISOString(), q: search_term, }; let url = `${googleCalendarCommon.baseUrl}/calendars/${calendar_id}/events`; if (event_types && event_types.length > 0) { url += `?${event_types .map((type) => `eventTypes=${encodeURIComponent(type)}`) .join('&')}`; } const request: HttpRequest = { method: HttpMethod.GET, url, authentication: { type: AuthenticationType.BEARER_TOKEN, token: await getAccessToken(auth), }, queryParams: queryParams, }; const response = await httpClient.sendRequest( request ); const events = response.body.items; const newEvents = events.filter((event) => { const created = new Date(event.created ?? 0).getTime(); const updated = new Date(event.updated ?? 0).getTime(); const isNewEvent = updated - created < 5000; if (!isNewEvent) return false; if (search_fields && search_fields.length > 0) { const searchTermLower = search_term.toLowerCase(); return search_fields.some((field) => { switch (field) { case 'summary': return ( event.summary?.toLowerCase().includes(searchTermLower) || false ); case 'description': return ( event.description?.toLowerCase().includes(searchTermLower) || false ); case 'location': return ( event.location?.toLowerCase().includes(searchTermLower) || false ); case 'attendees': return ( event.attendees?.some( (attendee) => attendee.email?.toLowerCase().includes(searchTermLower) || attendee.displayName ?.toLowerCase() .includes(searchTermLower) ) || false ); default: return false; } }); } return true; }); return newEvents.map((event) => { return { epochMilliSeconds: new Date(event.updated!).getTime(), data: event, }; }); }, }; export const newEventMatchingSearch = createTrigger({ auth: googleCalendarAuth, name: 'new_event_matching_search', classification: 'READ', displayName: 'New Event Matching Search', description: 'Fires when a new event is created that matches a specified search term.', aiMetadata: { description: 'Fires when a newly created event in the selected calendar matches a required search term (matched across title, description, location, and attendees by default, or restricted to chosen fields). Each fired item is the matching new event; can also be filtered by event type.', }, props: { calendar_id: googleCalendarCommon.calendarDropdown('writer'), search_term: Property.ShortText({ displayName: 'Search Term', description: 'Matched against title, description, location and guests.', required: true, }), event_types: Property.StaticMultiSelectDropdown({ displayName: 'Event Types', description: 'Leave empty to include every type.', required: false, advanced: true, options: { options: [ { label: 'Default', value: 'default' }, { label: 'Birthday', value: 'birthday' }, { label: 'Focus Time', value: 'focusTime' }, { label: 'Out of Office', value: 'outOfOffice' }, { label: 'Working Location', value: 'workingLocation' }, { label: 'From Gmail', value: 'fromGmail' }, ], }, }), search_fields: Property.StaticMultiSelectDropdown({ displayName: 'Search In', description: 'Leave empty to search every field.', required: false, advanced: true, options: { options: [ { label: 'Event Title/Summary', value: 'summary' }, { label: 'Event Description', value: 'description' }, { label: 'Event Location', value: 'location' }, { label: 'Attendee Names/Emails', value: 'attendees' }, ], }, }), }, outputSchema: eventOutputSchema, type: TriggerStrategy.POLLING, sampleData: { id: 'abc123def456', summary: 'Final Project Review', description: 'Review of the Q3 final project deliverables.', status: 'confirmed', created: '2025-08-14T09:05:00.000Z', updated: '2025-08-14T09:05:01.000Z', start: { dateTime: '2025-09-01T10:00:00-07:00' }, end: { dateTime: '2025-09-01T11:30:00-07:00' }, organizer: { email: 'project.manager@example.com' }, }, async onEnable(context) { await pollingHelper.onEnable(polling, { auth: context.auth, store: context.store, propsValue: context.propsValue, }); }, async onDisable(context) { await pollingHelper.onDisable(polling, { auth: context.auth, store: context.store, propsValue: context.propsValue, }); }, async run(context) { return await pollingHelper.poll(polling, { auth: context.auth, store: context.store, propsValue: context.propsValue, files: context.files, }); }, async test(context) { return await pollingHelper.test(polling, { auth: context.auth, store: context.store, propsValue: context.propsValue, files: context.files, }); }, });