Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Fix vacation calendar code #209

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 106 additions & 77 deletions vacation-calendar/src/Code.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
// FROM: https://developers.google.com/workspace/solutions/vacation-calendar
// FROM: https://gist.github.com/jc00ke/5b77c0fe7f6435061c37005731556b58

// Set the ID of the team calendar to add events to. You can find the calendar's
// ID on the settings page.
var TEAM_CALENDAR_ID = 'ENTER_TEAM_CALENDAR_ID_HERE';
// Include other Holiday calendars
var HOLIDAY_CALENDARS = new Map([
// ['HCALENDAR_TITLE', 'HCALENDAR_URL'],
])
// Set the email address of the Google Group that contains everyone in the team.
// Ensure the group has less than 500 members to avoid timeouts.
var GROUP_EMAIL = 'ENTER_GOOGLE_GROUP_EMAIL_HERE';
// Set some emails to ignore
var EMAILS_SKIP = [
]

var KEYWORDS = ['vacation', 'ooo', 'out of office', 'offline'];
var MONTHS_IN_ADVANCE = 3;
var MONTHS_BEFORE = 3;
var MONTHS_IN_ADVANCE = 6;

/**
* Setup the script to run automatically every hour.
Expand All @@ -16,85 +26,123 @@ function setup() {
if (triggers.length > 0) {
throw new Error('Triggers are already setup.');
}
ScriptApp.newTrigger('sync').timeBased().everyHours(1).create();
ScriptApp.newTrigger('syncSched').timeBased().everyHours(1).create();
// Run the first sync immediately.
sync();
syncForce();
}

function syncSched() {
// Determine the time the the script was last run.
var lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
lastRun = lastRun ? new Date(lastRun) : null;

return sync(lastRun)
}

function syncForce() {
return sync(null);
}

/**
* Look through the group members' public calendars and add any
* 'vacation' or 'out of office' events to the team calendar.
*/
function sync() {
function sync(lastRun) {
// Define the calendar event date range to search.
var today = new Date();
var minDate = new Date();
var maxDate = new Date();
minDate.setMonth(minDate.getMonth() - MONTHS_BEFORE);
maxDate.setMonth(maxDate.getMonth() + MONTHS_IN_ADVANCE);

// Determine the time the the script was last run.
var lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
lastRun = lastRun ? new Date(lastRun) : null;
//console.log('Date Range: ', minDate, ' => ', maxDate);

// Get the list of users in the Google Group.
var users = GroupsApp.getGroupByEmail(GROUP_EMAIL).getUsers();
var users = People.People.listDirectoryPeople({ "readMask": "names,emailAddresses", "sources": ["DIRECTORY_SOURCE_TYPE_DOMAIN_PROFILE"] }).people;

// For each user, find events having one or more of the keywords in the event
// summary in the specified date range. Import each of those to the team
// For each user, find outOfOffice events in the specified date range. Import each of those to the team
// calendar.
var count = 0;
users.forEach(function(user) {
var username = user.getEmail().split('@')[0];
KEYWORDS.forEach(function(keyword) {
var events = findEvents(user, keyword, today, maxDate, lastRun);
events.forEach(function(event) {
importEvent(username, event);
count++;
}); // End foreach event.
}); // End foreach keyword.
}); // End foreach user.

// User OOO
users.forEach(function (user) {
user.emailAddresses.forEach(function (email) {
var user_email = email.value;
var username = user_email.split('@')[0]; // Fallback "username"
var user_names = user.getNames()
if (user_names !== undefined && user_names.length > 0) {
if (user_names.length > 1) {
console.warn("User [", username, "] has multiple names, taking the first one.");
}
username = user_names[0].displayName;
}
// Only Company Emails
if (user_email.split('@')[1] == GROUP_EMAIL.split('@')[1] && EMAILS_SKIP.indexOf(user_email) == -1) {
//console.log('Checking: ', user_email);
var events = findCalendarEvents(user_email, minDate, maxDate, lastRun, sie=sieOOO);
events.forEach(function (event) {
importEvent(username, event);
count++;
}); // End foreach event.
}
else {
// console.log('Skipping: ', user_email);
}
})
});

// Holiday Calendars
HOLIDAY_CALENDARS.forEach(function (hcid, hlabel) {
//console.log("Checking Holidays @", hlabel);
var events = findCalendarEvents(hcid, minDate, maxDate, lastRun, sie=sieAll);
events.forEach(function (event) {
var prefix = "Holiday @ " + hlabel;
event.id = null; // Generate a new event ID
// TODO: Check Regional holidays (name ends with " ($LOCATION)")
importEvent(prefix, event);
count++;
}); // End foreach event.
});

PropertiesService.getScriptProperties().setProperty('lastRun', today);
console.log('Imported ' + count + ' events');
}

function cleanTeam() {
var events = findCalendarEvents(TEAM_CALENDAR_ID, null, null, null, sie=sieAll);
events.forEach(function (event) {
console.log('Delete[%s]', event.id);
Calendar.Events.remove(TEAM_CALENDAR_ID, event.id);
});
}

/**
* Imports the given event from the user's calendar into the shared team
* calendar.
* @param {string} username The team member that is attending the event.
* Imports the given event into the shared team calendar.
* @param {string} eprefix: Event Prefix
* @param {Calendar.Event} event The event to import.
*/
function importEvent(username, event) {
event.summary = '[' + username + '] ' + event.summary;
function importEvent(eprefix, event) {
if (eprefix) {
event.summary = '[' + eprefix + '] ' + event.summary;
}
event.organizer = {
id: TEAM_CALENDAR_ID,
};
event.attendees = [];
console.log('Importing: %s', event.summary);
console.log('Importing[%s]: %s', event.id, event.summary);
try {
Calendar.Events.import(event, TEAM_CALENDAR_ID);
} catch (e) {
console.error('Error attempting to import event: %s. Skipping.',
e.toString());
console.error('SKIP: Error attempting to import event: %s', e.toString());
}
}

/**
* In a given user's calendar, look for occurrences of the given keyword
* in events within the specified date range and return any such events
* found.
* @param {Session.User} user The user to retrieve events for.
* @param {string} keyword The keyword to look for.
* @param {Date} start The starting date of the range to examine.
* @param {Date} end The ending date of the range to examine.
* @param {Date} optSince A date indicating the last time this script was run.
* @return {Calendar.Event[]} An array of calendar events.
*/
function findEvents(user, keyword, start, end, optSince) {
function findCalendarEvents(user, start, end, optSince, sie) {
var params = {
q: keyword,
timeMin: formatDateAsRFC3339(start),
timeMax: formatDateAsRFC3339(end),
showDeleted: true,
};
if (start && end) {
params['timeMin'] = formatDateAsRFC3339(start);
params['timeMax'] = formatDateAsRFC3339(end);
params['showDeleted'] = true;
};
if (optSince) {
// This prevents the script from examining events that have not been
Expand All @@ -108,45 +156,26 @@ function findEvents(user, keyword, start, end, optSince) {
params.pageToken = pageToken;
var response;
try {
response = Calendar.Events.list(user.getEmail(), params);
console.log("Retrieving events for %s", user)
response = Calendar.Events.list(user, params);
} catch (e) {
console.error('Error retriving events for %s, %s: %s; skipping',
user, keyword, e.toString());
console.error('Error retriving events for %s: %s; skipping',
user, e.toString());
continue;
}
events = events.concat(response.items.filter(function(item) {
return shoudImportEvent(user, keyword, item);
}));
events = events.concat(response.items.filter(sie));
pageToken = response.nextPageToken;
} while (pageToken);
return events;
}

/**
* Determines if the given event should be imported into the shared team
* calendar.
* @param {Session.User} user The user that is attending the event.
* @param {string} keyword The keyword being searched for.
* @param {Calendar.Event} event The event being considered.
* @return {boolean} True if the event should be imported.
*/
function shoudImportEvent(user, keyword, event) {
// Filter out events where the keyword did not appear in the summary
// (that is, the keyword appeared in a different field, and are thus
// is not likely to be relevant).
if (event.summary.toLowerCase().indexOf(keyword.toLowerCase()) < 0) {
return false;
}
if (!event.organizer || event.organizer.email == user.getEmail()) {
// If the user is the creator of the event, always import it.
return true;
}
// Only import events the user has accepted.
if (!event.attendees) return false;
var matching = event.attendees.filter(function(attendee) {
return attendee.self;
});
return matching.length > 0 && matching[0].responseStatus == 'accepted';
// SIE: All
function sieAll(event) {
return true;
}
// SIE: Out of Office Events
function sieOOO(event) {
return event.eventType == "outOfOffice"
}

/**
Expand Down