code icon Code

Fetch Flight Email Details

Fetch full body content for specific email IDs

Source Code

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

const [idsPath, outputPath = "session/flight-emails.json"] =
  process.argv.slice(2);

if (!idsPath) {
  console.error("Error: idsPath is required");
  process.exit(1);
}

// Read the IDs file
let emailIds;
try {
  const idsContent = fs.readFileSync(idsPath, "utf-8");
  const idsData = JSON.parse(idsContent);
  emailIds = Array.isArray(idsData) ? idsData : idsData.ids || [];
} catch (e) {
  console.error(`Error reading IDs file: ${e.message}`);
  process.exit(1);
}

if (emailIds.length === 0) {
  console.log("No email IDs to fetch");
  const dir = path.dirname(outputPath);
  if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });
  fs.writeFileSync(
    outputPath,
    JSON.stringify({ count: 0, emails: [] }, null, 2)
  );
  console.log(JSON.stringify({ success: true, outputPath, count: 0 }));
  process.exit(0);
}

console.log(`Fetching full details for ${emailIds.length} emails...`);

/**
 * Extract body text from MIME payload
 */
function extractBodyText(payload) {
  if (payload.body?.data) {
    return Buffer.from(payload.body.data, "base64").toString("utf-8");
  }
  if (payload.parts) {
    for (const part of payload.parts) {
      if (part.mimeType === "text/plain" && part.body?.data) {
        return Buffer.from(part.body.data, "base64").toString("utf-8");
      }
      if (part.parts) {
        for (const nested of part.parts) {
          if (nested.mimeType === "text/plain" && nested.body?.data) {
            return Buffer.from(nested.body.data, "base64").toString("utf-8");
          }
        }
      }
    }
    // Try HTML if no plain text
    for (const part of payload.parts) {
      if (part.mimeType === "text/html" && part.body?.data) {
        return Buffer.from(part.body.data, "base64").toString("utf-8");
      }
    }
  }
  return "";
}

function getHeader(msg, name) {
  const h = msg.payload?.headers?.find(
    (h) => h.name.toLowerCase() === name.toLowerCase()
  );
  return h ? h.value : "";
}

try {
  const CONCURRENCY = 20;
  const emails = [];

  for (let i = 0; i < emailIds.length; i += CONCURRENCY) {
    const batch = emailIds.slice(i, i + CONCURRENCY);
    const fetched = await Promise.all(
      batch.map(async (id) => {
        const url = `https://gmail.googleapis.com/gmail/v1/users/me/messages/${id}?format=full`;
        const res = await fetch(url, {
          headers: { Authorization: "Bearer PLACEHOLDER_TOKEN" },
        });
        if (!res.ok) return null;
        return res.json();
      })
    );

    for (const msg of fetched.filter(Boolean)) {
      const bodyText = extractBodyText(msg.payload || {});
      emails.push({
        id: msg.id,
        threadId: msg.threadId,
        subject: getHeader(msg, "Subject"),
        from: getHeader(msg, "From"),
        to: getHeader(msg, "To"),
        date: getHeader(msg, "Date"),
        snippet: msg.snippet,
        body: bodyText.slice(0, 5000), // Limit body to 5KB
      });
    }

    console.log(
      `  Fetched ${Math.min(i + CONCURRENCY, emailIds.length)}/${emailIds.length}...`
    );
  }

  // Sort by date
  emails.sort((a, b) => new Date(a.date) - new Date(b.date));

  // Write output
  const dir = path.dirname(outputPath);
  if (dir && dir !== ".") fs.mkdirSync(dir, { recursive: true });

  const output = {
    fetchedAt: new Date().toISOString(),
    count: emails.length,
    emails,
  };

  fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));

  console.log(`\n✓ Fetched ${emails.length} email details`);
  console.log(`  Written to: ${outputPath}`);

  console.log(
    JSON.stringify({
      success: true,
      outputPath,
      count: emails.length,
    })
  );
} catch (error) {
  console.error("Error fetching emails:", error.message);
  throw error;
}