@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.
Files changed (99) hide show
  1. package/README.md +16 -9
  2. package/dist/backend.d.ts +2 -2
  3. package/dist/backend.js.map +1 -1
  4. package/dist/bridge.js +3 -3
  5. package/dist/bridge.js.map +1 -1
  6. package/dist/errors.d.ts +17 -8
  7. package/dist/errors.js +17 -13
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +3 -3
  10. package/dist/index.js +3 -2
  11. package/dist/index.js.map +1 -1
  12. package/dist/mac/accounts.d.ts +4 -0
  13. package/dist/mac/accounts.js +10 -0
  14. package/dist/mac/accounts.js.map +1 -1
  15. package/dist/mac/attachments.js +1 -1
  16. package/dist/mac/attachments.js.map +1 -1
  17. package/dist/mac/bounces.js +18 -31
  18. package/dist/mac/bounces.js.map +1 -1
  19. package/dist/mac/cleanup.d.ts +11 -3
  20. package/dist/mac/cleanup.js +30 -58
  21. package/dist/mac/cleanup.js.map +1 -1
  22. package/dist/mac/drafts.d.ts +3 -1
  23. package/dist/mac/drafts.js +37 -49
  24. package/dist/mac/drafts.js.map +1 -1
  25. package/dist/mac/folders.d.ts +10 -3
  26. package/dist/mac/folders.js +31 -23
  27. package/dist/mac/folders.js.map +1 -1
  28. package/dist/mac/profile.d.ts +12 -0
  29. package/dist/mac/profile.js +75 -1
  30. package/dist/mac/profile.js.map +1 -1
  31. package/dist/mac/read.d.ts +4 -0
  32. package/dist/mac/read.js +68 -42
  33. package/dist/mac/read.js.map +1 -1
  34. package/dist/mac/run.d.ts +19 -5
  35. package/dist/mac/run.js +49 -9
  36. package/dist/mac/run.js.map +1 -1
  37. package/dist/mac/scripts.d.ts +67 -14
  38. package/dist/mac/scripts.js +150 -31
  39. package/dist/mac/scripts.js.map +1 -1
  40. package/dist/mac/send.d.ts +5 -4
  41. package/dist/mac/send.js +52 -14
  42. package/dist/mac/send.js.map +1 -1
  43. package/dist/mac/templates.js +16 -22
  44. package/dist/mac/templates.js.map +1 -1
  45. package/dist/mail.d.ts +11 -3
  46. package/dist/mail.js +25 -6
  47. package/dist/mail.js.map +1 -1
  48. package/dist/runtime.js.map +1 -1
  49. package/dist/shared/args.js.map +1 -1
  50. package/dist/shared/attachmentMatch.d.ts +0 -6
  51. package/dist/shared/attachmentMatch.js +0 -1
  52. package/dist/shared/attachmentMatch.js.map +1 -1
  53. package/dist/shared/exec.js.map +1 -1
  54. package/dist/shared/locate.d.ts +0 -8
  55. package/dist/shared/locate.js +0 -1
  56. package/dist/shared/locate.js.map +1 -1
  57. package/dist/shared/replyBody.d.ts +0 -15
  58. package/dist/shared/replyBody.js +0 -2
  59. package/dist/shared/replyBody.js.map +1 -1
  60. package/dist/shared/templateLimits.d.ts +14 -0
  61. package/dist/shared/templateLimits.js +21 -0
  62. package/dist/shared/templateLimits.js.map +1 -0
  63. package/dist/templateBody.d.ts +6 -0
  64. package/dist/templateBody.js +8 -1
  65. package/dist/templateBody.js.map +1 -1
  66. package/dist/types.d.ts +43 -7
  67. package/dist/types.js.map +1 -1
  68. package/dist/windows/accounts.js +1 -1
  69. package/dist/windows/accounts.js.map +1 -1
  70. package/dist/windows/attachments.js +2 -2
  71. package/dist/windows/attachments.js.map +1 -1
  72. package/dist/windows/bounces.js +7 -7
  73. package/dist/windows/bounces.js.map +1 -1
  74. package/dist/windows/cleanup.d.ts +5 -1
  75. package/dist/windows/cleanup.js +18 -15
  76. package/dist/windows/cleanup.js.map +1 -1
  77. package/dist/windows/drafts.d.ts +4 -4
  78. package/dist/windows/drafts.js +22 -33
  79. package/dist/windows/drafts.js.map +1 -1
  80. package/dist/windows/folders.js +2 -2
  81. package/dist/windows/folders.js.map +1 -1
  82. package/dist/{shared → windows}/json.d.ts +11 -6
  83. package/dist/{shared → windows}/json.js +12 -7
  84. package/dist/windows/json.js.map +1 -0
  85. package/dist/windows/read.js +23 -16
  86. package/dist/windows/read.js.map +1 -1
  87. package/dist/windows/run.d.ts +0 -2
  88. package/dist/windows/run.js +1 -2
  89. package/dist/windows/run.js.map +1 -1
  90. package/dist/windows/scripts.d.ts +10 -4
  91. package/dist/windows/scripts.js +21 -12
  92. package/dist/windows/scripts.js.map +1 -1
  93. package/dist/windows/send.d.ts +13 -4
  94. package/dist/windows/send.js +24 -6
  95. package/dist/windows/send.js.map +1 -1
  96. package/dist/windows/templates.js +11 -17
  97. package/dist/windows/templates.js.map +1 -1
  98. package/package.json +2 -2
  99. package/dist/shared/json.js.map +0 -1
@@ -6,43 +6,19 @@ exports.purgeDeletedItems = purgeDeletedItems;
6
6
  const run_1 = require("./run");
7
7
  const scripts_1 = require("./scripts");
8
8
  const mail_1 = require("../mail");
9
- const OUTCOME_STATUSES = ['deleted', 'would-delete', 'refused', 'failed'];
10
- /**
11
- * Walk a folder's `container` chain to the top as the list `chainNames`,
12
- * nearest first. Requires `theFolder`.
13
- *
14
- * This is what extends the Inbox/Sent Items refusal to their SUBFOLDERS. The
15
- * chain's last element is the folder directly under the account, and an account
16
- * can't hold two top-level folders of one name, so comparing that name is exact.
17
- */
18
- function folderChainSnippet(indent) {
19
- return `${indent}set chainNames to {}
20
- ${indent}set cur to theFolder
21
- ${indent}set guard to 0
22
- ${indent}repeat while cur is not missing value and guard < 25
23
- ${indent} -- The account's root folder has a name of missing value, and coercing
24
- ${indent} -- that yields the STRING "missing value" — so test before coercing.
25
- ${indent} set rawName to missing value
26
- ${indent} try
27
- ${indent} set rawName to name of cur
28
- ${indent} end try
29
- ${indent} if rawName is missing value then exit repeat
30
- ${indent} set nm to rawName as string
31
- ${indent} if nm is "" then exit repeat
32
- ${indent} set end of chainNames to nm
33
- ${indent} set nextUp to missing value
34
- ${indent} try
35
- ${indent} set nextUp to container of cur
36
- ${indent} end try
37
- ${indent} set cur to nextUp
38
- ${indent} set guard to guard + 1
39
- ${indent}end repeat`;
40
- }
41
9
  /**
42
10
  * Delete mail by message id from anywhere in the account, into Deleted Items.
43
- * Inbox and Sent Items (and their subfolders) are refused unless
44
- * `allowProtected`; `dryRun` reports without deleting; every outcome names the
45
- * subject and folder of the item that actually resolved.
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.
46
22
  */
47
23
  async function deleteOutlookEmails(request) {
48
24
  const { valid, invalid } = (0, scripts_1.partitionMessageIds)(request.entryIds);
@@ -55,21 +31,23 @@ async function deleteOutlookEmails(request) {
55
31
  }));
56
32
  if (valid.length > 0) {
57
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');
58
40
  const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
59
41
  ${(0, scripts_1.accountLookupSnippet)(acct)}
60
- ${(0, scripts_1.rootFolderSnippet)(acct, 'inbox', 'inboxFolder')}
61
- ${(0, scripts_1.rootFolderSnippet)(acct, 'sent items', 'sentFolder')}
62
- set inboxName to (name of inboxFolder) as string
63
- set sentName to (name of sentFolder) as string
42
+ ${(0, scripts_1.accountRootIdSnippet)(acct)}
43
+ set protectedNames to {}
44
+ ${readProtectedNames}
64
45
  set out to ""
65
46
  repeat with theId in ${(0, run_1.asIdList)(valid)}
66
47
  set subj to ""
67
48
  set pathText to ""
68
49
  try
69
- set theMsg to missing value
70
- try
71
- set theMsg to message id theId
72
- end try
50
+ ${(0, scripts_1.messageByIdSnippet)(' ')}
73
51
  if theMsg is missing value then error "no item with that message id in this Outlook"
74
52
  try
75
53
  set subj to (subject of theMsg) as string
@@ -79,10 +57,12 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'sent items', 'sentFolder')}
79
57
  set theFolder to folder of theMsg
80
58
  end try
81
59
  if theFolder is missing value then error "item has no parent folder - refusing to delete"
82
- ${folderChainSnippet(' ')}
60
+ ${(0, scripts_1.folderChainSnippet)(' ')}
61
+ ${(0, scripts_1.ownMailGuardSnippet)(request.account, 'delete', ' ')}
83
62
  set pathText to my sanitizeList(my reverseList(chainNames))
63
+ -- The chain's top is what extends the refusal to Inbox and Sent Items SUBFOLDERS.
84
64
  set rootName to item (count of chainNames) of chainNames
85
- set isProtected to (rootName is inboxName) or (rootName is sentName)
65
+ set isProtected to (protectedNames contains rootName)
86
66
  if isProtected and not ${(0, run_1.asBool)(request.allowProtected)} then
87
67
  set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'pathText', '"refused"', (0, run_1.asString)(mail_1.PROTECTED_MAIL_REASON)])}
88
68
  else if ${(0, run_1.asBool)(request.dryRun)} then
@@ -112,18 +92,12 @@ end reverseList`, 'scan');
112
92
  entryId: (0, run_1.field)(parts, 0),
113
93
  subject: (0, run_1.field)(parts, 1).trim(),
114
94
  folderPath: segments.length ? (0, scripts_1.macFolderPath)(request.account, segments[0], segments.slice(1)) : '',
115
- status: OUTCOME_STATUSES.find(s => s === (0, run_1.field)(parts, 3)) ?? 'failed',
95
+ status: (0, mail_1.deleteStatus)((0, run_1.field)(parts, 3)),
116
96
  reason: (0, run_1.field)(parts, 4),
117
97
  });
118
98
  }
119
99
  }
120
- return {
121
- dryRun: request.dryRun,
122
- deleted: items.filter(i => i.status === 'deleted').length,
123
- refused: items.filter(i => i.status === 'refused').length,
124
- failed: items.filter(i => i.status === 'failed').length,
125
- items,
126
- };
100
+ return (0, mail_1.deleteMailResult)(request.dryRun, items);
127
101
  }
128
102
  /**
129
103
  * Permanently remove items from the account's Deleted Items. The folder is
@@ -142,12 +116,13 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'deleted items', 'trashFolder')}
142
116
  on error
143
117
  set timeList to {}
144
118
  end try
119
+ set hasTimes to ((count of timeList) is (count of idList))
145
120
  set olderThanDays to ${(0, run_1.asInt)(request.olderThanDays)}
146
121
  set cutoff to (current date) - (olderThanDays * days)
147
122
  set out to ""
148
123
  repeat with i from 1 to (count of idList)
149
124
  set stamp to missing value
150
- if (count of timeList) is (count of idList) then set stamp to item i of timeList
125
+ if hasTimes then set stamp to item i of timeList
151
126
  set keepIt to false
152
127
  if olderThanDays > 0 and stamp is not missing value and stamp is greater than cutoff then set keepIt to true
153
128
  set out to out & ${(0, run_1.asRow)(['(item i of idList as string)', '(keepIt as string)'])}
@@ -168,10 +143,7 @@ end tell`, 'scan');
168
143
  set failedCount to 0
169
144
  repeat with theId in ${(0, run_1.asIdList)(doomed.map(row => row.id))}
170
145
  try
171
- set theMsg to missing value
172
- try
173
- set theMsg to message id theId
174
- end try
146
+ ${(0, scripts_1.messageByIdSnippet)(' ')}
175
147
  if theMsg is missing value then error "gone"
176
148
  permanently delete theMsg
177
149
  set purgedCount to purgedCount + 1
@@ -1 +1 @@
1
- {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/mac/cleanup.ts"],"names":[],"mappings":";;AAmEA,kDAiFC;AAOD,8CA6DC;AAxND,wDAAwD;AACxD,+BAce;AACf,uCAMmB;AACnB,kCAAgD;AAIhD,MAAM,gBAAgB,GAA2C,CAAC,SAAS,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;AAElH;;;;;;;GAOG;AACH,SAAS,kBAAkB,CAAC,MAAc;IACtC,OAAO,GAAG,MAAM;EAClB,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM;EACN,MAAM,YAAY,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,mBAAmB,CAAC,OAA4B;IAClE,MAAM,EAAC,KAAK,EAAE,OAAO,EAAC,GAAG,IAAA,6BAAmB,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/D,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,MAAM,GAAG,GAAG,MAAM,IAAA,kBAAY,EAAC;EACrC,IAAA,8BAAoB,EAAC,IAAI,CAAC;EAC1B,IAAA,2BAAiB,EAAC,IAAI,EAAE,OAAO,EAAE,aAAa,CAAC;EAC/C,IAAA,2BAAiB,EAAC,IAAI,EAAE,YAAY,EAAE,YAAY,CAAC;;;;2BAI1B,IAAA,cAAQ,EAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;EAiBxC,kBAAkB,CAAC,cAAc,CAAC;;;;qCAIC,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,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ;gBACrE,MAAM,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC;aAC1B,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IACD,OAAO;QACH,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;QACzD,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,MAAM;QACzD,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,MAAM;QACvD,KAAK;KACR,CAAC;AACN,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;;;;;;;2BAO9B,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,EAAC,EAAE,EAAE,IAAA,WAAK,EAAC,KAAK,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAA,eAAS,EAAC,KAAK,EAAE,CAAC,CAAC,EAAC,CAAC;IAC5D,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,EAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAC,CAAC;IACpG,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;;;;;;;;;;;;;aAajD,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 macFolderPath,\n partitionMessageIds,\n resolveMacAccount,\n rootFolderSnippet\n} from './scripts';\nimport { PROTECTED_MAIL_REASON } from '../mail';\nimport type { DeleteEmailsRequest, PurgeRequest } from '../backend';\nimport type { DeleteMailOutcome, DeleteMailResult, PurgeDeletedItemsResult } from '../types';\n\nconst OUTCOME_STATUSES: readonly DeleteMailOutcome['status'][] = ['deleted', 'would-delete', 'refused', 'failed'];\n\n/**\n * Walk a folder's `container` chain to the top as the list `chainNames`,\n * nearest first. Requires `theFolder`.\n *\n * This is what extends the Inbox/Sent Items refusal to their SUBFOLDERS. The\n * chain's last element is the folder directly under the account, and an account\n * can't hold two top-level folders of one name, so comparing that name is exact.\n */\nfunction folderChainSnippet(indent: string): string {\n return `${indent}set chainNames to {}\n${indent}set cur to theFolder\n${indent}set guard to 0\n${indent}repeat while cur is not missing value and guard < 25\n${indent} -- The account's root folder has a name of missing value, and coercing\n${indent} -- that yields the STRING \"missing value\" — so test before coercing.\n${indent} set rawName to missing value\n${indent} try\n${indent} set rawName to name of cur\n${indent} end try\n${indent} if rawName is missing value then exit repeat\n${indent} set nm to rawName as string\n${indent} if nm is \"\" then exit repeat\n${indent} set end of chainNames to nm\n${indent} set nextUp to missing value\n${indent} try\n${indent} set nextUp to container of cur\n${indent} end try\n${indent} set cur to nextUp\n${indent} set guard to guard + 1\n${indent}end repeat`;\n}\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 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: OUTCOME_STATUSES.find(s => s === field(parts, 3)) ?? 'failed',\n reason: field(parts, 4),\n });\n }\n }\n return {\n dryRun: request.dryRun,\n deleted: items.filter(i => i.status === 'deleted').length,\n refused: items.filter(i => i.status === 'refused').length,\n failed: items.filter(i => i.status === 'failed').length,\n items,\n };\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":"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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 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"]}
@@ -12,6 +12,8 @@ export declare function deleteOutlookDrafts(request: EntryIdsRequest): Promise<D
12
12
  /**
13
13
  * Send every draft belonging to the account. The ids are snapshotted before the
14
14
  * first send: sending moves an item out of Drafts, and mutating the collection
15
- * mid-enumeration would skip every other one.
15
+ * mid-enumeration would skip every other one. A draft in the folder that is set
16
+ * to send from another account is not this account's, so it is skipped, as on
17
+ * Windows, rather than sent from that account.
16
18
  */
17
19
  export declare function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult>;
@@ -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
- ${preview}
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
@@ -65,7 +58,8 @@ ${preview}
65
58
  try
66
59
  set modAt to my isoDate(modification date of theMsg)
67
60
  end try
68
- set out to out & ${(0, run_1.asRow)(['(id of theMsg as string)', 'subj', 'my joinList(toNames, ", ")', 'my sanitizeList(toAddrs)', 'bodyText', '(attCount as string)', 'modAt'])}
61
+ ${boundElsewhereSnippet(request.account, ' ')}
62
+ if not boundElsewhere then set out to out & ${(0, run_1.asRow)(['(id of theMsg as string)', 'subj', 'my joinList(toNames, ", ")', 'my sanitizeList(toAddrs)', 'bodyText', '(attCount as string)', 'modAt'])}
69
63
  end repeat
70
64
  return out
71
65
  end tell`, 'standard');
@@ -92,6 +86,19 @@ end tell`, 'standard');
92
86
  drafts: drafts.slice(0, request.limit),
93
87
  };
94
88
  }
89
+ /**
90
+ * Set `boundElsewhere` to whether `theMsg`, a message in the account's Drafts
91
+ * folder, is set to send from some OTHER account. An unbound draft there is the
92
+ * account's own. Listing, sending all and the per-id guard all apply this one
93
+ * rule, as Windows' Test-DraftMatches is applied to all three.
94
+ */
95
+ function boundElsewhereSnippet(emailAccount, indent) {
96
+ return `${indent}set boundTo to ""
97
+ ${indent}try
98
+ ${indent} set boundTo to (email address of (account of theMsg)) as string
99
+ ${indent}end try
100
+ ${indent}set boundElsewhere to (boundTo is not "" and boundTo is not ${(0, run_1.asString)(emailAccount)})`;
101
+ }
95
102
  /**
96
103
  * The AppleScript that proves `theMsg` is one of this account's drafts before
97
104
  * `verb` happens to it: it sits in the account's Drafts folder, and it is bound
@@ -103,11 +110,8 @@ function ownDraftGuard(emailAccount, verb) {
103
110
  set inFolder to (name of (folder of theMsg)) as string
104
111
  end try
105
112
  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}"
106
- set boundTo to ""
107
- try
108
- set boundTo to (email address of (account of theMsg)) as string
109
- end try
110
- if boundTo is not "" and boundTo is not ${(0, run_1.asString)(emailAccount)} then error "draft is not bound to this account - refusing to ${verb}"`;
113
+ ${boundElsewhereSnippet(emailAccount, ' ')}
114
+ if boundElsewhere then error "draft is not bound to this account - refusing to ${verb}"`;
111
115
  }
112
116
  /** Run `verb` over named drafts of the account, each proved to be its own first. */
113
117
  async function actOnDrafts(request, verb) {
@@ -125,10 +129,7 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'drafts', 'draftsFolder')}
125
129
  repeat with theId in ${(0, run_1.asIdList)(valid)}
126
130
  set subj to ""
127
131
  try
128
- set theMsg to missing value
129
- try
130
- set theMsg to message id theId
131
- end try
132
+ ${(0, scripts_1.messageByIdSnippet)(' ')}
132
133
  if theMsg is missing value then error "no such item in this account's Drafts folder"
133
134
  try
134
135
  set subj to (subject of theMsg) as string
@@ -137,22 +138,13 @@ ${ownDraftGuard(request.account, verb)}
137
138
  ${verb} theMsg
138
139
  set doneCount to doneCount + 1
139
140
  on error errText
140
- set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'errText'])}
141
+ set out to out & ${run_1.ITEM_FAILURE_ROW}
141
142
  end try
142
143
  end repeat
143
144
  return ${(0, run_1.asRow)(['(doneCount as string)'])} & out
144
145
  end tell`, 'scan');
145
- const records = (0, run_1.splitRecords)(raw);
146
- return {
147
- done: (0, run_1.intField)((0, run_1.splitFields)(records[0] ?? ''), 0),
148
- failed: [
149
- ...invalid,
150
- ...records.slice(1).map(record => {
151
- const parts = (0, run_1.splitFields)(record);
152
- return { entryId: (0, run_1.field)(parts, 0), subject: (0, run_1.field)(parts, 1), error: (0, run_1.field)(parts, 2) };
153
- }),
154
- ],
155
- };
146
+ const { summary, failed } = (0, run_1.splitBatch)(raw);
147
+ return { done: (0, run_1.intField)(summary, 0), failed: [...invalid, ...failed] };
156
148
  }
157
149
  /** Send the named drafts. */
158
150
  async function sendDrafts(request) {
@@ -170,7 +162,9 @@ async function deleteOutlookDrafts(request) {
170
162
  /**
171
163
  * Send every draft belonging to the account. The ids are snapshotted before the
172
164
  * first send: sending moves an item out of Drafts, and mutating the collection
173
- * mid-enumeration would skip every other one.
165
+ * mid-enumeration would skip every other one. A draft in the folder that is set
166
+ * to send from another account is not this account's, so it is skipped, as on
167
+ * Windows, rather than sent from that account.
174
168
  */
175
169
  async function sendAllDrafts(request) {
176
170
  const acct = await (0, scripts_1.resolveMacAccount)(request.account);
@@ -183,29 +177,23 @@ ${(0, scripts_1.rootFolderSnippet)(acct, 'drafts', 'draftsFolder')}
183
177
  repeat with theId in wanted
184
178
  set subj to ""
185
179
  try
186
- set theMsg to missing value
187
- try
188
- set theMsg to message id theId
189
- end try
180
+ ${(0, scripts_1.messageByIdSnippet)(' ')}
190
181
  if theMsg is missing value then error "draft is no longer in the Drafts folder"
191
- try
192
- set subj to (subject of theMsg) as string
193
- end try
194
- send theMsg
195
- set sentCount to sentCount + 1
182
+ ${boundElsewhereSnippet(request.account, ' ')}
183
+ if not boundElsewhere then
184
+ try
185
+ set subj to (subject of theMsg) as string
186
+ end try
187
+ send theMsg
188
+ set sentCount to sentCount + 1
189
+ end if
196
190
  on error errText
197
- set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'errText'])}
191
+ set out to out & ${run_1.ITEM_FAILURE_ROW}
198
192
  end try
199
193
  end repeat
200
194
  return ${(0, run_1.asRow)(['(sentCount as string)'])} & out
201
195
  end tell`, 'scan');
202
- const records = (0, run_1.splitRecords)(raw);
203
- return {
204
- sent: (0, run_1.intField)((0, run_1.splitFields)(records[0] ?? ''), 0),
205
- failed: records.slice(1).map(record => {
206
- const parts = (0, run_1.splitFields)(record);
207
- return { entryId: (0, run_1.field)(parts, 0), subject: (0, run_1.field)(parts, 1), error: (0, run_1.field)(parts, 2) };
208
- }),
209
- };
196
+ const { summary, failed } = (0, run_1.splitBatch)(raw);
197
+ return { sent: (0, run_1.intField)(summary, 0), failed };
210
198
  }
211
199
  //# sourceMappingURL=drafts.js.map
@@ -1 +1 @@
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 asInt,\n asRow,\n asString,\n field,\n intField,\n runOsaScript,\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 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 * 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 set boundTo to \"\"\n try\n set boundTo to (email address of (account of theMsg)) as string\n end try\n if boundTo is not \"\" and boundTo is not ${asString(emailAccount)} 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(request: EntryIdsRequest, verb: 'send' | 'delete'): Promise<{\n done: number;\n failed: ItemFailure[]\n}> {\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 & ${asRow(['(theId as string)', 'subj', 'errText'])}\n end try\n end repeat\n return ${asRow(['(doneCount as string)'])} & out\nend tell`, 'scan');\n const records = splitRecords(raw);\n return {\n done: intField(splitFields(records[0] ?? ''), 0),\n failed: [\n ...invalid,\n ...records.slice(1).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}\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.\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 try\n set subj to (subject of theMsg) as string\n end try\n send theMsg\n set sentCount to sentCount + 1\n on error errText\n set out to out & ${asRow(['(theId as string)', 'subj', 'errText'])}\n end try\n end repeat\n return ${asRow(['(sentCount as string)'])} & out\nend tell`, 'scan');\n const records = splitRecords(raw);\n return {\n sent: intField(splitFields(records[0] ?? ''), 0),\n failed: records.slice(1).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"]}
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"]}
@@ -10,8 +10,15 @@ export declare function listInboxFolders(request: ListFoldersRequest): Promise<I
10
10
  * File emails into a folder of the account. Moving rewrites each message's id.
11
11
  *
12
12
  * Outlook for Mac's `move` reports success even where it silently does nothing,
13
- * so the destination's count is compared before and after: a run where every
14
- * move "succeeded" yet the folder never grew fails rather than reporting mail
15
- * that was never filed.
13
+ * so each move is confirmed by the destination growing, and one that didn't
14
+ * fails as its own item. A count compared once around the whole batch would let
15
+ * one real move cover for silent failures, and give no per-item results when it
16
+ * tripped. A message already in the destination counts as moved without being
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.
16
23
  */
17
24
  export declare function moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult>;
@@ -56,9 +56,16 @@ return my walkFolders(inb, 1, ${(0, run_1.asInt)(request.maxDepth)}, "")`, 'stan
56
56
  * File emails into a folder of the account. Moving rewrites each message's id.
57
57
  *
58
58
  * Outlook for Mac's `move` reports success even where it silently does nothing,
59
- * so the destination's count is compared before and after: a run where every
60
- * move "succeeded" yet the folder never grew fails rather than reporting mail
61
- * that was never filed.
59
+ * so each move is confirmed by the destination growing, and one that didn't
60
+ * fails as its own item. A count compared once around the whole batch would let
61
+ * one real move cover for silent failures, and give no per-item results when it
62
+ * tripped. A message already in the destination counts as moved without being
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.
62
69
  */
63
70
  async function moveOutlookEmails(request) {
64
71
  const folderPath = (0, scripts_1.macFolderPath)(request.account, request.folder.rootLabel, request.folder.segments);
@@ -68,46 +75,47 @@ async function moveOutlookEmails(request) {
68
75
  const acct = await (0, scripts_1.resolveMacAccount)(request.account);
69
76
  const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
70
77
  ${(0, scripts_1.accountLookupSnippet)(acct)}
78
+ ${(0, scripts_1.accountRootIdSnippet)(acct)}
71
79
  ${(0, scripts_1.mailScopeSnippet)(acct, request.folder, request.folderLabel, request.createIfMissing)}
72
- set countBefore to count of messages of scopeFolder
80
+ set scopeId to id of scopeFolder
73
81
  set movedCount to 0
74
82
  set out to ""
75
83
  repeat with theId in ${(0, run_1.asIdList)(valid)}
76
84
  set subj to ""
77
85
  try
78
- set theMsg to missing value
79
- try
80
- set theMsg to message id theId
81
- end try
86
+ ${(0, scripts_1.messageByIdSnippet)(' ')}
82
87
  if theMsg is missing value then error "no message with that id in this Outlook"
83
88
  try
84
89
  set subj to (subject of theMsg) as string
85
90
  end try
86
- move theMsg to scopeFolder
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', ' ')}
97
+ set fromId to missing value
98
+ try
99
+ set fromId to id of theFolder
100
+ end try
101
+ if fromId is not scopeId then
102
+ set countBefore to count of messages of scopeFolder
103
+ move theMsg to scopeFolder
104
+ 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"
105
+ end if
87
106
  set movedCount to movedCount + 1
88
107
  on error errText
89
- set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'errText'])}
108
+ set out to out & ${run_1.ITEM_FAILURE_ROW}
90
109
  end try
91
110
  end repeat
92
- set countAfter to count of messages of scopeFolder
93
- if movedCount > 0 and countAfter is not greater than countBefore then
94
- error "Outlook reported " & (movedCount as string) & " move(s) but '" & (name of scopeFolder) & "' did not grow - nothing was filed."
95
- end if
96
111
  return ${(0, run_1.asRow)(['(movedCount as string)', '(scopeCreated as string)'])} & out
97
112
  end tell`, 'scan');
98
- const records = (0, run_1.splitRecords)(raw);
99
- const summary = (0, run_1.splitFields)(records[0] ?? '');
113
+ const { summary, failed } = (0, run_1.splitBatch)(raw);
100
114
  return {
101
115
  folderPath,
102
116
  folderCreated: (0, run_1.boolField)(summary, 1),
103
117
  moved: (0, run_1.intField)(summary, 0),
104
- failed: [
105
- ...invalid,
106
- ...records.slice(1).map(record => {
107
- const parts = (0, run_1.splitFields)(record);
108
- return { entryId: (0, run_1.field)(parts, 0), subject: (0, run_1.field)(parts, 1), error: (0, run_1.field)(parts, 2) };
109
- }),
110
- ],
118
+ failed: [...invalid, ...failed],
111
119
  };
112
120
  }
113
121
  //# sourceMappingURL=folders.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/mac/folders.ts"],"names":[],"mappings":"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","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 runOsaScript,\n splitFields,\n splitList,\n splitRecords\n} from './run';\nimport {\n accountLookupSnippet,\n macFolderPath,\n mailScopeSnippet,\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 the destination's count is compared before and after: a run where every\n * move \"succeeded\" yet the folder never grew fails rather than reporting mail\n * that was never filed.\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${mailScopeSnippet(acct, request.folder, request.folderLabel, request.createIfMissing)}\n set countBefore to count of messages 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 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 message with that id in this Outlook\"\n try\n set subj to (subject of theMsg) as string\n end try\n move theMsg to scopeFolder\n set movedCount to movedCount + 1\n on error errText\n set out to out & ${asRow(['(theId as string)', 'subj', 'errText'])}\n end try\n end repeat\n set countAfter to count of messages of scopeFolder\n if movedCount > 0 and countAfter is not greater than countBefore then\n error \"Outlook reported \" & (movedCount as string) & \" move(s) but '\" & (name of scopeFolder) & \"' did not grow - nothing was filed.\"\n end if\n return ${asRow(['(movedCount as string)', '(scopeCreated as string)'])} & out\nend tell`, 'scan');\n const records = splitRecords(raw);\n const summary = splitFields(records[0] ?? '');\n return {\n folderPath,\n folderCreated: boolField(summary, 1),\n moved: intField(summary, 0),\n failed: [\n ...invalid,\n ...records.slice(1).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}\n"]}
1
+ {"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/mac/folders.ts"],"names":[],"mappings":"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","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"]}
@@ -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
  *