@memberjunction/communication-gmail 3.4.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -211
- package/dist/GmailProvider.d.ts +175 -0
- package/dist/GmailProvider.d.ts.map +1 -0
- package/dist/GmailProvider.js +242 -70
- package/dist/GmailProvider.js.map +1 -1
- package/dist/auth.d.ts +3 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +13 -35
- package/dist/auth.js.map +1 -1
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +14 -35
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -19
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
package/dist/GmailProvider.js
CHANGED
|
@@ -1,54 +1,37 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
1
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
6
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const communication_types_1 = require("@memberjunction/communication-types");
|
|
34
|
-
const global_1 = require("@memberjunction/global");
|
|
35
|
-
const core_1 = require("@memberjunction/core");
|
|
36
|
-
const Config = __importStar(require("./config"));
|
|
37
|
-
const googleApis = __importStar(require("googleapis"));
|
|
38
|
-
let GmailProvider = class GmailProvider extends communication_types_1.BaseCommunicationProvider {
|
|
7
|
+
import { BaseCommunicationProvider, resolveCredentialValue, validateRequiredCredentials } from "@memberjunction/communication-types";
|
|
8
|
+
import { RegisterClass } from "@memberjunction/global";
|
|
9
|
+
import { LogError, LogStatus } from "@memberjunction/core";
|
|
10
|
+
import * as Config from "./config.js";
|
|
11
|
+
import googleApis from 'googleapis';
|
|
12
|
+
/**
|
|
13
|
+
* Implementation of the Gmail provider for sending and receiving messages
|
|
14
|
+
*/
|
|
15
|
+
let GmailProvider = class GmailProvider extends BaseCommunicationProvider {
|
|
39
16
|
constructor() {
|
|
40
17
|
super(...arguments);
|
|
18
|
+
/** Cached Gmail client for environment credentials */
|
|
41
19
|
this.envGmailClient = null;
|
|
20
|
+
/** Cache of Gmail clients for per-request credentials */
|
|
42
21
|
this.clientCache = new Map();
|
|
43
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolves credentials by merging request credentials with environment fallback
|
|
25
|
+
*/
|
|
44
26
|
resolveCredentials(credentials) {
|
|
45
27
|
const disableFallback = credentials?.disableEnvironmentFallback ?? false;
|
|
46
|
-
const clientId =
|
|
47
|
-
const clientSecret =
|
|
48
|
-
const redirectUri =
|
|
49
|
-
const refreshToken =
|
|
50
|
-
const serviceAccountEmail =
|
|
51
|
-
|
|
28
|
+
const clientId = resolveCredentialValue(credentials?.clientId, Config.GMAIL_CLIENT_ID, disableFallback);
|
|
29
|
+
const clientSecret = resolveCredentialValue(credentials?.clientSecret, Config.GMAIL_CLIENT_SECRET, disableFallback);
|
|
30
|
+
const redirectUri = resolveCredentialValue(credentials?.redirectUri, Config.GMAIL_REDIRECT_URI, disableFallback);
|
|
31
|
+
const refreshToken = resolveCredentialValue(credentials?.refreshToken, Config.GMAIL_REFRESH_TOKEN, disableFallback);
|
|
32
|
+
const serviceAccountEmail = resolveCredentialValue(credentials?.serviceAccountEmail, Config.GMAIL_SERVICE_ACCOUNT_EMAIL, disableFallback);
|
|
33
|
+
// Validate required credentials
|
|
34
|
+
validateRequiredCredentials({ clientId, clientSecret, redirectUri, refreshToken }, ['clientId', 'clientSecret', 'redirectUri', 'refreshToken'], 'Gmail');
|
|
52
35
|
return {
|
|
53
36
|
clientId: clientId,
|
|
54
37
|
clientSecret: clientSecret,
|
|
@@ -57,17 +40,27 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
57
40
|
serviceAccountEmail: serviceAccountEmail || ''
|
|
58
41
|
};
|
|
59
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a Gmail client with the given credentials
|
|
45
|
+
*/
|
|
60
46
|
createGmailClient(creds) {
|
|
47
|
+
// Create OAuth2 client
|
|
61
48
|
const oauth2Client = new googleApis.google.auth.OAuth2(creds.clientId, creds.clientSecret, creds.redirectUri);
|
|
49
|
+
// Set refresh token to automatically refresh access tokens
|
|
62
50
|
oauth2Client.setCredentials({
|
|
63
51
|
refresh_token: creds.refreshToken
|
|
64
52
|
});
|
|
53
|
+
// Create Gmail API client
|
|
65
54
|
return googleApis.google.gmail({
|
|
66
55
|
version: 'v1',
|
|
67
56
|
auth: oauth2Client
|
|
68
57
|
});
|
|
69
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Gets a Gmail client for the given credentials, using caching for efficiency
|
|
61
|
+
*/
|
|
70
62
|
getGmailClient(creds) {
|
|
63
|
+
// Check if using environment credentials (can use shared client)
|
|
71
64
|
const isEnvCredentials = creds.clientId === Config.GMAIL_CLIENT_ID &&
|
|
72
65
|
creds.clientSecret === Config.GMAIL_CLIENT_SECRET &&
|
|
73
66
|
creds.refreshToken === Config.GMAIL_REFRESH_TOKEN;
|
|
@@ -80,6 +73,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
80
73
|
}
|
|
81
74
|
return this.envGmailClient;
|
|
82
75
|
}
|
|
76
|
+
// For per-request credentials, use cached client by credential key
|
|
83
77
|
const cacheKey = `${creds.clientId}:${creds.refreshToken.substring(0, 10)}`;
|
|
84
78
|
let cached = this.clientCache.get(cacheKey);
|
|
85
79
|
if (!cached) {
|
|
@@ -91,11 +85,15 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
91
85
|
}
|
|
92
86
|
return cached;
|
|
93
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* Gets the authenticated user's email address for a given cached client
|
|
90
|
+
*/
|
|
94
91
|
async getUserEmail(cached) {
|
|
95
92
|
if (cached.userEmail) {
|
|
96
93
|
return cached.userEmail;
|
|
97
94
|
}
|
|
98
95
|
try {
|
|
96
|
+
// Get user profile to verify authentication
|
|
99
97
|
const response = await cached.client.users.getProfile({
|
|
100
98
|
userId: 'me'
|
|
101
99
|
});
|
|
@@ -106,37 +104,48 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
106
104
|
return null;
|
|
107
105
|
}
|
|
108
106
|
catch (error) {
|
|
109
|
-
|
|
107
|
+
LogError('Failed to get Gmail user email', undefined, error);
|
|
110
108
|
return null;
|
|
111
109
|
}
|
|
112
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Encode and format email content for Gmail API
|
|
113
|
+
*/
|
|
113
114
|
createEmailContent(message, creds) {
|
|
115
|
+
// Get sender email
|
|
114
116
|
const from = message.From || creds.serviceAccountEmail;
|
|
115
117
|
const fromName = message.FromName || '';
|
|
116
118
|
const fromHeader = fromName ? `${fromName} <${from}>` : from;
|
|
119
|
+
// Create email content
|
|
117
120
|
const subject = message.ProcessedSubject;
|
|
118
121
|
const to = message.To;
|
|
119
122
|
const cc = message.CCRecipients?.join(', ') || '';
|
|
120
123
|
const bcc = message.BCCRecipients?.join(', ') || '';
|
|
124
|
+
// Headers
|
|
121
125
|
let emailContent = [
|
|
122
126
|
`From: ${fromHeader}`,
|
|
123
127
|
`To: ${to}`,
|
|
124
128
|
`Subject: ${subject}`
|
|
125
129
|
];
|
|
130
|
+
// Add CC and BCC if present
|
|
126
131
|
if (cc)
|
|
127
132
|
emailContent.push(`Cc: ${cc}`);
|
|
128
133
|
if (bcc)
|
|
129
134
|
emailContent.push(`Bcc: ${bcc}`);
|
|
135
|
+
// Add content type and message body
|
|
130
136
|
if (message.ProcessedHTMLBody) {
|
|
137
|
+
// For HTML emails
|
|
131
138
|
const boundary = `boundary_${Math.random().toString(36).substring(2)}`;
|
|
132
139
|
emailContent.push('MIME-Version: 1.0');
|
|
133
140
|
emailContent.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
134
141
|
emailContent.push('');
|
|
142
|
+
// Text part
|
|
135
143
|
emailContent.push(`--${boundary}`);
|
|
136
144
|
emailContent.push('Content-Type: text/plain; charset=UTF-8');
|
|
137
145
|
emailContent.push('');
|
|
138
146
|
emailContent.push(message.ProcessedBody || '');
|
|
139
147
|
emailContent.push('');
|
|
148
|
+
// HTML part
|
|
140
149
|
emailContent.push(`--${boundary}`);
|
|
141
150
|
emailContent.push('Content-Type: text/html; charset=UTF-8');
|
|
142
151
|
emailContent.push('');
|
|
@@ -145,16 +154,26 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
145
154
|
emailContent.push(`--${boundary}--`);
|
|
146
155
|
}
|
|
147
156
|
else {
|
|
157
|
+
// Plain text email
|
|
148
158
|
emailContent.push('Content-Type: text/plain; charset=UTF-8');
|
|
149
159
|
emailContent.push('');
|
|
150
160
|
emailContent.push(message.ProcessedBody || '');
|
|
151
161
|
}
|
|
152
162
|
return Buffer.from(emailContent.join('\r\n')).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
153
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Sends a single message using the Gmail API
|
|
166
|
+
* @param message - The message to send
|
|
167
|
+
* @param credentials - Optional credentials override for this request.
|
|
168
|
+
* If not provided, uses environment variables.
|
|
169
|
+
* Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
|
|
170
|
+
*/
|
|
154
171
|
async SendSingleMessage(message, credentials) {
|
|
155
172
|
try {
|
|
173
|
+
// Resolve credentials (request credentials with env fallback)
|
|
156
174
|
const creds = this.resolveCredentials(credentials);
|
|
157
175
|
const cached = this.getGmailClient(creds);
|
|
176
|
+
// Get user email
|
|
158
177
|
const userEmail = await this.getUserEmail(cached);
|
|
159
178
|
if (!userEmail) {
|
|
160
179
|
return {
|
|
@@ -163,7 +182,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
163
182
|
Error: 'Could not get user email'
|
|
164
183
|
};
|
|
165
184
|
}
|
|
185
|
+
// Create raw email content in base64 URL-safe format
|
|
166
186
|
const raw = this.createEmailContent(message, creds);
|
|
187
|
+
// Send the email
|
|
167
188
|
const result = await cached.client.users.messages.send({
|
|
168
189
|
userId: 'me',
|
|
169
190
|
requestBody: {
|
|
@@ -171,7 +192,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
171
192
|
}
|
|
172
193
|
});
|
|
173
194
|
if (result && result.status >= 200 && result.status < 300) {
|
|
174
|
-
|
|
195
|
+
LogStatus(`Email sent via Gmail: ${result.statusText}`);
|
|
175
196
|
return {
|
|
176
197
|
Message: message,
|
|
177
198
|
Success: true,
|
|
@@ -179,7 +200,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
179
200
|
};
|
|
180
201
|
}
|
|
181
202
|
else {
|
|
182
|
-
|
|
203
|
+
LogError('Failed to send email via Gmail', undefined, result);
|
|
183
204
|
return {
|
|
184
205
|
Message: message,
|
|
185
206
|
Success: false,
|
|
@@ -189,7 +210,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
189
210
|
}
|
|
190
211
|
catch (error) {
|
|
191
212
|
const errorMessage = error instanceof Error ? error.message : 'Error sending message';
|
|
192
|
-
|
|
213
|
+
LogError('Error sending message via Gmail', undefined, error);
|
|
193
214
|
return {
|
|
194
215
|
Message: message,
|
|
195
216
|
Success: false,
|
|
@@ -197,8 +218,16 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
197
218
|
};
|
|
198
219
|
}
|
|
199
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* Gets messages from Gmail
|
|
223
|
+
* @param params - Parameters for fetching messages
|
|
224
|
+
* @param credentials - Optional credentials override for this request.
|
|
225
|
+
* If not provided, uses environment variables.
|
|
226
|
+
* Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
|
|
227
|
+
*/
|
|
200
228
|
async GetMessages(params, credentials) {
|
|
201
229
|
try {
|
|
230
|
+
// Resolve credentials (request credentials with env fallback)
|
|
202
231
|
const creds = this.resolveCredentials(credentials);
|
|
203
232
|
const cached = this.getGmailClient(creds);
|
|
204
233
|
const userEmail = await this.getUserEmail(cached);
|
|
@@ -209,6 +238,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
209
238
|
ErrorMessage: 'Could not get user email'
|
|
210
239
|
};
|
|
211
240
|
}
|
|
241
|
+
// Build query
|
|
212
242
|
let query = '';
|
|
213
243
|
if (params.UnreadOnly) {
|
|
214
244
|
query = 'is:unread';
|
|
@@ -216,6 +246,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
216
246
|
if (params.ContextData?.query) {
|
|
217
247
|
query = params.ContextData.query;
|
|
218
248
|
}
|
|
249
|
+
// Get messages
|
|
219
250
|
const response = await cached.client.users.messages.list({
|
|
220
251
|
userId: 'me',
|
|
221
252
|
maxResults: params.NumMessages,
|
|
@@ -227,6 +258,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
227
258
|
Messages: []
|
|
228
259
|
};
|
|
229
260
|
}
|
|
261
|
+
// Get full message details for each message ID
|
|
230
262
|
const messagePromises = response.data.messages.map(async (message) => {
|
|
231
263
|
const fullMessage = await cached.client.users.messages.get({
|
|
232
264
|
userId: 'me',
|
|
@@ -236,7 +268,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
236
268
|
return fullMessage.data;
|
|
237
269
|
});
|
|
238
270
|
const fullMessages = await Promise.all(messagePromises);
|
|
271
|
+
// Process messages into standard format
|
|
239
272
|
const processedMessages = fullMessages.map(message => {
|
|
273
|
+
// Extract headers
|
|
240
274
|
const headers = message.payload?.headers || [];
|
|
241
275
|
const getHeader = (name) => {
|
|
242
276
|
const header = headers.find(h => h.name?.toLowerCase() === name.toLowerCase());
|
|
@@ -246,11 +280,14 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
246
280
|
const to = getHeader('to');
|
|
247
281
|
const subject = getHeader('subject');
|
|
248
282
|
const replyTo = getHeader('reply-to') ? [getHeader('reply-to')] : [from];
|
|
283
|
+
// Extract body
|
|
249
284
|
let body = '';
|
|
250
285
|
if (message.payload?.body?.data) {
|
|
286
|
+
// Base64 encoded data
|
|
251
287
|
body = Buffer.from(message.payload.body.data, 'base64').toString('utf-8');
|
|
252
288
|
}
|
|
253
289
|
else if (message.payload?.parts) {
|
|
290
|
+
// Multipart message, try to find text part
|
|
254
291
|
const textPart = message.payload.parts.find(part => part.mimeType === 'text/plain');
|
|
255
292
|
if (textPart && textPart.body?.data) {
|
|
256
293
|
body = Buffer.from(textPart.body.data, 'base64').toString('utf-8');
|
|
@@ -266,6 +303,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
266
303
|
ThreadID: message.threadId || ''
|
|
267
304
|
};
|
|
268
305
|
});
|
|
306
|
+
// Mark as read if requested
|
|
269
307
|
if (params.ContextData?.MarkAsRead) {
|
|
270
308
|
for (const message of fullMessages) {
|
|
271
309
|
if (message.id) {
|
|
@@ -281,7 +319,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
281
319
|
}
|
|
282
320
|
catch (error) {
|
|
283
321
|
const errorMessage = error instanceof Error ? error.message : 'Error getting messages';
|
|
284
|
-
|
|
322
|
+
LogError('Error getting messages from Gmail', undefined, error);
|
|
285
323
|
return {
|
|
286
324
|
Success: false,
|
|
287
325
|
Messages: [],
|
|
@@ -289,6 +327,13 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
289
327
|
};
|
|
290
328
|
}
|
|
291
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Reply to a message using Gmail API
|
|
332
|
+
* @param params - Parameters for replying to a message
|
|
333
|
+
* @param credentials - Optional credentials override for this request.
|
|
334
|
+
* If not provided, uses environment variables.
|
|
335
|
+
* Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
|
|
336
|
+
*/
|
|
292
337
|
async ReplyToMessage(params, credentials) {
|
|
293
338
|
try {
|
|
294
339
|
if (!params.MessageID) {
|
|
@@ -297,8 +342,10 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
297
342
|
ErrorMessage: 'Message ID not provided'
|
|
298
343
|
};
|
|
299
344
|
}
|
|
345
|
+
// Resolve credentials (request credentials with env fallback)
|
|
300
346
|
const creds = this.resolveCredentials(credentials);
|
|
301
347
|
const cached = this.getGmailClient(creds);
|
|
348
|
+
// Get the original message to obtain threadId
|
|
302
349
|
const originalMessage = await cached.client.users.messages.get({
|
|
303
350
|
userId: 'me',
|
|
304
351
|
id: params.MessageID
|
|
@@ -309,7 +356,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
309
356
|
ErrorMessage: 'Could not get thread ID from original message'
|
|
310
357
|
};
|
|
311
358
|
}
|
|
359
|
+
// Create raw email content
|
|
312
360
|
const raw = this.createEmailContent(params.Message, creds);
|
|
361
|
+
// Send the reply in the same thread
|
|
313
362
|
const result = await cached.client.users.messages.send({
|
|
314
363
|
userId: 'me',
|
|
315
364
|
requestBody: {
|
|
@@ -332,13 +381,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
332
381
|
}
|
|
333
382
|
catch (error) {
|
|
334
383
|
const errorMessage = error instanceof Error ? error.message : 'Error replying to message';
|
|
335
|
-
|
|
384
|
+
LogError('Error replying to message via Gmail', undefined, error);
|
|
336
385
|
return {
|
|
337
386
|
Success: false,
|
|
338
387
|
ErrorMessage: errorMessage
|
|
339
388
|
};
|
|
340
389
|
}
|
|
341
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Forward a message using Gmail API
|
|
393
|
+
* @param params - Parameters for forwarding a message
|
|
394
|
+
* @param credentials - Optional credentials override for this request.
|
|
395
|
+
* If not provided, uses environment variables.
|
|
396
|
+
* Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
|
|
397
|
+
*/
|
|
342
398
|
async ForwardMessage(params, credentials) {
|
|
343
399
|
try {
|
|
344
400
|
if (!params.MessageID) {
|
|
@@ -347,8 +403,10 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
347
403
|
ErrorMessage: 'Message ID not provided'
|
|
348
404
|
};
|
|
349
405
|
}
|
|
406
|
+
// Resolve credentials (request credentials with env fallback)
|
|
350
407
|
const creds = this.resolveCredentials(credentials);
|
|
351
408
|
const cached = this.getGmailClient(creds);
|
|
409
|
+
// Get the original message
|
|
352
410
|
const originalMessage = await cached.client.users.messages.get({
|
|
353
411
|
userId: 'me',
|
|
354
412
|
id: params.MessageID,
|
|
@@ -360,26 +418,33 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
360
418
|
ErrorMessage: 'Could not get raw content of original message'
|
|
361
419
|
};
|
|
362
420
|
}
|
|
421
|
+
// Convert raw message to proper format
|
|
363
422
|
const rawContent = Buffer.from(originalMessage.data.raw, 'base64').toString('utf-8');
|
|
423
|
+
// Build forwarded message
|
|
364
424
|
const userEmail = await this.getUserEmail(cached);
|
|
365
425
|
const to = params.ToRecipients.join(', ');
|
|
366
426
|
const cc = params.CCRecipients?.join(', ') || '';
|
|
367
427
|
const bcc = params.BCCRecipients?.join(', ') || '';
|
|
428
|
+
// Parse the original email to extract subject
|
|
368
429
|
const subjectMatch = rawContent.match(/Subject: (.*?)(\r?\n)/);
|
|
369
430
|
const subject = subjectMatch ? `Fwd: ${subjectMatch[1]}` : 'Fwd: ';
|
|
431
|
+
// Headers for new message
|
|
370
432
|
const emailContent = [
|
|
371
433
|
`From: ${userEmail}`,
|
|
372
434
|
`To: ${to}`,
|
|
373
435
|
`Subject: ${subject}`
|
|
374
436
|
];
|
|
437
|
+
// Add CC and BCC if present
|
|
375
438
|
if (cc)
|
|
376
439
|
emailContent.push(`Cc: ${cc}`);
|
|
377
440
|
if (bcc)
|
|
378
441
|
emailContent.push(`Bcc: ${bcc}`);
|
|
442
|
+
// Add content type
|
|
379
443
|
const boundary = `boundary_${Math.random().toString(36).substring(2)}`;
|
|
380
444
|
emailContent.push('MIME-Version: 1.0');
|
|
381
445
|
emailContent.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
382
446
|
emailContent.push('');
|
|
447
|
+
// Forward comment
|
|
383
448
|
if (params.Message) {
|
|
384
449
|
emailContent.push(`--${boundary}`);
|
|
385
450
|
emailContent.push('Content-Type: text/plain; charset=UTF-8');
|
|
@@ -387,6 +452,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
387
452
|
emailContent.push(params.Message);
|
|
388
453
|
emailContent.push('');
|
|
389
454
|
}
|
|
455
|
+
// Original message as attachment
|
|
390
456
|
emailContent.push(`--${boundary}`);
|
|
391
457
|
emailContent.push('Content-Type: message/rfc822; name="forwarded_message.eml"');
|
|
392
458
|
emailContent.push('Content-Disposition: attachment; filename="forwarded_message.eml"');
|
|
@@ -394,10 +460,12 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
394
460
|
emailContent.push(rawContent);
|
|
395
461
|
emailContent.push('');
|
|
396
462
|
emailContent.push(`--${boundary}--`);
|
|
463
|
+
// Encode email content
|
|
397
464
|
const raw = Buffer.from(emailContent.join('\r\n')).toString('base64')
|
|
398
465
|
.replace(/\+/g, '-')
|
|
399
466
|
.replace(/\//g, '_')
|
|
400
467
|
.replace(/=+$/, '');
|
|
468
|
+
// Send the forwarded message
|
|
401
469
|
const result = await cached.client.users.messages.send({
|
|
402
470
|
userId: 'me',
|
|
403
471
|
requestBody: {
|
|
@@ -419,13 +487,16 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
419
487
|
}
|
|
420
488
|
catch (error) {
|
|
421
489
|
const errorMessage = error instanceof Error ? error.message : 'Error forwarding message';
|
|
422
|
-
|
|
490
|
+
LogError('Error forwarding message via Gmail', undefined, error);
|
|
423
491
|
return {
|
|
424
492
|
Success: false,
|
|
425
493
|
ErrorMessage: errorMessage
|
|
426
494
|
};
|
|
427
495
|
}
|
|
428
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Helper to mark a message as read
|
|
499
|
+
*/
|
|
429
500
|
async markMessageAsRead(gmailClient, messageId) {
|
|
430
501
|
try {
|
|
431
502
|
await gmailClient.users.messages.modify({
|
|
@@ -438,12 +509,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
438
509
|
return true;
|
|
439
510
|
}
|
|
440
511
|
catch (error) {
|
|
441
|
-
|
|
512
|
+
LogError(`Error marking message ${messageId} as read`, undefined, error);
|
|
442
513
|
return false;
|
|
443
514
|
}
|
|
444
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Creates a draft message in Gmail
|
|
518
|
+
* @param params - Parameters for creating a draft
|
|
519
|
+
* @param credentials - Optional credentials override for this request.
|
|
520
|
+
* If not provided, uses environment variables.
|
|
521
|
+
* Set `credentials.disableEnvironmentFallback = true` to require explicit credentials.
|
|
522
|
+
*/
|
|
445
523
|
async CreateDraft(params, credentials) {
|
|
446
524
|
try {
|
|
525
|
+
// Resolve credentials (request credentials with env fallback)
|
|
447
526
|
const creds = this.resolveCredentials(credentials);
|
|
448
527
|
const cached = this.getGmailClient(creds);
|
|
449
528
|
const userEmail = await this.getUserEmail(cached);
|
|
@@ -453,7 +532,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
453
532
|
ErrorMessage: 'Could not get user email'
|
|
454
533
|
};
|
|
455
534
|
}
|
|
535
|
+
// Reuse existing email content creation logic
|
|
456
536
|
const raw = this.createEmailContent(params.Message, creds);
|
|
537
|
+
// Create draft using Gmail API
|
|
457
538
|
const result = await cached.client.users.drafts.create({
|
|
458
539
|
userId: 'me',
|
|
459
540
|
requestBody: {
|
|
@@ -461,7 +542,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
461
542
|
}
|
|
462
543
|
});
|
|
463
544
|
if (result && result.status >= 200 && result.status < 300) {
|
|
464
|
-
|
|
545
|
+
LogStatus(`Draft created via Gmail: ${result.data.id}`);
|
|
465
546
|
return {
|
|
466
547
|
Success: true,
|
|
467
548
|
DraftID: result.data.id || undefined,
|
|
@@ -469,7 +550,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
469
550
|
};
|
|
470
551
|
}
|
|
471
552
|
else {
|
|
472
|
-
|
|
553
|
+
LogError('Failed to create draft via Gmail', undefined, result);
|
|
473
554
|
return {
|
|
474
555
|
Success: false,
|
|
475
556
|
ErrorMessage: `Failed to create draft: ${result?.statusText || 'Unknown error'}`
|
|
@@ -478,13 +559,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
478
559
|
}
|
|
479
560
|
catch (error) {
|
|
480
561
|
const errorMessage = error instanceof Error ? error.message : 'Error creating draft';
|
|
481
|
-
|
|
562
|
+
LogError('Error creating draft via Gmail', undefined, error);
|
|
482
563
|
return {
|
|
483
564
|
Success: false,
|
|
484
565
|
ErrorMessage: errorMessage
|
|
485
566
|
};
|
|
486
567
|
}
|
|
487
568
|
}
|
|
569
|
+
// ========================================================================
|
|
570
|
+
// EXTENDED OPERATIONS - Gmail supports all mailbox operations via labels
|
|
571
|
+
// ========================================================================
|
|
572
|
+
/**
|
|
573
|
+
* Returns the list of operations supported by the Gmail provider.
|
|
574
|
+
* Gmail supports all operations through its label-based system.
|
|
575
|
+
*/
|
|
488
576
|
getSupportedOperations() {
|
|
489
577
|
return [
|
|
490
578
|
'SendSingleMessage',
|
|
@@ -495,7 +583,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
495
583
|
'CreateDraft',
|
|
496
584
|
'DeleteMessage',
|
|
497
585
|
'MoveMessage',
|
|
498
|
-
'ListFolders',
|
|
586
|
+
'ListFolders', // Gmail uses labels instead of folders
|
|
499
587
|
'MarkAsRead',
|
|
500
588
|
'ArchiveMessage',
|
|
501
589
|
'SearchMessages',
|
|
@@ -503,6 +591,11 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
503
591
|
'DownloadAttachment'
|
|
504
592
|
];
|
|
505
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Gets a single message by ID
|
|
596
|
+
* @param params - Parameters for retrieving the message
|
|
597
|
+
* @param credentials - Optional credentials override for this request
|
|
598
|
+
*/
|
|
506
599
|
async GetSingleMessage(params, credentials) {
|
|
507
600
|
try {
|
|
508
601
|
const creds = this.resolveCredentials(credentials);
|
|
@@ -527,53 +620,68 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
527
620
|
}
|
|
528
621
|
catch (error) {
|
|
529
622
|
const errorMessage = error instanceof Error ? error.message : 'Error getting message';
|
|
530
|
-
|
|
623
|
+
LogError(`Error getting message ${params.MessageID} from Gmail`, undefined, error);
|
|
531
624
|
return {
|
|
532
625
|
Success: false,
|
|
533
626
|
ErrorMessage: errorMessage
|
|
534
627
|
};
|
|
535
628
|
}
|
|
536
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Deletes a message from Gmail
|
|
632
|
+
* @param params - Parameters for deleting the message
|
|
633
|
+
* @param credentials - Optional credentials override for this request
|
|
634
|
+
*/
|
|
537
635
|
async DeleteMessage(params, credentials) {
|
|
538
636
|
try {
|
|
539
637
|
const creds = this.resolveCredentials(credentials);
|
|
540
638
|
const cached = this.getGmailClient(creds);
|
|
541
639
|
if (params.PermanentDelete) {
|
|
640
|
+
// Permanently delete the message
|
|
542
641
|
await cached.client.users.messages.delete({
|
|
543
642
|
userId: 'me',
|
|
544
643
|
id: params.MessageID
|
|
545
644
|
});
|
|
546
645
|
}
|
|
547
646
|
else {
|
|
647
|
+
// Move to trash (adds TRASH label, removes INBOX)
|
|
548
648
|
await cached.client.users.messages.trash({
|
|
549
649
|
userId: 'me',
|
|
550
650
|
id: params.MessageID
|
|
551
651
|
});
|
|
552
652
|
}
|
|
553
|
-
|
|
653
|
+
LogStatus(`Message ${params.MessageID} deleted from Gmail (permanent: ${params.PermanentDelete})`);
|
|
554
654
|
return {
|
|
555
655
|
Success: true
|
|
556
656
|
};
|
|
557
657
|
}
|
|
558
658
|
catch (error) {
|
|
559
659
|
const errorMessage = error instanceof Error ? error.message : 'Error deleting message';
|
|
560
|
-
|
|
660
|
+
LogError(`Error deleting message ${params.MessageID} from Gmail`, undefined, error);
|
|
561
661
|
return {
|
|
562
662
|
Success: false,
|
|
563
663
|
ErrorMessage: errorMessage
|
|
564
664
|
};
|
|
565
665
|
}
|
|
566
666
|
}
|
|
667
|
+
/**
|
|
668
|
+
* Moves a message to a different label (Gmail's equivalent of folders)
|
|
669
|
+
* In Gmail, moving is done by adding/removing labels
|
|
670
|
+
* @param params - Parameters for moving the message
|
|
671
|
+
* @param credentials - Optional credentials override for this request
|
|
672
|
+
*/
|
|
567
673
|
async MoveMessage(params, credentials) {
|
|
568
674
|
try {
|
|
569
675
|
const creds = this.resolveCredentials(credentials);
|
|
570
676
|
const cached = this.getGmailClient(creds);
|
|
677
|
+
// First get current labels on the message
|
|
571
678
|
const message = await cached.client.users.messages.get({
|
|
572
679
|
userId: 'me',
|
|
573
680
|
id: params.MessageID,
|
|
574
681
|
format: 'minimal'
|
|
575
682
|
});
|
|
576
683
|
const currentLabels = message.data.labelIds || [];
|
|
684
|
+
// Remove INBOX and other category labels, add the destination label
|
|
577
685
|
const labelsToRemove = currentLabels.filter(label => label === 'INBOX' ||
|
|
578
686
|
label === 'CATEGORY_PERSONAL' ||
|
|
579
687
|
label === 'CATEGORY_SOCIAL' ||
|
|
@@ -588,21 +696,26 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
588
696
|
removeLabelIds: labelsToRemove
|
|
589
697
|
}
|
|
590
698
|
});
|
|
591
|
-
|
|
699
|
+
LogStatus(`Message ${params.MessageID} moved to label ${params.DestinationFolderID}`);
|
|
592
700
|
return {
|
|
593
701
|
Success: true,
|
|
594
|
-
NewMessageID: params.MessageID
|
|
702
|
+
NewMessageID: params.MessageID // Gmail doesn't change message ID on move
|
|
595
703
|
};
|
|
596
704
|
}
|
|
597
705
|
catch (error) {
|
|
598
706
|
const errorMessage = error instanceof Error ? error.message : 'Error moving message';
|
|
599
|
-
|
|
707
|
+
LogError(`Error moving message ${params.MessageID} in Gmail`, undefined, error);
|
|
600
708
|
return {
|
|
601
709
|
Success: false,
|
|
602
710
|
ErrorMessage: errorMessage
|
|
603
711
|
};
|
|
604
712
|
}
|
|
605
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Lists Gmail labels (Gmail's equivalent of folders)
|
|
716
|
+
* @param params - Parameters for listing labels
|
|
717
|
+
* @param credentials - Optional credentials override for this request
|
|
718
|
+
*/
|
|
606
719
|
async ListFolders(params, credentials) {
|
|
607
720
|
try {
|
|
608
721
|
const creds = this.resolveCredentials(credentials);
|
|
@@ -616,6 +729,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
616
729
|
Folders: []
|
|
617
730
|
};
|
|
618
731
|
}
|
|
732
|
+
// Get detailed info for each label if counts requested
|
|
619
733
|
let labels = response.data.labels;
|
|
620
734
|
if (params.IncludeCounts) {
|
|
621
735
|
const detailedLabels = await Promise.all(labels.map(async (label) => {
|
|
@@ -642,6 +756,8 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
642
756
|
IsSystemFolder: label.type === 'system',
|
|
643
757
|
SystemFolderType: this.mapGmailLabelToSystemFolder(label.id || '')
|
|
644
758
|
}));
|
|
759
|
+
// Filter by parent if specified (Gmail doesn't have nested labels in the API the same way)
|
|
760
|
+
// User labels can have "/" in names to simulate hierarchy
|
|
645
761
|
if (params.ParentFolderID) {
|
|
646
762
|
const parent = folders.find(f => f.ID === params.ParentFolderID);
|
|
647
763
|
if (parent) {
|
|
@@ -661,17 +777,23 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
661
777
|
}
|
|
662
778
|
catch (error) {
|
|
663
779
|
const errorMessage = error instanceof Error ? error.message : 'Error listing labels';
|
|
664
|
-
|
|
780
|
+
LogError('Error listing labels from Gmail', undefined, error);
|
|
665
781
|
return {
|
|
666
782
|
Success: false,
|
|
667
783
|
ErrorMessage: errorMessage
|
|
668
784
|
};
|
|
669
785
|
}
|
|
670
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* Marks messages as read or unread
|
|
789
|
+
* @param params - Parameters for marking messages
|
|
790
|
+
* @param credentials - Optional credentials override for this request
|
|
791
|
+
*/
|
|
671
792
|
async MarkAsRead(params, credentials) {
|
|
672
793
|
try {
|
|
673
794
|
const creds = this.resolveCredentials(credentials);
|
|
674
795
|
const cached = this.getGmailClient(creds);
|
|
796
|
+
// Process all messages
|
|
675
797
|
await Promise.all(params.MessageIDs.map(async (messageId) => {
|
|
676
798
|
await cached.client.users.messages.modify({
|
|
677
799
|
userId: 'me',
|
|
@@ -681,24 +803,30 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
681
803
|
: { addLabelIds: ['UNREAD'] }
|
|
682
804
|
});
|
|
683
805
|
}));
|
|
684
|
-
|
|
806
|
+
LogStatus(`Marked ${params.MessageIDs.length} message(s) as ${params.IsRead ? 'read' : 'unread'}`);
|
|
685
807
|
return {
|
|
686
808
|
Success: true
|
|
687
809
|
};
|
|
688
810
|
}
|
|
689
811
|
catch (error) {
|
|
690
812
|
const errorMessage = error instanceof Error ? error.message : 'Error marking messages';
|
|
691
|
-
|
|
813
|
+
LogError('Error marking messages as read/unread in Gmail', undefined, error);
|
|
692
814
|
return {
|
|
693
815
|
Success: false,
|
|
694
816
|
ErrorMessage: errorMessage
|
|
695
817
|
};
|
|
696
818
|
}
|
|
697
819
|
}
|
|
820
|
+
/**
|
|
821
|
+
* Archives a message (removes INBOX label in Gmail)
|
|
822
|
+
* @param params - Parameters for archiving the message
|
|
823
|
+
* @param credentials - Optional credentials override for this request
|
|
824
|
+
*/
|
|
698
825
|
async ArchiveMessage(params, credentials) {
|
|
699
826
|
try {
|
|
700
827
|
const creds = this.resolveCredentials(credentials);
|
|
701
828
|
const cached = this.getGmailClient(creds);
|
|
829
|
+
// In Gmail, archiving is simply removing the INBOX label
|
|
702
830
|
await cached.client.users.messages.modify({
|
|
703
831
|
userId: 'me',
|
|
704
832
|
id: params.MessageID,
|
|
@@ -706,25 +834,32 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
706
834
|
removeLabelIds: ['INBOX']
|
|
707
835
|
}
|
|
708
836
|
});
|
|
709
|
-
|
|
837
|
+
LogStatus(`Message ${params.MessageID} archived in Gmail`);
|
|
710
838
|
return {
|
|
711
839
|
Success: true
|
|
712
840
|
};
|
|
713
841
|
}
|
|
714
842
|
catch (error) {
|
|
715
843
|
const errorMessage = error instanceof Error ? error.message : 'Error archiving message';
|
|
716
|
-
|
|
844
|
+
LogError(`Error archiving message ${params.MessageID} in Gmail`, undefined, error);
|
|
717
845
|
return {
|
|
718
846
|
Success: false,
|
|
719
847
|
ErrorMessage: errorMessage
|
|
720
848
|
};
|
|
721
849
|
}
|
|
722
850
|
}
|
|
851
|
+
/**
|
|
852
|
+
* Searches messages using Gmail's search syntax
|
|
853
|
+
* @param params - Parameters for searching messages
|
|
854
|
+
* @param credentials - Optional credentials override for this request
|
|
855
|
+
*/
|
|
723
856
|
async SearchMessages(params, credentials) {
|
|
724
857
|
try {
|
|
725
858
|
const creds = this.resolveCredentials(credentials);
|
|
726
859
|
const cached = this.getGmailClient(creds);
|
|
860
|
+
// Build Gmail search query
|
|
727
861
|
let query = params.Query;
|
|
862
|
+
// Add date filters if specified
|
|
728
863
|
if (params.FromDate) {
|
|
729
864
|
const fromDateStr = this.formatDateForGmail(params.FromDate);
|
|
730
865
|
query += ` after:${fromDateStr}`;
|
|
@@ -733,9 +868,11 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
733
868
|
const toDateStr = this.formatDateForGmail(params.ToDate);
|
|
734
869
|
query += ` before:${toDateStr}`;
|
|
735
870
|
}
|
|
871
|
+
// Add folder/label filter
|
|
736
872
|
if (params.FolderID) {
|
|
737
873
|
query += ` label:${params.FolderID}`;
|
|
738
874
|
}
|
|
875
|
+
// Search messages
|
|
739
876
|
const response = await cached.client.users.messages.list({
|
|
740
877
|
userId: 'me',
|
|
741
878
|
q: query,
|
|
@@ -748,6 +885,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
748
885
|
TotalCount: 0
|
|
749
886
|
};
|
|
750
887
|
}
|
|
888
|
+
// Get full message details
|
|
751
889
|
const fullMessages = await Promise.all(response.data.messages.map(async (msg) => {
|
|
752
890
|
const full = await cached.client.users.messages.get({
|
|
753
891
|
userId: 'me',
|
|
@@ -766,13 +904,18 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
766
904
|
}
|
|
767
905
|
catch (error) {
|
|
768
906
|
const errorMessage = error instanceof Error ? error.message : 'Error searching messages';
|
|
769
|
-
|
|
907
|
+
LogError('Error searching messages in Gmail', undefined, error);
|
|
770
908
|
return {
|
|
771
909
|
Success: false,
|
|
772
910
|
ErrorMessage: errorMessage
|
|
773
911
|
};
|
|
774
912
|
}
|
|
775
913
|
}
|
|
914
|
+
/**
|
|
915
|
+
* Lists attachments on a message
|
|
916
|
+
* @param params - Parameters for listing attachments
|
|
917
|
+
* @param credentials - Optional credentials override for this request
|
|
918
|
+
*/
|
|
776
919
|
async ListAttachments(params, credentials) {
|
|
777
920
|
try {
|
|
778
921
|
const creds = this.resolveCredentials(credentials);
|
|
@@ -798,17 +941,23 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
798
941
|
}
|
|
799
942
|
catch (error) {
|
|
800
943
|
const errorMessage = error instanceof Error ? error.message : 'Error listing attachments';
|
|
801
|
-
|
|
944
|
+
LogError(`Error listing attachments for message ${params.MessageID}`, undefined, error);
|
|
802
945
|
return {
|
|
803
946
|
Success: false,
|
|
804
947
|
ErrorMessage: errorMessage
|
|
805
948
|
};
|
|
806
949
|
}
|
|
807
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Downloads an attachment from a message
|
|
953
|
+
* @param params - Parameters for downloading the attachment
|
|
954
|
+
* @param credentials - Optional credentials override for this request
|
|
955
|
+
*/
|
|
808
956
|
async DownloadAttachment(params, credentials) {
|
|
809
957
|
try {
|
|
810
958
|
const creds = this.resolveCredentials(credentials);
|
|
811
959
|
const cached = this.getGmailClient(creds);
|
|
960
|
+
// First get attachment metadata to find filename and content type
|
|
812
961
|
const message = await cached.client.users.messages.get({
|
|
813
962
|
userId: 'me',
|
|
814
963
|
id: params.MessageID,
|
|
@@ -818,6 +967,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
818
967
|
if (message.data.payload) {
|
|
819
968
|
attachmentInfo = this.findAttachmentInfo(message.data.payload, params.AttachmentID);
|
|
820
969
|
}
|
|
970
|
+
// Download the attachment
|
|
821
971
|
const response = await cached.client.users.messages.attachments.get({
|
|
822
972
|
userId: 'me',
|
|
823
973
|
messageId: params.MessageID,
|
|
@@ -829,6 +979,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
829
979
|
ErrorMessage: 'Attachment content not found'
|
|
830
980
|
};
|
|
831
981
|
}
|
|
982
|
+
// Gmail returns base64url encoded data, convert to standard base64
|
|
832
983
|
const base64Data = response.data.data.replace(/-/g, '+').replace(/_/g, '/');
|
|
833
984
|
const content = Buffer.from(base64Data, 'base64');
|
|
834
985
|
return {
|
|
@@ -842,13 +993,19 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
842
993
|
}
|
|
843
994
|
catch (error) {
|
|
844
995
|
const errorMessage = error instanceof Error ? error.message : 'Error downloading attachment';
|
|
845
|
-
|
|
996
|
+
LogError(`Error downloading attachment ${params.AttachmentID}`, undefined, error);
|
|
846
997
|
return {
|
|
847
998
|
Success: false,
|
|
848
999
|
ErrorMessage: errorMessage
|
|
849
1000
|
};
|
|
850
1001
|
}
|
|
851
1002
|
}
|
|
1003
|
+
// ========================================================================
|
|
1004
|
+
// HELPER METHODS
|
|
1005
|
+
// ========================================================================
|
|
1006
|
+
/**
|
|
1007
|
+
* Parses a Gmail message into the standard GetMessageMessage format
|
|
1008
|
+
*/
|
|
852
1009
|
parseGmailMessage(message) {
|
|
853
1010
|
const headers = message.payload?.headers || [];
|
|
854
1011
|
const getHeader = (name) => {
|
|
@@ -860,6 +1017,7 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
860
1017
|
const subject = getHeader('subject');
|
|
861
1018
|
const replyTo = getHeader('reply-to') ? [getHeader('reply-to')] : [from];
|
|
862
1019
|
const dateStr = getHeader('date');
|
|
1020
|
+
// Extract body
|
|
863
1021
|
let body = '';
|
|
864
1022
|
if (message.payload?.body?.data) {
|
|
865
1023
|
body = Buffer.from(message.payload.body.data, 'base64').toString('utf-8');
|
|
@@ -870,14 +1028,17 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
870
1028
|
body = Buffer.from(textPart.body.data, 'base64').toString('utf-8');
|
|
871
1029
|
}
|
|
872
1030
|
}
|
|
1031
|
+
// Parse date
|
|
873
1032
|
let receivedAt;
|
|
874
1033
|
if (dateStr) {
|
|
875
1034
|
try {
|
|
876
1035
|
receivedAt = new Date(dateStr);
|
|
877
1036
|
}
|
|
878
1037
|
catch {
|
|
1038
|
+
// Ignore parse errors
|
|
879
1039
|
}
|
|
880
1040
|
}
|
|
1041
|
+
// Internal date from Gmail (epoch milliseconds)
|
|
881
1042
|
let createdAt;
|
|
882
1043
|
if (message.internalDate) {
|
|
883
1044
|
createdAt = new Date(parseInt(message.internalDate, 10));
|
|
@@ -894,6 +1055,9 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
894
1055
|
CreatedAt: createdAt
|
|
895
1056
|
};
|
|
896
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Maps Gmail label IDs to system folder types
|
|
1060
|
+
*/
|
|
897
1061
|
mapGmailLabelToSystemFolder(labelId) {
|
|
898
1062
|
const labelMap = {
|
|
899
1063
|
'INBOX': 'inbox',
|
|
@@ -912,13 +1076,20 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
912
1076
|
};
|
|
913
1077
|
return labelMap[labelId] || undefined;
|
|
914
1078
|
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Formats a date for Gmail search query (YYYY/MM/DD)
|
|
1081
|
+
*/
|
|
915
1082
|
formatDateForGmail(date) {
|
|
916
1083
|
const year = date.getFullYear();
|
|
917
1084
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
918
1085
|
const day = date.getDate().toString().padStart(2, '0');
|
|
919
1086
|
return `${year}/${month}/${day}`;
|
|
920
1087
|
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Recursively extracts attachment information from message parts
|
|
1090
|
+
*/
|
|
921
1091
|
extractAttachments(part, attachments) {
|
|
1092
|
+
// Check if this part is an attachment
|
|
922
1093
|
if (part.filename && part.body?.attachmentId) {
|
|
923
1094
|
attachments.push({
|
|
924
1095
|
ID: part.body.attachmentId,
|
|
@@ -930,12 +1101,16 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
930
1101
|
ContentID: part.headers?.find(h => h.name?.toLowerCase() === 'content-id')?.value?.replace(/[<>]/g, '') || undefined
|
|
931
1102
|
});
|
|
932
1103
|
}
|
|
1104
|
+
// Recursively process nested parts
|
|
933
1105
|
if (part.parts) {
|
|
934
1106
|
for (const nestedPart of part.parts) {
|
|
935
1107
|
this.extractAttachments(nestedPart, attachments);
|
|
936
1108
|
}
|
|
937
1109
|
}
|
|
938
1110
|
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Finds attachment info (filename, content type) by attachment ID
|
|
1113
|
+
*/
|
|
939
1114
|
findAttachmentInfo(part, attachmentId) {
|
|
940
1115
|
if (part.body?.attachmentId === attachmentId) {
|
|
941
1116
|
return {
|
|
@@ -953,11 +1128,8 @@ let GmailProvider = class GmailProvider extends communication_types_1.BaseCommun
|
|
|
953
1128
|
return null;
|
|
954
1129
|
}
|
|
955
1130
|
};
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
(0, global_1.RegisterClass)(communication_types_1.BaseCommunicationProvider, 'Gmail')
|
|
1131
|
+
GmailProvider = __decorate([
|
|
1132
|
+
RegisterClass(BaseCommunicationProvider, 'Gmail')
|
|
959
1133
|
], GmailProvider);
|
|
960
|
-
|
|
961
|
-
}
|
|
962
|
-
exports.LoadProvider = LoadProvider;
|
|
1134
|
+
export { GmailProvider };
|
|
963
1135
|
//# sourceMappingURL=GmailProvider.js.map
|