gmail-to-exchange365
Version:
Complete Gmail to Exchange 365 migration tool with UI - Migrate emails, attachments, and folders seamlessly
144 lines (123 loc) • 4.37 kB
text/typescript
import { google } from "googleapis";
import { EmailMessage } from "./types";
import { simpleParser, ParsedMail } from "mailparser";
interface GmailToken {
access_token: string;
refresh_token?: string;
expiry_date?: number;
}
export async function fetchAllEmails(gToken: GmailToken): Promise<EmailMessage[]> {
const oauth = new google.auth.OAuth2();
oauth.setCredentials(gToken);
const gmail = google.gmail({ version: "v1", auth: oauth });
const messages: EmailMessage[] = [];
let nextPageToken: string | undefined;
do {
try {
const res = await gmail.users.messages.list({
userId: "me",
maxResults: 500,
pageToken: nextPageToken
});
const messageList = res.data.messages || [];
// Fetch messages in batches
const batchSize = 10;
for (let i = 0; i < messageList.length; i += batchSize) {
const batch = messageList.slice(i, i + batchSize);
const promises = batch.map(msg => fetchEmailDetails(gmail, msg.id!));
const batchResults = await Promise.all(promises);
messages.push(...batchResults.filter(m => m !== null) as EmailMessage[]);
}
nextPageToken = res.data.nextPageToken || undefined;
} catch (error: any) {
console.error("Error fetching emails:", error.message);
if (error.code === 429) {
// Rate limit - wait and retry
await new Promise(resolve => setTimeout(resolve, 60000));
continue;
}
break;
}
} while (nextPageToken);
return messages;
}
async function fetchEmailDetails(gmail: any, messageId: string): Promise<EmailMessage | null> {
try {
const raw = await gmail.users.messages.get({
userId: "me",
id: messageId,
format: "raw"
});
if (!raw.data.raw) {
return null;
}
const rawStr = Buffer.from(raw.data.raw, "base64").toString("utf-8");
const parsed = await simpleParser(rawStr);
const attachments = [];
if (parsed.attachments) {
for (const attachment of parsed.attachments) {
attachments.push({
filename: attachment.filename || "attachment",
mimeType: attachment.contentType || "application/octet-stream",
data: attachment.content instanceof Buffer
? attachment.content
: Buffer.from(attachment.content as any)
});
}
}
// Helper function to extract addresses
const extractAddresses = (addr: any): string[] => {
if (!addr) return [];
if (Array.isArray(addr)) {
return addr.flatMap(a => extractAddresses(a));
}
if (addr.value) {
if (Array.isArray(addr.value)) {
return addr.value.map((v: any) => v.address || v);
}
return [addr.value.address || addr.value];
}
if (addr.address) {
return [addr.address];
}
return [];
};
// Extract from address
let fromAddress = "";
if (parsed.from?.text) {
fromAddress = parsed.from.text;
} else if (parsed.from) {
const fromAddrs = extractAddresses(parsed.from);
fromAddress = fromAddrs[0] || "";
}
return {
id: messageId,
threadId: raw.data.threadId,
from: fromAddress,
to: extractAddresses(parsed.to),
cc: extractAddresses(parsed.cc),
bcc: extractAddresses(parsed.bcc),
subject: parsed.subject || "",
date: parsed.date?.toISOString() || new Date().toISOString(),
htmlBody: parsed.html || undefined,
textBody: parsed.text || undefined,
attachments: attachments,
labels: raw.data.labelIds || []
};
} catch (error: any) {
console.error(`Error fetching email ${messageId}:`, error.message);
return null;
}
}
export async function fetchGmailLabels(gToken: GmailToken): Promise<string[]> {
const oauth = new google.auth.OAuth2();
oauth.setCredentials(gToken);
const gmail = google.gmail({ version: "v1", auth: oauth });
try {
const res = await gmail.users.labels.list({ userId: "me" });
return (res.data.labels || []).map(label => label.name || "").filter(Boolean);
} catch (error: any) {
console.error("Error fetching labels:", error.message);
return [];
}
}