code icon Code

Fetch Calendly Events

Fetch scheduled events from Calendly with invitee details

Source Code

import fs from "fs";
import path from "path";

const [outputPath, minStartTime, maxStartTime, status = "active"] = process.argv.slice(2);

if (!outputPath) {
  console.error("Error: outputPath is required");
  console.error("Usage: node script.js session/calendly-events.json [minStartTime] [maxStartTime] [status]");
  process.exit(1);
}

console.log("Fetching Calendly scheduled events...");

try {
  // First get the current user to get their URI
  const userRes = await fetch("https://api.calendly.com/users/me", {
    headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" },
  });
  const userData = await userRes.json();

  if (userData.resource?.uri === undefined) {
    throw new Error(`Failed to get user info: ${JSON.stringify(userData)}`);
  }

  const userUri = userData.resource.uri;
  const userName = userData.resource.name;
  const calendlyTimezone = userData.resource.timezone;

  console.log(`  User: ${userName}`);
  console.log(`  Calendly timezone: ${calendlyTimezone}`);

  // Build query params
  const params = new URLSearchParams({
    user: userUri,
    count: "100",
    sort: "start_time:asc",
  });

  if (minStartTime && minStartTime !== "undefined" && minStartTime !== "null") {
    params.set("min_start_time", minStartTime);
  } else {
    // Default to now if no min time specified
    params.set("min_start_time", new Date().toISOString());
  }

  if (maxStartTime && maxStartTime !== "undefined" && maxStartTime !== "null") {
    params.set("max_start_time", maxStartTime);
  }

  if (status && status !== "all") {
    params.set("status", status);
  }

  // Fetch events with pagination
  const events = [];
  let nextPageToken = null;

  do {
    if (nextPageToken) {
      params.set("page_token", nextPageToken);
    }

    const eventsRes = await fetch(
      `https://api.calendly.com/scheduled_events?${params}`,
      { headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" } }
    );
    const eventsData = await eventsRes.json();

    if (eventsData.collection === undefined) {
      throw new Error(`Failed to fetch events: ${JSON.stringify(eventsData)}`);
    }

    events.push(...eventsData.collection);
    nextPageToken = eventsData.pagination?.next_page_token;
  } while (nextPageToken);

  console.log(`  Found ${events.length} scheduled events`);

  // Fetch invitees for each event (in parallel batches to avoid rate limits)
  console.log(`  Fetching invitee details...`);

  const fetchInvitees = async (eventUuid) => {
    const res = await fetch(
      `https://api.calendly.com/scheduled_events/${eventUuid}/invitees?count=10`,
      { headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" } }
    );
    const data = await res.json();
    return data.collection || [];
  };

  // Process in batches of 5 to avoid rate limits
  const batchSize = 5;
  const eventInvitees = new Map();

  for (let i = 0; i < events.length; i += batchSize) {
    const batch = events.slice(i, i + batchSize);
    const results = await Promise.all(
      batch.map(async (event) => {
        const uuid = event.uri.split("/").pop();
        const invitees = await fetchInvitees(uuid);
        return { uuid, invitees };
      })
    );
    results.forEach(({ uuid, invitees }) => eventInvitees.set(uuid, invitees));
  }

  // Process events with invitee details
  const processedEvents = events.map((event) => {
    const startTime = new Date(event.start_time);
    const endTime = new Date(event.end_time);
    const durationMinutes = Math.round((endTime - startTime) / (1000 * 60));
    const eventUuid = event.uri.split("/").pop();

    // Get invitees for this event
    const invitees = eventInvitees.get(eventUuid) || [];
    const processedInvitees = invitees.map((inv) => ({
      name: inv.name,
      email: inv.email,
      status: inv.status,
      timezone: inv.timezone,
      questionsAndAnswers: (inv.questions_and_answers || []).map((qa) => ({
        question: qa.question,
        answer: qa.answer,
      })),
    }));

    // Extract location info
    let locationInfo = null;
    if (event.location) {
      locationInfo = {
        type: event.location.type,
        location: event.location.location || null,
        joinUrl: event.location.join_url || null,
      };
    }

    return {
      uuid: eventUuid,
      uri: event.uri,
      name: event.name,
      status: event.status,
      startTime: event.start_time,
      endTime: event.end_time,
      durationMinutes,
      location: locationInfo,
      invitees: processedInvitees,
      inviteesCount: processedInvitees.length,
      createdAt: event.created_at,
      cancellation: event.cancellation || null,
    };
  });

  // Group by date (using UTC date from ISO string)
  const eventsByDate = {};
  for (const event of processedEvents) {
    const dateKey = event.startTime.split("T")[0];
    if (!eventsByDate[dateKey]) {
      eventsByDate[dateKey] = [];
    }
    eventsByDate[dateKey].push(event);
  }

  // Build output
  const output = {
    query: {
      minStartTime: minStartTime || new Date().toISOString(),
      maxStartTime: maxStartTime || null,
      status,
    },
    user: {
      name: userName,
      calendlyTimezone,
    },
    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(`  Output: ${outputPath}`);

  // Show preview with invitee names
  if (processedEvents.length > 0) {
    console.log(`\n  Preview:`);
    processedEvents.slice(0, 5).forEach((e) => {
      const time = e.startTime.split("T")[1]?.slice(0, 5) || "";
      const date = e.startTime.split("T")[0];
      const inviteeNames = e.invitees.map((i) => i.name).join(", ") || "No invitees";
      console.log(`    ${date} ${time} UTC - ${e.name} - ${inviteeNames}`);
    });
    if (processedEvents.length > 5) {
      console.log(`    ... and ${processedEvents.length - 5} more`);
    }
  }

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      eventCount: processedEvents.length,
    })
  );
} catch (error) {
  console.error("Failed:", error.message);
  throw error;
}