UNPKG

gmail-reader

Version:

A Gmail reader package for Google Workspace accounts with domain-wide delegation support

300 lines 11.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GmailClient = void 0; // src/gmailClient.ts const googleapis_1 = require("googleapis"); const google_auth_library_1 = require("google-auth-library"); const gmail = googleapis_1.google.gmail('v1'); /** * Gmail client for reading emails from a workspace account * Supports domain-wide delegation for admin access */ class GmailClient { /** * Creates a new GmailClient instance * * @param config - Client configuration including clientId and clientSecret * @param refreshToken - OAuth2 refresh token (for user-level access) * @param options - Additional options */ constructor(config, refreshToken, options = {}) { this.config = config; this.options = options; this.scopes = [ 'https://www.googleapis.com/auth/gmail.readonly' ]; this.refreshToken = refreshToken; this.impersonateEmail = options.impersonateEmail; if (options.scopes) { this.scopes = options.scopes; } // Initialize auth client based on authentication method if (this.impersonateEmail && options.keyFile) { // Domain-wide delegation with service account this.auth = new googleapis_1.google.auth.JWT({ email: this.config.clientId, key: options.keyFile, scopes: this.scopes, subject: this.impersonateEmail }); } else { // Standard OAuth2 authentication this.auth = new googleapis_1.google.auth.OAuth2(this.config.clientId, this.config.clientSecret, this.config.redirectUri); if (this.refreshToken) { this.setRefreshToken(this.refreshToken); } } } /** * Set the refresh token for OAuth2 authentication * * @param refreshToken - OAuth2 refresh token */ setRefreshToken(refreshToken) { this.refreshToken = refreshToken; if (this.auth instanceof google_auth_library_1.OAuth2Client) { this.auth.setCredentials({ refresh_token: refreshToken }); } } /** * Set domain-wide delegation for impersonating a user * * @param email - Email address to impersonate * @param keyFile - Service account key file content */ setDomainWideDelegation(email, keyFile) { this.impersonateEmail = email; this.auth = new googleapis_1.google.auth.JWT({ email: this.config.clientId, key: keyFile, scopes: this.scopes, subject: email }); } /** * Authenticate with Google API * * @returns Promise resolving when authentication is complete */ async authenticate() { if (this.auth instanceof google_auth_library_1.OAuth2Client && this.refreshToken) { this.auth.setCredentials({ refresh_token: this.refreshToken, }); } else if (this.auth instanceof google_auth_library_1.JWT) { await this.auth.authorize(); } else { throw new Error('Authentication failed: No valid authentication method provided'); } } /** * List emails matching the given query options * * @param options - Query options for filtering emails * @returns Promise resolving to a list of emails */ async listEmails(options = {}) { try { await this.authenticate(); const userId = this.impersonateEmail || 'me'; const res = await gmail.users.messages.list({ userId, auth: this.auth, maxResults: options.maxResults || 10, labelIds: options.labelIds, includeSpamTrash: options.includeSpamTrash, q: options.q, pageToken: options.pageToken }); const messages = res.data.messages || []; const nextPageToken = res.data.nextPageToken || undefined; const resultSizeEstimate = res.data.resultSizeEstimate !== null && res.data.resultSizeEstimate !== undefined ? res.data.resultSizeEstimate : undefined; if (messages.length === 0) { return { emails: [], nextPageToken, resultSizeEstimate }; } const emails = await Promise.all(messages.map(message => this.getEmail(message.id))); return { emails: emails.filter(Boolean), nextPageToken, resultSizeEstimate }; } catch (error) { console.error('Error listing emails:', error); throw new Error(`Failed to list emails: ${error instanceof Error ? error.message : String(error)}`); } } /** * Get a specific email by ID * * @param emailId - The ID of the email to retrieve * @param options - Options for retrieving the email * @returns Promise resolving to the email details */ async getEmail(emailId, options = {}) { try { await this.authenticate(); const userId = this.impersonateEmail || 'me'; const messageData = await gmail.users.messages.get({ userId, id: emailId, auth: this.auth, format: options.format || 'full', metadataHeaders: options.metadataHeaders }); if (!messageData.data) { throw new Error(`Email with ID ${emailId} not found`); } const { data } = messageData; const headers = data.payload?.headers || []; const subject = headers.find(header => header.name === 'Subject')?.value || ''; const from = headers.find(header => header.name === 'From')?.value || ''; const to = headers.find(header => header.name === 'To')?.value || ''; const date = headers.find(header => header.name === 'Date')?.value || ''; const snippet = data.snippet || ''; const threadId = data.threadId || ''; const labels = data.labelIds || []; // Extract email body const body = this.extractEmailBody(data.payload); // Extract attachments const attachments = this.extractAttachments(data.payload); return { id: emailId, threadId, subject, from, to, date, snippet, body, attachments, labels }; } catch (error) { console.error(`Error getting email ${emailId}:`, error); throw new Error(`Failed to get email: ${error instanceof Error ? error.message : String(error)}`); } } /** * Search for emails matching a query * * @param query - Search query string in Gmail query format * @param options - Additional query options * @returns Promise resolving to a list of emails */ async searchEmails(query, options = {}) { return this.listEmails({ ...options, q: query }); } /** * Get emails from a specific sender * * @param fromEmail - Email address of the sender * @param options - Additional query options * @returns Promise resolving to a list of emails */ async getEmailsFrom(fromEmail, options = {}) { return this.searchEmails(`from:${fromEmail}`, options); } /** * Extract email body from message payload * * @param payload - Gmail message payload * @returns Object containing plain text and HTML body */ extractEmailBody(payload) { if (!payload) return {}; const body = {}; // Check if the current part has a body with data if (payload.mimeType === 'text/plain' && payload.body?.data) { body.plain = Buffer.from(payload.body.data, 'base64').toString('utf-8'); } else if (payload.mimeType === 'text/html' && payload.body?.data) { body.html = Buffer.from(payload.body.data, 'base64').toString('utf-8'); } // If this part has child parts, process them recursively if (payload.parts && payload.parts.length > 0) { for (const part of payload.parts) { if (part.mimeType === 'text/plain' && part.body?.data) { body.plain = Buffer.from(part.body.data, 'base64').toString('utf-8'); } else if (part.mimeType === 'text/html' && part.body?.data) { body.html = Buffer.from(part.body.data, 'base64').toString('utf-8'); } else if (part.parts) { // Recursively check nested parts const nestedBody = this.extractEmailBody(part); if (nestedBody.plain && !body.plain) body.plain = nestedBody.plain; if (nestedBody.html && !body.html) body.html = nestedBody.html; } } } return body; } /** * Extract attachments from message payload * * @param payload - Gmail message payload * @returns Array of email attachments */ extractAttachments(payload) { const attachments = []; if (!payload) return attachments; // Function to process a message part const processPart = (part) => { // Check if this part is an attachment if (part.filename && part.filename.length > 0 && part.body?.attachmentId) { attachments.push({ attachmentId: part.body.attachmentId, filename: part.filename, mimeType: part.mimeType || 'application/octet-stream', size: part.body.size !== null && part.body.size !== undefined ? part.body.size : 0 }); } // Process child parts recursively if (part.parts && part.parts.length > 0) { part.parts.forEach(processPart); } }; processPart(payload); return attachments; } /** * Get an attachment by ID * * @param messageId - ID of the email containing the attachment * @param attachmentId - ID of the attachment to retrieve * @returns Promise resolving to attachment data as a Buffer */ async getAttachment(messageId, attachmentId) { try { await this.authenticate(); const userId = this.impersonateEmail || 'me'; const response = await gmail.users.messages.attachments.get({ userId, messageId, id: attachmentId, auth: this.auth }); if (!response.data.data) { throw new Error('Attachment data not found'); } return Buffer.from(response.data.data, 'base64'); } catch (error) { console.error(`Error getting attachment ${attachmentId}:`, error); throw new Error(`Failed to get attachment: ${error instanceof Error ? error.message : String(error)}`); } } } exports.GmailClient = GmailClient; //# sourceMappingURL=gmailClient.js.map