const config = { 'source.email@gmail.com': { prefix: 'Source', color: CalendarApp.EventColor.PALE_RED, visibility: 'private' }, 'this.calendar@company.com': { target: 'Other Calendar Name', attendees: false, prefix: 'Company', filterColor: CalendarApp.EventColor.YELLOW, }, }; function getCalendars() { let pageToken; let calendars = []; do { const list = Calendar.CalendarList.list({pageToken, showHidden: true}); pageToken = list.pageToken; calendars = calendars.concat(list.items); } while (pageToken); calendars.sort(compareUsing(sortableId)); return calendars; } function compareUsing(fn) { return ((a, b) => fn(a).localeCompare(fn(b))); } function sortableId(calendar) { return calendar.id.split('@').reverse().join('@'); } function colorName(id) { return Object.keys(CalendarApp.EventColor).find(c => CalendarApp.EventColor[c] == id); } function onCalendarChanged(trigger) { copyEvents(trigger.calendarId); } function copyAll() { Object.keys(config).forEach(copyEvents); } // avoid multiple scripts running at the same time function getLock(retries = 3) { try { const lock = LockService.getScriptLock(); lock.tryLock(1000); return lock; } catch (err) { if (retries) { return getLock(retries - 1); } throw new Error('Could not obtain script lock'); } } function copyEvents(sourceId) { const lock = getLock(); if (!lock.hasLock()) { console.log('Process already running'); return; } if (!config[sourceId]) { console.log('No config for ' + sourceId); return; } console.log('Copying events from ' + sourceId); const summaryPrefix = (config[sourceId].prefix ? `[${config[sourceId].prefix}] ` : ''); const syncDays = config[sourceId].syncDays || 14; // set target calendar let targetId = Session.getActiveUser().getEmail(); if (config[sourceId].target) { targetId = getCalendars().find(c => c.summary == config[sourceId].target).id; } // start and end dates let startDate = new Date(); let endDate = new Date(); endDate.setDate(startDate.getDate() + syncDays); // delete and recreate in case things move cleanup(targetId, syncDays, summaryPrefix); // get all events const listResponse = Calendar.Events.list(sourceId, { timeMin: startDate.toISOString(), timeMax: endDate.toISOString(), singleEvents: true, }); const targetEventIds = Calendar.Events.list(targetId, { timeMin: startDate.toISOString(), timeMax: endDate.toISOString(), singleEvents: true }).items.map(e => e.id); // loop over and copy listResponse.items.forEach(function (event) { const summary = (event.summary || 'busy'); console.log('Copying event: ' + summary + ' (' + (event.start.dateTime || event.start.date) + ')'); if (targetEventIds.includes(event.id)) { console.log('Event is already shared with the target calendar, ignoring'); return; } if (config[sourceId].filterColor && config[sourceId].filterColor != event.colorId) { console.log('Ignoring event with colour: ' + (colorName(event.colorId) || 'default')); return; } try { Calendar.Events.insert({ start: event.start, end: event.end, summary: summaryPrefix + summary, description: event.description, conferenceData: event.conferenceData, colorId: config[sourceId].color || 0, visibility: config[sourceId].visibility || event.visibility || 'default', attendees: event.attendees ? [{ email: targetId, responseStatus: event.attendees.find(a => a.self).responseStatus }] : [] }, targetId, { conferenceDataVersion: 1 }); } catch (err) { console.log(err.toString()); } Utilities.sleep(500); }); lock.releaseLock(); console.log('All events copied'); } function cleanup(targetId, daysAhead, titlePrefix) { let calendar = CalendarApp.getCalendarById(targetId); let startDate = new Date(); let endDate = new Date(); endDate.setDate(startDate.getDate() + daysAhead); calendar.getEvents(startDate, endDate).forEach(function (event) { if (event.getTitle().startsWith(titlePrefix)) { console.log('Deleting event: ' + event.getTitle() + ' (' + event.getStartTime().toLocaleString('en-GB') + ')'); try { event.deleteEvent(); } catch (e) { console.log('Failed to delete: ' + e); } Utilities.sleep(500); } }); }