Skip to content

Instantly share code, notes, and snippets.

@noperator
Last active February 2, 2023 18:10
Show Gist options
  • Save noperator/7a6a2585f27a3a838f58531b21adae53 to your computer and use it in GitHub Desktop.
Save noperator/7a6a2585f27a3a838f58531b21adae53 to your computer and use it in GitHub Desktop.
Sync home calendar events with blocks on work calendar via Google Apps Script
function main() {
const props = PropertiesService.getScriptProperties().getProperties();
// Look for created/updated/deleted events on scheduler calendar.
const events = logSyncedEvents(props['schedulerCal'], false);
console.log("got %d new event(s)", events.length);
let e = 0
for (let event of events) {
e += 1
console.log("[%d: %s] event: %s", e, event.id, event.summary);
// If the scheduler calendar event doesn't have home email address as attendee, then add it.
if (event.status != "cancelled") {
if (event.attendees && event.attendees.length > 0) {
if (!event.attendees.map(({ email }) => email).includes(props['homeEmail'])) {
console.log("[%d: %s] adding attendee", e, event.id);
event.attendees.push({ email: props['homeEmail'] });
updateEvent(props['schedulerCal'], event.id, event);
}
} else {
console.log("[%d: %s] adding attendee", e, event.id);
event.attendees = [{ email: props['homeEmail'] }];
updateEvent(props['schedulerCal'], event.id, event);
}
}
// Look for blocks on blocker calendar whose description contains the scheduler calendar event ID.
const blocks = Calendar.Events.list(props['blockerCal'], {
q: event.id,
});
if (blocks.items && blocks.items.length > 0) {
console.log("[%d: %s] found matching block", e, event.id);
let block = blocks.items[0];
// If the scheduler calendar event was deleted, then delete its matching block on the blocker calendar.
if (event.status == "cancelled") {
console.log("[%d: %s] deleting block", e, event.id);
deleteEvent(props['blockerCal'], block.id);
// If the scheduler calendar event was updated, then update the corresponding blocker calendar block if needed.
} else {
const eventRecurrence = (event.recurrence ? event.recurrence : null);
const blockRecurrence = (block.recurrence ? block.recurrence : null);
if (block.start != event.start || block.end != event.end || blockRecurrence != eventRecurrence) {
console.log("[%d: %s] updating block", e, event.id);
block.start = event.start;
block.end = event.end;
delete block.recurrence;
if (eventRecurrence) {
block.recurrence = eventRecurrence
}
updateEvent(props['blockerCal'], block.id, block);
}
}
// If there's no matching block on the blocker calendar, then create it.
} else {
if (event.status != "cancelled") {
console.log("[%d: %s] creating block", e, event.id);
let block = {
summary: "🟢 BLOCK",
description: event.id,
start: event.start,
end: event.end,
attendees: [{ email: props['workEmail'] }],
};
if (event.recurrence) {
block.recurrence = event.recurrence
}
createEvent(props['blockerCal'], block);
}
}
}
}
function deleteEvent(calendarId, eventId) {
try {
const deletedEvent = Calendar.Events.remove(calendarId, eventId, {
sendUpdates: "all",
});
console.log("successfully deleted event: %s", deletedEvent.id);
} catch (e) {
console.log("delete failed with error: %s", e.message);
}
}
function updateEvent(calendarId, eventId, event) {
try {
const updatedEvent = Calendar.Events.update(event, calendarId, eventId, {
sendUpdates: "all",
});
console.log("successfully updated event: %s ", updatedEvent.id);
} catch (e) {
console.log("update failed with error: %s", e.message);
}
}
function createEvent(calendarId, event) {
try {
const createdEvent = Calendar.Events.insert(event, calendarId, {
sendUpdates: "all",
});
console.log("successfully created event: %s", createdEvent.id);
} catch (e) {
console.log("create failed with error: %s", e.message);
}
}
function getRelativeDate(daysOffset, hour) {
const date = new Date();
date.setDate(date.getDate() + daysOffset);
date.setHours(hour);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
}
function logSyncedEvents(calendarId, fullSync) {
const properties = PropertiesService.getUserProperties();
const options = {
maxResults: 100,
};
const syncToken = properties.getProperty("syncToken");
if (syncToken && !fullSync) {
options.syncToken = syncToken;
} else {
// Sync events up to thirty days in the past.
options.timeMin = getRelativeDate(-30, 0).toISOString();
}
// Retrieve events one page at a time.
let evts = [];
let events;
let pageToken;
do {
try {
options.pageToken = pageToken;
events = Calendar.Events.list(calendarId, options);
} catch (e) {
// Check to see if the sync token was invalidated by the server; if so, perform a full sync instead.
if (
e.message.includes("Sync token is no longer valid")
) {
properties.deleteProperty("syncToken");
return logSyncedEvents(calendarId, true);
} else {
throw new Error(e.message);
}
}
if (events.items && events.items.length > 0) {
}
evts = evts.concat(events.items);
pageToken = events.nextPageToken;
} while (pageToken);
properties.setProperty("syncToken", events.nextSyncToken);
return evts;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment