Created
October 6, 2025 19:03
-
-
Save chasecmiller/074b71df27b842d40df26a08264906d0 to your computer and use it in GitHub Desktop.
Bring Torc.dev events into my calendar and drive.
This file contains hidden or 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
/** | |
* Torc.dev Events brings together developers, makers, and innovators to share tools, experiments, and ideas | |
* at the intersection of technology and creativity. | |
* | |
* This brings events from the torc.dev/community site into a Google folder and a calendar. | |
* | |
* You really care about the cron function at the bottom, and that's it. You can disable the calendar functionality. | |
*/ | |
/** | |
* Main function to fetch all upcoming events from guild.host | |
* @param {string} guildSlug - The guild slug (e.g., 'torc-dev') | |
* @param {number} pageSize - Number of events per page (max 100) | |
* @return {Array} Array of all events | |
*/ | |
function getAllUpcomingEvents(guildSlug = 'torc-dev', pageSize = 100) { | |
const allEvents = []; | |
let hasMore = true; | |
let afterCursor = null; | |
let pageCount = 0; | |
while (hasMore) { | |
pageCount++; | |
Logger.log(`Fetching page ${pageCount}...`); | |
const pageData = fetchEventsPage(guildSlug, pageSize, afterCursor); | |
if (!pageData || !pageData.events) { | |
Logger.log('No events data received'); | |
break; | |
} | |
// Extract events from this page | |
const edges = pageData.events.edges || []; | |
Logger.log(`Found ${edges.length} events on page ${pageCount}`); | |
edges.forEach(edge => { | |
const event = parseEvent(edge.node); | |
allEvents.push(event); | |
}); | |
// Check if there are more pages | |
const pageInfo = pageData.events.pageInfo; | |
hasMore = pageInfo && pageInfo.hasNextPage; | |
if (hasMore) { | |
afterCursor = pageInfo.endCursor; | |
Logger.log(`More pages available. Next cursor: ${afterCursor}`); | |
} else { | |
Logger.log('No more pages. Fetching complete.'); | |
} | |
} | |
Logger.log(`Total events fetched: ${allEvents.length}`); | |
return allEvents; | |
} | |
/** | |
* Fetch a single page of events | |
*/ | |
function fetchEventsPage(guildSlug, pageSize, afterCursor) { | |
let url = `https://guild.host/api/next/${guildSlug}/events/upcoming?first=${pageSize}`; | |
if (afterCursor) { | |
url += `&after=${encodeURIComponent(afterCursor)}`; | |
} | |
try { | |
const response = UrlFetchApp.fetch(url, { | |
method: 'get', | |
muteHttpExceptions: true | |
}); | |
const statusCode = response.getResponseCode(); | |
if (statusCode !== 200) { | |
Logger.log(`Error: HTTP ${statusCode}`); | |
return null; | |
} | |
const data = JSON.parse(response.getContentText()); | |
return data; | |
} catch (error) { | |
Logger.log(`Error fetching events: ${error.message}`); | |
return null; | |
} | |
} | |
/** | |
* Parse event data into a clean object | |
*/ | |
function parseEvent(node) { | |
return { | |
id: node.id, | |
slug: node.slug, | |
name: node.name, | |
description: node.description || '', | |
startAt: node.startAt, // ISO 8601 UTC format: "2025-10-31T17:30:00+00:00" | |
endAt: node.endAt, // ISO 8601 UTC format | |
timeZone: node.timeZone, // IANA timezone: "America/New_York" | |
fullUrl: node.fullUrl, | |
shortUrl: node.shortUrl, | |
hasVenue: node.hasVenue, | |
hasExternalUrl: node.hasExternalUrl, | |
venue: node.venue ? { | |
id: node.venue.id, | |
location: node.venue.address?.location?.geojson?.coordinates || null | |
} : null, | |
owner: node.owner?.name || '', | |
socialCardUrl: node.uploadedSocialCard?.url || node.generatedSocialCardURL, | |
createdAt: node.createdAt, | |
updatedAt: node.updatedAt | |
}; | |
} | |
/** | |
* Convert ISO 8601 UTC time to ICS format in the event's local timezone | |
* Times from guild.host are in UTC (+00:00), but we need to represent them | |
* in the event's local timezone for proper ICS formatting. | |
* | |
* @param {string} isoString - ISO 8601 UTC timestamp | |
* @param {string} timeZone - IANA timezone (e.g., "America/New_York") | |
* @return {string} ICS formatted datetime (YYYYMMDDTHHMMSS) | |
*/ | |
function toICSDateTime(isoString, timeZone) { | |
// Parse the UTC time | |
const date = new Date(isoString); | |
// Format for ICS: YYYYMMDDTHHMMSS | |
// We'll use the UTC time and let the TZID handle localization | |
const year = date.getUTCFullYear(); | |
const month = String(date.getUTCMonth() + 1).padStart(2, '0'); | |
const day = String(date.getUTCDate()).padStart(2, '0'); | |
const hour = String(date.getUTCHours()).padStart(2, '0'); | |
const minute = String(date.getUTCMinutes()).padStart(2, '0'); | |
const second = String(date.getUTCSeconds()).padStart(2, '0'); | |
return `${year}${month}${day}T${hour}${minute}${second}Z`; | |
} | |
/** | |
* Generate ICS file content from an event | |
* | |
* TIMEZONE HANDLING: | |
* Guild.host returns times in UTC (+00:00) with a separate timeZone field. | |
* We use UTC times with the Z suffix, which allows the user's calendar app | |
* to properly convert to their local timezone. | |
*/ | |
function generateICS(event) { | |
const now = new Date(); | |
const dtstamp = toICSDateTime(now.toISOString(), 'UTC'); | |
const dtstart = toICSDateTime(event.startAt, event.timeZone); | |
const dtend = toICSDateTime(event.endAt, event.timeZone); | |
// Escape special characters in text fields | |
const escapeICS = (text) => { | |
return text.replace(/\\/g, '\\\\') | |
.replace(/;/g, '\\;') | |
.replace(/,/g, '\\,') | |
.replace(/\n/g, '\\n'); | |
}; | |
// Generate a UID | |
const uid = `${event.slug}@guild.host`; | |
// Build location string | |
let location = ''; | |
if (event.hasVenue && event.venue?.location) { | |
const [lng, lat] = event.venue.location; | |
location = `geo:${lat},${lng}`; | |
} else if (event.hasExternalUrl) { | |
location = 'Online Event'; | |
} | |
const ics = [ | |
'BEGIN:VCALENDAR', | |
'VERSION:2.0', | |
'PRODID:-//Guild.host//Events//EN', | |
'CALSCALE:GREGORIAN', | |
'METHOD:PUBLISH', | |
'BEGIN:VEVENT', | |
`UID:${uid}`, | |
`DTSTAMP:${dtstamp}`, | |
`DTSTART:${dtstart}`, | |
`DTEND:${dtend}`, | |
`SUMMARY:${escapeICS(event.name)}`, | |
`DESCRIPTION:${escapeICS(event.description)}\\n\\nEvent URL: ${event.fullUrl}`, | |
location ? `LOCATION:${escapeICS(location)}` : null, | |
`URL:${event.fullUrl}`, | |
`ORGANIZER;CN=${escapeICS(event.owner)}:[email protected]`, | |
'STATUS:CONFIRMED', | |
'TRANSP:OPAQUE', | |
'END:VEVENT', | |
'END:VCALENDAR' | |
].filter(line => line !== null).join('\r\n'); | |
return ics; | |
} | |
/** | |
* Get the property store for tracking event files | |
*/ | |
function getEventFileMapping() { | |
const props = PropertiesService.getUserProperties(); | |
const mapping = props.getProperty('GUILD_EVENT_FILES'); | |
return mapping ? JSON.parse(mapping) : {}; | |
} | |
/** | |
* Save the event file mapping | |
*/ | |
function saveEventFileMapping(mapping) { | |
const props = PropertiesService.getUserProperties(); | |
props.setProperty('GUILD_EVENT_FILES', JSON.stringify(mapping)); | |
} | |
/** | |
* Generate filename with datetime prefix | |
*/ | |
function generateFileName(event) { | |
// Format datetime for filename: YYYYMMDD-HHMM | |
const startDate = new Date(event.startAt); | |
const datePrefix = [ | |
startDate.getUTCFullYear(), | |
String(startDate.getUTCMonth() + 1).padStart(2, '0'), | |
String(startDate.getUTCDate()).padStart(2, '0') | |
].join(''); | |
const timePrefix = [ | |
String(startDate.getUTCHours()).padStart(2, '0'), | |
String(startDate.getUTCMinutes()).padStart(2, '0') | |
].join(''); | |
// Clean event name for filename | |
const cleanName = event.name | |
.replace(/[^a-z0-9]/gi, '_') | |
.replace(/_+/g, '_') | |
.substring(0, 50); // Limit length | |
return `${datePrefix}-${timePrefix}_${cleanName}_${event.slug}.ics`; | |
} | |
/** | |
* Save ICS files to Google Drive with intelligent duplicate handling | |
* Uses event slug as stable identifier to update existing files | |
* | |
* @param {string} guildSlug - The guild slug | |
* @param {string} folderId - Google Drive folder ID (or folder name) | |
*/ | |
function saveICSFilesToDrive(guildSlug = 'torc-dev', folderId = null) { | |
const events = getAllUpcomingEvents(guildSlug); | |
// Get or create the folder | |
let folder; | |
if (folderId) { | |
try { | |
folder = DriveApp.getFolderById(folderId); | |
} catch (e) { | |
// Try by name | |
const folders = DriveApp.getFoldersByName(folderId); | |
if (folders.hasNext()) { | |
folder = folders.next(); | |
} else { | |
Logger.log(`Creating new folder: ${folderId}`); | |
folder = DriveApp.createFolder(folderId); | |
} | |
} | |
} else { | |
// Use root folder | |
folder = DriveApp.getRootFolder(); | |
} | |
Logger.log(`Saving ${events.length} ICS files to folder: ${folder.getName()}`); | |
// Load existing file mapping | |
const fileMapping = getEventFileMapping(); | |
const savedFiles = []; | |
const updatedEvents = []; | |
const newEvents = []; | |
events.forEach(event => { | |
const icsContent = generateICS(event); | |
const fileName = generateFileName(event); | |
const eventKey = `${guildSlug}:${event.slug}`; | |
let file; | |
let isUpdate = false; | |
// Check if we already have a file for this event | |
if (fileMapping[eventKey]) { | |
try { | |
// Try to get the existing file | |
file = DriveApp.getFileById(fileMapping[eventKey]); | |
// Update the file content | |
file.setContent(icsContent); | |
// Update filename if it changed (due to name/time change) | |
if (file.getName() !== fileName) { | |
file.setName(fileName); | |
Logger.log(`Updated: ${fileName} (renamed)`); | |
} else { | |
Logger.log(`Updated: ${fileName}`); | |
} | |
isUpdate = true; | |
updatedEvents.push(event.slug); | |
} catch (e) { | |
// File no longer exists, remove from mapping | |
Logger.log(`File for ${event.slug} no longer exists, creating new one`); | |
delete fileMapping[eventKey]; | |
file = null; | |
} | |
} | |
// Create new file if we don't have one | |
if (!file) { | |
file = folder.createFile(fileName, icsContent, 'text/calendar'); | |
fileMapping[eventKey] = file.getId(); | |
Logger.log(`Created: ${fileName}`); | |
newEvents.push(event.slug); | |
} | |
savedFiles.push({ | |
name: fileName, | |
url: file.getUrl(), | |
event: event.name, | |
action: isUpdate ? 'updated' : 'created' | |
}); | |
}); | |
// Save the updated mapping | |
saveEventFileMapping(fileMapping); | |
Logger.log(`\n=== SUMMARY ===`); | |
Logger.log(`New events: ${newEvents.length}`); | |
Logger.log(`Updated events: ${updatedEvents.length}`); | |
Logger.log(`Total files: ${savedFiles.length}`); | |
return savedFiles; | |
} | |
/** | |
* Clean up orphaned files (events that no longer exist) | |
* @param {string} guildSlug - The guild slug | |
* @param {boolean} dryRun - If true, only report what would be deleted | |
*/ | |
function cleanupOrphanedFiles(guildSlug = 'torc-dev', dryRun = true) { | |
const events = getAllUpcomingEvents(guildSlug); | |
const currentSlugs = new Set(events.map(e => e.slug)); | |
const fileMapping = getEventFileMapping(); | |
const orphanedFiles = []; | |
Object.keys(fileMapping).forEach(eventKey => { | |
// Check if this key is for the current guild | |
if (!eventKey.startsWith(`${guildSlug}:`)) { | |
return; | |
} | |
const slug = eventKey.split(':')[1]; | |
// If event no longer exists in the API | |
if (!currentSlugs.has(slug)) { | |
const fileId = fileMapping[eventKey]; | |
try { | |
const file = DriveApp.getFileById(fileId); | |
orphanedFiles.push({ | |
eventKey: eventKey, | |
fileName: file.getName(), | |
fileId: fileId | |
}); | |
if (!dryRun) { | |
file.setTrashed(true); | |
delete fileMapping[eventKey]; | |
Logger.log(`Deleted: ${file.getName()}`); | |
} else { | |
Logger.log(`Would delete: ${file.getName()}`); | |
} | |
} catch (e) { | |
// File already gone, just remove from mapping | |
delete fileMapping[eventKey]; | |
Logger.log(`Removed mapping for non-existent file: ${eventKey}`); | |
} | |
} | |
}); | |
if (!dryRun) { | |
saveEventFileMapping(fileMapping); | |
} | |
Logger.log(`\n=== CLEANUP SUMMARY ===`); | |
Logger.log(`Orphaned files found: ${orphanedFiles.length}`); | |
Logger.log(`Dry run: ${dryRun}`); | |
return orphanedFiles; | |
} | |
/** | |
* Reset the file mapping (useful for debugging) | |
*/ | |
function resetFileMapping() { | |
const props = PropertiesService.getUserProperties(); | |
props.deleteProperty('GUILD_EVENT_FILES'); | |
Logger.log('File mapping reset'); | |
} | |
/** | |
* Debug: Show current file mapping | |
*/ | |
function debugShowMapping() { | |
const mapping = getEventFileMapping(); | |
Logger.log('=== CURRENT FILE MAPPING ==='); | |
Logger.log(JSON.stringify(mapping, null, 2)); | |
Logger.log(`\nTotal tracked files: ${Object.keys(mapping).length}`); | |
} | |
/** | |
* DEBUG: Dump all events to log | |
*/ | |
function debugDumpEvents(guildSlug = 'torc-dev') { | |
Logger.log('=== FETCHING EVENTS ===\n'); | |
const events = getAllUpcomingEvents(guildSlug); | |
Logger.log(`\n=== FOUND ${events.length} EVENTS ===\n`); | |
events.forEach((event, index) => { | |
Logger.log(`\n--- Event ${index + 1} ---`); | |
Logger.log(`Name: ${event.name}`); | |
Logger.log(`Slug: ${event.slug}`); | |
Logger.log(`Start (UTC): ${event.startAt}`); | |
Logger.log(`End (UTC): ${event.endAt}`); | |
Logger.log(`TimeZone: ${event.timeZone}`); | |
Logger.log(`URL: ${event.fullUrl}`); | |
Logger.log(`Has Venue: ${event.hasVenue}`); | |
Logger.log(`Description: ${event.description.substring(0, 100)}...`); | |
// Show ICS format times | |
Logger.log(`ICS Start: ${toICSDateTime(event.startAt, event.timeZone)}`); | |
Logger.log(`ICS End: ${toICSDateTime(event.endAt, event.timeZone)}`); | |
}); | |
Logger.log('\n=== TIMEZONE INFO ==='); | |
Logger.log('Guild.host times are in UTC (+00:00 offset)'); | |
Logger.log('The timeZone field indicates the event\'s intended timezone'); | |
Logger.log('ICS files use UTC times with Z suffix for proper localization'); | |
} | |
function saveToMyFolder() { | |
saveICSFilesToDrive('torc-dev', '1Ba6XYVGsyRehR-4xPK5yHdf9xFqKjcTe'); | |
} | |
/** | |
* Sync events to Google Calendar | |
* Uses extended properties to track guild.host events and avoid duplicates | |
* | |
* @param {string} guildSlug - The guild slug | |
* @param {string} calendarId - Calendar ID (use 'primary' for main calendar, or specific calendar ID) | |
*/ | |
function syncEventsToCalendar(guildSlug = 'torc-dev', calendarId = 'primary') { | |
const events = getAllUpcomingEvents(guildSlug); | |
const calendar = calendarId === 'primary' | |
? CalendarApp.getDefaultCalendar() | |
: CalendarApp.getCalendarById(calendarId); | |
if (!calendar) { | |
Logger.log('Error: Calendar not found'); | |
return; | |
} | |
Logger.log(`Syncing ${events.length} events to calendar: ${calendar.getName()}`); | |
const stats = { | |
created: 0, | |
updated: 0, | |
skipped: 0, | |
errors: 0 | |
}; | |
events.forEach(event => { | |
try { | |
const result = syncEventToCalendar(event, calendar, guildSlug); | |
stats[result]++; | |
} catch (error) { | |
Logger.log(`Error syncing ${event.name}: ${error.message}`); | |
stats.errors++; | |
} | |
}); | |
Logger.log('\n=== SYNC SUMMARY ==='); | |
Logger.log(`Created: ${stats.created}`); | |
Logger.log(`Updated: ${stats.updated}`); | |
Logger.log(`Skipped: ${stats.skipped}`); | |
Logger.log(`Errors: ${stats.errors}`); | |
return stats; | |
} | |
/** | |
* Sync a single event to Google Calendar | |
* Returns: 'created', 'updated', or 'skipped' | |
*/ | |
function syncEventToCalendar(event, calendar, guildSlug) { | |
const eventKey = `guildhost_${guildSlug}_${event.slug}`; | |
// Parse dates | |
const startDate = new Date(event.startAt); | |
const endDate = new Date(event.endAt); | |
// Search for existing event by our custom property | |
const existingEvent = findCalendarEventByProperty(calendar, eventKey, startDate, endDate); | |
// Prepare event details | |
const eventDetails = { | |
title: event.name, | |
description: formatEventDescription(event), | |
location: getEventLocation(event) | |
}; | |
if (existingEvent) { | |
// Check if event needs updating | |
if (needsUpdate(existingEvent, event, eventDetails)) { | |
updateCalendarEvent(existingEvent, event, eventDetails); | |
Logger.log(`Updated: ${event.name}`); | |
return 'updated'; | |
} else { | |
Logger.log(`Skipped (no changes): ${event.name}`); | |
return 'skipped'; | |
} | |
} else { | |
// Create new event | |
const calEvent = calendar.createEvent( | |
eventDetails.title, | |
startDate, | |
endDate, | |
{ | |
description: eventDetails.description, | |
location: eventDetails.location | |
} | |
); | |
// Tag it with our identifier | |
calEvent.setTag(eventKey, event.id); | |
calEvent.setTag('guildhost_slug', event.slug); | |
calEvent.setTag('guildhost_url', event.fullUrl); | |
calEvent.setTag('guildhost_updated', event.updatedAt); | |
Logger.log(`Created: ${event.name}`); | |
return 'created'; | |
} | |
} | |
/** | |
* Find an existing calendar event by our custom property | |
*/ | |
function findCalendarEventByProperty(calendar, eventKey, startDate, endDate) { | |
// Search in a range around the event time | |
const searchStart = new Date(startDate.getTime() - 7 * 24 * 60 * 60 * 1000); // 7 days before | |
const searchEnd = new Date(endDate.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days after | |
const events = calendar.getEvents(searchStart, searchEnd); | |
for (let i = 0; i < events.length; i++) { | |
const event = events[i]; | |
const tag = event.getTag(eventKey); | |
if (tag) { | |
return event; | |
} | |
} | |
return null; | |
} | |
/** | |
* Format event description with guild.host info | |
*/ | |
function formatEventDescription(event) { | |
let description = event.description || ''; | |
// Add source info | |
description += `\n\n---\n`; | |
description += `π Guild.host Event\n`; | |
description += `π ${event.fullUrl}\n`; | |
if (event.owner) { | |
description += `π€ Organized by: ${event.owner}\n`; | |
} | |
description += `β° Timezone: ${event.timeZone}`; | |
return description; | |
} | |
/** | |
* Get event location string | |
*/ | |
function getEventLocation(event) { | |
if (event.hasVenue && event.venue?.location) { | |
const [lng, lat] = event.venue.location; | |
return `${lat},${lng}`; | |
} else if (event.hasExternalUrl) { | |
return 'Online Event'; | |
} | |
return ''; | |
} | |
/** | |
* Check if calendar event needs updating | |
*/ | |
function needsUpdate(calendarEvent, guildEvent, eventDetails) { | |
// Check if title changed | |
if (calendarEvent.getTitle() !== eventDetails.title) { | |
return true; | |
} | |
// Check if description changed (approximately) | |
const currentDesc = calendarEvent.getDescription() || ''; | |
if (!currentDesc.includes(guildEvent.description.substring(0, 50))) { | |
return true; | |
} | |
// Check if times changed | |
const calStart = calendarEvent.getStartTime().getTime(); | |
const guildStart = new Date(guildEvent.startAt).getTime(); | |
if (Math.abs(calStart - guildStart) > 60000) { // More than 1 minute difference | |
return true; | |
} | |
// Check if updatedAt changed | |
const lastUpdate = calendarEvent.getTag('guildhost_updated'); | |
if (lastUpdate !== guildEvent.updatedAt) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Update an existing calendar event | |
*/ | |
function updateCalendarEvent(calendarEvent, guildEvent, eventDetails) { | |
calendarEvent.setTitle(eventDetails.title); | |
calendarEvent.setDescription(eventDetails.description); | |
calendarEvent.setLocation(eventDetails.location); | |
calendarEvent.setTime(new Date(guildEvent.startAt), new Date(guildEvent.endAt)); | |
// Update our tracking tags | |
calendarEvent.setTag('guildhost_updated', guildEvent.updatedAt); | |
} | |
/** | |
* Remove guild.host events from calendar that no longer exist | |
* @param {string} guildSlug - The guild slug | |
* @param {string} calendarId - Calendar ID | |
* @param {boolean} dryRun - If true, only report what would be deleted | |
*/ | |
function cleanupCalendarEvents(guildSlug = 'torc-dev', calendarId = 'primary', dryRun = true) { | |
const events = getAllUpcomingEvents(guildSlug); | |
const currentSlugs = new Set(events.map(e => e.slug)); | |
const calendar = calendarId === 'primary' | |
? CalendarApp.getDefaultCalendar() | |
: CalendarApp.getCalendarById(calendarId); | |
// Search far into the future | |
const now = new Date(); | |
const future = new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000); // 1 year | |
const calendarEvents = calendar.getEvents(now, future); | |
const orphaned = []; | |
calendarEvents.forEach(calEvent => { | |
const slug = calEvent.getTag('guildhost_slug'); | |
// Is this a guild.host event? | |
if (slug) { | |
// Does it still exist? | |
if (!currentSlugs.has(slug)) { | |
orphaned.push({ | |
title: calEvent.getTitle(), | |
slug: slug, | |
startTime: calEvent.getStartTime() | |
}); | |
if (!dryRun) { | |
calEvent.deleteEvent(); | |
Logger.log(`Deleted: ${calEvent.getTitle()}`); | |
} else { | |
Logger.log(`Would delete: ${calEvent.getTitle()}`); | |
} | |
} | |
} | |
}); | |
Logger.log(`\n=== CLEANUP SUMMARY ===`); | |
Logger.log(`Orphaned events found: ${orphaned.length}`); | |
Logger.log(`Dry run: ${dryRun}`); | |
return orphaned; | |
} | |
/** | |
* List all guild.host events currently in calendar | |
*/ | |
function listGuildHostEvents(calendarId = 'primary') { | |
const calendar = calendarId === 'primary' | |
? CalendarApp.getDefaultCalendar() | |
: CalendarApp.getCalendarById(calendarId); | |
const now = new Date(); | |
const future = new Date(now.getTime() + 365 * 24 * 60 * 60 * 1000); | |
const calendarEvents = calendar.getEvents(now, future); | |
const guildEvents = []; | |
calendarEvents.forEach(calEvent => { | |
const slug = calEvent.getTag('guildhost_slug'); | |
if (slug) { | |
guildEvents.push({ | |
title: calEvent.getTitle(), | |
slug: slug, | |
startTime: calEvent.getStartTime(), | |
url: calEvent.getTag('guildhost_url') | |
}); | |
} | |
}); | |
Logger.log(`\n=== GUILD.HOST EVENTS IN CALENDAR ===`); | |
Logger.log(`Total: ${guildEvents.length}\n`); | |
guildEvents.forEach(event => { | |
Logger.log(`${event.startTime.toISOString()} - ${event.title}`); | |
Logger.log(` Slug: ${event.slug}`); | |
Logger.log(` URL: ${event.url}\n`); | |
}); | |
return guildEvents; | |
} | |
/** | |
* Full sync: Files + Calendar | |
*/ | |
function fullSync(guildSlug = 'torc-dev', driveFolderId = 'Guild Events', calendarId = 'primary') { | |
Logger.log('=== STARTING FULL SYNC ===\n'); | |
Logger.log('Step 1: Syncing to Drive...'); | |
const driveResults = saveICSFilesToDrive(guildSlug, driveFolderId); | |
Logger.log('\nStep 2: Syncing to Calendar...'); | |
const calendarResults = syncEventsToCalendar(guildSlug, calendarId); | |
Logger.log('\n=== FULL SYNC COMPLETE ==='); | |
Logger.log(`Drive: ${driveResults.length} files`); | |
Logger.log(`Calendar: ${calendarResults.created + calendarResults.updated} events`); | |
} | |
/** | |
* THIS IS WHAT YOU CARE ABOUT. IGNORE EVERYTHING ABOVE. | |
*/ | |
function cron() { | |
cleanupOrphanedFiles('torc-dev', false); | |
saveToMyFolder(); | |
syncEventsToCalendar('torc-dev', 'primary'); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment