zele 0.3.14 → 0.3.16
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 +25 -0
- package/dist/api-utils.d.ts +3 -0
- package/dist/api-utils.js +6 -0
- package/dist/api-utils.js.map +1 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/calendar-time.js +6 -0
- package/dist/calendar-time.js.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/attachment.js +42 -2
- package/dist/commands/attachment.js.map +1 -1
- package/dist/commands/auth-cmd.js +1 -1
- package/dist/commands/auth-cmd.js.map +1 -1
- package/dist/commands/calendar.js +1 -1
- package/dist/commands/calendar.js.map +1 -1
- package/dist/commands/draft.js +2 -2
- package/dist/commands/draft.js.map +1 -1
- package/dist/commands/filter.d.ts +2 -0
- package/dist/commands/filter.js +59 -0
- package/dist/commands/filter.js.map +1 -0
- package/dist/commands/mail-actions.js +12 -2
- package/dist/commands/mail-actions.js.map +1 -1
- package/dist/commands/mail.js +176 -93
- package/dist/commands/mail.js.map +1 -1
- package/dist/db.js +24 -1
- package/dist/db.js.map +1 -1
- package/dist/gmail-client.d.ts +28 -0
- package/dist/gmail-client.js +168 -13
- package/dist/gmail-client.js.map +1 -1
- package/dist/mail-tui.js +34 -9
- package/dist/mail-tui.js.map +1 -1
- package/dist/output.d.ts +2 -0
- package/dist/output.js +4 -0
- package/dist/output.js.map +1 -1
- package/package.json +8 -3
- package/skills/zele/SKILL.md +112 -0
- package/src/api-utils.ts +7 -0
- package/src/app.log +9 -0
- package/src/auth.ts +1 -1
- package/src/calendar-time.test.ts +35 -0
- package/src/calendar-time.ts +5 -0
- package/src/cli.ts +6 -1
- package/src/commands/attachment.ts +47 -2
- package/src/commands/auth-cmd.ts +1 -1
- package/src/commands/calendar.ts +1 -1
- package/src/commands/draft.ts +2 -2
- package/src/commands/filter.ts +68 -0
- package/src/commands/mail-actions.ts +14 -2
- package/src/commands/mail.ts +186 -98
- package/src/db.ts +26 -1
- package/src/gmail-client.ts +202 -20
- package/src/mail-tui.test.ts +170 -0
- package/src/mail-tui.tsx +56 -9
- package/src/output.ts +8 -1
- package/src/opentui-react.d.ts +0 -9
package/dist/gmail-client.d.ts
CHANGED
|
@@ -54,10 +54,16 @@ export interface ThreadListItem {
|
|
|
54
54
|
snippet: string;
|
|
55
55
|
subject: string;
|
|
56
56
|
from: Sender;
|
|
57
|
+
to: Sender[];
|
|
58
|
+
cc: Sender[];
|
|
57
59
|
date: string;
|
|
58
60
|
labelIds: string[];
|
|
59
61
|
unread: boolean;
|
|
62
|
+
starred: boolean;
|
|
60
63
|
messageCount: number;
|
|
64
|
+
inReplyTo: string | null;
|
|
65
|
+
hasAttachments: boolean;
|
|
66
|
+
listUnsubscribe: string | null;
|
|
61
67
|
}
|
|
62
68
|
export interface ThreadListResult {
|
|
63
69
|
threads: ThreadListItem[];
|
|
@@ -244,6 +250,12 @@ export declare class GmailClient {
|
|
|
244
250
|
archive({ threadIds }: {
|
|
245
251
|
threadIds: string[];
|
|
246
252
|
}): Promise<void | AuthError | ApiError>;
|
|
253
|
+
markAsSpam({ threadIds }: {
|
|
254
|
+
threadIds: string[];
|
|
255
|
+
}): Promise<void | AuthError | ApiError>;
|
|
256
|
+
unmarkSpam({ threadIds }: {
|
|
257
|
+
threadIds: string[];
|
|
258
|
+
}): Promise<void | AuthError | ApiError>;
|
|
247
259
|
/** Invalidate thread cache after a thread mutation. */
|
|
248
260
|
private invalidateAfterThreadMutation;
|
|
249
261
|
/** Moves all spam threads to trash. Does not permanently delete. */
|
|
@@ -282,6 +294,18 @@ export declare class GmailClient {
|
|
|
282
294
|
deleteLabel({ labelId }: {
|
|
283
295
|
labelId: string;
|
|
284
296
|
}): Promise<void>;
|
|
297
|
+
listFilters(): Promise<{
|
|
298
|
+
parsed: gmail_v1.Schema$Filter[];
|
|
299
|
+
} | AuthError | ApiError>;
|
|
300
|
+
createFilter(opts: {
|
|
301
|
+
from?: string;
|
|
302
|
+
query?: string;
|
|
303
|
+
addLabelIds?: string[];
|
|
304
|
+
removeLabelIds?: string[];
|
|
305
|
+
}): Promise<gmail_v1.Schema$Filter | AuthError | ApiError>;
|
|
306
|
+
deleteFilter(filterId: string): Promise<void | AuthError | ApiError>;
|
|
307
|
+
/** Resolve a label name to its ID, auto-creating if missing. Public wrapper for filter commands. */
|
|
308
|
+
resolveLabel(nameOrId: string): Promise<string | AuthError | ApiError>;
|
|
285
309
|
getLabelCounts(): Promise<{
|
|
286
310
|
parsed: Array<{
|
|
287
311
|
label: string;
|
|
@@ -327,6 +351,10 @@ export declare class GmailClient {
|
|
|
327
351
|
private extractBody;
|
|
328
352
|
private findBodyPart;
|
|
329
353
|
private extractAttachmentMeta;
|
|
354
|
+
/** Quick check: does the message payload tree contain any non-inline attachments?
|
|
355
|
+
* Checks the root part itself (some messages have attachment metadata there)
|
|
356
|
+
* then recurses into child parts. */
|
|
357
|
+
private hasNonInlineAttachments;
|
|
330
358
|
getEmailAliases(): Promise<Array<{
|
|
331
359
|
email: string;
|
|
332
360
|
name?: string;
|
package/dist/gmail-client.js
CHANGED
|
@@ -71,6 +71,14 @@ function sanitizeSnippet(snippet) {
|
|
|
71
71
|
.replace(/\s+/g, ' ')
|
|
72
72
|
.trim();
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Sanitize header values to prevent CRLF injection attacks.
|
|
76
|
+
* The mimetext library does not sanitize custom header values, so newlines
|
|
77
|
+
* in In-Reply-To or References could inject arbitrary headers.
|
|
78
|
+
*/
|
|
79
|
+
function sanitizeHeaderValue(value) {
|
|
80
|
+
return value.replace(/[\r\n]/g, ' ').trim();
|
|
81
|
+
}
|
|
74
82
|
function encodeBase64Url(data) {
|
|
75
83
|
const buf = typeof data === 'string' ? Buffer.from(data) : data;
|
|
76
84
|
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
@@ -98,6 +106,23 @@ function gmailBoundary(email, fn) {
|
|
|
98
106
|
: new ApiError({ reason: String(err), cause: err }),
|
|
99
107
|
});
|
|
100
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Known folder names that map to Gmail system folders/labels.
|
|
111
|
+
* Used to validate custom label names and prevent query injection.
|
|
112
|
+
*/
|
|
113
|
+
const KNOWN_FOLDERS = new Set([
|
|
114
|
+
'inbox',
|
|
115
|
+
'sent',
|
|
116
|
+
'trash',
|
|
117
|
+
'bin',
|
|
118
|
+
'spam',
|
|
119
|
+
'drafts',
|
|
120
|
+
'draft',
|
|
121
|
+
'starred',
|
|
122
|
+
'archive',
|
|
123
|
+
'snoozed',
|
|
124
|
+
'all',
|
|
125
|
+
]);
|
|
101
126
|
export class GmailClient {
|
|
102
127
|
gmail;
|
|
103
128
|
labelIdCache = {};
|
|
@@ -503,7 +528,9 @@ export class GmailClient {
|
|
|
503
528
|
return messageIds;
|
|
504
529
|
if (messageIds.length === 0)
|
|
505
530
|
return;
|
|
506
|
-
await this.batchModifyMessages(messageIds, { removeLabelIds: ['UNREAD'] });
|
|
531
|
+
const mod = await this.batchModifyMessages(messageIds, { removeLabelIds: ['UNREAD'] });
|
|
532
|
+
if (mod instanceof Error)
|
|
533
|
+
return mod;
|
|
507
534
|
await this.invalidateAfterThreadMutation(threadIds);
|
|
508
535
|
}
|
|
509
536
|
async markAsUnread({ threadIds }) {
|
|
@@ -512,7 +539,9 @@ export class GmailClient {
|
|
|
512
539
|
return messageIds;
|
|
513
540
|
if (messageIds.length === 0)
|
|
514
541
|
return;
|
|
515
|
-
await this.batchModifyMessages(messageIds, { addLabelIds: ['UNREAD'] });
|
|
542
|
+
const mod = await this.batchModifyMessages(messageIds, { addLabelIds: ['UNREAD'] });
|
|
543
|
+
if (mod instanceof Error)
|
|
544
|
+
return mod;
|
|
516
545
|
await this.invalidateAfterThreadMutation(threadIds);
|
|
517
546
|
}
|
|
518
547
|
async star({ threadIds }) {
|
|
@@ -521,7 +550,9 @@ export class GmailClient {
|
|
|
521
550
|
return messageIds;
|
|
522
551
|
if (messageIds.length === 0)
|
|
523
552
|
return;
|
|
524
|
-
await this.batchModifyMessages(messageIds, { addLabelIds: ['STARRED'] });
|
|
553
|
+
const mod = await this.batchModifyMessages(messageIds, { addLabelIds: ['STARRED'] });
|
|
554
|
+
if (mod instanceof Error)
|
|
555
|
+
return mod;
|
|
525
556
|
await this.invalidateAfterThreadMutation(threadIds);
|
|
526
557
|
}
|
|
527
558
|
async unstar({ threadIds }) {
|
|
@@ -530,7 +561,9 @@ export class GmailClient {
|
|
|
530
561
|
return messageIds;
|
|
531
562
|
if (messageIds.length === 0)
|
|
532
563
|
return;
|
|
533
|
-
await this.batchModifyMessages(messageIds, { removeLabelIds: ['STARRED'] });
|
|
564
|
+
const mod = await this.batchModifyMessages(messageIds, { removeLabelIds: ['STARRED'] });
|
|
565
|
+
if (mod instanceof Error)
|
|
566
|
+
return mod;
|
|
534
567
|
await this.invalidateAfterThreadMutation(threadIds);
|
|
535
568
|
}
|
|
536
569
|
async modifyLabels({ threadIds, addLabelIds = [], removeLabelIds = [], }) {
|
|
@@ -549,10 +582,12 @@ export class GmailClient {
|
|
|
549
582
|
return messageIds;
|
|
550
583
|
if (messageIds.length === 0)
|
|
551
584
|
return;
|
|
552
|
-
await this.batchModifyMessages(messageIds, {
|
|
585
|
+
const mod = await this.batchModifyMessages(messageIds, {
|
|
553
586
|
addLabelIds: resolvedAdd.filter((r) => typeof r === 'string'),
|
|
554
587
|
removeLabelIds: resolvedRemove,
|
|
555
588
|
});
|
|
589
|
+
if (mod instanceof Error)
|
|
590
|
+
return mod;
|
|
556
591
|
await this.invalidateAfterThreadMutation(threadIds);
|
|
557
592
|
}
|
|
558
593
|
async trash({ threadId }) {
|
|
@@ -575,7 +610,31 @@ export class GmailClient {
|
|
|
575
610
|
return messageIds;
|
|
576
611
|
if (messageIds.length === 0)
|
|
577
612
|
return;
|
|
578
|
-
await this.batchModifyMessages(messageIds, { removeLabelIds: ['INBOX'] });
|
|
613
|
+
const mod = await this.batchModifyMessages(messageIds, { removeLabelIds: ['INBOX'] });
|
|
614
|
+
if (mod instanceof Error)
|
|
615
|
+
return mod;
|
|
616
|
+
await this.invalidateAfterThreadMutation(threadIds);
|
|
617
|
+
}
|
|
618
|
+
async markAsSpam({ threadIds }) {
|
|
619
|
+
const messageIds = await this.getMessageIdsForThreads(threadIds);
|
|
620
|
+
if (messageIds instanceof Error)
|
|
621
|
+
return messageIds;
|
|
622
|
+
if (messageIds.length === 0)
|
|
623
|
+
return;
|
|
624
|
+
const mod = await this.batchModifyMessages(messageIds, { addLabelIds: ['SPAM'], removeLabelIds: ['INBOX'] });
|
|
625
|
+
if (mod instanceof Error)
|
|
626
|
+
return mod;
|
|
627
|
+
await this.invalidateAfterThreadMutation(threadIds);
|
|
628
|
+
}
|
|
629
|
+
async unmarkSpam({ threadIds }) {
|
|
630
|
+
const messageIds = await this.getMessageIdsForThreads(threadIds, (labelIds) => labelIds.includes('SPAM'));
|
|
631
|
+
if (messageIds instanceof Error)
|
|
632
|
+
return messageIds;
|
|
633
|
+
if (messageIds.length === 0)
|
|
634
|
+
return;
|
|
635
|
+
const mod = await this.batchModifyMessages(messageIds, { removeLabelIds: ['SPAM'], addLabelIds: ['INBOX'] });
|
|
636
|
+
if (mod instanceof Error)
|
|
637
|
+
return mod;
|
|
579
638
|
await this.invalidateAfterThreadMutation(threadIds);
|
|
580
639
|
}
|
|
581
640
|
/** Invalidate thread cache after a thread mutation. */
|
|
@@ -600,10 +659,12 @@ export class GmailClient {
|
|
|
600
659
|
const messageIds = await this.getMessageIdsForThreads(threadIds);
|
|
601
660
|
if (messageIds instanceof Error)
|
|
602
661
|
return messageIds;
|
|
603
|
-
await this.batchModifyMessages(messageIds, {
|
|
662
|
+
const mod = await this.batchModifyMessages(messageIds, {
|
|
604
663
|
addLabelIds: ['TRASH'],
|
|
605
664
|
removeLabelIds: ['SPAM', 'INBOX'],
|
|
606
665
|
});
|
|
666
|
+
if (mod instanceof Error)
|
|
667
|
+
return mod;
|
|
607
668
|
totalDeleted += threadIds.length;
|
|
608
669
|
pageToken = res.nextPageToken ?? undefined;
|
|
609
670
|
if (!pageToken)
|
|
@@ -674,6 +735,46 @@ export class GmailClient {
|
|
|
674
735
|
await this.invalidateLabels();
|
|
675
736
|
}
|
|
676
737
|
// =========================================================================
|
|
738
|
+
// Filters
|
|
739
|
+
// =========================================================================
|
|
740
|
+
async listFilters() {
|
|
741
|
+
const res = await gmailBoundary(this.account?.email ?? 'unknown', () => withRetry(() => this.gmail.users.settings.filters.list({ userId: 'me' })));
|
|
742
|
+
if (res instanceof Error)
|
|
743
|
+
return res;
|
|
744
|
+
return { parsed: res.data.filter ?? [] };
|
|
745
|
+
}
|
|
746
|
+
async createFilter(opts) {
|
|
747
|
+
const criteria = {};
|
|
748
|
+
if (opts.from)
|
|
749
|
+
criteria.from = opts.from;
|
|
750
|
+
if (opts.query)
|
|
751
|
+
criteria.query = opts.query;
|
|
752
|
+
const action = {};
|
|
753
|
+
if (opts.addLabelIds?.length)
|
|
754
|
+
action.addLabelIds = opts.addLabelIds;
|
|
755
|
+
if (opts.removeLabelIds?.length)
|
|
756
|
+
action.removeLabelIds = opts.removeLabelIds;
|
|
757
|
+
const res = await gmailBoundary(this.account?.email ?? 'unknown', () => withRetry(() => this.gmail.users.settings.filters.create({
|
|
758
|
+
userId: 'me',
|
|
759
|
+
requestBody: { criteria, action },
|
|
760
|
+
})));
|
|
761
|
+
if (res instanceof Error)
|
|
762
|
+
return res;
|
|
763
|
+
return res.data;
|
|
764
|
+
}
|
|
765
|
+
async deleteFilter(filterId) {
|
|
766
|
+
const res = await gmailBoundary(this.account?.email ?? 'unknown', () => withRetry(() => this.gmail.users.settings.filters.delete({
|
|
767
|
+
userId: 'me',
|
|
768
|
+
id: filterId,
|
|
769
|
+
})));
|
|
770
|
+
if (res instanceof Error)
|
|
771
|
+
return res;
|
|
772
|
+
}
|
|
773
|
+
/** Resolve a label name to its ID, auto-creating if missing. Public wrapper for filter commands. */
|
|
774
|
+
async resolveLabel(nameOrId) {
|
|
775
|
+
return this.resolveLabelId(nameOrId);
|
|
776
|
+
}
|
|
777
|
+
// =========================================================================
|
|
677
778
|
// Label counts (unread counts per folder/label)
|
|
678
779
|
// =========================================================================
|
|
679
780
|
async getLabelCounts() {
|
|
@@ -865,16 +966,39 @@ export class GmailClient {
|
|
|
865
966
|
}
|
|
866
967
|
}
|
|
867
968
|
}
|
|
969
|
+
// Parse recipients from latest message
|
|
970
|
+
const toHeader = getHeader('to') ?? '';
|
|
971
|
+
const ccHeaders = headers
|
|
972
|
+
.filter((h) => h.name?.toLowerCase() === 'cc')
|
|
973
|
+
.map((h) => h.value ?? '')
|
|
974
|
+
.filter((v) => v.length > 0);
|
|
975
|
+
// Check if any non-draft message in the thread is a reply (has In-Reply-To header)
|
|
976
|
+
const inReplyTo = nonDraftMessages
|
|
977
|
+
.map((m) => m.payload?.headers?.find((h) => h.name?.toLowerCase() === 'in-reply-to')?.value ??
|
|
978
|
+
null)
|
|
979
|
+
.find((v) => v !== null) ?? null;
|
|
980
|
+
// Check if any message has attachments (non-inline)
|
|
981
|
+
const hasAttachments = messages.some((m) => this.hasNonInlineAttachments(m.payload));
|
|
982
|
+
// List-Unsubscribe from latest message
|
|
983
|
+
const listUnsubscribe = getHeader('list-unsubscribe') ?? null;
|
|
868
984
|
return {
|
|
869
985
|
id: raw.id ?? '',
|
|
870
986
|
historyId: raw.historyId ?? null,
|
|
871
987
|
snippet: sanitizeSnippet(latest?.snippet ?? ''),
|
|
872
988
|
subject: (getHeader('subject') ?? '(no subject)').replace(/"/g, '').trim(),
|
|
873
989
|
from: displayFrom,
|
|
990
|
+
to: toHeader ? parseAddressList(toHeader) : [],
|
|
991
|
+
cc: ccHeaders.length > 0
|
|
992
|
+
? ccHeaders.filter((h) => h.trim().length > 0).flatMap((h) => parseAddressList(h))
|
|
993
|
+
: [],
|
|
874
994
|
date: getHeader('date') ?? '',
|
|
875
995
|
labelIds: allLabels,
|
|
876
996
|
unread: allLabels.includes('UNREAD'),
|
|
997
|
+
starred: allLabels.includes('STARRED'),
|
|
877
998
|
messageCount: nonDraftMessages.length,
|
|
999
|
+
inReplyTo,
|
|
1000
|
+
hasAttachments,
|
|
1001
|
+
listUnsubscribe,
|
|
878
1002
|
};
|
|
879
1003
|
}
|
|
880
1004
|
/** Parse raw gmail_v1.Schema$Label[] from labels.list into our label objects. */
|
|
@@ -976,6 +1100,25 @@ export class GmailClient {
|
|
|
976
1100
|
}
|
|
977
1101
|
return results;
|
|
978
1102
|
}
|
|
1103
|
+
/** Quick check: does the message payload tree contain any non-inline attachments?
|
|
1104
|
+
* Checks the root part itself (some messages have attachment metadata there)
|
|
1105
|
+
* then recurses into child parts. */
|
|
1106
|
+
hasNonInlineAttachments(part) {
|
|
1107
|
+
if (!part)
|
|
1108
|
+
return false;
|
|
1109
|
+
if (part.filename && part.filename.length > 0 && part.body?.attachmentId) {
|
|
1110
|
+
const disposition = part.headers?.find((h) => h.name?.toLowerCase() === 'content-disposition')?.value ?? '';
|
|
1111
|
+
const hasContentId = part.headers?.some((h) => h.name?.toLowerCase() === 'content-id');
|
|
1112
|
+
const isInline = disposition.toLowerCase().includes('inline');
|
|
1113
|
+
if (!isInline || !hasContentId)
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
for (const child of part.parts ?? []) {
|
|
1117
|
+
if (this.hasNonInlineAttachments(child))
|
|
1118
|
+
return true;
|
|
1119
|
+
}
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
979
1122
|
async getEmailAliases() {
|
|
980
1123
|
const profile = await this.getProfile();
|
|
981
1124
|
if (profile instanceof Error)
|
|
@@ -1162,13 +1305,14 @@ export class GmailClient {
|
|
|
1162
1305
|
data: body,
|
|
1163
1306
|
});
|
|
1164
1307
|
if (inReplyTo) {
|
|
1165
|
-
msg.setHeader('In-Reply-To', inReplyTo);
|
|
1308
|
+
msg.setHeader('In-Reply-To', sanitizeHeaderValue(inReplyTo));
|
|
1166
1309
|
}
|
|
1167
1310
|
if (references) {
|
|
1168
1311
|
const refs = references
|
|
1169
1312
|
.split(' ')
|
|
1170
1313
|
.filter(Boolean)
|
|
1171
1314
|
.map((ref) => {
|
|
1315
|
+
ref = sanitizeHeaderValue(ref);
|
|
1172
1316
|
if (!ref.startsWith('<'))
|
|
1173
1317
|
ref = `<${ref}`;
|
|
1174
1318
|
if (!ref.endsWith('>'))
|
|
@@ -1244,7 +1388,16 @@ export class GmailClient {
|
|
|
1244
1388
|
}
|
|
1245
1389
|
// For non-inbox folders, use Gmail search syntax.
|
|
1246
1390
|
// Caller-provided labelIds are preserved as additional filters.
|
|
1247
|
-
|
|
1391
|
+
// Normalize folder name to lowercase for consistent matching
|
|
1392
|
+
const normalizedFolder = folder.toLowerCase();
|
|
1393
|
+
// Validate custom label names to prevent query injection.
|
|
1394
|
+
// Gmail query operators like "OR", "from:", parentheses, etc. could manipulate search results.
|
|
1395
|
+
// Known folders are handled by the switch cases below; custom labels must be safe characters only.
|
|
1396
|
+
// Slashes are allowed for nested labels (e.g., "work/projects").
|
|
1397
|
+
if (!KNOWN_FOLDERS.has(normalizedFolder) && !/^[\w\/-]+$/.test(normalizedFolder)) {
|
|
1398
|
+
throw new Error(`Invalid folder/label name: "${folder}". Use alphanumeric characters, underscores, hyphens, and slashes only.`);
|
|
1399
|
+
}
|
|
1400
|
+
switch (normalizedFolder) {
|
|
1248
1401
|
case 'sent':
|
|
1249
1402
|
q = `in:sent ${q}`.trim();
|
|
1250
1403
|
break;
|
|
@@ -1272,8 +1425,8 @@ export class GmailClient {
|
|
|
1272
1425
|
q = `in:anywhere ${q}`.trim();
|
|
1273
1426
|
break;
|
|
1274
1427
|
default:
|
|
1275
|
-
// Treat as a label name
|
|
1276
|
-
q = `label:${
|
|
1428
|
+
// Treat as a label name (use normalized for consistency)
|
|
1429
|
+
q = `label:${normalizedFolder} ${q}`.trim();
|
|
1277
1430
|
break;
|
|
1278
1431
|
}
|
|
1279
1432
|
return { q, resolvedLabelIds };
|
|
@@ -1310,13 +1463,15 @@ export class GmailClient {
|
|
|
1310
1463
|
const chunkSize = 1000;
|
|
1311
1464
|
for (let i = 0; i < messageIds.length; i += chunkSize) {
|
|
1312
1465
|
const chunk = messageIds.slice(i, i + chunkSize);
|
|
1313
|
-
await withRetry(() => this.gmail.users.messages.batchModify({
|
|
1466
|
+
const res = await gmailBoundary(this.account?.email ?? 'unknown', () => withRetry(() => this.gmail.users.messages.batchModify({
|
|
1314
1467
|
userId: 'me',
|
|
1315
1468
|
requestBody: {
|
|
1316
1469
|
ids: chunk,
|
|
1317
1470
|
...body,
|
|
1318
1471
|
},
|
|
1319
|
-
}));
|
|
1472
|
+
})));
|
|
1473
|
+
if (res instanceof Error)
|
|
1474
|
+
return res;
|
|
1320
1475
|
}
|
|
1321
1476
|
}
|
|
1322
1477
|
// =========================================================================
|