Skip to content

Commit 034ae38

Browse files
committed
Adding upcoming-event-alert-polling source
1 parent 37578e6 commit 034ae38

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export default {
2+
"kind": "calendar#event",
3+
"etag": "\"3442838491454000\"",
4+
"id": "0dip62r3f3d85o35jjnjcmqbmo",
5+
"status": "confirmed",
6+
"htmlLink": "https://www.google.com/calendar/event?eid=MGRpcDYycjNW8zNWgbWljaGVsbGUucGlwZWRyZWFtQG0",
7+
"created": "2024-07-19T20:00:45.000Z",
8+
"updated": "2024-07-19T20:00:45.727Z",
9+
"summary": "Upcoming Meeting",
10+
"creator": {
11+
"email": "test@sample.com",
12+
"self": true
13+
},
14+
"organizer": {
15+
"email": "test@sample.com",
16+
"self": true
17+
},
18+
"start": {
19+
"dateTime": "2024-07-19T16:07:00-04:00",
20+
"timeZone": "America/Detroit"
21+
},
22+
"end": {
23+
"dateTime": "2024-07-19T17:07:00-04:00",
24+
"timeZone": "America/Detroit"
25+
},
26+
"iCalUID": "0dip62r35jjnjcmqbmo@google.com",
27+
"sequence": 0,
28+
"reminders": {
29+
"useDefault": true
30+
},
31+
"eventType": "default"
32+
}
33+
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import common from "../common/common.mjs";
2+
import sampleEmit from "./test-event.mjs";
3+
4+
export default {
5+
...common,
6+
key: "google_calendar-upcoming-event-alert-polling",
7+
name: "New Upcoming Event Alert (Polling)",
8+
description: "Emit new event based on a time interval before an upcoming event in the calendar. [See the documentation](https://developers.google.com/calendar/api/v3/reference/events/list)",
9+
version: "0.0.1",
10+
type: "source",
11+
dedupe: "unique",
12+
props: {
13+
...common.props,
14+
db: "$.service.db",
15+
pollingInfo: {
16+
type: "alert",
17+
alertType: "info",
18+
content: "Since this source executes based on a timer, event emission may be slightly delayed. For example, if the source runs every 5 minutes, the delay may be up to 5 minutes. You can use the `upcoming-event-alert` source for instant event emission.",
19+
},
20+
calendarId: {
21+
propDefinition: [
22+
common.props.googleCalendar,
23+
"calendarId",
24+
],
25+
},
26+
eventTypes: {
27+
propDefinition: [
28+
common.props.googleCalendar,
29+
"eventTypes",
30+
],
31+
},
32+
minutesBefore: {
33+
type: "integer",
34+
label: "Minutes Before",
35+
description: "Number of minutes to trigger before the start of the calendar event.",
36+
min: 0,
37+
default: 30,
38+
},
39+
},
40+
methods: {
41+
...common.methods,
42+
_getEmittedEvents() {
43+
return this.db.get("emittedEvents") || {};
44+
},
45+
_setEmittedEvents(emittedEvents) {
46+
this.db.set("emittedEvents", emittedEvents);
47+
},
48+
_cleanupEmittedEvents(now) {
49+
const emittedEvents = this._getEmittedEvents();
50+
const cleanedEvents = {};
51+
let cleanedCount = 0;
52+
53+
// Keep only events that haven't passed yet
54+
for (const [
55+
eventId,
56+
startTime,
57+
] of Object.entries(emittedEvents)) {
58+
if (startTime > now.getTime()) {
59+
cleanedEvents[eventId] = startTime;
60+
} else {
61+
cleanedCount++;
62+
}
63+
}
64+
65+
if (cleanedCount > 0) {
66+
console.log(`Cleaned up ${cleanedCount} past event(s) from emitted events tracker`);
67+
this._setEmittedEvents(cleanedEvents);
68+
}
69+
70+
return cleanedEvents;
71+
},
72+
getConfig({ now }) {
73+
// Get events starting from now up to the alert window
74+
const timeMin = now.toISOString();
75+
// Look ahead to find events within our alert window
76+
const alertWindowMs = this.minutesBefore * 60 * 1000;
77+
const timeMax = new Date(now.getTime() + alertWindowMs).toISOString();
78+
79+
return {
80+
calendarId: this.calendarId,
81+
timeMin,
82+
timeMax,
83+
eventTypes: this.eventTypes,
84+
singleEvents: true,
85+
orderBy: "startTime",
86+
};
87+
},
88+
isRelevant(event, { now }) {
89+
// Skip cancelled events
90+
if (event.status === "cancelled") {
91+
return false;
92+
}
93+
94+
// Get event start time
95+
const startTime = event.start
96+
? new Date(event.start.dateTime || event.start.date)
97+
: null;
98+
99+
if (!startTime) {
100+
return false;
101+
}
102+
103+
// Calculate time remaining until event starts (in milliseconds)
104+
const timeRemaining = startTime.getTime() - now.getTime();
105+
106+
// Skip past events
107+
if (timeRemaining < 0) {
108+
return false;
109+
}
110+
111+
// Convert minutesBefore to milliseconds
112+
const alertThresholdMs = this.minutesBefore * 60 * 1000;
113+
114+
// Clean up old emitted events and get the current list
115+
const emittedEvents = this._cleanupEmittedEvents(now);
116+
117+
// Check if we've already emitted this event
118+
if (emittedEvents[event.id]) {
119+
return false;
120+
}
121+
122+
// Emit if time remaining is less than or equal to the alert threshold
123+
if (timeRemaining <= alertThresholdMs) {
124+
// Mark this event as emitted with its start time for future cleanup
125+
emittedEvents[event.id] = startTime.getTime();
126+
this._setEmittedEvents(emittedEvents);
127+
return true;
128+
}
129+
130+
return false;
131+
},
132+
generateMeta(event) {
133+
const {
134+
summary,
135+
id,
136+
} = event;
137+
return {
138+
summary: `Upcoming: ${summary || `Event ID: ${id}`}`,
139+
id: `${id}-${Date.now()}`,
140+
ts: Date.now(),
141+
};
142+
},
143+
},
144+
hooks: {
145+
async deploy() {
146+
// On initial deploy, don't emit historical events
147+
// Just initialize the emitted events tracker
148+
this._setEmittedEvents({});
149+
},
150+
},
151+
sampleEmit,
152+
};
153+

0 commit comments

Comments
 (0)