Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

6.0 html and json #400

Closed
wants to merge 18 commits into from
149 changes: 79 additions & 70 deletions Code.gs
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,20 @@
* 1) Make a copy:
* New Interface: Go to the project overview icon on the left (looks like this: ⓘ), then click the "copy" icon on the top right (looks like two files on top of each other)
* Old Interface: Click in the menu "File" > "Make a copy..." and make a copy to your Google Drive
* 2) Settings: Change lines 24-50 to be the settings that you want to use
* 3) Install:
* New Interface: Make sure your toolbar says "install" to the right of "Debug", then click "Run"
* Old Interface: Click "Run" > "Run function" > "install"
* 4) Authorize: You will be prompted to authorize the program and will need to click "Advanced" > "Go to GAS-ICS-Sync (unsafe)"
* 2) Authorize: You will be prompted to authorize the program and will need to click "Advanced" > "Go to GAS-ICS-Sync (unsafe)"
* (For steps to follow in authorization, see this video: https://youtu.be/_5k10maGtek?t=1m22s )
* 5) You can also run "startSync" if you want to sync only once (New Interface: change the dropdown to the right of "Debug" from "install" to "startSync")
* 3) Calendars and Settings: Click on "Deploy" (in the upper-right)-->"Test Deployments". In the dialog box that pops up, click the URL under web app. This will bring up the Calendar
* Manager webpage. From here you can manage global app setings, install, uninstall, and add/edit/delete calendars and their associated settings. Two files called appSettings.json
* and calendars.json will be saved to your My Drive folder in Google Drive. You can manually edit the json files in Google Drive if you don't want to use the html interface.
*
* **To stop the Script from running click in the menu "Run" > "Run function" > "uninstall" (New Interface: change the dropdown to the right of "Debug" from "install" to "uninstall")
* Manual Interface:
* 1) Install:
* New Interface: Make sure your toolbar says "install" to the right of "Debug", then click "Run"
* Old Interface: Click "Run" > "Run function" > "install"
* 2) You can also run "startSync" if you want to sync only once (New Interface: change the dropdown to the right of "Debug" from "install" to "startSync").
* 3) To stop the Script from running click in the menu "Run" > "Run function" > "uninstall" (New Interface: change the dropdown to the right of "Debug" from "install" to "uninstall")
*
*=========================================
* SETTINGS
*=========================================
*/

var sourceCalendars = [ // The ics/ical urls that you want to get events from along with their target calendars (list a new row for each mapping of ICS url to Google Calendar)
// For instance: ["https://p24-calendars.icloud.com/holidays/us_en.ics", "US Holidays"]
// Or with colors following mapping https://developers.google.com/apps-script/reference/calendar/event-color,
// for instance: ["https://p24-calendars.icloud.com/holidays/us_en.ics", "US Holidays", "11"]
["icsUrl1", "targetCalendar1"],
["icsUrl2", "targetCalendar2"],
["icsUrl3", "targetCalendar1"]

];

var howFrequent = 15; // What interval (minutes) to run this script on to check for new events. Any integer can be used, but will be rounded up to 5, 10, 15, 30 or to the nearest hour after that.. 60, 120, etc. 1440 (24 hours) is the maximum value. Anything above that will be replaced with 1440.
var onlyFutureEvents = false; // If you turn this to "true", past events will not be synced (this will also removed past events from the target calendar if removeEventsFromCalendar is true)
var addEventsToCalendar = true; // If you turn this to "false", you can check the log (View > Logs) to make sure your events are being read correctly before turning this on
var modifyExistingEvents = true; // If you turn this to "false", any event in the feed that was modified after being added to the calendar will not update
var removeEventsFromCalendar = true; // If you turn this to "true", any event created by the script that is not found in the feed will be removed.
var removePastEventsFromCalendar = true; // If you turn this to "false", any event that is in the past will not be removed.
var addAlerts = "yes"; // Whether to add the ics/ical alerts as notifications on the Google Calendar events or revert to the calendar's default reminders ("yes", "no", "default").
var addOrganizerToTitle = false; // Whether to prefix the event name with the event organiser for further clarity
var descriptionAsTitles = false; // Whether to use the ics/ical descriptions as titles (true) or to use the normal titles as titles (false)
var addCalToTitle = false; // Whether to add the source calendar to title
var addAttendees = false; // Whether to add the attendee list. If true, duplicate events will be automatically added to the attendees' calendar.
var defaultAllDayReminder = -1; // Default reminder for all day events in minutes before the day of the event (-1 = no reminder, the value has to be between 0 and 40320)
// See https://github.com/derekantrican/GAS-ICS-Sync/issues/75 for why this is neccessary.
var overrideVisibility = ""; // Changes the visibility of the event ("default", "public", "private", "confidential"). Anything else will revert to the class value of the ICAL event.
var addTasks = false;

var emailSummary = false; // Will email you when an event is added/modified/removed to your calendar
var email = ""; // OPTIONAL: If "emailSummary" is set to true or you want to receive update notifications, you will need to provide your email address
var customEmailSubject = ""; // OPTIONAL: If you want to change the email subject, provide a custom one here. Default: "GAS-ICS-Sync Execution Summary"
var dateFormat = "YYYY-MM-DD" // date format in the email summary (e.g. "YYYY-MM-DD", "DD.MM.YYYY", "MM/DD/YYYY". separators are ".", "-" and "/")

/*
*=========================================
* ABOUT THE AUTHOR
*=========================================
*
Expand Down Expand Up @@ -97,12 +63,19 @@ var dateFormat = "YYYY-MM-DD" // date format in the email summary (e
//!!!!!!!!!!!!!!!! DO NOT EDIT BELOW HERE UNLESS YOU REALLY KNOW WHAT YOU'RE DOING !!!!!!!!!!!!!!!!!!!!
//=====================================================================================================

var appSettings = getAppSettings(); //set appSettings object from appSettings.json
var howFrequent = appSettings.howFrequent;
var emailSummary = appSettings.emailSummary === true;
var email = appSettings.email;
var customEmailSubject = appSettings.customEmailSubject;
var dateFormat = appSettings.dateFormat;

var defaultMaxRetries = 10; // Maximum number of retries for api functions (with exponential backoff)

function install() {
// Delete any already existing triggers so we don't create excessive triggers
deleteAllTriggers();

// Schedule sync routine to explicitly repeat and schedule the initial sync
var adjustedMinutes = getValidTriggerFrequency(howFrequent);
if (adjustedMinutes >= 60) {
Expand All @@ -127,6 +100,7 @@ function install() {

function uninstall(){
deleteAllTriggers();
Logger.log("Uninstall successful.")
}

var startUpdateTime;
Expand All @@ -138,7 +112,6 @@ var icsEventsIds = [];
var calendarEventsMD5s = [];
var recurringEvents = [];
var targetCalendarId;
var targetCalendarName;

// Per-session global variables (must NOT be reset before processing each new calendar!)
var addedEvents = [];
Expand All @@ -152,52 +125,86 @@ function startSync(){
}

PropertiesService.getUserProperties().setProperty('LastRun', new Date().getTime());

if (onlyFutureEvents)
startUpdateTime = new ICAL.Time.fromJSDate(new Date());

var currentDate = new Date();
//Disable email notification if no mail adress is provided
emailSummary = emailSummary && email != "";

sourceCalendars = condenseCalendarMap(sourceCalendars);
for (var calendar of sourceCalendars){
//retrieve calendar data from calendars.json
function getCalendarsFromJson() {
var files = DriveApp.getFilesByName('calendars.json');
if (!files.hasNext()) {
throw new Error('calendars.json not found');
}
var file = files.next();
var content = file.getBlob().getDataAsString();
return JSON.parse(content);
}
var sourceCalendars = getCalendarsFromJson();

for (var key in sourceCalendars){
if (sourceCalendars.hasOwnProperty(key)) {
var calendar = sourceCalendars[key];
var calendarConfig = Object.assign(calendar);
}

//------------------------ Reset globals ------------------------
calendarEvents = [];
calendarEventsIds = [];
icsEventsIds = [];
calendarEventsMD5s = [];
recurringEvents = [];

targetCalendarName = calendar[0];
var sourceCalendarURLs = calendar[1];
var vevents;

if (calendarConfig.onlyFutureEvents) {
startUpdateTime = new ICAL.Time.fromJSDate(new Date(currentDate.setDate(currentDate.getDate() - calendarConfig.getPastDaysIfOnlyFutureEvents)));
}

//------------------------ Determine whether to sync each calendar based on SyncDelay ------------------------
let sourceSyncDelay = Number(calendarConfig.sourceSyncDelay)*60*1000;
let currentTime = Number(new Date().getTime());
let lastSyncTime = Number(PropertiesService.getUserProperties().getProperty(calendarConfig.sourceCalendarName));
var lastSyncDelta = currentTime - lastSyncTime;

if (isNaN(sourceSyncDelay)) {
Logger.log("Syncing " + calendarConfig.sourceCalendarName + " because no SyncDelay defined.");
} else if (lastSyncDelta >= sourceSyncDelay) {
Logger.log("Syncing " + calendarConfig.sourceCalendarName + " because lastSyncDelta ("+ (lastSyncDelta/60/1000).toFixed(1) + ") is greater than sourceSyncDelay (" + (sourceSyncDelay/60/1000).toFixed(0) + ").");
} else if (lastSyncDelta < sourceSyncDelay) {
Logger.log("Skipping " + calendarConfig.sourceCalendarName + " because lastSyncDelta ("+ (lastSyncDelta/60/1000).toFixed(1) + ") is less than sourceSyncDelay (" + (sourceSyncDelay/60/1000).toFixed(0) + ").");
continue;
}

//------------------------ Fetch URL items ------------------------
var responses = fetchSourceCalendars(sourceCalendarURLs);
Logger.log("Syncing " + responses.length + " calendars to " + targetCalendarName);
var responses = fetchSourceCalendars([[calendarConfig.sourceURL, calendarConfig.color]]);
//Skip the source calendar if a 5xx or 4xx error is returned. This prevents deleting all of the existing entries if the URL call fails.
if (responses.length == 0){
Logger.log("Error Syncing " + calendarConfig.sourceCalendarName + ". Skipping...");
continue;
}
Logger.log("Syncing " + calendarConfig.sourceCalendarName + " calendar to " + calendarConfig.targetCalendarName);

//------------------------ Get target calendar information------------------------
var targetCalendar = setupTargetCalendar(targetCalendarName);
var targetCalendar = setupTargetCalendar(calendarConfig.targetCalendarName);
targetCalendarId = targetCalendar.id;
Logger.log("Working on calendar: " + targetCalendarId);
Logger.log("Working on target calendar: " + targetCalendarId);

//------------------------ Parse existing events --------------------------
if(addEventsToCalendar || modifyExistingEvents || removeEventsFromCalendar){
if(calendarConfig.addEventsToCalendar || calendarConfig.modifyExistingEvents || calendarConfig.removeEventsFromCalendar){
var eventList =
callWithBackoff(function(){
return Calendar.Events.list(targetCalendarId, {showDeleted: false, privateExtendedProperty: "fromGAS=true", maxResults: 2500});
return Calendar.Events.list(targetCalendarId, {showDeleted: false, privateExtendedProperty: 'fromGAS=' + calendarConfig.sourceCalendarName, maxResults: 2500});
}, defaultMaxRetries);
calendarEvents = [].concat(calendarEvents, eventList.items);
//loop until we received all events
while(typeof eventList.nextPageToken !== 'undefined'){
eventList = callWithBackoff(function(){
return Calendar.Events.list(targetCalendarId, {showDeleted: false, privateExtendedProperty: "fromGAS=true", maxResults: 2500, pageToken: eventList.nextPageToken});
return Calendar.Events.list(targetCalendarId, {showDeleted: false, privateExtendedProperty: 'fromGAS=' + calendarConfig.sourceCalendarName, maxResults: 2500, pageToken: eventList.nextPageToken});
}, defaultMaxRetries);

if (eventList != null)
calendarEvents = [].concat(calendarEvents, eventList.items);
}
Logger.log("Fetched " + calendarEvents.length + " existing events from " + targetCalendarName);
Logger.log("Fetched " + calendarEvents.length + " existing events from " + calendarConfig.targetCalendarName);
for (var i = 0; i < calendarEvents.length; i++){
if (calendarEvents[i].extendedProperties != null){
calendarEventsIds[i] = calendarEvents[i].extendedProperties.private["rec-id"] || calendarEvents[i].extendedProperties.private["id"];
Expand All @@ -206,42 +213,44 @@ function startSync(){
}

//------------------------ Parse ical events --------------------------
vevents = parseResponses(responses, icsEventsIds);
vevents = parseResponses(responses, icsEventsIds, calendarConfig);
Logger.log("Parsed " + vevents.length + " events from ical sources");
}

//------------------------ Process ical events ------------------------
if (addEventsToCalendar || modifyExistingEvents){
if (calendarConfig.addEventsToCalendar || calendarConfig.modifyExistingEvents){
Logger.log("Processing " + vevents.length + " events");
var calendarTz =
callWithBackoff(function(){
return Calendar.Settings.get("timezone").value;
}, defaultMaxRetries);

vevents.forEach(function(e){
processEvent(e, calendarTz);
processEvent(e, calendarTz, targetCalendarId, calendarConfig.sourceCalendarName, calendarConfig);
});

Logger.log("Done processing events");
}

//------------------------ Remove old events from calendar ------------------------
if(removeEventsFromCalendar){
if(calendarConfig.removeEventsFromCalendar){
Logger.log("Checking " + calendarEvents.length + " events for removal");
processEventCleanup();
processEventCleanup(calendarConfig.sourceURL, calendarConfig.removePastEventsFromCalendar, calendarConfig.targetCalendarName);
Logger.log("Done checking events for removal");
}

//------------------------ Process Tasks ------------------------
if (addTasks){
if (calendarConfig.addTasks){
processTasks(responses);
}

//------------------------ Add Recurring Event Instances ------------------------
Logger.log("Processing " + recurringEvents.length + " Recurrence Instances!");
for (var recEvent of recurringEvents){
processEventInstance(recEvent);
processEventInstance(recEvent, calendarConfig);
}
//Set last sync time for given sourceCalendar
PropertiesService.getUserProperties().setProperty(calendarConfig.sourceCalendarName, new Date().getTime());
}

if ((addedEvents.length + modifiedEvents.length + removedEvents.length) > 0 && emailSummary){
Expand Down
Loading