Last active
December 13, 2024 19:45
-
-
Save tyrann0us/af4a553d116d00e783fff5404fcb2b97 to your computer and use it in GitHub Desktop.
Generate a free/busy ICS file from your Google Calendar using this Google Apps Script, which securely saves the file to your Google Drive. Share your availability without exposing event details. All data processing and storage occur within your Google account.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Generates an ICS file containing free/busy information from your Google Calendar | |
* and saves it to Google Drive. | |
*/ | |
function generateICS() { | |
// Configuration | |
var calendarId = 'primary'; // Calendar ID to read events from | |
var fileName = 'busy.ics'; // Name of the ICS file to be saved in Google Drive | |
var eventTitle = 'Busy'; // Title for the events in the ICS file | |
var organizationName = 'Acme Corporation'; // Name for the PRODID property | |
var pastMonths = 2; // Number of months to include past events | |
var futureMonths = 6; // Number of months to include future events | |
// Specify which event statuses to include | |
var includedStatuses = [ | |
CalendarApp.GuestStatus.OWNER, // Include events you created | |
CalendarApp.GuestStatus.YES, // Include events you accepted | |
// Add more statuses if needed (e.g., MAYBE, INVITED) | |
]; | |
// Get the date range (from current date minus pastMonths to current date plus futureMonths) | |
var now = new Date(); | |
var startDate = addMonths(now, -pastMonths); | |
var endDate = addMonths(now, futureMonths); | |
// Fetch the events we want to include | |
var events = getEvents(calendarId, startDate, endDate, includedStatuses); | |
// Generate ICS content | |
var icsContent = generateICSContent(events, eventTitle, organizationName); | |
// Save ICS file to Google Drive | |
saveICSToDrive(icsContent, fileName); | |
} | |
/** | |
* Retrieves events from the specified calendar within the given date range | |
* and filters them based on the specified statuses and conditions. | |
* | |
* @param {string} calendarId - The ID of the calendar. | |
* @param {Date} startDate - The start date of the range. | |
* @param {Date} endDate - The end date of the range. | |
* @param {Array} includedStatuses - Array of CalendarApp.GuestStatus constants to include. | |
* @return {Array} Array of event objects containing start and end times. | |
*/ | |
function getEvents(calendarId, startDate, endDate, includedStatuses) { | |
var calendar = CalendarApp.getCalendarById(calendarId); | |
var events = calendar.getEvents(startDate, endDate); | |
var includedEvents = []; | |
events.forEach(function(event) { | |
// Check the event's guest status | |
var myStatus = event.getMyStatus(); | |
// Include the event only if its status is in the includedStatuses array | |
if (includedStatuses.indexOf(myStatus) === -1) { | |
return; // Skip this event | |
} | |
// Skip all-day events | |
if (event.isAllDayEvent()) { | |
return; | |
} | |
// Include event | |
includedEvents.push({ | |
start: event.getStartTime(), | |
end: event.getEndTime() | |
}); | |
}); | |
return includedEvents; | |
} | |
/** | |
* Generates ICS file content from the events. | |
* | |
* @param {Array} events - Array of event objects containing start and end times. | |
* @param {string} eventTitle - The title to use for the events in the ICS file. | |
* @param {string} organizationName - The organization name for the PRODID property. | |
* @return {string} The ICS file content as a string. | |
*/ | |
function generateICSContent(events, eventTitle, organizationName) { | |
var uidDomain = CalendarApp.getDefaultCalendar().getId(); | |
var icsLines = [ | |
'BEGIN:VCALENDAR', | |
'VERSION:2.0', | |
'PRODID:-//' + organizationName + '//FreeBusy Calendar//EN', | |
'CALSCALE:GREGORIAN', | |
'METHOD:PUBLISH' | |
]; | |
events.forEach(function(ev, index) { | |
var eventUID = 'event-' + index + '@' + uidDomain; | |
var dtStamp = formatDate(new Date()); | |
var dtStart = formatDate(ev.start); | |
var dtEnd = formatDate(ev.end); | |
icsLines.push('BEGIN:VEVENT'); | |
icsLines.push('UID:' + eventUID); | |
icsLines.push('DTSTAMP:' + dtStamp); | |
icsLines.push('DTSTART:' + dtStart); | |
icsLines.push('DTEND:' + dtEnd); | |
icsLines.push('SUMMARY:' + eventTitle); | |
icsLines.push('CLASS:PRIVATE'); | |
icsLines.push('END:VEVENT'); | |
}); | |
icsLines.push('END:VCALENDAR'); | |
return icsLines.join('\r\n'); | |
} | |
/** | |
* Saves the ICS content to a file in Google Drive. | |
* | |
* @param {string} icsContent - The ICS file content. | |
* @param {string} fileName - The name of the file to save. | |
*/ | |
function saveICSToDrive(icsContent, fileName) { | |
// Search for existing file | |
var files = DriveApp.getFilesByName(fileName); | |
var file; | |
if (files.hasNext()) { | |
file = files.next(); | |
file.setContent(icsContent); | |
} else { | |
file = DriveApp.createFile(fileName, icsContent, MimeType.PLAIN_TEXT); | |
} | |
// Set sharing permissions to "Anyone with the link can view" | |
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW); | |
Logger.log('ICS file has been saved and is accessible at: ' + file.getDownloadUrl()); | |
} | |
/** | |
* Formats a Date object into a string suitable for ICS files (UTC time). | |
* | |
* @param {Date} date - The date to format. | |
* @return {string} The formatted date string in 'yyyyMMddTHHmmssZ' format. | |
*/ | |
function formatDate(date) { | |
return Utilities.formatDate(date, 'UTC', 'yyyyMMdd\'T\'HHmmss\'Z\''); | |
} | |
/** | |
* Adds a specified number of months to a date. | |
* | |
* @param {Date} date - The original date. | |
* @param {number} months - Number of months to add (can be negative). | |
* @return {Date} The new date. | |
*/ | |
function addMonths(date, months) { | |
var d = new Date(date); | |
var day = d.getDate(); | |
d.setMonth(d.getMonth() + months); | |
// Handle end-of-month issues | |
if (d.getDate() < day) { | |
d.setDate(0); | |
} | |
return d; | |
} |
If you find this helpful, please consider buying me a coffee ☕ or beer 🍻 at https://paypal.me/pbammes. Thank you so much! 🙇
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Instructions for Use:
Copy and Paste the Script:
Adjust Configuration Variables:
calendarId
:'primary'
for your primary calendar or find the ID in Google Calendar settings under "Integrate calendar".fileName
:'my-freebusy.ics'
).eventTitle
:'Busy'
).organizationName
:PRODID
property.pastMonths
andfutureMonths
:includedStatuses
:CalendarApp.GuestStatus.YES
- AcceptedCalendarApp.GuestStatus.NO
- DeclinedCalendarApp.GuestStatus.MAYBE
- TentativeCalendarApp.GuestStatus.INVITED
- Invited but not yet respondedCalendarApp.GuestStatus.OWNER
- You are the organizer of the eventAuthorize the Script:
generateFreeBusyICS
.Set Up Triggers:
generateFreeBusyICS
as the function to run.Access the ICS File:
View
>Logs
) to find the direct download URL of the ICS file.https://drive.google.com/uc?export=download&id=FILE_ID
.Subscribe to the Calendar: