@cli4ai/gmail 1.0.3 → 1.0.4
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 +75 -75
- package/{c4ai.json → cli4ai.json} +1 -1
- package/package.json +8 -13
- package/lib/api.test.ts +0 -287
- package/lib/api.ts +0 -299
- package/lib/attachments.ts +0 -199
- package/lib/drafts.ts +0 -434
- package/lib/labels.ts +0 -198
- package/lib/messages.ts +0 -310
- package/lib/send.ts +0 -331
- package/lib/threads.ts +0 -164
package/lib/drafts.ts
DELETED
|
@@ -1,434 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { getGmail, output, formatDate, encodeBase64Url, extractHeaders, extractBody, type OutputOptions, type MessagePayload } from './api';
|
|
4
|
-
|
|
5
|
-
/** Attachment path */
|
|
6
|
-
export interface AttachmentPath {
|
|
7
|
-
path: string;
|
|
8
|
-
mimeType?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Draft data */
|
|
12
|
-
export interface DraftData {
|
|
13
|
-
id: string;
|
|
14
|
-
messageId?: string;
|
|
15
|
-
threadId?: string;
|
|
16
|
-
to: string;
|
|
17
|
-
cc?: string | null;
|
|
18
|
-
subject: string;
|
|
19
|
-
snippet?: string;
|
|
20
|
-
body?: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/** Draft result */
|
|
24
|
-
export interface DraftResult {
|
|
25
|
-
ok: boolean;
|
|
26
|
-
id?: string;
|
|
27
|
-
messageId?: string;
|
|
28
|
-
threadId?: string;
|
|
29
|
-
to?: string;
|
|
30
|
-
subject?: string;
|
|
31
|
-
inReplyTo?: string;
|
|
32
|
-
deleted?: string;
|
|
33
|
-
sentDraft?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Draft options */
|
|
37
|
-
export interface DraftOptions extends OutputOptions {
|
|
38
|
-
cc?: string;
|
|
39
|
-
bcc?: string;
|
|
40
|
-
attach?: string | string[];
|
|
41
|
-
limit?: number;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Create multipart message with attachment
|
|
46
|
-
*/
|
|
47
|
-
export function createMultipartMessage({ to, cc, bcc, subject, body, inReplyTo, references, attachments = [] }: {
|
|
48
|
-
to: string;
|
|
49
|
-
cc?: string;
|
|
50
|
-
bcc?: string;
|
|
51
|
-
subject: string;
|
|
52
|
-
body: string;
|
|
53
|
-
inReplyTo?: string;
|
|
54
|
-
references?: string;
|
|
55
|
-
attachments?: AttachmentPath[];
|
|
56
|
-
}): string {
|
|
57
|
-
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
58
|
-
|
|
59
|
-
const headers: string[] = [];
|
|
60
|
-
headers.push(`To: ${to}`);
|
|
61
|
-
if (cc) headers.push(`Cc: ${cc}`);
|
|
62
|
-
if (bcc) headers.push(`Bcc: ${bcc}`);
|
|
63
|
-
headers.push(`Subject: ${subject}`);
|
|
64
|
-
if (inReplyTo) {
|
|
65
|
-
headers.push(`In-Reply-To: ${inReplyTo}`);
|
|
66
|
-
headers.push(`References: ${references || inReplyTo}`);
|
|
67
|
-
}
|
|
68
|
-
headers.push('MIME-Version: 1.0');
|
|
69
|
-
headers.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
70
|
-
|
|
71
|
-
const parts: string[] = [];
|
|
72
|
-
|
|
73
|
-
// Text body part
|
|
74
|
-
parts.push(`--${boundary}`);
|
|
75
|
-
parts.push('Content-Type: text/plain; charset=utf-8');
|
|
76
|
-
parts.push('Content-Transfer-Encoding: 7bit');
|
|
77
|
-
parts.push('');
|
|
78
|
-
parts.push(body);
|
|
79
|
-
|
|
80
|
-
// Attachment parts
|
|
81
|
-
for (const att of attachments) {
|
|
82
|
-
const filename = path.basename(att.path);
|
|
83
|
-
const content = fs.readFileSync(att.path);
|
|
84
|
-
const base64Content = content.toString('base64');
|
|
85
|
-
const mimeType = att.mimeType || getMimeType(filename);
|
|
86
|
-
|
|
87
|
-
parts.push(`--${boundary}`);
|
|
88
|
-
parts.push(`Content-Type: ${mimeType}; name="${filename}"`);
|
|
89
|
-
parts.push('Content-Transfer-Encoding: base64');
|
|
90
|
-
parts.push(`Content-Disposition: attachment; filename="${filename}"`);
|
|
91
|
-
parts.push('');
|
|
92
|
-
// Split base64 into lines of 76 chars
|
|
93
|
-
for (let i = 0; i < base64Content.length; i += 76) {
|
|
94
|
-
parts.push(base64Content.slice(i, i + 76));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
parts.push(`--${boundary}--`);
|
|
99
|
-
|
|
100
|
-
const message = headers.join('\r\n') + '\r\n\r\n' + parts.join('\r\n');
|
|
101
|
-
return encodeBase64Url(message);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Simple message without attachments
|
|
106
|
-
*/
|
|
107
|
-
export function createSimpleMessage({ to, cc, bcc, subject, body, inReplyTo, references }: {
|
|
108
|
-
to: string;
|
|
109
|
-
cc?: string;
|
|
110
|
-
bcc?: string;
|
|
111
|
-
subject: string;
|
|
112
|
-
body: string;
|
|
113
|
-
inReplyTo?: string;
|
|
114
|
-
references?: string;
|
|
115
|
-
}): string {
|
|
116
|
-
const lines: string[] = [];
|
|
117
|
-
lines.push(`To: ${to}`);
|
|
118
|
-
if (cc) lines.push(`Cc: ${cc}`);
|
|
119
|
-
if (bcc) lines.push(`Bcc: ${bcc}`);
|
|
120
|
-
lines.push(`Subject: ${subject}`);
|
|
121
|
-
lines.push('Content-Type: text/plain; charset=utf-8');
|
|
122
|
-
if (inReplyTo) {
|
|
123
|
-
lines.push(`In-Reply-To: ${inReplyTo}`);
|
|
124
|
-
lines.push(`References: ${references || inReplyTo}`);
|
|
125
|
-
}
|
|
126
|
-
lines.push('');
|
|
127
|
-
lines.push(body);
|
|
128
|
-
return encodeBase64Url(lines.join('\r\n'));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Get MIME type from filename
|
|
133
|
-
*/
|
|
134
|
-
function getMimeType(filename: string): string {
|
|
135
|
-
const ext = path.extname(filename).toLowerCase();
|
|
136
|
-
const mimeTypes: Record<string, string> = {
|
|
137
|
-
'.pdf': 'application/pdf',
|
|
138
|
-
'.doc': 'application/msword',
|
|
139
|
-
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
140
|
-
'.xls': 'application/vnd.ms-excel',
|
|
141
|
-
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
142
|
-
'.png': 'image/png',
|
|
143
|
-
'.jpg': 'image/jpeg',
|
|
144
|
-
'.jpeg': 'image/jpeg',
|
|
145
|
-
'.gif': 'image/gif',
|
|
146
|
-
'.txt': 'text/plain',
|
|
147
|
-
'.csv': 'text/csv',
|
|
148
|
-
'.zip': 'application/zip',
|
|
149
|
-
'.json': 'application/json',
|
|
150
|
-
'.xml': 'application/xml',
|
|
151
|
-
'.html': 'text/html'
|
|
152
|
-
};
|
|
153
|
-
return mimeTypes[ext] || 'application/octet-stream';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* List all drafts
|
|
158
|
-
*/
|
|
159
|
-
export async function list(options: DraftOptions = {}): Promise<DraftData[]> {
|
|
160
|
-
const gmail = await getGmail();
|
|
161
|
-
const limit = options.limit || 20;
|
|
162
|
-
|
|
163
|
-
const res = await gmail.users.drafts.list({
|
|
164
|
-
userId: 'me',
|
|
165
|
-
maxResults: limit
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (!res.data.drafts || res.data.drafts.length === 0) {
|
|
169
|
-
output([], options);
|
|
170
|
-
return [];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Get details for each draft
|
|
174
|
-
const drafts = await Promise.all(
|
|
175
|
-
res.data.drafts.map(async (draft) => {
|
|
176
|
-
const draftRes = await gmail.users.drafts.get({
|
|
177
|
-
userId: 'me',
|
|
178
|
-
id: draft.id!,
|
|
179
|
-
format: 'metadata',
|
|
180
|
-
metadataHeaders: ['To', 'Subject', 'Date']
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const headers = extractHeaders(draftRes.data.message?.payload?.headers as Array<{ name: string; value: string }>);
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
id: draft.id!,
|
|
187
|
-
messageId: draftRes.data.message?.id,
|
|
188
|
-
to: headers.to || '(no recipient)',
|
|
189
|
-
subject: headers.subject || '(no subject)',
|
|
190
|
-
snippet: draftRes.data.message?.snippet || ''
|
|
191
|
-
};
|
|
192
|
-
})
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
output(drafts, options);
|
|
196
|
-
return drafts;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Get a draft
|
|
201
|
-
*/
|
|
202
|
-
export async function get(draftId: string, options: OutputOptions = {}): Promise<DraftData> {
|
|
203
|
-
const gmail = await getGmail();
|
|
204
|
-
|
|
205
|
-
const res = await gmail.users.drafts.get({
|
|
206
|
-
userId: 'me',
|
|
207
|
-
id: draftId,
|
|
208
|
-
format: 'full'
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const headers = extractHeaders(res.data.message?.payload?.headers as Array<{ name: string; value: string }>);
|
|
212
|
-
const body = extractBody(res.data.message?.payload as MessagePayload);
|
|
213
|
-
|
|
214
|
-
const draft: DraftData = {
|
|
215
|
-
id: res.data.id!,
|
|
216
|
-
messageId: res.data.message?.id,
|
|
217
|
-
threadId: res.data.message?.threadId || undefined,
|
|
218
|
-
to: headers.to || '',
|
|
219
|
-
cc: headers.cc || null,
|
|
220
|
-
subject: headers.subject || '(no subject)',
|
|
221
|
-
body: body
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
output(draft, options);
|
|
225
|
-
return draft;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Create a new draft
|
|
230
|
-
*/
|
|
231
|
-
export async function create(to: string, subject: string, body: string, options: DraftOptions = {}): Promise<DraftResult> {
|
|
232
|
-
const gmail = await getGmail();
|
|
233
|
-
|
|
234
|
-
body = body.replace(/\\n/g, '\n');
|
|
235
|
-
|
|
236
|
-
let raw: string;
|
|
237
|
-
if (options.attach) {
|
|
238
|
-
const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
|
|
239
|
-
const attachmentPaths = attachments.map(a => ({ path: a }));
|
|
240
|
-
raw = createMultipartMessage({
|
|
241
|
-
to,
|
|
242
|
-
cc: options.cc,
|
|
243
|
-
bcc: options.bcc,
|
|
244
|
-
subject,
|
|
245
|
-
body,
|
|
246
|
-
attachments: attachmentPaths
|
|
247
|
-
});
|
|
248
|
-
} else {
|
|
249
|
-
raw = createSimpleMessage({
|
|
250
|
-
to,
|
|
251
|
-
cc: options.cc,
|
|
252
|
-
bcc: options.bcc,
|
|
253
|
-
subject,
|
|
254
|
-
body
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
const res = await gmail.users.drafts.create({
|
|
259
|
-
userId: 'me',
|
|
260
|
-
requestBody: {
|
|
261
|
-
message: { raw }
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
const result: DraftResult = {
|
|
266
|
-
ok: true,
|
|
267
|
-
id: res.data.id || undefined,
|
|
268
|
-
messageId: res.data.message?.id,
|
|
269
|
-
to,
|
|
270
|
-
subject
|
|
271
|
-
};
|
|
272
|
-
|
|
273
|
-
output(result, options);
|
|
274
|
-
return result;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Create a draft reply to a message
|
|
279
|
-
*/
|
|
280
|
-
export async function createReply(messageId: string, body: string, options: DraftOptions = {}): Promise<DraftResult> {
|
|
281
|
-
const gmail = await getGmail();
|
|
282
|
-
|
|
283
|
-
// Get original message
|
|
284
|
-
const original = await gmail.users.messages.get({
|
|
285
|
-
userId: 'me',
|
|
286
|
-
id: messageId,
|
|
287
|
-
format: 'metadata',
|
|
288
|
-
metadataHeaders: ['From', 'To', 'Subject', 'Message-ID', 'References']
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
const headers = extractHeaders(original.data.payload?.headers as Array<{ name: string; value: string }>);
|
|
292
|
-
|
|
293
|
-
// Determine reply-to address
|
|
294
|
-
const replyTo = headers.from!;
|
|
295
|
-
let subject = headers.subject || '';
|
|
296
|
-
if (!subject.toLowerCase().startsWith('re:')) {
|
|
297
|
-
subject = `Re: ${subject}`;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
body = body.replace(/\\n/g, '\n');
|
|
301
|
-
|
|
302
|
-
let raw: string;
|
|
303
|
-
if (options.attach) {
|
|
304
|
-
const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
|
|
305
|
-
const attachmentPaths = attachments.map(a => ({ path: a }));
|
|
306
|
-
raw = createMultipartMessage({
|
|
307
|
-
to: replyTo,
|
|
308
|
-
subject,
|
|
309
|
-
body,
|
|
310
|
-
inReplyTo: headers.message_id,
|
|
311
|
-
references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id,
|
|
312
|
-
attachments: attachmentPaths
|
|
313
|
-
});
|
|
314
|
-
} else {
|
|
315
|
-
raw = createSimpleMessage({
|
|
316
|
-
to: replyTo,
|
|
317
|
-
subject,
|
|
318
|
-
body,
|
|
319
|
-
inReplyTo: headers.message_id,
|
|
320
|
-
references: headers.references ? `${headers.references} ${headers.message_id}` : headers.message_id
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const res = await gmail.users.drafts.create({
|
|
325
|
-
userId: 'me',
|
|
326
|
-
requestBody: {
|
|
327
|
-
message: {
|
|
328
|
-
raw,
|
|
329
|
-
threadId: original.data.threadId || undefined
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
const result: DraftResult = {
|
|
335
|
-
ok: true,
|
|
336
|
-
id: res.data.id || undefined,
|
|
337
|
-
messageId: res.data.message?.id,
|
|
338
|
-
threadId: original.data.threadId || undefined,
|
|
339
|
-
to: replyTo,
|
|
340
|
-
subject,
|
|
341
|
-
inReplyTo: messageId
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
output(result, options);
|
|
345
|
-
return result;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Update a draft
|
|
350
|
-
*/
|
|
351
|
-
export async function update(draftId: string, to: string, subject: string, body: string, options: DraftOptions = {}): Promise<DraftResult> {
|
|
352
|
-
const gmail = await getGmail();
|
|
353
|
-
|
|
354
|
-
body = body.replace(/\\n/g, '\n');
|
|
355
|
-
|
|
356
|
-
let raw: string;
|
|
357
|
-
if (options.attach) {
|
|
358
|
-
const attachments = Array.isArray(options.attach) ? options.attach : [options.attach];
|
|
359
|
-
const attachmentPaths = attachments.map(a => ({ path: a }));
|
|
360
|
-
raw = createMultipartMessage({
|
|
361
|
-
to,
|
|
362
|
-
cc: options.cc,
|
|
363
|
-
subject,
|
|
364
|
-
body,
|
|
365
|
-
attachments: attachmentPaths
|
|
366
|
-
});
|
|
367
|
-
} else {
|
|
368
|
-
raw = createSimpleMessage({
|
|
369
|
-
to,
|
|
370
|
-
cc: options.cc,
|
|
371
|
-
subject,
|
|
372
|
-
body
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const res = await gmail.users.drafts.update({
|
|
377
|
-
userId: 'me',
|
|
378
|
-
id: draftId,
|
|
379
|
-
requestBody: {
|
|
380
|
-
message: { raw }
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
const result: DraftResult = {
|
|
385
|
-
ok: true,
|
|
386
|
-
id: res.data.id || undefined,
|
|
387
|
-
messageId: res.data.message?.id,
|
|
388
|
-
to,
|
|
389
|
-
subject
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
output(result, options);
|
|
393
|
-
return result;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Delete a draft
|
|
398
|
-
*/
|
|
399
|
-
export async function remove(draftId: string, options: OutputOptions = {}): Promise<DraftResult> {
|
|
400
|
-
const gmail = await getGmail();
|
|
401
|
-
|
|
402
|
-
await gmail.users.drafts.delete({
|
|
403
|
-
userId: 'me',
|
|
404
|
-
id: draftId
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
const result: DraftResult = { ok: true, deleted: draftId };
|
|
408
|
-
output(result, options);
|
|
409
|
-
return result;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Send a draft
|
|
414
|
-
*/
|
|
415
|
-
export async function send(draftId: string, options: OutputOptions = {}): Promise<DraftResult> {
|
|
416
|
-
const gmail = await getGmail();
|
|
417
|
-
|
|
418
|
-
const res = await gmail.users.drafts.send({
|
|
419
|
-
userId: 'me',
|
|
420
|
-
requestBody: {
|
|
421
|
-
id: draftId
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
const result: DraftResult = {
|
|
426
|
-
ok: true,
|
|
427
|
-
id: res.data.id || undefined,
|
|
428
|
-
threadId: res.data.threadId || undefined,
|
|
429
|
-
sentDraft: draftId
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
output(result, options);
|
|
433
|
-
return result;
|
|
434
|
-
}
|
package/lib/labels.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { getGmail, output, type OutputOptions } from './api';
|
|
2
|
-
|
|
3
|
-
/** Label data */
|
|
4
|
-
export interface LabelData {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
type?: string;
|
|
8
|
-
messageListVisibility?: string;
|
|
9
|
-
labelListVisibility?: string;
|
|
10
|
-
messagesTotal?: number;
|
|
11
|
-
messagesUnread?: number;
|
|
12
|
-
threadsTotal?: number;
|
|
13
|
-
threadsUnread?: number;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/** Label action result */
|
|
17
|
-
export interface LabelActionResult {
|
|
18
|
-
ok: boolean;
|
|
19
|
-
id?: string;
|
|
20
|
-
name?: string;
|
|
21
|
-
messageId?: string;
|
|
22
|
-
labelAdded?: string;
|
|
23
|
-
labelRemoved?: string;
|
|
24
|
-
deleted?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* List all labels
|
|
29
|
-
*/
|
|
30
|
-
export async function list(options: OutputOptions = {}): Promise<LabelData[]> {
|
|
31
|
-
const gmail = await getGmail();
|
|
32
|
-
|
|
33
|
-
const res = await gmail.users.labels.list({
|
|
34
|
-
userId: 'me'
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
const labels: LabelData[] = (res.data.labels || []).map(label => ({
|
|
38
|
-
id: label.id!,
|
|
39
|
-
name: label.name!,
|
|
40
|
-
type: label.type || undefined,
|
|
41
|
-
messageListVisibility: label.messageListVisibility || undefined,
|
|
42
|
-
labelListVisibility: label.labelListVisibility || undefined
|
|
43
|
-
}));
|
|
44
|
-
|
|
45
|
-
// Sort: system labels first, then user labels
|
|
46
|
-
labels.sort((a, b) => {
|
|
47
|
-
if (a.type === 'system' && b.type !== 'system') return -1;
|
|
48
|
-
if (a.type !== 'system' && b.type === 'system') return 1;
|
|
49
|
-
return a.name.localeCompare(b.name);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
output(labels, options);
|
|
53
|
-
return labels;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Get label details with counts
|
|
58
|
-
*/
|
|
59
|
-
export async function get(labelId: string, options: OutputOptions = {}): Promise<LabelData> {
|
|
60
|
-
const gmail = await getGmail();
|
|
61
|
-
|
|
62
|
-
// Resolve label name to ID if needed
|
|
63
|
-
const resolvedId = await resolveLabelId(gmail, labelId);
|
|
64
|
-
|
|
65
|
-
const res = await gmail.users.labels.get({
|
|
66
|
-
userId: 'me',
|
|
67
|
-
id: resolvedId
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const label: LabelData = {
|
|
71
|
-
id: res.data.id!,
|
|
72
|
-
name: res.data.name!,
|
|
73
|
-
type: res.data.type || undefined,
|
|
74
|
-
messagesTotal: res.data.messagesTotal || undefined,
|
|
75
|
-
messagesUnread: res.data.messagesUnread || undefined,
|
|
76
|
-
threadsTotal: res.data.threadsTotal || undefined,
|
|
77
|
-
threadsUnread: res.data.threadsUnread || undefined
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
output(label, options);
|
|
81
|
-
return label;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Add label to message
|
|
86
|
-
*/
|
|
87
|
-
export async function addToMessage(messageId: string, labelName: string, options: OutputOptions = {}): Promise<LabelActionResult> {
|
|
88
|
-
const gmail = await getGmail();
|
|
89
|
-
|
|
90
|
-
const labelId = await resolveLabelId(gmail, labelName);
|
|
91
|
-
|
|
92
|
-
await gmail.users.messages.modify({
|
|
93
|
-
userId: 'me',
|
|
94
|
-
id: messageId,
|
|
95
|
-
requestBody: {
|
|
96
|
-
addLabelIds: [labelId]
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
const result: LabelActionResult = { ok: true, messageId, labelAdded: labelName };
|
|
101
|
-
output(result, options);
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Remove label from message
|
|
107
|
-
*/
|
|
108
|
-
export async function removeFromMessage(messageId: string, labelName: string, options: OutputOptions = {}): Promise<LabelActionResult> {
|
|
109
|
-
const gmail = await getGmail();
|
|
110
|
-
|
|
111
|
-
const labelId = await resolveLabelId(gmail, labelName);
|
|
112
|
-
|
|
113
|
-
await gmail.users.messages.modify({
|
|
114
|
-
userId: 'me',
|
|
115
|
-
id: messageId,
|
|
116
|
-
requestBody: {
|
|
117
|
-
removeLabelIds: [labelId]
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const result: LabelActionResult = { ok: true, messageId, labelRemoved: labelName };
|
|
122
|
-
output(result, options);
|
|
123
|
-
return result;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Create a new label
|
|
128
|
-
*/
|
|
129
|
-
export async function create(labelName: string, options: OutputOptions = {}): Promise<LabelActionResult> {
|
|
130
|
-
const gmail = await getGmail();
|
|
131
|
-
|
|
132
|
-
const res = await gmail.users.labels.create({
|
|
133
|
-
userId: 'me',
|
|
134
|
-
requestBody: {
|
|
135
|
-
name: labelName,
|
|
136
|
-
labelListVisibility: 'labelShow',
|
|
137
|
-
messageListVisibility: 'show'
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const result: LabelActionResult = {
|
|
142
|
-
ok: true,
|
|
143
|
-
id: res.data.id!,
|
|
144
|
-
name: res.data.name!
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
output(result, options);
|
|
148
|
-
return result;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Delete a label
|
|
153
|
-
*/
|
|
154
|
-
export async function remove(labelName: string, options: OutputOptions = {}): Promise<LabelActionResult> {
|
|
155
|
-
const gmail = await getGmail();
|
|
156
|
-
|
|
157
|
-
const labelId = await resolveLabelId(gmail, labelName);
|
|
158
|
-
|
|
159
|
-
// Don't allow deleting system labels
|
|
160
|
-
const systemLabels = ['INBOX', 'SENT', 'TRASH', 'SPAM', 'STARRED', 'UNREAD', 'IMPORTANT', 'DRAFT'];
|
|
161
|
-
if (labelId.startsWith('CATEGORY_') || systemLabels.includes(labelId)) {
|
|
162
|
-
throw new Error(`Cannot delete system label: ${labelName}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
await gmail.users.labels.delete({
|
|
166
|
-
userId: 'me',
|
|
167
|
-
id: labelId
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
const result: LabelActionResult = { ok: true, deleted: labelName };
|
|
171
|
-
output(result, options);
|
|
172
|
-
return result;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Resolve label name to ID
|
|
177
|
-
*/
|
|
178
|
-
async function resolveLabelId(gmail: ReturnType<typeof import('googleapis').google.gmail>, labelNameOrId: string): Promise<string> {
|
|
179
|
-
// Check if already an ID (system labels or Label_xxx format)
|
|
180
|
-
const systemLabels = ['INBOX', 'SENT', 'TRASH', 'SPAM', 'STARRED', 'UNREAD', 'IMPORTANT', 'DRAFT', 'CHAT'];
|
|
181
|
-
if (labelNameOrId.startsWith('Label_') ||
|
|
182
|
-
labelNameOrId.startsWith('CATEGORY_') ||
|
|
183
|
-
systemLabels.includes(labelNameOrId.toUpperCase())) {
|
|
184
|
-
return labelNameOrId.toUpperCase();
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Look up by name
|
|
188
|
-
const res = await gmail.users.labels.list({ userId: 'me' });
|
|
189
|
-
const label = res.data.labels?.find(
|
|
190
|
-
l => l.name?.toLowerCase() === labelNameOrId.toLowerCase()
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
if (!label) {
|
|
194
|
-
throw new Error(`Label not found: ${labelNameOrId}`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return label.id!;
|
|
198
|
-
}
|