@dawnlit/outlook-bridge 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/backend.d.ts +2 -2
- package/dist/backend.js.map +1 -1
- package/dist/bridge.js +3 -3
- package/dist/bridge.js.map +1 -1
- package/dist/errors.d.ts +17 -8
- package/dist/errors.js +17 -13
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +4 -0
- package/dist/mac/accounts.js +10 -0
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.js +1 -1
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.js +18 -31
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -3
- package/dist/mac/cleanup.js +30 -58
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +3 -1
- package/dist/mac/drafts.js +37 -49
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +10 -3
- package/dist/mac/folders.js +31 -23
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/profile.d.ts +12 -0
- package/dist/mac/profile.js +75 -1
- package/dist/mac/profile.js.map +1 -1
- package/dist/mac/read.d.ts +4 -0
- package/dist/mac/read.js +68 -42
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +19 -5
- package/dist/mac/run.js +49 -9
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +67 -14
- package/dist/mac/scripts.js +150 -31
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +5 -4
- package/dist/mac/send.js +52 -14
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/templates.js +16 -22
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +11 -3
- package/dist/mail.js +25 -6
- package/dist/mail.js.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.js.map +1 -1
- package/dist/shared/attachmentMatch.d.ts +0 -6
- package/dist/shared/attachmentMatch.js +0 -1
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/exec.js.map +1 -1
- package/dist/shared/locate.d.ts +0 -8
- package/dist/shared/locate.js +0 -1
- package/dist/shared/locate.js.map +1 -1
- package/dist/shared/replyBody.d.ts +0 -15
- package/dist/shared/replyBody.js +0 -2
- package/dist/shared/replyBody.js.map +1 -1
- package/dist/shared/templateLimits.d.ts +14 -0
- package/dist/shared/templateLimits.js +21 -0
- package/dist/shared/templateLimits.js.map +1 -0
- package/dist/templateBody.d.ts +6 -0
- package/dist/templateBody.js +8 -1
- package/dist/templateBody.js.map +1 -1
- package/dist/types.d.ts +43 -7
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.js +1 -1
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.js +2 -2
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.js +7 -7
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +5 -1
- package/dist/windows/cleanup.js +18 -15
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +4 -4
- package/dist/windows/drafts.js +22 -33
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.js +2 -2
- package/dist/windows/folders.js.map +1 -1
- package/dist/{shared → windows}/json.d.ts +11 -6
- package/dist/{shared → windows}/json.js +12 -7
- package/dist/windows/json.js.map +1 -0
- package/dist/windows/read.js +23 -16
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +0 -2
- package/dist/windows/run.js +1 -2
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +10 -4
- package/dist/windows/scripts.js +21 -12
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +13 -4
- package/dist/windows/send.js +24 -6
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/templates.js +11 -17
- package/dist/windows/templates.js.map +1 -1
- package/package.json +2 -2
- package/dist/shared/json.js.map +0 -1
package/dist/mac/run.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.ITEM_FAILURE_ROW = exports.AS_HANDLERS = exports.AS_LIST_SEP = exports.LIST_SEP = exports.RECORD_SEP = exports.FIELD_SEP = void 0;
|
|
7
7
|
exports.runOsaScript = runOsaScript;
|
|
8
8
|
exports.asEscape = asEscape;
|
|
9
9
|
exports.asString = asString;
|
|
@@ -14,10 +14,12 @@ exports.asRow = asRow;
|
|
|
14
14
|
exports.splitRecords = splitRecords;
|
|
15
15
|
exports.splitFields = splitFields;
|
|
16
16
|
exports.splitList = splitList;
|
|
17
|
+
exports.splitPositionalList = splitPositionalList;
|
|
17
18
|
exports.field = field;
|
|
18
19
|
exports.summaryFields = summaryFields;
|
|
19
20
|
exports.intField = intField;
|
|
20
21
|
exports.boolField = boolField;
|
|
22
|
+
exports.splitBatch = splitBatch;
|
|
21
23
|
// Getting a generated AppleScript to osascript and its output back.
|
|
22
24
|
//
|
|
23
25
|
// The macOS counterpart of windows/run.ts: running a script, the escaping
|
|
@@ -106,12 +108,12 @@ function asIdList(ids) {
|
|
|
106
108
|
// bodies contain tabs, newlines and commas, but never these — and every emitted
|
|
107
109
|
// field passes through the `sanitize` handler, which makes the framing safe
|
|
108
110
|
// rather than merely unlikely to break.
|
|
109
|
-
exports.FIELD_SEP = '
|
|
110
|
-
exports.RECORD_SEP = '
|
|
111
|
-
exports.LIST_SEP = '
|
|
112
|
-
/** The same three, as AppleScript expressions. */
|
|
113
|
-
|
|
114
|
-
|
|
111
|
+
exports.FIELD_SEP = '\x1f';
|
|
112
|
+
exports.RECORD_SEP = '\x1e';
|
|
113
|
+
exports.LIST_SEP = '\x1d';
|
|
114
|
+
/** The same three, as AppleScript expressions. Only the list one is written outside this module. */
|
|
115
|
+
const AS_FIELD_SEP = '(character id 31)';
|
|
116
|
+
const AS_RECORD_SEP = '(character id 30)';
|
|
115
117
|
exports.AS_LIST_SEP = '(character id 29)';
|
|
116
118
|
/**
|
|
117
119
|
* One record: the AppleScript expressions in `fields`, separated and terminated
|
|
@@ -122,7 +124,7 @@ exports.AS_LIST_SEP = '(character id 29)';
|
|
|
122
124
|
function asRow(fields) {
|
|
123
125
|
return fields
|
|
124
126
|
.map(f => `my sanitize(${f})`)
|
|
125
|
-
.join(` & ${
|
|
127
|
+
.join(` & ${AS_FIELD_SEP} & `) + ` & ${AS_RECORD_SEP}`;
|
|
126
128
|
}
|
|
127
129
|
/**
|
|
128
130
|
* The handlers every script relies on, called with `my` from inside a `tell`.
|
|
@@ -186,6 +188,15 @@ on sanitizeList(lst)
|
|
|
186
188
|
return my joinList(cleaned, (character id 29))
|
|
187
189
|
end sanitizeList
|
|
188
190
|
|
|
191
|
+
-- A list whose positions matter, like an email's attachments: every element is
|
|
192
|
+
-- FOLLOWED by the LIST separator, so an empty element keeps its place and an
|
|
193
|
+
-- empty list stays distinct from a list of one empty name. splitPositionalList()
|
|
194
|
+
-- takes it apart.
|
|
195
|
+
on sanitizePositionalList(lst)
|
|
196
|
+
if (count of lst) is 0 then return ""
|
|
197
|
+
return my sanitizeList(lst) & (character id 29)
|
|
198
|
+
end sanitizePositionalList
|
|
199
|
+
|
|
189
200
|
on insertAboveQuoted(c, ins)
|
|
190
201
|
if c is missing value then return ins
|
|
191
202
|
set c to c as string
|
|
@@ -221,10 +232,21 @@ function splitRecords(raw) {
|
|
|
221
232
|
function splitFields(record) {
|
|
222
233
|
return record.split(exports.FIELD_SEP);
|
|
223
234
|
}
|
|
224
|
-
/**
|
|
235
|
+
/**
|
|
236
|
+
* A field that carries a list. Empty elements are dropped, so where an index
|
|
237
|
+
* into the list must match Outlook's, the field is written with
|
|
238
|
+
* `sanitizePositionalList` and read with `splitPositionalList` instead.
|
|
239
|
+
*/
|
|
225
240
|
function splitList(value) {
|
|
226
241
|
return (value || '').split(exports.LIST_SEP).filter(Boolean);
|
|
227
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* A field written by `sanitizePositionalList`: every element at its position,
|
|
245
|
+
* empty ones included, so index N here is item N+1 of the AppleScript list.
|
|
246
|
+
*/
|
|
247
|
+
function splitPositionalList(value) {
|
|
248
|
+
return value ? value.split(exports.LIST_SEP).slice(0, -1) : [];
|
|
249
|
+
}
|
|
228
250
|
/** A field by position; a missing field reads as ''. */
|
|
229
251
|
function field(fields, index) {
|
|
230
252
|
return fields[index] ?? '';
|
|
@@ -242,4 +264,22 @@ function intField(fields, index, fallback = 0) {
|
|
|
242
264
|
function boolField(fields, index) {
|
|
243
265
|
return field(fields, index).trim() === 'true';
|
|
244
266
|
}
|
|
267
|
+
// ── Batches ──────────────────────────────────────────────────────────────
|
|
268
|
+
//
|
|
269
|
+
// A script acting on a list of ids returns one summary record, then one record
|
|
270
|
+
// per id that failed. The failure row and its decoder sit together so the two
|
|
271
|
+
// can't come to disagree about the field order.
|
|
272
|
+
/** The record a batch script appends, inside `on error errText`, for the id `theId` it failed on. */
|
|
273
|
+
exports.ITEM_FAILURE_ROW = asRow(['(theId as string)', 'subj', 'errText']);
|
|
274
|
+
/** A batch script's output: the summary record's fields, and the items that failed. */
|
|
275
|
+
function splitBatch(raw) {
|
|
276
|
+
const [summary = '', ...failures] = splitRecords(raw);
|
|
277
|
+
return {
|
|
278
|
+
summary: splitFields(summary),
|
|
279
|
+
failed: failures.map(record => {
|
|
280
|
+
const parts = splitFields(record);
|
|
281
|
+
return { entryId: field(parts, 0), subject: field(parts, 1), error: field(parts, 2) };
|
|
282
|
+
}),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
245
285
|
//# sourceMappingURL=run.js.map
|
package/dist/mac/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/mac/run.ts"],"names":[],"mappings":"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","sourcesContent":["// Getting a generated AppleScript to osascript and its output back.\n//\n// The macOS counterpart of windows/run.ts: running a script, the escaping\n// rules, and the record framing every reader here shares.\nimport fs from 'fs';\nimport { removeTempFile, type RunBudget, tempFile } from '../runtime';\nimport { runScriptProcess } from '../shared/exec';\nimport { InvalidRequestError } from '../errors';\nimport { WORD_SECTION_ANCHOR } from '../shared/replyBody';\n\nconst RUNNER = 'osascript' as const;\n\n/**\n * osascript's report of a script error: \"<file>:<start>:<end>: execution error:\n * <message> (<number>)\". Only the message is worth showing — these reach people\n * in per-item failure lists — and -2700 is the number AppleScript gives every\n * `error \"…\"` a script raises itself.\n */\nfunction parseFailure(stderr: string): { message: string } {\n const message = stderr\n .replace(/^(?:.*?:)?\\d+:\\d+:\\s*execution error:\\s*/m, '')\n .replace(/\\s*\\(-2700\\)\\s*$/, '')\n .trim();\n return {message};\n}\n\n/**\n * Run a script from a temp file (no argument-length or quoting limits) and\n * return what it printed, minus the newline osascript appends.\n *\n * `AS_HANDLERS` is prepended here rather than by each caller: a script missing\n * a handler fails to compile as a WHOLE, with an error pointing nowhere near the\n * omission, so it can't be left to each script to remember.\n */\nexport async function runOsaScript(source: string, budget: RunBudget): Promise<string> {\n // The full text is what osascript compiles, so it is also what error offsets\n // refer to and what the debug hook and errors carry.\n const script = AS_HANDLERS + source;\n const file = tempFile('osa', 'applescript');\n fs.writeFileSync(file, script, 'utf8');\n try {\n const {stdout} = await runScriptProcess({\n runner: RUNNER,\n command: 'osascript',\n args: [file],\n script,\n budget,\n parseFailure,\n });\n return stdout.replace(/\\n$/, '');\n } finally {\n removeTempFile(file);\n }\n}\n\n/**\n * Escape text for the inside of an AppleScript double-quoted literal.\n * AppleScript strings never interpolate, so escaping the quote, the backslash\n * and line breaks is the whole job.\n */\nexport function asEscape(text: string): string {\n return text\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\r\\n|\\r|\\n/g, '\\\\n');\n}\n\n/** Text as an AppleScript string literal. */\nexport function asString(text: string): string {\n return `\"${asEscape(text)}\"`;\n}\n\n/** AppleScript's boolean literals. */\nexport function asBool(value: boolean): string {\n return value ? 'true' : 'false';\n}\n\n/** A whole number for a script; anything else is refused before it becomes code. */\nexport function asInt(value: number): string {\n if (!Number.isSafeInteger(value)) {\n throw new InvalidRequestError(`Expected a whole number, got ${String(value)}.`);\n }\n return String(value);\n}\n\n/** Message ids (already validated as integers) as an AppleScript list literal. */\nexport function asIdList(ids: readonly string[]): string {\n return `{${ids.map(id => asInt(Number(id))).join(', ')}}`;\n}\n\n// ── Record framing ───────────────────────────────────────────────────────\n//\n// Control characters separate fields, records and list items: subjects and\n// bodies contain tabs, newlines and commas, but never these — and every emitted\n// field passes through the `sanitize` handler, which makes the framing safe\n// rather than merely unlikely to break.\nexport const FIELD_SEP = '\u001f';\nexport const RECORD_SEP = '\u001e';\nexport const LIST_SEP = '\u001d';\n\n/** The same three, as AppleScript expressions. */\nexport const AS_FIELD_SEP = '(character id 31)';\nexport const AS_RECORD_SEP = '(character id 30)';\nexport const AS_LIST_SEP = '(character id 29)';\n\n/**\n * One record: the AppleScript expressions in `fields`, separated and terminated\n * so `splitRecords`/`splitFields` can take them apart again. A field holding a\n * LIST must be built with `my sanitizeList(...)`, which cleans the elements —\n * `sanitize` deliberately leaves the list separator alone.\n */\nexport function asRow(fields: readonly string[]): string {\n return fields\n .map(f => `my sanitize(${f})`)\n .join(` & ${AS_FIELD_SEP} & `) + ` & ${AS_RECORD_SEP}`;\n}\n\n/**\n * The handlers every script relies on, called with `my` from inside a `tell`.\n *\n * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms return;\n * `sanitize` strips the framing characters; `joinList` and `replaceText` are the\n * text-item-delimiter dance nobody should write twice.\n */\nexport const AS_HANDLERS = `\non pad2(n)\n set s to (n as integer) as string\n if (length of s) < 2 then set s to \"0\" & s\n return s\nend pad2\n\non isoDate(d)\n if d is missing value then return \"\"\n return (year of d as string) & \"-\" & pad2(month of d as integer) & \"-\" & pad2(day of d) & \" \" & pad2(hours of d) & \":\" & pad2(minutes of d)\nend isoDate\n\non joinList(lst, sep)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to sep\n set out to lst as string\n set AppleScript's text item delimiters to saved\n return out\nend joinList\n\non replaceText(t, findWhat, replaceWith)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to findWhat\n set parts to text items of t\n set AppleScript's text item delimiters to replaceWith\n set out to parts as string\n set AppleScript's text item delimiters to saved\n return out\nend replaceText\n\n-- Strips the FIELD and RECORD separators only. The LIST separator is left\n-- alone on purpose: a field may legitimately BE a list.\non sanitize(v)\n if v is missing value then return \"\"\n set t to v as string\n repeat with codePoint in {30, 31}\n set marker to (character id (codePoint as integer))\n if t contains marker then set t to my replaceText(t, marker, \" \")\n end repeat\n return t\nend sanitize\n\n-- A list field: each element loses all three separators, then they are joined\n-- with the LIST separator that splitList() takes them apart on.\non sanitizeList(lst)\n set cleaned to {}\n repeat with item_ in lst\n set t to my sanitize(item_)\n set marker to (character id 29)\n if t contains marker then set t to my replaceText(t, marker, \" \")\n set end of cleaned to t\n end repeat\n return my joinList(cleaned, (character id 29))\nend sanitizeList\n\non insertAboveQuoted(c, ins)\n if c is missing value then return ins\n set c to c as string\n if (length of c) is 0 then return ins\n -- A reply's own empty paragraph sits inside WordSection1; inserting straight\n -- after that tag puts the new text where the cursor would be. Anything else\n -- falls back to just inside <body>. ('at' is a reserved parameter name, so\n -- the cut point can't be called that.)\n set cutPoint to my tagEnd(c, \"${WORD_SECTION_ANCHOR}\")\n if cutPoint is 0 then set cutPoint to my tagEnd(c, \"<body\")\n if cutPoint is 0 then return ins & c\n if cutPoint is (length of c) then return c & ins\n return (text 1 thru cutPoint of c) & ins & (text (cutPoint + 1) thru -1 of c)\nend insertAboveQuoted\n\n-- 'rest' is an AppleScript term, so the remainder can't be called that:\n-- assigning to it compiles and then fails at run time.\non tagEnd(c, marker)\n set ix to offset of marker in c\n if ix is 0 then return 0\n if marker ends with \">\" then return ix + (length of marker) - 1\n set tailText to text ix thru -1 of c\n set gt to offset of \">\" in tailText\n if gt is 0 then return 0\n return ix + gt - 1\nend tagEnd\n`;\n\n/** A script's output as records, without the trailing empty one. */\nexport function splitRecords(raw: string): string[] {\n return raw.split(RECORD_SEP).filter(record => record.trim() !== '');\n}\n\n/** One record's fields. */\nexport function splitFields(record: string): string[] {\n return record.split(FIELD_SEP);\n}\n\n/** A field that carries a list. */\nexport function splitList(value: string | undefined): string[] {\n return (value || '').split(LIST_SEP).filter(Boolean);\n}\n\n/** A field by position; a missing field reads as ''. */\nexport function field(fields: readonly string[], index: number): string {\n return fields[index] ?? '';\n}\n\n/** The fields of a script's first record — the summary a mutation returns ahead of any per-item rows. */\nexport function summaryFields(raw: string): string[] {\n return splitFields(splitRecords(raw)[0] ?? '');\n}\n\n/** A numeric field, `fallback` when absent or unparsable. */\nexport function intField(fields: readonly string[], index: number, fallback = 0): number {\n const parsed = Number.parseInt(field(fields, index), 10);\n return Number.isNaN(parsed) ? fallback : parsed;\n}\n\n/** A boolean field; AppleScript renders booleans as the bare words true/false. */\nexport function boolField(fields: readonly string[], index: number): boolean {\n return field(fields, index).trim() === 'true';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/mac/run.ts"],"names":[],"mappings":"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","sourcesContent":["// Getting a generated AppleScript to osascript and its output back.\n//\n// The macOS counterpart of windows/run.ts: running a script, the escaping\n// rules, and the record framing every reader here shares.\nimport fs from 'fs';\nimport { removeTempFile, type RunBudget, tempFile } from '../runtime';\nimport { runScriptProcess } from '../shared/exec';\nimport { InvalidRequestError } from '../errors';\nimport { WORD_SECTION_ANCHOR } from '../shared/replyBody';\nimport type { ItemFailure } from '../types';\n\nconst RUNNER = 'osascript' as const;\n\n/**\n * osascript's report of a script error: \"<file>:<start>:<end>: execution error:\n * <message> (<number>)\". Only the message is worth showing — these reach people\n * in per-item failure lists — and -2700 is the number AppleScript gives every\n * `error \"…\"` a script raises itself.\n */\nfunction parseFailure(stderr: string): { message: string } {\n const message = stderr\n .replace(/^(?:.*?:)?\\d+:\\d+:\\s*execution error:\\s*/m, '')\n .replace(/\\s*\\(-2700\\)\\s*$/, '')\n .trim();\n return { message };\n}\n\n/**\n * Run a script from a temp file (no argument-length or quoting limits) and\n * return what it printed, minus the newline osascript appends.\n *\n * `AS_HANDLERS` is prepended here rather than by each caller: a script missing\n * a handler fails to compile as a WHOLE, with an error pointing nowhere near the\n * omission, so it can't be left to each script to remember.\n */\nexport async function runOsaScript(source: string, budget: RunBudget): Promise<string> {\n // The full text is what osascript compiles, so it is also what error offsets\n // refer to and what the debug hook and errors carry.\n const script = AS_HANDLERS + source;\n const file = tempFile('osa', 'applescript');\n fs.writeFileSync(file, script, 'utf8');\n try {\n const { stdout } = await runScriptProcess({\n runner: RUNNER,\n command: 'osascript',\n args: [file],\n script,\n budget,\n parseFailure,\n });\n return stdout.replace(/\\n$/, '');\n } finally {\n removeTempFile(file);\n }\n}\n\n/**\n * Escape text for the inside of an AppleScript double-quoted literal.\n * AppleScript strings never interpolate, so escaping the quote, the backslash\n * and line breaks is the whole job.\n */\nexport function asEscape(text: string): string {\n return text\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n .replace(/\\r\\n|\\r|\\n/g, '\\\\n');\n}\n\n/** Text as an AppleScript string literal. */\nexport function asString(text: string): string {\n return `\"${asEscape(text)}\"`;\n}\n\n/** AppleScript's boolean literals. */\nexport function asBool(value: boolean): string {\n return value ? 'true' : 'false';\n}\n\n/** A whole number for a script; anything else is refused before it becomes code. */\nexport function asInt(value: number): string {\n if (!Number.isSafeInteger(value)) {\n throw new InvalidRequestError(`Expected a whole number, got ${String(value)}.`);\n }\n return String(value);\n}\n\n/** Message ids (already validated as integers) as an AppleScript list literal. */\nexport function asIdList(ids: readonly string[]): string {\n return `{${ids.map(id => asInt(Number(id))).join(', ')}}`;\n}\n\n// ── Record framing ───────────────────────────────────────────────────────\n//\n// Control characters separate fields, records and list items: subjects and\n// bodies contain tabs, newlines and commas, but never these — and every emitted\n// field passes through the `sanitize` handler, which makes the framing safe\n// rather than merely unlikely to break.\nexport const FIELD_SEP = '\\x1f';\nexport const RECORD_SEP = '\\x1e';\nexport const LIST_SEP = '\\x1d';\n\n/** The same three, as AppleScript expressions. Only the list one is written outside this module. */\nconst AS_FIELD_SEP = '(character id 31)';\nconst AS_RECORD_SEP = '(character id 30)';\nexport const AS_LIST_SEP = '(character id 29)';\n\n/**\n * One record: the AppleScript expressions in `fields`, separated and terminated\n * so `splitRecords`/`splitFields` can take them apart again. A field holding a\n * LIST must be built with `my sanitizeList(...)`, which cleans the elements —\n * `sanitize` deliberately leaves the list separator alone.\n */\nexport function asRow(fields: readonly string[]): string {\n return fields\n .map(f => `my sanitize(${f})`)\n .join(` & ${AS_FIELD_SEP} & `) + ` & ${AS_RECORD_SEP}`;\n}\n\n/**\n * The handlers every script relies on, called with `my` from inside a `tell`.\n *\n * `isoDate` produces the zero-padded 'yyyy-MM-dd HH:mm' both platforms return;\n * `sanitize` strips the framing characters; `joinList` and `replaceText` are the\n * text-item-delimiter dance nobody should write twice.\n */\nexport const AS_HANDLERS = `\non pad2(n)\n set s to (n as integer) as string\n if (length of s) < 2 then set s to \"0\" & s\n return s\nend pad2\n\non isoDate(d)\n if d is missing value then return \"\"\n return (year of d as string) & \"-\" & pad2(month of d as integer) & \"-\" & pad2(day of d) & \" \" & pad2(hours of d) & \":\" & pad2(minutes of d)\nend isoDate\n\non joinList(lst, sep)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to sep\n set out to lst as string\n set AppleScript's text item delimiters to saved\n return out\nend joinList\n\non replaceText(t, findWhat, replaceWith)\n set saved to AppleScript's text item delimiters\n set AppleScript's text item delimiters to findWhat\n set parts to text items of t\n set AppleScript's text item delimiters to replaceWith\n set out to parts as string\n set AppleScript's text item delimiters to saved\n return out\nend replaceText\n\n-- Strips the FIELD and RECORD separators only. The LIST separator is left\n-- alone on purpose: a field may legitimately BE a list.\non sanitize(v)\n if v is missing value then return \"\"\n set t to v as string\n repeat with codePoint in {30, 31}\n set marker to (character id (codePoint as integer))\n if t contains marker then set t to my replaceText(t, marker, \" \")\n end repeat\n return t\nend sanitize\n\n-- A list field: each element loses all three separators, then they are joined\n-- with the LIST separator that splitList() takes them apart on.\non sanitizeList(lst)\n set cleaned to {}\n repeat with item_ in lst\n set t to my sanitize(item_)\n set marker to (character id 29)\n if t contains marker then set t to my replaceText(t, marker, \" \")\n set end of cleaned to t\n end repeat\n return my joinList(cleaned, (character id 29))\nend sanitizeList\n\n-- A list whose positions matter, like an email's attachments: every element is\n-- FOLLOWED by the LIST separator, so an empty element keeps its place and an\n-- empty list stays distinct from a list of one empty name. splitPositionalList()\n-- takes it apart.\non sanitizePositionalList(lst)\n if (count of lst) is 0 then return \"\"\n return my sanitizeList(lst) & (character id 29)\nend sanitizePositionalList\n\non insertAboveQuoted(c, ins)\n if c is missing value then return ins\n set c to c as string\n if (length of c) is 0 then return ins\n -- A reply's own empty paragraph sits inside WordSection1; inserting straight\n -- after that tag puts the new text where the cursor would be. Anything else\n -- falls back to just inside <body>. ('at' is a reserved parameter name, so\n -- the cut point can't be called that.)\n set cutPoint to my tagEnd(c, \"${WORD_SECTION_ANCHOR}\")\n if cutPoint is 0 then set cutPoint to my tagEnd(c, \"<body\")\n if cutPoint is 0 then return ins & c\n if cutPoint is (length of c) then return c & ins\n return (text 1 thru cutPoint of c) & ins & (text (cutPoint + 1) thru -1 of c)\nend insertAboveQuoted\n\n-- 'rest' is an AppleScript term, so the remainder can't be called that:\n-- assigning to it compiles and then fails at run time.\non tagEnd(c, marker)\n set ix to offset of marker in c\n if ix is 0 then return 0\n if marker ends with \">\" then return ix + (length of marker) - 1\n set tailText to text ix thru -1 of c\n set gt to offset of \">\" in tailText\n if gt is 0 then return 0\n return ix + gt - 1\nend tagEnd\n`;\n\n/** A script's output as records, without the trailing empty one. */\nexport function splitRecords(raw: string): string[] {\n return raw.split(RECORD_SEP).filter(record => record.trim() !== '');\n}\n\n/** One record's fields. */\nexport function splitFields(record: string): string[] {\n return record.split(FIELD_SEP);\n}\n\n/**\n * A field that carries a list. Empty elements are dropped, so where an index\n * into the list must match Outlook's, the field is written with\n * `sanitizePositionalList` and read with `splitPositionalList` instead.\n */\nexport function splitList(value: string | undefined): string[] {\n return (value || '').split(LIST_SEP).filter(Boolean);\n}\n\n/**\n * A field written by `sanitizePositionalList`: every element at its position,\n * empty ones included, so index N here is item N+1 of the AppleScript list.\n */\nexport function splitPositionalList(value: string | undefined): string[] {\n return value ? value.split(LIST_SEP).slice(0, -1) : [];\n}\n\n/** A field by position; a missing field reads as ''. */\nexport function field(fields: readonly string[], index: number): string {\n return fields[index] ?? '';\n}\n\n/** The fields of a script's first record — the summary a mutation returns ahead of any per-item rows. */\nexport function summaryFields(raw: string): string[] {\n return splitFields(splitRecords(raw)[0] ?? '');\n}\n\n/** A numeric field, `fallback` when absent or unparsable. */\nexport function intField(fields: readonly string[], index: number, fallback = 0): number {\n const parsed = Number.parseInt(field(fields, index), 10);\n return Number.isNaN(parsed) ? fallback : parsed;\n}\n\n/** A boolean field; AppleScript renders booleans as the bare words true/false. */\nexport function boolField(fields: readonly string[], index: number): boolean {\n return field(fields, index).trim() === 'true';\n}\n\n// ── Batches ──────────────────────────────────────────────────────────────\n//\n// A script acting on a list of ids returns one summary record, then one record\n// per id that failed. The failure row and its decoder sit together so the two\n// can't come to disagree about the field order.\n\n/** The record a batch script appends, inside `on error errText`, for the id `theId` it failed on. */\nexport const ITEM_FAILURE_ROW = asRow(['(theId as string)', 'subj', 'errText']);\n\n/** A batch script's output: the summary record's fields, and the items that failed. */\nexport function splitBatch(raw: string): { summary: string[]; failed: ItemFailure[] } {\n const [summary = '', ...failures] = splitRecords(raw);\n return {\n summary: splitFields(summary),\n failed: failures.map(record => {\n const parts = splitFields(record);\n return { entryId: field(parts, 0), subject: field(parts, 1), error: field(parts, 2) };\n }),\n };\n}\n"]}
|
package/dist/mac/scripts.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ import type { ItemFailure, MailFolderRef } from '../types';
|
|
|
12
12
|
export interface MacAccount {
|
|
13
13
|
readonly emailAccount: string;
|
|
14
14
|
readonly folderIds?: Readonly<Record<string, number>>;
|
|
15
|
+
/**
|
|
16
|
+
* Set when the profile has no row for the address because macOS refused it:
|
|
17
|
+
* the sentence to fail with should AppleScript not find the account either,
|
|
18
|
+
* since the profile is where a mailbox AppleScript can't see would have been.
|
|
19
|
+
*/
|
|
20
|
+
readonly profileRefusal?: string;
|
|
15
21
|
}
|
|
16
22
|
/** Look an address up in Outlook's profile database, for the folder ids it can add. */
|
|
17
23
|
export declare function resolveMacAccount(emailAccount: string): Promise<MacAccount>;
|
|
@@ -52,8 +58,43 @@ export declare function dateProperty(rootId: number): string;
|
|
|
52
58
|
* AppleScript literal is unreadable, and the parts are known here anyway.
|
|
53
59
|
*/
|
|
54
60
|
export declare function macFolderPath(emailAccount: string, rootLabel: string, segments: readonly string[]): string;
|
|
55
|
-
/**
|
|
56
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Walk a folder's `container` chain to the top as the list `chainNames`,
|
|
63
|
+
* nearest first. Requires `theFolder`, and leaves `cur` on the folder the walk
|
|
64
|
+
* stopped at: the account's nameless root folder, when it got that far.
|
|
65
|
+
*
|
|
66
|
+
* The chain's last element is the folder directly under the account, and an
|
|
67
|
+
* account can't hold two top-level folders of one name, so comparing that name
|
|
68
|
+
* is exact.
|
|
69
|
+
*/
|
|
70
|
+
export declare function folderChainSnippet(indent: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* Bind `acctRootId` to the id of the account's root folder — the folder every
|
|
73
|
+
* one of its top-level folders sits in — through the account object when the
|
|
74
|
+
* probe found one, else from the profile database. Requires
|
|
75
|
+
* `accountLookupSnippet`.
|
|
76
|
+
*
|
|
77
|
+
* Never fails: an account neither names a root for leaves `acctRootId` as
|
|
78
|
+
* `missing value`, which `ownMailGuardSnippet` reads as a root nothing can be
|
|
79
|
+
* shown to sit under.
|
|
80
|
+
*/
|
|
81
|
+
export declare function accountRootIdSnippet(acct: MacAccount): string;
|
|
82
|
+
/**
|
|
83
|
+
* Fail unless `theMsg` is the account's own mail — the macOS counterpart of
|
|
84
|
+
* Windows resolving an id in the account's store. `message id N` resolves
|
|
85
|
+
* against the application, so an id alone says nothing about whose mailbox the
|
|
86
|
+
* message is in, and a stale or wrong one can name mail in another.
|
|
87
|
+
*
|
|
88
|
+
* Either of two signs is enough, because neither exists for every mailbox:
|
|
89
|
+
* - the message's folder chain ends at the account's root folder. Requires
|
|
90
|
+
* `folderChainSnippet` (for `cur`) and `accountRootIdSnippet`.
|
|
91
|
+
* - the message's own account carries the address. A mailbox the dictionary
|
|
92
|
+
* publishes no account object for answers this with `missing value` (see
|
|
93
|
+
* profile.ts), which is why the first sign has to be able to stand alone.
|
|
94
|
+
* An item neither vouches for is refused rather than given the benefit of the
|
|
95
|
+
* doubt: what follows moves or deletes it.
|
|
96
|
+
*/
|
|
97
|
+
export declare function ownMailGuardSnippet(emailAccount: string, verb: 'delete' | 'move', indent: string): string;
|
|
57
98
|
/**
|
|
58
99
|
* Resolve `scopeFolder` from `targetAcct`: a well-known root walked down through
|
|
59
100
|
* any further segments. Also binds `scopeCreated`.
|
|
@@ -81,16 +122,28 @@ export declare function partitionMessageIds(entryIds: readonly string[]): {
|
|
|
81
122
|
invalid: ItemFailure[];
|
|
82
123
|
};
|
|
83
124
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
125
|
+
* Bind `theMsg` to a message id, or fail as NOT_FOUND. `message id N` resolves
|
|
126
|
+
* against the application, so the message is found wherever it sits.
|
|
127
|
+
*/
|
|
128
|
+
export declare function messageLookupSnippet(id: string): string;
|
|
129
|
+
/**
|
|
130
|
+
* Bind `theMsg` to the id a batch loop holds in `theId`, leaving it `missing
|
|
131
|
+
* value` when the id no longer resolves — the per-item counterpart of
|
|
132
|
+
* `messageLookupSnippet`, for a loop that skips or reports a miss and goes on
|
|
133
|
+
* rather than failing the whole run.
|
|
134
|
+
*/
|
|
135
|
+
export declare function messageByIdSnippet(indent: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* `theMsg`'s plain-text body as `bodyText`: whole, cut to `maxChars`, or — with
|
|
138
|
+
* a `maxChars` of 0 — not read at all, the body being the one expensive
|
|
139
|
+
* property of a message.
|
|
86
140
|
*/
|
|
87
|
-
export declare function
|
|
141
|
+
export declare function bodyTextSnippet(indent: string, maxChars?: number): string;
|
|
88
142
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* first.
|
|
143
|
+
* `theMsg`'s sender as `sndName` / `sndAddr`. `sender` is a record, and
|
|
144
|
+
* `address of sender of m` doesn't coerce, so the record is bound first.
|
|
92
145
|
*/
|
|
93
|
-
export declare function senderSnippet(
|
|
146
|
+
export declare function senderSnippet(indent?: string): string;
|
|
94
147
|
/**
|
|
95
148
|
* Everything a reader wants off one message bound to `theMsg`, emitted together
|
|
96
149
|
* with the row it produces (see `messageDetailFields`) so the read order and the
|
|
@@ -111,12 +164,12 @@ export declare const MessageDetail: {
|
|
|
111
164
|
readonly body: 6;
|
|
112
165
|
};
|
|
113
166
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
167
|
+
* `theMsg`'s first recipient as `sndName` / `sndAddr` — what outgoing mail
|
|
168
|
+
* reports in place of a sender.
|
|
116
169
|
*/
|
|
117
|
-
export declare function firstRecipientSnippet(
|
|
118
|
-
/** Every address
|
|
119
|
-
export declare function allRecipientsSnippet(
|
|
170
|
+
export declare function firstRecipientSnippet(indent?: string): string;
|
|
171
|
+
/** Every address `theMsg` was sent to — To, CC and BCC — as the list `addrList`. */
|
|
172
|
+
export declare function allRecipientsSnippet(indent?: string): string;
|
|
120
173
|
/**
|
|
121
174
|
* Recursive, case-insensitive, depth-capped folder search from `startFolder` —
|
|
122
175
|
* the macOS counterpart of the Windows Find-FolderByName.
|
package/dist/mac/scripts.js
CHANGED
|
@@ -6,11 +6,15 @@ exports.accountLookupSnippet = accountLookupSnippet;
|
|
|
6
6
|
exports.rootFolderSnippet = rootFolderSnippet;
|
|
7
7
|
exports.dateProperty = dateProperty;
|
|
8
8
|
exports.macFolderPath = macFolderPath;
|
|
9
|
-
exports.
|
|
9
|
+
exports.folderChainSnippet = folderChainSnippet;
|
|
10
|
+
exports.accountRootIdSnippet = accountRootIdSnippet;
|
|
11
|
+
exports.ownMailGuardSnippet = ownMailGuardSnippet;
|
|
10
12
|
exports.mailScopeSnippet = mailScopeSnippet;
|
|
11
13
|
exports.macMessageId = macMessageId;
|
|
12
14
|
exports.partitionMessageIds = partitionMessageIds;
|
|
13
15
|
exports.messageLookupSnippet = messageLookupSnippet;
|
|
16
|
+
exports.messageByIdSnippet = messageByIdSnippet;
|
|
17
|
+
exports.bodyTextSnippet = bodyTextSnippet;
|
|
14
18
|
exports.senderSnippet = senderSnippet;
|
|
15
19
|
exports.messageDetailSnippet = messageDetailSnippet;
|
|
16
20
|
exports.messageDetailFields = messageDetailFields;
|
|
@@ -26,7 +30,12 @@ async function resolveMacAccount(emailAccount) {
|
|
|
26
30
|
const wanted = emailAccount.trim().toLowerCase();
|
|
27
31
|
const found = (await (0, profile_1.readProfileAccounts)())
|
|
28
32
|
.find(account => account.emailAccount.trim().toLowerCase() === wanted);
|
|
29
|
-
|
|
33
|
+
if (found)
|
|
34
|
+
return { emailAccount, folderIds: found.folderIds };
|
|
35
|
+
// Asked only for an address the profile did not give: the script still looks
|
|
36
|
+
// for an account object first, so a classic account works without access.
|
|
37
|
+
const refusal = await (0, profile_1.profileRefusal)();
|
|
38
|
+
return refusal ? { emailAccount, profileRefusal: refusal } : { emailAccount };
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
41
|
* Resolve the account whose address matches into `targetAcct`, inside a
|
|
@@ -54,10 +63,18 @@ function accountLookupSnippet(acct, needsAccountObject = false) {
|
|
|
54
63
|
end try`;
|
|
55
64
|
if (acct.folderIds && !needsAccountObject)
|
|
56
65
|
return probe;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
let complaint;
|
|
67
|
+
if (acct.folderIds) {
|
|
68
|
+
complaint = `${(0, errors_1.failureTag)('INVALID_REQUEST')}Outlook publishes no account object for '${acct.emailAccount}', `
|
|
69
|
+
+ 'so the account a message is sent from cannot be set. Its folders can be read; mail cannot be composed from it.';
|
|
70
|
+
}
|
|
71
|
+
else if (acct.profileRefusal) {
|
|
72
|
+
complaint = `${(0, errors_1.failureTag)('PERMISSION_DENIED')}Account '${acct.emailAccount}' is not one Outlook's scripting `
|
|
73
|
+
+ `can see. ${acct.profileRefusal}`;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
complaint = `${(0, errors_1.failureTag)('ACCOUNT_NOT_FOUND')}Account '${acct.emailAccount}' not found in Outlook.`;
|
|
77
|
+
}
|
|
61
78
|
return `${probe}
|
|
62
79
|
if targetAcct is missing value then error ${(0, run_1.asString)(complaint)}`;
|
|
63
80
|
}
|
|
@@ -113,6 +130,84 @@ function dateProperty(rootId) {
|
|
|
113
130
|
function macFolderPath(emailAccount, rootLabel, segments) {
|
|
114
131
|
return ['\\\\' + emailAccount, rootLabel, ...segments].join('\\');
|
|
115
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Walk a folder's `container` chain to the top as the list `chainNames`,
|
|
135
|
+
* nearest first. Requires `theFolder`, and leaves `cur` on the folder the walk
|
|
136
|
+
* stopped at: the account's nameless root folder, when it got that far.
|
|
137
|
+
*
|
|
138
|
+
* The chain's last element is the folder directly under the account, and an
|
|
139
|
+
* account can't hold two top-level folders of one name, so comparing that name
|
|
140
|
+
* is exact.
|
|
141
|
+
*/
|
|
142
|
+
function folderChainSnippet(indent) {
|
|
143
|
+
return `${indent}set chainNames to {}
|
|
144
|
+
${indent}set cur to theFolder
|
|
145
|
+
${indent}set guard to 0
|
|
146
|
+
${indent}repeat while cur is not missing value and guard < 25
|
|
147
|
+
${indent} -- The account's root folder has a name of missing value, and coercing
|
|
148
|
+
${indent} -- that yields the STRING "missing value" — so test before coercing.
|
|
149
|
+
${indent} set rawName to missing value
|
|
150
|
+
${indent} try
|
|
151
|
+
${indent} set rawName to name of cur
|
|
152
|
+
${indent} end try
|
|
153
|
+
${indent} if rawName is missing value then exit repeat
|
|
154
|
+
${indent} set nm to rawName as string
|
|
155
|
+
${indent} if nm is "" then exit repeat
|
|
156
|
+
${indent} set end of chainNames to nm
|
|
157
|
+
${indent} set nextUp to missing value
|
|
158
|
+
${indent} try
|
|
159
|
+
${indent} set nextUp to container of cur
|
|
160
|
+
${indent} end try
|
|
161
|
+
${indent} set cur to nextUp
|
|
162
|
+
${indent} set guard to guard + 1
|
|
163
|
+
${indent}end repeat`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Bind `acctRootId` to the id of the account's root folder — the folder every
|
|
167
|
+
* one of its top-level folders sits in — through the account object when the
|
|
168
|
+
* probe found one, else from the profile database. Requires
|
|
169
|
+
* `accountLookupSnippet`.
|
|
170
|
+
*
|
|
171
|
+
* Never fails: an account neither names a root for leaves `acctRootId` as
|
|
172
|
+
* `missing value`, which `ownMailGuardSnippet` reads as a root nothing can be
|
|
173
|
+
* shown to sit under.
|
|
174
|
+
*/
|
|
175
|
+
function accountRootIdSnippet(acct) {
|
|
176
|
+
const profiled = acct.folderIds?.['root folder'];
|
|
177
|
+
return ` set acctRootId to missing value
|
|
178
|
+
try
|
|
179
|
+
if targetAcct is not missing value then set acctRootId to id of (root folder of targetAcct)
|
|
180
|
+
end try` + (profiled === undefined ? '' : `
|
|
181
|
+
if acctRootId is missing value then set acctRootId to ${(0, run_1.asInt)(profiled)}`);
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Fail unless `theMsg` is the account's own mail — the macOS counterpart of
|
|
185
|
+
* Windows resolving an id in the account's store. `message id N` resolves
|
|
186
|
+
* against the application, so an id alone says nothing about whose mailbox the
|
|
187
|
+
* message is in, and a stale or wrong one can name mail in another.
|
|
188
|
+
*
|
|
189
|
+
* Either of two signs is enough, because neither exists for every mailbox:
|
|
190
|
+
* - the message's folder chain ends at the account's root folder. Requires
|
|
191
|
+
* `folderChainSnippet` (for `cur`) and `accountRootIdSnippet`.
|
|
192
|
+
* - the message's own account carries the address. A mailbox the dictionary
|
|
193
|
+
* publishes no account object for answers this with `missing value` (see
|
|
194
|
+
* profile.ts), which is why the first sign has to be able to stand alone.
|
|
195
|
+
* An item neither vouches for is refused rather than given the benefit of the
|
|
196
|
+
* doubt: what follows moves or deletes it.
|
|
197
|
+
*/
|
|
198
|
+
function ownMailGuardSnippet(emailAccount, verb, indent) {
|
|
199
|
+
return `${indent}set inAccount to false
|
|
200
|
+
${indent}try
|
|
201
|
+
${indent} if acctRootId is not missing value and (id of cur) is acctRootId then set inAccount to true
|
|
202
|
+
${indent}end try
|
|
203
|
+
${indent}if not inAccount then
|
|
204
|
+
${indent} try
|
|
205
|
+
${indent} set ownerAddr to email address of (account of theMsg)
|
|
206
|
+
${indent} if ownerAddr is not missing value and (ownerAddr as string) is ${(0, run_1.asString)(emailAccount)} then set inAccount to true
|
|
207
|
+
${indent} end try
|
|
208
|
+
${indent}end if
|
|
209
|
+
${indent}if not inAccount then error "item is in another mailbox, not this account - refusing to ${verb}"`;
|
|
210
|
+
}
|
|
116
211
|
/** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */
|
|
117
212
|
function rootTerm(ref, folderLabel = '') {
|
|
118
213
|
const term = exports.MAC_ROOT_TERMS[ref.rootId];
|
|
@@ -188,27 +283,55 @@ function partitionMessageIds(entryIds) {
|
|
|
188
283
|
return { valid, invalid };
|
|
189
284
|
}
|
|
190
285
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
286
|
+
* Bind `theMsg` to a message id, or fail as NOT_FOUND. `message id N` resolves
|
|
287
|
+
* against the application, so the message is found wherever it sits.
|
|
193
288
|
*/
|
|
194
|
-
function messageLookupSnippet(id
|
|
289
|
+
function messageLookupSnippet(id) {
|
|
195
290
|
const complaint = `${(0, errors_1.failureTag)('NOT_FOUND', 'email')}No email found for message id '${id}'. It may have been deleted - list the mail again for a current id.`;
|
|
196
|
-
return ` set
|
|
291
|
+
return ` set theMsg to missing value
|
|
197
292
|
try
|
|
198
|
-
set
|
|
293
|
+
set theMsg to message id ${id}
|
|
199
294
|
end try
|
|
200
|
-
if
|
|
295
|
+
if theMsg is missing value then error ${(0, run_1.asString)(complaint)}`;
|
|
201
296
|
}
|
|
202
297
|
/**
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
298
|
+
* Bind `theMsg` to the id a batch loop holds in `theId`, leaving it `missing
|
|
299
|
+
* value` when the id no longer resolves — the per-item counterpart of
|
|
300
|
+
* `messageLookupSnippet`, for a loop that skips or reports a miss and goes on
|
|
301
|
+
* rather than failing the whole run.
|
|
206
302
|
*/
|
|
207
|
-
function
|
|
303
|
+
function messageByIdSnippet(indent) {
|
|
304
|
+
return `${indent}set theMsg to missing value
|
|
305
|
+
${indent}try
|
|
306
|
+
${indent} set theMsg to message id theId
|
|
307
|
+
${indent}end try`;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* `theMsg`'s plain-text body as `bodyText`: whole, cut to `maxChars`, or — with
|
|
311
|
+
* a `maxChars` of 0 — not read at all, the body being the one expensive
|
|
312
|
+
* property of a message.
|
|
313
|
+
*/
|
|
314
|
+
function bodyTextSnippet(indent, maxChars) {
|
|
315
|
+
if (maxChars === 0)
|
|
316
|
+
return `${indent}set bodyText to ""`;
|
|
317
|
+
const read = `${indent}set bodyText to ""
|
|
318
|
+
${indent}try
|
|
319
|
+
${indent} set bodyText to (plain text content of theMsg) as string
|
|
320
|
+
${indent}end try`;
|
|
321
|
+
if (maxChars === undefined)
|
|
322
|
+
return read;
|
|
323
|
+
return `${read}
|
|
324
|
+
${indent}if (length of bodyText) > ${(0, run_1.asInt)(maxChars)} then set bodyText to text 1 thru ${(0, run_1.asInt)(maxChars)} of bodyText`;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* `theMsg`'s sender as `sndName` / `sndAddr`. `sender` is a record, and
|
|
328
|
+
* `address of sender of m` doesn't coerce, so the record is bound first.
|
|
329
|
+
*/
|
|
330
|
+
function senderSnippet(indent = ' ') {
|
|
208
331
|
return `${indent}set sndName to ""
|
|
209
332
|
${indent}set sndAddr to ""
|
|
210
333
|
${indent}try
|
|
211
|
-
${indent} set snd to sender of
|
|
334
|
+
${indent} set snd to sender of theMsg
|
|
212
335
|
${indent} try
|
|
213
336
|
${indent} set sndAddr to (address of snd) as string
|
|
214
337
|
${indent} end try
|
|
@@ -228,7 +351,7 @@ function messageDetailSnippet(withBody = false, indent = ' ') {
|
|
|
228
351
|
${indent}try
|
|
229
352
|
${indent} set subj to (subject of theMsg) as string
|
|
230
353
|
${indent}end try
|
|
231
|
-
${senderSnippet(
|
|
354
|
+
${senderSnippet(indent)}
|
|
232
355
|
${indent}set recvd to ""
|
|
233
356
|
${indent}try
|
|
234
357
|
${indent} set recvd to my isoDate(time received of theMsg)
|
|
@@ -240,15 +363,11 @@ ${indent}end try
|
|
|
240
363
|
${indent}set attNames to {}
|
|
241
364
|
${indent}try
|
|
242
365
|
${indent} set attNames to name of every attachment of theMsg
|
|
243
|
-
${indent}end try` + (withBody ? `
|
|
244
|
-
${indent}set bodyText to ""
|
|
245
|
-
${indent}try
|
|
246
|
-
${indent} set bodyText to (plain text content of theMsg) as string
|
|
247
|
-
${indent}end try` : '');
|
|
366
|
+
${indent}end try` + (withBody ? `\n${bodyTextSnippet(indent)}` : '');
|
|
248
367
|
}
|
|
249
368
|
/** The fields `messageDetailSnippet` fills, in row order — see `MessageDetail`. */
|
|
250
369
|
function messageDetailFields(withBody = false) {
|
|
251
|
-
const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my
|
|
370
|
+
const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my sanitizePositionalList(attNames)'];
|
|
252
371
|
if (withBody)
|
|
253
372
|
fields.push('bodyText');
|
|
254
373
|
return fields;
|
|
@@ -264,14 +383,14 @@ exports.MessageDetail = {
|
|
|
264
383
|
body: 6,
|
|
265
384
|
};
|
|
266
385
|
/**
|
|
267
|
-
*
|
|
268
|
-
*
|
|
386
|
+
* `theMsg`'s first recipient as `sndName` / `sndAddr` — what outgoing mail
|
|
387
|
+
* reports in place of a sender.
|
|
269
388
|
*/
|
|
270
|
-
function firstRecipientSnippet(
|
|
389
|
+
function firstRecipientSnippet(indent = ' ') {
|
|
271
390
|
return `${indent}set sndName to ""
|
|
272
391
|
${indent}set sndAddr to ""
|
|
273
392
|
${indent}try
|
|
274
|
-
${indent} set rcps to to recipients of
|
|
393
|
+
${indent} set rcps to to recipients of theMsg
|
|
275
394
|
${indent} if (count of rcps) > 0 then
|
|
276
395
|
${indent} set ea to email address of (item 1 of rcps)
|
|
277
396
|
${indent} try
|
|
@@ -283,11 +402,11 @@ ${indent} end try
|
|
|
283
402
|
${indent} end if
|
|
284
403
|
${indent}end try`;
|
|
285
404
|
}
|
|
286
|
-
/** Every address
|
|
287
|
-
function allRecipientsSnippet(
|
|
405
|
+
/** Every address `theMsg` was sent to — To, CC and BCC — as the list `addrList`. */
|
|
406
|
+
function allRecipientsSnippet(indent = ' ') {
|
|
288
407
|
return `${indent}set addrList to {}
|
|
289
408
|
${indent}try
|
|
290
|
-
${indent} repeat with r in (every recipient of
|
|
409
|
+
${indent} repeat with r in (every recipient of theMsg)
|
|
291
410
|
${indent} try
|
|
292
411
|
${indent} -- The record is bound before its field is read: a nested
|
|
293
412
|
${indent} -- 'address of (email address of r)' doesn't coerce, and fails
|