@elizaos/plugin-google 2.0.0-beta.1 → 2.0.3-beta.3
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/LICENSE +21 -0
- package/README.md +124 -17
- package/package.json +22 -4
- package/registry-entry.json +82 -0
- package/dist/auth.d.ts +0 -9
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -45
- package/dist/auth.js.map +0 -1
- package/dist/calendar.d.ts +0 -26
- package/dist/calendar.d.ts.map +0 -1
- package/dist/calendar.js +0 -237
- package/dist/calendar.js.map +0 -1
- package/dist/client-factory.d.ts +0 -17
- package/dist/client-factory.d.ts.map +0 -1
- package/dist/client-factory.js +0 -66
- package/dist/client-factory.js.map +0 -1
- package/dist/connector-account-provider.d.ts +0 -23
- package/dist/connector-account-provider.d.ts.map +0 -1
- package/dist/connector-account-provider.js +0 -348
- package/dist/connector-account-provider.js.map +0 -1
- package/dist/connector-credential-refs.d.ts +0 -43
- package/dist/connector-credential-refs.d.ts.map +0 -1
- package/dist/connector-credential-refs.js +0 -252
- package/dist/connector-credential-refs.js.map +0 -1
- package/dist/credential-resolver.d.ts +0 -45
- package/dist/credential-resolver.d.ts.map +0 -1
- package/dist/credential-resolver.js +0 -525
- package/dist/credential-resolver.js.map +0 -1
- package/dist/drive.d.ts +0 -41
- package/dist/drive.d.ts.map +0 -1
- package/dist/drive.js +0 -207
- package/dist/drive.js.map +0 -1
- package/dist/gmail.d.ts +0 -89
- package/dist/gmail.d.ts.map +0 -1
- package/dist/gmail.js +0 -765
- package/dist/gmail.js.map +0 -1
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -46
- package/dist/index.js.map +0 -1
- package/dist/meet.d.ts +0 -61
- package/dist/meet.d.ts.map +0 -1
- package/dist/meet.js +0 -329
- package/dist/meet.js.map +0 -1
- package/dist/scopes.d.ts +0 -59
- package/dist/scopes.d.ts.map +0 -1
- package/dist/scopes.js +0 -142
- package/dist/scopes.js.map +0 -1
- package/dist/service.d.ts +0 -165
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -170
- package/dist/service.js.map +0 -1
- package/dist/types.d.ts +0 -492
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -9
- package/dist/types.js.map +0 -1
package/dist/gmail.js
DELETED
|
@@ -1,765 +0,0 @@
|
|
|
1
|
-
const MESSAGE_METADATA_HEADERS = ["Subject", "From", "To", "Date"];
|
|
2
|
-
const GMAIL_METADATA_HEADERS = [
|
|
3
|
-
"Subject",
|
|
4
|
-
"From",
|
|
5
|
-
"To",
|
|
6
|
-
"Cc",
|
|
7
|
-
"Date",
|
|
8
|
-
"Reply-To",
|
|
9
|
-
"Message-Id",
|
|
10
|
-
"References",
|
|
11
|
-
"List-Unsubscribe",
|
|
12
|
-
"List-Unsubscribe-Post",
|
|
13
|
-
"List-Id",
|
|
14
|
-
"Precedence",
|
|
15
|
-
"Auto-Submitted",
|
|
16
|
-
];
|
|
17
|
-
const SUBSCRIPTION_SCAN_QUERY_DEFAULT = "(category:promotions OR category:updates OR list:* OR unsubscribe) newer_than:180d";
|
|
18
|
-
const GMAIL_LIST_PAGE_SIZE = 500;
|
|
19
|
-
const GMAIL_METADATA_CONCURRENCY = 25;
|
|
20
|
-
const MAX_GMAIL_RESULTS = 1000;
|
|
21
|
-
export class GoogleGmailClient {
|
|
22
|
-
clientFactory;
|
|
23
|
-
constructor(clientFactory) {
|
|
24
|
-
this.clientFactory = clientFactory;
|
|
25
|
-
}
|
|
26
|
-
async searchMessages(params) {
|
|
27
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.searchMessages");
|
|
28
|
-
const response = await gmail.users.messages.list({
|
|
29
|
-
userId: "me",
|
|
30
|
-
q: params.query,
|
|
31
|
-
maxResults: params.limit ?? 10,
|
|
32
|
-
});
|
|
33
|
-
const messages = response.data.messages ?? [];
|
|
34
|
-
return Promise.all(messages
|
|
35
|
-
.filter((message) => message.id)
|
|
36
|
-
.map((message) => this.getMessageWithClient(gmail, {
|
|
37
|
-
accountId: params.accountId,
|
|
38
|
-
messageId: message.id,
|
|
39
|
-
includeBody: false,
|
|
40
|
-
})));
|
|
41
|
-
}
|
|
42
|
-
async getMessage(params) {
|
|
43
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.getMessage");
|
|
44
|
-
return this.getMessageWithClient(gmail, params);
|
|
45
|
-
}
|
|
46
|
-
async sendEmail(params) {
|
|
47
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.send"], "gmail.sendEmail");
|
|
48
|
-
const raw = encodeMessage(params);
|
|
49
|
-
const response = await gmail.users.messages.send({
|
|
50
|
-
userId: "me",
|
|
51
|
-
requestBody: {
|
|
52
|
-
raw,
|
|
53
|
-
threadId: params.threadId,
|
|
54
|
-
},
|
|
55
|
-
});
|
|
56
|
-
return {
|
|
57
|
-
id: response.data.id ?? "",
|
|
58
|
-
threadId: response.data.threadId ?? undefined,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
async listGmailTriageMessages(params) {
|
|
62
|
-
return this.searchGmailMessages({
|
|
63
|
-
accountId: params.accountId,
|
|
64
|
-
selfEmail: params.selfEmail,
|
|
65
|
-
maxResults: params.maxResults,
|
|
66
|
-
query: "in:inbox",
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
async searchGmailMessages(params) {
|
|
70
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.searchGmailMessages");
|
|
71
|
-
const maxResults = normalizedLimit(params.maxResults, 20, MAX_GMAIL_RESULTS);
|
|
72
|
-
const messages = [];
|
|
73
|
-
let pageToken;
|
|
74
|
-
while (messages.length < maxResults) {
|
|
75
|
-
const response = await gmail.users.messages.list({
|
|
76
|
-
userId: "me",
|
|
77
|
-
q: params.query,
|
|
78
|
-
includeSpamTrash: params.includeSpamTrash === true,
|
|
79
|
-
maxResults: Math.min(GMAIL_LIST_PAGE_SIZE, maxResults - messages.length),
|
|
80
|
-
pageToken,
|
|
81
|
-
});
|
|
82
|
-
const pageMessages = await mapWithConcurrency(response.data.messages ?? [], GMAIL_METADATA_CONCURRENCY, async (messageRef) => {
|
|
83
|
-
const messageId = messageRef.id?.trim();
|
|
84
|
-
if (!messageId) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
return this.getRichMessageWithClient(gmail, {
|
|
88
|
-
accountId: params.accountId,
|
|
89
|
-
messageId,
|
|
90
|
-
selfEmail: params.selfEmail,
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
for (const message of pageMessages) {
|
|
94
|
-
if (message) {
|
|
95
|
-
messages.push(message);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
const nextPageToken = response.data.nextPageToken?.trim();
|
|
99
|
-
if (!nextPageToken || nextPageToken === pageToken) {
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
pageToken = nextPageToken;
|
|
103
|
-
}
|
|
104
|
-
return sortGmailMessages(messages).slice(0, maxResults);
|
|
105
|
-
}
|
|
106
|
-
async getGmailMessage(params) {
|
|
107
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.getGmailMessage");
|
|
108
|
-
return this.getRichMessageWithClient(gmail, params);
|
|
109
|
-
}
|
|
110
|
-
async getGmailMessageDetail(params) {
|
|
111
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.getGmailMessageDetail");
|
|
112
|
-
const response = await gmail.users.messages.get({
|
|
113
|
-
userId: "me",
|
|
114
|
-
id: params.messageId,
|
|
115
|
-
format: "full",
|
|
116
|
-
});
|
|
117
|
-
const message = mapRichMessage(response.data, params.selfEmail ?? null);
|
|
118
|
-
if (!message) {
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
return {
|
|
122
|
-
message,
|
|
123
|
-
bodyText: extractGoogleGmailBody(response.data.payload).trim() || message.snippet,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
async getGmailThread(params) {
|
|
127
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.getGmailThread");
|
|
128
|
-
const response = await gmail.users.threads.get({
|
|
129
|
-
userId: "me",
|
|
130
|
-
id: params.threadId,
|
|
131
|
-
format: "metadata",
|
|
132
|
-
metadataHeaders: [...GMAIL_METADATA_HEADERS],
|
|
133
|
-
});
|
|
134
|
-
return (response.data.messages ?? [])
|
|
135
|
-
.map((message) => mapRichMessage(message, params.selfEmail ?? null))
|
|
136
|
-
.filter((message) => message !== null)
|
|
137
|
-
.sort((left, right) => Date.parse(left.receivedAt) - Date.parse(right.receivedAt));
|
|
138
|
-
}
|
|
139
|
-
async listGmailUnrespondedThreads(params) {
|
|
140
|
-
const olderThanDays = normalizedLimit(params.olderThanDays, 3, 3650);
|
|
141
|
-
const maxResults = normalizedLimit(params.maxResults, 20, 50);
|
|
142
|
-
const selfEmail = params.selfEmail?.trim().toLowerCase() || null;
|
|
143
|
-
const sentCandidates = await this.searchGmailMessages({
|
|
144
|
-
accountId: params.accountId,
|
|
145
|
-
selfEmail,
|
|
146
|
-
maxResults: Math.min(Math.max(maxResults * 5, maxResults), 250),
|
|
147
|
-
query: `in:sent older_than:${olderThanDays}d`,
|
|
148
|
-
});
|
|
149
|
-
const seenThreads = new Set();
|
|
150
|
-
const threads = [];
|
|
151
|
-
const now = params.now ?? new Date();
|
|
152
|
-
for (const sentMessage of sentCandidates) {
|
|
153
|
-
if (seenThreads.has(sentMessage.threadId)) {
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
seenThreads.add(sentMessage.threadId);
|
|
157
|
-
const threadMessages = await this.getGmailThread({
|
|
158
|
-
accountId: params.accountId,
|
|
159
|
-
selfEmail,
|
|
160
|
-
threadId: sentMessage.threadId,
|
|
161
|
-
});
|
|
162
|
-
const humanMessages = threadMessages.filter((message) => !isAutomatedMessage(message));
|
|
163
|
-
const lastOutbound = [...humanMessages]
|
|
164
|
-
.reverse()
|
|
165
|
-
.find((message) => isMessageFromSelf(message, selfEmail));
|
|
166
|
-
if (!lastOutbound) {
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
const lastOutboundAtMs = Date.parse(lastOutbound.receivedAt);
|
|
170
|
-
if (!Number.isFinite(lastOutboundAtMs)) {
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
const hasLaterInbound = humanMessages.some((message) => !isMessageFromSelf(message, selfEmail) &&
|
|
174
|
-
Date.parse(message.receivedAt) > lastOutboundAtMs);
|
|
175
|
-
if (hasLaterInbound) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
const ageMs = now.getTime() - lastOutboundAtMs;
|
|
179
|
-
if (ageMs < olderThanDays * 24 * 60 * 60 * 1000) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
const lastInbound = [...humanMessages]
|
|
183
|
-
.reverse()
|
|
184
|
-
.find((message) => !isMessageFromSelf(message, selfEmail));
|
|
185
|
-
threads.push({
|
|
186
|
-
threadId: lastOutbound.threadId,
|
|
187
|
-
externalMessageId: lastOutbound.externalId,
|
|
188
|
-
subject: lastOutbound.subject,
|
|
189
|
-
to: lastOutbound.to,
|
|
190
|
-
cc: lastOutbound.cc,
|
|
191
|
-
lastOutboundAt: lastOutbound.receivedAt,
|
|
192
|
-
lastInboundAt: lastInbound?.receivedAt ?? null,
|
|
193
|
-
daysWaiting: Math.max(0, Math.floor(ageMs / (24 * 60 * 60 * 1000))),
|
|
194
|
-
snippet: lastOutbound.snippet,
|
|
195
|
-
labels: lastOutbound.labels,
|
|
196
|
-
htmlLink: lastOutbound.htmlLink,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
return threads.sort((left, right) => right.daysWaiting - left.daysWaiting).slice(0, maxResults);
|
|
200
|
-
}
|
|
201
|
-
async modifyGmailMessages(params) {
|
|
202
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.manage"], "gmail.modifyMessages");
|
|
203
|
-
const ids = params.messageIds.map((messageId) => messageId.trim()).filter(Boolean);
|
|
204
|
-
if (ids.length === 0) {
|
|
205
|
-
throw new Error("Gmail operation requires message ids");
|
|
206
|
-
}
|
|
207
|
-
const labelIds = requireLabelIdsForOperation(params.operation, params.labelIds);
|
|
208
|
-
if (params.operation === "trash") {
|
|
209
|
-
await Promise.all(ids.map((id) => gmail.users.messages.trash({ userId: "me", id }).then(() => undefined)));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
if (params.operation === "delete") {
|
|
213
|
-
await gmail.users.messages.batchDelete({
|
|
214
|
-
userId: "me",
|
|
215
|
-
requestBody: { ids },
|
|
216
|
-
});
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
const labelPatch = labelsForOperation(params.operation, labelIds);
|
|
220
|
-
await gmail.users.messages.batchModify({
|
|
221
|
-
userId: "me",
|
|
222
|
-
requestBody: {
|
|
223
|
-
ids,
|
|
224
|
-
addLabelIds: labelPatch.addLabelIds,
|
|
225
|
-
removeLabelIds: labelPatch.removeLabelIds,
|
|
226
|
-
},
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
async sendGmailReply(params) {
|
|
230
|
-
const raw = encodeRawGmailMessage([
|
|
231
|
-
`To: ${params.to.join(", ")}`,
|
|
232
|
-
...(params.cc && params.cc.length > 0 ? [`Cc: ${params.cc.join(", ")}`] : []),
|
|
233
|
-
`Subject: ${normalizeReplySubject(params.subject)}`,
|
|
234
|
-
"MIME-Version: 1.0",
|
|
235
|
-
"Content-Type: text/plain; charset=UTF-8",
|
|
236
|
-
...(params.inReplyTo ? [`In-Reply-To: ${params.inReplyTo}`] : []),
|
|
237
|
-
...(params.references ? [`References: ${params.references}`] : []),
|
|
238
|
-
"",
|
|
239
|
-
params.bodyText.replace(/\r?\n/g, "\r\n"),
|
|
240
|
-
]);
|
|
241
|
-
return this.sendRawGmailMessage(params, raw, "gmail.sendGmailReply");
|
|
242
|
-
}
|
|
243
|
-
async sendGmailMessage(params) {
|
|
244
|
-
const raw = encodeRawGmailMessage([
|
|
245
|
-
`To: ${params.to.join(", ")}`,
|
|
246
|
-
...(params.cc && params.cc.length > 0 ? [`Cc: ${params.cc.join(", ")}`] : []),
|
|
247
|
-
...(params.bcc && params.bcc.length > 0 ? [`Bcc: ${params.bcc.join(", ")}`] : []),
|
|
248
|
-
`Subject: ${params.subject.trim() || "(no subject)"}`,
|
|
249
|
-
"MIME-Version: 1.0",
|
|
250
|
-
"Content-Type: text/plain; charset=UTF-8",
|
|
251
|
-
"",
|
|
252
|
-
params.bodyText.replace(/\r?\n/g, "\r\n"),
|
|
253
|
-
]);
|
|
254
|
-
return this.sendRawGmailMessage(params, raw, "gmail.sendGmailMessage");
|
|
255
|
-
}
|
|
256
|
-
async getGmailSubscriptionHeaders(params) {
|
|
257
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.read"], "gmail.getSubscriptionHeaders");
|
|
258
|
-
const query = params.query?.trim() || SUBSCRIPTION_SCAN_QUERY_DEFAULT;
|
|
259
|
-
const maxMessages = normalizedLimit(params.maxMessages, 200, MAX_GMAIL_RESULTS);
|
|
260
|
-
const results = [];
|
|
261
|
-
let pageToken;
|
|
262
|
-
while (results.length < maxMessages) {
|
|
263
|
-
const response = await gmail.users.messages.list({
|
|
264
|
-
userId: "me",
|
|
265
|
-
q: query,
|
|
266
|
-
includeSpamTrash: false,
|
|
267
|
-
maxResults: Math.min(100, maxMessages - results.length),
|
|
268
|
-
pageToken,
|
|
269
|
-
});
|
|
270
|
-
const batch = await mapWithConcurrency(response.data.messages ?? [], GMAIL_METADATA_CONCURRENCY, async (messageRef) => {
|
|
271
|
-
const messageId = messageRef.id?.trim();
|
|
272
|
-
if (!messageId) {
|
|
273
|
-
return null;
|
|
274
|
-
}
|
|
275
|
-
const rich = await this.getRichMessageWithClient(gmail, {
|
|
276
|
-
accountId: params.accountId,
|
|
277
|
-
messageId,
|
|
278
|
-
});
|
|
279
|
-
return rich ? mapSubscriptionHeaders(rich) : null;
|
|
280
|
-
});
|
|
281
|
-
for (const headers of batch) {
|
|
282
|
-
if (headers) {
|
|
283
|
-
results.push(headers);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const nextPageToken = response.data.nextPageToken?.trim();
|
|
287
|
-
if (!nextPageToken || nextPageToken === pageToken) {
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
pageToken = nextPageToken;
|
|
291
|
-
}
|
|
292
|
-
return results;
|
|
293
|
-
}
|
|
294
|
-
async createGmailFilterForSender(params) {
|
|
295
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.manage"], "gmail.createFilterForSender");
|
|
296
|
-
const response = await gmail.users.settings.filters.create({
|
|
297
|
-
userId: "me",
|
|
298
|
-
requestBody: {
|
|
299
|
-
criteria: { from: params.fromAddress },
|
|
300
|
-
action: params.trash
|
|
301
|
-
? { removeLabelIds: ["INBOX"], addLabelIds: ["TRASH"] }
|
|
302
|
-
: { addLabelIds: ["TRASH"], removeLabelIds: ["INBOX", "UNREAD"] },
|
|
303
|
-
},
|
|
304
|
-
});
|
|
305
|
-
return {
|
|
306
|
-
filterId: response.data.id ?? null,
|
|
307
|
-
trashed: true,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
async trashGmailThread(params) {
|
|
311
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.manage"], "gmail.trashThread");
|
|
312
|
-
await gmail.users.threads.trash({
|
|
313
|
-
userId: "me",
|
|
314
|
-
id: params.threadId,
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
async modifyGmailMessageLabels(params) {
|
|
318
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.manage"], "gmail.modifyMessageLabels");
|
|
319
|
-
await gmail.users.messages.modify({
|
|
320
|
-
userId: "me",
|
|
321
|
-
id: params.messageId,
|
|
322
|
-
requestBody: {
|
|
323
|
-
addLabelIds: params.addLabelIds ?? [],
|
|
324
|
-
removeLabelIds: params.removeLabelIds ?? [],
|
|
325
|
-
},
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
async sendMailtoUnsubscribeEmail(params) {
|
|
329
|
-
await this.sendGmailMessage({
|
|
330
|
-
accountId: params.accountId,
|
|
331
|
-
to: [params.mailto.recipient],
|
|
332
|
-
subject: params.mailto.subject ?? "unsubscribe",
|
|
333
|
-
bodyText: params.mailto.body ?? "unsubscribe",
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
async getMessageWithClient(gmail, params) {
|
|
337
|
-
const response = await gmail.users.messages.get({
|
|
338
|
-
userId: "me",
|
|
339
|
-
id: params.messageId,
|
|
340
|
-
format: params.includeBody ? "full" : "metadata",
|
|
341
|
-
metadataHeaders: MESSAGE_METADATA_HEADERS,
|
|
342
|
-
});
|
|
343
|
-
return mapMessage(response.data, Boolean(params.includeBody));
|
|
344
|
-
}
|
|
345
|
-
async getRichMessageWithClient(gmail, params) {
|
|
346
|
-
try {
|
|
347
|
-
const response = await gmail.users.messages.get({
|
|
348
|
-
userId: "me",
|
|
349
|
-
id: params.messageId,
|
|
350
|
-
format: "metadata",
|
|
351
|
-
metadataHeaders: [...GMAIL_METADATA_HEADERS],
|
|
352
|
-
});
|
|
353
|
-
return mapRichMessage(response.data, params.selfEmail ?? null);
|
|
354
|
-
}
|
|
355
|
-
catch (error) {
|
|
356
|
-
if (googleErrorStatus(error) === 404) {
|
|
357
|
-
return null;
|
|
358
|
-
}
|
|
359
|
-
throw error;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
async sendRawGmailMessage(params, raw, reason) {
|
|
363
|
-
const gmail = await this.clientFactory.gmail(params, ["gmail.send"], reason);
|
|
364
|
-
const response = await gmail.users.messages.send({
|
|
365
|
-
userId: "me",
|
|
366
|
-
requestBody: { raw },
|
|
367
|
-
});
|
|
368
|
-
return {
|
|
369
|
-
messageId: response.data.id ?? null,
|
|
370
|
-
threadId: response.data.threadId ?? null,
|
|
371
|
-
labelIds: response.data.labelIds ?? [],
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
function mapMessage(message, includeBody) {
|
|
376
|
-
const headers = message.payload?.headers ?? [];
|
|
377
|
-
const dateHeader = headerValue(headers, "Date");
|
|
378
|
-
const body = includeBody ? collectMessageBody(message.payload) : {};
|
|
379
|
-
const headerMap = Object.fromEntries(headers
|
|
380
|
-
.map((header) => [header.name?.trim() ?? "", header.value?.trim() ?? ""])
|
|
381
|
-
.filter(([name, value]) => name.length > 0 && value.length > 0));
|
|
382
|
-
return {
|
|
383
|
-
id: message.id ?? "",
|
|
384
|
-
threadId: message.threadId ?? undefined,
|
|
385
|
-
subject: headerValue(headers, "Subject"),
|
|
386
|
-
from: parseEmailAddresses(headerValue(headers, "From"))[0],
|
|
387
|
-
replyTo: parseEmailAddresses(headerValue(headers, "Reply-To"))[0],
|
|
388
|
-
to: parseEmailAddresses(headerValue(headers, "To")),
|
|
389
|
-
cc: parseEmailAddresses(headerValue(headers, "Cc")),
|
|
390
|
-
snippet: message.snippet ?? undefined,
|
|
391
|
-
receivedAt: dateHeader ? new Date(dateHeader).toISOString() : undefined,
|
|
392
|
-
labelIds: message.labelIds ?? undefined,
|
|
393
|
-
headers: headerMap,
|
|
394
|
-
...body,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
function mapRichMessage(message, selfEmail) {
|
|
398
|
-
const externalId = message.id?.trim();
|
|
399
|
-
const threadId = message.threadId?.trim();
|
|
400
|
-
if (!externalId || !threadId) {
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
const headers = message.payload?.headers ?? [];
|
|
404
|
-
const subject = decodeHtmlEntities(headerValue(headers, "Subject") || "") || "(no subject)";
|
|
405
|
-
const from = parseMailbox(headerValue(headers, "From") || "Unknown sender");
|
|
406
|
-
const replyTo = headerValue(headers, "Reply-To");
|
|
407
|
-
const replyToMailbox = replyTo ? parseMailbox(replyTo) : null;
|
|
408
|
-
const to = parseEmailAddresses(headerValue(headers, "To")).map(formatAddressValue);
|
|
409
|
-
const cc = parseEmailAddresses(headerValue(headers, "Cc")).map(formatAddressValue);
|
|
410
|
-
const labels = (message.labelIds ?? []).map((label) => label.trim()).filter(Boolean);
|
|
411
|
-
const receivedAt = internalDateToIso(message.internalDate);
|
|
412
|
-
const precedence = headerValue(headers, "Precedence");
|
|
413
|
-
const listId = headerValue(headers, "List-Id");
|
|
414
|
-
const autoSubmitted = headerValue(headers, "Auto-Submitted");
|
|
415
|
-
const triage = classifyReplyNeed({
|
|
416
|
-
labels,
|
|
417
|
-
fromEmail: from.email,
|
|
418
|
-
to,
|
|
419
|
-
cc,
|
|
420
|
-
selfEmail,
|
|
421
|
-
precedence,
|
|
422
|
-
listId,
|
|
423
|
-
autoSubmitted,
|
|
424
|
-
});
|
|
425
|
-
return {
|
|
426
|
-
externalId,
|
|
427
|
-
threadId,
|
|
428
|
-
subject,
|
|
429
|
-
from: from.name || from.email || "Unknown sender",
|
|
430
|
-
fromEmail: from.email ? from.email.toLowerCase() : null,
|
|
431
|
-
replyTo: replyToMailbox?.email ?? replyToMailbox?.name ?? null,
|
|
432
|
-
to,
|
|
433
|
-
cc,
|
|
434
|
-
snippet: normalizeSnippet(message.snippet),
|
|
435
|
-
receivedAt,
|
|
436
|
-
isUnread: labels.includes("UNREAD"),
|
|
437
|
-
isImportant: triage.isImportant,
|
|
438
|
-
likelyReplyNeeded: triage.likelyReplyNeeded,
|
|
439
|
-
triageScore: triage.triageScore,
|
|
440
|
-
triageReason: triage.triageReason,
|
|
441
|
-
labels,
|
|
442
|
-
htmlLink: deriveHtmlLink(threadId, selfEmail),
|
|
443
|
-
metadata: {
|
|
444
|
-
historyId: message.historyId?.trim() || null,
|
|
445
|
-
sizeEstimate: typeof message.sizeEstimate === "number" ? message.sizeEstimate : null,
|
|
446
|
-
dateHeader: headerValue(headers, "Date") || null,
|
|
447
|
-
messageIdHeader: headerValue(headers, "Message-Id") || null,
|
|
448
|
-
referencesHeader: headerValue(headers, "References") || null,
|
|
449
|
-
listUnsubscribe: headerValue(headers, "List-Unsubscribe") || null,
|
|
450
|
-
listUnsubscribePost: headerValue(headers, "List-Unsubscribe-Post") || null,
|
|
451
|
-
listId: listId || null,
|
|
452
|
-
precedence: precedence || null,
|
|
453
|
-
autoSubmitted: autoSubmitted || null,
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
function headerValue(headers, name) {
|
|
458
|
-
return (headers.find((header) => header.name?.toLowerCase() === name.toLowerCase())?.value ?? undefined);
|
|
459
|
-
}
|
|
460
|
-
function parseEmailAddresses(value) {
|
|
461
|
-
if (!value) {
|
|
462
|
-
return [];
|
|
463
|
-
}
|
|
464
|
-
return value
|
|
465
|
-
.split(",")
|
|
466
|
-
.map((part) => part.trim())
|
|
467
|
-
.filter(Boolean)
|
|
468
|
-
.map((part) => {
|
|
469
|
-
const match = part.match(/^(?:"?([^"<]*)"?\s*)?<([^>]+)>$/);
|
|
470
|
-
if (!match) {
|
|
471
|
-
return { email: part };
|
|
472
|
-
}
|
|
473
|
-
return {
|
|
474
|
-
name: match[1]?.trim() || undefined,
|
|
475
|
-
email: match[2].trim(),
|
|
476
|
-
};
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
function collectMessageBody(part) {
|
|
480
|
-
if (!part) {
|
|
481
|
-
return {};
|
|
482
|
-
}
|
|
483
|
-
const body = {};
|
|
484
|
-
collectMessagePart(part, body);
|
|
485
|
-
return body;
|
|
486
|
-
}
|
|
487
|
-
function collectMessagePart(part, body) {
|
|
488
|
-
const data = part.body?.data ? decodeBase64Url(part.body.data) : undefined;
|
|
489
|
-
if (data && part.mimeType === "text/plain" && !body.bodyText) {
|
|
490
|
-
body.bodyText = data;
|
|
491
|
-
}
|
|
492
|
-
if (data && part.mimeType === "text/html" && !body.bodyHtml) {
|
|
493
|
-
body.bodyHtml = data;
|
|
494
|
-
}
|
|
495
|
-
for (const child of part.parts ?? []) {
|
|
496
|
-
collectMessagePart(child, body);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
function encodeMessage(input) {
|
|
500
|
-
const headers = [
|
|
501
|
-
`To: ${formatEmailAddresses(input.to)}`,
|
|
502
|
-
input.cc?.length ? `Cc: ${formatEmailAddresses(input.cc)}` : undefined,
|
|
503
|
-
input.bcc?.length ? `Bcc: ${formatEmailAddresses(input.bcc)}` : undefined,
|
|
504
|
-
`Subject: ${input.subject}`,
|
|
505
|
-
"MIME-Version: 1.0",
|
|
506
|
-
].filter(Boolean);
|
|
507
|
-
const contentType = input.html ? "text/html; charset=utf-8" : "text/plain; charset=utf-8";
|
|
508
|
-
const body = input.html ?? input.text ?? "";
|
|
509
|
-
const message = [...headers, `Content-Type: ${contentType}`, "", body].join("\r\n");
|
|
510
|
-
return Buffer.from(message).toString("base64url");
|
|
511
|
-
}
|
|
512
|
-
function formatEmailAddresses(addresses) {
|
|
513
|
-
return addresses
|
|
514
|
-
.map((address) => (address.name ? `"${address.name}" <${address.email}>` : address.email))
|
|
515
|
-
.join(", ");
|
|
516
|
-
}
|
|
517
|
-
function decodeBase64Url(value) {
|
|
518
|
-
return Buffer.from(value, "base64url").toString("utf8");
|
|
519
|
-
}
|
|
520
|
-
function normalizedLimit(value, fallback, max) {
|
|
521
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
522
|
-
return fallback;
|
|
523
|
-
}
|
|
524
|
-
return Math.min(Math.trunc(value), max);
|
|
525
|
-
}
|
|
526
|
-
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
527
|
-
if (items.length === 0) {
|
|
528
|
-
return [];
|
|
529
|
-
}
|
|
530
|
-
const results = new Array(items.length);
|
|
531
|
-
let cursor = 0;
|
|
532
|
-
const workerCount = Math.min(Math.max(1, concurrency), items.length);
|
|
533
|
-
const workers = Array.from({ length: workerCount }, async () => {
|
|
534
|
-
while (true) {
|
|
535
|
-
const index = cursor;
|
|
536
|
-
cursor += 1;
|
|
537
|
-
if (index >= items.length) {
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
results[index] = await mapper(items[index]);
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
await Promise.all(workers);
|
|
544
|
-
return results;
|
|
545
|
-
}
|
|
546
|
-
function sortGmailMessages(messages) {
|
|
547
|
-
return [...messages].sort((left, right) => {
|
|
548
|
-
if (left.isImportant !== right.isImportant) {
|
|
549
|
-
return right.isImportant ? 1 : -1;
|
|
550
|
-
}
|
|
551
|
-
if (left.likelyReplyNeeded !== right.likelyReplyNeeded) {
|
|
552
|
-
return right.likelyReplyNeeded ? 1 : -1;
|
|
553
|
-
}
|
|
554
|
-
if (left.isUnread !== right.isUnread) {
|
|
555
|
-
return right.isUnread ? 1 : -1;
|
|
556
|
-
}
|
|
557
|
-
return Date.parse(right.receivedAt) - Date.parse(left.receivedAt);
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
function parseMailbox(value) {
|
|
561
|
-
const trimmed = value.trim();
|
|
562
|
-
const match = trimmed.match(/^(.*?)(?:<([^>]+)>)$/);
|
|
563
|
-
if (match) {
|
|
564
|
-
const name = (match[1] ?? "").trim().replace(/^"|"$/g, "");
|
|
565
|
-
const email = (match[2] ?? "").trim();
|
|
566
|
-
return { name: name || undefined, email };
|
|
567
|
-
}
|
|
568
|
-
return { email: trimmed };
|
|
569
|
-
}
|
|
570
|
-
function formatAddressValue(address) {
|
|
571
|
-
return address.email || address.name || "";
|
|
572
|
-
}
|
|
573
|
-
function normalizeSnippet(value) {
|
|
574
|
-
return decodeHtmlEntities(value ?? "")
|
|
575
|
-
.replace(/\s+/g, " ")
|
|
576
|
-
.trim();
|
|
577
|
-
}
|
|
578
|
-
function decodeHtmlEntities(value) {
|
|
579
|
-
return value
|
|
580
|
-
.replace(/ /gi, " ")
|
|
581
|
-
.replace(/</gi, "<")
|
|
582
|
-
.replace(/>/gi, ">")
|
|
583
|
-
.replace(/"/gi, '"')
|
|
584
|
-
.replace(/'/gi, "'")
|
|
585
|
-
.replace(/&/gi, "&");
|
|
586
|
-
}
|
|
587
|
-
function internalDateToIso(value) {
|
|
588
|
-
const ms = value ? Number(value) : Number.NaN;
|
|
589
|
-
return Number.isFinite(ms) ? new Date(ms).toISOString() : new Date().toISOString();
|
|
590
|
-
}
|
|
591
|
-
function deriveHtmlLink(threadId, accountEmail) {
|
|
592
|
-
const accountSegment = accountEmail && accountEmail.trim().length > 0
|
|
593
|
-
? encodeURIComponent(accountEmail.trim().toLowerCase())
|
|
594
|
-
: "0";
|
|
595
|
-
return `https://mail.google.com/mail/u/${accountSegment}/#all/${encodeURIComponent(threadId)}`;
|
|
596
|
-
}
|
|
597
|
-
function classifyReplyNeed(args) {
|
|
598
|
-
const labels = new Set(args.labels.map((label) => label.trim().toUpperCase()));
|
|
599
|
-
const isUnread = labels.has("UNREAD");
|
|
600
|
-
const explicitlyImportant = labels.has("IMPORTANT");
|
|
601
|
-
const selfEmail = args.selfEmail?.trim().toLowerCase() || null;
|
|
602
|
-
const fromEmail = args.fromEmail?.trim().toLowerCase() || null;
|
|
603
|
-
const directRecipients = [...args.to, ...args.cc].map((entry) => entry.trim().toLowerCase());
|
|
604
|
-
const directlyAddressed = selfEmail ? directRecipients.includes(selfEmail) : false;
|
|
605
|
-
const fromSelf = Boolean(selfEmail && fromEmail && selfEmail === fromEmail);
|
|
606
|
-
const precedence = args.precedence?.trim().toLowerCase();
|
|
607
|
-
const autoSubmitted = args.autoSubmitted?.trim().toLowerCase();
|
|
608
|
-
const automated = Boolean(args.listId) ||
|
|
609
|
-
precedence === "bulk" ||
|
|
610
|
-
precedence === "list" ||
|
|
611
|
-
precedence === "junk" ||
|
|
612
|
-
precedence === "auto-reply" ||
|
|
613
|
-
(autoSubmitted !== undefined && autoSubmitted !== "no");
|
|
614
|
-
const likelyReplyNeeded = !automated && !fromSelf && isUnread && directlyAddressed;
|
|
615
|
-
const isImportant = explicitlyImportant || likelyReplyNeeded;
|
|
616
|
-
const triageSignals = [
|
|
617
|
-
explicitlyImportant ? "gmail-important-label" : null,
|
|
618
|
-
likelyReplyNeeded ? "direct-unread-reply-needed" : null,
|
|
619
|
-
isUnread ? "unread" : null,
|
|
620
|
-
automated ? "automated-header" : null,
|
|
621
|
-
fromSelf ? "sent-by-self" : null,
|
|
622
|
-
].filter((signal) => Boolean(signal));
|
|
623
|
-
return {
|
|
624
|
-
likelyReplyNeeded,
|
|
625
|
-
isImportant,
|
|
626
|
-
triageScore: isImportant ? 2 : isUnread ? 1 : 0,
|
|
627
|
-
triageReason: triageSignals.join(", ") || "recent inbox message",
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
function isMessageFromSelf(message, selfEmail) {
|
|
631
|
-
const labels = new Set(message.labels.map((label) => label.toUpperCase()));
|
|
632
|
-
if (labels.has("SENT")) {
|
|
633
|
-
return true;
|
|
634
|
-
}
|
|
635
|
-
const fromEmail = message.fromEmail?.trim().toLowerCase() || null;
|
|
636
|
-
return Boolean(selfEmail && fromEmail && fromEmail === selfEmail);
|
|
637
|
-
}
|
|
638
|
-
function isAutomatedMessage(message) {
|
|
639
|
-
const precedence = typeof message.metadata.precedence === "string"
|
|
640
|
-
? message.metadata.precedence.trim().toLowerCase()
|
|
641
|
-
: "";
|
|
642
|
-
const autoSubmitted = typeof message.metadata.autoSubmitted === "string"
|
|
643
|
-
? message.metadata.autoSubmitted.trim().toLowerCase()
|
|
644
|
-
: "";
|
|
645
|
-
return (Boolean(message.metadata.listId) ||
|
|
646
|
-
precedence === "bulk" ||
|
|
647
|
-
precedence === "list" ||
|
|
648
|
-
precedence === "junk" ||
|
|
649
|
-
precedence === "auto-reply" ||
|
|
650
|
-
(autoSubmitted.length > 0 && autoSubmitted !== "no"));
|
|
651
|
-
}
|
|
652
|
-
function requireLabelIdsForOperation(operation, labelIds) {
|
|
653
|
-
const labels = (labelIds ?? []).map((labelId) => labelId.trim()).filter(Boolean);
|
|
654
|
-
if ((operation === "apply_label" || operation === "remove_label") && labels.length === 0) {
|
|
655
|
-
throw new Error(`${operation} requires at least one labelId`);
|
|
656
|
-
}
|
|
657
|
-
return labels;
|
|
658
|
-
}
|
|
659
|
-
function labelsForOperation(operation, labelIds) {
|
|
660
|
-
const labels = {
|
|
661
|
-
archive: { removeLabelIds: ["INBOX"] },
|
|
662
|
-
trash: {},
|
|
663
|
-
delete: {},
|
|
664
|
-
report_spam: { addLabelIds: ["SPAM"], removeLabelIds: ["INBOX"] },
|
|
665
|
-
mark_read: { removeLabelIds: ["UNREAD"] },
|
|
666
|
-
mark_unread: { addLabelIds: ["UNREAD"] },
|
|
667
|
-
apply_label: { addLabelIds: labelIds },
|
|
668
|
-
remove_label: { removeLabelIds: labelIds },
|
|
669
|
-
};
|
|
670
|
-
return labels[operation];
|
|
671
|
-
}
|
|
672
|
-
function normalizeReplySubject(subject) {
|
|
673
|
-
const trimmed = subject.trim();
|
|
674
|
-
if (trimmed.length === 0) {
|
|
675
|
-
return "Re: your message";
|
|
676
|
-
}
|
|
677
|
-
return /^re:/i.test(trimmed) ? trimmed : `Re: ${trimmed}`;
|
|
678
|
-
}
|
|
679
|
-
function encodeRawGmailMessage(lines) {
|
|
680
|
-
return Buffer.from(lines.join("\r\n"), "utf-8").toString("base64url");
|
|
681
|
-
}
|
|
682
|
-
function extractGoogleGmailBody(payload) {
|
|
683
|
-
const plainText = extractGoogleGmailBodyByMime(payload, "text/plain");
|
|
684
|
-
if (plainText) {
|
|
685
|
-
return plainText;
|
|
686
|
-
}
|
|
687
|
-
const htmlText = extractGoogleGmailBodyByMime(payload, "text/html");
|
|
688
|
-
if (htmlText) {
|
|
689
|
-
return htmlText;
|
|
690
|
-
}
|
|
691
|
-
const directBody = payload?.body?.data;
|
|
692
|
-
if (typeof directBody === "string") {
|
|
693
|
-
const decoded = decodeBase64Url(directBody);
|
|
694
|
-
return payload?.mimeType === "text/html" ? htmlToPlainText(decoded) : decoded.trim();
|
|
695
|
-
}
|
|
696
|
-
return "";
|
|
697
|
-
}
|
|
698
|
-
function extractGoogleGmailBodyByMime(payload, mimeType) {
|
|
699
|
-
if (!payload) {
|
|
700
|
-
return "";
|
|
701
|
-
}
|
|
702
|
-
const directBody = payload.body?.data;
|
|
703
|
-
if (payload.mimeType === mimeType && typeof directBody === "string") {
|
|
704
|
-
const decoded = decodeBase64Url(directBody);
|
|
705
|
-
return mimeType === "text/html" ? htmlToPlainText(decoded) : decoded.trim();
|
|
706
|
-
}
|
|
707
|
-
for (const part of payload.parts ?? []) {
|
|
708
|
-
const nested = extractGoogleGmailBodyByMime(part, mimeType);
|
|
709
|
-
if (nested) {
|
|
710
|
-
return nested;
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
return "";
|
|
714
|
-
}
|
|
715
|
-
function htmlToPlainText(value) {
|
|
716
|
-
return decodeHtmlEntities(value
|
|
717
|
-
.replace(/<style\b[^>]*>[\s\S]*?<\/style\b[^>]*>/gi, " ")
|
|
718
|
-
.replace(/<script\b[^>]*>[\s\S]*?<\/script\b[^>]*>/gi, " ")
|
|
719
|
-
.replace(/<br\s*\/?>/gi, "\n")
|
|
720
|
-
.replace(/<\/(?:p|div|section|article|li|tr|table|h[1-6])>/gi, "\n")
|
|
721
|
-
.replace(/<(?:li)[^>]*>/gi, "- ")
|
|
722
|
-
.replace(/<[^>]+>/g, " "))
|
|
723
|
-
.replace(/\r/g, "")
|
|
724
|
-
.replace(/[ \t]+\n/g, "\n")
|
|
725
|
-
.replace(/\n{3,}/g, "\n\n")
|
|
726
|
-
.replace(/[ \t]{2,}/g, " ")
|
|
727
|
-
.trim();
|
|
728
|
-
}
|
|
729
|
-
function mapSubscriptionHeaders(message) {
|
|
730
|
-
const listUnsubscribe = typeof message.metadata.listUnsubscribe === "string" ? message.metadata.listUnsubscribe : null;
|
|
731
|
-
const listUnsubscribePost = typeof message.metadata.listUnsubscribePost === "string"
|
|
732
|
-
? message.metadata.listUnsubscribePost
|
|
733
|
-
: null;
|
|
734
|
-
const listId = typeof message.metadata.listId === "string" ? message.metadata.listId : null;
|
|
735
|
-
return {
|
|
736
|
-
messageId: message.externalId,
|
|
737
|
-
threadId: message.threadId,
|
|
738
|
-
receivedAt: message.receivedAt,
|
|
739
|
-
subject: message.subject,
|
|
740
|
-
fromDisplay: message.from,
|
|
741
|
-
fromEmail: message.fromEmail,
|
|
742
|
-
listId,
|
|
743
|
-
listUnsubscribe,
|
|
744
|
-
listUnsubscribePost,
|
|
745
|
-
snippet: message.snippet,
|
|
746
|
-
labels: message.labels,
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
function googleErrorStatus(error) {
|
|
750
|
-
if (typeof error !== "object" || error === null) {
|
|
751
|
-
return undefined;
|
|
752
|
-
}
|
|
753
|
-
const candidate = error;
|
|
754
|
-
if (typeof candidate.code === "number") {
|
|
755
|
-
return candidate.code;
|
|
756
|
-
}
|
|
757
|
-
if (typeof candidate.status === "number") {
|
|
758
|
-
return candidate.status;
|
|
759
|
-
}
|
|
760
|
-
if (typeof candidate.response?.status === "number") {
|
|
761
|
-
return candidate.response.status;
|
|
762
|
-
}
|
|
763
|
-
return undefined;
|
|
764
|
-
}
|
|
765
|
-
//# sourceMappingURL=gmail.js.map
|