@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.
- package/.babelrc +3 -0
- package/.eslintrc.json +18 -0
- package/README.md +5 -0
- package/assets/logo.png +0 -0
- package/package.json +23 -0
- package/src/i18n/ca.json +71 -0
- package/src/i18n/de.json +151 -0
- package/src/i18n/es.json +151 -0
- package/src/i18n/fr.json +151 -0
- package/src/i18n/hi.json +71 -0
- package/src/i18n/id.json +71 -0
- package/src/i18n/ja.json +151 -0
- package/src/i18n/nl.json +151 -0
- package/src/i18n/pt.json +151 -0
- package/src/i18n/ru.json +71 -0
- package/src/i18n/translation.json +151 -0
- package/src/i18n/vi.json +71 -0
- package/src/i18n/zh.json +151 -0
- package/src/index.ts +78 -0
- package/src/lib/actions/add-attendees.action.ts +52 -0
- package/src/lib/actions/add-calendar-to-calendarlist.ts +33 -0
- package/src/lib/actions/create-event.ts +163 -0
- package/src/lib/actions/create-quick-event.ts +66 -0
- package/src/lib/actions/delete-event.action.ts +32 -0
- package/src/lib/actions/find-busy-free-periods.ts +97 -0
- package/src/lib/actions/get-event-by-id.ts +123 -0
- package/src/lib/actions/get-events.ts +111 -0
- package/src/lib/actions/update-event.action.ts +126 -0
- package/src/lib/auth.ts +81 -0
- package/src/lib/common/helper.ts +228 -0
- package/src/lib/common/index.ts +95 -0
- package/src/lib/common/types.ts +246 -0
- package/src/lib/google-calendar.mdx +25 -0
- package/src/lib/triggers/calendar-event.ts +139 -0
- package/src/lib/triggers/event-cancelled.ts +200 -0
- package/src/lib/triggers/event-ends.ts +201 -0
- package/src/lib/triggers/event-start-time-before.ts +201 -0
- package/src/lib/triggers/new-calendar.ts +203 -0
- package/src/lib/triggers/new-event-matching-search.ts +225 -0
- package/src/lib/triggers/new-event.ts +184 -0
- package/tsconfig.json +16 -0
- package/tsconfig.lib.json +15 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTrigger,
|
|
3
|
+
AppConnectionValueForAuthProperty,
|
|
4
|
+
Property,
|
|
5
|
+
} from '@guayaba/workflows-framework';
|
|
6
|
+
import { TriggerStrategy } from '@guayaba/workflows-framework';
|
|
7
|
+
import { googleCalendarCommon, googleCalendarAuth, getAccessToken } from '../common';
|
|
8
|
+
import { GoogleCalendarEvent } from '../common/types';
|
|
9
|
+
import {
|
|
10
|
+
DedupeStrategy,
|
|
11
|
+
Polling,
|
|
12
|
+
pollingHelper,
|
|
13
|
+
} from '@guayaba/workflows-common';
|
|
14
|
+
import {
|
|
15
|
+
AuthenticationType,
|
|
16
|
+
httpClient,
|
|
17
|
+
HttpMethod,
|
|
18
|
+
HttpRequest,
|
|
19
|
+
} from '@guayaba/workflows-common';
|
|
20
|
+
|
|
21
|
+
interface GoogleCalendarEventList {
|
|
22
|
+
items: GoogleCalendarEvent[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const polling: Polling<
|
|
26
|
+
AppConnectionValueForAuthProperty<typeof googleCalendarAuth>,
|
|
27
|
+
{
|
|
28
|
+
calendar_id: string | undefined;
|
|
29
|
+
search_term: string | undefined;
|
|
30
|
+
event_types: string[] | undefined;
|
|
31
|
+
search_fields: string[] | undefined;
|
|
32
|
+
}
|
|
33
|
+
> = {
|
|
34
|
+
strategy: DedupeStrategy.TIMEBASED,
|
|
35
|
+
items: async ({ auth, propsValue, lastFetchEpochMS }) => {
|
|
36
|
+
const { calendar_id, search_term, event_types, search_fields } = propsValue;
|
|
37
|
+
|
|
38
|
+
if (!calendar_id || !search_term) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let minUpdated: Date;
|
|
43
|
+
if (lastFetchEpochMS === 0) {
|
|
44
|
+
minUpdated = new Date();
|
|
45
|
+
minUpdated.setDate(minUpdated.getDate() - 1);
|
|
46
|
+
} else {
|
|
47
|
+
minUpdated = new Date(lastFetchEpochMS);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const queryParams: Record<string, string> = {
|
|
51
|
+
singleEvents: 'true',
|
|
52
|
+
orderBy: 'updated',
|
|
53
|
+
updatedMin: minUpdated.toISOString(),
|
|
54
|
+
q: search_term,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (event_types && event_types.length > 0) {
|
|
58
|
+
event_types.forEach((type) => {
|
|
59
|
+
if (!queryParams.eventTypes) {
|
|
60
|
+
queryParams.eventTypes = type;
|
|
61
|
+
} else {
|
|
62
|
+
queryParams.eventTypes += '&eventTypes=' + type;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const request: HttpRequest = {
|
|
68
|
+
method: HttpMethod.GET,
|
|
69
|
+
url: `${googleCalendarCommon.baseUrl}/calendars/${calendar_id}/events`,
|
|
70
|
+
authentication: {
|
|
71
|
+
type: AuthenticationType.BEARER_TOKEN,
|
|
72
|
+
token: await getAccessToken(auth),
|
|
73
|
+
},
|
|
74
|
+
queryParams: queryParams,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const response = await httpClient.sendRequest<GoogleCalendarEventList>(
|
|
78
|
+
request
|
|
79
|
+
);
|
|
80
|
+
const events = response.body.items;
|
|
81
|
+
|
|
82
|
+
const newEvents = events.filter((event) => {
|
|
83
|
+
const created = new Date(event.created ?? 0).getTime();
|
|
84
|
+
const updated = new Date(event.updated ?? 0).getTime();
|
|
85
|
+
|
|
86
|
+
const isNewEvent = updated - created < 5000;
|
|
87
|
+
|
|
88
|
+
if (!isNewEvent) return false;
|
|
89
|
+
|
|
90
|
+
if (search_fields && search_fields.length > 0) {
|
|
91
|
+
const searchTermLower = search_term.toLowerCase();
|
|
92
|
+
return search_fields.some((field) => {
|
|
93
|
+
switch (field) {
|
|
94
|
+
case 'summary':
|
|
95
|
+
return (
|
|
96
|
+
event.summary?.toLowerCase().includes(searchTermLower) || false
|
|
97
|
+
);
|
|
98
|
+
case 'description':
|
|
99
|
+
return (
|
|
100
|
+
event.description?.toLowerCase().includes(searchTermLower) ||
|
|
101
|
+
false
|
|
102
|
+
);
|
|
103
|
+
case 'location':
|
|
104
|
+
return (
|
|
105
|
+
event.location?.toLowerCase().includes(searchTermLower) || false
|
|
106
|
+
);
|
|
107
|
+
case 'attendees':
|
|
108
|
+
return (
|
|
109
|
+
event.attendees?.some(
|
|
110
|
+
(attendee) =>
|
|
111
|
+
attendee.email?.toLowerCase().includes(searchTermLower) ||
|
|
112
|
+
attendee.displayName
|
|
113
|
+
?.toLowerCase()
|
|
114
|
+
.includes(searchTermLower)
|
|
115
|
+
) || false
|
|
116
|
+
);
|
|
117
|
+
default:
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return true;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return newEvents.map((event) => {
|
|
127
|
+
return {
|
|
128
|
+
epochMilliSeconds: new Date(event.updated!).getTime(),
|
|
129
|
+
data: event,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const newEventMatchingSearch = createTrigger({
|
|
136
|
+
auth: googleCalendarAuth,
|
|
137
|
+
name: 'new_event_matching_search',
|
|
138
|
+
displayName: 'New Event Matching Search',
|
|
139
|
+
description:
|
|
140
|
+
'Fires when a new event is created that matches a specified search term.',
|
|
141
|
+
props: {
|
|
142
|
+
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
|
143
|
+
search_term: Property.ShortText({
|
|
144
|
+
displayName: 'Search Term',
|
|
145
|
+
description:
|
|
146
|
+
'The keyword(s) to search for in new events (searches across title, description, location, and attendees by default).',
|
|
147
|
+
required: true,
|
|
148
|
+
}),
|
|
149
|
+
event_types: Property.StaticMultiSelectDropdown({
|
|
150
|
+
displayName: 'Event Types',
|
|
151
|
+
description: 'Filter by specific event types (optional)',
|
|
152
|
+
required: false,
|
|
153
|
+
options: {
|
|
154
|
+
options: [
|
|
155
|
+
{ label: 'Default Events', value: 'default' },
|
|
156
|
+
{ label: 'Birthday Events', value: 'birthday' },
|
|
157
|
+
{ label: 'Focus Time', value: 'focusTime' },
|
|
158
|
+
{ label: 'Out of Office', value: 'outOfOffice' },
|
|
159
|
+
{ label: 'Working Location', value: 'workingLocation' },
|
|
160
|
+
{ label: 'From Gmail', value: 'fromGmail' },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
search_fields: Property.StaticMultiSelectDropdown({
|
|
165
|
+
displayName: 'Search In Fields',
|
|
166
|
+
description:
|
|
167
|
+
"Specify which fields to search in (leave empty to use Google's default search across all fields)",
|
|
168
|
+
required: false,
|
|
169
|
+
options: {
|
|
170
|
+
options: [
|
|
171
|
+
{ label: 'Event Title/Summary', value: 'summary' },
|
|
172
|
+
{ label: 'Event Description', value: 'description' },
|
|
173
|
+
{ label: 'Event Location', value: 'location' },
|
|
174
|
+
{ label: 'Attendee Names/Emails', value: 'attendees' },
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
},
|
|
179
|
+
type: TriggerStrategy.POLLING,
|
|
180
|
+
sampleData: {
|
|
181
|
+
id: 'abc123def456',
|
|
182
|
+
summary: 'Final Project Review',
|
|
183
|
+
description: 'Review of the Q3 final project deliverables.',
|
|
184
|
+
status: 'confirmed',
|
|
185
|
+
created: '2025-08-14T09:05:00.000Z',
|
|
186
|
+
updated: '2025-08-14T09:05:01.000Z',
|
|
187
|
+
start: { dateTime: '2025-09-01T10:00:00-07:00' },
|
|
188
|
+
end: { dateTime: '2025-09-01T11:30:00-07:00' },
|
|
189
|
+
organizer: { email: 'project.manager@example.com' },
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
async onEnable(context) {
|
|
193
|
+
await pollingHelper.onEnable(polling, {
|
|
194
|
+
auth: context.auth,
|
|
195
|
+
store: context.store,
|
|
196
|
+
propsValue: context.propsValue,
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
async onDisable(context) {
|
|
201
|
+
await pollingHelper.onDisable(polling, {
|
|
202
|
+
auth: context.auth,
|
|
203
|
+
store: context.store,
|
|
204
|
+
propsValue: context.propsValue,
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
async run(context) {
|
|
209
|
+
return await pollingHelper.poll(polling, {
|
|
210
|
+
auth: context.auth,
|
|
211
|
+
store: context.store,
|
|
212
|
+
propsValue: context.propsValue,
|
|
213
|
+
files: context.files,
|
|
214
|
+
});
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
async test(context) {
|
|
218
|
+
return await pollingHelper.test(polling, {
|
|
219
|
+
auth: context.auth,
|
|
220
|
+
store: context.store,
|
|
221
|
+
propsValue: context.propsValue,
|
|
222
|
+
files: context.files,
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
});
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createTrigger,
|
|
3
|
+
TriggerStrategy,
|
|
4
|
+
Property,
|
|
5
|
+
} from '@guayaba/workflows-framework';
|
|
6
|
+
import { googleCalendarCommon, googleCalendarAuth, GoogleCalendarAuthValue } from '../common';
|
|
7
|
+
import { stopWatchEvent, watchEvent, getLatestEvent } from '../common/helper';
|
|
8
|
+
import { GoogleWatchResponse, GoogleCalendarEvent } from '../common/types';
|
|
9
|
+
|
|
10
|
+
export const newEvent = createTrigger({
|
|
11
|
+
auth: googleCalendarAuth,
|
|
12
|
+
name: 'new_event',
|
|
13
|
+
displayName: 'New Event',
|
|
14
|
+
description: 'Fires when a new event is created in a calendar.',
|
|
15
|
+
props: {
|
|
16
|
+
calendar_id: googleCalendarCommon.calendarDropdown('writer'),
|
|
17
|
+
event_types: Property.StaticMultiSelectDropdown({
|
|
18
|
+
displayName: 'Event Types to Monitor',
|
|
19
|
+
description:
|
|
20
|
+
'Filter by specific event types (leave empty to monitor all event types)',
|
|
21
|
+
required: false,
|
|
22
|
+
options: {
|
|
23
|
+
options: [
|
|
24
|
+
{ label: 'Default Events', value: 'default' },
|
|
25
|
+
{ label: 'Birthday Events', value: 'birthday' },
|
|
26
|
+
{ label: 'Focus Time', value: 'focusTime' },
|
|
27
|
+
{ label: 'Out of Office', value: 'outOfOffice' },
|
|
28
|
+
{ label: 'Working Location', value: 'workingLocation' },
|
|
29
|
+
{ label: 'From Gmail', value: 'fromGmail' },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
search_filter: Property.ShortText({
|
|
34
|
+
displayName: 'Search Filter',
|
|
35
|
+
description:
|
|
36
|
+
'Only trigger for events containing this text in title, description, or location (optional)',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
exclude_all_day: Property.Checkbox({
|
|
40
|
+
displayName: 'Exclude All-Day Events',
|
|
41
|
+
description: 'Skip triggering for all-day events',
|
|
42
|
+
required: false,
|
|
43
|
+
defaultValue: false,
|
|
44
|
+
}),
|
|
45
|
+
},
|
|
46
|
+
type: TriggerStrategy.WEBHOOK,
|
|
47
|
+
sampleData: {
|
|
48
|
+
kind: 'calendar#event',
|
|
49
|
+
etag: '"3419997894982000"',
|
|
50
|
+
id: 'sample_event_id_12345',
|
|
51
|
+
status: 'confirmed',
|
|
52
|
+
htmlLink:
|
|
53
|
+
'https://www.google.com/calendar/event?eid=c2FtcGxlX2V2ZW50X2lkXzEyMzQ1',
|
|
54
|
+
created: '2025-08-15T11:02:27.000Z',
|
|
55
|
+
updated: '2025-08-15T11:02:27.491Z',
|
|
56
|
+
summary: 'Team Sync',
|
|
57
|
+
creator: { email: 'creator@example.com' },
|
|
58
|
+
organizer: {
|
|
59
|
+
email: 'creator@example.com',
|
|
60
|
+
self: true,
|
|
61
|
+
},
|
|
62
|
+
start: {
|
|
63
|
+
dateTime: '2025-08-18T10:00:00-07:00',
|
|
64
|
+
timeZone: 'America/Los_Angeles',
|
|
65
|
+
},
|
|
66
|
+
end: {
|
|
67
|
+
dateTime: '2025-08-18T11:00:00-07:00',
|
|
68
|
+
timeZone: 'America/Los_Angeles',
|
|
69
|
+
},
|
|
70
|
+
iCalUID: 'sample_event_id_12345@google.com',
|
|
71
|
+
sequence: 0,
|
|
72
|
+
reminders: { useDefault: true },
|
|
73
|
+
eventType: 'default',
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async onEnable(context) {
|
|
77
|
+
const calendarId = context.propsValue.calendar_id!;
|
|
78
|
+
const auth = context.auth as GoogleCalendarAuthValue;
|
|
79
|
+
|
|
80
|
+
const response = await watchEvent(calendarId, context.webhookUrl, auth);
|
|
81
|
+
|
|
82
|
+
await context.store.put<GoogleWatchResponse>(
|
|
83
|
+
'google_calendar_watch',
|
|
84
|
+
response
|
|
85
|
+
);
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
async onDisable(context) {
|
|
89
|
+
const auth = context.auth as GoogleCalendarAuthValue;
|
|
90
|
+
const watch = await context.store.get<GoogleWatchResponse>(
|
|
91
|
+
'google_calendar_watch'
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (watch) {
|
|
95
|
+
await stopWatchEvent(watch, auth);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
async run(context) {
|
|
100
|
+
const payload = context.payload;
|
|
101
|
+
const headers = payload.headers as Record<string, string>;
|
|
102
|
+
const { event_types, search_filter, exclude_all_day } = context.propsValue;
|
|
103
|
+
|
|
104
|
+
if (headers['x-goog-resource-state'] === 'add') {
|
|
105
|
+
const eventData = payload.body as GoogleCalendarEvent;
|
|
106
|
+
|
|
107
|
+
if (event_types && event_types.length > 0) {
|
|
108
|
+
const eventType = eventData.eventType || 'default';
|
|
109
|
+
if (!event_types.includes(eventType)) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (search_filter && search_filter.trim()) {
|
|
115
|
+
const searchTerm = search_filter.toLowerCase().trim();
|
|
116
|
+
const summary = (eventData.summary || '').toLowerCase();
|
|
117
|
+
const description = (eventData.description || '').toLowerCase();
|
|
118
|
+
const location = (eventData.location || '').toLowerCase();
|
|
119
|
+
|
|
120
|
+
const matchesSearch =
|
|
121
|
+
summary.includes(searchTerm) ||
|
|
122
|
+
description.includes(searchTerm) ||
|
|
123
|
+
location.includes(searchTerm);
|
|
124
|
+
|
|
125
|
+
if (!matchesSearch) {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (exclude_all_day) {
|
|
131
|
+
const isAllDay = eventData.start?.date && !eventData.start?.dateTime;
|
|
132
|
+
if (isAllDay) {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return [eventData];
|
|
138
|
+
} else {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async test(context) {
|
|
144
|
+
const auth = context.auth as GoogleCalendarAuthValue;
|
|
145
|
+
const { event_types, search_filter, exclude_all_day } = context.propsValue;
|
|
146
|
+
|
|
147
|
+
const latestEvent = await getLatestEvent(
|
|
148
|
+
context.propsValue.calendar_id!,
|
|
149
|
+
auth
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (event_types && event_types.length > 0) {
|
|
153
|
+
const eventType = latestEvent.eventType || 'default';
|
|
154
|
+
if (!event_types.includes(eventType)) {
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (search_filter && search_filter.trim()) {
|
|
160
|
+
const searchTerm = search_filter.toLowerCase().trim();
|
|
161
|
+
const summary = (latestEvent.summary || '').toLowerCase();
|
|
162
|
+
const description = (latestEvent.description || '').toLowerCase();
|
|
163
|
+
const location = (latestEvent.location || '').toLowerCase();
|
|
164
|
+
|
|
165
|
+
const matchesSearch =
|
|
166
|
+
summary.includes(searchTerm) ||
|
|
167
|
+
description.includes(searchTerm) ||
|
|
168
|
+
location.includes(searchTerm);
|
|
169
|
+
|
|
170
|
+
if (!matchesSearch) {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (exclude_all_day) {
|
|
176
|
+
const isAllDay = latestEvent.start?.date && !latestEvent.start?.dateTime;
|
|
177
|
+
if (isAllDay) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return [latestEvent];
|
|
183
|
+
},
|
|
184
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../../tsconfig.base.json",
|
|
3
|
+
"files": [],
|
|
4
|
+
"include": [],
|
|
5
|
+
"references": [
|
|
6
|
+
{
|
|
7
|
+
"path": "./tsconfig.lib.json"
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
"compilerOptions": {
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"strict": true,
|
|
13
|
+
"noImplicitReturns": true,
|
|
14
|
+
"noFallthroughCasesInSwitch": true
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"rootDir": ".",
|
|
6
|
+
"baseUrl": ".",
|
|
7
|
+
"paths": {},
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"declaration": true,
|
|
10
|
+
"declarationMap": true,
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
|
|
14
|
+
"include": ["src/**/*.ts"]
|
|
15
|
+
}
|