Add Activepieces integration for workflow automation
- Add Activepieces fork with SmoothSchedule custom piece - Create integrations app with Activepieces service layer - Add embed token endpoint for iframe integration - Create Automations page with embedded workflow builder - Add sidebar visibility fix for embed mode - Add list inactive customers endpoint to Public API - Include SmoothSchedule triggers: event created/updated/cancelled - Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { google, calendar_v3 } from 'googleapis';
|
||||
import { OAuth2Client } from 'googleapis-common';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
|
||||
export const addAttendeesToEventAction = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'google-calendar-add-attendees',
|
||||
displayName: 'Add Attendees to Event',
|
||||
description: 'Add one or more person to existing event.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
eventId: Property.ShortText({
|
||||
displayName: 'Event ID',
|
||||
required: true,
|
||||
}),
|
||||
attendees: Property.Array({
|
||||
displayName: 'Attendees',
|
||||
description: 'Emails of the attendees (guests)',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { calendar_id, eventId } = context.propsValue;
|
||||
const attendeesInput = context.propsValue.attendees as string[];
|
||||
|
||||
const authClient = new OAuth2Client();
|
||||
authClient.setCredentials(context.auth);
|
||||
const calendar = google.calendar({ version: 'v3', auth: authClient });
|
||||
|
||||
// Note that each patch request consumes three quota units;
|
||||
// prefer using a get followed by an update
|
||||
const currentEvent = await calendar.events.get({
|
||||
calendarId: calendar_id,
|
||||
eventId: eventId,
|
||||
});
|
||||
const currentAttendees = currentEvent.data.attendees ?? [];
|
||||
|
||||
const attendeeFormattedList: calendar_v3.Schema$EventAttendee[] = [];
|
||||
attendeeFormattedList.push(...currentAttendees);
|
||||
attendeeFormattedList.push(...attendeesInput.map((email) => ({ email })));
|
||||
|
||||
const response = await calendar.events.update({
|
||||
calendarId: calendar_id!,
|
||||
eventId,
|
||||
requestBody: {
|
||||
...currentEvent.data,
|
||||
attendees: attendeeFormattedList,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { googleCalendarAuth } from '../../index';
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { OAuth2Client } from 'googleapis-common';
|
||||
import { google } from 'googleapis';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
|
||||
export const addCalendarToCalendarlist = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'addCalendarToCalendarlist',
|
||||
displayName: 'Add Calendar to calendarList',
|
||||
description: "Adds other people's calendars to your calendarList",
|
||||
props: {
|
||||
id: Property.ShortText({
|
||||
displayName: "Calendar Id",
|
||||
description: "Find calendar id by going to calendar settings",
|
||||
required: true
|
||||
})
|
||||
},
|
||||
async run(context) {
|
||||
|
||||
const id = context.propsValue.id;
|
||||
|
||||
const authClient = new OAuth2Client();
|
||||
authClient.setCredentials(context.auth);
|
||||
|
||||
const calendar = google.calendar({ version: 'v3', auth: authClient});
|
||||
|
||||
const response = await calendar.calendarList.insert({
|
||||
requestBody: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import dayjs from 'dayjs';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import { google } from 'googleapis';
|
||||
import { OAuth2Client } from 'googleapis-common';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
export const createEvent = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'create_google_calendar_event',
|
||||
description: 'Add Event',
|
||||
displayName: 'Create Event',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
title: Property.ShortText({
|
||||
displayName: 'Title of the event',
|
||||
required: true,
|
||||
}),
|
||||
start_date_time: Property.DateTime({
|
||||
displayName: 'Start date time of the event',
|
||||
required: true,
|
||||
}),
|
||||
end_date_time: Property.DateTime({
|
||||
displayName: 'End date time of the event',
|
||||
description: "By default it'll be 30 min post start time",
|
||||
required: false,
|
||||
}),
|
||||
location: Property.ShortText({
|
||||
displayName: 'Location',
|
||||
required: false,
|
||||
}),
|
||||
/*attachment: Property.ShortText({
|
||||
displayName: 'Attachment',
|
||||
description: 'URL of the file to be attached',
|
||||
required: false,
|
||||
}),*/
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
description: 'Description of the event. You can use HTML tags here.',
|
||||
required: false,
|
||||
}),
|
||||
colorId: googleCalendarCommon.colorId,
|
||||
attendees: Property.Array({
|
||||
displayName: 'Attendees',
|
||||
description: 'Emails of the attendees (guests)',
|
||||
required: false,
|
||||
}),
|
||||
guests_can_modify: Property.Checkbox({
|
||||
displayName: 'Guests can modify',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
guests_can_invite_others: Property.Checkbox({
|
||||
displayName: 'Guests can invite others',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
guests_can_see_other_guests: Property.Checkbox({
|
||||
displayName: 'Guests can see other guests',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
send_notifications: Property.StaticDropdown({
|
||||
displayName: 'Send Notifications',
|
||||
defaultValue: 'all',
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Yes, to everyone', value: 'all' },
|
||||
{
|
||||
label: 'To non-Google Calendar guests only',
|
||||
value: 'externalOnly',
|
||||
},
|
||||
{ label: 'To no one', value: 'none' },
|
||||
],
|
||||
},
|
||||
required: true,
|
||||
}),
|
||||
create_meet_link: Property.Checkbox({
|
||||
displayName: 'Create Google Meet Link',
|
||||
description: 'Automatically create a Google Meet video conference link for this event',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(configValue) {
|
||||
// docs: https://developers.google.com/calendar/api/v3/reference/events/insert
|
||||
const {
|
||||
calendar_id: calendarId,
|
||||
title: summary,
|
||||
start_date_time,
|
||||
end_date_time,
|
||||
location,
|
||||
description,
|
||||
colorId,
|
||||
guests_can_modify: guestsCanModify,
|
||||
guests_can_invite_others: guestsCanInviteOthers,
|
||||
guests_can_see_other_guests: guestsCanSeeOtherGuests,
|
||||
create_meet_link: createMeetLink,
|
||||
} = configValue.propsValue;
|
||||
|
||||
const start = {
|
||||
dateTime: dayjs(start_date_time).format('YYYY-MM-DDTHH:mm:ss.sssZ'),
|
||||
};
|
||||
const endTime = end_date_time
|
||||
? end_date_time
|
||||
: dayjs(start_date_time).add(30, 'm');
|
||||
const end = {
|
||||
dateTime: dayjs(endTime).format('YYYY-MM-DDTHH:mm:ss.sssZ'),
|
||||
};
|
||||
|
||||
/*const attachment = {
|
||||
fileUrl: configValue.propsValue.attachment,
|
||||
};*/
|
||||
|
||||
const attendeesArray = configValue.propsValue.attendees as string[];
|
||||
|
||||
const sendNotifications = configValue.propsValue.send_notifications;
|
||||
|
||||
const attendeesObject = [];
|
||||
if (attendeesArray) {
|
||||
for (const attendee of attendeesArray) {
|
||||
attendeesObject.push({ email: attendee });
|
||||
}
|
||||
}
|
||||
|
||||
const authClient = new OAuth2Client();
|
||||
authClient.setCredentials(configValue.auth);
|
||||
|
||||
const calendar = google.calendar({ version: 'v3', auth: authClient });
|
||||
|
||||
const requestBody: any = {
|
||||
summary,
|
||||
start,
|
||||
end,
|
||||
colorId,
|
||||
//attachments: configValue.propsValue.attachment ? [attachment] : [],
|
||||
location: location ?? '',
|
||||
description: description ?? '',
|
||||
attendees: attendeesObject,
|
||||
guestsCanInviteOthers,
|
||||
guestsCanModify,
|
||||
guestsCanSeeOtherGuests,
|
||||
};
|
||||
|
||||
if (createMeetLink) {
|
||||
requestBody.conferenceData = {
|
||||
createRequest: {
|
||||
conferenceSolutionKey: {
|
||||
type: 'hangoutsMeet',
|
||||
},
|
||||
requestId: randomUUID(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await calendar.events.insert({
|
||||
calendarId,
|
||||
sendUpdates: sendNotifications,
|
||||
conferenceDataVersion: createMeetLink ? 1 : 0,
|
||||
requestBody,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpMethod,
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
|
||||
export const createQuickCalendarEvent = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'create_quick_event',
|
||||
description: 'Add Quick Calendar Event',
|
||||
displayName: 'Create Quick Event',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
text: Property.LongText({
|
||||
displayName: 'Summary',
|
||||
description: 'The text describing the event to be created',
|
||||
required: true,
|
||||
}),
|
||||
send_updates: Property.StaticDropdown<string>({
|
||||
displayName: 'Send Updates',
|
||||
description:
|
||||
'Guests who should receive notifications about the creation of the new event.',
|
||||
required: false,
|
||||
options: {
|
||||
disabled: false,
|
||||
options: [
|
||||
{
|
||||
label: 'All',
|
||||
value: 'all',
|
||||
},
|
||||
{
|
||||
label: 'External Only',
|
||||
value: 'externalOnly',
|
||||
},
|
||||
{
|
||||
label: 'none',
|
||||
value: 'none',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
async run(configValue) {
|
||||
// docs: https://developers.google.com/calendar/api/v3/reference/events/quickAdd
|
||||
const calendarId = configValue.propsValue['calendar_id'];
|
||||
const url = `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/quickAdd`;
|
||||
const qParams: Record<string, string> = {
|
||||
text: configValue.propsValue['text'],
|
||||
sendUpdates: configValue.propsValue['send_updates'] || 'none',
|
||||
};
|
||||
const request: HttpRequest<Record<string, unknown>> = {
|
||||
method: HttpMethod.POST,
|
||||
url,
|
||||
body: {},
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: configValue.auth.access_token,
|
||||
},
|
||||
queryParams: qParams,
|
||||
};
|
||||
return await httpClient.sendRequest(request);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { google } from 'googleapis';
|
||||
import { OAuth2Client } from 'googleapis-common';
|
||||
import { googleCalendarAuth } from '../../index';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
|
||||
export const deleteEventAction = createAction({
|
||||
displayName: 'Delete Event',
|
||||
auth: googleCalendarAuth,
|
||||
name: 'delete_event',
|
||||
description: 'Deletes an event from Google Calendar.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
eventId: Property.ShortText({
|
||||
displayName: 'Event ID',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const authClient = new OAuth2Client();
|
||||
authClient.setCredentials(context.auth);
|
||||
|
||||
const calendarId = context.propsValue.calendar_id;
|
||||
const eventId = context.propsValue.eventId;
|
||||
|
||||
const calendar = google.calendar({ version: 'v3', auth: authClient });
|
||||
|
||||
const response = await calendar.events.delete({
|
||||
calendarId,
|
||||
eventId,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { createAction, Property, OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import { HttpRequest, HttpMethod, AuthenticationType, httpClient } from '@activepieces/pieces-common';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { getCalendars } from '../common/helper';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
|
||||
interface FreeBusyResponse {
|
||||
kind: 'calendar#freeBusy';
|
||||
timeMin: string;
|
||||
timeMax: string;
|
||||
calendars: {
|
||||
[calendarId: string]: {
|
||||
busy: {
|
||||
start: string;
|
||||
end: string;
|
||||
}[];
|
||||
errors?: {
|
||||
domain: string;
|
||||
reason: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const findFreeBusy = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'google_calendar_find_busy_free_periods',
|
||||
displayName: 'Find Busy/Free Periods in Calendar',
|
||||
description: 'Finds free/busy calendar details from Google Calendar.',
|
||||
props: {
|
||||
calendar_ids: Property.MultiSelectDropdown({
|
||||
auth: googleCalendarAuth,
|
||||
displayName: 'Calendars',
|
||||
description: 'Select the calendars to check for busy periods.',
|
||||
required: true,
|
||||
refreshers: [],
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const authProp = auth as OAuth2PropertyValue;
|
||||
const calendars = await getCalendars(authProp);
|
||||
return {
|
||||
disabled: false,
|
||||
options: calendars.map((calendar) => {
|
||||
return {
|
||||
label: calendar.summary,
|
||||
value: calendar.id,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
}),
|
||||
start_date: Property.DateTime({
|
||||
displayName: 'Start Time',
|
||||
description: 'The start of the time range to check.',
|
||||
required: true,
|
||||
}),
|
||||
end_date: Property.DateTime({
|
||||
displayName: 'End Time',
|
||||
description: 'The end of the time range to check.',
|
||||
required: true,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const { calendar_ids, start_date, end_date } = context.propsValue;
|
||||
const { access_token } = context.auth;
|
||||
|
||||
const requestBody = {
|
||||
|
||||
timeMin: dayjs(start_date).toISOString(),
|
||||
timeMax: dayjs(end_date).toISOString(),
|
||||
|
||||
items: calendar_ids.map((id) => ({ id })),
|
||||
};
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `${googleCalendarCommon.baseUrl}/freeBusy`,
|
||||
body: requestBody,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: access_token,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<FreeBusyResponse>(request);
|
||||
|
||||
|
||||
return response.body;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpMethod,
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import { GoogleCalendarEvent } from '../common/types';
|
||||
|
||||
export const getEventById = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'google_calendar_get_event_by_id',
|
||||
displayName: 'Get Event by ID',
|
||||
description: 'Fetch event details by its unique ID from Google Calendar.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown(),
|
||||
event_id: Property.ShortText({
|
||||
displayName: 'Event ID',
|
||||
description:
|
||||
'The unique ID of the event (e.g., "abc123def456"). You can find this in the event URL or from other calendar actions.',
|
||||
required: true,
|
||||
}),
|
||||
max_attendees: Property.Number({
|
||||
displayName: 'Max Attendees',
|
||||
description:
|
||||
'Maximum number of attendees to include in the response. If there are more attendees, only the participant is returned.',
|
||||
required: false,
|
||||
}),
|
||||
time_zone: Property.ShortText({
|
||||
displayName: 'Time Zone',
|
||||
description:
|
||||
'Time zone for the response (e.g., "America/New_York", "Europe/London"). Defaults to the calendar\'s time zone if not specified.',
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
calendar_id: calendarId,
|
||||
event_id: eventId,
|
||||
max_attendees: maxAttendees,
|
||||
time_zone: timeZone,
|
||||
} = context.propsValue;
|
||||
const { access_token: token } = context.auth;
|
||||
|
||||
if (
|
||||
!calendarId ||
|
||||
typeof calendarId !== 'string' ||
|
||||
calendarId.trim().length === 0
|
||||
) {
|
||||
throw new Error('Calendar ID is required');
|
||||
}
|
||||
|
||||
if (
|
||||
!eventId ||
|
||||
typeof eventId !== 'string' ||
|
||||
eventId.trim().length === 0
|
||||
) {
|
||||
throw new Error('Event ID cannot be empty');
|
||||
}
|
||||
|
||||
if (eventId.length < 5 || eventId.length > 1024) {
|
||||
throw new Error('Event ID must be between 5 and 1024 characters');
|
||||
}
|
||||
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
if (maxAttendees !== undefined && maxAttendees > 0) {
|
||||
queryParams.maxAttendees = maxAttendees.toString();
|
||||
}
|
||||
|
||||
if (
|
||||
timeZone &&
|
||||
typeof timeZone === 'string' &&
|
||||
timeZone.trim().length > 0
|
||||
) {
|
||||
queryParams.timeZone = timeZone.trim();
|
||||
}
|
||||
|
||||
const url = `${googleCalendarCommon.baseUrl}/calendars/${encodeURIComponent(
|
||||
calendarId.trim()
|
||||
)}/events/${encodeURIComponent(eventId.trim())}`;
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: url,
|
||||
queryParams: queryParams,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: token,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await httpClient.sendRequest<GoogleCalendarEvent>(
|
||||
request
|
||||
);
|
||||
return response.body;
|
||||
} catch (error: any) {
|
||||
if (error.response?.status === 404) {
|
||||
throw new Error(
|
||||
`Event with ID "${eventId}" not found in calendar "${calendarId}". Please verify the event ID and calendar selection.`
|
||||
);
|
||||
} else if (error.response?.status === 403) {
|
||||
throw new Error(
|
||||
`Access denied to event "${eventId}" in calendar "${calendarId}". Please check your permissions.`
|
||||
);
|
||||
} else if (error.response?.status === 400) {
|
||||
throw new Error(
|
||||
`Invalid request parameters. Please check the event ID format and other parameters.`
|
||||
);
|
||||
} else if (error.response?.status === 401) {
|
||||
throw new Error(
|
||||
'Authentication failed. Please reconnect your Google Calendar account.'
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Failed to fetch event: ${error.message || 'Unknown error occurred'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createAction, Property } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
HttpRequest,
|
||||
HttpMethod,
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import dayjs from 'dayjs';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
|
||||
export const getEvents = createAction({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'google_calendar_get_events',
|
||||
description: 'Get Events',
|
||||
displayName: 'Get all Events',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
event_types: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Event types',
|
||||
description: 'Select event types',
|
||||
required: true,
|
||||
defaultValue: ['default', 'focusTime', 'outOfOffice'],
|
||||
options: {
|
||||
options: [
|
||||
{
|
||||
label: 'Default',
|
||||
value: 'default',
|
||||
},
|
||||
{
|
||||
label: 'Out Of Office',
|
||||
value: 'outOfOffice',
|
||||
},
|
||||
{
|
||||
label: 'Focus Time',
|
||||
value: 'focusTime',
|
||||
},
|
||||
{
|
||||
label: 'Working Location',
|
||||
value: 'workingLocation',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
search: Property.ShortText({
|
||||
displayName: 'Search Term',
|
||||
required: false,
|
||||
}),
|
||||
start_date: Property.DateTime({
|
||||
displayName: 'Date from',
|
||||
required: false,
|
||||
}),
|
||||
end_date: Property.DateTime({
|
||||
displayName: 'Date to',
|
||||
required: false,
|
||||
}),
|
||||
singleEvents: Property.Checkbox({
|
||||
displayName: 'Expand Recurring Event?',
|
||||
description: "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves.",
|
||||
required: true,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
async run(configValue) {
|
||||
// docs: https://developers.google.com/calendar/api/v3/reference/events/list
|
||||
const {
|
||||
calendar_id: calendarId,
|
||||
start_date,
|
||||
end_date,
|
||||
search,
|
||||
event_types,
|
||||
singleEvents,
|
||||
} = configValue.propsValue;
|
||||
const { access_token: token } = configValue.auth;
|
||||
const queryParams: Record<string, string> = { showDeleted: 'false' };
|
||||
let url = `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events`;
|
||||
|
||||
if(singleEvents !== null) {
|
||||
queryParams['singleEvents'] = singleEvents ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (search) {
|
||||
queryParams['q'] = `"${search}"`;
|
||||
}
|
||||
|
||||
// date range
|
||||
if (start_date) {
|
||||
queryParams['timeMin'] = dayjs(start_date).format(
|
||||
'YYYY-MM-DDTHH:mm:ss.sssZ'
|
||||
);
|
||||
}
|
||||
if (start_date && end_date) {
|
||||
queryParams['timeMax'] = dayjs(end_date).format(
|
||||
'YYYY-MM-DDTHH:mm:ss.sssZ'
|
||||
);
|
||||
}
|
||||
// filter by event type
|
||||
if (event_types.length > 0) {
|
||||
url += `?${event_types.map((type) => `eventTypes=${type}`).join('&')}`;
|
||||
}
|
||||
const request: HttpRequest<Record<string, unknown>> = {
|
||||
method: HttpMethod.GET,
|
||||
url,
|
||||
queryParams,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token,
|
||||
},
|
||||
};
|
||||
return await httpClient.sendRequest(request);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
import { Property, createAction } from '@activepieces/pieces-framework';
|
||||
import { google, calendar_v3 } from 'googleapis';
|
||||
import { OAuth2Client } from 'googleapis-common';
|
||||
import { googleCalendarAuth } from '../../index';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export const updateEventAction = createAction({
|
||||
displayName: 'Update Event',
|
||||
auth: googleCalendarAuth,
|
||||
name: 'update_event',
|
||||
description: 'Updates an event in Google Calendar.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
eventId: Property.ShortText({
|
||||
displayName: 'Event ID',
|
||||
required: true,
|
||||
}),
|
||||
title: Property.ShortText({
|
||||
displayName: 'Title of the event',
|
||||
required: false,
|
||||
}),
|
||||
start_date_time: Property.DateTime({
|
||||
displayName: 'Start date time of the event',
|
||||
required: false,
|
||||
}),
|
||||
end_date_time: Property.DateTime({
|
||||
displayName: 'End date time of the event',
|
||||
required: false,
|
||||
}),
|
||||
location: Property.ShortText({
|
||||
displayName: 'Location',
|
||||
required: false,
|
||||
}),
|
||||
description: Property.LongText({
|
||||
displayName: 'Description',
|
||||
description: 'Description of the event. You can use HTML tags here.',
|
||||
required: false,
|
||||
}),
|
||||
colorId: googleCalendarCommon.colorId,
|
||||
attendees: Property.Array({
|
||||
displayName: 'Attendees',
|
||||
description: 'Emails of the attendees (guests)',
|
||||
required: false,
|
||||
}),
|
||||
guests_can_modify: Property.Checkbox({
|
||||
displayName: 'Guests can modify',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
guests_can_invite_others: Property.Checkbox({
|
||||
displayName: 'Guests can invite others',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
guests_can_see_other_guests: Property.Checkbox({
|
||||
displayName: 'Guests can see other guests',
|
||||
defaultValue: false,
|
||||
required: false,
|
||||
}),
|
||||
},
|
||||
async run(context) {
|
||||
const {
|
||||
calendar_id,
|
||||
eventId,
|
||||
title,
|
||||
start_date_time,
|
||||
end_date_time,
|
||||
location,
|
||||
description,
|
||||
colorId,
|
||||
guests_can_invite_others,
|
||||
guests_can_modify,
|
||||
guests_can_see_other_guests,
|
||||
} = context.propsValue;
|
||||
|
||||
const attendees = context.propsValue.attendees as string[];
|
||||
|
||||
const authClient = new OAuth2Client();
|
||||
authClient.setCredentials(context.auth);
|
||||
const calendar = google.calendar({ version: 'v3', auth: authClient });
|
||||
|
||||
// Note that each patch request consumes three quota units;
|
||||
// prefer using a get followed by an update
|
||||
const currentEvent = await calendar.events.get({
|
||||
calendarId: calendar_id,
|
||||
eventId: eventId,
|
||||
});
|
||||
|
||||
let attendeeFormattedList: calendar_v3.Schema$EventAttendee[] = [];
|
||||
if (Array.isArray(attendees) && attendees.length > 0) {
|
||||
attendeeFormattedList = attendees.map((email) => ({ email }));
|
||||
} else if (
|
||||
currentEvent.data.attendees &&
|
||||
Array.isArray(currentEvent.data.attendees)
|
||||
) {
|
||||
attendeeFormattedList = currentEvent.data.attendees;
|
||||
}
|
||||
|
||||
const response = await calendar.events.update({
|
||||
calendarId: calendar_id,
|
||||
eventId: eventId,
|
||||
requestBody: {
|
||||
summary: title ?? currentEvent.data.summary,
|
||||
attendees: attendeeFormattedList,
|
||||
description: description ?? currentEvent.data.description,
|
||||
colorId: colorId,
|
||||
location: location ?? currentEvent.data.location,
|
||||
start: start_date_time
|
||||
? {
|
||||
dateTime: dayjs(start_date_time).format(
|
||||
'YYYY-MM-DDTHH:mm:ss.sssZ'
|
||||
),
|
||||
}
|
||||
: currentEvent.data.start,
|
||||
end: end_date_time
|
||||
? {
|
||||
dateTime: dayjs(end_date_time).format('YYYY-MM-DDTHH:mm:ss.sssZ'),
|
||||
}
|
||||
: currentEvent.data.end,
|
||||
guestsCanInviteOthers: guests_can_invite_others,
|
||||
guestsCanModify: guests_can_modify,
|
||||
guestsCanSeeOtherGuests: guests_can_see_other_guests,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,228 @@
|
||||
import { OAuth2PropertyValue } from '@activepieces/pieces-framework';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { googleCalendarCommon } from '.';
|
||||
import {
|
||||
GoogleWatchResponse,
|
||||
GoogleWatchType,
|
||||
CalendarObject,
|
||||
CalendarList,
|
||||
GoogleCalendarEvent,
|
||||
GoogleCalendarEventList,
|
||||
GetColorsResponse,
|
||||
} from './types';
|
||||
|
||||
export async function stopWatchEvent(
|
||||
body: GoogleWatchResponse,
|
||||
authProp: OAuth2PropertyValue
|
||||
) {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `${googleCalendarCommon.baseUrl}/channels/stop`,
|
||||
body: {
|
||||
id: body?.id,
|
||||
resourceId: body?.resourceId,
|
||||
},
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authProp.access_token,
|
||||
},
|
||||
};
|
||||
await httpClient.sendRequest<any>(request);
|
||||
}
|
||||
|
||||
export async function watchEvent(
|
||||
calendarId: string,
|
||||
webhookUrl: string,
|
||||
authProp: OAuth2PropertyValue
|
||||
): Promise<GoogleWatchResponse> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.POST,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/watch`,
|
||||
body: {
|
||||
id: randomUUID(),
|
||||
type: GoogleWatchType.WEBHOOK,
|
||||
address: webhookUrl,
|
||||
},
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authProp.access_token,
|
||||
},
|
||||
};
|
||||
const { body: webhook } = await httpClient.sendRequest<GoogleWatchResponse>(
|
||||
request
|
||||
);
|
||||
return webhook;
|
||||
}
|
||||
|
||||
export async function getCalendars(
|
||||
authProp: OAuth2PropertyValue,
|
||||
minAccessRole?: 'writer'
|
||||
): Promise<CalendarObject[]> {
|
||||
// docs: https://developers.google.com/calendar/api/v3/reference/calendarList/list
|
||||
const queryParams: Record<string, string> = {
|
||||
showDeleted: 'false',
|
||||
};
|
||||
if (minAccessRole) {
|
||||
queryParams['minAccessRole'] = minAccessRole;
|
||||
}
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/users/me/calendarList`,
|
||||
queryParams: queryParams,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authProp.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<CalendarList>(request);
|
||||
return response.body.items;
|
||||
}
|
||||
|
||||
export async function getColors(
|
||||
authProp: OAuth2PropertyValue
|
||||
): Promise<GetColorsResponse> {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/colors`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authProp.access_token,
|
||||
},
|
||||
};
|
||||
const response = await httpClient.sendRequest<GetColorsResponse>(request);
|
||||
return response.body;
|
||||
}
|
||||
|
||||
export async function getEvents(
|
||||
calendarId: string,
|
||||
expandRecurringEvent: boolean,
|
||||
authProp: OAuth2PropertyValue,
|
||||
minUpdated?: Date
|
||||
): Promise<GoogleCalendarEvent[]> {
|
||||
// docs: https://developers.google.com/calendar/api/v3/reference/events/list
|
||||
const now = new Date();
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(now.getDate() - 1);
|
||||
|
||||
const qParams: Record<string, string> = {
|
||||
updatedMin: minUpdated?.toISOString() ?? yesterday.toISOString(),
|
||||
maxResults: '2500', // Modified
|
||||
orderBy: 'updated',
|
||||
singleEvents: expandRecurringEvent ? 'true' : 'false',
|
||||
showDeleted: 'true',
|
||||
};
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events`,
|
||||
queryParams: qParams,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authProp.access_token,
|
||||
},
|
||||
};
|
||||
|
||||
let eventList: GoogleCalendarEvent[] = [];
|
||||
let pageToken = '';
|
||||
do {
|
||||
qParams['pageToken'] = pageToken;
|
||||
const { body: res } = await httpClient.sendRequest<GoogleCalendarEventList>(
|
||||
request
|
||||
);
|
||||
if (res.items.length > 0) {
|
||||
eventList = [...eventList, ...res.items];
|
||||
}
|
||||
pageToken = res.nextPageToken;
|
||||
} while (pageToken);
|
||||
|
||||
return eventList;
|
||||
}
|
||||
|
||||
export async function getLatestEvent(
|
||||
calendarId: string,
|
||||
authProp: OAuth2PropertyValue
|
||||
): Promise<GoogleCalendarEvent> {
|
||||
const eventList = await getEvents(calendarId, false, authProp);
|
||||
const lastUpdatedEvent = eventList.pop()!; // You can retrieve the last updated event.
|
||||
return lastUpdatedEvent;
|
||||
}
|
||||
|
||||
export async function getEventsForDropdown(
|
||||
authProp: OAuth2PropertyValue,
|
||||
calendarId?: string,
|
||||
maxResults = 50
|
||||
): Promise<{ label: string; value: string }[]> {
|
||||
if (!calendarId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const now = new Date();
|
||||
const futureDate = new Date();
|
||||
futureDate.setDate(now.getDate() + 30);
|
||||
|
||||
const queryParams: Record<string, string> = {
|
||||
singleEvents: 'true',
|
||||
orderBy: 'startTime',
|
||||
timeMin: now.toISOString(),
|
||||
timeMax: futureDate.toISOString(),
|
||||
maxResults: maxResults.toString(),
|
||||
showDeleted: 'false',
|
||||
};
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${encodeURIComponent(
|
||||
calendarId
|
||||
)}/events`,
|
||||
queryParams: queryParams,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: authProp.access_token,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<GoogleCalendarEventList>(
|
||||
request
|
||||
);
|
||||
|
||||
if (!response.body.items || response.body.items.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.body.items
|
||||
.map((event) => {
|
||||
const startTime = event.start?.dateTime || event.start?.date || '';
|
||||
const startDate = startTime
|
||||
? new Date(startTime).toLocaleDateString()
|
||||
: '';
|
||||
const startTimeFormatted = startTime
|
||||
? new Date(startTime).toLocaleTimeString()
|
||||
: '';
|
||||
|
||||
let label = event.summary || 'Untitled Event';
|
||||
if (startDate) {
|
||||
label += ` (${startDate}`;
|
||||
if (event.start?.dateTime) {
|
||||
label += ` at ${startTimeFormatted}`;
|
||||
}
|
||||
label += ')';
|
||||
}
|
||||
|
||||
return {
|
||||
label: label,
|
||||
value: event.id || '',
|
||||
};
|
||||
})
|
||||
.filter((item) => item.value !== '');
|
||||
} catch (error) {
|
||||
console.error('Error fetching events for dropdown:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { OAuth2PropertyValue, Property } from '@activepieces/pieces-framework';
|
||||
import { getCalendars, getColors, getEventsForDropdown } from './helper';
|
||||
import { googleCalendarAuth } from '../..';
|
||||
|
||||
export const googleCalendarCommon = {
|
||||
baseUrl: 'https://www.googleapis.com/calendar/v3',
|
||||
calendarDropdown: (minAccessRole?: 'writer') => {
|
||||
return Property.Dropdown<string,true,typeof googleCalendarAuth>({
|
||||
auth: googleCalendarAuth,
|
||||
displayName: 'Calendar',
|
||||
refreshers: [],
|
||||
required: true,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const authProp = auth as OAuth2PropertyValue;
|
||||
const calendars = await getCalendars(authProp, minAccessRole);
|
||||
return {
|
||||
disabled: false,
|
||||
options: calendars.map((calendar) => {
|
||||
return {
|
||||
label: calendar.summary,
|
||||
value: calendar.id,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
eventDropdown: (required = false) => {
|
||||
return Property.Dropdown<string,boolean,typeof googleCalendarAuth>({
|
||||
displayName: 'Event',
|
||||
refreshers: ['calendar_id'],
|
||||
required: required,
|
||||
auth: googleCalendarAuth,
|
||||
options: async ({ auth, calendar_id }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
if (!calendar_id) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please select a calendar first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const authProp = auth as OAuth2PropertyValue;
|
||||
const events = await getEventsForDropdown(
|
||||
authProp,
|
||||
calendar_id as string
|
||||
);
|
||||
return {
|
||||
disabled: false,
|
||||
options: events,
|
||||
};
|
||||
},
|
||||
});
|
||||
},
|
||||
colorId: Property.Dropdown({
|
||||
auth: googleCalendarAuth,
|
||||
displayName: 'Color',
|
||||
refreshers: [],
|
||||
required: false,
|
||||
options: async ({ auth }) => {
|
||||
if (!auth) {
|
||||
return {
|
||||
disabled: true,
|
||||
placeholder: 'Please connect your account first',
|
||||
options: [],
|
||||
};
|
||||
}
|
||||
const response = await getColors(auth);
|
||||
return {
|
||||
disabled: false,
|
||||
options: Object.entries(response.event).map(([key, value]) => {
|
||||
return {
|
||||
label: value.background,
|
||||
value: key,
|
||||
};
|
||||
}),
|
||||
};
|
||||
},
|
||||
}),
|
||||
};
|
||||
@@ -0,0 +1,246 @@
|
||||
export enum GoogleWatchType {
|
||||
WEBHOOK = 'web_hook',
|
||||
}
|
||||
|
||||
export enum GoogleCalendarKind {
|
||||
CALENDAR_LIST = 'calendar#calendarList',
|
||||
CALENDAR_ENTRY = 'calendar#calendarListEntry',
|
||||
CALENDAR_EVENT = 'calendar#event',
|
||||
CALENDAR_EVENT_LIST = 'calendar#events',
|
||||
EVENT_WATCH = 'api#channel',
|
||||
CALENDAR_COLORS = 'calendar#colors',
|
||||
}
|
||||
|
||||
export interface CalendarList {
|
||||
kind: GoogleCalendarKind.CALENDAR_LIST;
|
||||
etag: string;
|
||||
nextPageToken: string;
|
||||
nextSyncToken: string;
|
||||
items: CalendarObject[];
|
||||
}
|
||||
|
||||
export interface CalendarObject {
|
||||
kind: GoogleCalendarKind.CALENDAR_ENTRY;
|
||||
etag: string;
|
||||
id: string;
|
||||
summary: string;
|
||||
description: string;
|
||||
location: string;
|
||||
timeZone: string;
|
||||
summaryOverride: string;
|
||||
colorId: string;
|
||||
backgroundColor: string;
|
||||
foregroundColor: string;
|
||||
hidden: boolean;
|
||||
selected: boolean;
|
||||
accessRole: string;
|
||||
defaultReminders: [
|
||||
{
|
||||
method: string;
|
||||
minutes: number;
|
||||
}
|
||||
];
|
||||
notificationSettings: {
|
||||
notifications: [
|
||||
{
|
||||
type: string;
|
||||
method: string;
|
||||
}
|
||||
];
|
||||
};
|
||||
primary: boolean;
|
||||
deleted: boolean;
|
||||
conferenceProperties: {
|
||||
allowedConferenceSolutionTypes: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface GoogleWatchResponse {
|
||||
kind: GoogleCalendarKind.EVENT_WATCH;
|
||||
id: string;
|
||||
resourceId: string;
|
||||
resourceUri: string;
|
||||
token: string;
|
||||
expiration: number;
|
||||
}
|
||||
|
||||
interface Attendee {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
organizer: boolean;
|
||||
self: boolean;
|
||||
resource: boolean;
|
||||
optional: boolean;
|
||||
responseStatus: string;
|
||||
comment: string;
|
||||
additionalGuests: BigInteger;
|
||||
}
|
||||
|
||||
export interface GoogleCalendarEvent {
|
||||
kind: GoogleCalendarKind.CALENDAR_EVENT;
|
||||
etag: string;
|
||||
id: string;
|
||||
status: string;
|
||||
htmlLink: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
summary: string;
|
||||
description: string;
|
||||
location: string;
|
||||
colorId: string;
|
||||
creator: {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
self: boolean;
|
||||
};
|
||||
organizer: {
|
||||
id: string;
|
||||
email: string;
|
||||
displayName: string;
|
||||
self: boolean;
|
||||
};
|
||||
start: {
|
||||
date: Date;
|
||||
dateTime: Date;
|
||||
timeZone: string;
|
||||
};
|
||||
end: {
|
||||
date: Date;
|
||||
dateTime: Date;
|
||||
timeZone: string;
|
||||
};
|
||||
endTimeUnspecified: boolean;
|
||||
recurrence: [string];
|
||||
recurringEventId: string;
|
||||
originalStartTime: {
|
||||
date: Date;
|
||||
dateTime: Date;
|
||||
timeZone: string;
|
||||
};
|
||||
transparency: string;
|
||||
visibility: string;
|
||||
iCalUID: string;
|
||||
sequence: BigInteger;
|
||||
attendees: Attendee[];
|
||||
attendeesOmitted: boolean;
|
||||
extendedProperties: {
|
||||
private: {
|
||||
key: string;
|
||||
};
|
||||
shared: {
|
||||
key: string;
|
||||
};
|
||||
};
|
||||
hangoutLink: string;
|
||||
conferenceData: {
|
||||
createRequest: {
|
||||
requestId: string;
|
||||
conferenceSolutionKey: {
|
||||
type: string;
|
||||
};
|
||||
status: {
|
||||
statusCode: string;
|
||||
};
|
||||
};
|
||||
entryPoints: [
|
||||
{
|
||||
entryPointType: string;
|
||||
uri: string;
|
||||
label: string;
|
||||
pin: string;
|
||||
accessCode: string;
|
||||
meetingCode: string;
|
||||
passcode: string;
|
||||
password: string;
|
||||
}
|
||||
];
|
||||
conferenceSolution: {
|
||||
key: {
|
||||
type: string;
|
||||
};
|
||||
name: string;
|
||||
iconUri: string;
|
||||
};
|
||||
conferenceId: string;
|
||||
signature: string;
|
||||
notes: string;
|
||||
};
|
||||
gadget: {
|
||||
type: string;
|
||||
title: string;
|
||||
link: string;
|
||||
iconLink: string;
|
||||
width: BigInteger;
|
||||
height: BigInteger;
|
||||
display: string;
|
||||
preferences: {
|
||||
key: string;
|
||||
};
|
||||
};
|
||||
anyoneCanAddSelf: boolean;
|
||||
guestsCanInviteOthers: boolean;
|
||||
guestsCanModify: boolean;
|
||||
guestsCanSeeOtherGuests: boolean;
|
||||
privateCopy: boolean;
|
||||
locked: boolean;
|
||||
reminders: {
|
||||
useDefault: boolean;
|
||||
overrides: [
|
||||
{
|
||||
method: string;
|
||||
minutes: BigInteger;
|
||||
}
|
||||
];
|
||||
};
|
||||
source: {
|
||||
url: string;
|
||||
title: string;
|
||||
};
|
||||
attachments: [
|
||||
{
|
||||
fileUrl: string;
|
||||
title: string;
|
||||
mimeType: string;
|
||||
iconLink: string;
|
||||
fileId: string;
|
||||
}
|
||||
];
|
||||
eventType: string;
|
||||
}
|
||||
|
||||
export interface GoogleCalendarEventList {
|
||||
kind: GoogleCalendarKind.CALENDAR_EVENT_LIST;
|
||||
etag: string;
|
||||
summary: string;
|
||||
description: string;
|
||||
updated: number;
|
||||
timeZone: string;
|
||||
accessRole: string;
|
||||
defaultReminders: [
|
||||
{
|
||||
method: string;
|
||||
minutes: BigInteger;
|
||||
}
|
||||
];
|
||||
nextPageToken: string;
|
||||
nextSyncToken: string;
|
||||
items: GoogleCalendarEvent[];
|
||||
}
|
||||
|
||||
export interface GetColorsResponse {
|
||||
kind: GoogleCalendarKind.CALENDAR_COLORS;
|
||||
calendar: {
|
||||
[s: string]: {
|
||||
background: string;
|
||||
foreground: string;
|
||||
};
|
||||
};
|
||||
event: {
|
||||
[s: string]: {
|
||||
background: string;
|
||||
foreground: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
title: 'Google Calendar'
|
||||
description: ''
|
||||
---
|
||||
|
||||
## Set up and run an app that calls a Google calendar API.
|
||||
|
||||
1. In the Google Cloud console, enable the Google Calendar API.
|
||||
2. Click In the Google Cloud console, and go to Menu menu > APIs & Services > Credentials.
|
||||
3. Click Create Credentials > OAuth client ID.
|
||||
4. In the Name field, type a name for the credential. This name is only shown in the Google Cloud console.
|
||||
5. Click Create. The OAuth client created screen appears, showing your new Client ID and Client secret.
|
||||
6. Click OK. The newly created credential appears under OAuth 2.0 Client IDs.
|
||||
|
||||
---
|
||||
|
||||
## Triggers
|
||||
|
||||
TRIGGERS
|
||||
|
||||
---
|
||||
|
||||
## Actions
|
||||
|
||||
ACTIONS
|
||||
@@ -0,0 +1,139 @@
|
||||
import { AppConnectionValueForAuthProperty, createTrigger, PiecePropValueSchema, Property } from '@activepieces/pieces-framework';
|
||||
import { TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { getEvents } from '../common/helper';
|
||||
import { GoogleCalendarEvent } from '../common/types';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import { DedupeStrategy, Polling, pollingHelper } from '@activepieces/pieces-common';
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
||||
{ calendarId?: string; expandRecurringEvent: boolean }
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue: { calendarId, expandRecurringEvent }, lastFetchEpochMS }) => {
|
||||
let minUpdated = new Date(lastFetchEpochMS);
|
||||
// Google Calendar API breaks if minUpdated is too far in the past
|
||||
if (lastFetchEpochMS === 0) {
|
||||
const now = new Date();
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(now.getDate() - 7);
|
||||
|
||||
minUpdated = yesterday;
|
||||
}
|
||||
|
||||
const currentValues: GoogleCalendarEvent[] =
|
||||
(await getEvents(calendarId!, expandRecurringEvent, auth, minUpdated)) ?? [];
|
||||
const items = currentValues.map((item) => ({
|
||||
epochMilliSeconds: new Date(item.updated).getTime(),
|
||||
data: item,
|
||||
}));
|
||||
return items;
|
||||
},
|
||||
};
|
||||
|
||||
export const calendarEventChanged = createTrigger({
|
||||
// docs: https://developers.google.com/calendar/api/guides/push
|
||||
auth: googleCalendarAuth,
|
||||
name: 'new_or_updated_event',
|
||||
displayName: 'New or Updated Event',
|
||||
description: 'Triggers when an event is added or updated',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown(),
|
||||
expandRecurringEvent: Property.Checkbox({
|
||||
displayName: 'Expand Recurring Event?',
|
||||
description: 'If true, the trigger will activate for every occurrence of a recurring event.',
|
||||
required: true,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
sampleData: {
|
||||
kind: 'calendar#event',
|
||||
etag: '3350849506974000',
|
||||
id: '0nsfi5ttd2b17ac76ma2f37oi9',
|
||||
htmlLink: 'https://www.google.com/calendar/event?eid=kgjb90uioj4klrgfmdsnjsjvlgkm',
|
||||
summary: 'ap-event-test',
|
||||
created: '2023-02-03T11:36:36.000Z',
|
||||
updated: '2023-02-03T11:45:53.487Z',
|
||||
description: 'Sample description',
|
||||
status: 'canceled',
|
||||
creator: {
|
||||
email: 'test@test.com',
|
||||
self: true,
|
||||
},
|
||||
organizer: {
|
||||
email: 'test@test.com',
|
||||
self: true,
|
||||
},
|
||||
start: {
|
||||
dateTime: '2023-02-02T22:30:00+03:00',
|
||||
timeZone: 'Asia/Amman',
|
||||
},
|
||||
end: {
|
||||
dateTime: '2023-02-02T23:30:00+03:00',
|
||||
timeZone: 'Asia/Amman',
|
||||
},
|
||||
transparency: 'transparent',
|
||||
iCalUID: '0nsfi5ttd2b17ac76ma2f37oi9@google.com',
|
||||
sequence: 1,
|
||||
attendees: [
|
||||
{
|
||||
email: 'attende@test.com',
|
||||
responseStatus: 'needsAction',
|
||||
},
|
||||
{
|
||||
email: 'test@test.com',
|
||||
organizer: true,
|
||||
self: true,
|
||||
responseStatus: 'accepted',
|
||||
},
|
||||
],
|
||||
reminders: {
|
||||
useDefault: true,
|
||||
},
|
||||
eventType: 'default',
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
async test({ store, auth, propsValue, files }) {
|
||||
return await pollingHelper.test(polling, {
|
||||
store,
|
||||
auth,
|
||||
propsValue: {
|
||||
calendarId: propsValue.calendar_id,
|
||||
expandRecurringEvent: propsValue.expandRecurringEvent,
|
||||
},
|
||||
files,
|
||||
});
|
||||
},
|
||||
async onEnable({ store, auth, propsValue }) {
|
||||
await pollingHelper.onEnable(polling, {
|
||||
store,
|
||||
auth,
|
||||
propsValue: {
|
||||
calendarId: propsValue.calendar_id,
|
||||
expandRecurringEvent: propsValue.expandRecurringEvent,
|
||||
},
|
||||
});
|
||||
},
|
||||
async onDisable({ store, auth, propsValue }) {
|
||||
await pollingHelper.onDisable(polling, {
|
||||
store,
|
||||
auth,
|
||||
propsValue: {
|
||||
calendarId: propsValue.calendar_id,
|
||||
expandRecurringEvent: propsValue.expandRecurringEvent,
|
||||
},
|
||||
});
|
||||
},
|
||||
async run({ store, auth, propsValue, files }) {
|
||||
return await pollingHelper.poll(polling, {
|
||||
store,
|
||||
auth,
|
||||
propsValue: {
|
||||
calendarId: propsValue.calendar_id,
|
||||
expandRecurringEvent: propsValue.expandRecurringEvent,
|
||||
},
|
||||
files,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
createTrigger,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { GoogleCalendarEvent } from '../common/types';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { getEvents } from '../common/helper';
|
||||
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
||||
{
|
||||
calendar_id: string | undefined;
|
||||
specific_event: boolean | undefined;
|
||||
event_id: string | undefined;
|
||||
cancellation_reason: string[] | undefined;
|
||||
}
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
const {
|
||||
calendar_id: calendarId,
|
||||
specific_event,
|
||||
event_id,
|
||||
cancellation_reason,
|
||||
} = propsValue;
|
||||
|
||||
if (!calendarId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (specific_event && !event_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let minUpdated: Date;
|
||||
if (lastFetchEpochMS === 0) {
|
||||
minUpdated = new Date();
|
||||
minUpdated.setDate(minUpdated.getDate() - 1);
|
||||
} else {
|
||||
minUpdated = new Date(lastFetchEpochMS);
|
||||
}
|
||||
|
||||
let events: GoogleCalendarEvent[] = [];
|
||||
|
||||
if (specific_event && event_id) {
|
||||
const eventRequest: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/${event_id}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const eventResponse = await httpClient.sendRequest<GoogleCalendarEvent>(
|
||||
eventRequest
|
||||
);
|
||||
const event = eventResponse.body;
|
||||
|
||||
const updatedTime = new Date(event.updated ?? 0).getTime();
|
||||
if (updatedTime > lastFetchEpochMS && event.status === 'cancelled') {
|
||||
events = [event];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching specific event:', error);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
const allEvents = await getEvents(calendarId, true, auth, minUpdated);
|
||||
events = allEvents.filter((event) => event.status === 'cancelled');
|
||||
}
|
||||
|
||||
if (cancellation_reason && cancellation_reason.length > 0) {
|
||||
events = events.filter((event) => {
|
||||
return cancellation_reason.some((reason) => {
|
||||
switch (reason) {
|
||||
case 'deleted':
|
||||
return !event.summary || event.summary.includes('Deleted');
|
||||
case 'declined':
|
||||
return (
|
||||
event.attendees?.some(
|
||||
(attendee) => attendee.responseStatus === 'declined'
|
||||
) || false
|
||||
);
|
||||
case 'rescheduled':
|
||||
return (
|
||||
event.summary?.toLowerCase().includes('rescheduled') ||
|
||||
event.description?.toLowerCase().includes('rescheduled') ||
|
||||
false
|
||||
);
|
||||
case 'other':
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return events.map((event) => {
|
||||
return {
|
||||
epochMilliSeconds: new Date(event.updated!).getTime(),
|
||||
data: event,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const eventCancelled = createTrigger({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'event_cancelled',
|
||||
displayName: 'Event Cancelled',
|
||||
description: 'Fires when an event is canceled or deleted.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
specific_event: Property.Checkbox({
|
||||
displayName: 'Target Specific Event',
|
||||
description:
|
||||
'Enable to monitor a specific event instead of all events in the calendar.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
event_id: googleCalendarCommon.eventDropdown(false),
|
||||
cancellation_reason: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Cancellation Reasons',
|
||||
description: 'Filter by specific types of cancellations (optional)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Event Deleted', value: 'deleted' },
|
||||
{ label: 'Attendee Declined', value: 'declined' },
|
||||
{ label: 'Event Rescheduled', value: 'rescheduled' },
|
||||
{ label: 'Other Cancellations', value: 'other' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {
|
||||
id: 'abc123def456_cancelled',
|
||||
summary: 'Cancelled: Q3 Planning Session',
|
||||
status: 'cancelled',
|
||||
created: '2025-07-20T10:00:00.000Z',
|
||||
updated: '2025-08-14T09:30:00.000Z',
|
||||
organizer: { email: 'project.manager@example.com' },
|
||||
start: { dateTime: '2025-08-25T10:00:00-07:00' },
|
||||
end: { dateTime: '2025-08-25T11:30:00-07:00' },
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
createTrigger,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { GoogleCalendarEvent } from '../common/types';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
interface GoogleCalendarEventList {
|
||||
items: GoogleCalendarEvent[];
|
||||
}
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
||||
{
|
||||
calendar_id: string | undefined;
|
||||
specific_event: boolean | undefined;
|
||||
event_id: string | undefined;
|
||||
}
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
||||
if (lastFetchEpochMS === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { calendar_id: calendarId, specific_event, event_id } = propsValue;
|
||||
|
||||
if (!calendarId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (specific_event && !event_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let events: GoogleCalendarEvent[] = [];
|
||||
|
||||
if (specific_event && event_id) {
|
||||
const eventRequest: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/${event_id}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const eventResponse = await httpClient.sendRequest<GoogleCalendarEvent>(
|
||||
eventRequest
|
||||
);
|
||||
const event = eventResponse.body;
|
||||
|
||||
const endTimeString = event.end?.dateTime ?? event.end?.date;
|
||||
if (endTimeString) {
|
||||
const endTime = new Date(endTimeString).getTime();
|
||||
if (endTime > lastFetchEpochMS) {
|
||||
events = [event];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching specific event:', error);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
singleEvents: 'true',
|
||||
orderBy: 'startTime',
|
||||
timeMin: new Date(lastFetchEpochMS).toISOString(),
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<GoogleCalendarEventList>(
|
||||
request
|
||||
);
|
||||
events = response.body.items;
|
||||
}
|
||||
const endedEvents: {
|
||||
epochMilliSeconds: number;
|
||||
data: GoogleCalendarEvent;
|
||||
}[] = [];
|
||||
const now = Date.now();
|
||||
|
||||
for (const event of events) {
|
||||
const endTimeString = event.end?.dateTime ?? event.end?.date;
|
||||
if (!endTimeString) continue;
|
||||
|
||||
const endTime = new Date(endTimeString).getTime();
|
||||
|
||||
if (endTime > lastFetchEpochMS && endTime <= now) {
|
||||
endedEvents.push({
|
||||
epochMilliSeconds: endTime,
|
||||
data: event,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return endedEvents;
|
||||
},
|
||||
};
|
||||
|
||||
export const eventEnds = createTrigger({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'event_ends',
|
||||
displayName: 'Event Ends',
|
||||
description: 'Fires when an event ends.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
specific_event: Property.Checkbox({
|
||||
displayName: 'Target Specific Event',
|
||||
description:
|
||||
'Enable to monitor a specific event instead of all events in the calendar.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
event_id: googleCalendarCommon.eventDropdown(false),
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {
|
||||
kind: 'calendar#event',
|
||||
etag: '"3419997894982000"',
|
||||
id: 'sample_event_id_67890',
|
||||
status: 'confirmed',
|
||||
htmlLink:
|
||||
'https://www.google.com/calendar/event?eid=c2FtcGxlX2V2ZW50X2lkXzY3ODkw',
|
||||
created: '2025-08-14T09:00:00.000Z',
|
||||
updated: '2025-08-14T09:00:00.000Z',
|
||||
summary: 'Project Deadline',
|
||||
creator: { email: 'manager@example.com' },
|
||||
organizer: {
|
||||
email: 'manager@example.com',
|
||||
self: true,
|
||||
},
|
||||
start: {
|
||||
dateTime: '2025-08-14T14:30:00+05:30',
|
||||
timeZone: 'Asia/Kolkata',
|
||||
},
|
||||
end: {
|
||||
dateTime: '2025-08-14T15:30:00+05:30',
|
||||
timeZone: 'Asia/Kolkata',
|
||||
},
|
||||
iCalUID: 'sample_event_id_67890@google.com',
|
||||
sequence: 0,
|
||||
reminders: { useDefault: true },
|
||||
eventType: 'default',
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,202 @@
|
||||
import {
|
||||
AppConnectionValueForAuthProperty,
|
||||
createTrigger,
|
||||
PiecePropValueSchema,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { GoogleCalendarEvent } from '../common/types';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
interface GoogleCalendarEventList {
|
||||
items: GoogleCalendarEvent[];
|
||||
}
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
||||
{
|
||||
calendar_id: string | undefined;
|
||||
specific_event: boolean | undefined;
|
||||
event_id: string | undefined;
|
||||
time_value: number | undefined;
|
||||
time_unit: string | undefined;
|
||||
}
|
||||
> = {
|
||||
strategy: DedupeStrategy.TIMEBASED,
|
||||
items: async ({ auth, propsValue }) => {
|
||||
const { calendar_id, specific_event, event_id, time_value, time_unit } =
|
||||
propsValue;
|
||||
|
||||
if (!calendar_id || !time_value || !time_unit) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (specific_event && !event_id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let offset_ms = time_value * 60 * 1000;
|
||||
if (time_unit === 'hours') {
|
||||
offset_ms = time_value * 60 * 60 * 1000;
|
||||
} else if (time_unit === 'days') {
|
||||
offset_ms = time_value * 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
|
||||
const pollingIntervalMs = 5 * 60 * 1000;
|
||||
|
||||
const timeMin = new Date(now + offset_ms).toISOString();
|
||||
const timeMax = new Date(now + offset_ms + pollingIntervalMs).toISOString();
|
||||
|
||||
let events: GoogleCalendarEvent[] = [];
|
||||
|
||||
if (specific_event && event_id) {
|
||||
const eventRequest: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendar_id}/events/${event_id}`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const eventResponse = await httpClient.sendRequest<GoogleCalendarEvent>(
|
||||
eventRequest
|
||||
);
|
||||
const event = eventResponse.body;
|
||||
|
||||
// Check if this specific event falls within our time window
|
||||
const eventStartTime = new Date(
|
||||
event.start?.dateTime ?? event.start?.date ?? 0
|
||||
).getTime();
|
||||
const triggerTime = eventStartTime - offset_ms;
|
||||
|
||||
if (triggerTime >= now && triggerTime <= now + pollingIntervalMs) {
|
||||
events = [event];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching specific event:', error);
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendar_id}/events`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: {
|
||||
singleEvents: 'true',
|
||||
orderBy: 'startTime',
|
||||
timeMin: timeMin,
|
||||
timeMax: timeMax,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<GoogleCalendarEventList>(
|
||||
request
|
||||
);
|
||||
events = response.body.items;
|
||||
}
|
||||
|
||||
return events.map((event) => {
|
||||
const startTime = new Date(
|
||||
event.start?.dateTime ?? event.start?.date ?? 0
|
||||
).getTime();
|
||||
|
||||
const triggerTime = startTime - offset_ms;
|
||||
return {
|
||||
epochMilliSeconds: triggerTime,
|
||||
data: event,
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const eventStartTimeBefore = createTrigger({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'event_starts_in',
|
||||
displayName: 'Event Start (Time Before)',
|
||||
description:
|
||||
'Fires at a specified amount of time before an event starts (e.g., a reminder).',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
specific_event: Property.Checkbox({
|
||||
displayName: 'Target Specific Event',
|
||||
description:
|
||||
'Enable to monitor a specific event instead of all events in the calendar.',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
event_id: googleCalendarCommon.eventDropdown(false),
|
||||
time_value: Property.Number({
|
||||
displayName: 'Time Before',
|
||||
description: 'The amount of time before the event starts.',
|
||||
required: true,
|
||||
defaultValue: 15,
|
||||
}),
|
||||
time_unit: Property.StaticDropdown({
|
||||
displayName: 'Time Unit',
|
||||
required: true,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Minutes', value: 'minutes' },
|
||||
{ label: 'Hours', value: 'hours' },
|
||||
{ label: 'Days', value: 'days' },
|
||||
],
|
||||
},
|
||||
defaultValue: 'minutes',
|
||||
}),
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {},
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,203 @@
|
||||
import {
|
||||
createTrigger,
|
||||
AppConnectionValueForAuthProperty,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import { getCalendars } from '../common/helper';
|
||||
import { CalendarObject } from '../common/types';
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
||||
{
|
||||
access_role_filter: string[] | undefined;
|
||||
calendar_name_filter: string | undefined;
|
||||
exclude_shared: boolean | undefined;
|
||||
}
|
||||
> = {
|
||||
strategy: DedupeStrategy.LAST_ITEM,
|
||||
items: async ({ auth, store, propsValue }) => {
|
||||
const { access_role_filter, calendar_name_filter, exclude_shared } =
|
||||
propsValue;
|
||||
|
||||
const currentCalendars = await getCalendars(auth);
|
||||
|
||||
let filteredCalendars = currentCalendars;
|
||||
|
||||
if (access_role_filter && access_role_filter.length > 0) {
|
||||
filteredCalendars = filteredCalendars.filter((cal) =>
|
||||
access_role_filter.includes(cal.accessRole)
|
||||
);
|
||||
}
|
||||
|
||||
if (calendar_name_filter && calendar_name_filter.trim()) {
|
||||
const searchTerm = calendar_name_filter.toLowerCase().trim();
|
||||
filteredCalendars = filteredCalendars.filter(
|
||||
(cal) =>
|
||||
cal.summary?.toLowerCase().includes(searchTerm) ||
|
||||
cal.description?.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
if (exclude_shared) {
|
||||
filteredCalendars = filteredCalendars.filter(
|
||||
(cal) => cal.accessRole === 'owner' || cal.primary
|
||||
);
|
||||
}
|
||||
|
||||
const currentCalendarIds = filteredCalendars.map((cal) => cal.id);
|
||||
|
||||
const oldCalendarIds = (await store.get<string[]>('calendars')) || [];
|
||||
const oldCalendarIdsSet = new Set(oldCalendarIds);
|
||||
|
||||
const newCalendars = filteredCalendars.filter(
|
||||
(cal) => !oldCalendarIdsSet.has(cal.id)
|
||||
);
|
||||
|
||||
await store.put('calendars', currentCalendarIds);
|
||||
|
||||
return newCalendars.map((cal) => ({
|
||||
id: cal.id,
|
||||
data: {
|
||||
...cal,
|
||||
isOwned: cal.accessRole === 'owner' || cal.primary,
|
||||
isShared: cal.accessRole !== 'owner' && !cal.primary,
|
||||
calendarType: cal.primary
|
||||
? 'primary'
|
||||
: cal.accessRole === 'owner'
|
||||
? 'owned'
|
||||
: 'shared',
|
||||
},
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
export const newCalendar = createTrigger({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'new_calendar',
|
||||
displayName: 'New Calendar',
|
||||
description: 'Fires when a new calendar is created or becomes accessible.',
|
||||
props: {
|
||||
access_role_filter: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Access Role Filter',
|
||||
description:
|
||||
'Only trigger for calendars with specific access roles (optional)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Owner', value: 'owner' },
|
||||
{ label: 'Writer', value: 'writer' },
|
||||
{ label: 'Reader', value: 'reader' },
|
||||
{ label: 'Free/Busy Reader', value: 'freeBusyReader' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
calendar_name_filter: Property.ShortText({
|
||||
displayName: 'Calendar Name Filter',
|
||||
description:
|
||||
'Only trigger for calendars containing this text in name or description (optional)',
|
||||
required: false,
|
||||
}),
|
||||
exclude_shared: Property.Checkbox({
|
||||
displayName: 'Exclude Shared Calendars',
|
||||
description: 'Only trigger for calendars you own, not shared calendars',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
type: TriggerStrategy.POLLING,
|
||||
sampleData: {
|
||||
id: 'sample_calendar_id@group.calendar.google.com',
|
||||
summary: 'New Project Team Calendar',
|
||||
description: 'A shared calendar for the new project team.',
|
||||
timeZone: 'Asia/Kolkata',
|
||||
backgroundColor: '#9fe1e7',
|
||||
foregroundColor: '#000000',
|
||||
accessRole: 'owner',
|
||||
isOwned: true,
|
||||
isShared: false,
|
||||
calendarType: 'owned',
|
||||
primary: false,
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const calendars = await getCalendars(context.auth);
|
||||
|
||||
const { access_role_filter, calendar_name_filter, exclude_shared } =
|
||||
context.propsValue;
|
||||
let filteredCalendars = calendars;
|
||||
|
||||
if (access_role_filter && access_role_filter.length > 0) {
|
||||
filteredCalendars = filteredCalendars.filter((cal) =>
|
||||
access_role_filter.includes(cal.accessRole)
|
||||
);
|
||||
}
|
||||
|
||||
if (calendar_name_filter && calendar_name_filter.trim()) {
|
||||
const searchTerm = calendar_name_filter.toLowerCase().trim();
|
||||
filteredCalendars = filteredCalendars.filter(
|
||||
(cal) =>
|
||||
cal.summary?.toLowerCase().includes(searchTerm) ||
|
||||
cal.description?.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
if (exclude_shared) {
|
||||
filteredCalendars = filteredCalendars.filter(
|
||||
(cal) => cal.accessRole === 'owner' || cal.primary
|
||||
);
|
||||
}
|
||||
|
||||
await context.store.put(
|
||||
'calendars',
|
||||
filteredCalendars.map((cal) => cal.id)
|
||||
);
|
||||
|
||||
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) {
|
||||
const calendars = await getCalendars(context.auth);
|
||||
const recentCalendars = calendars.slice(-1);
|
||||
return recentCalendars.map((cal: CalendarObject) => ({
|
||||
id: cal.id,
|
||||
data: {
|
||||
...cal,
|
||||
isOwned: cal.accessRole === 'owner' || cal.primary,
|
||||
isShared: cal.accessRole !== 'owner' && !cal.primary,
|
||||
calendarType: cal.primary
|
||||
? 'primary'
|
||||
: cal.accessRole === 'owner'
|
||||
? 'owned'
|
||||
: 'shared',
|
||||
},
|
||||
}));
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,226 @@
|
||||
import {
|
||||
createTrigger,
|
||||
AppConnectionValueForAuthProperty,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { TriggerStrategy } from '@activepieces/pieces-framework';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { GoogleCalendarEvent } from '../common/types';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import {
|
||||
DedupeStrategy,
|
||||
Polling,
|
||||
pollingHelper,
|
||||
} from '@activepieces/pieces-common';
|
||||
import {
|
||||
AuthenticationType,
|
||||
httpClient,
|
||||
HttpMethod,
|
||||
HttpRequest,
|
||||
} from '@activepieces/pieces-common';
|
||||
|
||||
interface GoogleCalendarEventList {
|
||||
items: GoogleCalendarEvent[];
|
||||
}
|
||||
|
||||
const polling: Polling<
|
||||
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
||||
{
|
||||
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<string, string> = {
|
||||
singleEvents: 'true',
|
||||
orderBy: 'updated',
|
||||
updatedMin: minUpdated.toISOString(),
|
||||
q: search_term,
|
||||
};
|
||||
|
||||
if (event_types && event_types.length > 0) {
|
||||
event_types.forEach((type) => {
|
||||
if (!queryParams.eventTypes) {
|
||||
queryParams.eventTypes = type;
|
||||
} else {
|
||||
queryParams.eventTypes += '&eventTypes=' + type;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const request: HttpRequest = {
|
||||
method: HttpMethod.GET,
|
||||
url: `${googleCalendarCommon.baseUrl}/calendars/${calendar_id}/events`,
|
||||
authentication: {
|
||||
type: AuthenticationType.BEARER_TOKEN,
|
||||
token: auth.access_token,
|
||||
},
|
||||
queryParams: queryParams,
|
||||
};
|
||||
|
||||
const response = await httpClient.sendRequest<GoogleCalendarEventList>(
|
||||
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',
|
||||
displayName: 'New Event Matching Search',
|
||||
description:
|
||||
'Fires when a new event is created that matches a specified search term.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
search_term: Property.ShortText({
|
||||
displayName: 'Search Term',
|
||||
description:
|
||||
'The keyword(s) to search for in new events (searches across title, description, location, and attendees by default).',
|
||||
required: true,
|
||||
}),
|
||||
event_types: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Event Types',
|
||||
description: 'Filter by specific event types (optional)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Default Events', value: 'default' },
|
||||
{ label: 'Birthday Events', 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 Fields',
|
||||
description:
|
||||
"Specify which fields to search in (leave empty to use Google's default search across all fields)",
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Event Title/Summary', value: 'summary' },
|
||||
{ label: 'Event Description', value: 'description' },
|
||||
{ label: 'Event Location', value: 'location' },
|
||||
{ label: 'Attendee Names/Emails', value: 'attendees' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
},
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,186 @@
|
||||
import {
|
||||
createTrigger,
|
||||
TriggerStrategy,
|
||||
OAuth2PropertyValue,
|
||||
Property,
|
||||
} from '@activepieces/pieces-framework';
|
||||
import { googleCalendarAuth } from '../../';
|
||||
import { googleCalendarCommon } from '../common';
|
||||
import { stopWatchEvent, watchEvent, getLatestEvent } from '../common/helper';
|
||||
import { GoogleWatchResponse, GoogleCalendarEvent } from '../common/types';
|
||||
|
||||
export const newEvent = createTrigger({
|
||||
auth: googleCalendarAuth,
|
||||
name: 'new_event',
|
||||
displayName: 'New Event',
|
||||
description: 'Fires when a new event is created in a calendar.',
|
||||
props: {
|
||||
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
||||
event_types: Property.StaticMultiSelectDropdown({
|
||||
displayName: 'Event Types to Monitor',
|
||||
description:
|
||||
'Filter by specific event types (leave empty to monitor all event types)',
|
||||
required: false,
|
||||
options: {
|
||||
options: [
|
||||
{ label: 'Default Events', value: 'default' },
|
||||
{ label: 'Birthday Events', value: 'birthday' },
|
||||
{ label: 'Focus Time', value: 'focusTime' },
|
||||
{ label: 'Out of Office', value: 'outOfOffice' },
|
||||
{ label: 'Working Location', value: 'workingLocation' },
|
||||
{ label: 'From Gmail', value: 'fromGmail' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
search_filter: Property.ShortText({
|
||||
displayName: 'Search Filter',
|
||||
description:
|
||||
'Only trigger for events containing this text in title, description, or location (optional)',
|
||||
required: false,
|
||||
}),
|
||||
exclude_all_day: Property.Checkbox({
|
||||
displayName: 'Exclude All-Day Events',
|
||||
description: 'Skip triggering for all-day events',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
type: TriggerStrategy.WEBHOOK,
|
||||
sampleData: {
|
||||
kind: 'calendar#event',
|
||||
etag: '"3419997894982000"',
|
||||
id: 'sample_event_id_12345',
|
||||
status: 'confirmed',
|
||||
htmlLink:
|
||||
'https://www.google.com/calendar/event?eid=c2FtcGxlX2V2ZW50X2lkXzEyMzQ1',
|
||||
created: '2025-08-15T11:02:27.000Z',
|
||||
updated: '2025-08-15T11:02:27.491Z',
|
||||
summary: 'Team Sync',
|
||||
creator: { email: 'creator@example.com' },
|
||||
organizer: {
|
||||
email: 'creator@example.com',
|
||||
self: true,
|
||||
},
|
||||
start: {
|
||||
dateTime: '2025-08-18T10:00:00-07:00',
|
||||
timeZone: 'America/Los_Angeles',
|
||||
},
|
||||
end: {
|
||||
dateTime: '2025-08-18T11:00:00-07:00',
|
||||
timeZone: 'America/Los_Angeles',
|
||||
},
|
||||
iCalUID: 'sample_event_id_12345@google.com',
|
||||
sequence: 0,
|
||||
reminders: { useDefault: true },
|
||||
eventType: 'default',
|
||||
},
|
||||
|
||||
async onEnable(context) {
|
||||
const calendarId = context.propsValue.calendar_id!;
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
|
||||
const response = await watchEvent(calendarId, context.webhookUrl, auth);
|
||||
|
||||
await context.store.put<GoogleWatchResponse>(
|
||||
'google_calendar_watch',
|
||||
response
|
||||
);
|
||||
},
|
||||
|
||||
async onDisable(context) {
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
const watch = await context.store.get<GoogleWatchResponse>(
|
||||
'google_calendar_watch'
|
||||
);
|
||||
|
||||
if (watch) {
|
||||
await stopWatchEvent(watch, auth);
|
||||
}
|
||||
},
|
||||
|
||||
async run(context) {
|
||||
const payload = context.payload;
|
||||
const headers = payload.headers as Record<string, string>;
|
||||
const { event_types, search_filter, exclude_all_day } = context.propsValue;
|
||||
|
||||
if (headers['x-goog-resource-state'] === 'add') {
|
||||
const eventData = payload.body as GoogleCalendarEvent;
|
||||
|
||||
if (event_types && event_types.length > 0) {
|
||||
const eventType = eventData.eventType || 'default';
|
||||
if (!event_types.includes(eventType)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (search_filter && search_filter.trim()) {
|
||||
const searchTerm = search_filter.toLowerCase().trim();
|
||||
const summary = (eventData.summary || '').toLowerCase();
|
||||
const description = (eventData.description || '').toLowerCase();
|
||||
const location = (eventData.location || '').toLowerCase();
|
||||
|
||||
const matchesSearch =
|
||||
summary.includes(searchTerm) ||
|
||||
description.includes(searchTerm) ||
|
||||
location.includes(searchTerm);
|
||||
|
||||
if (!matchesSearch) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (exclude_all_day) {
|
||||
const isAllDay = eventData.start?.date && !eventData.start?.dateTime;
|
||||
if (isAllDay) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return [eventData];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
async test(context) {
|
||||
const auth = context.auth as OAuth2PropertyValue;
|
||||
const { event_types, search_filter, exclude_all_day } = context.propsValue;
|
||||
|
||||
const latestEvent = await getLatestEvent(
|
||||
context.propsValue.calendar_id!,
|
||||
auth
|
||||
);
|
||||
|
||||
if (event_types && event_types.length > 0) {
|
||||
const eventType = latestEvent.eventType || 'default';
|
||||
if (!event_types.includes(eventType)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (search_filter && search_filter.trim()) {
|
||||
const searchTerm = search_filter.toLowerCase().trim();
|
||||
const summary = (latestEvent.summary || '').toLowerCase();
|
||||
const description = (latestEvent.description || '').toLowerCase();
|
||||
const location = (latestEvent.location || '').toLowerCase();
|
||||
|
||||
const matchesSearch =
|
||||
summary.includes(searchTerm) ||
|
||||
description.includes(searchTerm) ||
|
||||
location.includes(searchTerm);
|
||||
|
||||
if (!matchesSearch) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (exclude_all_day) {
|
||||
const isAllDay = latestEvent.start?.date && !latestEvent.start?.dateTime;
|
||||
if (isAllDay) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
return [latestEvent];
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user