@pome-sh/cli 0.21.5 → 0.21.6
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/dist/build-info.json +3 -3
- package/dist/{checks-2AIHHQC2.js → checks-4GJB73YX.js} +1 -1
- package/dist/{chunk-CLGRZY53.js → chunk-4ILBTPO4.js} +4 -4
- package/dist/chunk-5ETJZJR6.js +590 -0
- package/dist/{chunk-MBM7LY7E.js → chunk-A3IDZXJY.js} +9 -9
- package/dist/chunk-FKZZWWYC.js +4 -0
- package/dist/{chunk-OMVRPIBP.js → chunk-JULH5ECU.js} +1 -1
- package/dist/{chunk-JQQPZQOJ.js → chunk-M3X6OT4N.js} +1 -1
- package/dist/chunk-NJ246QPJ.js +683 -0
- package/dist/chunk-SGDUD7KK.js +138 -0
- package/dist/{chunk-NWXONLFF.js → chunk-WR7KP3LE.js} +2 -2
- package/dist/chunk-XS66ZRBJ.js +537 -0
- package/dist/chunk-ZKID2HS3.js +628 -0
- package/dist/{runDemo-M3GEOJLT.js → runDemo-BCB6EDDN.js} +7 -8
- package/dist/{runTrialGroup-QQNDVHMR.js → runTrialGroup-YCSGW56C.js} +6 -7
- package/dist/src/cli/main.js +20 -16
- package/dist/{chunk-WVWV7DLA.js → src-FLTYWM6P.js} +500 -1761
- package/dist/{chunk-APAS34RJ.js → src-K3KTJTMG.js} +154 -1308
- package/dist/{chunk-OKDJRKJV.js → src-RVUEXCKN.js} +6 -135
- package/dist/twinHarness-KFZ2ZNAX.js +5 -0
- package/dist/{twinStart-E42H5CYD.js → twinStart-4OGP2GBY.js} +2 -2
- package/package.json +1 -1
- package/dist/src-JGOW5WYI.js +0 -5
- package/dist/src-KIHABGI5.js +0 -5
- package/dist/src-TUX5L2RC.js +0 -4
- package/dist/twinHarness-YTU4427G.js +0 -5
|
@@ -1,686 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
export { GMAIL_CHECKS } from './chunk-5ETJZJR6.js';
|
|
2
|
+
import { gmailErrorEnvelope, gmailSeedSchema, parseSeed, defaultSeedState, notFound, invalidArgument, checkFault, unsupported, validateSearchQuery, SEARCH_MAILBOX_MESSAGE_BUDGET, GmailError, parseSearchQuery, CATEGORY_LABELS, parseDate, parseSize, KNOWN_FIELDS, parseDuration, assertHasOperator } from './chunk-NJ246QPJ.js';
|
|
3
|
+
export { DEFAULT_GMAIL_AGENT_EMAIL, GmailError, SEARCH_MAILBOX_MESSAGE_BUDGET, agentPathInboxMailbox, defaultSeedState, gmailErrorEnvelope, gmailSeedSchema, loadSeedFromEnv, parseSearchQuery, parseSeed, validateSearchQuery } from './chunk-NJ246QPJ.js';
|
|
4
|
+
import './chunk-NY55QTVQ.js';
|
|
2
5
|
import { defineTwin, openTwinDatabase, createApp } from './chunk-LZIPGCQJ.js';
|
|
3
|
-
import
|
|
6
|
+
import './chunk-VBATFCWR.js';
|
|
7
|
+
import './chunk-SG6ZTIMT.js';
|
|
4
8
|
import { createHash, createHmac, timingSafeEqual, randomBytes } from 'node:crypto';
|
|
5
|
-
|
|
6
|
-
var GmailError = class extends Error {
|
|
7
|
-
status;
|
|
8
|
-
reason;
|
|
9
|
-
constructor(status, reason, message2) {
|
|
10
|
-
super(message2);
|
|
11
|
-
this.status = status;
|
|
12
|
-
this.reason = reason;
|
|
13
|
-
this.name = "GmailError";
|
|
14
|
-
}
|
|
15
|
-
};
|
|
16
|
-
function gmailErrorEnvelope(error) {
|
|
17
|
-
if (error instanceof GmailError) {
|
|
18
|
-
return {
|
|
19
|
-
status: error.status,
|
|
20
|
-
body: {
|
|
21
|
-
error: {
|
|
22
|
-
code: error.status,
|
|
23
|
-
message: error.message,
|
|
24
|
-
errors: [{ message: error.message, domain: "global", reason: error.reason }],
|
|
25
|
-
status: googleStatus(error.status)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
if (error instanceof ZodError || error instanceof Error && error.name === "ZodError") {
|
|
31
|
-
return {
|
|
32
|
-
status: 400,
|
|
33
|
-
body: {
|
|
34
|
-
error: {
|
|
35
|
-
code: 400,
|
|
36
|
-
message: "Invalid request",
|
|
37
|
-
errors: [{ message: "Invalid request", domain: "global", reason: "invalidArgument" }],
|
|
38
|
-
status: "INVALID_ARGUMENT"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (error instanceof SyntaxError) {
|
|
44
|
-
return {
|
|
45
|
-
status: 400,
|
|
46
|
-
body: {
|
|
47
|
-
error: {
|
|
48
|
-
code: 400,
|
|
49
|
-
message: "Invalid JSON",
|
|
50
|
-
errors: [{ message: "Invalid JSON", domain: "global", reason: "invalidArgument" }],
|
|
51
|
-
status: "INVALID_ARGUMENT"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
if (error instanceof Error && (error.name === "TwinError" || error.name === "UnknownToolError")) {
|
|
57
|
-
const unknownTool = error.name === "UnknownToolError";
|
|
58
|
-
const status = unknownTool ? 404 : typeof error.status === "number" ? error.status : 400;
|
|
59
|
-
return {
|
|
60
|
-
status,
|
|
61
|
-
body: {
|
|
62
|
-
error: {
|
|
63
|
-
code: status,
|
|
64
|
-
message: error.message,
|
|
65
|
-
errors: [
|
|
66
|
-
{
|
|
67
|
-
message: error.message,
|
|
68
|
-
domain: "global",
|
|
69
|
-
reason: unknownTool ? "notFound" : "invalidArgument"
|
|
70
|
-
}
|
|
71
|
-
],
|
|
72
|
-
status: googleStatus(status)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
status: 500,
|
|
79
|
-
body: {
|
|
80
|
-
error: {
|
|
81
|
-
code: 500,
|
|
82
|
-
message: "Internal error",
|
|
83
|
-
errors: [{ message: "Internal error", domain: "global", reason: "backendError" }],
|
|
84
|
-
status: "INTERNAL"
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
function notFound(resource = "Requested entity") {
|
|
90
|
-
throw new GmailError(404, "notFound", `${resource} was not found.`);
|
|
91
|
-
}
|
|
92
|
-
function invalidArgument(message2) {
|
|
93
|
-
throw new GmailError(400, "invalidArgument", message2);
|
|
94
|
-
}
|
|
95
|
-
function unsupported(message2) {
|
|
96
|
-
throw new GmailError(501, "notImplemented", message2);
|
|
97
|
-
}
|
|
98
|
-
function googleStatus(status) {
|
|
99
|
-
if (status === 400)
|
|
100
|
-
return "INVALID_ARGUMENT";
|
|
101
|
-
if (status === 401)
|
|
102
|
-
return "UNAUTHENTICATED";
|
|
103
|
-
if (status === 403)
|
|
104
|
-
return "PERMISSION_DENIED";
|
|
105
|
-
if (status === 404)
|
|
106
|
-
return "NOT_FOUND";
|
|
107
|
-
if (status === 429)
|
|
108
|
-
return "RESOURCE_EXHAUSTED";
|
|
109
|
-
if (status === 409)
|
|
110
|
-
return "ALREADY_EXISTS";
|
|
111
|
-
if (status === 501)
|
|
112
|
-
return "UNIMPLEMENTED";
|
|
113
|
-
return "INTERNAL";
|
|
114
|
-
}
|
|
115
|
-
var KNOWN_FAULT_NAMES = ["rate-limited"];
|
|
116
|
-
var gmailFaultSchema = z.object({
|
|
117
|
-
name: z.enum(KNOWN_FAULT_NAMES),
|
|
118
|
-
target: z.string().min(1).max(128).default("messages.send"),
|
|
119
|
-
succeedFirst: z.number().int().nonnegative().max(1e3).default(2),
|
|
120
|
-
throttleFor: z.number().int().positive().max(1e3).default(3),
|
|
121
|
-
retryAfterSeconds: z.number().int().positive().max(3600).default(1)
|
|
122
|
-
}).strict();
|
|
123
|
-
function checkFault(db, operation) {
|
|
124
|
-
const fault = readFaults(db).find((f) => f.target === operation);
|
|
125
|
-
if (!fault)
|
|
126
|
-
return;
|
|
127
|
-
const calls = bumpFaultCounter(db, operation);
|
|
128
|
-
if (calls > fault.succeedFirst && calls <= fault.succeedFirst + fault.throttleFor) {
|
|
129
|
-
throw new GmailError(429, "rateLimitExceeded", `Rate limit exceeded for ${operation}. Retry after ${fault.retryAfterSeconds}s.`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
function readFaults(db) {
|
|
133
|
-
const row = db.prepare("SELECT value FROM gmail_config WHERE key = 'faults'").get();
|
|
134
|
-
if (!row)
|
|
135
|
-
return [];
|
|
136
|
-
try {
|
|
137
|
-
return JSON.parse(row.value);
|
|
138
|
-
} catch {
|
|
139
|
-
return [];
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
function bumpFaultCounter(db, operation) {
|
|
143
|
-
db.prepare("INSERT INTO fault_counters(operation, calls) VALUES (?, 1) ON CONFLICT(operation) DO UPDATE SET calls = calls + 1").run(operation);
|
|
144
|
-
const row = db.prepare("SELECT calls FROM fault_counters WHERE operation = ?").get(operation);
|
|
145
|
-
return row.calls;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ../packages/twin-gmail/dist/src/search-parse.js
|
|
149
|
-
var MAX_QUERY_BYTES = 4096;
|
|
150
|
-
var MAX_TOKENS = 256;
|
|
151
|
-
var MAX_DEPTH = 20;
|
|
152
|
-
var MAX_BRANCHES = 256;
|
|
153
|
-
var SEARCH_MAILBOX_MESSAGE_BUDGET = 1e4;
|
|
154
|
-
var KNOWN_FIELDS = /* @__PURE__ */ new Set([
|
|
155
|
-
"from",
|
|
156
|
-
"to",
|
|
157
|
-
"cc",
|
|
158
|
-
"bcc",
|
|
159
|
-
"deliveredto",
|
|
160
|
-
"list",
|
|
161
|
-
"subject",
|
|
162
|
-
"rfc822msgid",
|
|
163
|
-
"filename",
|
|
164
|
-
"after",
|
|
165
|
-
"newer",
|
|
166
|
-
"before",
|
|
167
|
-
"older",
|
|
168
|
-
"newer_than",
|
|
169
|
-
"older_than",
|
|
170
|
-
"size",
|
|
171
|
-
"larger",
|
|
172
|
-
"smaller",
|
|
173
|
-
"label",
|
|
174
|
-
"category",
|
|
175
|
-
"in",
|
|
176
|
-
"is",
|
|
177
|
-
"has"
|
|
178
|
-
]);
|
|
179
|
-
var CATEGORY_LABELS = {
|
|
180
|
-
primary: "CATEGORY_PERSONAL",
|
|
181
|
-
personal: "CATEGORY_PERSONAL",
|
|
182
|
-
social: "CATEGORY_SOCIAL",
|
|
183
|
-
promotions: "CATEGORY_PROMOTIONS",
|
|
184
|
-
updates: "CATEGORY_UPDATES",
|
|
185
|
-
forums: "CATEGORY_FORUMS"
|
|
186
|
-
};
|
|
187
|
-
function validateSearchQuery(query) {
|
|
188
|
-
const node = parseSearchQuery(query);
|
|
189
|
-
const walk = (current) => {
|
|
190
|
-
switch (current.type) {
|
|
191
|
-
case "and":
|
|
192
|
-
case "or":
|
|
193
|
-
for (const child of current.children)
|
|
194
|
-
walk(child);
|
|
195
|
-
return;
|
|
196
|
-
case "not":
|
|
197
|
-
walk(current.child);
|
|
198
|
-
return;
|
|
199
|
-
case "around":
|
|
200
|
-
return;
|
|
201
|
-
case "term":
|
|
202
|
-
assertSearchTerm(current.field, current.value);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
walk(node);
|
|
207
|
-
return node;
|
|
208
|
-
}
|
|
209
|
-
function assertSearchTerm(field, value) {
|
|
210
|
-
if (!field)
|
|
211
|
-
return;
|
|
212
|
-
if (!KNOWN_FIELDS.has(field)) {
|
|
213
|
-
invalidArgument(`Unsupported search operator: ${field}`);
|
|
214
|
-
}
|
|
215
|
-
if (field === "category") {
|
|
216
|
-
if (!CATEGORY_LABELS[value.toLocaleLowerCase("en-US")]) {
|
|
217
|
-
invalidArgument(`Unsupported search category: ${value}`);
|
|
218
|
-
}
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
if (field === "after" || field === "newer" || field === "before" || field === "older") {
|
|
222
|
-
parseDate(value);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
if (field === "newer_than" || field === "older_than") {
|
|
226
|
-
parseDuration(value);
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
if (field === "size" || field === "larger" || field === "smaller") {
|
|
230
|
-
parseSize(value);
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
if (field === "has") {
|
|
234
|
-
assertHasOperator(value);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
function parseSearchQuery(query) {
|
|
238
|
-
if (Buffer.byteLength(query) > MAX_QUERY_BYTES)
|
|
239
|
-
invalidArgument("Search query exceeds limit");
|
|
240
|
-
const tokens = tokenize(query);
|
|
241
|
-
if (tokens.length > MAX_TOKENS)
|
|
242
|
-
invalidArgument("Search query has too many tokens");
|
|
243
|
-
if (tokens.length === 0)
|
|
244
|
-
return { type: "and", children: [] };
|
|
245
|
-
let position = 0;
|
|
246
|
-
const parseExpression = (depth, stop) => {
|
|
247
|
-
if (depth > MAX_DEPTH)
|
|
248
|
-
invalidArgument("Search query nesting exceeds limit");
|
|
249
|
-
const alternatives = [];
|
|
250
|
-
let conjunction = [];
|
|
251
|
-
const flush = () => {
|
|
252
|
-
alternatives.push(conjunction.length === 1 ? conjunction[0] : { type: "and", children: conjunction });
|
|
253
|
-
conjunction = [];
|
|
254
|
-
};
|
|
255
|
-
while (position < tokens.length) {
|
|
256
|
-
const token = tokens[position];
|
|
257
|
-
if (stop && token.value === stop)
|
|
258
|
-
break;
|
|
259
|
-
if (token.value.toUpperCase() === "OR") {
|
|
260
|
-
position++;
|
|
261
|
-
if (!conjunction.length)
|
|
262
|
-
invalidArgument("OR requires a left expression");
|
|
263
|
-
flush();
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
if (token.value.toUpperCase() === "AND") {
|
|
267
|
-
position++;
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
conjunction.push(parsePrimary(depth + 1));
|
|
271
|
-
}
|
|
272
|
-
if (conjunction.length)
|
|
273
|
-
flush();
|
|
274
|
-
if (!alternatives.length)
|
|
275
|
-
return { type: "and", children: [] };
|
|
276
|
-
return alternatives.length === 1 ? alternatives[0] : { type: "or", children: alternatives };
|
|
277
|
-
};
|
|
278
|
-
const parsePrimary = (depth) => {
|
|
279
|
-
const token = tokens[position++];
|
|
280
|
-
if (token.value === "(" || token.value === "{") {
|
|
281
|
-
const end = token.value === "(" ? ")" : "}";
|
|
282
|
-
const child = parseExpression(depth, end);
|
|
283
|
-
if (tokens[position]?.value !== end)
|
|
284
|
-
invalidArgument(`Unclosed ${token.value}`);
|
|
285
|
-
position++;
|
|
286
|
-
return token.value === "{" ? makeImplicitOr(child) : child;
|
|
287
|
-
}
|
|
288
|
-
if (token.value === ")" || token.value === "}")
|
|
289
|
-
invalidArgument(`Unexpected ${token.value}`);
|
|
290
|
-
let value = token.value;
|
|
291
|
-
let negate = false;
|
|
292
|
-
if (value === "-") {
|
|
293
|
-
negate = true;
|
|
294
|
-
const next = tokens[position++];
|
|
295
|
-
if (!next)
|
|
296
|
-
invalidArgument("Negation requires an expression");
|
|
297
|
-
position--;
|
|
298
|
-
const child = parsePrimary(depth + 1);
|
|
299
|
-
return { type: "not", child };
|
|
300
|
-
}
|
|
301
|
-
if (value.startsWith("-") && value.length > 1) {
|
|
302
|
-
negate = true;
|
|
303
|
-
value = value.slice(1);
|
|
304
|
-
}
|
|
305
|
-
const around = tokens[position]?.value.toUpperCase() === "AROUND";
|
|
306
|
-
if (around) {
|
|
307
|
-
position++;
|
|
308
|
-
const distance = Number(tokens[position++]?.value);
|
|
309
|
-
const right = tokens[position++]?.value;
|
|
310
|
-
if (!Number.isInteger(distance) || distance < 1 || distance > 100 || !right) {
|
|
311
|
-
invalidArgument("AROUND requires a distance and right term");
|
|
312
|
-
}
|
|
313
|
-
const node2 = { type: "around", left: value, right, distance };
|
|
314
|
-
return negate ? { type: "not", child: node2 } : node2;
|
|
315
|
-
}
|
|
316
|
-
const colon = value.indexOf(":");
|
|
317
|
-
let node;
|
|
318
|
-
if (colon > 0) {
|
|
319
|
-
const field = value.slice(0, colon).toLowerCase();
|
|
320
|
-
let fieldValue = value.slice(colon + 1);
|
|
321
|
-
if (!fieldValue && (tokens[position]?.value === "(" || tokens[position]?.value === "{")) {
|
|
322
|
-
node = parseFieldGroup(field, depth + 1);
|
|
323
|
-
return negate ? { type: "not", child: node } : node;
|
|
324
|
-
}
|
|
325
|
-
if (!fieldValue && tokens[position] && !["AND", "OR", ")", "}"].includes(tokens[position].value.toUpperCase())) {
|
|
326
|
-
fieldValue = tokens[position++].value;
|
|
327
|
-
}
|
|
328
|
-
node = { type: "term", field, value: fieldValue, exact: token.quoted };
|
|
329
|
-
} else {
|
|
330
|
-
node = { type: "term", value: value.startsWith("+") ? value.slice(1) : value, exact: token.quoted || value.startsWith("+") };
|
|
331
|
-
}
|
|
332
|
-
return negate ? { type: "not", child: node } : node;
|
|
333
|
-
};
|
|
334
|
-
const parseFieldGroup = (field, depth) => {
|
|
335
|
-
if (depth > MAX_DEPTH)
|
|
336
|
-
invalidArgument("Search query nesting exceeds limit");
|
|
337
|
-
const opening = tokens[position++].value;
|
|
338
|
-
const end = opening === "(" ? ")" : "}";
|
|
339
|
-
const groups = [[]];
|
|
340
|
-
while (position < tokens.length && tokens[position].value !== end) {
|
|
341
|
-
const token = tokens[position++];
|
|
342
|
-
if (token.value.toUpperCase() === "OR") {
|
|
343
|
-
groups.push([]);
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (token.value.toUpperCase() === "AND")
|
|
347
|
-
continue;
|
|
348
|
-
if (["(", "{", ")", "}"].includes(token.value))
|
|
349
|
-
invalidArgument("Nested field groups are unsupported");
|
|
350
|
-
let value = token.value;
|
|
351
|
-
let negate = false;
|
|
352
|
-
if (value.startsWith("-")) {
|
|
353
|
-
negate = true;
|
|
354
|
-
value = value.slice(1);
|
|
355
|
-
}
|
|
356
|
-
const term = { type: "term", field, value, exact: token.quoted };
|
|
357
|
-
groups.at(-1).push(negate ? { type: "not", child: term } : term);
|
|
358
|
-
}
|
|
359
|
-
if (tokens[position]?.value !== end)
|
|
360
|
-
invalidArgument(`Unclosed ${opening}`);
|
|
361
|
-
position++;
|
|
362
|
-
const nodes = groups.map((children) => children.length === 1 ? children[0] : { type: "and", children });
|
|
363
|
-
const useOr = opening === "{" || nodes.length > 1;
|
|
364
|
-
return useOr ? { type: "or", children: nodes } : nodes[0] ?? { type: "and", children: [] };
|
|
365
|
-
};
|
|
366
|
-
const root = parseExpression(0);
|
|
367
|
-
if (position !== tokens.length)
|
|
368
|
-
invalidArgument("Unexpected search token");
|
|
369
|
-
if (countBranches(root) > MAX_BRANCHES)
|
|
370
|
-
invalidArgument("Search query has too many branches");
|
|
371
|
-
return root;
|
|
372
|
-
}
|
|
373
|
-
function assertHasOperator(value) {
|
|
374
|
-
const normalized = value.toLocaleLowerCase("en-US");
|
|
375
|
-
if (normalized.endsWith("-star")) {
|
|
376
|
-
invalidArgument(`Unsupported colored-star operator: has:${value}; twin maps only STARRED via is:starred`);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
function parseDate(value) {
|
|
380
|
-
const normalized = /^\d{4}\/\d{1,2}\/\d{1,2}$/.test(value) ? value.replaceAll("/", "-") : value;
|
|
381
|
-
const date = Date.parse(`${normalized}${/^\d{4}-\d/.test(normalized) ? "T00:00:00Z" : ""}`);
|
|
382
|
-
if (Number.isNaN(date))
|
|
383
|
-
invalidArgument(`Invalid search date: ${value}`);
|
|
384
|
-
return date;
|
|
385
|
-
}
|
|
386
|
-
function parseDuration(value) {
|
|
387
|
-
const match = value.match(/^(\d+)([dmy])$/i);
|
|
388
|
-
if (!match)
|
|
389
|
-
invalidArgument(`Invalid search duration: ${value}`);
|
|
390
|
-
const units = { d: 864e5, m: 30 * 864e5, y: 365 * 864e5 };
|
|
391
|
-
return Number(match[1]) * units[match[2].toLowerCase()];
|
|
392
|
-
}
|
|
393
|
-
function parseSize(value) {
|
|
394
|
-
const match = value.match(/^(\d+(?:\.\d+)?)([kmg])?$/i);
|
|
395
|
-
if (!match)
|
|
396
|
-
invalidArgument(`Invalid search size: ${value}`);
|
|
397
|
-
const scale = { k: 1024, m: 1024 ** 2, g: 1024 ** 3 };
|
|
398
|
-
return Math.floor(Number(match[1]) * (match[2] ? scale[match[2].toLowerCase()] : 1));
|
|
399
|
-
}
|
|
400
|
-
function countBranches(node) {
|
|
401
|
-
switch (node.type) {
|
|
402
|
-
case "and":
|
|
403
|
-
case "or":
|
|
404
|
-
return node.children.reduce((sum, child) => sum + countBranches(child), 0);
|
|
405
|
-
case "not":
|
|
406
|
-
return countBranches(node.child);
|
|
407
|
-
case "around":
|
|
408
|
-
case "term":
|
|
409
|
-
return 1;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
function tokenize(query) {
|
|
413
|
-
const out = [];
|
|
414
|
-
let index = 0;
|
|
415
|
-
while (index < query.length) {
|
|
416
|
-
if (/\s/.test(query[index])) {
|
|
417
|
-
index++;
|
|
418
|
-
continue;
|
|
419
|
-
}
|
|
420
|
-
const char = query[index];
|
|
421
|
-
if ("(){}".includes(char)) {
|
|
422
|
-
out.push({ value: char });
|
|
423
|
-
index++;
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
let value = "";
|
|
427
|
-
let quoted = false;
|
|
428
|
-
while (index < query.length && !/\s/.test(query[index]) && !"(){}".includes(query[index])) {
|
|
429
|
-
if (query[index] === '"') {
|
|
430
|
-
quoted = true;
|
|
431
|
-
index++;
|
|
432
|
-
while (index < query.length && query[index] !== '"') {
|
|
433
|
-
if (query[index] === "\\" && index + 1 < query.length)
|
|
434
|
-
index++;
|
|
435
|
-
value += query[index++];
|
|
436
|
-
}
|
|
437
|
-
if (query[index] !== '"')
|
|
438
|
-
invalidArgument("Unclosed search quote");
|
|
439
|
-
index++;
|
|
440
|
-
} else {
|
|
441
|
-
value += query[index++];
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
if (value)
|
|
445
|
-
out.push({ value, quoted });
|
|
446
|
-
}
|
|
447
|
-
return out;
|
|
448
|
-
}
|
|
449
|
-
function makeImplicitOr(node) {
|
|
450
|
-
return node.type === "and" ? { type: "or", children: node.children } : node;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// ../packages/twin-gmail/dist/src/seed.js
|
|
454
|
-
var email = z.string().trim().email().transform((value) => value.toLowerCase());
|
|
455
|
-
var id = z.string().min(1).max(128).regex(/^[A-Za-z0-9_-]+$/);
|
|
456
|
-
var attachmentSchema = z.object({
|
|
457
|
-
filename: z.string().max(512),
|
|
458
|
-
mimeType: z.string().min(1).max(255).default("application/octet-stream"),
|
|
459
|
-
disposition: z.enum(["attachment", "inline"]).default("attachment"),
|
|
460
|
-
contentId: z.string().max(998).optional(),
|
|
461
|
-
data: z.string().max(5e7)
|
|
462
|
-
}).strict();
|
|
463
|
-
var messageFields = {
|
|
464
|
-
id: id.optional(),
|
|
465
|
-
threadId: id.optional(),
|
|
466
|
-
raw: z.string().max(5e7).optional(),
|
|
467
|
-
from: email.optional(),
|
|
468
|
-
to: z.array(email).max(500).default([]),
|
|
469
|
-
cc: z.array(email).max(500).default([]),
|
|
470
|
-
bcc: z.array(email).max(500).default([]),
|
|
471
|
-
subject: z.string().max(998).default(""),
|
|
472
|
-
text: z.string().max(25e6).default(""),
|
|
473
|
-
html: z.string().max(25e6).default(""),
|
|
474
|
-
date: z.string().datetime({ offset: true }).optional(),
|
|
475
|
-
messageId: z.string().min(3).max(998).optional(),
|
|
476
|
-
inReplyTo: z.string().max(998).optional(),
|
|
477
|
-
references: z.array(z.string().max(998)).max(100).default([]),
|
|
478
|
-
attachments: z.array(attachmentSchema).max(100).default([])
|
|
479
|
-
};
|
|
480
|
-
var messageSchema = z.object({
|
|
481
|
-
...messageFields,
|
|
482
|
-
labels: z.array(z.string().min(1).max(255)).max(100).default([])
|
|
483
|
-
}).strict();
|
|
484
|
-
var draftSchema = z.object(messageFields).strict();
|
|
485
|
-
var labelSchema = z.object({
|
|
486
|
-
id: id.optional(),
|
|
487
|
-
name: z.string().trim().min(1).max(225),
|
|
488
|
-
color: z.object({
|
|
489
|
-
textColor: z.string().max(32).optional(),
|
|
490
|
-
backgroundColor: z.string().max(32).optional()
|
|
491
|
-
}).strict().optional()
|
|
492
|
-
}).strict();
|
|
493
|
-
var filterSchema = z.object({
|
|
494
|
-
id: id.optional(),
|
|
495
|
-
criteria: z.object({
|
|
496
|
-
from: z.string().max(998).optional(),
|
|
497
|
-
to: z.string().max(998).optional(),
|
|
498
|
-
subject: z.string().max(998).optional(),
|
|
499
|
-
query: z.string().max(4096).optional(),
|
|
500
|
-
negatedQuery: z.string().max(4096).optional(),
|
|
501
|
-
hasAttachment: z.boolean().optional(),
|
|
502
|
-
excludeChats: z.boolean().optional(),
|
|
503
|
-
size: z.number().int().nonnegative().optional(),
|
|
504
|
-
sizeComparison: z.enum(["larger", "smaller"]).optional()
|
|
505
|
-
}).strict().default({}),
|
|
506
|
-
action: z.object({
|
|
507
|
-
addLabelIds: z.array(z.string().min(1)).max(100).default([]),
|
|
508
|
-
removeLabelIds: z.array(z.string().min(1)).max(100).default([]),
|
|
509
|
-
forward: email.optional()
|
|
510
|
-
}).strict().default({ addLabelIds: [], removeLabelIds: [] })
|
|
511
|
-
}).strict();
|
|
512
|
-
var sendAsSchema = z.object({
|
|
513
|
-
sendAsEmail: email,
|
|
514
|
-
displayName: z.string().max(256).default(""),
|
|
515
|
-
replyToAddress: email.optional(),
|
|
516
|
-
isPrimary: z.boolean().default(false),
|
|
517
|
-
isDefault: z.boolean().default(false),
|
|
518
|
-
verificationStatus: z.enum(["accepted", "pending"]).default("accepted")
|
|
519
|
-
}).strict();
|
|
520
|
-
var mailboxSchema = z.object({
|
|
521
|
-
email,
|
|
522
|
-
displayName: z.string().max(256).default(""),
|
|
523
|
-
labels: z.array(labelSchema).max(5e3).default([]),
|
|
524
|
-
messages: z.array(messageSchema).max(1e4).default([]),
|
|
525
|
-
drafts: z.array(draftSchema).max(5e3).default([]),
|
|
526
|
-
filters: z.array(filterSchema).max(1e3).default([]),
|
|
527
|
-
forwardingAddresses: z.array(z.object({
|
|
528
|
-
forwardingEmail: email,
|
|
529
|
-
verificationStatus: z.enum(["accepted", "pending"]).default("pending")
|
|
530
|
-
}).strict()).max(1e3).default([]),
|
|
531
|
-
sendAs: z.array(sendAsSchema).max(1e3).default([])
|
|
532
|
-
}).strict();
|
|
533
|
-
var gmailSeedSchema = z.object({
|
|
534
|
-
primaryMailbox: mailboxSchema,
|
|
535
|
-
mailboxes: z.array(mailboxSchema).max(100).default([]),
|
|
536
|
-
deliveryMode: z.enum(["sender-only", "seeded-mailboxes"]).default("sender-only"),
|
|
537
|
-
clock: z.string().datetime({ offset: true }).default("2025-01-01T00:00:00.000Z"),
|
|
538
|
-
faults: z.array(gmailFaultSchema).max(50).default([])
|
|
539
|
-
}).strict().superRefine((seed, ctx) => {
|
|
540
|
-
const emails = [seed.primaryMailbox.email, ...seed.mailboxes.map((mailbox) => mailbox.email)];
|
|
541
|
-
const seen = /* @__PURE__ */ new Set();
|
|
542
|
-
for (const mailboxEmail of emails) {
|
|
543
|
-
if (seen.has(mailboxEmail)) {
|
|
544
|
-
ctx.addIssue({ code: "custom", message: `Duplicate mailbox: ${mailboxEmail}` });
|
|
545
|
-
}
|
|
546
|
-
seen.add(mailboxEmail);
|
|
547
|
-
}
|
|
548
|
-
for (const mailbox of [seed.primaryMailbox, ...seed.mailboxes]) {
|
|
549
|
-
const labelNames = /* @__PURE__ */ new Set();
|
|
550
|
-
for (const label2 of mailbox.labels) {
|
|
551
|
-
const key = label2.name.toLowerCase();
|
|
552
|
-
if (labelNames.has(key)) {
|
|
553
|
-
ctx.addIssue({ code: "custom", message: `Duplicate label in ${mailbox.email}: ${label2.name}` });
|
|
554
|
-
}
|
|
555
|
-
labelNames.add(key);
|
|
556
|
-
}
|
|
557
|
-
for (const filter2 of mailbox.filters) {
|
|
558
|
-
if (filter2.action.forward) {
|
|
559
|
-
ctx.addIssue({
|
|
560
|
-
code: "custom",
|
|
561
|
-
message: `Filter forwarding is unsupported: ${mailbox.email}`
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
for (const key of ["query", "negatedQuery"]) {
|
|
565
|
-
const value = filter2.criteria[key];
|
|
566
|
-
if (!value)
|
|
567
|
-
continue;
|
|
568
|
-
try {
|
|
569
|
-
validateSearchQuery(value);
|
|
570
|
-
} catch (error) {
|
|
571
|
-
ctx.addIssue({
|
|
572
|
-
code: "custom",
|
|
573
|
-
message: `Invalid filter ${key} in ${mailbox.email}: ${error.message}`
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
function parseSeed(input) {
|
|
581
|
-
return gmailSeedSchema.parse(input);
|
|
582
|
-
}
|
|
583
|
-
function loadSeedFromEnv(env = process.env) {
|
|
584
|
-
const raw = env.POME_SEED_JSON;
|
|
585
|
-
if (!raw)
|
|
586
|
-
return parseSeed(defaultSeedState());
|
|
587
|
-
let parsed;
|
|
588
|
-
try {
|
|
589
|
-
parsed = JSON.parse(raw);
|
|
590
|
-
} catch (error) {
|
|
591
|
-
throw new Error(`POME_SEED_JSON is not valid JSON: ${error.message}`);
|
|
592
|
-
}
|
|
593
|
-
return parseSeed(parsed);
|
|
594
|
-
}
|
|
595
|
-
var DEFAULT_GMAIL_AGENT_EMAIL = "pome-agent@pome-twin.test";
|
|
596
|
-
function agentPathInboxMailbox(email3 = DEFAULT_GMAIL_AGENT_EMAIL) {
|
|
597
|
-
return {
|
|
598
|
-
email: email3,
|
|
599
|
-
displayName: "Pome Agent",
|
|
600
|
-
labels: [
|
|
601
|
-
{ id: "Label_follow_up", name: "Follow Up" },
|
|
602
|
-
{ id: "Label_build", name: "Build" }
|
|
603
|
-
],
|
|
604
|
-
messages: [
|
|
605
|
-
{
|
|
606
|
-
id: "msg_welcome",
|
|
607
|
-
threadId: "thread_welcome",
|
|
608
|
-
from: "welcome@pome-twin.test",
|
|
609
|
-
to: [email3],
|
|
610
|
-
subject: "Welcome to your Pome Gmail twin",
|
|
611
|
-
text: "Your deterministic inbox is ready for agent testing.",
|
|
612
|
-
html: "<p>Your deterministic inbox is ready for agent testing.</p>",
|
|
613
|
-
date: "2026-07-18T09:00:00.000Z",
|
|
614
|
-
messageId: "welcome@pome-twin.test",
|
|
615
|
-
labels: ["INBOX"]
|
|
616
|
-
},
|
|
617
|
-
{
|
|
618
|
-
id: "msg_build",
|
|
619
|
-
threadId: "thread_build",
|
|
620
|
-
from: "ci@example.com",
|
|
621
|
-
to: [email3],
|
|
622
|
-
subject: "Build failed on main",
|
|
623
|
-
text: "The nightly build failed. See the attached log.",
|
|
624
|
-
date: "2026-07-19T10:00:00.000Z",
|
|
625
|
-
messageId: "build-001@example.com",
|
|
626
|
-
labels: ["INBOX", "UNREAD", "Build"],
|
|
627
|
-
attachments: [
|
|
628
|
-
{
|
|
629
|
-
filename: "build.log",
|
|
630
|
-
mimeType: "text/plain",
|
|
631
|
-
data: Buffer.from("BUILD FAILED step=test\n", "utf8").toString("base64")
|
|
632
|
-
}
|
|
633
|
-
]
|
|
634
|
-
},
|
|
635
|
-
{
|
|
636
|
-
id: "msg_build_reply",
|
|
637
|
-
threadId: "thread_build",
|
|
638
|
-
from: email3,
|
|
639
|
-
to: ["ci@example.com"],
|
|
640
|
-
subject: "Re: Build failed on main",
|
|
641
|
-
text: "Looking into the failure now.",
|
|
642
|
-
date: "2026-07-19T11:00:00.000Z",
|
|
643
|
-
messageId: "build-reply@pome-twin.test",
|
|
644
|
-
inReplyTo: "build-001@example.com",
|
|
645
|
-
references: ["build-001@example.com"],
|
|
646
|
-
labels: ["SENT"]
|
|
647
|
-
},
|
|
648
|
-
{
|
|
649
|
-
id: "msg_support",
|
|
650
|
-
threadId: "thread_support",
|
|
651
|
-
from: "alice@example.com",
|
|
652
|
-
to: [email3],
|
|
653
|
-
subject: "Production export is stuck",
|
|
654
|
-
text: "Our production export has been stuck for an hour. Can you investigate?",
|
|
655
|
-
date: "2026-07-19T12:00:00.000Z",
|
|
656
|
-
messageId: "support-001@example.com",
|
|
657
|
-
labels: ["INBOX", "UNREAD"]
|
|
658
|
-
}
|
|
659
|
-
],
|
|
660
|
-
drafts: [
|
|
661
|
-
{
|
|
662
|
-
id: "draft_ack",
|
|
663
|
-
threadId: "thread_draft_ack",
|
|
664
|
-
to: ["bob@example.com"],
|
|
665
|
-
subject: "Draft acknowledgment",
|
|
666
|
-
text: "Thanks \u2014 I'll follow up shortly.",
|
|
667
|
-
date: "2026-07-19T13:00:00.000Z",
|
|
668
|
-
messageId: "draft-ack@pome-twin.test"
|
|
669
|
-
}
|
|
670
|
-
],
|
|
671
|
-
filters: [],
|
|
672
|
-
forwardingAddresses: [],
|
|
673
|
-
sendAs: []
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
function defaultSeedState() {
|
|
677
|
-
return {
|
|
678
|
-
primaryMailbox: agentPathInboxMailbox(),
|
|
679
|
-
mailboxes: [],
|
|
680
|
-
deliveryMode: "sender-only",
|
|
681
|
-
clock: "2026-07-20T00:00:00.000Z"
|
|
682
|
-
};
|
|
683
|
-
}
|
|
9
|
+
import { z } from 'zod';
|
|
684
10
|
|
|
685
11
|
// ../packages/twin-gmail/dist/src/db.js
|
|
686
12
|
var MIGRATION_SQL = `
|
|
@@ -1104,7 +430,7 @@ var MAX_RAW_BYTES = 36700160;
|
|
|
1104
430
|
var MAX_HEADERS = 1e3;
|
|
1105
431
|
var MAX_HEADER_BYTES = 256 * 1024;
|
|
1106
432
|
var MAX_PARTS = 500;
|
|
1107
|
-
var
|
|
433
|
+
var MAX_DEPTH = 20;
|
|
1108
434
|
function canonicalRaw(input) {
|
|
1109
435
|
const raw = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input);
|
|
1110
436
|
if (raw.length === 0)
|
|
@@ -1263,7 +589,7 @@ function parseHeaders(raw) {
|
|
|
1263
589
|
});
|
|
1264
590
|
}
|
|
1265
591
|
function parsePart(body, headers2, contentType, state, depth) {
|
|
1266
|
-
if (depth >
|
|
592
|
+
if (depth > MAX_DEPTH || ++state.parts > MAX_PARTS)
|
|
1267
593
|
invalidArgument("MIME nesting/part limit exceeded");
|
|
1268
594
|
if (contentType.type.startsWith("multipart/")) {
|
|
1269
595
|
const boundary = contentType.params.boundary;
|
|
@@ -1589,16 +915,16 @@ var SYSTEM_LABELS = [
|
|
|
1589
915
|
["CATEGORY_UPDATES", "CATEGORY_UPDATES"],
|
|
1590
916
|
["CATEGORY_FORUMS", "CATEGORY_FORUMS"]
|
|
1591
917
|
];
|
|
1592
|
-
function createMailbox(db,
|
|
1593
|
-
const result = db.prepare("INSERT INTO mailboxes(email, display_name, created_at) VALUES (?, ?, ?)").run(
|
|
918
|
+
function createMailbox(db, email2, displayName, createdAt) {
|
|
919
|
+
const result = db.prepare("INSERT INTO mailboxes(email, display_name, created_at) VALUES (?, ?, ?)").run(email2, displayName, createdAt);
|
|
1594
920
|
const mailboxId = Number(result.lastInsertRowid);
|
|
1595
921
|
db.prepare("INSERT INTO mailbox_counters(mailbox_id) VALUES (?)").run(mailboxId);
|
|
1596
|
-
for (const [
|
|
1597
|
-
db.prepare("INSERT INTO labels(mailbox_id, id, name, type, text_color, background_color) VALUES (?, ?, ?, 'system', NULL, NULL)").run(mailboxId,
|
|
922
|
+
for (const [id, name] of SYSTEM_LABELS) {
|
|
923
|
+
db.prepare("INSERT INTO labels(mailbox_id, id, name, type, text_color, background_color) VALUES (?, ?, ?, 'system', NULL, NULL)").run(mailboxId, id, name);
|
|
1598
924
|
}
|
|
1599
925
|
db.prepare(`INSERT INTO send_as(
|
|
1600
926
|
mailbox_id, email, display_name, is_primary, is_default, verification_status
|
|
1601
|
-
) VALUES (?, ?, ?, 1, 1, 'accepted')`).run(mailboxId,
|
|
927
|
+
) VALUES (?, ?, ?, 1, 1, 'accepted')`).run(mailboxId, email2, displayName);
|
|
1602
928
|
return mailboxId;
|
|
1603
929
|
}
|
|
1604
930
|
function nextId(db, mailboxId, counter, prefix) {
|
|
@@ -1612,17 +938,17 @@ function nextTimestamp(db, mailboxId) {
|
|
|
1612
938
|
const config = db.prepare("SELECT value FROM gmail_config WHERE key = 'clock'").get();
|
|
1613
939
|
return new Date(Date.parse(config.value) + row.logical_clock * 1e3).toISOString();
|
|
1614
940
|
}
|
|
1615
|
-
function addHistory(db, mailboxId,
|
|
1616
|
-
const
|
|
1617
|
-
const numeric = Number.parseInt(
|
|
941
|
+
function addHistory(db, mailboxId, messageId, threadId, eventType, labelIds2 = []) {
|
|
942
|
+
const id = nextId(db, mailboxId, "history_counter", "history").slice("history_".length);
|
|
943
|
+
const numeric = Number.parseInt(id, 16);
|
|
1618
944
|
const timestamp = nextTimestamp(db, mailboxId);
|
|
1619
945
|
db.prepare(`INSERT INTO history(mailbox_id, id, message_id, thread_id, event_type, label_ids_json, created_at)
|
|
1620
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(mailboxId, numeric,
|
|
946
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(mailboxId, numeric, messageId, threadId, eventType, JSON.stringify([...labelIds2].sort()), timestamp);
|
|
1621
947
|
return String(numeric);
|
|
1622
948
|
}
|
|
1623
949
|
function insertStoredMessage(db, mailboxId, raw, options = {}) {
|
|
1624
950
|
const parsed = parseMime(raw);
|
|
1625
|
-
const
|
|
951
|
+
const messageId = options.id ?? nextId(db, mailboxId, "message_counter", "msg");
|
|
1626
952
|
const subjectKey = normalizeSubject(parsed.subject);
|
|
1627
953
|
const threadId = options.forceThreadId && options.threadId ? options.threadId : resolveThread(db, mailboxId, options.threadId, subjectKey, [
|
|
1628
954
|
...parsed.inReplyTo ? [parsed.inReplyTo] : [],
|
|
@@ -1639,32 +965,32 @@ function insertStoredMessage(db, mailboxId, raw, options = {}) {
|
|
|
1639
965
|
mailbox_id, id, thread_id, rfc_message_id, internal_date, sent_at,
|
|
1640
966
|
from_address, to_json, cc_json, bcc_json, delivered_to, subject,
|
|
1641
967
|
normalized_subject, snippet, text_body, html_body, headers_json, size_estimate
|
|
1642
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(mailboxId,
|
|
1643
|
-
db.prepare("INSERT INTO message_blobs(mailbox_id, message_id, raw, sha256, size) VALUES (?, ?, ?, ?, ?)").run(mailboxId,
|
|
968
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(mailboxId, messageId, threadId, parsed.messageId || `${messageId}@pome-twin.test`, internalDate, date, parsed.from, JSON.stringify(parsed.to), JSON.stringify(parsed.cc), JSON.stringify(parsed.bcc), options.deliveredTo ?? parsed.deliveredTo, parsed.subject, subjectKey, snippet, parsed.text, parsed.html, JSON.stringify(parsed.headers), raw.byteLength);
|
|
969
|
+
db.prepare("INSERT INTO message_blobs(mailbox_id, message_id, raw, sha256, size) VALUES (?, ?, ?, ?, ?)").run(mailboxId, messageId, Buffer.from(raw), mimeSha256(raw), raw.byteLength);
|
|
1644
970
|
parsed.attachments.forEach((attachment2, index) => {
|
|
1645
971
|
const attachmentId = nextId(db, mailboxId, "attachment_counter", "att");
|
|
1646
972
|
db.prepare(`INSERT INTO attachments(
|
|
1647
973
|
mailbox_id, message_id, id, part_index, filename, mime_type, disposition,
|
|
1648
974
|
content_id, sha256, size, data
|
|
1649
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(mailboxId,
|
|
975
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(mailboxId, messageId, attachmentId, index, attachment2.filename, attachment2.mimeType, attachment2.disposition, attachment2.contentId ?? null, mimeSha256(attachment2.data), attachment2.data.byteLength, attachment2.data);
|
|
1650
976
|
});
|
|
1651
977
|
const labels2 = options.draft ? ["DRAFT"] : [...new Set(options.labels ?? [])];
|
|
1652
978
|
assertLabels(db, mailboxId, labels2);
|
|
1653
979
|
for (const label2 of labels2) {
|
|
1654
|
-
db.prepare("INSERT INTO message_labels(mailbox_id, message_id, label_id) VALUES (?, ?, ?)").run(mailboxId,
|
|
980
|
+
db.prepare("INSERT INTO message_labels(mailbox_id, message_id, label_id) VALUES (?, ?, ?)").run(mailboxId, messageId, label2);
|
|
1655
981
|
}
|
|
1656
982
|
db.prepare("UPDATE threads SET updated_at = ? WHERE mailbox_id = ? AND id = ?").run(date, mailboxId, threadId);
|
|
1657
983
|
if (options.recordHistory !== false)
|
|
1658
|
-
addHistory(db, mailboxId,
|
|
1659
|
-
return semanticMessage(db, mailboxId,
|
|
984
|
+
addHistory(db, mailboxId, messageId, threadId, "messageAdded", labels2);
|
|
985
|
+
return semanticMessage(db, mailboxId, messageId);
|
|
1660
986
|
}
|
|
1661
|
-
function semanticMessage(db, mailboxId,
|
|
1662
|
-
const row = db.prepare("SELECT * FROM messages WHERE mailbox_id = ? AND id = ?").get(mailboxId,
|
|
987
|
+
function semanticMessage(db, mailboxId, messageId) {
|
|
988
|
+
const row = db.prepare("SELECT * FROM messages WHERE mailbox_id = ? AND id = ?").get(mailboxId, messageId);
|
|
1663
989
|
if (!row)
|
|
1664
990
|
notFound("Message");
|
|
1665
|
-
const labels2 = db.prepare("SELECT label_id FROM message_labels WHERE mailbox_id = ? AND message_id = ? ORDER BY label_id").all(mailboxId,
|
|
991
|
+
const labels2 = db.prepare("SELECT label_id FROM message_labels WHERE mailbox_id = ? AND message_id = ? ORDER BY label_id").all(mailboxId, messageId);
|
|
1666
992
|
const attachments = db.prepare(`SELECT id, filename, mime_type, disposition, content_id, sha256, size
|
|
1667
|
-
FROM attachments WHERE mailbox_id = ? AND message_id = ? ORDER BY part_index`).all(mailboxId,
|
|
993
|
+
FROM attachments WHERE mailbox_id = ? AND message_id = ? ORDER BY part_index`).all(mailboxId, messageId);
|
|
1668
994
|
return {
|
|
1669
995
|
id: row.id,
|
|
1670
996
|
threadId: row.thread_id,
|
|
@@ -1691,8 +1017,8 @@ function semanticMessage(db, mailboxId, messageId2) {
|
|
|
1691
1017
|
}))
|
|
1692
1018
|
};
|
|
1693
1019
|
}
|
|
1694
|
-
function rawMessage(db, mailboxId,
|
|
1695
|
-
const row = db.prepare("SELECT raw FROM message_blobs WHERE mailbox_id = ? AND message_id = ?").get(mailboxId,
|
|
1020
|
+
function rawMessage(db, mailboxId, messageId) {
|
|
1021
|
+
const row = db.prepare("SELECT raw FROM message_blobs WHERE mailbox_id = ? AND message_id = ?").get(mailboxId, messageId);
|
|
1696
1022
|
if (!row)
|
|
1697
1023
|
notFound("Message");
|
|
1698
1024
|
return Buffer.from(row.raw);
|
|
@@ -1733,37 +1059,37 @@ function seedMailbox(db, mailbox) {
|
|
|
1733
1059
|
db.prepare("INSERT INTO labels(mailbox_id, id, name, type, text_color, background_color) VALUES (?, ?, ?, 'user', ?, ?)").run(mailboxId, labelId, label2.name, label2.color?.textColor ?? null, label2.color?.backgroundColor ?? null);
|
|
1734
1060
|
labelByName.set(label2.name.toLowerCase(), labelId);
|
|
1735
1061
|
}
|
|
1736
|
-
for (const
|
|
1737
|
-
if (
|
|
1062
|
+
for (const message of mailbox.messages) {
|
|
1063
|
+
if (message.id)
|
|
1738
1064
|
nextId(db, mailboxId, "message_counter", "msg");
|
|
1739
|
-
if (
|
|
1065
|
+
if (message.threadId)
|
|
1740
1066
|
nextId(db, mailboxId, "thread_counter", "thread");
|
|
1741
|
-
const raw = seedRaw(db, mailbox,
|
|
1742
|
-
const labels2 =
|
|
1067
|
+
const raw = seedRaw(db, mailbox, message, mailboxId);
|
|
1068
|
+
const labels2 = message.labels.map((label2) => labelByName.get(label2.toLowerCase()) ?? label2);
|
|
1743
1069
|
insertStoredMessage(db, mailboxId, raw, {
|
|
1744
|
-
id:
|
|
1745
|
-
threadId:
|
|
1070
|
+
id: message.id,
|
|
1071
|
+
threadId: message.threadId,
|
|
1746
1072
|
labels: labels2,
|
|
1747
1073
|
recordHistory: false,
|
|
1748
|
-
forceThreadId:
|
|
1074
|
+
forceThreadId: message.threadId !== void 0
|
|
1749
1075
|
});
|
|
1750
1076
|
}
|
|
1751
|
-
for (const
|
|
1752
|
-
if (
|
|
1077
|
+
for (const draft2 of mailbox.drafts) {
|
|
1078
|
+
if (draft2.id)
|
|
1753
1079
|
nextId(db, mailboxId, "draft_counter", "draft");
|
|
1754
|
-
if (
|
|
1080
|
+
if (draft2.threadId)
|
|
1755
1081
|
nextId(db, mailboxId, "thread_counter", "thread");
|
|
1756
|
-
const raw = seedRaw(db, mailbox,
|
|
1757
|
-
const
|
|
1758
|
-
id:
|
|
1759
|
-
threadId:
|
|
1082
|
+
const raw = seedRaw(db, mailbox, draft2, mailboxId);
|
|
1083
|
+
const message = insertStoredMessage(db, mailboxId, raw, {
|
|
1084
|
+
id: draft2.id ? `${draft2.id}_message` : void 0,
|
|
1085
|
+
threadId: draft2.threadId,
|
|
1760
1086
|
draft: true,
|
|
1761
1087
|
recordHistory: false,
|
|
1762
|
-
forceThreadId:
|
|
1088
|
+
forceThreadId: draft2.threadId !== void 0
|
|
1763
1089
|
});
|
|
1764
|
-
const draftId =
|
|
1765
|
-
const date = new Date(
|
|
1766
|
-
db.prepare("INSERT INTO drafts(mailbox_id, id, message_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(mailboxId, draftId,
|
|
1090
|
+
const draftId = draft2.id ?? nextId(db, mailboxId, "draft_counter", "draft");
|
|
1091
|
+
const date = new Date(message.internalDate).toISOString();
|
|
1092
|
+
db.prepare("INSERT INTO drafts(mailbox_id, id, message_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(mailboxId, draftId, message.id, date, date);
|
|
1767
1093
|
}
|
|
1768
1094
|
seedSettings(db, mailboxId, mailbox);
|
|
1769
1095
|
}
|
|
@@ -1779,29 +1105,29 @@ function seedSettings(db, mailboxId, mailbox) {
|
|
|
1779
1105
|
for (const filter2 of mailbox.filters) {
|
|
1780
1106
|
if (filter2.id)
|
|
1781
1107
|
nextId(db, mailboxId, "filter_counter", "filter");
|
|
1782
|
-
const
|
|
1783
|
-
db.prepare("INSERT INTO filters(mailbox_id, id, criteria_json, action_json) VALUES (?, ?, ?, ?)").run(mailboxId,
|
|
1108
|
+
const id = filter2.id ?? nextId(db, mailboxId, "filter_counter", "filter");
|
|
1109
|
+
db.prepare("INSERT INTO filters(mailbox_id, id, criteria_json, action_json) VALUES (?, ?, ?, ?)").run(mailboxId, id, JSON.stringify(filter2.criteria), JSON.stringify(filter2.action));
|
|
1784
1110
|
}
|
|
1785
1111
|
}
|
|
1786
|
-
function seedRaw(db, mailbox,
|
|
1787
|
-
if (
|
|
1788
|
-
return
|
|
1112
|
+
function seedRaw(db, mailbox, message, mailboxId) {
|
|
1113
|
+
if (message.raw !== void 0) {
|
|
1114
|
+
return message.raw.includes("\n") ? Buffer.from(message.raw, "utf8") : decodeGmailRaw(message.raw);
|
|
1789
1115
|
}
|
|
1790
|
-
const date =
|
|
1791
|
-
const
|
|
1116
|
+
const date = message.date ?? nextTimestamp(db, mailboxId);
|
|
1117
|
+
const id = message.messageId ?? `${nextId(db, mailboxId, "message_counter", "rfc")}@pome-twin.test`;
|
|
1792
1118
|
return composeMime({
|
|
1793
|
-
from:
|
|
1794
|
-
to:
|
|
1795
|
-
cc:
|
|
1796
|
-
bcc:
|
|
1797
|
-
subject:
|
|
1798
|
-
text:
|
|
1799
|
-
html:
|
|
1119
|
+
from: message.from ?? mailbox.email,
|
|
1120
|
+
to: message.to,
|
|
1121
|
+
cc: message.cc,
|
|
1122
|
+
bcc: message.bcc,
|
|
1123
|
+
subject: message.subject,
|
|
1124
|
+
text: message.text,
|
|
1125
|
+
html: message.html,
|
|
1800
1126
|
date,
|
|
1801
|
-
messageId:
|
|
1802
|
-
inReplyTo:
|
|
1803
|
-
references:
|
|
1804
|
-
attachments:
|
|
1127
|
+
messageId: id,
|
|
1128
|
+
inReplyTo: message.inReplyTo,
|
|
1129
|
+
references: message.references,
|
|
1130
|
+
attachments: message.attachments
|
|
1805
1131
|
});
|
|
1806
1132
|
}
|
|
1807
1133
|
|
|
@@ -1814,7 +1140,7 @@ function batchSemanticMessages(db, mailboxId, messageIds2) {
|
|
|
1814
1140
|
WHERE mailbox_id = ? AND id IN (${placeholders})`).all(mailboxId, ...messageIds2);
|
|
1815
1141
|
if (rows2.length !== messageIds2.length) {
|
|
1816
1142
|
const found = new Set(rows2.map((row) => row.id));
|
|
1817
|
-
const missing = messageIds2.find((
|
|
1143
|
+
const missing = messageIds2.find((id) => !found.has(id));
|
|
1818
1144
|
if (missing)
|
|
1819
1145
|
notFound("Message");
|
|
1820
1146
|
}
|
|
@@ -1846,8 +1172,8 @@ function batchSemanticMessages(db, mailboxId, messageIds2) {
|
|
|
1846
1172
|
});
|
|
1847
1173
|
attachmentsByMessage.set(row.message_id, list);
|
|
1848
1174
|
}
|
|
1849
|
-
return messageIds2.map((
|
|
1850
|
-
const row = byId.get(
|
|
1175
|
+
return messageIds2.map((id) => {
|
|
1176
|
+
const row = byId.get(id);
|
|
1851
1177
|
return {
|
|
1852
1178
|
id: row.id,
|
|
1853
1179
|
threadId: row.thread_id,
|
|
@@ -1862,15 +1188,15 @@ function batchSemanticMessages(db, mailboxId, messageIds2) {
|
|
|
1862
1188
|
text: row.text_body,
|
|
1863
1189
|
html: row.html_body,
|
|
1864
1190
|
sizeEstimate: row.size_estimate,
|
|
1865
|
-
labelIds: labelsByMessage.get(
|
|
1866
|
-
attachments: attachmentsByMessage.get(
|
|
1191
|
+
labelIds: labelsByMessage.get(id) ?? [],
|
|
1192
|
+
attachments: attachmentsByMessage.get(id) ?? []
|
|
1867
1193
|
};
|
|
1868
1194
|
});
|
|
1869
1195
|
}
|
|
1870
1196
|
function batchSearchDocuments(db, mailboxId, messages) {
|
|
1871
1197
|
if (!messages.length)
|
|
1872
1198
|
return [];
|
|
1873
|
-
const ids = messages.map((
|
|
1199
|
+
const ids = messages.map((message) => message.id);
|
|
1874
1200
|
const placeholders = ids.map(() => "?").join(", ");
|
|
1875
1201
|
const metaRows = db.prepare(`SELECT id, delivered_to, headers_json FROM messages
|
|
1876
1202
|
WHERE mailbox_id = ? AND id IN (${placeholders})`).all(mailboxId, ...ids);
|
|
@@ -1881,30 +1207,30 @@ function batchSearchDocuments(db, mailboxId, messages) {
|
|
|
1881
1207
|
WHERE ml.mailbox_id = ? AND ml.message_id IN (${placeholders}) AND l.type = 'user'
|
|
1882
1208
|
GROUP BY ml.message_id`).all(mailboxId, ...ids);
|
|
1883
1209
|
const userLabelCount = new Map(userLabelRows.map((row) => [row.message_id, row.count]));
|
|
1884
|
-
return messages.map((
|
|
1885
|
-
const meta = metaById.get(
|
|
1210
|
+
return messages.map((message) => {
|
|
1211
|
+
const meta = metaById.get(message.id);
|
|
1886
1212
|
return {
|
|
1887
|
-
from:
|
|
1888
|
-
to:
|
|
1889
|
-
cc:
|
|
1890
|
-
bcc:
|
|
1213
|
+
from: message.from.toLowerCase(),
|
|
1214
|
+
to: message.to.map((item) => item.toLowerCase()),
|
|
1215
|
+
cc: message.cc.map((item) => item.toLowerCase()),
|
|
1216
|
+
bcc: message.bcc.map((item) => item.toLowerCase()),
|
|
1891
1217
|
deliveredTo: meta.delivered_to.toLowerCase(),
|
|
1892
|
-
subject:
|
|
1893
|
-
text:
|
|
1894
|
-
html:
|
|
1895
|
-
dateMs:
|
|
1896
|
-
rfcMessageId:
|
|
1897
|
-
size:
|
|
1898
|
-
labels:
|
|
1899
|
-
userLabelCount: userLabelCount.get(
|
|
1900
|
-
attachmentNames:
|
|
1901
|
-
attachmentMimeTypes:
|
|
1218
|
+
subject: message.subject,
|
|
1219
|
+
text: message.text,
|
|
1220
|
+
html: message.html,
|
|
1221
|
+
dateMs: message.internalDate,
|
|
1222
|
+
rfcMessageId: message.rfcMessageId,
|
|
1223
|
+
size: message.sizeEstimate,
|
|
1224
|
+
labels: message.labelIds,
|
|
1225
|
+
userLabelCount: userLabelCount.get(message.id) ?? 0,
|
|
1226
|
+
attachmentNames: message.attachments.map((attachment2) => attachment2.filename),
|
|
1227
|
+
attachmentMimeTypes: message.attachments.map((attachment2) => attachment2.mimeType),
|
|
1902
1228
|
headers: JSON.parse(meta.headers_json)
|
|
1903
1229
|
};
|
|
1904
1230
|
});
|
|
1905
1231
|
}
|
|
1906
|
-
function searchDocument(db, mailboxId,
|
|
1907
|
-
return batchSearchDocuments(db, mailboxId, [
|
|
1232
|
+
function searchDocument(db, mailboxId, message) {
|
|
1233
|
+
return batchSearchDocuments(db, mailboxId, [message])[0];
|
|
1908
1234
|
}
|
|
1909
1235
|
function searchNow(db) {
|
|
1910
1236
|
const clock = db.prepare("SELECT value FROM gmail_config WHERE key = 'clock'").get();
|
|
@@ -1913,56 +1239,56 @@ function searchNow(db) {
|
|
|
1913
1239
|
}
|
|
1914
1240
|
|
|
1915
1241
|
// ../packages/twin-gmail/dist/src/domain/filters.js
|
|
1916
|
-
function applyInboundFilters(db, mailboxId,
|
|
1242
|
+
function applyInboundFilters(db, mailboxId, messageId) {
|
|
1917
1243
|
const filters2 = db.prepare("SELECT criteria_json, action_json FROM filters WHERE mailbox_id = ? ORDER BY id").all(mailboxId);
|
|
1918
1244
|
for (const row of filters2) {
|
|
1919
1245
|
const criteria = JSON.parse(row.criteria_json);
|
|
1920
1246
|
const action = JSON.parse(row.action_json);
|
|
1921
1247
|
if (action.forward)
|
|
1922
1248
|
unsupported("Filter forwarding is not implemented");
|
|
1923
|
-
const
|
|
1924
|
-
const document = searchDocument(db, mailboxId,
|
|
1249
|
+
const message = semanticMessage(db, mailboxId, messageId);
|
|
1250
|
+
const document = searchDocument(db, mailboxId, message);
|
|
1925
1251
|
const criteriaTo = typeof criteria.to === "string" ? criteria.to.toLowerCase() : void 0;
|
|
1926
1252
|
const now = searchNow(db);
|
|
1927
1253
|
const matches = (typeof criteria.from !== "string" || document.from.includes(criteria.from.toLowerCase())) && (criteriaTo === void 0 || document.to.some((to) => to.includes(criteriaTo))) && (typeof criteria.subject !== "string" || document.subject.toLowerCase().includes(criteria.subject.toLowerCase())) && (criteria.hasAttachment !== true || document.attachmentNames.length > 0) && (typeof criteria.size !== "number" || (criteria.sizeComparison === "smaller" ? document.size < criteria.size : document.size > criteria.size)) && (typeof criteria.query !== "string" || matchesSearch(parseSearchQuery(criteria.query), document, now)) && (typeof criteria.negatedQuery !== "string" || !matchesSearch(parseSearchQuery(criteria.negatedQuery), document, now));
|
|
1928
1254
|
if (!matches)
|
|
1929
1255
|
continue;
|
|
1930
1256
|
assertLabels(db, mailboxId, [...action.addLabelIds ?? [], ...action.removeLabelIds ?? []]);
|
|
1931
|
-
const beforeLabels = new Set(
|
|
1257
|
+
const beforeLabels = new Set(message.labelIds);
|
|
1932
1258
|
const removed = [];
|
|
1933
1259
|
const added = [];
|
|
1934
1260
|
for (const label2 of action.removeLabelIds ?? []) {
|
|
1935
|
-
const result = db.prepare("DELETE FROM message_labels WHERE mailbox_id = ? AND message_id = ? AND label_id = ?").run(mailboxId,
|
|
1261
|
+
const result = db.prepare("DELETE FROM message_labels WHERE mailbox_id = ? AND message_id = ? AND label_id = ?").run(mailboxId, messageId, label2);
|
|
1936
1262
|
if (result.changes > 0 && beforeLabels.has(label2))
|
|
1937
1263
|
removed.push(label2);
|
|
1938
1264
|
}
|
|
1939
1265
|
for (const label2 of action.addLabelIds ?? []) {
|
|
1940
|
-
const result = db.prepare("INSERT OR IGNORE INTO message_labels(mailbox_id, message_id, label_id) VALUES (?, ?, ?)").run(mailboxId,
|
|
1266
|
+
const result = db.prepare("INSERT OR IGNORE INTO message_labels(mailbox_id, message_id, label_id) VALUES (?, ?, ?)").run(mailboxId, messageId, label2);
|
|
1941
1267
|
if (result.changes > 0 && !beforeLabels.has(label2))
|
|
1942
1268
|
added.push(label2);
|
|
1943
1269
|
}
|
|
1944
1270
|
if (added.length)
|
|
1945
|
-
addHistory(db, mailboxId,
|
|
1271
|
+
addHistory(db, mailboxId, messageId, message.threadId, "labelAdded", added);
|
|
1946
1272
|
if (removed.length)
|
|
1947
|
-
addHistory(db, mailboxId,
|
|
1273
|
+
addHistory(db, mailboxId, messageId, message.threadId, "labelRemoved", removed);
|
|
1948
1274
|
}
|
|
1949
1275
|
}
|
|
1950
|
-
function filters(domain,
|
|
1951
|
-
const mailboxId = domain.mailboxId(
|
|
1276
|
+
function filters(domain, email2) {
|
|
1277
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1952
1278
|
const rows2 = domain.db.prepare("SELECT id, criteria_json, action_json FROM filters WHERE mailbox_id = ? ORDER BY id").all(mailboxId);
|
|
1953
1279
|
return rows2.map(toFilter);
|
|
1954
1280
|
}
|
|
1955
|
-
function filter(domain,
|
|
1956
|
-
const mailboxId = domain.mailboxId(
|
|
1281
|
+
function filter(domain, email2, filterId) {
|
|
1282
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1957
1283
|
const row = domain.db.prepare("SELECT id, criteria_json, action_json FROM filters WHERE mailbox_id = ? AND id = ?").get(mailboxId, filterId);
|
|
1958
1284
|
if (!row)
|
|
1959
1285
|
notFound("Filter");
|
|
1960
1286
|
return toFilter(row);
|
|
1961
1287
|
}
|
|
1962
|
-
function createFilter(domain,
|
|
1288
|
+
function createFilter(domain, email2, criteria = {}, action = {}) {
|
|
1963
1289
|
if (action.forward)
|
|
1964
1290
|
unsupported("Filter action.forward is not supported by the Gmail twin");
|
|
1965
|
-
const mailboxId = domain.mailboxId(
|
|
1291
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1966
1292
|
const count = domain.db.prepare("SELECT COUNT(*) AS count FROM filters WHERE mailbox_id = ?").get(mailboxId);
|
|
1967
1293
|
if (count.count >= 1e3)
|
|
1968
1294
|
invalidArgument("Filter limit exceeded");
|
|
@@ -1971,12 +1297,12 @@ function createFilter(domain, email3, criteria = {}, action = {}) {
|
|
|
1971
1297
|
validateSearchQuery(criteria.query);
|
|
1972
1298
|
if (criteria.negatedQuery)
|
|
1973
1299
|
validateSearchQuery(criteria.negatedQuery);
|
|
1974
|
-
const
|
|
1975
|
-
domain.db.prepare("INSERT INTO filters(mailbox_id, id, criteria_json, action_json) VALUES (?, ?, ?, ?)").run(mailboxId,
|
|
1976
|
-
return { id
|
|
1300
|
+
const id = nextId(domain.db, mailboxId, "filter_counter", "filter");
|
|
1301
|
+
domain.db.prepare("INSERT INTO filters(mailbox_id, id, criteria_json, action_json) VALUES (?, ?, ?, ?)").run(mailboxId, id, JSON.stringify(criteria), JSON.stringify(action));
|
|
1302
|
+
return { id, criteria, action };
|
|
1977
1303
|
}
|
|
1978
|
-
function deleteFilter(domain,
|
|
1979
|
-
const mailboxId = domain.mailboxId(
|
|
1304
|
+
function deleteFilter(domain, email2, filterId) {
|
|
1305
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1980
1306
|
const result = domain.db.prepare("DELETE FROM filters WHERE mailbox_id = ? AND id = ?").run(mailboxId, filterId);
|
|
1981
1307
|
if (result.changes === 0)
|
|
1982
1308
|
notFound("Filter");
|
|
@@ -2159,47 +1485,47 @@ function pushNotHasLabel(clauses, params, labelId) {
|
|
|
2159
1485
|
}
|
|
2160
1486
|
|
|
2161
1487
|
// ../packages/twin-gmail/dist/src/domain/messages.js
|
|
2162
|
-
function getRaw(domain,
|
|
2163
|
-
return rawMessage(domain.db, domain.mailboxId(
|
|
1488
|
+
function getRaw(domain, email2, messageId) {
|
|
1489
|
+
return rawMessage(domain.db, domain.mailboxId(email2), messageId);
|
|
2164
1490
|
}
|
|
2165
|
-
function getMessage(domain,
|
|
2166
|
-
return semanticMessage(domain.db, domain.mailboxId(
|
|
1491
|
+
function getMessage(domain, email2, messageId) {
|
|
1492
|
+
return semanticMessage(domain.db, domain.mailboxId(email2), messageId);
|
|
2167
1493
|
}
|
|
2168
|
-
function getThread(domain,
|
|
2169
|
-
const mailboxId = domain.mailboxId(
|
|
1494
|
+
function getThread(domain, email2, threadId) {
|
|
1495
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2170
1496
|
const rows2 = domain.db.prepare("SELECT id FROM messages WHERE mailbox_id = ? AND thread_id = ? ORDER BY internal_date, id").all(mailboxId, threadId);
|
|
2171
1497
|
if (!rows2.length)
|
|
2172
1498
|
notFound("Thread");
|
|
2173
1499
|
const messages = batchSemanticMessages(domain.db, mailboxId, rows2.map((row) => row.id));
|
|
2174
1500
|
return {
|
|
2175
1501
|
id: threadId,
|
|
2176
|
-
labelIds: [...new Set(messages.flatMap((
|
|
1502
|
+
labelIds: [...new Set(messages.flatMap((message) => message.labelIds))].sort(),
|
|
2177
1503
|
messages
|
|
2178
1504
|
};
|
|
2179
1505
|
}
|
|
2180
|
-
function insertMessage(domain,
|
|
2181
|
-
const mailboxId = domain.mailboxId(
|
|
1506
|
+
function insertMessage(domain, email2, raw, options = {}) {
|
|
1507
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2182
1508
|
const bytes = acceptedRaw(raw);
|
|
2183
1509
|
return domain.db.transaction(() => {
|
|
2184
|
-
const
|
|
1510
|
+
const message = insertStoredMessage(domain.db, mailboxId, bytes, {
|
|
2185
1511
|
threadId: options.threadId,
|
|
2186
1512
|
labels: options.labels ?? (options.incoming ? ["INBOX"] : []),
|
|
2187
|
-
deliveredTo: options.incoming ?
|
|
1513
|
+
deliveredTo: options.incoming ? email2 : void 0
|
|
2188
1514
|
});
|
|
2189
1515
|
if (options.incoming)
|
|
2190
|
-
applyInboundFilters(domain.db, mailboxId,
|
|
2191
|
-
return semanticMessage(domain.db, mailboxId,
|
|
1516
|
+
applyInboundFilters(domain.db, mailboxId, message.id);
|
|
1517
|
+
return semanticMessage(domain.db, mailboxId, message.id);
|
|
2192
1518
|
}).immediate();
|
|
2193
1519
|
}
|
|
2194
|
-
function sendMessage(domain,
|
|
1520
|
+
function sendMessage(domain, email2, raw, options = {}) {
|
|
2195
1521
|
checkFault(domain.db, "messages.send");
|
|
2196
|
-
const senderMailboxId = domain.mailboxId(
|
|
1522
|
+
const senderMailboxId = domain.mailboxId(email2);
|
|
2197
1523
|
const bytes = acceptedRaw(raw);
|
|
2198
1524
|
const parsed = parseMime(bytes);
|
|
2199
1525
|
assertAcceptedFrom(domain, senderMailboxId, parsed.from);
|
|
2200
1526
|
return domain.db.transaction(() => {
|
|
2201
1527
|
const recipientEmails = [...new Set([...parsed.to, ...parsed.cc, ...parsed.bcc].map((item) => item.toLowerCase()))];
|
|
2202
|
-
const selfDelivery = deliveryMode(domain) === "seeded-mailboxes" && recipientEmails.includes(
|
|
1528
|
+
const selfDelivery = deliveryMode(domain) === "seeded-mailboxes" && recipientEmails.includes(email2.toLowerCase());
|
|
2203
1529
|
const sender = insertStoredMessage(domain.db, senderMailboxId, bytes, {
|
|
2204
1530
|
threadId: options.threadId,
|
|
2205
1531
|
labels: selfDelivery ? ["INBOX", "SENT"] : ["SENT"]
|
|
@@ -2210,7 +1536,7 @@ function sendMessage(domain, email3, raw, options = {}) {
|
|
|
2210
1536
|
if (deliveryMode(domain) === "seeded-mailboxes") {
|
|
2211
1537
|
const visibleRaw = stripBcc(bytes);
|
|
2212
1538
|
for (const recipient of recipientEmails) {
|
|
2213
|
-
if (recipient ===
|
|
1539
|
+
if (recipient === email2.toLowerCase())
|
|
2214
1540
|
continue;
|
|
2215
1541
|
const mailbox = domain.db.prepare("SELECT id, email FROM mailboxes WHERE email = ? COLLATE NOCASE").get(recipient);
|
|
2216
1542
|
if (!mailbox)
|
|
@@ -2226,11 +1552,11 @@ function sendMessage(domain, email3, raw, options = {}) {
|
|
|
2226
1552
|
return { sender: semanticMessage(domain.db, senderMailboxId, sender.id), deliveries };
|
|
2227
1553
|
}).immediate();
|
|
2228
1554
|
}
|
|
2229
|
-
function modifyMessageLabels(domain,
|
|
2230
|
-
const mailboxId = domain.mailboxId(
|
|
1555
|
+
function modifyMessageLabels(domain, email2, messageId, add = [], remove = []) {
|
|
1556
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2231
1557
|
return domain.db.transaction(() => {
|
|
2232
|
-
const before = semanticMessage(domain.db, mailboxId,
|
|
2233
|
-
if (before.labelIds.includes("DRAFT") && (add.some((
|
|
1558
|
+
const before = semanticMessage(domain.db, mailboxId, messageId);
|
|
1559
|
+
if (before.labelIds.includes("DRAFT") && (add.some((id) => id !== "DRAFT") || remove.includes("DRAFT"))) {
|
|
2234
1560
|
invalidArgument("Draft messages may only carry the DRAFT label");
|
|
2235
1561
|
}
|
|
2236
1562
|
if (!before.labelIds.includes("DRAFT") && add.includes("DRAFT")) {
|
|
@@ -2246,87 +1572,87 @@ function modifyMessageLabels(domain, email3, messageId2, add = [], remove = [])
|
|
|
2246
1572
|
const actualRemove = [...removeSet].filter((label2) => before.labelIds.includes(label2));
|
|
2247
1573
|
const actualAdd = [...addSet].filter((label2) => !before.labelIds.includes(label2));
|
|
2248
1574
|
for (const label2 of actualRemove) {
|
|
2249
|
-
domain.db.prepare("DELETE FROM message_labels WHERE mailbox_id = ? AND message_id = ? AND label_id = ?").run(mailboxId,
|
|
1575
|
+
domain.db.prepare("DELETE FROM message_labels WHERE mailbox_id = ? AND message_id = ? AND label_id = ?").run(mailboxId, messageId, label2);
|
|
2250
1576
|
}
|
|
2251
1577
|
for (const label2 of actualAdd) {
|
|
2252
|
-
domain.db.prepare("INSERT OR IGNORE INTO message_labels(mailbox_id, message_id, label_id) VALUES (?, ?, ?)").run(mailboxId,
|
|
1578
|
+
domain.db.prepare("INSERT OR IGNORE INTO message_labels(mailbox_id, message_id, label_id) VALUES (?, ?, ?)").run(mailboxId, messageId, label2);
|
|
2253
1579
|
}
|
|
2254
1580
|
if (actualAdd.length)
|
|
2255
|
-
addHistory(domain.db, mailboxId,
|
|
1581
|
+
addHistory(domain.db, mailboxId, messageId, before.threadId, "labelAdded", actualAdd);
|
|
2256
1582
|
if (actualRemove.length) {
|
|
2257
|
-
addHistory(domain.db, mailboxId,
|
|
1583
|
+
addHistory(domain.db, mailboxId, messageId, before.threadId, "labelRemoved", actualRemove);
|
|
2258
1584
|
}
|
|
2259
|
-
return semanticMessage(domain.db, mailboxId,
|
|
1585
|
+
return semanticMessage(domain.db, mailboxId, messageId);
|
|
2260
1586
|
}).immediate();
|
|
2261
1587
|
}
|
|
2262
|
-
function modifyThreadLabels(domain,
|
|
1588
|
+
function modifyThreadLabels(domain, email2, threadId, add = [], remove = []) {
|
|
2263
1589
|
return domain.db.transaction(() => {
|
|
2264
|
-
const thread = getThread(domain,
|
|
2265
|
-
for (const
|
|
2266
|
-
modifyMessageLabels(domain,
|
|
2267
|
-
return getThread(domain,
|
|
1590
|
+
const thread = getThread(domain, email2, threadId);
|
|
1591
|
+
for (const message of thread.messages)
|
|
1592
|
+
modifyMessageLabels(domain, email2, message.id, add, remove);
|
|
1593
|
+
return getThread(domain, email2, threadId);
|
|
2268
1594
|
}).immediate();
|
|
2269
1595
|
}
|
|
2270
|
-
function deleteMessage(domain,
|
|
2271
|
-
const mailboxId = domain.mailboxId(
|
|
1596
|
+
function deleteMessage(domain, email2, messageId) {
|
|
1597
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2272
1598
|
domain.db.transaction(() => {
|
|
2273
|
-
const
|
|
2274
|
-
domain.db.prepare("DELETE FROM messages WHERE mailbox_id = ? AND id = ?").run(mailboxId,
|
|
2275
|
-
addHistory(domain.db, mailboxId,
|
|
2276
|
-
removeEmptyThread(domain, mailboxId,
|
|
1599
|
+
const message = semanticMessage(domain.db, mailboxId, messageId);
|
|
1600
|
+
domain.db.prepare("DELETE FROM messages WHERE mailbox_id = ? AND id = ?").run(mailboxId, messageId);
|
|
1601
|
+
addHistory(domain.db, mailboxId, messageId, message.threadId, "messageDeleted");
|
|
1602
|
+
removeEmptyThread(domain, mailboxId, message.threadId);
|
|
2277
1603
|
}).immediate();
|
|
2278
1604
|
}
|
|
2279
|
-
function batchDeleteMessages(domain,
|
|
2280
|
-
const mailboxId = domain.mailboxId(
|
|
1605
|
+
function batchDeleteMessages(domain, email2, messageIds2) {
|
|
1606
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2281
1607
|
domain.db.transaction(() => {
|
|
2282
|
-
for (const
|
|
2283
|
-
const exists = domain.db.prepare("SELECT thread_id FROM messages WHERE mailbox_id = ? AND id = ?").get(mailboxId,
|
|
1608
|
+
for (const messageId of messageIds2) {
|
|
1609
|
+
const exists = domain.db.prepare("SELECT thread_id FROM messages WHERE mailbox_id = ? AND id = ?").get(mailboxId, messageId);
|
|
2284
1610
|
if (!exists)
|
|
2285
1611
|
continue;
|
|
2286
|
-
domain.db.prepare("DELETE FROM messages WHERE mailbox_id = ? AND id = ?").run(mailboxId,
|
|
2287
|
-
addHistory(domain.db, mailboxId,
|
|
1612
|
+
domain.db.prepare("DELETE FROM messages WHERE mailbox_id = ? AND id = ?").run(mailboxId, messageId);
|
|
1613
|
+
addHistory(domain.db, mailboxId, messageId, exists.thread_id, "messageDeleted");
|
|
2288
1614
|
removeEmptyThread(domain, mailboxId, exists.thread_id);
|
|
2289
1615
|
}
|
|
2290
1616
|
}).immediate();
|
|
2291
1617
|
}
|
|
2292
|
-
function deleteThread(domain,
|
|
2293
|
-
const mailboxId = domain.mailboxId(
|
|
1618
|
+
function deleteThread(domain, email2, threadId) {
|
|
1619
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2294
1620
|
domain.db.transaction(() => {
|
|
2295
|
-
const thread = getThread(domain,
|
|
1621
|
+
const thread = getThread(domain, email2, threadId);
|
|
2296
1622
|
domain.db.prepare("DELETE FROM threads WHERE mailbox_id = ? AND id = ?").run(mailboxId, threadId);
|
|
2297
|
-
for (const
|
|
2298
|
-
addHistory(domain.db, mailboxId,
|
|
1623
|
+
for (const message of thread.messages) {
|
|
1624
|
+
addHistory(domain.db, mailboxId, message.id, threadId, "messageDeleted");
|
|
2299
1625
|
}
|
|
2300
1626
|
}).immediate();
|
|
2301
1627
|
}
|
|
2302
|
-
function headers(domain,
|
|
2303
|
-
const mailboxId = domain.mailboxId(
|
|
2304
|
-
const row = domain.db.prepare("SELECT headers_json FROM messages WHERE mailbox_id = ? AND id = ?").get(mailboxId,
|
|
1628
|
+
function headers(domain, email2, messageId) {
|
|
1629
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1630
|
+
const row = domain.db.prepare("SELECT headers_json FROM messages WHERE mailbox_id = ? AND id = ?").get(mailboxId, messageId);
|
|
2305
1631
|
if (!row)
|
|
2306
1632
|
notFound("Message");
|
|
2307
1633
|
return JSON.parse(row.headers_json);
|
|
2308
1634
|
}
|
|
2309
|
-
function attachment(domain,
|
|
2310
|
-
const mailboxId = domain.mailboxId(
|
|
2311
|
-
const row = domain.db.prepare("SELECT size, data FROM attachments WHERE mailbox_id = ? AND message_id = ? AND id = ?").get(mailboxId,
|
|
1635
|
+
function attachment(domain, email2, messageId, attachmentId) {
|
|
1636
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1637
|
+
const row = domain.db.prepare("SELECT size, data FROM attachments WHERE mailbox_id = ? AND message_id = ? AND id = ?").get(mailboxId, messageId, attachmentId);
|
|
2312
1638
|
if (!row)
|
|
2313
1639
|
notFound("Attachment");
|
|
2314
1640
|
return { size: row.size, data: Buffer.from(row.data).toString("base64url") };
|
|
2315
1641
|
}
|
|
2316
|
-
function applyInternalDateSource(domain,
|
|
2317
|
-
const mailboxId = domain.mailboxId(
|
|
1642
|
+
function applyInternalDateSource(domain, email2, messageId, source) {
|
|
1643
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2318
1644
|
if (source === "receivedTime") {
|
|
2319
1645
|
const history = domain.db.prepare(`SELECT created_at FROM history
|
|
2320
1646
|
WHERE mailbox_id = ? AND message_id = ? AND event_type = 'messageAdded'
|
|
2321
|
-
ORDER BY id DESC LIMIT 1`).get(mailboxId,
|
|
1647
|
+
ORDER BY id DESC LIMIT 1`).get(mailboxId, messageId);
|
|
2322
1648
|
if (history) {
|
|
2323
|
-
domain.db.prepare("UPDATE messages SET internal_date = ? WHERE mailbox_id = ? AND id = ?").run(Date.parse(history.created_at), mailboxId,
|
|
1649
|
+
domain.db.prepare("UPDATE messages SET internal_date = ? WHERE mailbox_id = ? AND id = ?").run(Date.parse(history.created_at), mailboxId, messageId);
|
|
2324
1650
|
}
|
|
2325
1651
|
}
|
|
2326
|
-
return semanticMessage(domain.db, mailboxId,
|
|
1652
|
+
return semanticMessage(domain.db, mailboxId, messageId);
|
|
2327
1653
|
}
|
|
2328
|
-
function searchMessages(domain,
|
|
2329
|
-
const mailboxId = domain.mailboxId(
|
|
1654
|
+
function searchMessages(domain, email2, query = "", options = {}) {
|
|
1655
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2330
1656
|
const ast = validateSearchQuery(query);
|
|
2331
1657
|
const explicitAnywhere = /\bin:(anywhere|trash|spam|draft)\b/i.test(query);
|
|
2332
1658
|
const plan = compileSearchToSql(ast, {
|
|
@@ -2356,16 +1682,16 @@ function searchMessages(domain, email3, query = "", options = {}) {
|
|
|
2356
1682
|
return messages;
|
|
2357
1683
|
const now = searchNow(domain.db);
|
|
2358
1684
|
const documents = batchSearchDocuments(domain.db, mailboxId, messages);
|
|
2359
|
-
return messages.filter((
|
|
2360
|
-
if (!explicitAnywhere && !options.includeTrash &&
|
|
1685
|
+
return messages.filter((message, index) => {
|
|
1686
|
+
if (!explicitAnywhere && !options.includeTrash && message.labelIds.some((id) => ["TRASH", "SPAM", "DRAFT"].includes(id))) {
|
|
2361
1687
|
return false;
|
|
2362
1688
|
}
|
|
2363
1689
|
return matchesSearch(ast, documents[index], now);
|
|
2364
1690
|
});
|
|
2365
1691
|
}
|
|
2366
|
-
function searchThreads(domain,
|
|
2367
|
-
const matches = searchMessages(domain,
|
|
2368
|
-
return [...new Set(matches.map((
|
|
1692
|
+
function searchThreads(domain, email2, query = "", options = {}) {
|
|
1693
|
+
const matches = searchMessages(domain, email2, query, options);
|
|
1694
|
+
return [...new Set(matches.map((message) => message.threadId))].map((threadId) => getThread(domain, email2, threadId));
|
|
2369
1695
|
}
|
|
2370
1696
|
function removeEmptyThread(domain, mailboxId, threadId) {
|
|
2371
1697
|
const member = domain.db.prepare("SELECT 1 FROM messages WHERE mailbox_id = ? AND thread_id = ? LIMIT 1").get(mailboxId, threadId);
|
|
@@ -2390,27 +1716,27 @@ function assertAcceptedFrom(domain, mailboxId, from) {
|
|
|
2390
1716
|
}
|
|
2391
1717
|
|
|
2392
1718
|
// ../packages/twin-gmail/dist/src/domain/drafts.js
|
|
2393
|
-
function createDraft(domain,
|
|
2394
|
-
const mailboxId = domain.mailboxId(
|
|
1719
|
+
function createDraft(domain, email2, raw, options = {}) {
|
|
1720
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2395
1721
|
const bytes = acceptedRaw(raw);
|
|
2396
1722
|
return domain.db.transaction(() => {
|
|
2397
|
-
const
|
|
1723
|
+
const message = insertStoredMessage(domain.db, mailboxId, bytes, {
|
|
2398
1724
|
threadId: options.threadId,
|
|
2399
1725
|
draft: true
|
|
2400
1726
|
});
|
|
2401
1727
|
const draftId = nextId(domain.db, mailboxId, "draft_counter", "draft");
|
|
2402
1728
|
const now = nextTimestamp(domain.db, mailboxId);
|
|
2403
|
-
domain.db.prepare("INSERT INTO drafts(mailbox_id, id, message_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(mailboxId, draftId,
|
|
2404
|
-
addHistory(domain.db, mailboxId,
|
|
2405
|
-
return { id: draftId, message
|
|
1729
|
+
domain.db.prepare("INSERT INTO drafts(mailbox_id, id, message_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(mailboxId, draftId, message.id, now, now);
|
|
1730
|
+
addHistory(domain.db, mailboxId, message.id, message.threadId, "draftCreated", ["DRAFT"]);
|
|
1731
|
+
return { id: draftId, message };
|
|
2406
1732
|
}).immediate();
|
|
2407
1733
|
}
|
|
2408
|
-
function createComposedDraft(domain,
|
|
2409
|
-
const mailboxId = domain.mailboxId(
|
|
1734
|
+
function createComposedDraft(domain, email2, input) {
|
|
1735
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2410
1736
|
return domain.db.transaction(() => {
|
|
2411
1737
|
const reply = input.replyToMessageId ? semanticMessage(domain.db, mailboxId, input.replyToMessageId) : void 0;
|
|
2412
1738
|
const raw = composeMime({
|
|
2413
|
-
from:
|
|
1739
|
+
from: email2,
|
|
2414
1740
|
to: input.to,
|
|
2415
1741
|
cc: input.cc,
|
|
2416
1742
|
bcc: input.bcc,
|
|
@@ -2423,20 +1749,20 @@ function createComposedDraft(domain, email3, input) {
|
|
|
2423
1749
|
references: reply ? [reply.rfcMessageId] : void 0,
|
|
2424
1750
|
attachments: input.attachments
|
|
2425
1751
|
});
|
|
2426
|
-
return createDraft(domain,
|
|
1752
|
+
return createDraft(domain, email2, raw, { threadId: reply?.threadId });
|
|
2427
1753
|
}).immediate();
|
|
2428
1754
|
}
|
|
2429
|
-
function listDrafts(domain,
|
|
2430
|
-
const mailboxId = domain.mailboxId(
|
|
1755
|
+
function listDrafts(domain, email2, query = "") {
|
|
1756
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2431
1757
|
const rows2 = domain.db.prepare(`SELECT d.id, d.message_id
|
|
2432
1758
|
FROM drafts d JOIN messages m ON m.mailbox_id = d.mailbox_id AND m.id = d.message_id
|
|
2433
1759
|
WHERE d.mailbox_id = ? ORDER BY d.updated_at DESC, d.id DESC`).all(mailboxId);
|
|
2434
|
-
const matchingIds = query ? new Set(searchMessages(domain,
|
|
1760
|
+
const matchingIds = query ? new Set(searchMessages(domain, email2, `in:draft ${query}`, { includeTrash: true }).map((message) => message.id)) : void 0;
|
|
2435
1761
|
return rows2.filter((row) => matchingIds?.has(row.message_id) ?? true).map((row) => ({ id: row.id, message: semanticMessage(domain.db, mailboxId, row.message_id) }));
|
|
2436
1762
|
}
|
|
2437
|
-
function drafts(domain,
|
|
2438
|
-
const mailboxId = domain.mailboxId(
|
|
2439
|
-
const matching = query ? new Set(searchMessages(domain,
|
|
1763
|
+
function drafts(domain, email2, query = "", includeTrash = false) {
|
|
1764
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1765
|
+
const matching = query ? new Set(searchMessages(domain, email2, query, { includeTrash }).map((message) => message.id)) : null;
|
|
2440
1766
|
const rows2 = domain.db.prepare("SELECT id, message_id, updated_at FROM drafts WHERE mailbox_id = ? ORDER BY updated_at DESC, id DESC").all(mailboxId);
|
|
2441
1767
|
return rows2.filter((row) => !matching || matching.has(row.message_id)).map((row) => ({
|
|
2442
1768
|
id: row.id,
|
|
@@ -2444,15 +1770,15 @@ function drafts(domain, email3, query = "", includeTrash = false) {
|
|
|
2444
1770
|
updatedAt: row.updated_at
|
|
2445
1771
|
}));
|
|
2446
1772
|
}
|
|
2447
|
-
function draft(domain,
|
|
2448
|
-
const mailboxId = domain.mailboxId(
|
|
1773
|
+
function draft(domain, email2, draftId) {
|
|
1774
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2449
1775
|
const row = domain.db.prepare("SELECT id, message_id, updated_at FROM drafts WHERE mailbox_id = ? AND id = ?").get(mailboxId, draftId);
|
|
2450
1776
|
if (!row)
|
|
2451
1777
|
notFound("Draft");
|
|
2452
1778
|
return { id: row.id, message: semanticMessage(domain.db, mailboxId, row.message_id), updatedAt: row.updated_at };
|
|
2453
1779
|
}
|
|
2454
|
-
function updateDraft(domain,
|
|
2455
|
-
const mailboxId = domain.mailboxId(
|
|
1780
|
+
function updateDraft(domain, email2, draftId, raw, options = {}) {
|
|
1781
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2456
1782
|
const bytes = acceptedRaw(raw);
|
|
2457
1783
|
return domain.db.transaction(() => {
|
|
2458
1784
|
const draftRow = requireDraft(domain, mailboxId, draftId);
|
|
@@ -2469,8 +1795,8 @@ function updateDraft(domain, email3, draftId, raw, options = {}) {
|
|
|
2469
1795
|
return { id: draftId, message: replacement };
|
|
2470
1796
|
}).immediate();
|
|
2471
1797
|
}
|
|
2472
|
-
function sendDraft(domain,
|
|
2473
|
-
const mailboxId = domain.mailboxId(
|
|
1798
|
+
function sendDraft(domain, email2, draftId) {
|
|
1799
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2474
1800
|
return domain.db.transaction(() => {
|
|
2475
1801
|
const draftRow = requireDraft(domain, mailboxId, draftId);
|
|
2476
1802
|
const raw = rawMessage(domain.db, mailboxId, draftRow.message_id);
|
|
@@ -2478,17 +1804,17 @@ function sendDraft(domain, email3, draftId) {
|
|
|
2478
1804
|
domain.db.prepare("DELETE FROM drafts WHERE mailbox_id = ? AND id = ?").run(mailboxId, draftId);
|
|
2479
1805
|
domain.db.prepare("DELETE FROM messages WHERE mailbox_id = ? AND id = ?").run(mailboxId, draftRow.message_id);
|
|
2480
1806
|
addHistory(domain.db, mailboxId, draftRow.message_id, old.threadId, "draftSent");
|
|
2481
|
-
return sendMessage(domain,
|
|
1807
|
+
return sendMessage(domain, email2, raw, { threadId: old.threadId });
|
|
2482
1808
|
}).immediate();
|
|
2483
1809
|
}
|
|
2484
|
-
function deleteDraft(domain,
|
|
2485
|
-
const mailboxId = domain.mailboxId(
|
|
1810
|
+
function deleteDraft(domain, email2, draftId) {
|
|
1811
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2486
1812
|
domain.db.transaction(() => {
|
|
2487
1813
|
const draftRow = requireDraft(domain, mailboxId, draftId);
|
|
2488
|
-
const
|
|
1814
|
+
const message = semanticMessage(domain.db, mailboxId, draftRow.message_id);
|
|
2489
1815
|
domain.db.prepare("DELETE FROM messages WHERE mailbox_id = ? AND id = ?").run(mailboxId, draftRow.message_id);
|
|
2490
|
-
removeEmptyThread(domain, mailboxId,
|
|
2491
|
-
addHistory(domain.db, mailboxId, draftRow.message_id,
|
|
1816
|
+
removeEmptyThread(domain, mailboxId, message.threadId);
|
|
1817
|
+
addHistory(domain.db, mailboxId, draftRow.message_id, message.threadId, "draftDeleted");
|
|
2492
1818
|
}).immediate();
|
|
2493
1819
|
}
|
|
2494
1820
|
function requireDraft(domain, mailboxId, draftId) {
|
|
@@ -2499,22 +1825,22 @@ function requireDraft(domain, mailboxId, draftId) {
|
|
|
2499
1825
|
}
|
|
2500
1826
|
|
|
2501
1827
|
// ../packages/twin-gmail/dist/src/domain/labels.js
|
|
2502
|
-
function createLabel(domain,
|
|
2503
|
-
const mailboxId = domain.mailboxId(
|
|
1828
|
+
function createLabel(domain, email2, name, color) {
|
|
1829
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2504
1830
|
if (!name.trim())
|
|
2505
1831
|
invalidArgument("Label name is required");
|
|
2506
1832
|
return domain.db.transaction(() => {
|
|
2507
|
-
const
|
|
1833
|
+
const id = nextId(domain.db, mailboxId, "label_counter", "Label");
|
|
2508
1834
|
try {
|
|
2509
|
-
domain.db.prepare("INSERT INTO labels(mailbox_id, id, name, type, text_color, background_color) VALUES (?, ?, ?, 'user', ?, ?)").run(mailboxId,
|
|
1835
|
+
domain.db.prepare("INSERT INTO labels(mailbox_id, id, name, type, text_color, background_color) VALUES (?, ?, ?, 'user', ?, ?)").run(mailboxId, id, name.trim(), color?.textColor ?? null, color?.backgroundColor ?? null);
|
|
2510
1836
|
} catch {
|
|
2511
1837
|
invalidArgument(`Label already exists: ${name}`);
|
|
2512
1838
|
}
|
|
2513
|
-
return { id
|
|
1839
|
+
return { id, name: name.trim() };
|
|
2514
1840
|
}).immediate();
|
|
2515
1841
|
}
|
|
2516
|
-
function listUserLabels(domain,
|
|
2517
|
-
const mailboxId = domain.mailboxId(
|
|
1842
|
+
function listUserLabels(domain, email2) {
|
|
1843
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2518
1844
|
const rows2 = domain.db.prepare(`SELECT l.id, l.name, l.text_color, l.background_color,
|
|
2519
1845
|
COUNT(DISTINCT m.thread_id) AS threads_total,
|
|
2520
1846
|
COUNT(DISTINCT CASE WHEN unread.message_id IS NOT NULL THEN m.thread_id END) AS threads_unread
|
|
@@ -2543,8 +1869,8 @@ function listUserLabels(domain, email3) {
|
|
|
2543
1869
|
threadsUnread: row.threads_unread
|
|
2544
1870
|
}));
|
|
2545
1871
|
}
|
|
2546
|
-
function labels(domain,
|
|
2547
|
-
const mailboxId = domain.mailboxId(
|
|
1872
|
+
function labels(domain, email2) {
|
|
1873
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2548
1874
|
const rows2 = domain.db.prepare(`SELECT l.id, l.name, l.type, l.text_color, l.background_color,
|
|
2549
1875
|
COUNT(DISTINCT ml.message_id) AS messagesTotal,
|
|
2550
1876
|
COUNT(DISTINCT CASE WHEN unread.message_id IS NOT NULL THEN ml.message_id END) AS messagesUnread,
|
|
@@ -2574,15 +1900,15 @@ function labels(domain, email3) {
|
|
|
2574
1900
|
threadsUnread: row.threadsUnread
|
|
2575
1901
|
}));
|
|
2576
1902
|
}
|
|
2577
|
-
function label(domain,
|
|
2578
|
-
const found = labels(domain,
|
|
1903
|
+
function label(domain, email2, labelId) {
|
|
1904
|
+
const found = labels(domain, email2).find((item) => item.id === labelId);
|
|
2579
1905
|
if (!found)
|
|
2580
1906
|
notFound("Label");
|
|
2581
1907
|
return found;
|
|
2582
1908
|
}
|
|
2583
|
-
function updateLabel(domain,
|
|
2584
|
-
const mailboxId = domain.mailboxId(
|
|
2585
|
-
const current = label(domain,
|
|
1909
|
+
function updateLabel(domain, email2, labelId, input, replace) {
|
|
1910
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1911
|
+
const current = label(domain, email2, labelId);
|
|
2586
1912
|
if (current.type !== "user")
|
|
2587
1913
|
invalidArgument("System labels cannot be modified");
|
|
2588
1914
|
const name = replace ? input.name : input.name ?? current.name;
|
|
@@ -2595,38 +1921,38 @@ function updateLabel(domain, email3, labelId, input, replace) {
|
|
|
2595
1921
|
} catch {
|
|
2596
1922
|
invalidArgument(`Label already exists: ${name}`);
|
|
2597
1923
|
}
|
|
2598
|
-
return label(domain,
|
|
1924
|
+
return label(domain, email2, labelId);
|
|
2599
1925
|
}
|
|
2600
|
-
function deleteLabel(domain,
|
|
2601
|
-
const mailboxId = domain.mailboxId(
|
|
2602
|
-
const current = label(domain,
|
|
1926
|
+
function deleteLabel(domain, email2, labelId) {
|
|
1927
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1928
|
+
const current = label(domain, email2, labelId);
|
|
2603
1929
|
if (current.type !== "user")
|
|
2604
1930
|
invalidArgument("System labels cannot be deleted");
|
|
2605
1931
|
domain.db.prepare("DELETE FROM labels WHERE mailbox_id = ? AND id = ?").run(mailboxId, labelId);
|
|
2606
1932
|
}
|
|
2607
1933
|
|
|
2608
1934
|
// ../packages/twin-gmail/dist/src/domain/settings.js
|
|
2609
|
-
function profile(domain,
|
|
2610
|
-
const mailboxId = domain.mailboxId(
|
|
1935
|
+
function profile(domain, email2) {
|
|
1936
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2611
1937
|
const totals = domain.db.prepare(`SELECT COUNT(*) AS messagesTotal, COUNT(DISTINCT thread_id) AS threadsTotal
|
|
2612
1938
|
FROM messages WHERE mailbox_id = ?`).get(mailboxId);
|
|
2613
|
-
return { emailAddress:
|
|
1939
|
+
return { emailAddress: email2, ...totals, historyId: currentHistoryId(domain, mailboxId) };
|
|
2614
1940
|
}
|
|
2615
|
-
function currentHistoryIdFor(domain,
|
|
2616
|
-
return currentHistoryId(domain, domain.mailboxId(
|
|
1941
|
+
function currentHistoryIdFor(domain, email2) {
|
|
1942
|
+
return currentHistoryId(domain, domain.mailboxId(email2));
|
|
2617
1943
|
}
|
|
2618
|
-
function latestMessageHistory(domain,
|
|
2619
|
-
const mailboxId = domain.mailboxId(
|
|
2620
|
-
const row = domain.db.prepare("SELECT MAX(id) AS id FROM history WHERE mailbox_id = ? AND message_id = ?").get(mailboxId,
|
|
1944
|
+
function latestMessageHistory(domain, email2, messageId) {
|
|
1945
|
+
const mailboxId = domain.mailboxId(email2);
|
|
1946
|
+
const row = domain.db.prepare("SELECT MAX(id) AS id FROM history WHERE mailbox_id = ? AND message_id = ?").get(mailboxId, messageId);
|
|
2621
1947
|
return String(row.id ?? Number(currentHistoryId(domain, mailboxId)));
|
|
2622
1948
|
}
|
|
2623
|
-
function latestThreadHistory(domain,
|
|
2624
|
-
const mailboxId = domain.mailboxId(
|
|
1949
|
+
function latestThreadHistory(domain, email2, threadId) {
|
|
1950
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2625
1951
|
const row = domain.db.prepare("SELECT MAX(id) AS id FROM history WHERE mailbox_id = ? AND thread_id = ?").get(mailboxId, threadId);
|
|
2626
1952
|
return String(row.id ?? Number(currentHistoryId(domain, mailboxId)));
|
|
2627
1953
|
}
|
|
2628
|
-
function listHistory(domain,
|
|
2629
|
-
const mailboxId = domain.mailboxId(
|
|
1954
|
+
function listHistory(domain, email2, startHistoryId, options = {}) {
|
|
1955
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2630
1956
|
const start = Number(startHistoryId);
|
|
2631
1957
|
if (!Number.isSafeInteger(start) || start < 0)
|
|
2632
1958
|
invalidArgument("Invalid startHistoryId");
|
|
@@ -2649,19 +1975,19 @@ function listHistory(domain, email3, startHistoryId, options = {}) {
|
|
|
2649
1975
|
historyId: String(current)
|
|
2650
1976
|
};
|
|
2651
1977
|
}
|
|
2652
|
-
function forwardingAddresses(domain,
|
|
2653
|
-
const mailboxId = domain.mailboxId(
|
|
1978
|
+
function forwardingAddresses(domain, email2) {
|
|
1979
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2654
1980
|
const rows2 = domain.db.prepare("SELECT email, verification_status FROM forwarding_addresses WHERE mailbox_id = ? ORDER BY email COLLATE NOCASE").all(mailboxId);
|
|
2655
1981
|
return rows2.map((row) => ({ forwardingEmail: row.email, verificationStatus: row.verification_status }));
|
|
2656
1982
|
}
|
|
2657
|
-
function forwardingAddress(domain,
|
|
2658
|
-
const found = forwardingAddresses(domain,
|
|
1983
|
+
function forwardingAddress(domain, email2, forwardingEmail) {
|
|
1984
|
+
const found = forwardingAddresses(domain, email2).find((item) => item.forwardingEmail.toLowerCase() === forwardingEmail.toLowerCase());
|
|
2659
1985
|
if (!found)
|
|
2660
1986
|
notFound("Forwarding address");
|
|
2661
1987
|
return found;
|
|
2662
1988
|
}
|
|
2663
|
-
function sendAs(domain,
|
|
2664
|
-
const mailboxId = domain.mailboxId(
|
|
1989
|
+
function sendAs(domain, email2) {
|
|
1990
|
+
const mailboxId = domain.mailboxId(email2);
|
|
2665
1991
|
const rows2 = domain.db.prepare(`SELECT email, display_name, reply_to_address, is_primary, is_default, verification_status
|
|
2666
1992
|
FROM send_as WHERE mailbox_id = ? ORDER BY is_primary DESC, email COLLATE NOCASE`).all(mailboxId);
|
|
2667
1993
|
return rows2.map((row) => ({
|
|
@@ -2675,8 +2001,8 @@ function sendAs(domain, email3) {
|
|
|
2675
2001
|
signature: ""
|
|
2676
2002
|
}));
|
|
2677
2003
|
}
|
|
2678
|
-
function sendAsAddress(domain,
|
|
2679
|
-
const found = sendAs(domain,
|
|
2004
|
+
function sendAsAddress(domain, email2, sendAsEmail) {
|
|
2005
|
+
const found = sendAs(domain, email2).find((item) => String(item.sendAsEmail).toLowerCase() === sendAsEmail.toLowerCase());
|
|
2680
2006
|
if (!found)
|
|
2681
2007
|
notFound("Send-as alias");
|
|
2682
2008
|
return found;
|
|
@@ -2711,730 +2037,143 @@ var GmailDomain = class {
|
|
|
2711
2037
|
this.seed(defaultSeedState());
|
|
2712
2038
|
return { ok: true };
|
|
2713
2039
|
}
|
|
2714
|
-
mailboxId(
|
|
2715
|
-
const row = this.db.prepare("SELECT id FROM mailboxes WHERE email = ? COLLATE NOCASE").get(
|
|
2040
|
+
mailboxId(email2) {
|
|
2041
|
+
const row = this.db.prepare("SELECT id FROM mailboxes WHERE email = ? COLLATE NOCASE").get(email2);
|
|
2716
2042
|
if (!row)
|
|
2717
2043
|
notFound("User");
|
|
2718
2044
|
return row.id;
|
|
2719
2045
|
}
|
|
2720
|
-
getRaw(
|
|
2721
|
-
return getRaw(this,
|
|
2046
|
+
getRaw(email2, messageId) {
|
|
2047
|
+
return getRaw(this, email2, messageId);
|
|
2722
2048
|
}
|
|
2723
|
-
getMessage(
|
|
2724
|
-
return getMessage(this,
|
|
2049
|
+
getMessage(email2, messageId) {
|
|
2050
|
+
return getMessage(this, email2, messageId);
|
|
2725
2051
|
}
|
|
2726
|
-
getThread(
|
|
2727
|
-
return getThread(this,
|
|
2052
|
+
getThread(email2, threadId) {
|
|
2053
|
+
return getThread(this, email2, threadId);
|
|
2728
2054
|
}
|
|
2729
|
-
insertMessage(
|
|
2730
|
-
return insertMessage(this,
|
|
2055
|
+
insertMessage(email2, raw, options = {}) {
|
|
2056
|
+
return insertMessage(this, email2, raw, options);
|
|
2731
2057
|
}
|
|
2732
|
-
sendMessage(
|
|
2733
|
-
return sendMessage(this,
|
|
2058
|
+
sendMessage(email2, raw, options = {}) {
|
|
2059
|
+
return sendMessage(this, email2, raw, options);
|
|
2734
2060
|
}
|
|
2735
|
-
createDraft(
|
|
2736
|
-
return createDraft(this,
|
|
2061
|
+
createDraft(email2, raw, options = {}) {
|
|
2062
|
+
return createDraft(this, email2, raw, options);
|
|
2737
2063
|
}
|
|
2738
|
-
createComposedDraft(
|
|
2739
|
-
return createComposedDraft(this,
|
|
2064
|
+
createComposedDraft(email2, input) {
|
|
2065
|
+
return createComposedDraft(this, email2, input);
|
|
2740
2066
|
}
|
|
2741
|
-
listDrafts(
|
|
2742
|
-
return listDrafts(this,
|
|
2067
|
+
listDrafts(email2, query = "") {
|
|
2068
|
+
return listDrafts(this, email2, query);
|
|
2743
2069
|
}
|
|
2744
|
-
drafts(
|
|
2745
|
-
return drafts(this,
|
|
2070
|
+
drafts(email2, query = "", includeTrash = false) {
|
|
2071
|
+
return drafts(this, email2, query, includeTrash);
|
|
2746
2072
|
}
|
|
2747
|
-
draft(
|
|
2748
|
-
return draft(this,
|
|
2073
|
+
draft(email2, draftId) {
|
|
2074
|
+
return draft(this, email2, draftId);
|
|
2749
2075
|
}
|
|
2750
|
-
updateDraft(
|
|
2751
|
-
return updateDraft(this,
|
|
2076
|
+
updateDraft(email2, draftId, raw, options = {}) {
|
|
2077
|
+
return updateDraft(this, email2, draftId, raw, options);
|
|
2752
2078
|
}
|
|
2753
|
-
sendDraft(
|
|
2754
|
-
return sendDraft(this,
|
|
2079
|
+
sendDraft(email2, draftId) {
|
|
2080
|
+
return sendDraft(this, email2, draftId);
|
|
2755
2081
|
}
|
|
2756
|
-
deleteDraft(
|
|
2757
|
-
deleteDraft(this,
|
|
2082
|
+
deleteDraft(email2, draftId) {
|
|
2083
|
+
deleteDraft(this, email2, draftId);
|
|
2758
2084
|
}
|
|
2759
|
-
modifyMessageLabels(
|
|
2760
|
-
return modifyMessageLabels(this,
|
|
2085
|
+
modifyMessageLabels(email2, messageId, add = [], remove = []) {
|
|
2086
|
+
return modifyMessageLabels(this, email2, messageId, add, remove);
|
|
2761
2087
|
}
|
|
2762
|
-
modifyThreadLabels(
|
|
2763
|
-
return modifyThreadLabels(this,
|
|
2088
|
+
modifyThreadLabels(email2, threadId, add = [], remove = []) {
|
|
2089
|
+
return modifyThreadLabels(this, email2, threadId, add, remove);
|
|
2764
2090
|
}
|
|
2765
|
-
deleteMessage(
|
|
2766
|
-
deleteMessage(this,
|
|
2091
|
+
deleteMessage(email2, messageId) {
|
|
2092
|
+
deleteMessage(this, email2, messageId);
|
|
2767
2093
|
}
|
|
2768
|
-
batchDeleteMessages(
|
|
2769
|
-
batchDeleteMessages(this,
|
|
2094
|
+
batchDeleteMessages(email2, messageIds2) {
|
|
2095
|
+
batchDeleteMessages(this, email2, messageIds2);
|
|
2770
2096
|
}
|
|
2771
|
-
deleteThread(
|
|
2772
|
-
deleteThread(this,
|
|
2097
|
+
deleteThread(email2, threadId) {
|
|
2098
|
+
deleteThread(this, email2, threadId);
|
|
2773
2099
|
}
|
|
2774
|
-
headers(
|
|
2775
|
-
return headers(this,
|
|
2100
|
+
headers(email2, messageId) {
|
|
2101
|
+
return headers(this, email2, messageId);
|
|
2776
2102
|
}
|
|
2777
|
-
attachment(
|
|
2778
|
-
return attachment(this,
|
|
2103
|
+
attachment(email2, messageId, attachmentId) {
|
|
2104
|
+
return attachment(this, email2, messageId, attachmentId);
|
|
2779
2105
|
}
|
|
2780
|
-
applyInternalDateSource(
|
|
2781
|
-
return applyInternalDateSource(this,
|
|
2106
|
+
applyInternalDateSource(email2, messageId, source) {
|
|
2107
|
+
return applyInternalDateSource(this, email2, messageId, source);
|
|
2782
2108
|
}
|
|
2783
|
-
createLabel(
|
|
2784
|
-
return createLabel(this,
|
|
2109
|
+
createLabel(email2, name, color) {
|
|
2110
|
+
return createLabel(this, email2, name, color);
|
|
2785
2111
|
}
|
|
2786
|
-
listUserLabels(
|
|
2787
|
-
return listUserLabels(this,
|
|
2112
|
+
listUserLabels(email2) {
|
|
2113
|
+
return listUserLabels(this, email2);
|
|
2788
2114
|
}
|
|
2789
|
-
labels(
|
|
2790
|
-
return labels(this,
|
|
2115
|
+
labels(email2) {
|
|
2116
|
+
return labels(this, email2);
|
|
2791
2117
|
}
|
|
2792
|
-
label(
|
|
2793
|
-
return label(this,
|
|
2118
|
+
label(email2, labelId) {
|
|
2119
|
+
return label(this, email2, labelId);
|
|
2794
2120
|
}
|
|
2795
|
-
updateLabel(
|
|
2796
|
-
return updateLabel(this,
|
|
2121
|
+
updateLabel(email2, labelId, input, replace) {
|
|
2122
|
+
return updateLabel(this, email2, labelId, input, replace);
|
|
2797
2123
|
}
|
|
2798
|
-
deleteLabel(
|
|
2799
|
-
deleteLabel(this,
|
|
2124
|
+
deleteLabel(email2, labelId) {
|
|
2125
|
+
deleteLabel(this, email2, labelId);
|
|
2800
2126
|
}
|
|
2801
|
-
filters(
|
|
2802
|
-
return filters(this,
|
|
2127
|
+
filters(email2) {
|
|
2128
|
+
return filters(this, email2);
|
|
2803
2129
|
}
|
|
2804
|
-
filter(
|
|
2805
|
-
return filter(this,
|
|
2130
|
+
filter(email2, filterId) {
|
|
2131
|
+
return filter(this, email2, filterId);
|
|
2806
2132
|
}
|
|
2807
|
-
createFilter(
|
|
2808
|
-
return createFilter(this,
|
|
2133
|
+
createFilter(email2, criteria = {}, action = {}) {
|
|
2134
|
+
return createFilter(this, email2, criteria, action);
|
|
2809
2135
|
}
|
|
2810
|
-
deleteFilter(
|
|
2811
|
-
deleteFilter(this,
|
|
2136
|
+
deleteFilter(email2, filterId) {
|
|
2137
|
+
deleteFilter(this, email2, filterId);
|
|
2812
2138
|
}
|
|
2813
|
-
listHistory(
|
|
2814
|
-
return listHistory(this,
|
|
2139
|
+
listHistory(email2, startHistoryId, options = {}) {
|
|
2140
|
+
return listHistory(this, email2, startHistoryId, options);
|
|
2815
2141
|
}
|
|
2816
|
-
profile(
|
|
2817
|
-
return profile(this,
|
|
2142
|
+
profile(email2) {
|
|
2143
|
+
return profile(this, email2);
|
|
2818
2144
|
}
|
|
2819
|
-
currentHistoryIdFor(
|
|
2820
|
-
return currentHistoryIdFor(this,
|
|
2145
|
+
currentHistoryIdFor(email2) {
|
|
2146
|
+
return currentHistoryIdFor(this, email2);
|
|
2821
2147
|
}
|
|
2822
|
-
latestMessageHistory(
|
|
2823
|
-
return latestMessageHistory(this,
|
|
2148
|
+
latestMessageHistory(email2, messageId) {
|
|
2149
|
+
return latestMessageHistory(this, email2, messageId);
|
|
2824
2150
|
}
|
|
2825
|
-
latestThreadHistory(
|
|
2826
|
-
return latestThreadHistory(this,
|
|
2151
|
+
latestThreadHistory(email2, threadId) {
|
|
2152
|
+
return latestThreadHistory(this, email2, threadId);
|
|
2827
2153
|
}
|
|
2828
|
-
forwardingAddresses(
|
|
2829
|
-
return forwardingAddresses(this,
|
|
2154
|
+
forwardingAddresses(email2) {
|
|
2155
|
+
return forwardingAddresses(this, email2);
|
|
2830
2156
|
}
|
|
2831
|
-
forwardingAddress(
|
|
2832
|
-
return forwardingAddress(this,
|
|
2157
|
+
forwardingAddress(email2, forwardingEmail) {
|
|
2158
|
+
return forwardingAddress(this, email2, forwardingEmail);
|
|
2833
2159
|
}
|
|
2834
|
-
sendAs(
|
|
2835
|
-
return sendAs(this,
|
|
2160
|
+
sendAs(email2) {
|
|
2161
|
+
return sendAs(this, email2);
|
|
2836
2162
|
}
|
|
2837
|
-
sendAsAddress(
|
|
2838
|
-
return sendAsAddress(this,
|
|
2163
|
+
sendAsAddress(email2, sendAsEmail) {
|
|
2164
|
+
return sendAsAddress(this, email2, sendAsEmail);
|
|
2839
2165
|
}
|
|
2840
|
-
searchMessages(
|
|
2841
|
-
return searchMessages(this,
|
|
2166
|
+
searchMessages(email2, query = "", options = {}) {
|
|
2167
|
+
return searchMessages(this, email2, query, options);
|
|
2842
2168
|
}
|
|
2843
|
-
searchThreads(
|
|
2844
|
-
return searchThreads(this,
|
|
2169
|
+
searchThreads(email2, query = "", options = {}) {
|
|
2170
|
+
return searchThreads(this, email2, query, options);
|
|
2845
2171
|
}
|
|
2846
2172
|
exportState() {
|
|
2847
2173
|
return exportGmailState(this.db);
|
|
2848
2174
|
}
|
|
2849
2175
|
};
|
|
2850
2176
|
|
|
2851
|
-
// ../packages/twin-gmail/dist/src/check-params.js
|
|
2852
|
-
var messageId = {
|
|
2853
|
-
name: "message",
|
|
2854
|
-
pattern: "[A-Za-z0-9_-]{1,128}",
|
|
2855
|
-
example: "msg_support",
|
|
2856
|
-
render: (value) => value,
|
|
2857
|
-
parse: (raw) => raw
|
|
2858
|
-
};
|
|
2859
|
-
var labelRef = {
|
|
2860
|
-
name: "label",
|
|
2861
|
-
pattern: "[A-Za-z0-9_-]{1,128}",
|
|
2862
|
-
example: "STARRED",
|
|
2863
|
-
render: (value) => value,
|
|
2864
|
-
parse: (raw) => raw
|
|
2865
|
-
};
|
|
2866
|
-
var labelName = {
|
|
2867
|
-
name: "label",
|
|
2868
|
-
pattern: "[A-Za-z0-9][A-Za-z0-9 _-]{0,127}",
|
|
2869
|
-
example: "Parity Complete",
|
|
2870
|
-
render: (value) => value,
|
|
2871
|
-
parse: (raw) => raw
|
|
2872
|
-
};
|
|
2873
|
-
var emailAddress = {
|
|
2874
|
-
name: "email",
|
|
2875
|
-
pattern: "[^\\s@\"'`]+@[^\\s@\"'`]+",
|
|
2876
|
-
example: "alice@example.com",
|
|
2877
|
-
render: (value) => value,
|
|
2878
|
-
parse: (raw) => raw
|
|
2879
|
-
};
|
|
2880
|
-
var mailboxRef = {
|
|
2881
|
-
name: "mailbox",
|
|
2882
|
-
pattern: "[^\\s@\"'`]+@[^\\s@\"'`]+",
|
|
2883
|
-
example: "pome-agent@pome-twin.test",
|
|
2884
|
-
render: (value) => value,
|
|
2885
|
-
parse: (raw) => raw
|
|
2886
|
-
};
|
|
2887
|
-
var exactCount = {
|
|
2888
|
-
name: "count",
|
|
2889
|
-
pattern: "\\d{1,9}",
|
|
2890
|
-
example: "5",
|
|
2891
|
-
render: (value) => value,
|
|
2892
|
-
parse: (raw) => raw
|
|
2893
|
-
};
|
|
2894
|
-
var countWord = {
|
|
2895
|
-
name: "count",
|
|
2896
|
-
pattern: "(?:\\d{1,9}|zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)",
|
|
2897
|
-
example: "two",
|
|
2898
|
-
render: (value) => value,
|
|
2899
|
-
parse: (raw) => raw
|
|
2900
|
-
};
|
|
2901
|
-
var NUMBER_WORDS = {
|
|
2902
|
-
zero: 0,
|
|
2903
|
-
one: 1,
|
|
2904
|
-
two: 2,
|
|
2905
|
-
three: 3,
|
|
2906
|
-
four: 4,
|
|
2907
|
-
five: 5,
|
|
2908
|
-
six: 6,
|
|
2909
|
-
seven: 7,
|
|
2910
|
-
eight: 8,
|
|
2911
|
-
nine: 9,
|
|
2912
|
-
ten: 10,
|
|
2913
|
-
eleven: 11,
|
|
2914
|
-
twelve: 12
|
|
2915
|
-
};
|
|
2916
|
-
function parseCount(raw) {
|
|
2917
|
-
const trimmed = raw.trim().toLowerCase();
|
|
2918
|
-
if (/^\d+$/.test(trimmed))
|
|
2919
|
-
return Number(trimmed);
|
|
2920
|
-
return NUMBER_WORDS[trimmed] ?? null;
|
|
2921
|
-
}
|
|
2922
|
-
|
|
2923
|
-
// ../packages/twin-gmail/dist/src/check-state.js
|
|
2924
|
-
var MESSAGES_PATH = statePath("messages");
|
|
2925
|
-
var DRAFTS_PATH = statePath("drafts");
|
|
2926
|
-
var LABELS_PATH = statePath("labels");
|
|
2927
|
-
var MESSAGE_LABELS_PATH = statePath("messageLabels");
|
|
2928
|
-
function missSkip(miss) {
|
|
2929
|
-
const outcome = { passed: false, status: "skipped", reason: miss.missing };
|
|
2930
|
-
if (miss.searched === void 0)
|
|
2931
|
-
return outcome;
|
|
2932
|
-
return { ...outcome, evidenceStatePaths: [miss.searched] };
|
|
2933
|
-
}
|
|
2934
|
-
var lower = (value) => (value ?? "").toLowerCase();
|
|
2935
|
-
function isTruncated(state, collection) {
|
|
2936
|
-
return (state.exportBounds?.truncatedCollections ?? []).includes(collection);
|
|
2937
|
-
}
|
|
2938
|
-
function resolveMessage(state, id2) {
|
|
2939
|
-
if (state.messages == null)
|
|
2940
|
-
return { missing: "state_incomplete" };
|
|
2941
|
-
const indices = state.messages.map((message2, index) => lower(message2.id) === lower(id2) ? index : -1).filter((index) => index >= 0);
|
|
2942
|
-
if (indices.length > 1) {
|
|
2943
|
-
return {
|
|
2944
|
-
missing: `message_ambiguous ("${id2}" exists in ${indices.length} mailboxes)`,
|
|
2945
|
-
searched: MESSAGES_PATH
|
|
2946
|
-
};
|
|
2947
|
-
}
|
|
2948
|
-
if (indices.length === 1) {
|
|
2949
|
-
const index = indices[0];
|
|
2950
|
-
return { found: state.messages[index], path: childStatePath(MESSAGES_PATH, index) };
|
|
2951
|
-
}
|
|
2952
|
-
if (isTruncated(state, "messages")) {
|
|
2953
|
-
return { missing: 'collection_truncated ("messages")', searched: MESSAGES_PATH };
|
|
2954
|
-
}
|
|
2955
|
-
return { missing: `message_not_found ("${id2}")`, searched: MESSAGES_PATH };
|
|
2956
|
-
}
|
|
2957
|
-
function labelIdsFor(state, wanted) {
|
|
2958
|
-
const ids = /* @__PURE__ */ new Set([lower(wanted)]);
|
|
2959
|
-
for (const label2 of state.labels ?? []) {
|
|
2960
|
-
const matches = lower(label2.name) === lower(wanted) || lower(label2.id) === lower(wanted);
|
|
2961
|
-
if (matches && label2.id != null)
|
|
2962
|
-
ids.add(lower(label2.id));
|
|
2963
|
-
}
|
|
2964
|
-
return ids;
|
|
2965
|
-
}
|
|
2966
|
-
function messageCarriesLabel(state, messageId2, wanted) {
|
|
2967
|
-
if (state.messageLabels == null)
|
|
2968
|
-
return { missing: "state_incomplete" };
|
|
2969
|
-
const ids = labelIdsFor(state, wanted);
|
|
2970
|
-
const carried = state.messageLabels.some((row) => lower(row.messageId) === lower(messageId2) && ids.has(lower(row.labelId)));
|
|
2971
|
-
if (!carried && isTruncated(state, "messageLabels")) {
|
|
2972
|
-
return { missing: 'collection_truncated ("messageLabels")', searched: MESSAGE_LABELS_PATH };
|
|
2973
|
-
}
|
|
2974
|
-
return { found: carried, path: MESSAGE_LABELS_PATH };
|
|
2975
|
-
}
|
|
2976
|
-
function resolveLabelByName(state, name) {
|
|
2977
|
-
if (state.labels == null)
|
|
2978
|
-
return { missing: "state_incomplete" };
|
|
2979
|
-
const index = state.labels.findIndex((label2) => lower(label2.name) === lower(name));
|
|
2980
|
-
if (index >= 0) {
|
|
2981
|
-
return { found: state.labels[index], path: childStatePath(LABELS_PATH, index) };
|
|
2982
|
-
}
|
|
2983
|
-
if (isTruncated(state, "labels")) {
|
|
2984
|
-
return { missing: 'collection_truncated ("labels")', searched: LABELS_PATH };
|
|
2985
|
-
}
|
|
2986
|
-
return { missing: `label_not_found ("${name}")`, searched: LABELS_PATH };
|
|
2987
|
-
}
|
|
2988
|
-
function draftRecipients(state, draft3) {
|
|
2989
|
-
const message2 = (state.messages ?? []).find((candidate) => lower(candidate.id) === lower(draft3.messageId));
|
|
2990
|
-
if (!message2)
|
|
2991
|
-
return [];
|
|
2992
|
-
return [...message2.to ?? [], ...message2.cc ?? []];
|
|
2993
|
-
}
|
|
2994
|
-
|
|
2995
|
-
// ../packages/twin-gmail/dist/src/check-worlds.js
|
|
2996
|
-
var FIXTURE_MAILBOX = "pome-agent@pome-twin.test";
|
|
2997
|
-
function gmailState(parts = {}) {
|
|
2998
|
-
return {
|
|
2999
|
-
mailboxes: [{ email: FIXTURE_MAILBOX }],
|
|
3000
|
-
messages: [],
|
|
3001
|
-
drafts: [],
|
|
3002
|
-
labels: [],
|
|
3003
|
-
messageLabels: [],
|
|
3004
|
-
exportBounds: { messageBodiesOmitted: true, largeMailbox: false, truncatedCollections: [] },
|
|
3005
|
-
...parts
|
|
3006
|
-
};
|
|
3007
|
-
}
|
|
3008
|
-
function finalWorld(final) {
|
|
3009
|
-
return { seed: null, final, tape: null };
|
|
3010
|
-
}
|
|
3011
|
-
function tapeWorld(tape) {
|
|
3012
|
-
return { seed: null, final: gmailState(), tape };
|
|
3013
|
-
}
|
|
3014
|
-
function message(id2, parts = {}) {
|
|
3015
|
-
return { mailboxEmail: FIXTURE_MAILBOX, id: id2, to: [FIXTURE_MAILBOX], bodyOmitted: true, ...parts };
|
|
3016
|
-
}
|
|
3017
|
-
function systemLabel(name) {
|
|
3018
|
-
return { mailboxEmail: FIXTURE_MAILBOX, id: name, name, type: "system" };
|
|
3019
|
-
}
|
|
3020
|
-
function userLabel(id2, name) {
|
|
3021
|
-
return { mailboxEmail: FIXTURE_MAILBOX, id: id2, name, type: "user" };
|
|
3022
|
-
}
|
|
3023
|
-
function messageLabel(messageId2, labelId) {
|
|
3024
|
-
return { mailboxEmail: FIXTURE_MAILBOX, messageId: messageId2, labelId };
|
|
3025
|
-
}
|
|
3026
|
-
function draft2(id2, messageId2) {
|
|
3027
|
-
return { mailboxEmail: FIXTURE_MAILBOX, id: id2, messageId: messageId2 };
|
|
3028
|
-
}
|
|
3029
|
-
function draftAddressedTo(id2, recipients) {
|
|
3030
|
-
const messageId2 = `${id2}_message`;
|
|
3031
|
-
return {
|
|
3032
|
-
draft: draft2(id2, messageId2),
|
|
3033
|
-
message: message(messageId2, { to: recipients })
|
|
3034
|
-
};
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
// ../packages/twin-gmail/dist/src/check-drafts.js
|
|
3038
|
-
var draftAddressedTo2 = defineCheck({
|
|
3039
|
-
id: "gmail.draft-addressed-to",
|
|
3040
|
-
description: "Joins every draft to its backing message and asks whether any of them addresses this recipient in `to` or `cc`, compared case-insensitively as an EXACT address rather than a substring. It asserts nothing about the draft's body \u2014 message bodies are digested out of the state export unconditionally, so no check on this twin can read one \u2014 and nothing about whether the draft was left unsent, which is a separate claim. A draft whose backing message did not survive the export contributes no recipients rather than throwing.",
|
|
3041
|
-
template: "A draft addressed to {email} exists",
|
|
3042
|
-
params: { email: emailAddress },
|
|
3043
|
-
substrate: "final",
|
|
3044
|
-
// An achievement: task 22's seed ships one draft addressed to bob, so an
|
|
3045
|
-
// examinee that does nothing does not satisfy this.
|
|
3046
|
-
polarity: () => "positive",
|
|
3047
|
-
//
|
|
3048
|
-
// THE LOAD-BEARING DECLARATION ON THIS CHECK.
|
|
3049
|
-
//
|
|
3050
|
-
// pome-cloud's `corpus.ts` has carried a prediction about this exact criterion
|
|
3051
|
-
// since F-1028: `22-gmail-inbox-triage`'s `alice@example.com` "becomes visible
|
|
3052
|
-
// here as 'unguarded' the day a gmail draft-recipient predicate lands without a
|
|
3053
|
-
// `subject`, which is precisely when it starts to matter." An email address is
|
|
3054
|
-
// squarely inside what a team's redaction config may destroy. Without this
|
|
3055
|
-
// field the evaluator has no way to turn an impossible comparison into an
|
|
3056
|
-
// honest skip, so the criterion scores a vacuous verdict at both doors
|
|
3057
|
-
// instead — and the count of unguarded hazards must stay at zero.
|
|
3058
|
-
subject: ({ email: email3 }) => email3,
|
|
3059
|
-
// The address is the scanned literal, so the mutant points at it. It stays
|
|
3060
|
-
// email-shaped so it re-binds to this same check: a mutant that stops matching
|
|
3061
|
-
// evaluates to `unmatched`, which reads as "the verdict moved -> healthy" and
|
|
3062
|
-
// hands the criterion a clean bill it did not earn.
|
|
3063
|
-
vacuityMutant: (args) => ({ ...args, email: `${VACUITY_SENTINEL}@example.invalid` }),
|
|
3064
|
-
discriminatingWorlds: ({ email: email3 }) => {
|
|
3065
|
-
const wanted = draftAddressedTo("draft_target", [email3]);
|
|
3066
|
-
const other = draftAddressedTo("draft_other", ["someone-else@example.com"]);
|
|
3067
|
-
return {
|
|
3068
|
-
passing: finalWorld(gmailState({ drafts: [wanted.draft], messages: [wanted.message] })),
|
|
3069
|
-
failing: finalWorld(gmailState({ drafts: [other.draft], messages: [other.message] }))
|
|
3070
|
-
};
|
|
3071
|
-
},
|
|
3072
|
-
evaluate({ email: email3 }, { final }) {
|
|
3073
|
-
if (final.drafts == null || final.messages == null) {
|
|
3074
|
-
return { passed: false, status: "skipped", reason: "state_incomplete" };
|
|
3075
|
-
}
|
|
3076
|
-
const wanted = email3.toLowerCase();
|
|
3077
|
-
const index = final.drafts.findIndex((entry) => draftRecipients(final, entry).some((to) => to.toLowerCase() === wanted));
|
|
3078
|
-
if (index >= 0) {
|
|
3079
|
-
const hit = final.drafts[index];
|
|
3080
|
-
return {
|
|
3081
|
-
passed: true,
|
|
3082
|
-
reason: `draft ${hit.id ?? "?"} is addressed to ${email3}`,
|
|
3083
|
-
// The DRAFT row, not the backing message that actually holds the
|
|
3084
|
-
// address. A reader following this pointer lands on the thing the
|
|
3085
|
-
// sentence is about — "a draft addressed to X" — and the join to the
|
|
3086
|
-
// message is an implementation detail of how the twin exports, not the
|
|
3087
|
-
// claim (F-1197).
|
|
3088
|
-
evidenceStatePaths: [childStatePath(DRAFTS_PATH, index)]
|
|
3089
|
-
};
|
|
3090
|
-
}
|
|
3091
|
-
if (isTruncated(final, "drafts") || isTruncated(final, "messages")) {
|
|
3092
|
-
return { passed: false, status: "skipped", reason: "collection_truncated" };
|
|
3093
|
-
}
|
|
3094
|
-
return {
|
|
3095
|
-
passed: false,
|
|
3096
|
-
reason: `no draft is addressed to ${email3} (${final.drafts.length} draft(s) inspected)`,
|
|
3097
|
-
evidenceStatePaths: [DRAFTS_PATH]
|
|
3098
|
-
};
|
|
3099
|
-
}
|
|
3100
|
-
});
|
|
3101
|
-
var draftCountAtLeast = defineCheck({
|
|
3102
|
-
id: "gmail.draft-count-at-least",
|
|
3103
|
-
description: "Counts the rows in the exported `drafts` collection and asserts there are AT LEAST this many \u2014 a lower bound, so a mailbox with more drafts than asked still passes. It counts drafts the SEED placed there as well as any the examinee created, which means a criterion whose number the seed already satisfies can be passed by an agent that does nothing. That is a property of the task, not of this check, and `measure-criterion-discrimination` is where it surfaces.",
|
|
3104
|
-
template: "At least {count} drafts exist",
|
|
3105
|
-
params: { count: countWord },
|
|
3106
|
-
substrate: "final",
|
|
3107
|
-
polarity: () => "positive",
|
|
3108
|
-
// A threshold, not a literal hunted for inside state — there is nothing here a
|
|
3109
|
-
// redactor could silently delete.
|
|
3110
|
-
subject: () => null,
|
|
3111
|
-
//
|
|
3112
|
-
// The NUMERIC sentinel, and the argument D10 demands before it may be used.
|
|
3113
|
-
//
|
|
3114
|
-
// In every other pattern a numeric capture is a SELECTOR — an issue number, a
|
|
3115
|
-
// PR number — that the predicate RESOLVES before it scans anything, so
|
|
3116
|
-
// falsifying it early-returns "not found" and moves the verdict for a reason
|
|
3117
|
-
// that never reaches the assertion. Here the count is the ONLY slot and there
|
|
3118
|
-
// is nothing to resolve: it is compared directly against a cardinality the
|
|
3119
|
-
// predicate computes, so falsifying it moves the verdict THROUGH the
|
|
3120
|
-
// assertion. That is the same argument `stripe.payment-intent-amount` makes,
|
|
3121
|
-
// and it is why this check is the second entry in pome-cloud's
|
|
3122
|
-
// `NUMERIC_SENTINEL_ALLOWED`.
|
|
3123
|
-
vacuityMutant: (args) => ({ ...args, count: String(VACUITY_SENTINEL_NUMBER) }),
|
|
3124
|
-
discriminatingWorlds: ({ count }) => {
|
|
3125
|
-
const wanted = parseCount(count) ?? 1;
|
|
3126
|
-
const build = (n) => {
|
|
3127
|
-
const pairs = Array.from({ length: n }, (_, i) => draftAddressedTo(`draft_${i}`, ["a@example.com"]));
|
|
3128
|
-
return gmailState({
|
|
3129
|
-
drafts: pairs.map((p) => p.draft),
|
|
3130
|
-
messages: pairs.map((p) => p.message)
|
|
3131
|
-
});
|
|
3132
|
-
};
|
|
3133
|
-
return { passing: finalWorld(build(wanted)), failing: finalWorld(build(Math.max(0, wanted - 1))) };
|
|
3134
|
-
},
|
|
3135
|
-
evaluate({ count }, { final }) {
|
|
3136
|
-
const wanted = parseCount(count);
|
|
3137
|
-
if (wanted === null) {
|
|
3138
|
-
return { passed: false, status: "skipped", reason: `uncountable ("${count}")` };
|
|
3139
|
-
}
|
|
3140
|
-
if (final.drafts == null)
|
|
3141
|
-
return { passed: false, status: "skipped", reason: "state_incomplete" };
|
|
3142
|
-
const total = final.drafts.length;
|
|
3143
|
-
if (total < wanted && isTruncated(final, "drafts")) {
|
|
3144
|
-
return { passed: false, status: "skipped", reason: 'collection_truncated ("drafts")' };
|
|
3145
|
-
}
|
|
3146
|
-
return {
|
|
3147
|
-
passed: total >= wanted,
|
|
3148
|
-
reason: `${total} draft(s) exist (wanted at least ${wanted})`,
|
|
3149
|
-
// The collection whose LENGTH is the assertion. There is no narrower
|
|
3150
|
-
// address for a cardinality.
|
|
3151
|
-
evidenceStatePaths: [DRAFTS_PATH]
|
|
3152
|
-
};
|
|
3153
|
-
}
|
|
3154
|
-
});
|
|
3155
|
-
|
|
3156
|
-
// ../packages/twin-gmail/dist/src/check-labels.js
|
|
3157
|
-
var labelExists = defineCheck({
|
|
3158
|
-
id: "gmail.label-exists",
|
|
3159
|
-
description: "Asks whether the mailbox defines a label with this DISPLAY NAME, compared case-insensitively the way the twin's own seeder keys its uniqueness lookup. It reads the `labels` collection only \u2014 a label that exists but has been applied to nothing still passes, and a message carrying a label is `gmail.message-has-label`'s question, not this one. It deliberately does NOT match on the minted id: `Label_follow_up` is an id and `Follow Up` is the name of that same label, and letting one sentence mean both would make the assertion unreadable.",
|
|
3160
|
-
// The name slot admits spaces, which is what the corpus needs (`Parity
|
|
3161
|
-
// Complete`) and what separates this template from `gmail.message-has-label`'s
|
|
3162
|
-
// id slot. The two cannot claim one sentence: this one says "A label named",
|
|
3163
|
-
// that one says "Message ... has label".
|
|
3164
|
-
template: "A label named {label} exists",
|
|
3165
|
-
params: { label: labelName },
|
|
3166
|
-
substrate: "final",
|
|
3167
|
-
// An achievement — task 23's seed defines no `Parity Complete`, so the
|
|
3168
|
-
// examinee has to create it.
|
|
3169
|
-
polarity: () => "positive",
|
|
3170
|
-
// The name is a caller-supplied literal compared against state.
|
|
3171
|
-
subject: ({ label: label2 }) => label2,
|
|
3172
|
-
vacuityMutant: (args) => ({ ...args, label: VACUITY_SENTINEL }),
|
|
3173
|
-
discriminatingWorlds: ({ label: label2 }) => ({
|
|
3174
|
-
// Both worlds carry a non-empty `labels` collection, so the failing world
|
|
3175
|
-
// fails on the ASSERTION (`label_not_found`) rather than reproducing the
|
|
3176
|
-
// `state_incomplete` an empty world gives — the degenerate arm the probe
|
|
3177
|
-
// rejects.
|
|
3178
|
-
passing: finalWorld(gmailState({ labels: [systemLabel("INBOX"), userLabel("Label_1", label2)] })),
|
|
3179
|
-
failing: finalWorld(gmailState({ labels: [systemLabel("INBOX")] }))
|
|
3180
|
-
}),
|
|
3181
|
-
evaluate({ label: label2 }, { final }) {
|
|
3182
|
-
const found = resolveLabelByName(final, label2);
|
|
3183
|
-
if ("missing" in found) {
|
|
3184
|
-
if (found.missing.startsWith("label_not_found")) {
|
|
3185
|
-
return {
|
|
3186
|
-
passed: false,
|
|
3187
|
-
reason: `no label named "${label2}" exists`,
|
|
3188
|
-
// The label COLLECTION — the honest citation for a lookup that found
|
|
3189
|
-
// nothing, and the arm a reader most wants to open: see for yourself
|
|
3190
|
-
// that the name is not in it (F-1197).
|
|
3191
|
-
...found.searched === void 0 ? {} : { evidenceStatePaths: [found.searched] }
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
|
-
return missSkip(found);
|
|
3195
|
-
}
|
|
3196
|
-
return {
|
|
3197
|
-
passed: true,
|
|
3198
|
-
reason: `label "${label2}" exists (id ${found.found.id ?? "?"})`,
|
|
3199
|
-
evidenceStatePaths: [found.path]
|
|
3200
|
-
};
|
|
3201
|
-
}
|
|
3202
|
-
});
|
|
3203
|
-
|
|
3204
|
-
// ../packages/twin-gmail/dist/src/check-messages.js
|
|
3205
|
-
var messageHasLabel = defineCheck({
|
|
3206
|
-
id: "gmail.message-has-label",
|
|
3207
|
-
description: "Looks the message up by id, then asks the `messageLabels` JOIN whether any row links it to the named label. The label may be given as its minted id (`Label_follow_up`) or as the display name of the same label (`Follow Up`); a system label carries both as one string (`INBOX`, `STARRED`). It asserts nothing about the message's OTHER labels \u2014 a message carrying the named label plus five more passes. A message the export does not carry is a SKIP, not a fail, and so is a label collection the export truncated: absent from a capped list is not absent.",
|
|
3208
|
-
template: "Message {message} has label {label}",
|
|
3209
|
-
params: { message: messageId, label: labelRef },
|
|
3210
|
-
substrate: "final",
|
|
3211
|
-
// An achievement. The seed leaves `msg_support` on INBOX/UNREAD only, so the
|
|
3212
|
-
// examinee has to act for this to hold.
|
|
3213
|
-
polarity: () => "positive",
|
|
3214
|
-
// The label is a caller-supplied literal compared against state, so it is
|
|
3215
|
-
// declared. No first-party redactor pattern touches a Gmail label id, which is
|
|
3216
|
-
// exactly why declaring it keeps that a verified fact rather than an
|
|
3217
|
-
// assumption — a team's own redaction config is not first-party.
|
|
3218
|
-
subject: ({ label: label2 }) => label2,
|
|
3219
|
-
// The message id only SELECTS; falsifying it moves the verdict through the
|
|
3220
|
-
// lookup rather than the assertion. The label is the scanned literal.
|
|
3221
|
-
vacuityMutant: (args) => ({ ...args, label: VACUITY_SENTINEL }),
|
|
3222
|
-
discriminatingWorlds: ({ message: id2, label: label2 }) => {
|
|
3223
|
-
const labels2 = [systemLabel("INBOX"), userLabel(label2, label2)];
|
|
3224
|
-
const base = { messages: [message(id2)], labels: labels2 };
|
|
3225
|
-
return {
|
|
3226
|
-
passing: finalWorld(gmailState({ ...base, messageLabels: [messageLabel(id2, "INBOX"), messageLabel(id2, label2)] })),
|
|
3227
|
-
failing: finalWorld(gmailState({ ...base, messageLabels: [messageLabel(id2, "INBOX")] }))
|
|
3228
|
-
};
|
|
3229
|
-
},
|
|
3230
|
-
evaluate({ message: id2, label: label2 }, { final }) {
|
|
3231
|
-
const found = resolveMessage(final, id2);
|
|
3232
|
-
if ("missing" in found)
|
|
3233
|
-
return missSkip(found);
|
|
3234
|
-
const carried = messageCarriesLabel(final, id2, label2);
|
|
3235
|
-
if ("missing" in carried)
|
|
3236
|
-
return missSkip(carried);
|
|
3237
|
-
return {
|
|
3238
|
-
passed: carried.found,
|
|
3239
|
-
reason: carried.found ? `message ${id2} carries label ${label2}` : `message ${id2} does not carry label ${label2}`,
|
|
3240
|
-
// BOTH halves of the lookup, because the predicate really does read two
|
|
3241
|
-
// places: the message row it resolved by id, and the join table it then
|
|
3242
|
-
// questioned. Labels are not nested under their message in this export —
|
|
3243
|
-
// that is the whole reason `messageCarriesLabel` exists — so one pointer
|
|
3244
|
-
// could not say where this verdict came from (F-1197).
|
|
3245
|
-
evidenceStatePaths: [found.path, carried.path]
|
|
3246
|
-
};
|
|
3247
|
-
}
|
|
3248
|
-
});
|
|
3249
|
-
var mailboxLabelCount = defineCheck({
|
|
3250
|
-
id: "gmail.mailbox-label-count",
|
|
3251
|
-
description: "Counts the messages in the named mailbox that the `messageLabels` JOIN links to the named label, and asserts the total is EXACTLY the number given. Not 'at least' \u2014 a mailbox with six SENT messages fails a criterion asking for five, which is what makes it able to catch a duplicate send. A mailbox the export lists but does not contain is a SKIP; so is a truncated collection.",
|
|
3252
|
-
template: "The mailbox `{mailbox}` has exactly {count} messages labeled {label}",
|
|
3253
|
-
params: { mailbox: mailboxRef, count: exactCount, label: labelRef },
|
|
3254
|
-
substrate: "final",
|
|
3255
|
-
// The count carries the direction: asserting zero is a prohibition, any
|
|
3256
|
-
// other count is work the examinee must do. Preserved from the legacy rule,
|
|
3257
|
-
// which computed the same thing per match.
|
|
3258
|
-
polarity: ({ count }) => Number(count) === 0 ? "negative" : "positive",
|
|
3259
|
-
subject: ({ label: label2 }) => label2,
|
|
3260
|
-
// The mailbox resolves and the count is compared to a derived total; the
|
|
3261
|
-
// label is the literal actually scanned in state. Pointing the mutant at the
|
|
3262
|
-
// count would falsify a threshold rather than a lookup — see
|
|
3263
|
-
// `gmail.draft-count-at-least`, where the count IS the only slot.
|
|
3264
|
-
vacuityMutant: (args) => ({ ...args, label: VACUITY_SENTINEL }),
|
|
3265
|
-
discriminatingWorlds: ({ mailbox, count, label: label2 }) => {
|
|
3266
|
-
const wanted = Number(count);
|
|
3267
|
-
const world = (n) => {
|
|
3268
|
-
const messages = Array.from({ length: n }, (_, i) => message(`msg_${i}`, { mailboxEmail: mailbox }));
|
|
3269
|
-
return gmailState({
|
|
3270
|
-
mailboxes: [{ email: mailbox }],
|
|
3271
|
-
messages,
|
|
3272
|
-
labels: [systemLabel(label2)],
|
|
3273
|
-
messageLabels: messages.map((m) => ({ mailboxEmail: mailbox, messageId: m.id, labelId: label2 }))
|
|
3274
|
-
});
|
|
3275
|
-
};
|
|
3276
|
-
return { passing: finalWorld(world(wanted)), failing: finalWorld(world(wanted + 1)) };
|
|
3277
|
-
},
|
|
3278
|
-
evaluate({ mailbox, count, label: label2 }, { final }) {
|
|
3279
|
-
const wanted = Number(count);
|
|
3280
|
-
if (final.messages == null || final.messageLabels == null) {
|
|
3281
|
-
return { passed: false, status: "skipped", reason: "state_incomplete" };
|
|
3282
|
-
}
|
|
3283
|
-
if (isTruncated(final, "messages") || isTruncated(final, "messageLabels")) {
|
|
3284
|
-
return { passed: false, status: "skipped", reason: "collection_truncated" };
|
|
3285
|
-
}
|
|
3286
|
-
if (final.mailboxes != null && !final.mailboxes.some((mb) => (mb.email ?? "").toLowerCase() === mailbox.toLowerCase())) {
|
|
3287
|
-
return { passed: false, status: "skipped", reason: `mailbox_not_found ("${mailbox}")` };
|
|
3288
|
-
}
|
|
3289
|
-
const ids = labelIdsFor(final, label2);
|
|
3290
|
-
const labeled = new Set(final.messageLabels.filter((row) => ids.has((row.labelId ?? "").toLowerCase())).map((row) => (row.messageId ?? "").toLowerCase()));
|
|
3291
|
-
const total = final.messages.filter((msg) => (msg.mailboxEmail ?? "").toLowerCase() === mailbox.toLowerCase() && msg.id != null && labeled.has(msg.id.toLowerCase())).length;
|
|
3292
|
-
return {
|
|
3293
|
-
passed: total === wanted,
|
|
3294
|
-
reason: `mailbox "${mailbox}" has ${total} message(s) labeled ${label2} (wanted ${wanted})`,
|
|
3295
|
-
// The two collections the count is computed FROM. The count itself lives
|
|
3296
|
-
// nowhere in the tree, so there is no narrower honest address — and the
|
|
3297
|
-
// guards above already established that both collections are present and
|
|
3298
|
-
// un-capped, which is what makes these pointers resolve.
|
|
3299
|
-
evidenceStatePaths: [MESSAGES_PATH, MESSAGE_LABELS_PATH]
|
|
3300
|
-
};
|
|
3301
|
-
}
|
|
3302
|
-
});
|
|
3303
|
-
var oneMessagePerRecipient = defineCheck({
|
|
3304
|
-
id: "gmail.one-message-per-recipient",
|
|
3305
|
-
description: "Flattens every addressee across the messages carrying the named label and asserts each address appears EXACTLY ONCE \u2014 and, when a count is named, that there are that many messages, that many distinct recipients, and no more addressee slots than that. It is the assertion that separates 'sent to everyone' from 'sent to everyone, some of them twice', which an exact total alone cannot do.",
|
|
3306
|
-
template: "Exactly one {label} message is addressed to each of the {count} recipients",
|
|
3307
|
-
params: { label: labelRef, count: countWord },
|
|
3308
|
-
substrate: "final",
|
|
3309
|
-
polarity: () => "positive",
|
|
3310
|
-
subject: ({ label: label2 }) => label2,
|
|
3311
|
-
// The label is scanned; the count is a declared arity compared to a derived
|
|
3312
|
-
// one. Same argument as `mailboxLabelCount`.
|
|
3313
|
-
vacuityMutant: (args) => ({ ...args, label: VACUITY_SENTINEL }),
|
|
3314
|
-
discriminatingWorlds: ({ label: label2, count }) => {
|
|
3315
|
-
const wanted = parseCount(count) ?? 1;
|
|
3316
|
-
const recipients = Array.from({ length: wanted }, (_, i) => `user${i}@example.com`);
|
|
3317
|
-
const build = (addressees) => {
|
|
3318
|
-
const messages = addressees.map((to, i) => message(`msg_${i}`, { to }));
|
|
3319
|
-
return finalWorld(gmailState({
|
|
3320
|
-
messages,
|
|
3321
|
-
labels: [systemLabel(label2)],
|
|
3322
|
-
messageLabels: messages.map((m) => messageLabel(m.id, label2))
|
|
3323
|
-
}));
|
|
3324
|
-
};
|
|
3325
|
-
return {
|
|
3326
|
-
passing: build(recipients.map((r) => [r])),
|
|
3327
|
-
// The DUPLICATE SEND, not a missing one: the same number of messages, one
|
|
3328
|
-
// recipient served twice and another not at all. A world with fewer
|
|
3329
|
-
// messages would fail on arity, which the exact-count check already covers.
|
|
3330
|
-
failing: build([[recipients[0]], ...recipients.slice(2).map((r) => [r]), [recipients[0]]])
|
|
3331
|
-
};
|
|
3332
|
-
},
|
|
3333
|
-
evaluate({ label: label2, count }, { final }) {
|
|
3334
|
-
const wanted = parseCount(count);
|
|
3335
|
-
if (final.messages == null || final.messageLabels == null) {
|
|
3336
|
-
return { passed: false, status: "skipped", reason: "state_incomplete" };
|
|
3337
|
-
}
|
|
3338
|
-
if (isTruncated(final, "messages") || isTruncated(final, "messageLabels")) {
|
|
3339
|
-
return { passed: false, status: "skipped", reason: "collection_truncated" };
|
|
3340
|
-
}
|
|
3341
|
-
const ids = labelIdsFor(final, label2);
|
|
3342
|
-
const labeled = new Set(final.messageLabels.filter((row) => ids.has((row.labelId ?? "").toLowerCase())).map((row) => (row.messageId ?? "").toLowerCase()));
|
|
3343
|
-
const sent = final.messages.filter((msg) => msg.id != null && labeled.has(msg.id.toLowerCase()));
|
|
3344
|
-
const addressees = sent.flatMap((msg) => (msg.to ?? []).map((to) => to.toLowerCase()));
|
|
3345
|
-
const distinct = new Set(addressees);
|
|
3346
|
-
const noDuplicates = addressees.length === distinct.size;
|
|
3347
|
-
const arityOk = wanted == null ? sent.length === distinct.size : sent.length === wanted && distinct.size === wanted && addressees.length === wanted;
|
|
3348
|
-
const passed = noDuplicates && arityOk;
|
|
3349
|
-
return {
|
|
3350
|
-
passed,
|
|
3351
|
-
reason: passed ? `${sent.length} ${label2} message(s), one per distinct recipient, no duplicates` : `${sent.length} ${label2} message(s) to ${distinct.size} distinct recipient(s) (${addressees.length} addressee slot(s)${wanted == null ? "" : `, wanted ${wanted}`}${noDuplicates ? "" : ", duplicate send detected"})`,
|
|
3352
|
-
// Same two collections, same reason: the duplicate this check exists to
|
|
3353
|
-
// catch is a property of the addressee lists across `messages`, filtered
|
|
3354
|
-
// by `messageLabels`, and neither the flattened list nor its distinct set
|
|
3355
|
-
// is anything the tree holds.
|
|
3356
|
-
evidenceStatePaths: [MESSAGES_PATH, MESSAGE_LABELS_PATH]
|
|
3357
|
-
};
|
|
3358
|
-
}
|
|
3359
|
-
});
|
|
3360
|
-
|
|
3361
|
-
// ../packages/twin-gmail/dist/src/check-tape.js
|
|
3362
|
-
var noUnsupportedEndpoint = defineCheck({
|
|
3363
|
-
id: "gmail.no-unsupported-endpoint",
|
|
3364
|
-
description: 'Scans the recorded call tape for any request the twin answered with fidelity "unsupported" \u2014 a route it does not implement, answered 501. It asserts nothing about whether the run SUCCEEDED, and nothing about calls that were merely rejected: a 404 or a 422 from a route the twin does implement is a semantic answer and passes this check. The tape is scoped to this twin by the engine before the check sees it, so an unsupported call to a DIFFERENT twin in a multi-twin session cannot fail it \u2014 which matters here, because task 27 runs gmail and github together.',
|
|
3365
|
-
// No slots, and no twin word. The legacy cloud rule accepted "No unsupported
|
|
3366
|
-
// Gmail endpoint was called" alongside the bare form, plus plural and `were`
|
|
3367
|
-
// variants, because an author typed English. Under position 2 an author PICKS
|
|
3368
|
-
// the check, so those variants are retired rather than ported — the same
|
|
3369
|
-
// decision `github.no-unsupported-endpoint` made, and this is now the same
|
|
3370
|
-
// sentence on both twins, resolved per-twin by the engine.
|
|
3371
|
-
template: "No unsupported endpoint was called",
|
|
3372
|
-
params: {},
|
|
3373
|
-
substrate: "tape",
|
|
3374
|
-
// A prohibition. Nothing is required to happen; only the examinee reaching for
|
|
3375
|
-
// an unimplemented route can break it.
|
|
3376
|
-
polarity: () => "negative",
|
|
3377
|
-
// No caller-supplied literal is hunted for in any substrate, so there is
|
|
3378
|
-
// nothing a redactor could silently delete out from under this check.
|
|
3379
|
-
subject: () => null,
|
|
3380
|
-
// No slots, so the sentence carries no literal to falsify. The trigger is "a
|
|
3381
|
-
// call with fidelity=unsupported exists", which lives on the tape and not in
|
|
3382
|
-
// the sentence. Reported as `no_trigger`, never as clean.
|
|
3383
|
-
vacuityMutant: () => null,
|
|
3384
|
-
discriminatingWorlds: () => ({
|
|
3385
|
-
passing: tapeWorld([
|
|
3386
|
-
{
|
|
3387
|
-
twin: "gmail",
|
|
3388
|
-
method: "GET",
|
|
3389
|
-
path: "/gmail/v1/users/me/messages",
|
|
3390
|
-
status: 200,
|
|
3391
|
-
fidelity: "semantic",
|
|
3392
|
-
event_id: "evt_ok"
|
|
3393
|
-
}
|
|
3394
|
-
]),
|
|
3395
|
-
failing: tapeWorld([
|
|
3396
|
-
{
|
|
3397
|
-
twin: "gmail",
|
|
3398
|
-
method: "POST",
|
|
3399
|
-
path: "/gmail/v1/users/me/watch",
|
|
3400
|
-
status: 501,
|
|
3401
|
-
fidelity: "unsupported",
|
|
3402
|
-
event_id: "evt_bad"
|
|
3403
|
-
}
|
|
3404
|
-
])
|
|
3405
|
-
}),
|
|
3406
|
-
evaluate(_args, { tape }) {
|
|
3407
|
-
if (tape === null)
|
|
3408
|
-
return { passed: false, reason: "tape_missing", status: "skipped" };
|
|
3409
|
-
const unsupported2 = tape.filter((event) => event.fidelity === "unsupported");
|
|
3410
|
-
if (unsupported2.length === 0) {
|
|
3411
|
-
return {
|
|
3412
|
-
passed: true,
|
|
3413
|
-
reason: `no unsupported Gmail endpoint was called (${tape.length} call(s) inspected)`
|
|
3414
|
-
};
|
|
3415
|
-
}
|
|
3416
|
-
const evidenceEventIds = unsupported2.map((event) => event.event_id).filter((id2) => typeof id2 === "string" && id2 !== "");
|
|
3417
|
-
const outcome = {
|
|
3418
|
-
passed: false,
|
|
3419
|
-
reason: `${unsupported2.length} unsupported Gmail call(s): [${unsupported2.map((event) => event.path ?? "?").join(", ")}]`
|
|
3420
|
-
};
|
|
3421
|
-
return evidenceEventIds.length > 0 ? { ...outcome, evidenceEventIds } : outcome;
|
|
3422
|
-
}
|
|
3423
|
-
});
|
|
3424
|
-
|
|
3425
|
-
// ../packages/twin-gmail/dist/src/checks.js
|
|
3426
|
-
var GMAIL_CHECKS = [
|
|
3427
|
-
messageHasLabel,
|
|
3428
|
-
labelExists,
|
|
3429
|
-
draftAddressedTo2,
|
|
3430
|
-
draftCountAtLeast,
|
|
3431
|
-
mailboxLabelCount,
|
|
3432
|
-
oneMessagePerRecipient,
|
|
3433
|
-
// Last, because these are the only ones that assert about the RUN rather than
|
|
3434
|
-
// the world it left behind — twin-github orders its tape checks the same way.
|
|
3435
|
-
noUnsupportedEndpoint
|
|
3436
|
-
];
|
|
3437
|
-
|
|
3438
2177
|
// ../packages/twin-gmail/dist/src/identity.js
|
|
3439
2178
|
var DEFAULT_GMAIL_EMAIL = "pome-agent@pome-twin.test";
|
|
3440
2179
|
function identityFromSession(session) {
|
|
@@ -4592,7 +3331,7 @@ var mcp_tools_list_canonical_default = {
|
|
|
4592
3331
|
]
|
|
4593
3332
|
}
|
|
4594
3333
|
};
|
|
4595
|
-
var
|
|
3334
|
+
var email = z.string().trim().email();
|
|
4596
3335
|
var pageSize = z.number().int().min(1).max(50).optional();
|
|
4597
3336
|
var pageToken = z.string().optional();
|
|
4598
3337
|
var labelIds = z.array(z.string().min(1)).min(1).max(100);
|
|
@@ -4610,13 +3349,13 @@ var attachmentInputSchema = z.object({
|
|
|
4610
3349
|
}).passthrough();
|
|
4611
3350
|
var createDraftInputSchema = z.object({
|
|
4612
3351
|
attachments: z.array(attachmentInputSchema).max(100).optional(),
|
|
4613
|
-
bcc: z.array(
|
|
3352
|
+
bcc: z.array(email).max(500).optional(),
|
|
4614
3353
|
body: z.string().optional(),
|
|
4615
|
-
cc: z.array(
|
|
3354
|
+
cc: z.array(email).max(500).optional(),
|
|
4616
3355
|
htmlBody: z.string().optional(),
|
|
4617
3356
|
replyToMessageId: z.string().min(1).optional(),
|
|
4618
3357
|
subject: z.string().optional(),
|
|
4619
|
-
to: z.array(
|
|
3358
|
+
to: z.array(email).max(500).optional()
|
|
4620
3359
|
}).passthrough();
|
|
4621
3360
|
var listDraftsInputSchema = z.object({
|
|
4622
3361
|
pageSize,
|
|
@@ -4766,9 +3505,9 @@ function decodePageToken(token, binding, snapshot, secret = resolvePageTokenSecr
|
|
|
4766
3505
|
invalidArgument("Invalid page token");
|
|
4767
3506
|
}
|
|
4768
3507
|
}
|
|
4769
|
-
function normalizeListBinding(route,
|
|
3508
|
+
function normalizeListBinding(route, email2, values) {
|
|
4770
3509
|
const canonical = Object.entries(values).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => [key, Array.isArray(value) ? [...value].sort() : value]);
|
|
4771
|
-
return createHash("sha256").update(JSON.stringify([route,
|
|
3510
|
+
return createHash("sha256").update(JSON.stringify([route, email2.toLowerCase(), canonical])).digest("hex");
|
|
4772
3511
|
}
|
|
4773
3512
|
|
|
4774
3513
|
// ../packages/twin-gmail/dist/src/mcp.js
|
|
@@ -4779,8 +3518,8 @@ var implementations = {
|
|
|
4779
3518
|
mutation: true,
|
|
4780
3519
|
handler: (domain, args, ctx) => mutate(domain, ctx, () => {
|
|
4781
3520
|
const input = args;
|
|
4782
|
-
const
|
|
4783
|
-
const
|
|
3521
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
3522
|
+
const draft2 = domain.createComposedDraft(email2, {
|
|
4784
3523
|
to: input.to,
|
|
4785
3524
|
cc: input.cc,
|
|
4786
3525
|
bcc: input.bcc,
|
|
@@ -4796,7 +3535,7 @@ var implementations = {
|
|
|
4796
3535
|
data: attachment2.content
|
|
4797
3536
|
}))
|
|
4798
3537
|
});
|
|
4799
|
-
return draftResult(
|
|
3538
|
+
return draftResult(draft2.id, draft2.message, false);
|
|
4800
3539
|
})
|
|
4801
3540
|
},
|
|
4802
3541
|
list_drafts: {
|
|
@@ -4804,14 +3543,14 @@ var implementations = {
|
|
|
4804
3543
|
mutation: false,
|
|
4805
3544
|
handler: (domain, args, ctx) => {
|
|
4806
3545
|
const input = args;
|
|
4807
|
-
const
|
|
4808
|
-
const drafts2 = domain.listDrafts(
|
|
4809
|
-
const page = paginate(domain,
|
|
3546
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
3547
|
+
const drafts2 = domain.listDrafts(email2, input.query ?? "");
|
|
3548
|
+
const page = paginate(domain, email2, "drafts.list", drafts2, input.pageSize, input.pageToken, {
|
|
4810
3549
|
query: input.query ?? "",
|
|
4811
3550
|
view: input.view ?? "DRAFT_VIEW_FULL"
|
|
4812
3551
|
});
|
|
4813
3552
|
return {
|
|
4814
|
-
drafts: page.items.map((
|
|
3553
|
+
drafts: page.items.map((draft2) => draftResult(draft2.id, draft2.message, input.view === "DRAFT_VIEW_METADATA_ONLY")),
|
|
4815
3554
|
...page.nextPageToken ? { nextPageToken: page.nextPageToken } : {}
|
|
4816
3555
|
};
|
|
4817
3556
|
}
|
|
@@ -4821,8 +3560,8 @@ var implementations = {
|
|
|
4821
3560
|
mutation: false,
|
|
4822
3561
|
handler: (domain, args, ctx) => {
|
|
4823
3562
|
const input = args;
|
|
4824
|
-
const
|
|
4825
|
-
return threadResult(domain.getThread(
|
|
3563
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
3564
|
+
return threadResult(domain.getThread(email2, input.threadId), normalizeMessageFormat(input.messageFormat));
|
|
4826
3565
|
}
|
|
4827
3566
|
},
|
|
4828
3567
|
get_message: {
|
|
@@ -4830,8 +3569,8 @@ var implementations = {
|
|
|
4830
3569
|
mutation: false,
|
|
4831
3570
|
handler: (domain, args, ctx) => {
|
|
4832
3571
|
const input = args;
|
|
4833
|
-
const
|
|
4834
|
-
return messageResult(domain.getMessage(
|
|
3572
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
3573
|
+
return messageResult(domain.getMessage(email2, input.messageId), normalizeMessageFormat(input.messageFormat));
|
|
4835
3574
|
}
|
|
4836
3575
|
},
|
|
4837
3576
|
search_threads: {
|
|
@@ -4839,11 +3578,11 @@ var implementations = {
|
|
|
4839
3578
|
mutation: false,
|
|
4840
3579
|
handler: (domain, args, ctx) => {
|
|
4841
3580
|
const input = args;
|
|
4842
|
-
const
|
|
4843
|
-
const threads = domain.searchThreads(
|
|
3581
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
3582
|
+
const threads = domain.searchThreads(email2, input.query ?? "", {
|
|
4844
3583
|
includeTrash: input.includeTrash
|
|
4845
3584
|
});
|
|
4846
|
-
const page = paginate(domain,
|
|
3585
|
+
const page = paginate(domain, email2, "threads.search", threads, input.pageSize, input.pageToken, {
|
|
4847
3586
|
includeTrash: input.includeTrash ?? false,
|
|
4848
3587
|
query: input.query ?? "",
|
|
4849
3588
|
view: input.view ?? "THREAD_VIEW_MINIMAL"
|
|
@@ -4873,8 +3612,8 @@ var implementations = {
|
|
|
4873
3612
|
mutation: false,
|
|
4874
3613
|
handler: (domain, args, ctx) => {
|
|
4875
3614
|
const input = args;
|
|
4876
|
-
const
|
|
4877
|
-
const page = paginate(domain,
|
|
3615
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
3616
|
+
const page = paginate(domain, email2, "labels.list", domain.listUserLabels(email2), input.pageSize, input.pageToken, {});
|
|
4878
3617
|
return {
|
|
4879
3618
|
labels: page.items.map(labelResult),
|
|
4880
3619
|
...page.nextPageToken ? { nextPageToken: page.nextPageToken } : {}
|
|
@@ -4900,18 +3639,18 @@ var implementations = {
|
|
|
4900
3639
|
mutation: true,
|
|
4901
3640
|
handler: (domain, args, ctx) => mutate(domain, ctx, () => {
|
|
4902
3641
|
const input = args;
|
|
4903
|
-
const
|
|
3642
|
+
const email2 = identityFromSession(ctx.session).email;
|
|
4904
3643
|
if (input.autoCreateParentLabels !== false) {
|
|
4905
3644
|
const parts = input.displayName.split("/");
|
|
4906
3645
|
for (let index = 1; index < parts.length; index += 1) {
|
|
4907
3646
|
const parent = parts.slice(0, index).join("/");
|
|
4908
|
-
const exists = domain.listUserLabels(
|
|
3647
|
+
const exists = domain.listUserLabels(email2).some((label3) => label3.name.toLowerCase() === parent.toLowerCase());
|
|
4909
3648
|
if (!exists)
|
|
4910
|
-
domain.createLabel(
|
|
3649
|
+
domain.createLabel(email2, parent);
|
|
4911
3650
|
}
|
|
4912
3651
|
}
|
|
4913
|
-
const created = domain.createLabel(
|
|
4914
|
-
const label2 = domain.listUserLabels(
|
|
3652
|
+
const created = domain.createLabel(email2, input.displayName, input.color);
|
|
3653
|
+
const label2 = domain.listUserLabels(email2).find((item) => item.id === created.id);
|
|
4915
3654
|
if (!label2)
|
|
4916
3655
|
throw new Error("Created label was not found");
|
|
4917
3656
|
return labelResult(label2);
|
|
@@ -4968,52 +3707,52 @@ function mutate(domain, ctx, operation) {
|
|
|
4968
3707
|
ctx.reportDelta(gmailStateDelta(before, domain.exportState()));
|
|
4969
3708
|
return output;
|
|
4970
3709
|
}
|
|
4971
|
-
function draftResult(
|
|
3710
|
+
function draftResult(id, message, metadataOnly) {
|
|
4972
3711
|
return {
|
|
4973
|
-
id
|
|
4974
|
-
threadId:
|
|
4975
|
-
toRecipients:
|
|
4976
|
-
ccRecipients:
|
|
4977
|
-
bccRecipients:
|
|
4978
|
-
date: dateOnly(
|
|
3712
|
+
id,
|
|
3713
|
+
threadId: message.threadId,
|
|
3714
|
+
toRecipients: message.to,
|
|
3715
|
+
ccRecipients: message.cc,
|
|
3716
|
+
bccRecipients: message.bcc,
|
|
3717
|
+
date: dateOnly(message.internalDate),
|
|
4979
3718
|
...!metadataOnly ? {
|
|
4980
|
-
subject:
|
|
4981
|
-
plaintextBody:
|
|
4982
|
-
...
|
|
3719
|
+
subject: message.subject,
|
|
3720
|
+
plaintextBody: message.text,
|
|
3721
|
+
...message.html ? { htmlBody: message.html } : {}
|
|
4983
3722
|
} : {}
|
|
4984
3723
|
};
|
|
4985
3724
|
}
|
|
4986
3725
|
function threadResult(thread, format) {
|
|
4987
3726
|
return {
|
|
4988
3727
|
id: thread.id,
|
|
4989
|
-
messages: thread.messages.map((
|
|
3728
|
+
messages: thread.messages.map((message) => messageResult(message, format))
|
|
4990
3729
|
};
|
|
4991
3730
|
}
|
|
4992
|
-
function messageResult(
|
|
3731
|
+
function messageResult(message, format) {
|
|
4993
3732
|
const metadata = {
|
|
4994
|
-
id:
|
|
4995
|
-
labelIds:
|
|
4996
|
-
date: dateOnly(
|
|
3733
|
+
id: message.id,
|
|
3734
|
+
labelIds: message.labelIds,
|
|
3735
|
+
date: dateOnly(message.internalDate)
|
|
4997
3736
|
};
|
|
4998
3737
|
if (format === "metadata")
|
|
4999
3738
|
return metadata;
|
|
5000
3739
|
const minimal = {
|
|
5001
3740
|
...metadata,
|
|
5002
|
-
snippet:
|
|
5003
|
-
subject:
|
|
5004
|
-
sender:
|
|
5005
|
-
toRecipients:
|
|
5006
|
-
ccRecipients:
|
|
3741
|
+
snippet: message.snippet,
|
|
3742
|
+
subject: message.subject,
|
|
3743
|
+
sender: message.from,
|
|
3744
|
+
toRecipients: message.to,
|
|
3745
|
+
ccRecipients: message.cc
|
|
5007
3746
|
};
|
|
5008
3747
|
if (format === "minimal")
|
|
5009
3748
|
return minimal;
|
|
5010
3749
|
return {
|
|
5011
3750
|
...minimal,
|
|
5012
|
-
plaintextBody:
|
|
5013
|
-
...
|
|
5014
|
-
attachmentIds:
|
|
5015
|
-
...
|
|
5016
|
-
attachments:
|
|
3751
|
+
plaintextBody: message.text,
|
|
3752
|
+
...message.html ? { htmlBody: message.html } : {},
|
|
3753
|
+
attachmentIds: message.attachments.map((attachment2) => attachment2.id),
|
|
3754
|
+
...message.attachments.length ? {
|
|
3755
|
+
attachments: message.attachments.map((attachment2) => ({
|
|
5017
3756
|
id: attachment2.id,
|
|
5018
3757
|
filename: attachment2.filename,
|
|
5019
3758
|
mimeType: attachment2.mimeType
|
|
@@ -5042,10 +3781,10 @@ function resolveSensitiveLabel(option) {
|
|
|
5042
3781
|
return option;
|
|
5043
3782
|
invalidArgument("labelOption must be TRASH or SPAM");
|
|
5044
3783
|
}
|
|
5045
|
-
function paginate(domain,
|
|
3784
|
+
function paginate(domain, email2, route, items, requestedSize, token, filter2) {
|
|
5046
3785
|
const size = requestedSize ?? 20;
|
|
5047
|
-
const snapshot = domain.currentHistoryIdFor(
|
|
5048
|
-
const binding = normalizeListBinding(route,
|
|
3786
|
+
const snapshot = domain.currentHistoryIdFor(email2);
|
|
3787
|
+
const binding = normalizeListBinding(route, email2, filter2);
|
|
5049
3788
|
const offset = token ? decodePageToken(token, binding, snapshot) : 0;
|
|
5050
3789
|
if (offset > items.length)
|
|
5051
3790
|
invalidArgument("Invalid pageToken");
|
|
@@ -5284,16 +4023,16 @@ async function readDraftSend(c) {
|
|
|
5284
4023
|
const contentType = c.req.header("content-type") ?? "";
|
|
5285
4024
|
if (/^application\/json\b/i.test(contentType) || /^text\/json\b/i.test(contentType)) {
|
|
5286
4025
|
const body = await readJsonObject(c);
|
|
5287
|
-
const
|
|
5288
|
-
const
|
|
5289
|
-
if (!
|
|
4026
|
+
const id = stringField(body, "id");
|
|
4027
|
+
const message = objectField(body, "message");
|
|
4028
|
+
if (!id && !message)
|
|
5290
4029
|
invalidArgument("Draft id is required");
|
|
5291
4030
|
return {
|
|
5292
|
-
id
|
|
5293
|
-
...
|
|
4031
|
+
id,
|
|
4032
|
+
...message ? {
|
|
5294
4033
|
message: {
|
|
5295
|
-
raw: stringField(
|
|
5296
|
-
threadId: stringField(
|
|
4034
|
+
raw: stringField(message, "raw", true),
|
|
4035
|
+
threadId: stringField(message, "threadId")
|
|
5297
4036
|
}
|
|
5298
4037
|
} : {}
|
|
5299
4038
|
};
|
|
@@ -5363,13 +4102,13 @@ var UPLOAD = "/upload/gmail/v1/users/:userId/drafts";
|
|
|
5363
4102
|
function registerDraftRoutes(app, kit) {
|
|
5364
4103
|
const { serializers, domain } = kit;
|
|
5365
4104
|
app.get(BASE, kit.read((c) => {
|
|
5366
|
-
const
|
|
4105
|
+
const email2 = emailFromContext(c);
|
|
5367
4106
|
const query = c.req.query("q") ?? "";
|
|
5368
4107
|
const includeSpamTrash = booleanQuery(c, "includeSpamTrash");
|
|
5369
|
-
const drafts2 = asInputError(() => domain.drafts(
|
|
4108
|
+
const drafts2 = asInputError(() => domain.drafts(email2, query, includeSpamTrash));
|
|
5370
4109
|
const maxResults = numberQuery(c, "maxResults", 100, 500);
|
|
5371
|
-
const snapshot = domain.currentHistoryIdFor(
|
|
5372
|
-
const binding = normalizeListBinding("drafts.list",
|
|
4110
|
+
const snapshot = domain.currentHistoryIdFor(email2);
|
|
4111
|
+
const binding = normalizeListBinding("drafts.list", email2, { query, includeSpamTrash });
|
|
5373
4112
|
const { page, nextPageToken } = paginate2(drafts2, {
|
|
5374
4113
|
maxResults,
|
|
5375
4114
|
pageToken: c.req.query("pageToken"),
|
|
@@ -5379,9 +4118,9 @@ function registerDraftRoutes(app, kit) {
|
|
|
5379
4118
|
return {
|
|
5380
4119
|
body: {
|
|
5381
4120
|
...page.length ? {
|
|
5382
|
-
drafts: page.map((
|
|
5383
|
-
id:
|
|
5384
|
-
message: { id:
|
|
4121
|
+
drafts: page.map((draft2) => ({
|
|
4122
|
+
id: draft2.id,
|
|
4123
|
+
message: { id: draft2.message.id, threadId: draft2.message.threadId }
|
|
5385
4124
|
}))
|
|
5386
4125
|
} : {},
|
|
5387
4126
|
resultSizeEstimate: drafts2.length,
|
|
@@ -5390,37 +4129,37 @@ function registerDraftRoutes(app, kit) {
|
|
|
5390
4129
|
};
|
|
5391
4130
|
}));
|
|
5392
4131
|
const create = kit.write(async (c) => {
|
|
5393
|
-
const
|
|
4132
|
+
const email2 = emailFromContext(c);
|
|
5394
4133
|
const input = await readMessageWrite(c, true);
|
|
5395
|
-
const
|
|
5396
|
-
return { body: serializers.draft(
|
|
4134
|
+
const draft2 = asInputError(() => domain.createDraft(email2, input.raw, { threadId: input.threadId }));
|
|
4135
|
+
return { body: serializers.draft(email2, draft2, "full") };
|
|
5397
4136
|
});
|
|
5398
4137
|
app.post(BASE, create);
|
|
5399
4138
|
app.post(UPLOAD, create);
|
|
5400
4139
|
const send = kit.write(async (c) => {
|
|
5401
|
-
const
|
|
4140
|
+
const email2 = emailFromContext(c);
|
|
5402
4141
|
const input = await readDraftSend(c);
|
|
5403
4142
|
if (input.id) {
|
|
5404
4143
|
if (input.message) {
|
|
5405
|
-
asInputError(() => domain.updateDraft(
|
|
4144
|
+
asInputError(() => domain.updateDraft(email2, input.id, input.message.raw, { threadId: input.message.threadId }));
|
|
5406
4145
|
}
|
|
5407
|
-
const sent2 = asInputError(() => domain.sendDraft(
|
|
5408
|
-
return { body: serializers.message(
|
|
4146
|
+
const sent2 = asInputError(() => domain.sendDraft(email2, input.id));
|
|
4147
|
+
return { body: serializers.message(email2, sent2.sender, "full") };
|
|
5409
4148
|
}
|
|
5410
|
-
const sent = asInputError(() => domain.sendMessage(
|
|
5411
|
-
return { body: serializers.message(
|
|
4149
|
+
const sent = asInputError(() => domain.sendMessage(email2, input.message.raw, { threadId: input.message.threadId }));
|
|
4150
|
+
return { body: serializers.message(email2, sent.sender, "full") };
|
|
5412
4151
|
});
|
|
5413
4152
|
app.post(`${BASE}/send`, send);
|
|
5414
4153
|
app.post(`${UPLOAD}/send`, send);
|
|
5415
4154
|
app.get(`${BASE}/:id`, kit.read((c) => {
|
|
5416
|
-
const
|
|
5417
|
-
return { body: serializers.draft(
|
|
4155
|
+
const email2 = emailFromContext(c);
|
|
4156
|
+
return { body: serializers.draft(email2, domain.draft(email2, routeParam(c, "id")), messageFormat(c)) };
|
|
5418
4157
|
}));
|
|
5419
4158
|
const update = kit.write(async (c) => {
|
|
5420
|
-
const
|
|
4159
|
+
const email2 = emailFromContext(c);
|
|
5421
4160
|
const input = await readMessageWrite(c, true);
|
|
5422
|
-
const
|
|
5423
|
-
return { body: serializers.draft(
|
|
4161
|
+
const draft2 = asInputError(() => domain.updateDraft(email2, routeParam(c, "id"), input.raw, { threadId: input.threadId }));
|
|
4162
|
+
return { body: serializers.draft(email2, draft2, "full") };
|
|
5424
4163
|
});
|
|
5425
4164
|
app.put(`${BASE}/:id`, update);
|
|
5426
4165
|
app.put(`${UPLOAD}/:id`, update);
|
|
@@ -5444,22 +4183,22 @@ var GmailRestSerializers = class {
|
|
|
5444
4183
|
constructor(domain) {
|
|
5445
4184
|
this.domain = domain;
|
|
5446
4185
|
}
|
|
5447
|
-
message(
|
|
4186
|
+
message(email2, message, format = "full", metadataHeaders = []) {
|
|
5448
4187
|
const base = {
|
|
5449
|
-
id:
|
|
5450
|
-
threadId:
|
|
5451
|
-
labelIds:
|
|
5452
|
-
snippet:
|
|
5453
|
-
historyId: this.domain.latestMessageHistory(
|
|
5454
|
-
internalDate: String(
|
|
5455
|
-
sizeEstimate:
|
|
4188
|
+
id: message.id,
|
|
4189
|
+
threadId: message.threadId,
|
|
4190
|
+
labelIds: message.labelIds,
|
|
4191
|
+
snippet: message.snippet,
|
|
4192
|
+
historyId: this.domain.latestMessageHistory(email2, message.id),
|
|
4193
|
+
internalDate: String(message.internalDate),
|
|
4194
|
+
sizeEstimate: message.sizeEstimate
|
|
5456
4195
|
};
|
|
5457
4196
|
if (format === "minimal")
|
|
5458
4197
|
return base;
|
|
5459
4198
|
if (format === "raw") {
|
|
5460
|
-
return { ...base, raw: encodeGmailRaw(this.domain.getRaw(
|
|
4199
|
+
return { ...base, raw: encodeGmailRaw(this.domain.getRaw(email2, message.id)) };
|
|
5461
4200
|
}
|
|
5462
|
-
const headers2 = this.domain.headers(
|
|
4201
|
+
const headers2 = this.domain.headers(email2, message.id);
|
|
5463
4202
|
if (format === "metadata") {
|
|
5464
4203
|
return {
|
|
5465
4204
|
...base,
|
|
@@ -5472,19 +4211,19 @@ var GmailRestSerializers = class {
|
|
|
5472
4211
|
}
|
|
5473
4212
|
};
|
|
5474
4213
|
}
|
|
5475
|
-
return { ...base, payload: fullPayload(
|
|
4214
|
+
return { ...base, payload: fullPayload(message, headers2) };
|
|
5476
4215
|
}
|
|
5477
|
-
thread(
|
|
4216
|
+
thread(email2, thread, format = "full", metadataHeaders = []) {
|
|
5478
4217
|
const latest = thread.messages.at(-1);
|
|
5479
4218
|
return {
|
|
5480
4219
|
id: thread.id,
|
|
5481
|
-
historyId: this.domain.latestThreadHistory(
|
|
4220
|
+
historyId: this.domain.latestThreadHistory(email2, thread.id),
|
|
5482
4221
|
...latest?.snippet ? { snippet: latest.snippet } : {},
|
|
5483
|
-
messages: thread.messages.map((
|
|
4222
|
+
messages: thread.messages.map((message) => this.message(email2, message, format, metadataHeaders))
|
|
5484
4223
|
};
|
|
5485
4224
|
}
|
|
5486
|
-
draft(
|
|
5487
|
-
return { id:
|
|
4225
|
+
draft(email2, draft2, format = "full") {
|
|
4226
|
+
return { id: draft2.id, message: this.message(email2, draft2.message, format) };
|
|
5488
4227
|
}
|
|
5489
4228
|
};
|
|
5490
4229
|
function labelSummary(label2) {
|
|
@@ -5509,29 +4248,29 @@ function labelDetail(label2) {
|
|
|
5509
4248
|
function historyResource(event) {
|
|
5510
4249
|
if (!event.messageId || !event.threadId)
|
|
5511
4250
|
return null;
|
|
5512
|
-
const
|
|
5513
|
-
const base = { id: event.id, messages: [
|
|
4251
|
+
const message = { id: event.messageId, threadId: event.threadId };
|
|
4252
|
+
const base = { id: event.id, messages: [message] };
|
|
5514
4253
|
if (event.type === "messageAdded" || event.type === "draftCreated") {
|
|
5515
|
-
base.messagesAdded = [{ message
|
|
4254
|
+
base.messagesAdded = [{ message }];
|
|
5516
4255
|
} else if (event.type === "messageDeleted" || ["draftDeleted", "draftReplaced", "draftSent"].includes(event.type)) {
|
|
5517
|
-
base.messagesDeleted = [{ message
|
|
4256
|
+
base.messagesDeleted = [{ message }];
|
|
5518
4257
|
} else if (event.type === "labelAdded") {
|
|
5519
|
-
base.labelsAdded = [{ message
|
|
4258
|
+
base.labelsAdded = [{ message, labelIds: event.labelIds }];
|
|
5520
4259
|
} else if (event.type === "labelRemoved") {
|
|
5521
|
-
base.labelsRemoved = [{ message
|
|
4260
|
+
base.labelsRemoved = [{ message, labelIds: event.labelIds }];
|
|
5522
4261
|
} else {
|
|
5523
4262
|
return null;
|
|
5524
4263
|
}
|
|
5525
4264
|
return base;
|
|
5526
4265
|
}
|
|
5527
|
-
function fullPayload(
|
|
4266
|
+
function fullPayload(message, headers2) {
|
|
5528
4267
|
const declaredType = header2(headers2, "content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
5529
4268
|
const contentParts = [];
|
|
5530
|
-
if (
|
|
5531
|
-
contentParts.push(inlinePart(String(contentParts.length), "text/plain",
|
|
5532
|
-
if (
|
|
5533
|
-
contentParts.push(inlinePart(String(contentParts.length), "text/html",
|
|
5534
|
-
for (const attachment2 of
|
|
4269
|
+
if (message.text)
|
|
4270
|
+
contentParts.push(inlinePart(String(contentParts.length), "text/plain", message.text));
|
|
4271
|
+
if (message.html)
|
|
4272
|
+
contentParts.push(inlinePart(String(contentParts.length), "text/html", message.html));
|
|
4273
|
+
for (const attachment2 of message.attachments) {
|
|
5535
4274
|
contentParts.push({
|
|
5536
4275
|
partId: String(contentParts.length),
|
|
5537
4276
|
mimeType: attachment2.mimeType,
|
|
@@ -5544,7 +4283,7 @@ function fullPayload(message2, headers2) {
|
|
|
5544
4283
|
body: { attachmentId: attachment2.id, size: attachment2.size }
|
|
5545
4284
|
});
|
|
5546
4285
|
}
|
|
5547
|
-
if (contentParts.length === 1 &&
|
|
4286
|
+
if (contentParts.length === 1 && message.attachments.length === 0) {
|
|
5548
4287
|
const only = contentParts[0];
|
|
5549
4288
|
return {
|
|
5550
4289
|
partId: "",
|
|
@@ -5615,14 +4354,14 @@ var GmailRouteKit = class {
|
|
|
5615
4354
|
};
|
|
5616
4355
|
});
|
|
5617
4356
|
}
|
|
5618
|
-
unsupported(
|
|
4357
|
+
unsupported(message) {
|
|
5619
4358
|
return this.context.recorder.handle({ mutation: false, fidelity: "unsupported" }, () => ({
|
|
5620
4359
|
status: 501,
|
|
5621
4360
|
body: {
|
|
5622
4361
|
error: {
|
|
5623
4362
|
code: 501,
|
|
5624
|
-
message
|
|
5625
|
-
errors: [{ message
|
|
4363
|
+
message,
|
|
4364
|
+
errors: [{ message, domain: "global", reason: "notImplemented" }],
|
|
5626
4365
|
status: "UNIMPLEMENTED"
|
|
5627
4366
|
}
|
|
5628
4367
|
},
|
|
@@ -5637,19 +4376,19 @@ var UPLOAD2 = "/upload/gmail/v1/users/:userId/messages";
|
|
|
5637
4376
|
function registerMessageRoutes(app, kit) {
|
|
5638
4377
|
const { serializers, domain } = kit;
|
|
5639
4378
|
app.get(BASE2, kit.read((c) => {
|
|
5640
|
-
const
|
|
4379
|
+
const email2 = emailFromContext(c);
|
|
5641
4380
|
const query = c.req.query("q") ?? "";
|
|
5642
4381
|
const includeSpamTrash = booleanQuery(c, "includeSpamTrash");
|
|
5643
4382
|
const labelIds2 = repeatedQuery(c, "labelIds");
|
|
5644
|
-
let messages = asInputError(() => domain.searchMessages(
|
|
4383
|
+
let messages = asInputError(() => domain.searchMessages(email2, query, { includeTrash: includeSpamTrash }));
|
|
5645
4384
|
if (!/\bin:draft\b/i.test(query))
|
|
5646
|
-
messages = messages.filter((
|
|
4385
|
+
messages = messages.filter((message) => !message.labelIds.includes("DRAFT"));
|
|
5647
4386
|
if (labelIds2.length) {
|
|
5648
|
-
messages = messages.filter((
|
|
4387
|
+
messages = messages.filter((message) => labelIds2.every((labelId) => message.labelIds.includes(labelId)));
|
|
5649
4388
|
}
|
|
5650
4389
|
const maxResults = numberQuery(c, "maxResults", 100, 500);
|
|
5651
|
-
const snapshot = domain.currentHistoryIdFor(
|
|
5652
|
-
const binding = normalizeListBinding("messages.list",
|
|
4390
|
+
const snapshot = domain.currentHistoryIdFor(email2);
|
|
4391
|
+
const binding = normalizeListBinding("messages.list", email2, { query, includeSpamTrash, labelIds: labelIds2 });
|
|
5653
4392
|
const { page, nextPageToken } = paginate2(messages, {
|
|
5654
4393
|
maxResults,
|
|
5655
4394
|
pageToken: c.req.query("pageToken"),
|
|
@@ -5658,14 +4397,14 @@ function registerMessageRoutes(app, kit) {
|
|
|
5658
4397
|
});
|
|
5659
4398
|
return {
|
|
5660
4399
|
body: {
|
|
5661
|
-
...page.length ? { messages: page.map((
|
|
4400
|
+
...page.length ? { messages: page.map((message) => ({ id: message.id, threadId: message.threadId })) } : {},
|
|
5662
4401
|
resultSizeEstimate: messages.length,
|
|
5663
4402
|
...nextPageToken ? { nextPageToken } : {}
|
|
5664
4403
|
}
|
|
5665
4404
|
};
|
|
5666
4405
|
}));
|
|
5667
4406
|
app.post(`${BASE2}/batchModify`, kit.write(async (c) => {
|
|
5668
|
-
const
|
|
4407
|
+
const email2 = emailFromContext(c);
|
|
5669
4408
|
const body = await readJsonObject(c);
|
|
5670
4409
|
rejectClassification(body);
|
|
5671
4410
|
const ids = stringArray(body, "ids", 1e3);
|
|
@@ -5674,80 +4413,80 @@ function registerMessageRoutes(app, kit) {
|
|
|
5674
4413
|
const add = stringArray(body, "addLabelIds");
|
|
5675
4414
|
const remove = stringArray(body, "removeLabelIds");
|
|
5676
4415
|
domain.db.transaction(() => {
|
|
5677
|
-
for (const
|
|
5678
|
-
domain.modifyMessageLabels(
|
|
4416
|
+
for (const id of ids)
|
|
4417
|
+
domain.modifyMessageLabels(email2, id, add, remove);
|
|
5679
4418
|
}).immediate();
|
|
5680
4419
|
return { body: {} };
|
|
5681
4420
|
}));
|
|
5682
4421
|
app.post(`${BASE2}/batchDelete`, kit.write(async (c) => {
|
|
5683
|
-
const
|
|
4422
|
+
const email2 = emailFromContext(c);
|
|
5684
4423
|
const body = await readJsonObject(c);
|
|
5685
4424
|
const ids = stringArray(body, "ids", 1e3);
|
|
5686
4425
|
if (!ids.length)
|
|
5687
4426
|
invalidArgument("ids is required");
|
|
5688
|
-
domain.batchDeleteMessages(
|
|
4427
|
+
domain.batchDeleteMessages(email2, ids);
|
|
5689
4428
|
return { status: 204, body: null };
|
|
5690
4429
|
}));
|
|
5691
4430
|
const send = kit.write(async (c) => {
|
|
5692
|
-
const
|
|
4431
|
+
const email2 = emailFromContext(c);
|
|
5693
4432
|
const input = await readMessageWrite(c);
|
|
5694
|
-
const result = asInputError(() => domain.sendMessage(
|
|
5695
|
-
return { body: serializers.message(
|
|
4433
|
+
const result = asInputError(() => domain.sendMessage(email2, input.raw, { threadId: input.threadId }));
|
|
4434
|
+
return { body: serializers.message(email2, result.sender, "full") };
|
|
5696
4435
|
});
|
|
5697
4436
|
app.post(`${BASE2}/send`, send);
|
|
5698
4437
|
app.post(`${UPLOAD2}/send`, send);
|
|
5699
4438
|
const importMessage = kit.write(async (c) => {
|
|
5700
|
-
const
|
|
4439
|
+
const email2 = emailFromContext(c);
|
|
5701
4440
|
rejectUnsupportedQuery(c, ["deleted", "processForCalendar"]);
|
|
5702
4441
|
const source = internalDateSource(c, "dateHeader");
|
|
5703
4442
|
booleanQuery(c, "neverMarkSpam");
|
|
5704
4443
|
const input = await readMessageWrite(c);
|
|
5705
|
-
const inserted = asInputError(() => domain.insertMessage(
|
|
4444
|
+
const inserted = asInputError(() => domain.insertMessage(email2, input.raw, {
|
|
5706
4445
|
threadId: input.threadId,
|
|
5707
4446
|
labels: input.labelIds,
|
|
5708
4447
|
incoming: true
|
|
5709
4448
|
}));
|
|
5710
|
-
const
|
|
5711
|
-
return { body: serializers.message(
|
|
4449
|
+
const message = domain.applyInternalDateSource(email2, inserted.id, source);
|
|
4450
|
+
return { body: serializers.message(email2, message, "full") };
|
|
5712
4451
|
});
|
|
5713
4452
|
app.post(`${BASE2}/import`, importMessage);
|
|
5714
4453
|
app.post(`${UPLOAD2}/import`, importMessage);
|
|
5715
4454
|
const insert = kit.write(async (c) => {
|
|
5716
|
-
const
|
|
4455
|
+
const email2 = emailFromContext(c);
|
|
5717
4456
|
rejectUnsupportedQuery(c, ["deleted"]);
|
|
5718
4457
|
const source = internalDateSource(c, "receivedTime");
|
|
5719
4458
|
const input = await readMessageWrite(c);
|
|
5720
|
-
const inserted = asInputError(() => domain.insertMessage(
|
|
4459
|
+
const inserted = asInputError(() => domain.insertMessage(email2, input.raw, {
|
|
5721
4460
|
threadId: input.threadId,
|
|
5722
4461
|
labels: input.labelIds
|
|
5723
4462
|
}));
|
|
5724
|
-
const
|
|
5725
|
-
return { body: serializers.message(
|
|
4463
|
+
const message = domain.applyInternalDateSource(email2, inserted.id, source);
|
|
4464
|
+
return { body: serializers.message(email2, message, "full") };
|
|
5726
4465
|
});
|
|
5727
4466
|
app.post(BASE2, insert);
|
|
5728
4467
|
app.post(UPLOAD2, insert);
|
|
5729
4468
|
app.get(`${BASE2}/:id`, kit.read((c) => {
|
|
5730
|
-
const
|
|
4469
|
+
const email2 = emailFromContext(c);
|
|
5731
4470
|
const format = messageFormat(c);
|
|
5732
|
-
const
|
|
5733
|
-
return { body: serializers.message(
|
|
4471
|
+
const message = domain.getMessage(email2, routeParam(c, "id"));
|
|
4472
|
+
return { body: serializers.message(email2, message, format, repeatedQuery(c, "metadataHeaders")) };
|
|
5734
4473
|
}));
|
|
5735
4474
|
app.post(`${BASE2}/:id/modify`, kit.write(async (c) => {
|
|
5736
|
-
const
|
|
4475
|
+
const email2 = emailFromContext(c);
|
|
5737
4476
|
const body = await readJsonObject(c);
|
|
5738
4477
|
rejectClassification(body);
|
|
5739
|
-
const
|
|
5740
|
-
return { body: serializers.message(
|
|
4478
|
+
const message = domain.modifyMessageLabels(email2, routeParam(c, "id"), stringArray(body, "addLabelIds"), stringArray(body, "removeLabelIds"));
|
|
4479
|
+
return { body: serializers.message(email2, message, "minimal") };
|
|
5741
4480
|
}));
|
|
5742
4481
|
app.post(`${BASE2}/:id/trash`, kit.write((c) => {
|
|
5743
|
-
const
|
|
5744
|
-
const
|
|
5745
|
-
return { body: serializers.message(
|
|
4482
|
+
const email2 = emailFromContext(c);
|
|
4483
|
+
const message = domain.modifyMessageLabels(email2, routeParam(c, "id"), ["TRASH"], ["INBOX"]);
|
|
4484
|
+
return { body: serializers.message(email2, message, "minimal") };
|
|
5746
4485
|
}));
|
|
5747
4486
|
app.post(`${BASE2}/:id/untrash`, kit.write((c) => {
|
|
5748
|
-
const
|
|
5749
|
-
const
|
|
5750
|
-
return { body: serializers.message(
|
|
4487
|
+
const email2 = emailFromContext(c);
|
|
4488
|
+
const message = domain.modifyMessageLabels(email2, routeParam(c, "id"), [], ["TRASH"]);
|
|
4489
|
+
return { body: serializers.message(email2, message, "minimal") };
|
|
5751
4490
|
}));
|
|
5752
4491
|
app.delete(`${BASE2}/:id`, kit.write((c) => {
|
|
5753
4492
|
domain.deleteMessage(emailFromContext(c), routeParam(c, "id"));
|
|
@@ -5778,17 +4517,17 @@ function registerResourceRoutes(app, kit) {
|
|
|
5778
4517
|
const { serializers, domain } = kit;
|
|
5779
4518
|
app.get(`${USERS}/profile`, kit.read((c) => ({ body: domain.profile(emailFromContext(c)) })));
|
|
5780
4519
|
app.get(`${USERS}/threads`, kit.read((c) => {
|
|
5781
|
-
const
|
|
4520
|
+
const email2 = emailFromContext(c);
|
|
5782
4521
|
const query = c.req.query("q") ?? "";
|
|
5783
4522
|
const includeSpamTrash = booleanQuery(c, "includeSpamTrash");
|
|
5784
4523
|
const labelIds2 = repeatedQuery(c, "labelIds");
|
|
5785
|
-
let threads = asInputError(() => domain.searchThreads(
|
|
4524
|
+
let threads = asInputError(() => domain.searchThreads(email2, query, { includeTrash: includeSpamTrash }));
|
|
5786
4525
|
if (labelIds2.length) {
|
|
5787
4526
|
threads = threads.filter((thread) => labelIds2.every((label2) => thread.labelIds.includes(label2)));
|
|
5788
4527
|
}
|
|
5789
4528
|
const maxResults = numberQuery(c, "maxResults", 100, 500);
|
|
5790
|
-
const snapshot = domain.currentHistoryIdFor(
|
|
5791
|
-
const binding = normalizeListBinding("threads.list",
|
|
4529
|
+
const snapshot = domain.currentHistoryIdFor(email2);
|
|
4530
|
+
const binding = normalizeListBinding("threads.list", email2, { query, includeSpamTrash, labelIds: labelIds2 });
|
|
5792
4531
|
const { page, nextPageToken } = paginate2(threads, {
|
|
5793
4532
|
maxResults,
|
|
5794
4533
|
pageToken: c.req.query("pageToken"),
|
|
@@ -5800,7 +4539,7 @@ function registerResourceRoutes(app, kit) {
|
|
|
5800
4539
|
...page.length ? {
|
|
5801
4540
|
threads: page.map((thread) => ({
|
|
5802
4541
|
id: thread.id,
|
|
5803
|
-
historyId: domain.latestThreadHistory(
|
|
4542
|
+
historyId: domain.latestThreadHistory(email2, thread.id),
|
|
5804
4543
|
...thread.messages.at(-1)?.snippet ? { snippet: thread.messages.at(-1).snippet } : {}
|
|
5805
4544
|
}))
|
|
5806
4545
|
} : {},
|
|
@@ -5810,28 +4549,28 @@ function registerResourceRoutes(app, kit) {
|
|
|
5810
4549
|
};
|
|
5811
4550
|
}));
|
|
5812
4551
|
app.get(`${USERS}/threads/:id`, kit.read((c) => {
|
|
5813
|
-
const
|
|
4552
|
+
const email2 = emailFromContext(c);
|
|
5814
4553
|
const format = messageFormat(c, false);
|
|
5815
4554
|
return {
|
|
5816
|
-
body: serializers.thread(
|
|
4555
|
+
body: serializers.thread(email2, domain.getThread(email2, routeParam(c, "id")), format, repeatedQuery(c, "metadataHeaders"))
|
|
5817
4556
|
};
|
|
5818
4557
|
}));
|
|
5819
4558
|
app.post(`${USERS}/threads/:id/modify`, kit.write(async (c) => {
|
|
5820
|
-
const
|
|
4559
|
+
const email2 = emailFromContext(c);
|
|
5821
4560
|
const body = await readJsonObject(c);
|
|
5822
|
-
const thread = domain.modifyThreadLabels(
|
|
5823
|
-
return { body: serializers.thread(
|
|
4561
|
+
const thread = domain.modifyThreadLabels(email2, routeParam(c, "id"), stringArray(body, "addLabelIds"), stringArray(body, "removeLabelIds"));
|
|
4562
|
+
return { body: serializers.thread(email2, thread, "minimal") };
|
|
5824
4563
|
}));
|
|
5825
4564
|
app.post(`${USERS}/threads/:id/trash`, kit.write((c) => {
|
|
5826
|
-
const
|
|
4565
|
+
const email2 = emailFromContext(c);
|
|
5827
4566
|
return {
|
|
5828
|
-
body: serializers.thread(
|
|
4567
|
+
body: serializers.thread(email2, domain.modifyThreadLabels(email2, routeParam(c, "id"), ["TRASH"], ["INBOX"]), "minimal")
|
|
5829
4568
|
};
|
|
5830
4569
|
}));
|
|
5831
4570
|
app.post(`${USERS}/threads/:id/untrash`, kit.write((c) => {
|
|
5832
|
-
const
|
|
4571
|
+
const email2 = emailFromContext(c);
|
|
5833
4572
|
return {
|
|
5834
|
-
body: serializers.thread(
|
|
4573
|
+
body: serializers.thread(email2, domain.modifyThreadLabels(email2, routeParam(c, "id"), [], ["TRASH"]), "minimal")
|
|
5835
4574
|
};
|
|
5836
4575
|
}));
|
|
5837
4576
|
app.delete(`${USERS}/threads/:id`, kit.write((c) => {
|
|
@@ -5841,12 +4580,12 @@ function registerResourceRoutes(app, kit) {
|
|
|
5841
4580
|
app.get(`${USERS}/labels`, kit.read((c) => ({ body: { labels: domain.labels(emailFromContext(c)).map(labelSummary) } })));
|
|
5842
4581
|
app.get(`${USERS}/labels/:id`, kit.read((c) => ({ body: labelDetail(domain.label(emailFromContext(c), routeParam(c, "id"))) })));
|
|
5843
4582
|
app.post(`${USERS}/labels`, kit.write(async (c) => {
|
|
5844
|
-
const
|
|
4583
|
+
const email2 = emailFromContext(c);
|
|
5845
4584
|
const body = await readJsonObject(c);
|
|
5846
4585
|
if (body.type !== void 0 && body.type !== "user")
|
|
5847
4586
|
invalidArgument("Only user labels can be created");
|
|
5848
|
-
const created = domain.createLabel(
|
|
5849
|
-
return { body: labelDetail(domain.label(
|
|
4587
|
+
const created = domain.createLabel(email2, stringField(body, "name", true), colorInput(body));
|
|
4588
|
+
return { body: labelDetail(domain.label(email2, created.id)) };
|
|
5850
4589
|
}));
|
|
5851
4590
|
app.put(`${USERS}/labels/:id`, kit.write(async (c) => {
|
|
5852
4591
|
const body = await readJsonObject(c);
|
|
@@ -5869,7 +4608,7 @@ function registerResourceRoutes(app, kit) {
|
|
|
5869
4608
|
}
|
|
5870
4609
|
function registerHistory(app, kit) {
|
|
5871
4610
|
app.get(`${USERS}/history`, kit.read((c) => {
|
|
5872
|
-
const
|
|
4611
|
+
const email2 = emailFromContext(c);
|
|
5873
4612
|
const startHistoryId = c.req.query("startHistoryId");
|
|
5874
4613
|
if (!startHistoryId)
|
|
5875
4614
|
invalidArgument("startHistoryId is required");
|
|
@@ -5877,13 +4616,13 @@ function registerHistory(app, kit) {
|
|
|
5877
4616
|
const allowed = /* @__PURE__ */ new Set(["messageAdded", "messageDeleted", "labelAdded", "labelRemoved"]);
|
|
5878
4617
|
if (historyTypes.some((type) => !allowed.has(type)))
|
|
5879
4618
|
invalidArgument("Invalid historyTypes");
|
|
5880
|
-
const result = kit.context.domain.listHistory(
|
|
4619
|
+
const result = kit.context.domain.listHistory(email2, startHistoryId, {
|
|
5881
4620
|
types: historyTypes.length ? historyTypes : void 0
|
|
5882
4621
|
});
|
|
5883
4622
|
const labelId = c.req.query("labelId");
|
|
5884
4623
|
const resources = result.history.filter((event) => !labelId || event.labelIds.includes(labelId)).map(historyResource).filter((item) => item !== null);
|
|
5885
4624
|
const maxResults = numberQuery(c, "maxResults", 100, 500);
|
|
5886
|
-
const binding = normalizeListBinding("history.list",
|
|
4625
|
+
const binding = normalizeListBinding("history.list", email2, { startHistoryId, historyTypes, labelId });
|
|
5887
4626
|
const { page, nextPageToken } = paginate2(resources, {
|
|
5888
4627
|
maxResults,
|
|
5889
4628
|
pageToken: c.req.query("pageToken"),
|
|
@@ -6058,4 +4797,4 @@ function createGmailTwinApp(options = {}) {
|
|
|
6058
4797
|
});
|
|
6059
4798
|
}
|
|
6060
4799
|
|
|
6061
|
-
export {
|
|
4800
|
+
export { DEFAULT_GMAIL_EMAIL, GmailDomain, STATE_EXPORT_COLLECTION_CAP, STATE_EXPORT_FULL_MESSAGE_BUDGET, canonicalRaw, capExportRows, composeMime, createGmailTwinApp, decodeGmailRaw, encodeGmailRaw, exportGmailState, gmailStateDelta, gmailTools, gmailTwinDefinition, identityFromSession, matchesSearch, migrate, mimeSha256, normalizeSubject, openGmailTwinDatabase, parseMime, projectGmailRecording, registerGmailRoutes, resetDatabase, resolveUserEmail, stripBcc, stripHtmlTags };
|