408 lines
10 KiB
TypeScript
408 lines
10 KiB
TypeScript
/**
|
|
* Gmail API - Node.js/TypeScript Example
|
|
*
|
|
* Complete working example showing all major operations.
|
|
* Save this file and run: npx ts-node example-nodejs.ts
|
|
*/
|
|
|
|
import fs from "fs/promises";
|
|
import readline from "readline";
|
|
import { google, gmail_v1 } from "googleapis";
|
|
import { OAuth2Client } from "google-auth-library";
|
|
|
|
// Configuration
|
|
const SCOPES = ["https://www.googleapis.com/auth/gmail.modify"];
|
|
const CREDENTIALS_PATH = "credentials.json";
|
|
const TOKEN_PATH = "token.json";
|
|
|
|
/**
|
|
* Authenticate user and return Gmail service
|
|
*/
|
|
async function authenticate(): Promise<gmail_v1.Gmail> {
|
|
try {
|
|
const credentialsContent = await fs.readFile(CREDENTIALS_PATH, "utf-8");
|
|
const credentials = JSON.parse(credentialsContent);
|
|
const { client_id, client_secret, redirect_uris } = credentials.installed;
|
|
|
|
const oauth2Client = new google.auth.OAuth2(
|
|
client_id,
|
|
client_secret,
|
|
redirect_uris[0]
|
|
);
|
|
|
|
// Check for cached token
|
|
try {
|
|
const tokenContent = await fs.readFile(TOKEN_PATH, "utf-8");
|
|
const token = JSON.parse(tokenContent);
|
|
oauth2Client.setCredentials(token);
|
|
|
|
// Refresh if expired
|
|
if (token.expiry_date && token.expiry_date < Date.now()) {
|
|
const newToken = await oauth2Client.refreshAccessToken();
|
|
oauth2Client.setCredentials(newToken.credentials);
|
|
await fs.writeFile(TOKEN_PATH, JSON.stringify(newToken.credentials));
|
|
}
|
|
|
|
console.log("✅ Using cached credentials");
|
|
return google.gmail({ version: "v1", auth: oauth2Client });
|
|
} catch {
|
|
// No token, need to authenticate
|
|
console.log("⚠️ No cached token, initiating authentication...");
|
|
return authenticateUser(oauth2Client);
|
|
}
|
|
} catch (error) {
|
|
console.error("❌ Authentication failed:", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get user authorization via browser
|
|
*/
|
|
async function authenticateUser(
|
|
oauth2Client: OAuth2Client
|
|
): Promise<gmail_v1.Gmail> {
|
|
const authUrl = oauth2Client.generateAuthUrl({
|
|
access_type: "offline",
|
|
scope: SCOPES,
|
|
});
|
|
|
|
console.log(`\n🔗 Please visit this URL to authorize:\n${authUrl}\n`);
|
|
|
|
const rl = readline.createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout,
|
|
});
|
|
|
|
return new Promise((resolve, reject) => {
|
|
rl.question("Enter the authorization code: ", async (code) => {
|
|
rl.close();
|
|
try {
|
|
const { tokens } = await oauth2Client.getToken(code);
|
|
oauth2Client.setCredentials(tokens);
|
|
await fs.writeFile(TOKEN_PATH, JSON.stringify(tokens));
|
|
console.log("✅ Token saved");
|
|
resolve(google.gmail({ version: "v1", auth: oauth2Client }));
|
|
} catch (error) {
|
|
reject(error);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get user profile
|
|
*/
|
|
async function getProfile(gmail: gmail_v1.Gmail) {
|
|
console.log("\n📋 Getting user profile...");
|
|
const response = await gmail.users.getProfile({ userId: "me" });
|
|
const data = response.data;
|
|
|
|
console.log(` Email: ${data.emailAddress}`);
|
|
console.log(` Total Messages: ${data.messagesTotal}`);
|
|
console.log(` Total Threads: ${data.threadsTotal}`);
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* List all labels
|
|
*/
|
|
async function listLabels(gmail: gmail_v1.Gmail) {
|
|
console.log("\n🏷️ Listing labels...");
|
|
const response = await gmail.users.labels.list({ userId: "me" });
|
|
const labels = response.data.labels || [];
|
|
|
|
labels.slice(0, 10).forEach((label) => {
|
|
console.log(` - ${label.name} (ID: ${label.id})`);
|
|
});
|
|
|
|
console.log(` ... and ${Math.max(0, labels.length - 10)} more`);
|
|
return labels;
|
|
}
|
|
|
|
/**
|
|
* List inbox messages
|
|
*/
|
|
async function listInboxMessages(gmail: gmail_v1.Gmail) {
|
|
console.log("\n📧 Listing inbox messages (last 5)...");
|
|
const response = await gmail.users.messages.list({
|
|
userId: "me",
|
|
q: "in:inbox",
|
|
maxResults: 5,
|
|
});
|
|
|
|
const messages = response.data.messages || [];
|
|
|
|
for (const message of messages) {
|
|
const msgDetails = await gmail.users.messages.get({
|
|
userId: "me",
|
|
id: message.id!,
|
|
format: "metadata",
|
|
metadataHeaders: ["Subject", "From", "Date"],
|
|
});
|
|
|
|
const headers = msgDetails.data.payload?.headers || [];
|
|
const subject =
|
|
headers.find((h) => h.name === "Subject")?.value || "No Subject";
|
|
const from = headers.find((h) => h.name === "From")?.value || "Unknown";
|
|
const date = headers.find((h) => h.name === "Date")?.value || "Unknown";
|
|
|
|
console.log(`\n From: ${from}`);
|
|
console.log(` Subject: ${subject}`);
|
|
console.log(` Date: ${date}`);
|
|
}
|
|
|
|
return messages;
|
|
}
|
|
|
|
/**
|
|
* Search messages
|
|
*/
|
|
async function searchMessages(gmail: gmail_v1.Gmail, query: string) {
|
|
console.log(`\n🔍 Searching for: ${query}`);
|
|
const response = await gmail.users.messages.list({
|
|
userId: "me",
|
|
q: query,
|
|
maxResults: 5,
|
|
});
|
|
|
|
const messages = response.data.messages || [];
|
|
console.log(` Found ${messages.length} messages`);
|
|
|
|
return messages;
|
|
}
|
|
|
|
/**
|
|
* Get full message details
|
|
*/
|
|
async function getFullMessage(gmail: gmail_v1.Gmail, messageId: string) {
|
|
console.log(`\n📄 Getting full message: ${messageId}`);
|
|
const response = await gmail.users.messages.get({
|
|
userId: "me",
|
|
id: messageId,
|
|
format: "full",
|
|
});
|
|
|
|
const message = response.data;
|
|
const headers = message.payload?.headers || [];
|
|
const bodyData = message.payload?.body?.data || "";
|
|
|
|
const subject =
|
|
headers.find((h) => h.name === "Subject")?.value || "No Subject";
|
|
const from = headers.find((h) => h.name === "From")?.value || "Unknown";
|
|
const to = headers.find((h) => h.name === "To")?.value || "Unknown";
|
|
|
|
let body = "";
|
|
if (bodyData) {
|
|
body = Buffer.from(bodyData, "base64").toString();
|
|
}
|
|
|
|
console.log(` Subject: ${subject}`);
|
|
console.log(` From: ${from}`);
|
|
console.log(` To: ${to}`);
|
|
console.log(` Body length: ${body.length} chars`);
|
|
|
|
return {
|
|
subject,
|
|
from,
|
|
to,
|
|
body: body.substring(0, 500), // First 500 chars
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Send a message
|
|
*/
|
|
async function sendMessage(
|
|
gmail: gmail_v1.Gmail,
|
|
to: string,
|
|
subject: string,
|
|
body: string
|
|
) {
|
|
console.log(`\n✉️ Sending message to ${to}...`);
|
|
|
|
const message =
|
|
`From: me\r\n` +
|
|
`To: ${to}\r\n` +
|
|
`Subject: ${subject}\r\n` +
|
|
`\r\n` +
|
|
`${body}`;
|
|
|
|
const encodedMessage = Buffer.from(message)
|
|
.toString("base64")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=+$/, "");
|
|
|
|
const response = await gmail.users.messages.send({
|
|
userId: "me",
|
|
requestBody: {
|
|
raw: encodedMessage,
|
|
},
|
|
});
|
|
|
|
console.log(` ✅ Message sent with ID: ${response.data.id}`);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Create a draft
|
|
*/
|
|
async function createDraft(
|
|
gmail: gmail_v1.Gmail,
|
|
to: string,
|
|
subject: string,
|
|
body: string
|
|
) {
|
|
console.log(`\n📝 Creating draft for ${to}...`);
|
|
|
|
const message =
|
|
`From: me\r\n` +
|
|
`To: ${to}\r\n` +
|
|
`Subject: ${subject}\r\n` +
|
|
`\r\n` +
|
|
`${body}`;
|
|
|
|
const encodedMessage = Buffer.from(message)
|
|
.toString("base64")
|
|
.replace(/\+/g, "-")
|
|
.replace(/\//g, "_")
|
|
.replace(/=+$/, "");
|
|
|
|
const response = await gmail.users.drafts.create({
|
|
userId: "me",
|
|
requestBody: {
|
|
message: {
|
|
raw: encodedMessage,
|
|
},
|
|
},
|
|
});
|
|
|
|
console.log(` ✅ Draft created with ID: ${response.data.id}`);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Apply label to message
|
|
*/
|
|
async function applyLabelToMessage(
|
|
gmail: gmail_v1.Gmail,
|
|
messageId: string,
|
|
labelName: string
|
|
) {
|
|
console.log(`\n🏷️ Applying label "${labelName}" to message...`);
|
|
|
|
const labelsResponse = await gmail.users.labels.list({ userId: "me" });
|
|
const label = labelsResponse.data.labels?.find((l) => l.name === labelName);
|
|
|
|
if (!label) {
|
|
console.log(` ❌ Label "${labelName}" not found`);
|
|
return null;
|
|
}
|
|
|
|
const response = await gmail.users.messages.modify({
|
|
userId: "me",
|
|
id: messageId,
|
|
requestBody: {
|
|
addLabelIds: [label.id!],
|
|
},
|
|
});
|
|
|
|
console.log(` ✅ Label applied`);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* List threads
|
|
*/
|
|
async function listThreads(gmail: gmail_v1.Gmail) {
|
|
console.log("\n💬 Listing threads (last 3)...");
|
|
const response = await gmail.users.threads.list({
|
|
userId: "me",
|
|
q: "in:inbox",
|
|
maxResults: 3,
|
|
});
|
|
|
|
const threads = response.data.threads || [];
|
|
|
|
for (const thread of threads) {
|
|
const threadData = await gmail.users.threads.get({
|
|
userId: "me",
|
|
id: thread.id!,
|
|
format: "metadata",
|
|
});
|
|
|
|
const numMessages = threadData.data.messages?.length || 0;
|
|
const firstMsg = threadData.data.messages?.[0];
|
|
const headers = firstMsg?.payload?.headers || [];
|
|
const subject =
|
|
headers.find((h) => h.name === "Subject")?.value || "No Subject";
|
|
|
|
console.log(
|
|
`\n Thread ${thread.id}: ${numMessages} messages - "${subject}"`
|
|
);
|
|
}
|
|
|
|
return threads;
|
|
}
|
|
|
|
/**
|
|
* Move message to trash
|
|
*/
|
|
async function moveToTrash(gmail: gmail_v1.Gmail, messageId: string) {
|
|
console.log(`\n🗑️ Moving message to trash...`);
|
|
const response = await gmail.users.messages.trash({
|
|
userId: "me",
|
|
id: messageId,
|
|
});
|
|
|
|
console.log(` ✅ Message moved to trash`);
|
|
return response.data;
|
|
}
|
|
|
|
/**
|
|
* Main demo function
|
|
*/
|
|
async function main() {
|
|
console.log("🚀 Gmail API - Node.js/TypeScript Example\n");
|
|
|
|
try {
|
|
// Authenticate
|
|
const gmail = await authenticate();
|
|
|
|
// Run examples
|
|
await getProfile(gmail);
|
|
await listLabels(gmail);
|
|
await listInboxMessages(gmail);
|
|
|
|
// Search example
|
|
await searchMessages(gmail, "is:unread");
|
|
|
|
// Get first message details
|
|
const messages = await listInboxMessages(gmail);
|
|
if (messages.length > 0) {
|
|
await getFullMessage(gmail, messages[0].id!);
|
|
}
|
|
|
|
// Draft example (uncomment to use)
|
|
// await createDraft(
|
|
// gmail,
|
|
// "recipient@example.com",
|
|
// "Test Draft",
|
|
// "This is a test draft created by the example script."
|
|
// );
|
|
|
|
// List threads
|
|
await listThreads(gmail);
|
|
|
|
console.log("\n✅ All examples completed!");
|
|
} catch (error) {
|
|
console.error("\n❌ Error:", error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
main();
|