IAmMoonie

Google Calendar App Script for making events Private based on color

Dec 28th, 2024
92
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Configuration object for the script settings.
  3.  * @type {Object}
  4.  * @property {Set<string>} targetColors - A set of Google Calendar colour IDs that indicate events to be made private.
  5.  *                                         The colour IDs and their corresponding colours are as follows:
  6.  *                                         1: Lavender
  7.  *                                         2: Sage
  8.  *                                         3: Grape (Purple)
  9.  *                                         4: Flamingo
  10.  *                                         5: Banana
  11.  *                                         6: Tangerine
  12.  *                                         7: Peacock
  13.  *                                         8: Graphite (Grey)
  14.  *                                         9: Blueberry
  15.  *                                         10: Basil
  16.  *                                         11: Tomato
  17.  * @property {string} calendarId - The ID of the calendar to monitor.
  18.  *                                  Use "primary" for the user's default calendar or provide a specific calendar ID.
  19.  * @property {boolean} debug - A flag to enable or disable detailed logging.
  20.  *                              Set to true for debugging or monitoring and false for production to reduce log noise.
  21.  * @example
  22.  * // Customise configuration
  23.  * const CONFIG = {
  24.  *   targetColors: new Set(["2", "4"]), // Sage and Flamingo
  25.  *   calendarId: "[email protected]",
  26.  *   debug: false // Disable detailed logging
  27.  * };
  28.  */
  29. const CONFIG = {
  30.   targetColors: new Set(["8", "3"]),
  31.   calendarId: "primary",
  32.   debug: true
  33. };
  34.  
  35. /**
  36.  * Main function triggered by onEventUpdated.
  37.  * Handles the processing of the updated event.
  38.  * @param {object} e - The event trigger object containing calendarId and other metadata.
  39.  */
  40. const onEventUpdated = (e) => {
  41.   try {
  42.     log_(`Trigger event object: ${JSON.stringify(e)}`);
  43.     validateConfig_();
  44.     const calendarId = e.calendarId || CONFIG.calendarId;
  45.     const recentEvent = fetchLatestUpdatedEvent_(calendarId);
  46.     if (!recentEvent) {
  47.       log_(`No recently updated event found.`);
  48.       return;
  49.     }
  50.     log_(
  51.       `Processing updated event: ${recentEvent.summary || "No Title"} (ID: ${
  52.         recentEvent.id
  53.       })`
  54.     );
  55.     processEvent_(recentEvent);
  56.     log_(`Finished processing the updated event.`);
  57.   } catch (error) {
  58.     logError_(
  59.       `Error processing updated event in calendar ${CONFIG.calendarId}`,
  60.       error
  61.     );
  62.   }
  63. };
  64.  
  65. /**
  66.  * Fetches the latest updated event from the specified calendar.
  67.  * Retries once if the event data is incomplete.
  68.  * @param {string} calendarId - The ID of the calendar to fetch events from.
  69.  * @returns {object|null} The most recently updated event, or null if none found.
  70.  */
  71. const fetchLatestUpdatedEvent_ = (calendarId) => {
  72.   const now = new Date().toISOString();
  73.   const pastMinute = new Date(Date.now() - 1000 * 60).toISOString(); // 1 minute ago
  74.  
  75.   log_(`Fetching events updated since ${pastMinute} in calendar ${calendarId}`);
  76.   let response = Calendar.Events.list(calendarId, {
  77.     updatedMin: pastMinute,
  78.     singleEvents: true,
  79.     orderBy: "updated",
  80.     maxResults: 1,
  81.     fields: "items(id,summary,colorId,visibility,recurrence)"
  82.   });
  83.   if (response.items && response.items.length > 0) {
  84.     let event = response.items[0];
  85.     log_(`Fetched event details: ${JSON.stringify(event)}`);
  86.     if (!event.summary || !event.colorId) {
  87.       log_(`Event data incomplete. Retrying...`);
  88.       Utilities.sleep(1000); // Wait 1 second
  89.       response = Calendar.Events.list(calendarId, {
  90.         updatedMin: pastMinute,
  91.         singleEvents: true,
  92.         orderBy: "updated",
  93.         maxResults: 1,
  94.         fields: "items(id,summary,colorId,visibility,recurrence)"
  95.       });
  96.       if (response.items && response.items.length > 0) {
  97.         event = response.items[0];
  98.         log_(`Fetched updated event details: ${JSON.stringify(event)}`);
  99.         return event;
  100.       }
  101.     }
  102.     return event;
  103.   }
  104.   log_(`No recently updated events found.`);
  105.   return null;
  106. };
  107.  
  108. /**
  109.  * Processes the given event object.
  110.  * @param {object} event - The event object from the Calendar API.
  111.  */
  112. const processEvent_ = (event) => {
  113.   if (!event.summary) {
  114.     log_(`Event has no title (ID: ${event.id}). Skipping.`);
  115.     return;
  116.   }
  117.   if (!event.colorId) {
  118.     log_(`Event has no color (ID: ${event.id}). Skipping.`);
  119.     return;
  120.   }
  121.   if (shouldProcessEvent_(event)) {
  122.     if (event.recurrence) {
  123.       log_(
  124.         `Processing recurring event series: ${event.summary} (ID: ${event.id})`
  125.       );
  126.       handleRecurringEventSeries_(event);
  127.     } else {
  128.       log_(`Processing single event: ${event.summary} (ID: ${event.id})`);
  129.       updateEventToPrivate_(event);
  130.     }
  131.   } else {
  132.     log_(`Event does not meet conditions: ${event.summary} (ID: ${event.id})`);
  133.   }
  134. };
  135.  
  136. /**
  137.  * Determines if an event should be processed.
  138.  * @param {object} event - The event object from the Calendar API.
  139.  * @returns {boolean} True if the event meets processing criteria, false otherwise.
  140.  */
  141. const shouldProcessEvent_ = ({ visibility, summary, id, colorId }) => {
  142.   if (visibility === "private") {
  143.     log_(`Event already private: ${summary} (ID: ${id})`);
  144.     return false;
  145.   }
  146.   if (!CONFIG.targetColors.has(colorId)) {
  147.     log_(`Event does not match target colors: ${summary} (ID: ${id})`);
  148.     return false;
  149.   }
  150.   return true;
  151. };
  152.  
  153. /**
  154.  * Sets a single event's visibility to PRIVATE.
  155.  * @param {object} event - The event object from the Calendar API.
  156.  */
  157. const updateEventToPrivate_ = ({ id, summary }) => {
  158.   try {
  159.     Calendar.Events.patch(
  160.       {
  161.         visibility: "private"
  162.       },
  163.       CONFIG.calendarId,
  164.       id
  165.     );
  166.     log_(`Set single event to private: ${summary} (ID: ${id})`);
  167.   } catch (error) {
  168.     logError_(
  169.       `Failed to update event visibility: ${summary} (ID: ${id})`,
  170.       error
  171.     );
  172.   }
  173. };
  174.  
  175. /**
  176.  * Validates the CONFIG object at runtime to ensure proper configuration.
  177.  * Throws an error if validation fails.
  178.  */
  179. const validateConfig_ = () => {
  180.   if (!(CONFIG.targetColors instanceof Set) || CONFIG.targetColors.size === 0) {
  181.     console.error("CONFIG.targetColors is empty or invalid.");
  182.     throw new Error("CONFIG.targetColors is empty or invalid.");
  183.   }
  184.   if (typeof CONFIG.calendarId !== "string" || !CONFIG.calendarId) {
  185.     throw new Error("CONFIG.calendarId must be a non-empty string.");
  186.   }
  187. };
  188.  
  189. /**
  190.  * Formats a log message with a timestamp.
  191.  * @param {string} level - The log level (INFO or ERROR).
  192.  * @param {string} message - The log message.
  193.  * @returns {string} Formatted log message with timestamp.
  194.  */
  195. const formatLogMessage_ = (level, message) =>
  196.   `[${level} - ${new Date().toISOString()}] ${message}`;
  197.  
  198. /**
  199.  * Logs informational messages controlled by the debug flag in CONFIG.
  200.  * @param {string} message - Message to log.
  201.  */
  202. const log_ = (message) => {
  203.   if (CONFIG.debug) console.log(formatLogMessage_("INFO", message));
  204. };
  205.  
  206. /**
  207.  * Logs error messages.
  208.  * @param {string} message - Error message to log.
  209.  * @param {Error} [error] - Optional error object for additional context.
  210.  */
  211. const logError_ = (message, error) => {
  212.   console.error(formatLogMessage_("ERROR", message));
  213.   if (error) console.error(error.stack || error.message);
  214. };
Advertisement
Add Comment
Please, Sign In to add comment