@dawnlit/outlook-bridge 2.1.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 +11 -6
- package/dist/errors.d.ts +17 -6
- package/dist/errors.js +16 -7
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +1 -1
- 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/bounces.js +10 -19
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -3
- package/dist/mac/cleanup.js +26 -17
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.js +3 -16
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +5 -0
- package/dist/mac/folders.js +14 -5
- 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.js +14 -26
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +0 -3
- package/dist/mac/run.js +8 -8
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +46 -0
- package/dist/mac/scripts.js +98 -10
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +2 -1
- package/dist/mac/send.js +9 -3
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/templates.js +1 -4
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +6 -0
- package/dist/mail.js +7 -1
- package/dist/mail.js.map +1 -1
- package/dist/types.d.ts +20 -4
- 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 +3 -3
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.js +2 -4
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.js +2 -2
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.js +1 -1
- package/dist/windows/folders.js.map +1 -1
- package/dist/windows/json.js.map +1 -0
- package/dist/windows/read.js +17 -14
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/scripts.d.ts +6 -0
- package/dist/windows/scripts.js +11 -2
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +5 -1
- package/dist/windows/send.js +14 -4
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/templates.js +2 -2
- package/dist/windows/templates.js.map +1 -1
- package/package.json +1 -1
- package/dist/shared/json.js.map +0 -1
- /package/dist/{shared → windows}/json.d.ts +0 -0
- /package/dist/{shared → windows}/json.js +0 -0
package/dist/mac/cleanup.js
CHANGED
|
@@ -8,9 +8,17 @@ const scripts_1 = require("./scripts");
|
|
|
8
8
|
const mail_1 = require("../mail");
|
|
9
9
|
/**
|
|
10
10
|
* Delete mail by message id from anywhere in the account, into Deleted Items.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The same four things hold the line as on Windows:
|
|
12
|
+
* - An id that resolves outside this account is refused, whatever
|
|
13
|
+
* `allowProtected` says. A message id resolves against the whole
|
|
14
|
+
* application, and the Inbox/Sent check below only knows this account's
|
|
15
|
+
* folders by name, so another mailbox's mail would otherwise pass it.
|
|
16
|
+
* - Inbox and Sent Items (and their subfolders) are refused unless
|
|
17
|
+
* `allowProtected`.
|
|
18
|
+
* - `dryRun` reports without deleting.
|
|
19
|
+
* - Every outcome names the subject and folder of the item that actually
|
|
20
|
+
* resolved — the folder only once the item is known to be this account's,
|
|
21
|
+
* since the path is built from this account's address.
|
|
14
22
|
*/
|
|
15
23
|
async function deleteOutlookEmails(request) {
|
|
16
24
|
const { valid, invalid } = (0, scripts_1.partitionMessageIds)(request.entryIds);
|
|
@@ -23,21 +31,23 @@ async function deleteOutlookEmails(request) {
|
|
|
23
31
|
}));
|
|
24
32
|
if (valid.length > 0) {
|
|
25
33
|
const acct = await (0, scripts_1.resolveMacAccount)(request.account);
|
|
34
|
+
// The protected roots' names, read off this account: the roots are the
|
|
35
|
+
// ones Windows protects, from the one list both read.
|
|
36
|
+
const readProtectedNames = mail_1.PROTECTED_ROOTS
|
|
37
|
+
.map(rootId => `${(0, scripts_1.rootFolderSnippet)(acct, scripts_1.MAC_ROOT_TERMS[rootId], 'protectedFolder')}
|
|
38
|
+
set end of protectedNames to (name of protectedFolder) as string`)
|
|
39
|
+
.join('\n');
|
|
26
40
|
const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
27
41
|
${(0, scripts_1.accountLookupSnippet)(acct)}
|
|
28
|
-
${(0, scripts_1.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
set sentName to (name of sentFolder) as string
|
|
42
|
+
${(0, scripts_1.accountRootIdSnippet)(acct)}
|
|
43
|
+
set protectedNames to {}
|
|
44
|
+
${readProtectedNames}
|
|
32
45
|
set out to ""
|
|
33
46
|
repeat with theId in ${(0, run_1.asIdList)(valid)}
|
|
34
47
|
set subj to ""
|
|
35
48
|
set pathText to ""
|
|
36
49
|
try
|
|
37
|
-
|
|
38
|
-
try
|
|
39
|
-
set theMsg to message id theId
|
|
40
|
-
end try
|
|
50
|
+
${(0, scripts_1.messageByIdSnippet)(' ')}
|
|
41
51
|
if theMsg is missing value then error "no item with that message id in this Outlook"
|
|
42
52
|
try
|
|
43
53
|
set subj to (subject of theMsg) as string
|
|
@@ -48,10 +58,11 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'sent items', 'sentFolder')}
|
|
|
48
58
|
end try
|
|
49
59
|
if theFolder is missing value then error "item has no parent folder - refusing to delete"
|
|
50
60
|
${(0, scripts_1.folderChainSnippet)(' ')}
|
|
61
|
+
${(0, scripts_1.ownMailGuardSnippet)(request.account, 'delete', ' ')}
|
|
51
62
|
set pathText to my sanitizeList(my reverseList(chainNames))
|
|
52
63
|
-- The chain's top is what extends the refusal to Inbox and Sent Items SUBFOLDERS.
|
|
53
64
|
set rootName to item (count of chainNames) of chainNames
|
|
54
|
-
set isProtected to (
|
|
65
|
+
set isProtected to (protectedNames contains rootName)
|
|
55
66
|
if isProtected and not ${(0, run_1.asBool)(request.allowProtected)} then
|
|
56
67
|
set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'pathText', '"refused"', (0, run_1.asString)(mail_1.PROTECTED_MAIL_REASON)])}
|
|
57
68
|
else if ${(0, run_1.asBool)(request.dryRun)} then
|
|
@@ -105,12 +116,13 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'deleted items', 'trashFolder')}
|
|
|
105
116
|
on error
|
|
106
117
|
set timeList to {}
|
|
107
118
|
end try
|
|
119
|
+
set hasTimes to ((count of timeList) is (count of idList))
|
|
108
120
|
set olderThanDays to ${(0, run_1.asInt)(request.olderThanDays)}
|
|
109
121
|
set cutoff to (current date) - (olderThanDays * days)
|
|
110
122
|
set out to ""
|
|
111
123
|
repeat with i from 1 to (count of idList)
|
|
112
124
|
set stamp to missing value
|
|
113
|
-
if
|
|
125
|
+
if hasTimes then set stamp to item i of timeList
|
|
114
126
|
set keepIt to false
|
|
115
127
|
if olderThanDays > 0 and stamp is not missing value and stamp is greater than cutoff then set keepIt to true
|
|
116
128
|
set out to out & ${(0, run_1.asRow)(['(item i of idList as string)', '(keepIt as string)'])}
|
|
@@ -131,10 +143,7 @@ end tell`, 'scan');
|
|
|
131
143
|
set failedCount to 0
|
|
132
144
|
repeat with theId in ${(0, run_1.asIdList)(doomed.map(row => row.id))}
|
|
133
145
|
try
|
|
134
|
-
|
|
135
|
-
try
|
|
136
|
-
set theMsg to message id theId
|
|
137
|
-
end try
|
|
146
|
+
${(0, scripts_1.messageByIdSnippet)(' ')}
|
|
138
147
|
if theMsg is missing value then error "gone"
|
|
139
148
|
permanently delete theMsg
|
|
140
149
|
set purgedCount to purgedCount + 1
|
package/dist/mac/cleanup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/mac/cleanup.ts"],"names":[],"mappings":"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","sourcesContent":["// Deleting mail, and emptying what was already deleted.\nimport {\n asBool,\n asIdList,\n asInt,\n asRow,\n asString,\n boolField,\n field,\n intField,\n runOsaScript,\n splitFields,\n splitList,\n splitRecords,\n summaryFields,\n} from './run';\nimport {\n accountLookupSnippet,\n folderChainSnippet,\n macFolderPath,\n partitionMessageIds,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport { deleteMailResult, deleteStatus, PROTECTED_MAIL_REASON } from '../mail';\nimport type { DeleteEmailsRequest, PurgeRequest } from '../backend';\nimport type { DeleteMailOutcome, DeleteMailResult, PurgeDeletedItemsResult } from '../types';\n\n/**\n * Delete mail by message id from anywhere in the account, into Deleted Items.\n * Inbox and Sent Items (and their subfolders) are refused unless\n * `allowProtected`; `dryRun` reports without deleting; every outcome names the\n * subject and folder of the item that actually resolved.\n */\nexport async function deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult> {\n const { valid, invalid } = partitionMessageIds(request.entryIds);\n const items: DeleteMailOutcome[] = invalid.map(bad => ({\n entryId: bad.entryId,\n subject: '',\n folderPath: '',\n status: 'failed',\n reason: bad.error,\n }));\n if (valid.length > 0) {\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'inbox', 'inboxFolder')}\n${rootFolderSnippet(acct, 'sent items', 'sentFolder')}\n set inboxName to (name of inboxFolder) as string\n set sentName to (name of sentFolder) as string\n set out to \"\"\n repeat with theId in ${asIdList(valid)}\n set subj to \"\"\n set pathText to \"\"\n try\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is missing value then error \"no item with that message id in this Outlook\"\n try\n set subj to (subject of theMsg) as string\n end try\n set theFolder to missing value\n try\n set theFolder to folder of theMsg\n end try\n if theFolder is missing value then error \"item has no parent folder - refusing to delete\"\n${folderChainSnippet(' ')}\n set pathText to my sanitizeList(my reverseList(chainNames))\n -- The chain's top is what extends the refusal to Inbox and Sent Items SUBFOLDERS.\n set rootName to item (count of chainNames) of chainNames\n set isProtected to (rootName is inboxName) or (rootName is sentName)\n if isProtected and not ${asBool(request.allowProtected)} then\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"refused\"', asString(PROTECTED_MAIL_REASON)])}\n else if ${asBool(request.dryRun)} then\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"would-delete\"', '\"\"'])}\n else\n delete theMsg\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"deleted\"', '\"\"'])}\n end if\n on error errText\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"failed\"', 'errText'])}\n end try\n end repeat\n return out\nend tell\n\non reverseList(lst)\n set out to {}\n repeat with i from (count of lst) to 1 by -1\n set end of out to item i of lst\n end repeat\n return out\nend reverseList`, 'scan');\n\n for (const record of splitRecords(raw)) {\n const parts = splitFields(record);\n const segments = splitList(field(parts, 2));\n items.push({\n entryId: field(parts, 0),\n subject: field(parts, 1).trim(),\n folderPath: segments.length ? macFolderPath(request.account, segments[0], segments.slice(1)) : '',\n status: deleteStatus(field(parts, 3)),\n reason: field(parts, 4),\n });\n }\n }\n return deleteMailResult(request.dryRun, items);\n}\n\n/**\n * Permanently remove items from the account's Deleted Items. The folder is\n * indexed first and items destroyed by id afterwards, so deleting can't shift\n * the collection out from under the walk.\n */\nexport async function purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult> {\n const folderPath = macFolderPath(request.account, 'Deleted Items', []);\n const acct = await resolveMacAccount(request.account);\n const index = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'deleted items', 'trashFolder')}\n set idList to id of every message of trashFolder\n try\n set timeList to time received of every message of trashFolder\n on error\n set timeList to {}\n end try\n set olderThanDays to ${asInt(request.olderThanDays)}\n set cutoff to (current date) - (olderThanDays * days)\n set out to \"\"\n repeat with i from 1 to (count of idList)\n set stamp to missing value\n if (count of timeList) is (count of idList) then set stamp to item i of timeList\n set keepIt to false\n if olderThanDays > 0 and stamp is not missing value and stamp is greater than cutoff then set keepIt to true\n set out to out & ${asRow(['(item i of idList as string)', '(keepIt as string)'])}\n end repeat\n return out\nend tell`, 'scan');\n\n const rows = splitRecords(index).map(record => {\n const parts = splitFields(record);\n return { id: field(parts, 0), keep: boolField(parts, 1) };\n });\n const doomed = rows.filter(row => !row.keep);\n const kept = rows.length - doomed.length;\n if (request.dryRun || doomed.length === 0) {\n return { folderPath, dryRun: request.dryRun, matched: doomed.length, purged: 0, kept, failed: 0 };\n }\n\n const summary = summaryFields(await runOsaScript(`tell application \"Microsoft Outlook\"\n set purgedCount to 0\n set failedCount to 0\n repeat with theId in ${asIdList(doomed.map(row => row.id))}\n try\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is missing value then error \"gone\"\n permanently delete theMsg\n set purgedCount to purgedCount + 1\n on error\n set failedCount to failedCount + 1\n end try\n end repeat\n return ${asRow(['(purgedCount as string)', '(failedCount as string)'])}\nend tell`, 'purge'));\n return {\n folderPath,\n dryRun: request.dryRun,\n matched: doomed.length,\n purged: intField(summary, 0),\n kept,\n failed: intField(summary, 1),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/mac/cleanup.ts"],"names":[],"mappings":";;AA8CA,kDA+EC;AAOD,8CA2DC;AA/LD,wDAAwD;AACxD,+BAce;AACf,uCAWmB;AACnB,kCAAiG;AAIjG;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,mBAAmB,CAAC,OAA4B;IAClE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAA,6BAAmB,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,MAAM,KAAK,GAAwB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnD,OAAO,EAAE,GAAG,CAAC,OAAO;QACpB,OAAO,EAAE,EAAE;QACX,UAAU,EAAE,EAAE;QACd,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,GAAG,CAAC,KAAK;KACpB,CAAC,CAAC,CAAC;IACJ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnB,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACtD,uEAAuE;QACvE,sDAAsD;QACtD,MAAM,kBAAkB,GAAG,sBAAe;aACrC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAA,2BAAiB,EAAC,IAAI,EAAE,wBAAc,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC;qEAC3B,CAAC;aACzD,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;EACrC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,8BAAoB,EAAC,IAAI,CAAC;;EAE1B,kBAAkB;;2BAEO,IAAA,cAAQ,EAAC,KAAK,CAAC;;;;EAIxC,IAAA,4BAAkB,EAAC,cAAc,CAAC;;;;;;;;;;EAUlC,IAAA,4BAAkB,EAAC,cAAc,CAAC;EAClC,IAAA,6BAAmB,EAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,cAAc,CAAC;;;;;qCAK3B,IAAA,YAAM,EAAC,OAAO,CAAC,cAAc,CAAC;mCAChC,IAAA,WAAK,EAAC,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,IAAA,cAAQ,EAAC,4BAAqB,CAAC,CAAC,CAAC;sBAC3G,IAAA,YAAM,EAAC,OAAO,CAAC,MAAM,CAAC;mCACT,IAAA,WAAK,EAAC,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,IAAI,CAAC,CAAC;;;mCAGxE,IAAA,WAAK,EAAC,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;;;+BAGvE,IAAA,WAAK,EAAC,CAAC,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;;;;;;;;;;;;gBAYtF,EAAE,MAAM,CAAC,CAAC;QAElB,KAAK,MAAM,MAAM,IAAI,IAAA,kBAAY,EAAC,GAAG,CAAC,EAAE,CAAC;YACrC,MAAM,KAAK,GAAG,IAAA,iBAAW,EAAC,MAAM,CAAC,CAAC;YAClC,MAAM,QAAQ,GAAG,IAAA,eAAS,EAAC,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC;gBACP,OAAO,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;gBACxB,OAAO,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;gBAC/B,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;gBACjG,MAAM,EAAE,IAAA,mBAAY,EAAC,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC;gBACrC,MAAM,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;aAC1B,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IACD,OAAO,IAAA,uBAAgB,EAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,iBAAiB,CAAC,OAAqB;IACzD,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC,CAAC;IACvE,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,KAAK,GAAG,MAAM,IAAA,kBAAY,EAAC;EACnC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,2BAAiB,EAAC,IAAI,EAAE,eAAe,EAAE,aAAa,CAAC;;;;;;;;2BAQ9B,IAAA,WAAK,EAAC,OAAO,CAAC,aAAa,CAAC;;;;;;;;2BAQ5B,IAAA,WAAK,EAAC,CAAC,8BAA8B,EAAE,oBAAoB,CAAC,CAAC;;;SAG/E,EAAE,MAAM,CAAC,CAAC;IAEf,MAAM,IAAI,GAAG,IAAA,kBAAY,EAAC,KAAK,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAA,iBAAW,EAAC,MAAM,CAAC,CAAC;QAClC,OAAO,EAAE,EAAE,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAA,eAAS,EAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC;IAC9D,CAAC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;IACzC,IAAI,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACtG,CAAC;IAED,MAAM,OAAO,GAAG,IAAA,mBAAa,EAAC,MAAM,IAAA,kBAAY,EAAC;;;2BAG1B,IAAA,cAAQ,EAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;;EAE5D,IAAA,4BAAkB,EAAC,cAAc,CAAC;;;;;;;;aAQvB,IAAA,WAAK,EAAC,CAAC,yBAAyB,EAAE,yBAAyB,CAAC,CAAC;SACjE,EAAE,OAAO,CAAC,CAAC,CAAC;IACjB,OAAO;QACH,UAAU;QACV,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,MAAM,CAAC,MAAM;QACtB,MAAM,EAAE,IAAA,cAAQ,EAAC,OAAO,EAAE,CAAC,CAAC;QAC5B,IAAI;QACJ,MAAM,EAAE,IAAA,cAAQ,EAAC,OAAO,EAAE,CAAC,CAAC;KAC/B,CAAC;AACN,CAAC","sourcesContent":["// Deleting mail, and emptying what was already deleted.\nimport {\n asBool,\n asIdList,\n asInt,\n asRow,\n asString,\n boolField,\n field,\n intField,\n runOsaScript,\n splitFields,\n splitList,\n splitRecords,\n summaryFields,\n} from './run';\nimport {\n accountLookupSnippet,\n accountRootIdSnippet,\n folderChainSnippet,\n MAC_ROOT_TERMS,\n macFolderPath,\n messageByIdSnippet,\n ownMailGuardSnippet,\n partitionMessageIds,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport { deleteMailResult, deleteStatus, PROTECTED_MAIL_REASON, PROTECTED_ROOTS } from '../mail';\nimport type { DeleteEmailsRequest, PurgeRequest } from '../backend';\nimport type { DeleteMailOutcome, DeleteMailResult, PurgeDeletedItemsResult } from '../types';\n\n/**\n * Delete mail by message id from anywhere in the account, into Deleted Items.\n * The same four things hold the line as on Windows:\n * - An id that resolves outside this account is refused, whatever\n * `allowProtected` says. A message id resolves against the whole\n * application, and the Inbox/Sent check below only knows this account's\n * folders by name, so another mailbox's mail would otherwise pass it.\n * - Inbox and Sent Items (and their subfolders) are refused unless\n * `allowProtected`.\n * - `dryRun` reports without deleting.\n * - Every outcome names the subject and folder of the item that actually\n * resolved — the folder only once the item is known to be this account's,\n * since the path is built from this account's address.\n */\nexport async function deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult> {\n const { valid, invalid } = partitionMessageIds(request.entryIds);\n const items: DeleteMailOutcome[] = invalid.map(bad => ({\n entryId: bad.entryId,\n subject: '',\n folderPath: '',\n status: 'failed',\n reason: bad.error,\n }));\n if (valid.length > 0) {\n const acct = await resolveMacAccount(request.account);\n // The protected roots' names, read off this account: the roots are the\n // ones Windows protects, from the one list both read.\n const readProtectedNames = PROTECTED_ROOTS\n .map(rootId => `${rootFolderSnippet(acct, MAC_ROOT_TERMS[rootId], 'protectedFolder')}\n set end of protectedNames to (name of protectedFolder) as string`)\n .join('\\n');\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${accountRootIdSnippet(acct)}\n set protectedNames to {}\n${readProtectedNames}\n set out to \"\"\n repeat with theId in ${asIdList(valid)}\n set subj to \"\"\n set pathText to \"\"\n try\n${messageByIdSnippet(' ')}\n if theMsg is missing value then error \"no item with that message id in this Outlook\"\n try\n set subj to (subject of theMsg) as string\n end try\n set theFolder to missing value\n try\n set theFolder to folder of theMsg\n end try\n if theFolder is missing value then error \"item has no parent folder - refusing to delete\"\n${folderChainSnippet(' ')}\n${ownMailGuardSnippet(request.account, 'delete', ' ')}\n set pathText to my sanitizeList(my reverseList(chainNames))\n -- The chain's top is what extends the refusal to Inbox and Sent Items SUBFOLDERS.\n set rootName to item (count of chainNames) of chainNames\n set isProtected to (protectedNames contains rootName)\n if isProtected and not ${asBool(request.allowProtected)} then\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"refused\"', asString(PROTECTED_MAIL_REASON)])}\n else if ${asBool(request.dryRun)} then\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"would-delete\"', '\"\"'])}\n else\n delete theMsg\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"deleted\"', '\"\"'])}\n end if\n on error errText\n set out to out & ${asRow(['(theId as string)', 'subj', 'pathText', '\"failed\"', 'errText'])}\n end try\n end repeat\n return out\nend tell\n\non reverseList(lst)\n set out to {}\n repeat with i from (count of lst) to 1 by -1\n set end of out to item i of lst\n end repeat\n return out\nend reverseList`, 'scan');\n\n for (const record of splitRecords(raw)) {\n const parts = splitFields(record);\n const segments = splitList(field(parts, 2));\n items.push({\n entryId: field(parts, 0),\n subject: field(parts, 1).trim(),\n folderPath: segments.length ? macFolderPath(request.account, segments[0], segments.slice(1)) : '',\n status: deleteStatus(field(parts, 3)),\n reason: field(parts, 4),\n });\n }\n }\n return deleteMailResult(request.dryRun, items);\n}\n\n/**\n * Permanently remove items from the account's Deleted Items. The folder is\n * indexed first and items destroyed by id afterwards, so deleting can't shift\n * the collection out from under the walk.\n */\nexport async function purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult> {\n const folderPath = macFolderPath(request.account, 'Deleted Items', []);\n const acct = await resolveMacAccount(request.account);\n const index = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'deleted items', 'trashFolder')}\n set idList to id of every message of trashFolder\n try\n set timeList to time received of every message of trashFolder\n on error\n set timeList to {}\n end try\n set hasTimes to ((count of timeList) is (count of idList))\n set olderThanDays to ${asInt(request.olderThanDays)}\n set cutoff to (current date) - (olderThanDays * days)\n set out to \"\"\n repeat with i from 1 to (count of idList)\n set stamp to missing value\n if hasTimes then set stamp to item i of timeList\n set keepIt to false\n if olderThanDays > 0 and stamp is not missing value and stamp is greater than cutoff then set keepIt to true\n set out to out & ${asRow(['(item i of idList as string)', '(keepIt as string)'])}\n end repeat\n return out\nend tell`, 'scan');\n\n const rows = splitRecords(index).map(record => {\n const parts = splitFields(record);\n return { id: field(parts, 0), keep: boolField(parts, 1) };\n });\n const doomed = rows.filter(row => !row.keep);\n const kept = rows.length - doomed.length;\n if (request.dryRun || doomed.length === 0) {\n return { folderPath, dryRun: request.dryRun, matched: doomed.length, purged: 0, kept, failed: 0 };\n }\n\n const summary = summaryFields(await runOsaScript(`tell application \"Microsoft Outlook\"\n set purgedCount to 0\n set failedCount to 0\n repeat with theId in ${asIdList(doomed.map(row => row.id))}\n try\n${messageByIdSnippet(' ')}\n if theMsg is missing value then error \"gone\"\n permanently delete theMsg\n set purgedCount to purgedCount + 1\n on error\n set failedCount to failedCount + 1\n end try\n end repeat\n return ${asRow(['(purgedCount as string)', '(failedCount as string)'])}\nend tell`, 'purge'));\n return {\n folderPath,\n dryRun: request.dryRun,\n matched: doomed.length,\n purged: intField(summary, 0),\n kept,\n failed: intField(summary, 1),\n };\n}\n"]}
|
package/dist/mac/drafts.js
CHANGED
|
@@ -20,13 +20,6 @@ const scripts_1 = require("./scripts");
|
|
|
20
20
|
/** The account's drafts, newest first. Bodies are previewed, never returned whole. */
|
|
21
21
|
async function listOutlookDrafts(request) {
|
|
22
22
|
const acct = await (0, scripts_1.resolveMacAccount)(request.account);
|
|
23
|
-
const preview = request.previewChars > 0
|
|
24
|
-
? ` set bodyText to ""
|
|
25
|
-
try
|
|
26
|
-
set bodyText to (plain text content of theMsg) as string
|
|
27
|
-
end try
|
|
28
|
-
if (length of bodyText) > ${(0, run_1.asInt)(request.previewChars)} then set bodyText to text 1 thru ${(0, run_1.asInt)(request.previewChars)} of bodyText`
|
|
29
|
-
: ' set bodyText to ""';
|
|
30
23
|
const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
31
24
|
${(0, scripts_1.accountLookupSnippet)(acct)}
|
|
32
25
|
${(0, scripts_1.rootFolderSnippet)(acct, 'drafts', 'draftsFolder')}
|
|
@@ -56,7 +49,7 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'drafts', 'draftsFolder')}
|
|
|
56
49
|
end try
|
|
57
50
|
end repeat
|
|
58
51
|
end try
|
|
59
|
-
${
|
|
52
|
+
${(0, scripts_1.bodyTextSnippet)(' ', request.previewChars)}
|
|
60
53
|
set attCount to 0
|
|
61
54
|
try
|
|
62
55
|
set attCount to count of attachments of theMsg
|
|
@@ -136,10 +129,7 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'drafts', 'draftsFolder')}
|
|
|
136
129
|
repeat with theId in ${(0, run_1.asIdList)(valid)}
|
|
137
130
|
set subj to ""
|
|
138
131
|
try
|
|
139
|
-
|
|
140
|
-
try
|
|
141
|
-
set theMsg to message id theId
|
|
142
|
-
end try
|
|
132
|
+
${(0, scripts_1.messageByIdSnippet)(' ')}
|
|
143
133
|
if theMsg is missing value then error "no such item in this account's Drafts folder"
|
|
144
134
|
try
|
|
145
135
|
set subj to (subject of theMsg) as string
|
|
@@ -187,10 +177,7 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'drafts', 'draftsFolder')}
|
|
|
187
177
|
repeat with theId in wanted
|
|
188
178
|
set subj to ""
|
|
189
179
|
try
|
|
190
|
-
|
|
191
|
-
try
|
|
192
|
-
set theMsg to message id theId
|
|
193
|
-
end try
|
|
180
|
+
${(0, scripts_1.messageByIdSnippet)(' ')}
|
|
194
181
|
if theMsg is missing value then error "draft is no longer in the Drafts folder"
|
|
195
182
|
${boundElsewhereSnippet(request.account, ' ')}
|
|
196
183
|
if not boundElsewhere then
|
package/dist/mac/drafts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/mac/drafts.ts"],"names":[],"mappings":";;AAoCA,8CA2EC;AAuED,gCAGC;AAMD,kDAGC;AASD,sCAgCC;AA3OD,kFAAkF;AAClF,EAAE;AACF,wEAAwE;AACxE,2EAA2E;AAC3E,0EAA0E;AAC1E,6EAA6E;AAC7E,+BAA+B;AAC/B,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,uDAAuD;AACvD,+BAae;AACf,uCAMmB;AAInB,sFAAsF;AAC/E,KAAK,UAAU,iBAAiB,CAAC,OAA0B;IAC9D,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,GAAG,CAAC;QACpC,CAAC,CAAC;;;;oCAI0B,IAAA,WAAK,EAAC,OAAO,CAAC,YAAY,CAAC,qCAAqC,IAAA,WAAK,EAAC,OAAO,CAAC,YAAY,CAAC,cAAc;QACrI,CAAC,CAAC,4BAA4B,CAAC;IACnC,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;EACjC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,2BAAiB,EAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BjD,OAAO;;;;;;;;;EASP,qBAAqB,CAAC,OAAO,CAAC,OAAO,EAAE,UAAU,CAAC;sDACE,IAAA,WAAK,EAAC,CAAC,0BAA0B,EAAE,MAAM,EAAE,4BAA4B,EAAE,0BAA0B,EAAE,UAAU,EAAE,sBAAsB,EAAE,OAAO,CAAC,CAAC;;;SAG/L,EAAE,UAAU,CAAC,CAAC;IAEnB,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,IAAA,kBAAY,EAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAC1C,MAAM,KAAK,GAAG,IAAA,iBAAW,EAAC,MAAM,CAAC,CAAC;QAClC,OAAO;YACH,OAAO,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;YACxB,OAAO,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;YACxB,EAAE,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;YACnB,QAAQ,EAAE,IAAA,eAAS,EAAC,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACpC,WAAW,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE;YACxD,cAAc,EAAE,IAAA,cAAQ,EAAC,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC;YACtC,YAAY,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;YAC7B,UAAU;SACb,CAAC;IACN,CAAC,CAAC,CAAC;IACH,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;IACpE,OAAO;QACH,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,cAAc,EAAE,CAAC,UAAU,CAAC;QAC5B,KAAK,EAAE,MAAM,CAAC,MAAM;QACpB,SAAS,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK;QACxC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC;KACzC,CAAC;AACN,CAAC;AAED;;;;;GAKG;AACH,SAAS,qBAAqB,CAAC,YAAoB,EAAE,MAAc;IAC/D,OAAO,GAAG,MAAM;EAClB,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM,+DAA+D,IAAA,cAAQ,EAAC,YAAY,CAAC,GAAG,CAAC;AACjG,CAAC;AAED;;;;GAIG;AACH,SAAS,aAAa,CAAC,YAAoB,EAAE,IAAuB;IAChE,OAAO;;;;8JAImJ,IAAI;EAChK,qBAAqB,CAAC,YAAY,EAAE,cAAc,CAAC;6FACwC,IAAI,GAAG,CAAC;AACrG,CAAC;AAED,oFAAoF;AACpF,KAAK,UAAU,WAAW,CACtB,OAAwB,EACxB,IAAuB;IAEvB,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAA,6BAAmB,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC5D,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;EACjC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,2BAAiB,EAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,CAAC;;;;;2BAKxB,IAAA,cAAQ,EAAC,KAAK,CAAC;;;;;;;;;;;EAWxC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;cACxB,IAAI;;;+BAGa,sBAAgB;;;aAGlC,IAAA,WAAK,EAAC,CAAC,uBAAuB,CAAC,CAAC;SACpC,EAAE,MAAM,CAAC,CAAC;IACf,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAA,gBAAU,EAAC,GAAG,CAAC,CAAC;IAC5C,OAAO,EAAE,IAAI,EAAE,IAAA,cAAQ,EAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,6BAA6B;AACtB,KAAK,UAAU,UAAU,CAAC,OAAwB;IACrD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5D,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAClC,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,mBAAmB,CAAC,OAAwB;IAC9D,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC9D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,aAAa,CAAC,OAAuB;IACvD,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;EACjC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,2BAAiB,EAAC,IAAI,EAAE,QAAQ,EAAE,cAAc,CAAC;;;;;;;;;;;;EAYjD,qBAAqB,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC;;;;;;;;;+BASzB,sBAAgB;;;aAGlC,IAAA,WAAK,EAAC,CAAC,uBAAuB,CAAC,CAAC;SACpC,EAAE,MAAM,CAAC,CAAC;IACf,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAA,gBAAU,EAAC,GAAG,CAAC,CAAC;IAC5C,OAAO,EAAE,IAAI,EAAE,IAAA,cAAQ,EAAC,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;AAClD,CAAC","sourcesContent":["// Drafts: which ones belong to an account, and listing, sending or deleting them.\n//\n// Where Windows scans two Drafts folders and proves each item's sending\n// account, macOS resolves the one Drafts folder belonging to the account —\n// through the account object, or by the folder's own id for a mailbox the\n// dictionary won't name (see profile.ts) — so a draft found there belongs to\n// the account by construction.\n//\n// What the folder can't vouch for is an item's own binding, which is why the\n// guards compare against the requested address as a literal rather than asking\n// `targetAcct`: there may be no account object to ask.\nimport {\n asIdList,\n asInt,\n asRow,\n asString,\n field,\n intField,\n ITEM_FAILURE_ROW,\n runOsaScript,\n splitBatch,\n splitFields,\n splitList,\n splitRecords,\n} from './run';\nimport {\n accountLookupSnippet,\n macFolderPath,\n partitionMessageIds,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../backend';\nimport type { DeleteDraftsResult, ItemFailure, ListDraftsResult, SendDraftsResult } from '../types';\n\n/** The account's drafts, newest first. Bodies are previewed, never returned whole. */\nexport async function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult> {\n const acct = await resolveMacAccount(request.account);\n const preview = request.previewChars > 0\n ? ` set bodyText to \"\"\n try\n set bodyText to (plain text content of theMsg) as string\n end try\n if (length of bodyText) > ${asInt(request.previewChars)} then set bodyText to text 1 thru ${asInt(request.previewChars)} of bodyText`\n : ' set bodyText to \"\"';\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'drafts', 'draftsFolder')}\n set out to \"\"\n repeat with theMsg in (messages of draftsFolder)\n set subj to \"\"\n try\n set subj to (subject of theMsg) as string\n end try\n set toNames to {}\n set toAddrs to {}\n try\n repeat with r in (every to recipient of theMsg)\n try\n set ea to email address of r\n set oneAddr to \"\"\n set oneName to \"\"\n try\n set oneAddr to (address of ea) as string\n end try\n try\n set oneName to (name of ea) as string\n end try\n if oneName is \"\" then set oneName to oneAddr\n if oneName is not \"\" then set end of toNames to oneName\n if oneAddr is not \"\" then set end of toAddrs to oneAddr\n end try\n end repeat\n end try\n${preview}\n set attCount to 0\n try\n set attCount to count of attachments of theMsg\n end try\n set modAt to \"\"\n try\n set modAt to my isoDate(modification date of theMsg)\n end try\n${boundElsewhereSnippet(request.account, ' ')}\n if not boundElsewhere then set out to out & ${asRow(['(id of theMsg as string)', 'subj', 'my joinList(toNames, \", \")', 'my sanitizeList(toAddrs)', 'bodyText', '(attCount as string)', 'modAt'])}\n end repeat\n return out\nend tell`, 'standard');\n\n const folderPath = macFolderPath(request.account, 'Drafts', []);\n const drafts = splitRecords(raw).map(record => {\n const parts = splitFields(record);\n return {\n entryId: field(parts, 0),\n subject: field(parts, 1),\n to: field(parts, 2),\n toEmails: splitList(field(parts, 3)),\n bodyPreview: field(parts, 4).replace(/\\s+/g, ' ').trim(),\n hasAttachments: intField(parts, 5) > 0,\n lastModified: field(parts, 6),\n folderPath,\n };\n });\n drafts.sort((a, b) => b.lastModified.localeCompare(a.lastModified));\n return {\n account: request.account,\n foldersScanned: [folderPath],\n count: drafts.length,\n truncated: drafts.length > request.limit,\n drafts: drafts.slice(0, request.limit),\n };\n}\n\n/**\n * Set `boundElsewhere` to whether `theMsg`, a message in the account's Drafts\n * folder, is set to send from some OTHER account. An unbound draft there is the\n * account's own. Listing, sending all and the per-id guard all apply this one\n * rule, as Windows' Test-DraftMatches is applied to all three.\n */\nfunction boundElsewhereSnippet(emailAccount: string, indent: string): string {\n return `${indent}set boundTo to \"\"\n${indent}try\n${indent} set boundTo to (email address of (account of theMsg)) as string\n${indent}end try\n${indent}set boundElsewhere to (boundTo is not \"\" and boundTo is not ${asString(emailAccount)})`;\n}\n\n/**\n * The AppleScript that proves `theMsg` is one of this account's drafts before\n * `verb` happens to it: it sits in the account's Drafts folder, and it is bound\n * to this account or to none.\n */\nfunction ownDraftGuard(emailAccount: string, verb: 'send' | 'delete'): string {\n return ` set inFolder to \"\"\n try\n set inFolder to (name of (folder of theMsg)) as string\n end try\n if inFolder is not draftsName or draftIds does not contain (id of theMsg) then error \"item is not in this account's Drafts folder - refusing to ${verb}\"\n${boundElsewhereSnippet(emailAccount, ' ')}\n if boundElsewhere then error \"draft is not bound to this account - refusing to ${verb}\"`;\n}\n\n/** Run `verb` over named drafts of the account, each proved to be its own first. */\nasync function actOnDrafts(\n request: EntryIdsRequest,\n verb: 'send' | 'delete',\n): Promise<{ done: number; failed: ItemFailure[] }> {\n const { valid, invalid } = partitionMessageIds(request.entryIds);\n if (valid.length === 0) return { done: 0, failed: invalid };\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'drafts', 'draftsFolder')}\n set draftsName to (name of draftsFolder) as string\n set draftIds to id of every message of draftsFolder\n set doneCount to 0\n set out to \"\"\n repeat with theId in ${asIdList(valid)}\n set subj to \"\"\n try\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is missing value then error \"no such item in this account's Drafts folder\"\n try\n set subj to (subject of theMsg) as string\n end try\n${ownDraftGuard(request.account, verb)}\n ${verb} theMsg\n set doneCount to doneCount + 1\n on error errText\n set out to out & ${ITEM_FAILURE_ROW}\n end try\n end repeat\n return ${asRow(['(doneCount as string)'])} & out\nend tell`, 'scan');\n const { summary, failed } = splitBatch(raw);\n return { done: intField(summary, 0), failed: [...invalid, ...failed] };\n}\n\n/** Send the named drafts. */\nexport async function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult> {\n const { done, failed } = await actOnDrafts(request, 'send');\n return { sent: done, failed };\n}\n\n/**\n * Delete the named drafts. `delete` files an item in Deleted Items, so a\n * mistaken call stays recoverable.\n */\nexport async function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult> {\n const { done, failed } = await actOnDrafts(request, 'delete');\n return { deleted: done, failed };\n}\n\n/**\n * Send every draft belonging to the account. The ids are snapshotted before the\n * first send: sending moves an item out of Drafts, and mutating the collection\n * mid-enumeration would skip every other one. A draft in the folder that is set\n * to send from another account is not this account's, so it is skipped, as on\n * Windows, rather than sent from that account.\n */\nexport async function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult> {\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'drafts', 'draftsFolder')}\n set wanted to id of every message of draftsFolder\n set sentCount to 0\n set out to \"\"\n repeat with theId in wanted\n set subj to \"\"\n try\n set theMsg to missing value\n try\n set theMsg to message id theId\n end try\n if theMsg is missing value then error \"draft is no longer in the Drafts folder\"\n${boundElsewhereSnippet(request.account, ' ')}\n if not boundElsewhere then\n try\n set subj to (subject of theMsg) as string\n end try\n send theMsg\n set sentCount to sentCount + 1\n end if\n on error errText\n set out to out & ${ITEM_FAILURE_ROW}\n end try\n end repeat\n return ${asRow(['(sentCount as string)'])} & out\nend tell`, 'scan');\n const { summary, failed } = splitBatch(raw);\n return { sent: intField(summary, 0), failed };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/mac/drafts.ts"],"names":[],"mappings":"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","sourcesContent":["// Drafts: which ones belong to an account, and listing, sending or deleting them.\n//\n// Where Windows scans two Drafts folders and proves each item's sending\n// account, macOS resolves the one Drafts folder belonging to the account —\n// through the account object, or by the folder's own id for a mailbox the\n// dictionary won't name (see profile.ts) — so a draft found there belongs to\n// the account by construction.\n//\n// What the folder can't vouch for is an item's own binding, which is why the\n// guards compare against the requested address as a literal rather than asking\n// `targetAcct`: there may be no account object to ask.\nimport {\n asIdList,\n asRow,\n asString,\n field,\n intField,\n ITEM_FAILURE_ROW,\n runOsaScript,\n splitBatch,\n splitFields,\n splitList,\n splitRecords,\n} from './run';\nimport {\n accountLookupSnippet,\n bodyTextSnippet,\n macFolderPath,\n messageByIdSnippet,\n partitionMessageIds,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../backend';\nimport type { DeleteDraftsResult, ItemFailure, ListDraftsResult, SendDraftsResult } from '../types';\n\n/** The account's drafts, newest first. Bodies are previewed, never returned whole. */\nexport async function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult> {\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'drafts', 'draftsFolder')}\n set out to \"\"\n repeat with theMsg in (messages of draftsFolder)\n set subj to \"\"\n try\n set subj to (subject of theMsg) as string\n end try\n set toNames to {}\n set toAddrs to {}\n try\n repeat with r in (every to recipient of theMsg)\n try\n set ea to email address of r\n set oneAddr to \"\"\n set oneName to \"\"\n try\n set oneAddr to (address of ea) as string\n end try\n try\n set oneName to (name of ea) as string\n end try\n if oneName is \"\" then set oneName to oneAddr\n if oneName is not \"\" then set end of toNames to oneName\n if oneAddr is not \"\" then set end of toAddrs to oneAddr\n end try\n end repeat\n end try\n${bodyTextSnippet(' ', request.previewChars)}\n set attCount to 0\n try\n set attCount to count of attachments of theMsg\n end try\n set modAt to \"\"\n try\n set modAt to my isoDate(modification date of theMsg)\n end try\n${boundElsewhereSnippet(request.account, ' ')}\n if not boundElsewhere then set out to out & ${asRow(['(id of theMsg as string)', 'subj', 'my joinList(toNames, \", \")', 'my sanitizeList(toAddrs)', 'bodyText', '(attCount as string)', 'modAt'])}\n end repeat\n return out\nend tell`, 'standard');\n\n const folderPath = macFolderPath(request.account, 'Drafts', []);\n const drafts = splitRecords(raw).map(record => {\n const parts = splitFields(record);\n return {\n entryId: field(parts, 0),\n subject: field(parts, 1),\n to: field(parts, 2),\n toEmails: splitList(field(parts, 3)),\n bodyPreview: field(parts, 4).replace(/\\s+/g, ' ').trim(),\n hasAttachments: intField(parts, 5) > 0,\n lastModified: field(parts, 6),\n folderPath,\n };\n });\n drafts.sort((a, b) => b.lastModified.localeCompare(a.lastModified));\n return {\n account: request.account,\n foldersScanned: [folderPath],\n count: drafts.length,\n truncated: drafts.length > request.limit,\n drafts: drafts.slice(0, request.limit),\n };\n}\n\n/**\n * Set `boundElsewhere` to whether `theMsg`, a message in the account's Drafts\n * folder, is set to send from some OTHER account. An unbound draft there is the\n * account's own. Listing, sending all and the per-id guard all apply this one\n * rule, as Windows' Test-DraftMatches is applied to all three.\n */\nfunction boundElsewhereSnippet(emailAccount: string, indent: string): string {\n return `${indent}set boundTo to \"\"\n${indent}try\n${indent} set boundTo to (email address of (account of theMsg)) as string\n${indent}end try\n${indent}set boundElsewhere to (boundTo is not \"\" and boundTo is not ${asString(emailAccount)})`;\n}\n\n/**\n * The AppleScript that proves `theMsg` is one of this account's drafts before\n * `verb` happens to it: it sits in the account's Drafts folder, and it is bound\n * to this account or to none.\n */\nfunction ownDraftGuard(emailAccount: string, verb: 'send' | 'delete'): string {\n return ` set inFolder to \"\"\n try\n set inFolder to (name of (folder of theMsg)) as string\n end try\n if inFolder is not draftsName or draftIds does not contain (id of theMsg) then error \"item is not in this account's Drafts folder - refusing to ${verb}\"\n${boundElsewhereSnippet(emailAccount, ' ')}\n if boundElsewhere then error \"draft is not bound to this account - refusing to ${verb}\"`;\n}\n\n/** Run `verb` over named drafts of the account, each proved to be its own first. */\nasync function actOnDrafts(\n request: EntryIdsRequest,\n verb: 'send' | 'delete',\n): Promise<{ done: number; failed: ItemFailure[] }> {\n const { valid, invalid } = partitionMessageIds(request.entryIds);\n if (valid.length === 0) return { done: 0, failed: invalid };\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'drafts', 'draftsFolder')}\n set draftsName to (name of draftsFolder) as string\n set draftIds to id of every message of draftsFolder\n set doneCount to 0\n set out to \"\"\n repeat with theId in ${asIdList(valid)}\n set subj to \"\"\n try\n${messageByIdSnippet(' ')}\n if theMsg is missing value then error \"no such item in this account's Drafts folder\"\n try\n set subj to (subject of theMsg) as string\n end try\n${ownDraftGuard(request.account, verb)}\n ${verb} theMsg\n set doneCount to doneCount + 1\n on error errText\n set out to out & ${ITEM_FAILURE_ROW}\n end try\n end repeat\n return ${asRow(['(doneCount as string)'])} & out\nend tell`, 'scan');\n const { summary, failed } = splitBatch(raw);\n return { done: intField(summary, 0), failed: [...invalid, ...failed] };\n}\n\n/** Send the named drafts. */\nexport async function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult> {\n const { done, failed } = await actOnDrafts(request, 'send');\n return { sent: done, failed };\n}\n\n/**\n * Delete the named drafts. `delete` files an item in Deleted Items, so a\n * mistaken call stays recoverable.\n */\nexport async function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult> {\n const { done, failed } = await actOnDrafts(request, 'delete');\n return { deleted: done, failed };\n}\n\n/**\n * Send every draft belonging to the account. The ids are snapshotted before the\n * first send: sending moves an item out of Drafts, and mutating the collection\n * mid-enumeration would skip every other one. A draft in the folder that is set\n * to send from another account is not this account's, so it is skipped, as on\n * Windows, rather than sent from that account.\n */\nexport async function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult> {\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'drafts', 'draftsFolder')}\n set wanted to id of every message of draftsFolder\n set sentCount to 0\n set out to \"\"\n repeat with theId in wanted\n set subj to \"\"\n try\n${messageByIdSnippet(' ')}\n if theMsg is missing value then error \"draft is no longer in the Drafts folder\"\n${boundElsewhereSnippet(request.account, ' ')}\n if not boundElsewhere then\n try\n set subj to (subject of theMsg) as string\n end try\n send theMsg\n set sentCount to sentCount + 1\n end if\n on error errText\n set out to out & ${ITEM_FAILURE_ROW}\n end try\n end repeat\n return ${asRow(['(sentCount as string)'])} & out\nend tell`, 'scan');\n const { summary, failed } = splitBatch(raw);\n return { sent: intField(summary, 0), failed };\n}\n"]}
|
package/dist/mac/folders.d.ts
CHANGED
|
@@ -15,5 +15,10 @@ export declare function listInboxFolders(request: ListFoldersRequest): Promise<I
|
|
|
15
15
|
* one real move cover for silent failures, and give no per-item results when it
|
|
16
16
|
* tripped. A message already in the destination counts as moved without being
|
|
17
17
|
* moved, as Windows' Move() to its own folder does.
|
|
18
|
+
*
|
|
19
|
+
* Only the account's own mail is moved. A message id resolves against the whole
|
|
20
|
+
* application, so each one is first shown to sit in this account — as Windows
|
|
21
|
+
* resolves every id in the account's store — and one that doesn't fails as its
|
|
22
|
+
* own item rather than being filed out of another mailbox.
|
|
18
23
|
*/
|
|
19
24
|
export declare function moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult>;
|
package/dist/mac/folders.js
CHANGED
|
@@ -61,6 +61,11 @@ return my walkFolders(inb, 1, ${(0, run_1.asInt)(request.maxDepth)}, "")`, 'stan
|
|
|
61
61
|
* one real move cover for silent failures, and give no per-item results when it
|
|
62
62
|
* tripped. A message already in the destination counts as moved without being
|
|
63
63
|
* moved, as Windows' Move() to its own folder does.
|
|
64
|
+
*
|
|
65
|
+
* Only the account's own mail is moved. A message id resolves against the whole
|
|
66
|
+
* application, so each one is first shown to sit in this account — as Windows
|
|
67
|
+
* resolves every id in the account's store — and one that doesn't fails as its
|
|
68
|
+
* own item rather than being filed out of another mailbox.
|
|
64
69
|
*/
|
|
65
70
|
async function moveOutlookEmails(request) {
|
|
66
71
|
const folderPath = (0, scripts_1.macFolderPath)(request.account, request.folder.rootLabel, request.folder.segments);
|
|
@@ -70,6 +75,7 @@ async function moveOutlookEmails(request) {
|
|
|
70
75
|
const acct = await (0, scripts_1.resolveMacAccount)(request.account);
|
|
71
76
|
const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
72
77
|
${(0, scripts_1.accountLookupSnippet)(acct)}
|
|
78
|
+
${(0, scripts_1.accountRootIdSnippet)(acct)}
|
|
73
79
|
${(0, scripts_1.mailScopeSnippet)(acct, request.folder, request.folderLabel, request.createIfMissing)}
|
|
74
80
|
set scopeId to id of scopeFolder
|
|
75
81
|
set movedCount to 0
|
|
@@ -77,17 +83,20 @@ ${(0, scripts_1.mailScopeSnippet)(acct, request.folder, request.folderLabel, req
|
|
|
77
83
|
repeat with theId in ${(0, run_1.asIdList)(valid)}
|
|
78
84
|
set subj to ""
|
|
79
85
|
try
|
|
80
|
-
|
|
81
|
-
try
|
|
82
|
-
set theMsg to message id theId
|
|
83
|
-
end try
|
|
86
|
+
${(0, scripts_1.messageByIdSnippet)(' ')}
|
|
84
87
|
if theMsg is missing value then error "no message with that id in this Outlook"
|
|
85
88
|
try
|
|
86
89
|
set subj to (subject of theMsg) as string
|
|
87
90
|
end try
|
|
91
|
+
set theFolder to missing value
|
|
92
|
+
try
|
|
93
|
+
set theFolder to folder of theMsg
|
|
94
|
+
end try
|
|
95
|
+
${(0, scripts_1.folderChainSnippet)(' ')}
|
|
96
|
+
${(0, scripts_1.ownMailGuardSnippet)(request.account, 'move', ' ')}
|
|
88
97
|
set fromId to missing value
|
|
89
98
|
try
|
|
90
|
-
set fromId to id of
|
|
99
|
+
set fromId to id of theFolder
|
|
91
100
|
end try
|
|
92
101
|
if fromId is not scopeId then
|
|
93
102
|
set countBefore to count of messages of scopeFolder
|
package/dist/mac/folders.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/mac/folders.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/mac/folders.ts"],"names":[],"mappings":";;AAoCA,4CAyCC;AAiBD,8CAiDC;AA/ID,gEAAgE;AAChE,+BAce;AACf,uCAWmB;AAInB;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CAAC,OAA2B;IAC9D,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;;;;;;;;;;;;;;;;;2BAiBR,IAAA,WAAK,EAAC,CAAC,IAAI,EAAE,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,CAAC,CAAC;;kFAEV,iBAAW;;;;;;;EAO3F,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,2BAAiB,EAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC;;gCAET,IAAA,WAAK,EAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IACxE,OAAO,IAAA,kBAAY,EAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;QAClC,MAAM,KAAK,GAAG,IAAA,iBAAW,EAAC,MAAM,CAAC,CAAC;QAClC,OAAO;YACH,IAAI,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;YACrB,UAAU,EAAE,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,IAAA,eAAS,EAAC,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;YAC/E,SAAS,EAAE,IAAA,cAAQ,EAAC,KAAK,EAAE,CAAC,CAAC;YAC7B,KAAK,EAAE,IAAA,cAAQ,EAAC,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;SAC/B,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACI,KAAK,UAAU,iBAAiB,CAAC,OAAoB;IACxD,MAAM,UAAU,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACrG,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAA,6BAAmB,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC/F,MAAM,IAAI,GAAG,MAAM,IAAA,2BAAiB,EAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;EACjC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,0BAAgB,EAAC,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC;;;;2BAI3D,IAAA,cAAQ,EAAC,KAAK,CAAC;;;EAGxC,IAAA,4BAAkB,EAAC,cAAc,CAAC;;;;;;;;;EASlC,IAAA,4BAAkB,EAAC,cAAc,CAAC;EAClC,IAAA,6BAAmB,EAAC,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC;;;;;;;;;;;;+BAY/B,sBAAgB;;;aAGlC,IAAA,WAAK,EAAC,CAAC,wBAAwB,EAAE,0BAA0B,CAAC,CAAC;SACjE,EAAE,MAAM,CAAC,CAAC;IACf,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAA,gBAAU,EAAC,GAAG,CAAC,CAAC;IAC5C,OAAO;QACH,UAAU;QACV,aAAa,EAAE,IAAA,eAAS,EAAC,OAAO,EAAE,CAAC,CAAC;QACpC,KAAK,EAAE,IAAA,cAAQ,EAAC,OAAO,EAAE,CAAC,CAAC;QAC3B,MAAM,EAAE,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC;KAClC,CAAC;AACN,CAAC","sourcesContent":["// Listing the folders under an Inbox, and filing mail into one.\nimport {\n AS_LIST_SEP,\n asIdList,\n asInt,\n asRow,\n boolField,\n field,\n intField,\n ITEM_FAILURE_ROW,\n runOsaScript,\n splitBatch,\n splitFields,\n splitList,\n splitRecords,\n} from './run';\nimport {\n accountLookupSnippet,\n accountRootIdSnippet,\n folderChainSnippet,\n macFolderPath,\n mailScopeSnippet,\n messageByIdSnippet,\n ownMailGuardSnippet,\n partitionMessageIds,\n resolveMacAccount,\n rootFolderSnippet,\n} from './scripts';\nimport type { ListFoldersRequest, MoveRequest } from '../backend';\nimport type { InboxFolderInfo, MoveEmailsResult } from '../types';\n\n/**\n * The folders under an account's Inbox with their message counts. Path segments\n * travel joined by the LIST separator and are assembled into a folder path in\n * TypeScript, rather than pushed through two layers of backslash escaping.\n */\nexport async function listInboxFolders(request: ListFoldersRequest): Promise<InboxFolderInfo[]> {\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`on walkFolders(theFolder, level, maxLevel, prefix)\n set out to \"\"\n tell application \"Microsoft Outlook\"\n set subs to mail folders of theFolder\n end tell\n repeat with f in subs\n set nm to \"\"\n set cnt to 0\n tell application \"Microsoft Outlook\"\n try\n set nm to (name of f) as string\n end try\n try\n set cnt to (count of messages of f)\n end try\n end tell\n set thisPath to prefix & nm\n set out to out & ${asRow(['nm', 'thisPath', '(cnt as string)', '(level as string)'])}\n if level < maxLevel then\n set out to out & (my walkFolders(f, level + 1, maxLevel, thisPath & ${AS_LIST_SEP}))\n end if\n end repeat\n return out\nend walkFolders\n\ntell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${rootFolderSnippet(acct, 'inbox', 'inb')}\nend tell\nreturn my walkFolders(inb, 1, ${asInt(request.maxDepth)}, \"\")`, 'standard');\n return splitRecords(raw).map(record => {\n const parts = splitFields(record);\n return {\n name: field(parts, 0),\n folderPath: macFolderPath(request.account, 'Inbox', splitList(field(parts, 1))),\n itemCount: intField(parts, 2),\n depth: intField(parts, 3, 1),\n };\n });\n}\n\n/**\n * File emails into a folder of the account. Moving rewrites each message's id.\n *\n * Outlook for Mac's `move` reports success even where it silently does nothing,\n * so each move is confirmed by the destination growing, and one that didn't\n * fails as its own item. A count compared once around the whole batch would let\n * one real move cover for silent failures, and give no per-item results when it\n * tripped. A message already in the destination counts as moved without being\n * moved, as Windows' Move() to its own folder does.\n *\n * Only the account's own mail is moved. A message id resolves against the whole\n * application, so each one is first shown to sit in this account — as Windows\n * resolves every id in the account's store — and one that doesn't fails as its\n * own item rather than being filed out of another mailbox.\n */\nexport async function moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult> {\n const folderPath = macFolderPath(request.account, request.folder.rootLabel, request.folder.segments);\n const { valid, invalid } = partitionMessageIds(request.entryIds);\n if (valid.length === 0) return { folderPath, folderCreated: false, moved: 0, failed: invalid };\n const acct = await resolveMacAccount(request.account);\n const raw = await runOsaScript(`tell application \"Microsoft Outlook\"\n${accountLookupSnippet(acct)}\n${accountRootIdSnippet(acct)}\n${mailScopeSnippet(acct, request.folder, request.folderLabel, request.createIfMissing)}\n set scopeId to id of scopeFolder\n set movedCount to 0\n set out to \"\"\n repeat with theId in ${asIdList(valid)}\n set subj to \"\"\n try\n${messageByIdSnippet(' ')}\n if theMsg is missing value then error \"no message with that id in this Outlook\"\n try\n set subj to (subject of theMsg) as string\n end try\n set theFolder to missing value\n try\n set theFolder to folder of theMsg\n end try\n${folderChainSnippet(' ')}\n${ownMailGuardSnippet(request.account, 'move', ' ')}\n set fromId to missing value\n try\n set fromId to id of theFolder\n end try\n if fromId is not scopeId then\n set countBefore to count of messages of scopeFolder\n move theMsg to scopeFolder\n if (count of messages of scopeFolder) is not greater than countBefore then error \"Outlook reported the move but '\" & (name of scopeFolder) & \"' did not grow - not filed\"\n end if\n set movedCount to movedCount + 1\n on error errText\n set out to out & ${ITEM_FAILURE_ROW}\n end try\n end repeat\n return ${asRow(['(movedCount as string)', '(scopeCreated as string)'])} & out\nend tell`, 'scan');\n const { summary, failed } = splitBatch(raw);\n return {\n folderPath,\n folderCreated: boolField(summary, 1),\n moved: intField(summary, 0),\n failed: [...invalid, ...failed],\n };\n}\n"]}
|
package/dist/mac/profile.d.ts
CHANGED
|
@@ -4,6 +4,18 @@ export interface ProfileAccount {
|
|
|
4
4
|
/** AppleScript folder id per root term — the keys of `ROOT_SPECIAL_TYPES`. */
|
|
5
5
|
folderIds: Record<string, number>;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Why Outlook's profile can't be read, when macOS privacy control is the
|
|
9
|
+
* reason: the sentence to act on, naming the app to grant access to. null when
|
|
10
|
+
* the profile can be read, or simply isn't there — New Outlook, or no Outlook
|
|
11
|
+
* profile on this Mac.
|
|
12
|
+
*
|
|
13
|
+
* Everything else in this module stays silent on purpose, which is right for an
|
|
14
|
+
* enrichment pass and wrong when the pass was the only way to see a mailbox:
|
|
15
|
+
* the caller then hears "no accounts" or "not found" about a mailbox in plain
|
|
16
|
+
* sight in Outlook. The callers that would say that ask here first.
|
|
17
|
+
*/
|
|
18
|
+
export declare function profileRefusal(): Promise<string | null>;
|
|
7
19
|
/**
|
|
8
20
|
* Every mail account the profile knows, whether or not AppleScript can see it.
|
|
9
21
|
*
|
package/dist/mac/profile.js
CHANGED
|
@@ -3,6 +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.profileRefusal = profileRefusal;
|
|
6
7
|
exports.readProfileAccounts = readProfileAccounts;
|
|
7
8
|
// Outlook's own profile database, read for the mailboxes the dictionary omits.
|
|
8
9
|
//
|
|
@@ -24,7 +25,9 @@ exports.readProfileAccounts = readProfileAccounts;
|
|
|
24
25
|
// folder id wherever the account probe comes up empty.
|
|
25
26
|
//
|
|
26
27
|
// Strictly read-only, and never fatal: every failure here leaves the caller with
|
|
27
|
-
// exactly what AppleScript could see on its own.
|
|
28
|
+
// exactly what AppleScript could see on its own. When macOS privacy control is
|
|
29
|
+
// what failed, `profileRefusal` says so, for the callers that would otherwise
|
|
30
|
+
// report a mailbox in plain sight as missing.
|
|
28
31
|
const child_process_1 = require("child_process");
|
|
29
32
|
const fs_1 = __importDefault(require("fs"));
|
|
30
33
|
const os_1 = __importDefault(require("os"));
|
|
@@ -111,6 +114,77 @@ function query(database, sql) {
|
|
|
111
114
|
(0, child_process_1.execFile)('sqlite3', ['-readonly', '-list', '-noheader', '-separator', SEP, database, sql], { timeout: 5000, maxBuffer: 1024 * 1024 }, (error, stdout) => resolve(error ? '' : stdout));
|
|
112
115
|
});
|
|
113
116
|
}
|
|
117
|
+
/** The app macOS holds this process's file access against, once asked. */
|
|
118
|
+
let responsible = null;
|
|
119
|
+
/**
|
|
120
|
+
* The app macOS privacy control asks about this process's file access, by name:
|
|
121
|
+
* the outermost app bundle among its ancestors, the one launchd started. A
|
|
122
|
+
* process started from a terminal or an editor is that app's to grant, not its
|
|
123
|
+
* own, so naming this process would send the person to the wrong switch. '' when
|
|
124
|
+
* it can't be told.
|
|
125
|
+
*/
|
|
126
|
+
function responsibleApp() {
|
|
127
|
+
responsible ??= new Promise(resolve => {
|
|
128
|
+
(0, child_process_1.execFile)('ps', ['-A', '-o', 'pid=,ppid=,comm='], {
|
|
129
|
+
timeout: 5000,
|
|
130
|
+
maxBuffer: 4 * 1024 * 1024
|
|
131
|
+
}, (error, stdout) => {
|
|
132
|
+
if (error)
|
|
133
|
+
return resolve('');
|
|
134
|
+
const processes = new Map();
|
|
135
|
+
for (const line of stdout.split('\n')) {
|
|
136
|
+
const row = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
137
|
+
if (row)
|
|
138
|
+
processes.set(Number(row[1]), { ppid: Number(row[2]), command: row[3] ?? '' });
|
|
139
|
+
}
|
|
140
|
+
let app = '';
|
|
141
|
+
const climbed = new Set();
|
|
142
|
+
for (let pid = process.pid; pid > 1 && !climbed.has(pid);) {
|
|
143
|
+
climbed.add(pid);
|
|
144
|
+
const entry = processes.get(pid);
|
|
145
|
+
if (!entry)
|
|
146
|
+
break;
|
|
147
|
+
// The first bundle in a path is the outer one: a helper app sits
|
|
148
|
+
// inside its parent's. Each one higher up replaces the last.
|
|
149
|
+
const bundle = /\/([^/]+)\.app(?:\/|$)/.exec(entry.command)?.[1];
|
|
150
|
+
if (bundle)
|
|
151
|
+
app = bundle;
|
|
152
|
+
pid = entry.ppid;
|
|
153
|
+
}
|
|
154
|
+
resolve(app);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return responsible;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Why Outlook's profile can't be read, when macOS privacy control is the
|
|
161
|
+
* reason: the sentence to act on, naming the app to grant access to. null when
|
|
162
|
+
* the profile can be read, or simply isn't there — New Outlook, or no Outlook
|
|
163
|
+
* profile on this Mac.
|
|
164
|
+
*
|
|
165
|
+
* Everything else in this module stays silent on purpose, which is right for an
|
|
166
|
+
* enrichment pass and wrong when the pass was the only way to see a mailbox:
|
|
167
|
+
* the caller then hears "no accounts" or "not found" about a mailbox in plain
|
|
168
|
+
* sight in Outlook. The callers that would say that ask here first.
|
|
169
|
+
*/
|
|
170
|
+
async function profileRefusal() {
|
|
171
|
+
try {
|
|
172
|
+
fs_1.default.readdirSync(PROFILES_DIR);
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const code = error.code;
|
|
177
|
+
if (code !== 'EPERM' && code !== 'EACCES')
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const app = await responsibleApp();
|
|
181
|
+
const who = app || 'the app this runs in (the terminal or editor it was started from, if it was)';
|
|
182
|
+
const it = app || 'that app';
|
|
183
|
+
// Plain ASCII: the sentence also travels inside a generated script's string literal.
|
|
184
|
+
return `macOS is not letting ${who} read Outlook's profile, the only place Outlook for Mac lists `
|
|
185
|
+
+ "a Microsoft 365 mailbox it syncs through Microsoft's cloud. "
|
|
186
|
+
+ `Turn on ${it} under System Settings > Privacy & Security > Full Disk Access, then quit ${it} and open it again.`;
|
|
187
|
+
}
|
|
114
188
|
/**
|
|
115
189
|
* Every mail account the profile knows, whether or not AppleScript can see it.
|
|
116
190
|
*
|
package/dist/mac/profile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/mac/profile.ts"],"names":[],"mappings":"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","sourcesContent":["// Outlook's own profile database, read for the mailboxes the dictionary omits.\n//\n// `getOutlookAccounts` can only enumerate `exchange accounts`, `imap accounts`\n// and `pop accounts`, and a Microsoft 365 mailbox added by the modern sync\n// engine is none of those: Outlook files it in `AccountsMail` under a 'SHDO'\n// server type, publishes no account object for it, and answers `every account`\n// with -1728 rather than listing it. Even `account of <folder>` and\n// `account of <message>` come back `missing value` inside that mailbox's own\n// tree, so there is nothing to match an address against — the mailbox is\n// invisible to every function in this directory while its mail sits in plain\n// sight in the folder list.\n//\n// The folders themselves are reachable, because `mail folder id N` resolves\n// against the application rather than through an account, and N is exactly the\n// `Record_RecordID` Outlook's profile database gives that folder. So the\n// addresses AppleScript can't report, and the ids of their well-known folders,\n// are read from that database instead, and the generated scripts fall back to a\n// folder id wherever the account probe comes up empty.\n//\n// Strictly read-only, and never fatal: every failure here leaves the caller with\n// exactly what AppleScript could see on its own.\nimport { execFile } from 'child_process';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\n/** Where Outlook keeps its profiles, and the profile used when there are several. */\nconst PROFILES_DIR = path.join(\n os.homedir(),\n 'Library', 'Group Containers', 'UBF8T346G9.Office', 'Outlook', 'Outlook 15 Profiles',\n);\nconst DEFAULT_PROFILE = 'Main Profile';\n\n/**\n * `Folder_SpecialFolderType` for each root the generated scripts ask for, keyed\n * by the dictionary term `MAC_ROOT_TERMS` resolves.\n *\n * These are Outlook's own numbering, confirmed against a profile's local \"On My\n * Computer\" store, where every one of them appears exactly once: 1 Inbox,\n * 2 Outbox, 8 Sent Items, 9 Deleted Items, 10 Drafts, 12 Junk, 99 the account\n * root. Outbox is left out: a caller can still name it (`MAC_ROOT_TERMS` maps it),\n * but nothing here files mail into one, so it is not worth an id — that request\n * falls back to the account probe like any root the profile has no row for.\n */\nconst ROOT_SPECIAL_TYPES: Readonly<Record<string, number>> = {\n 'inbox': 1,\n 'sent items': 8,\n 'deleted items': 9,\n 'drafts': 10,\n 'junk mail': 12,\n 'root folder': 99,\n};\n\n/** One mail account as the profile database records it. */\nexport interface ProfileAccount {\n emailAccount: string;\n /** AppleScript folder id per root term — the keys of `ROOT_SPECIAL_TYPES`. */\n folderIds: Record<string, number>;\n}\n\n/** Column separator for the query output - a unit separator cannot occur in a row. */\nconst SEP = '\\x1f';\n\n/**\n * Address and well-known folder ids for every mail account in the profile.\n *\n * `Record_AccountUID` is a tagged id — the account's `Record_RecordID` in its low\n * 32 bits, a table tag above — so the join masks rather than assuming the tag,\n * and drops UID 0, which is the local \"On My Computer\" store rather than an\n * account.\n */\nconst ACCOUNTS_SQL = `\n SELECT a.Account_EmailAddress, f.Folder_SpecialFolderType, f.Record_RecordID\n FROM AccountsMail a\n JOIN Folders f ON (f.Record_AccountUID & 4294967295) = a.Record_RecordID\n WHERE f.Record_AccountUID <> 0\n AND a.Account_EmailAddress IS NOT NULL\n AND a.Account_EmailAddress <> ''\n AND f.Folder_SpecialFolderType IN (${Object.values(ROOT_SPECIAL_TYPES).join(', ')})\n ORDER BY a.Record_RecordID, f.Folder_SpecialFolderType`;\n\n/** The profile whose database to read, or '' when there is none to read. */\nfunction profileDatabase(): string {\n let names: string[];\n try {\n names = fs.readdirSync(PROFILES_DIR);\n } catch {\n return '';\n }\n // Outlook records no \"default profile\" anywhere reachable — not in its\n // preferences, not beside the profiles — so with more than one the newest\n // wins, after the name Outlook itself creates and all but everyone keeps.\n const dbOf = (name: string) => path.join(PROFILES_DIR, name, 'Data', 'Outlook.sqlite');\n // Outlook rewrites these files while it runs, and the whole directory sits\n // behind macOS privacy control, so a database that existed a moment ago need\n // not stat. An unreadable timestamp sorts last rather than throwing: this\n // runs on nearly every macOS operation (resolveMacAccount), and a mailbox\n // AppleScript could have named on its own must not be lost to it.\n const mtimeOf = (db: string) => {\n try {\n return fs.statSync(db).mtimeMs;\n } catch {\n return -Infinity;\n }\n };\n const candidates = names\n .map(dbOf)\n .filter(db => fs.existsSync(db))\n .sort((a, b) => mtimeOf(b) - mtimeOf(a));\n const preferred = dbOf(DEFAULT_PROFILE);\n if (candidates.includes(preferred)) return preferred;\n return candidates[0] ?? '';\n}\n\n/** Run one read-only query, or return '' if anything at all gets in the way. */\nfunction query(database: string, sql: string): Promise<string> {\n return new Promise(resolve => {\n execFile(\n 'sqlite3',\n ['-readonly', '-list', '-noheader', '-separator', SEP, database, sql],\n { timeout: 5000, maxBuffer: 1024 * 1024 },\n (error, stdout) => resolve(error ? '' : stdout),\n );\n });\n}\n\n/**\n * Every mail account the profile knows, whether or not AppleScript can see it.\n *\n * An account whose row is missing a given root simply has no id for it, and the\n * caller keeps whatever the account probe would have done.\n */\nexport async function readProfileAccounts(): Promise<ProfileAccount[]> {\n try {\n return await readAccounts();\n } catch {\n // The module's contract, enforced at its one exit: this is an enrichment\n // pass over what AppleScript already found, so nothing that happens in it\n // is worth failing an operation that would otherwise have worked.\n return [];\n }\n}\n\nasync function readAccounts(): Promise<ProfileAccount[]> {\n const database = profileDatabase();\n if (!database) return [];\n const termOf = new Map(Object.entries(ROOT_SPECIAL_TYPES).map(([term, type]) => [type, term]));\n const byAddress = new Map<string, ProfileAccount>();\n for (const line of (await query(database, ACCOUNTS_SQL)).split('\\n')) {\n const [address, specialType, folderId] = line.split(SEP);\n const term = termOf.get(Number(specialType));\n if (!address || !term || !/^\\d+$/.test(folderId ?? '')) continue;\n const account = byAddress.get(address.toLowerCase())\n ?? { emailAccount: address, folderIds: {} };\n account.folderIds[term] = Number(folderId);\n byAddress.set(address.toLowerCase(), account);\n }\n return [...byAddress.values()];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/mac/profile.ts"],"names":[],"mappings":"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","sourcesContent":["// Outlook's own profile database, read for the mailboxes the dictionary omits.\n//\n// `getOutlookAccounts` can only enumerate `exchange accounts`, `imap accounts`\n// and `pop accounts`, and a Microsoft 365 mailbox added by the modern sync\n// engine is none of those: Outlook files it in `AccountsMail` under a 'SHDO'\n// server type, publishes no account object for it, and answers `every account`\n// with -1728 rather than listing it. Even `account of <folder>` and\n// `account of <message>` come back `missing value` inside that mailbox's own\n// tree, so there is nothing to match an address against — the mailbox is\n// invisible to every function in this directory while its mail sits in plain\n// sight in the folder list.\n//\n// The folders themselves are reachable, because `mail folder id N` resolves\n// against the application rather than through an account, and N is exactly the\n// `Record_RecordID` Outlook's profile database gives that folder. So the\n// addresses AppleScript can't report, and the ids of their well-known folders,\n// are read from that database instead, and the generated scripts fall back to a\n// folder id wherever the account probe comes up empty.\n//\n// Strictly read-only, and never fatal: every failure here leaves the caller with\n// exactly what AppleScript could see on its own. When macOS privacy control is\n// what failed, `profileRefusal` says so, for the callers that would otherwise\n// report a mailbox in plain sight as missing.\nimport { execFile } from 'child_process';\nimport fs from 'fs';\nimport os from 'os';\nimport path from 'path';\n\n/** Where Outlook keeps its profiles, and the profile used when there are several. */\nconst PROFILES_DIR = path.join(\n os.homedir(),\n 'Library', 'Group Containers', 'UBF8T346G9.Office', 'Outlook', 'Outlook 15 Profiles',\n);\nconst DEFAULT_PROFILE = 'Main Profile';\n\n/**\n * `Folder_SpecialFolderType` for each root the generated scripts ask for, keyed\n * by the dictionary term `MAC_ROOT_TERMS` resolves.\n *\n * These are Outlook's own numbering, confirmed against a profile's local \"On My\n * Computer\" store, where every one of them appears exactly once: 1 Inbox,\n * 2 Outbox, 8 Sent Items, 9 Deleted Items, 10 Drafts, 12 Junk, 99 the account\n * root. Outbox is left out: a caller can still name it (`MAC_ROOT_TERMS` maps it),\n * but nothing here files mail into one, so it is not worth an id — that request\n * falls back to the account probe like any root the profile has no row for.\n */\nconst ROOT_SPECIAL_TYPES: Readonly<Record<string, number>> = {\n 'inbox': 1,\n 'sent items': 8,\n 'deleted items': 9,\n 'drafts': 10,\n 'junk mail': 12,\n 'root folder': 99,\n};\n\n/** One mail account as the profile database records it. */\nexport interface ProfileAccount {\n emailAccount: string;\n /** AppleScript folder id per root term — the keys of `ROOT_SPECIAL_TYPES`. */\n folderIds: Record<string, number>;\n}\n\n/** Column separator for the query output - a unit separator cannot occur in a row. */\nconst SEP = '\\x1f';\n\n/**\n * Address and well-known folder ids for every mail account in the profile.\n *\n * `Record_AccountUID` is a tagged id — the account's `Record_RecordID` in its low\n * 32 bits, a table tag above — so the join masks rather than assuming the tag,\n * and drops UID 0, which is the local \"On My Computer\" store rather than an\n * account.\n */\nconst ACCOUNTS_SQL = `\n SELECT a.Account_EmailAddress, f.Folder_SpecialFolderType, f.Record_RecordID\n FROM AccountsMail a\n JOIN Folders f ON (f.Record_AccountUID & 4294967295) = a.Record_RecordID\n WHERE f.Record_AccountUID <> 0\n AND a.Account_EmailAddress IS NOT NULL\n AND a.Account_EmailAddress <> ''\n AND f.Folder_SpecialFolderType IN (${Object.values(ROOT_SPECIAL_TYPES).join(', ')})\n ORDER BY a.Record_RecordID, f.Folder_SpecialFolderType`;\n\n/** The profile whose database to read, or '' when there is none to read. */\nfunction profileDatabase(): string {\n let names: string[];\n try {\n names = fs.readdirSync(PROFILES_DIR);\n } catch {\n return '';\n }\n // Outlook records no \"default profile\" anywhere reachable — not in its\n // preferences, not beside the profiles — so with more than one the newest\n // wins, after the name Outlook itself creates and all but everyone keeps.\n const dbOf = (name: string) => path.join(PROFILES_DIR, name, 'Data', 'Outlook.sqlite');\n // Outlook rewrites these files while it runs, and the whole directory sits\n // behind macOS privacy control, so a database that existed a moment ago need\n // not stat. An unreadable timestamp sorts last rather than throwing: this\n // runs on nearly every macOS operation (resolveMacAccount), and a mailbox\n // AppleScript could have named on its own must not be lost to it.\n const mtimeOf = (db: string) => {\n try {\n return fs.statSync(db).mtimeMs;\n } catch {\n return -Infinity;\n }\n };\n const candidates = names\n .map(dbOf)\n .filter(db => fs.existsSync(db))\n .sort((a, b) => mtimeOf(b) - mtimeOf(a));\n const preferred = dbOf(DEFAULT_PROFILE);\n if (candidates.includes(preferred)) return preferred;\n return candidates[0] ?? '';\n}\n\n/** Run one read-only query, or return '' if anything at all gets in the way. */\nfunction query(database: string, sql: string): Promise<string> {\n return new Promise(resolve => {\n execFile(\n 'sqlite3',\n ['-readonly', '-list', '-noheader', '-separator', SEP, database, sql],\n { timeout: 5000, maxBuffer: 1024 * 1024 },\n (error, stdout) => resolve(error ? '' : stdout),\n );\n });\n}\n\n/** The app macOS holds this process's file access against, once asked. */\nlet responsible: Promise<string> | null = null;\n\n/**\n * The app macOS privacy control asks about this process's file access, by name:\n * the outermost app bundle among its ancestors, the one launchd started. A\n * process started from a terminal or an editor is that app's to grant, not its\n * own, so naming this process would send the person to the wrong switch. '' when\n * it can't be told.\n */\nfunction responsibleApp(): Promise<string> {\n responsible ??= new Promise(resolve => {\n execFile('ps', ['-A', '-o', 'pid=,ppid=,comm='], {\n timeout: 5000,\n maxBuffer: 4 * 1024 * 1024\n }, (error, stdout) => {\n if (error) return resolve('');\n const processes = new Map<number, { ppid: number; command: string }>();\n for (const line of stdout.split('\\n')) {\n const row = /^\\s*(\\d+)\\s+(\\d+)\\s+(.*)$/.exec(line);\n if (row) processes.set(Number(row[1]), { ppid: Number(row[2]), command: row[3] ?? '' });\n }\n let app = '';\n const climbed = new Set<number>();\n for (let pid = process.pid; pid > 1 && !climbed.has(pid);) {\n climbed.add(pid);\n const entry = processes.get(pid);\n if (!entry) break;\n // The first bundle in a path is the outer one: a helper app sits\n // inside its parent's. Each one higher up replaces the last.\n const bundle = /\\/([^/]+)\\.app(?:\\/|$)/.exec(entry.command)?.[1];\n if (bundle) app = bundle;\n pid = entry.ppid;\n }\n resolve(app);\n });\n });\n return responsible;\n}\n\n/**\n * Why Outlook's profile can't be read, when macOS privacy control is the\n * reason: the sentence to act on, naming the app to grant access to. null when\n * the profile can be read, or simply isn't there — New Outlook, or no Outlook\n * profile on this Mac.\n *\n * Everything else in this module stays silent on purpose, which is right for an\n * enrichment pass and wrong when the pass was the only way to see a mailbox:\n * the caller then hears \"no accounts\" or \"not found\" about a mailbox in plain\n * sight in Outlook. The callers that would say that ask here first.\n */\nexport async function profileRefusal(): Promise<string | null> {\n try {\n fs.readdirSync(PROFILES_DIR);\n return null;\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'EPERM' && code !== 'EACCES') return null;\n }\n const app = await responsibleApp();\n const who = app || 'the app this runs in (the terminal or editor it was started from, if it was)';\n const it = app || 'that app';\n // Plain ASCII: the sentence also travels inside a generated script's string literal.\n return `macOS is not letting ${who} read Outlook's profile, the only place Outlook for Mac lists `\n + \"a Microsoft 365 mailbox it syncs through Microsoft's cloud. \"\n + `Turn on ${it} under System Settings > Privacy & Security > Full Disk Access, then quit ${it} and open it again.`;\n}\n\n/**\n * Every mail account the profile knows, whether or not AppleScript can see it.\n *\n * An account whose row is missing a given root simply has no id for it, and the\n * caller keeps whatever the account probe would have done.\n */\nexport async function readProfileAccounts(): Promise<ProfileAccount[]> {\n try {\n return await readAccounts();\n } catch {\n // The module's contract, enforced at its one exit: this is an enrichment\n // pass over what AppleScript already found, so nothing that happens in it\n // is worth failing an operation that would otherwise have worked.\n return [];\n }\n}\n\nasync function readAccounts(): Promise<ProfileAccount[]> {\n const database = profileDatabase();\n if (!database) return [];\n const termOf = new Map(Object.entries(ROOT_SPECIAL_TYPES).map(([term, type]) => [type, term]));\n const byAddress = new Map<string, ProfileAccount>();\n for (const line of (await query(database, ACCOUNTS_SQL)).split('\\n')) {\n const [address, specialType, folderId] = line.split(SEP);\n const term = termOf.get(Number(specialType));\n if (!address || !term || !/^\\d+$/.test(folderId ?? '')) continue;\n const account = byAddress.get(address.toLowerCase())\n ?? { emailAccount: address, folderIds: {} };\n account.folderIds[term] = Number(folderId);\n byAddress.set(address.toLowerCase(), account);\n }\n return [...byAddress.values()];\n}\n"]}
|