@guayaba/workflow-piece-google-calendar 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.babelrc +3 -0
  2. package/.eslintrc.json +18 -0
  3. package/README.md +5 -0
  4. package/assets/logo.png +0 -0
  5. package/package.json +23 -0
  6. package/src/i18n/ca.json +71 -0
  7. package/src/i18n/de.json +151 -0
  8. package/src/i18n/es.json +151 -0
  9. package/src/i18n/fr.json +151 -0
  10. package/src/i18n/hi.json +71 -0
  11. package/src/i18n/id.json +71 -0
  12. package/src/i18n/ja.json +151 -0
  13. package/src/i18n/nl.json +151 -0
  14. package/src/i18n/pt.json +151 -0
  15. package/src/i18n/ru.json +71 -0
  16. package/src/i18n/translation.json +151 -0
  17. package/src/i18n/vi.json +71 -0
  18. package/src/i18n/zh.json +151 -0
  19. package/src/index.ts +78 -0
  20. package/src/lib/actions/add-attendees.action.ts +52 -0
  21. package/src/lib/actions/add-calendar-to-calendarlist.ts +33 -0
  22. package/src/lib/actions/create-event.ts +163 -0
  23. package/src/lib/actions/create-quick-event.ts +66 -0
  24. package/src/lib/actions/delete-event.action.ts +32 -0
  25. package/src/lib/actions/find-busy-free-periods.ts +97 -0
  26. package/src/lib/actions/get-event-by-id.ts +123 -0
  27. package/src/lib/actions/get-events.ts +111 -0
  28. package/src/lib/actions/update-event.action.ts +126 -0
  29. package/src/lib/auth.ts +81 -0
  30. package/src/lib/common/helper.ts +228 -0
  31. package/src/lib/common/index.ts +95 -0
  32. package/src/lib/common/types.ts +246 -0
  33. package/src/lib/google-calendar.mdx +25 -0
  34. package/src/lib/triggers/calendar-event.ts +139 -0
  35. package/src/lib/triggers/event-cancelled.ts +200 -0
  36. package/src/lib/triggers/event-ends.ts +201 -0
  37. package/src/lib/triggers/event-start-time-before.ts +201 -0
  38. package/src/lib/triggers/new-calendar.ts +203 -0
  39. package/src/lib/triggers/new-event-matching-search.ts +225 -0
  40. package/src/lib/triggers/new-event.ts +184 -0
  41. package/tsconfig.json +16 -0
  42. package/tsconfig.lib.json +15 -0
@@ -0,0 +1,246 @@
1
+ export enum GoogleWatchType {
2
+ WEBHOOK = 'web_hook',
3
+ }
4
+
5
+ export enum GoogleCalendarKind {
6
+ CALENDAR_LIST = 'calendar#calendarList',
7
+ CALENDAR_ENTRY = 'calendar#calendarListEntry',
8
+ CALENDAR_EVENT = 'calendar#event',
9
+ CALENDAR_EVENT_LIST = 'calendar#events',
10
+ EVENT_WATCH = 'api#channel',
11
+ CALENDAR_COLORS = 'calendar#colors',
12
+ }
13
+
14
+ export interface CalendarList {
15
+ kind: GoogleCalendarKind.CALENDAR_LIST;
16
+ etag: string;
17
+ nextPageToken: string;
18
+ nextSyncToken: string;
19
+ items: CalendarObject[];
20
+ }
21
+
22
+ export interface CalendarObject {
23
+ kind: GoogleCalendarKind.CALENDAR_ENTRY;
24
+ etag: string;
25
+ id: string;
26
+ summary: string;
27
+ description: string;
28
+ location: string;
29
+ timeZone: string;
30
+ summaryOverride: string;
31
+ colorId: string;
32
+ backgroundColor: string;
33
+ foregroundColor: string;
34
+ hidden: boolean;
35
+ selected: boolean;
36
+ accessRole: string;
37
+ defaultReminders: [
38
+ {
39
+ method: string;
40
+ minutes: number;
41
+ }
42
+ ];
43
+ notificationSettings: {
44
+ notifications: [
45
+ {
46
+ type: string;
47
+ method: string;
48
+ }
49
+ ];
50
+ };
51
+ primary: boolean;
52
+ deleted: boolean;
53
+ conferenceProperties: {
54
+ allowedConferenceSolutionTypes: string[];
55
+ };
56
+ }
57
+
58
+ export interface GoogleWatchResponse {
59
+ kind: GoogleCalendarKind.EVENT_WATCH;
60
+ id: string;
61
+ resourceId: string;
62
+ resourceUri: string;
63
+ token: string;
64
+ expiration: number;
65
+ }
66
+
67
+ interface Attendee {
68
+ id: string;
69
+ email: string;
70
+ displayName: string;
71
+ organizer: boolean;
72
+ self: boolean;
73
+ resource: boolean;
74
+ optional: boolean;
75
+ responseStatus: string;
76
+ comment: string;
77
+ additionalGuests: BigInteger;
78
+ }
79
+
80
+ export interface GoogleCalendarEvent {
81
+ kind: GoogleCalendarKind.CALENDAR_EVENT;
82
+ etag: string;
83
+ id: string;
84
+ status: string;
85
+ htmlLink: string;
86
+ created: string;
87
+ updated: string;
88
+ summary: string;
89
+ description: string;
90
+ location: string;
91
+ colorId: string;
92
+ creator: {
93
+ id: string;
94
+ email: string;
95
+ displayName: string;
96
+ self: boolean;
97
+ };
98
+ organizer: {
99
+ id: string;
100
+ email: string;
101
+ displayName: string;
102
+ self: boolean;
103
+ };
104
+ start: {
105
+ date: Date;
106
+ dateTime: Date;
107
+ timeZone: string;
108
+ };
109
+ end: {
110
+ date: Date;
111
+ dateTime: Date;
112
+ timeZone: string;
113
+ };
114
+ endTimeUnspecified: boolean;
115
+ recurrence: [string];
116
+ recurringEventId: string;
117
+ originalStartTime: {
118
+ date: Date;
119
+ dateTime: Date;
120
+ timeZone: string;
121
+ };
122
+ transparency: string;
123
+ visibility: string;
124
+ iCalUID: string;
125
+ sequence: BigInteger;
126
+ attendees: Attendee[];
127
+ attendeesOmitted: boolean;
128
+ extendedProperties: {
129
+ private: {
130
+ key: string;
131
+ };
132
+ shared: {
133
+ key: string;
134
+ };
135
+ };
136
+ hangoutLink: string;
137
+ conferenceData: {
138
+ createRequest: {
139
+ requestId: string;
140
+ conferenceSolutionKey: {
141
+ type: string;
142
+ };
143
+ status: {
144
+ statusCode: string;
145
+ };
146
+ };
147
+ entryPoints: [
148
+ {
149
+ entryPointType: string;
150
+ uri: string;
151
+ label: string;
152
+ pin: string;
153
+ accessCode: string;
154
+ meetingCode: string;
155
+ passcode: string;
156
+ password: string;
157
+ }
158
+ ];
159
+ conferenceSolution: {
160
+ key: {
161
+ type: string;
162
+ };
163
+ name: string;
164
+ iconUri: string;
165
+ };
166
+ conferenceId: string;
167
+ signature: string;
168
+ notes: string;
169
+ };
170
+ gadget: {
171
+ type: string;
172
+ title: string;
173
+ link: string;
174
+ iconLink: string;
175
+ width: BigInteger;
176
+ height: BigInteger;
177
+ display: string;
178
+ preferences: {
179
+ key: string;
180
+ };
181
+ };
182
+ anyoneCanAddSelf: boolean;
183
+ guestsCanInviteOthers: boolean;
184
+ guestsCanModify: boolean;
185
+ guestsCanSeeOtherGuests: boolean;
186
+ privateCopy: boolean;
187
+ locked: boolean;
188
+ reminders: {
189
+ useDefault: boolean;
190
+ overrides: [
191
+ {
192
+ method: string;
193
+ minutes: BigInteger;
194
+ }
195
+ ];
196
+ };
197
+ source: {
198
+ url: string;
199
+ title: string;
200
+ };
201
+ attachments: [
202
+ {
203
+ fileUrl: string;
204
+ title: string;
205
+ mimeType: string;
206
+ iconLink: string;
207
+ fileId: string;
208
+ }
209
+ ];
210
+ eventType: string;
211
+ }
212
+
213
+ export interface GoogleCalendarEventList {
214
+ kind: GoogleCalendarKind.CALENDAR_EVENT_LIST;
215
+ etag: string;
216
+ summary: string;
217
+ description: string;
218
+ updated: number;
219
+ timeZone: string;
220
+ accessRole: string;
221
+ defaultReminders: [
222
+ {
223
+ method: string;
224
+ minutes: BigInteger;
225
+ }
226
+ ];
227
+ nextPageToken: string;
228
+ nextSyncToken: string;
229
+ items: GoogleCalendarEvent[];
230
+ }
231
+
232
+ export interface GetColorsResponse {
233
+ kind: GoogleCalendarKind.CALENDAR_COLORS;
234
+ calendar: {
235
+ [s: string]: {
236
+ background: string;
237
+ foreground: string;
238
+ };
239
+ };
240
+ event: {
241
+ [s: string]: {
242
+ background: string;
243
+ foreground: string;
244
+ };
245
+ };
246
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ title: 'Google Calendar'
3
+ description: ''
4
+ ---
5
+
6
+ ## Set up and run an app that calls a Google calendar API.
7
+
8
+ 1. In the Google Cloud console, enable the Google Calendar API.
9
+ 2. Click In the Google Cloud console, and go to Menu menu > APIs & Services > Credentials.
10
+ 3. Click Create Credentials > OAuth client ID.
11
+ 4. In the Name field, type a name for the credential. This name is only shown in the Google Cloud console.
12
+ 5. Click Create. The OAuth client created screen appears, showing your new Client ID and Client secret.
13
+ 6. Click OK. The newly created credential appears under OAuth 2.0 Client IDs.
14
+
15
+ ---
16
+
17
+ ## Triggers
18
+
19
+ TRIGGERS
20
+
21
+ ---
22
+
23
+ ## Actions
24
+
25
+ ACTIONS
@@ -0,0 +1,139 @@
1
+ import { AppConnectionValueForAuthProperty, createTrigger, PiecePropValueSchema, Property } from '@guayaba/workflows-framework';
2
+ import { TriggerStrategy } from '@guayaba/workflows-framework';
3
+ import { googleCalendarCommon } from '../common';
4
+ import { getEvents } from '../common/helper';
5
+ import { GoogleCalendarEvent } from '../common/types';
6
+ import { googleCalendarAuth } from '../common';
7
+ import { DedupeStrategy, Polling, pollingHelper } from '@guayaba/workflows-common';
8
+
9
+ const polling: Polling<
10
+ AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
11
+ { calendarId?: string; expandRecurringEvent: boolean }
12
+ > = {
13
+ strategy: DedupeStrategy.TIMEBASED,
14
+ items: async ({ auth, propsValue: { calendarId, expandRecurringEvent }, lastFetchEpochMS }) => {
15
+ let minUpdated = new Date(lastFetchEpochMS);
16
+ // Google Calendar API breaks if minUpdated is too far in the past
17
+ if (lastFetchEpochMS === 0) {
18
+ const now = new Date();
19
+ const yesterday = new Date();
20
+ yesterday.setDate(now.getDate() - 7);
21
+
22
+ minUpdated = yesterday;
23
+ }
24
+
25
+ const currentValues: GoogleCalendarEvent[] =
26
+ (await getEvents(calendarId!, expandRecurringEvent, auth, minUpdated)) ?? [];
27
+ const items = currentValues.map((item) => ({
28
+ epochMilliSeconds: new Date(item.updated).getTime(),
29
+ data: item,
30
+ }));
31
+ return items;
32
+ },
33
+ };
34
+
35
+ export const calendarEventChanged = createTrigger({
36
+ // docs: https://developers.google.com/calendar/api/guides/push
37
+ auth: googleCalendarAuth,
38
+ name: 'new_or_updated_event',
39
+ displayName: 'New or Updated Event',
40
+ description: 'Triggers when an event is added or updated',
41
+ props: {
42
+ calendar_id: googleCalendarCommon.calendarDropdown(),
43
+ expandRecurringEvent: Property.Checkbox({
44
+ displayName: 'Expand Recurring Event?',
45
+ description: 'If true, the trigger will activate for every occurrence of a recurring event.',
46
+ required: true,
47
+ defaultValue: false,
48
+ }),
49
+ },
50
+ sampleData: {
51
+ kind: 'calendar#event',
52
+ etag: '3350849506974000',
53
+ id: '0nsfi5ttd2b17ac76ma2f37oi9',
54
+ htmlLink: 'https://www.google.com/calendar/event?eid=kgjb90uioj4klrgfmdsnjsjvlgkm',
55
+ summary: 'ap-event-test',
56
+ created: '2023-02-03T11:36:36.000Z',
57
+ updated: '2023-02-03T11:45:53.487Z',
58
+ description: 'Sample description',
59
+ status: 'canceled',
60
+ creator: {
61
+ email: 'test@test.com',
62
+ self: true,
63
+ },
64
+ organizer: {
65
+ email: 'test@test.com',
66
+ self: true,
67
+ },
68
+ start: {
69
+ dateTime: '2023-02-02T22:30:00+03:00',
70
+ timeZone: 'Asia/Amman',
71
+ },
72
+ end: {
73
+ dateTime: '2023-02-02T23:30:00+03:00',
74
+ timeZone: 'Asia/Amman',
75
+ },
76
+ transparency: 'transparent',
77
+ iCalUID: '0nsfi5ttd2b17ac76ma2f37oi9@google.com',
78
+ sequence: 1,
79
+ attendees: [
80
+ {
81
+ email: 'attende@test.com',
82
+ responseStatus: 'needsAction',
83
+ },
84
+ {
85
+ email: 'test@test.com',
86
+ organizer: true,
87
+ self: true,
88
+ responseStatus: 'accepted',
89
+ },
90
+ ],
91
+ reminders: {
92
+ useDefault: true,
93
+ },
94
+ eventType: 'default',
95
+ },
96
+ type: TriggerStrategy.POLLING,
97
+ async test({ store, auth, propsValue, files }) {
98
+ return await pollingHelper.test(polling, {
99
+ store,
100
+ auth,
101
+ propsValue: {
102
+ calendarId: propsValue.calendar_id,
103
+ expandRecurringEvent: propsValue.expandRecurringEvent,
104
+ },
105
+ files,
106
+ });
107
+ },
108
+ async onEnable({ store, auth, propsValue }) {
109
+ await pollingHelper.onEnable(polling, {
110
+ store,
111
+ auth,
112
+ propsValue: {
113
+ calendarId: propsValue.calendar_id,
114
+ expandRecurringEvent: propsValue.expandRecurringEvent,
115
+ },
116
+ });
117
+ },
118
+ async onDisable({ store, auth, propsValue }) {
119
+ await pollingHelper.onDisable(polling, {
120
+ store,
121
+ auth,
122
+ propsValue: {
123
+ calendarId: propsValue.calendar_id,
124
+ expandRecurringEvent: propsValue.expandRecurringEvent,
125
+ },
126
+ });
127
+ },
128
+ async run({ store, auth, propsValue, files }) {
129
+ return await pollingHelper.poll(polling, {
130
+ store,
131
+ auth,
132
+ propsValue: {
133
+ calendarId: propsValue.calendar_id,
134
+ expandRecurringEvent: propsValue.expandRecurringEvent,
135
+ },
136
+ files,
137
+ });
138
+ },
139
+ });
@@ -0,0 +1,200 @@
1
+ import {
2
+ AppConnectionValueForAuthProperty,
3
+ createTrigger,
4
+ PiecePropValueSchema,
5
+ Property,
6
+ } from '@guayaba/workflows-framework';
7
+ import { TriggerStrategy } from '@guayaba/workflows-framework';
8
+ import { googleCalendarCommon, getAccessToken } from '../common';
9
+ import { GoogleCalendarEvent } from '../common/types';
10
+ import { googleCalendarAuth } from '../common';
11
+ import {
12
+ DedupeStrategy,
13
+ Polling,
14
+ pollingHelper,
15
+ } from '@guayaba/workflows-common';
16
+ import {
17
+ AuthenticationType,
18
+ httpClient,
19
+ HttpMethod,
20
+ HttpRequest,
21
+ } from '@guayaba/workflows-common';
22
+ import { getEvents } from '../common/helper';
23
+
24
+
25
+ const polling: Polling<
26
+ AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
27
+ {
28
+ calendar_id: string | undefined;
29
+ specific_event: boolean | undefined;
30
+ event_id: string | undefined;
31
+ cancellation_reason: string[] | undefined;
32
+ }
33
+ > = {
34
+ strategy: DedupeStrategy.TIMEBASED,
35
+ items: async ({ auth, propsValue, lastFetchEpochMS }) => {
36
+ const {
37
+ calendar_id: calendarId,
38
+ specific_event,
39
+ event_id,
40
+ cancellation_reason,
41
+ } = propsValue;
42
+
43
+ if (!calendarId) {
44
+ return [];
45
+ }
46
+
47
+ if (specific_event && !event_id) {
48
+ return [];
49
+ }
50
+
51
+ let minUpdated: Date;
52
+ if (lastFetchEpochMS === 0) {
53
+ minUpdated = new Date();
54
+ minUpdated.setDate(minUpdated.getDate() - 1);
55
+ } else {
56
+ minUpdated = new Date(lastFetchEpochMS);
57
+ }
58
+
59
+ let events: GoogleCalendarEvent[] = [];
60
+
61
+ if (specific_event && event_id) {
62
+ const eventRequest: HttpRequest = {
63
+ method: HttpMethod.GET,
64
+ url: `${googleCalendarCommon.baseUrl}/calendars/${calendarId}/events/${event_id}`,
65
+ authentication: {
66
+ type: AuthenticationType.BEARER_TOKEN,
67
+ token: await getAccessToken(auth),
68
+ },
69
+ };
70
+
71
+ try {
72
+ const eventResponse = await httpClient.sendRequest<GoogleCalendarEvent>(
73
+ eventRequest
74
+ );
75
+ const event = eventResponse.body;
76
+
77
+ const updatedTime = new Date(event.updated ?? 0).getTime();
78
+ if (updatedTime > lastFetchEpochMS && event.status === 'cancelled') {
79
+ events = [event];
80
+ }
81
+ } catch (error) {
82
+ console.error('Error fetching specific event:', error);
83
+ return [];
84
+ }
85
+ } else {
86
+ const allEvents = await getEvents(calendarId, true, auth, minUpdated);
87
+ events = allEvents.filter((event) => event.status === 'cancelled');
88
+ }
89
+
90
+ if (cancellation_reason && cancellation_reason.length > 0) {
91
+ events = events.filter((event) => {
92
+ return cancellation_reason.some((reason) => {
93
+ switch (reason) {
94
+ case 'deleted':
95
+ return !event.summary || event.summary.includes('Deleted');
96
+ case 'declined':
97
+ return (
98
+ event.attendees?.some(
99
+ (attendee) => attendee.responseStatus === 'declined'
100
+ ) || false
101
+ );
102
+ case 'rescheduled':
103
+ return (
104
+ event.summary?.toLowerCase().includes('rescheduled') ||
105
+ event.description?.toLowerCase().includes('rescheduled') ||
106
+ false
107
+ );
108
+ case 'other':
109
+ return true;
110
+ default:
111
+ return true;
112
+ }
113
+ });
114
+ });
115
+ }
116
+
117
+ return events.map((event) => {
118
+ return {
119
+ epochMilliSeconds: new Date(event.updated!).getTime(),
120
+ data: event,
121
+ };
122
+ });
123
+ },
124
+ };
125
+
126
+ export const eventCancelled = createTrigger({
127
+ auth: googleCalendarAuth,
128
+ name: 'event_cancelled',
129
+ displayName: 'Event Cancelled',
130
+ description: 'Fires when an event is canceled or deleted.',
131
+ props: {
132
+ calendar_id: googleCalendarCommon.calendarDropdown('writer'),
133
+ specific_event: Property.Checkbox({
134
+ displayName: 'Target Specific Event',
135
+ description:
136
+ 'Enable to monitor a specific event instead of all events in the calendar.',
137
+ required: false,
138
+ defaultValue: false,
139
+ }),
140
+ event_id: googleCalendarCommon.eventDropdown(false),
141
+ cancellation_reason: Property.StaticMultiSelectDropdown({
142
+ displayName: 'Cancellation Reasons',
143
+ description: 'Filter by specific types of cancellations (optional)',
144
+ required: false,
145
+ options: {
146
+ options: [
147
+ { label: 'Event Deleted', value: 'deleted' },
148
+ { label: 'Attendee Declined', value: 'declined' },
149
+ { label: 'Event Rescheduled', value: 'rescheduled' },
150
+ { label: 'Other Cancellations', value: 'other' },
151
+ ],
152
+ },
153
+ }),
154
+ },
155
+ type: TriggerStrategy.POLLING,
156
+ sampleData: {
157
+ id: 'abc123def456_cancelled',
158
+ summary: 'Cancelled: Q3 Planning Session',
159
+ status: 'cancelled',
160
+ created: '2025-07-20T10:00:00.000Z',
161
+ updated: '2025-08-14T09:30:00.000Z',
162
+ organizer: { email: 'project.manager@example.com' },
163
+ start: { dateTime: '2025-08-25T10:00:00-07:00' },
164
+ end: { dateTime: '2025-08-25T11:30:00-07:00' },
165
+ },
166
+
167
+ async onEnable(context) {
168
+ await pollingHelper.onEnable(polling, {
169
+ auth: context.auth,
170
+ store: context.store,
171
+ propsValue: context.propsValue,
172
+ });
173
+ },
174
+
175
+ async onDisable(context) {
176
+ await pollingHelper.onDisable(polling, {
177
+ auth: context.auth,
178
+ store: context.store,
179
+ propsValue: context.propsValue,
180
+ });
181
+ },
182
+
183
+ async run(context) {
184
+ return await pollingHelper.poll(polling, {
185
+ auth: context.auth,
186
+ store: context.store,
187
+ propsValue: context.propsValue,
188
+ files: context.files,
189
+ });
190
+ },
191
+
192
+ async test(context) {
193
+ return await pollingHelper.test(polling, {
194
+ auth: context.auth,
195
+ store: context.store,
196
+ propsValue: context.propsValue,
197
+ files: context.files,
198
+ });
199
+ },
200
+ });