Fetch Calendar Events
Fetch events for a specific date range (for queries)
Source Code
import fs from "fs";
import path from "path";
const [startDate, endDate, outputPath] = process.argv.slice(2);
if (!startDate || !endDate || !outputPath) {
console.error("Error: startDate, endDate, and outputPath are required");
console.error("Usage: node script.js 2024-03-15 2024-03-22 session/events.json");
process.exit(1);
}
// Parse dates - accept YYYY-MM-DD or ISO8601
const parseDate = (dateStr, isEnd = false) => {
// If already ISO8601 with time, use as-is
if (dateStr.includes("T")) {
return new Date(dateStr);
}
// Otherwise, treat as date in user's local context
// For end date, use end of day
const d = new Date(dateStr + "T00:00:00");
if (isEnd) {
d.setHours(23, 59, 59, 999);
}
return d;
};
const start = parseDate(startDate);
const end = parseDate(endDate, true);
console.log(`Fetching events from ${startDate} to ${endDate}...`);
try {
// Get user's timezone first
const calRes = await fetch(
"https://www.googleapis.com/calendar/v3/calendars/primary",
{ headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" } }
);
const calData = await calRes.json();
if (calData.error) throw new Error(`Calendar info failed: ${calData.error.message}`);
const userTimezone = calData.timeZone;
const userEmail = calData.id;
console.log(` User timezone: ${userTimezone}`);
// Fetch events
const events = [];
let pageToken = null;
do {
const params = new URLSearchParams({
timeMin: start.toISOString(),
timeMax: end.toISOString(),
singleEvents: "true",
orderBy: "startTime",
maxResults: "100",
});
if (pageToken) params.set("pageToken", pageToken);
const res = await fetch(
`https://www.googleapis.com/calendar/v3/calendars/primary/events?${params}`,
{ headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" } }
);
const data = await res.json();
if (data.error) throw new Error(`Events fetch failed: ${data.error.message}`);
events.push(...(data.items || []));
pageToken = data.nextPageToken;
} while (pageToken);
console.log(` Found ${events.length} events`);
// Process events for readable output
const processedEvents = events
.filter((e) => e.status !== "cancelled")
.map((event) => {
const isAllDay = !event.start?.dateTime;
const startTime = event.start?.dateTime || event.start?.date;
const endTime = event.end?.dateTime || event.end?.date;
// Calculate duration in minutes (only for timed events)
let durationMinutes = null;
if (!isAllDay && startTime && endTime) {
durationMinutes = Math.round(
(new Date(endTime) - new Date(startTime)) / (1000 * 60)
);
}
// Process attendees (exclude self)
const attendees = (event.attendees || [])
.filter((a) => !a.self)
.map((a) => ({
name: a.displayName || a.email.split("@")[0],
email: a.email,
status: a.responseStatus,
}));
// Identify organizer
const organizer = event.organizer
? {
name: event.organizer.displayName || event.organizer.email?.split("@")[0],
email: event.organizer.email,
self: event.organizer.self || false,
}
: null;
// Check for video conferencing
const videoLink = event.conferenceData?.entryPoints?.find(
(e) => e.entryPointType === "video"
)?.uri || event.hangoutLink || null;
return {
id: event.id,
title: event.summary || "(No title)",
start: startTime,
end: endTime,
isAllDay,
durationMinutes,
timezone: event.start?.timeZone || userTimezone,
location: event.location || null,
videoLink,
organizer,
attendees,
attendeeCount: attendees.length,
isRecurring: !!event.recurringEventId,
description: event.description || null,
};
});
// Sort by start time (earliest first for query results)
processedEvents.sort((a, b) => new Date(a.start) - new Date(b.start));
// Group by date for easier display
const eventsByDate = {};
for (const event of processedEvents) {
// Extract date part
const dateKey = event.isAllDay
? event.start
: event.start.split("T")[0];
if (!eventsByDate[dateKey]) {
eventsByDate[dateKey] = [];
}
eventsByDate[dateKey].push(event);
}
// Build output
const output = {
query: {
start: startDate,
end: endDate,
timezone: userTimezone,
},
summary: {
totalEvents: processedEvents.length,
datesWithEvents: Object.keys(eventsByDate).length,
},
events: processedEvents,
eventsByDate,
};
// Write output
const dir = path.dirname(outputPath);
if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
console.log(`\nā Found ${processedEvents.length} events`);
console.log(` Date range: ${startDate} to ${endDate}`);
console.log(` Output: ${outputPath}`);
// Show preview
if (processedEvents.length > 0) {
console.log(`\n Preview:`);
processedEvents.slice(0, 5).forEach((e) => {
const time = e.isAllDay ? "All day" : e.start.split("T")[1]?.slice(0, 5) || "";
console.log(` ${time} - ${e.title}${e.attendeeCount > 0 ? ` (${e.attendeeCount} attendees)` : ""}`);
});
if (processedEvents.length > 5) {
console.log(` ... and ${processedEvents.length - 5} more`);
}
}
console.log(
JSON.stringify({
success: true,
outputPath,
eventCount: processedEvents.length,
dateRange: { start: startDate, end: endDate },
})
);
} catch (error) {
console.error("Failed:", error.message);
throw error;
}