@dawnlit/outlook-bridge 1.0.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 (117) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +253 -0
  3. package/dist/OutlookService.d.ts +43 -0
  4. package/dist/OutlookService.js +137 -0
  5. package/dist/OutlookService.js.map +1 -0
  6. package/dist/errors.d.ts +112 -0
  7. package/dist/errors.js +140 -0
  8. package/dist/errors.js.map +1 -0
  9. package/dist/index.d.ts +11 -0
  10. package/dist/index.js +71 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/mac/accounts.d.ts +5 -0
  13. package/dist/mac/accounts.js +17 -0
  14. package/dist/mac/accounts.js.map +1 -0
  15. package/dist/mac/attachments.d.ts +28 -0
  16. package/dist/mac/attachments.js +120 -0
  17. package/dist/mac/attachments.js.map +1 -0
  18. package/dist/mac/bounces.d.ts +32 -0
  19. package/dist/mac/bounces.js +285 -0
  20. package/dist/mac/bounces.js.map +1 -0
  21. package/dist/mac/cleanup.d.ts +29 -0
  22. package/dist/mac/cleanup.js +219 -0
  23. package/dist/mac/cleanup.js.map +1 -0
  24. package/dist/mac/drafts.d.ts +27 -0
  25. package/dist/mac/drafts.js +214 -0
  26. package/dist/mac/drafts.js.map +1 -0
  27. package/dist/mac/folders.d.ts +28 -0
  28. package/dist/mac/folders.js +127 -0
  29. package/dist/mac/folders.js.map +1 -0
  30. package/dist/mac/index.d.ts +22 -0
  31. package/dist/mac/index.js +119 -0
  32. package/dist/mac/index.js.map +1 -0
  33. package/dist/mac/read.d.ts +46 -0
  34. package/dist/mac/read.js +406 -0
  35. package/dist/mac/read.js.map +1 -0
  36. package/dist/mac/run.d.ts +64 -0
  37. package/dist/mac/run.js +250 -0
  38. package/dist/mac/run.js.map +1 -0
  39. package/dist/mac/scripts.d.ts +137 -0
  40. package/dist/mac/scripts.js +337 -0
  41. package/dist/mac/scripts.js.map +1 -0
  42. package/dist/mac/send.d.ts +23 -0
  43. package/dist/mac/send.js +135 -0
  44. package/dist/mac/send.js.map +1 -0
  45. package/dist/mac/signatures.d.ts +4 -0
  46. package/dist/mac/signatures.js +36 -0
  47. package/dist/mac/signatures.js.map +1 -0
  48. package/dist/mac/templates.d.ts +34 -0
  49. package/dist/mac/templates.js +266 -0
  50. package/dist/mac/templates.js.map +1 -0
  51. package/dist/mail.d.ts +72 -0
  52. package/dist/mail.js +131 -0
  53. package/dist/mail.js.map +1 -0
  54. package/dist/outlookTemplateSections.d.ts +62 -0
  55. package/dist/outlookTemplateSections.js +313 -0
  56. package/dist/outlookTemplateSections.js.map +1 -0
  57. package/dist/runtime.d.ts +76 -0
  58. package/dist/runtime.js +123 -0
  59. package/dist/runtime.js.map +1 -0
  60. package/dist/shared/attachmentMatch.d.ts +32 -0
  61. package/dist/shared/attachmentMatch.js +50 -0
  62. package/dist/shared/attachmentMatch.js.map +1 -0
  63. package/dist/shared/bounceRules.d.ts +33 -0
  64. package/dist/shared/bounceRules.js +104 -0
  65. package/dist/shared/bounceRules.js.map +1 -0
  66. package/dist/shared/capabilities.d.ts +3 -0
  67. package/dist/shared/capabilities.js +9 -0
  68. package/dist/shared/capabilities.js.map +1 -0
  69. package/dist/shared/json.d.ts +11 -0
  70. package/dist/shared/json.js +45 -0
  71. package/dist/shared/json.js.map +1 -0
  72. package/dist/shared/replyBody.d.ts +49 -0
  73. package/dist/shared/replyBody.js +113 -0
  74. package/dist/shared/replyBody.js.map +1 -0
  75. package/dist/types.d.ts +365 -0
  76. package/dist/types.js +13 -0
  77. package/dist/types.js.map +1 -0
  78. package/dist/windows/accounts.d.ts +2 -0
  79. package/dist/windows/accounts.js +22 -0
  80. package/dist/windows/accounts.js.map +1 -0
  81. package/dist/windows/attachments.d.ts +28 -0
  82. package/dist/windows/attachments.js +141 -0
  83. package/dist/windows/attachments.js.map +1 -0
  84. package/dist/windows/bounces.d.ts +34 -0
  85. package/dist/windows/bounces.js +267 -0
  86. package/dist/windows/bounces.js.map +1 -0
  87. package/dist/windows/cleanup.d.ts +29 -0
  88. package/dist/windows/cleanup.js +165 -0
  89. package/dist/windows/cleanup.js.map +1 -0
  90. package/dist/windows/drafts.d.ts +29 -0
  91. package/dist/windows/drafts.js +253 -0
  92. package/dist/windows/drafts.js.map +1 -0
  93. package/dist/windows/folders.d.ts +16 -0
  94. package/dist/windows/folders.js +88 -0
  95. package/dist/windows/folders.js.map +1 -0
  96. package/dist/windows/index.d.ts +27 -0
  97. package/dist/windows/index.js +100 -0
  98. package/dist/windows/index.js.map +1 -0
  99. package/dist/windows/read.d.ts +54 -0
  100. package/dist/windows/read.js +372 -0
  101. package/dist/windows/read.js.map +1 -0
  102. package/dist/windows/run.d.ts +54 -0
  103. package/dist/windows/run.js +161 -0
  104. package/dist/windows/run.js.map +1 -0
  105. package/dist/windows/scripts.d.ts +56 -0
  106. package/dist/windows/scripts.js +163 -0
  107. package/dist/windows/scripts.js.map +1 -0
  108. package/dist/windows/send.d.ts +11 -0
  109. package/dist/windows/send.js +114 -0
  110. package/dist/windows/send.js.map +1 -0
  111. package/dist/windows/signatures.d.ts +14 -0
  112. package/dist/windows/signatures.js +69 -0
  113. package/dist/windows/signatures.js.map +1 -0
  114. package/dist/windows/templates.d.ts +25 -0
  115. package/dist/windows/templates.js +246 -0
  116. package/dist/windows/templates.js.map +1 -0
  117. package/package.json +50 -0
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanUndeliverableEmails = cleanUndeliverableEmails;
4
+ exports.collectBouncedRecipients = collectBouncedRecipients;
5
+ exports.readSentRecipientGroups = readSentRecipientGroups;
6
+ // Bounce-backs: finding them, filing them away, and mining them for the
7
+ // addresses that failed.
8
+ const run_1 = require("./run");
9
+ const scripts_1 = require("./scripts");
10
+ const json_1 = require("../shared/json");
11
+ const bounceRules_1 = require("../shared/bounceRules");
12
+ const mail_1 = require("../mail");
13
+ /**
14
+ * The classifier lists, generated from the shared rules so Windows and macOS
15
+ * cannot come to disagree about what a bounce is. Emitted once per script,
16
+ * before the item loop.
17
+ */
18
+ const BOUNCE_LISTS_PS = `
19
+ $phrases = @(${(0, run_1.psList)(bounceRules_1.BOUNCE_SUBJECT_PHRASES)})
20
+ $daemonAddr = @(${(0, run_1.psList)(bounceRules_1.BOUNCE_DAEMON_ADDRESSES)})
21
+ $daemonName = @(${(0, run_1.psList)(bounceRules_1.BOUNCE_DAEMON_NAMES)})
22
+ `;
23
+ /**
24
+ * Per-item classifier. Given an Outlook item in `$item` and the account address
25
+ * (lowercased) in `$targetLc`, sets `$reason` to why it's a bounce ('' if not)
26
+ * and fills `$failedRcpts` with the recipient addresses parsed from the bounce
27
+ * body. Also leaves `$subj`/`$sName`/`$sEmail` populated for the caller's report.
28
+ *
29
+ * The body is only read once a cheaper signal (class/sender/subject) has already
30
+ * matched — that ordering is what keeps a large-inbox scan affordable.
31
+ */
32
+ const BOUNCE_CLASSIFY_PS = `
33
+ $reason = ''
34
+ $failedRcpts = @()
35
+ $subj = ''
36
+ $sName = ''
37
+ $sEmail = ''
38
+ $cls = 0
39
+ try { $cls = [int]$item.Class } catch {}
40
+ if ($cls -eq 43 -or $cls -eq 46) {
41
+ try { if ($item.Subject) { $subj = [string]$item.Subject } } catch {}
42
+ $subjLc = $subj.ToLower()
43
+ try { if ($item.SenderName) { $sName = [string]$item.SenderName } } catch {}
44
+ $sNameLc = $sName.ToLower()
45
+ try { if ($item.SenderEmailAddress) { $sEmail = [string]$item.SenderEmailAddress } } catch {}
46
+ $sEmailLc = $sEmail.ToLower()
47
+ $msgClass = ''
48
+ try { $msgClass = [string]$item.MessageClass } catch {}
49
+ if ($msgClass -like '${bounceRules_1.NDR_MESSAGE_CLASS_PREFIX}*') { $reason = 'Non-delivery report (NDR)' }
50
+ if ($reason -eq '') { foreach ($d in $daemonAddr) { if ($sEmailLc.Contains($d)) { $reason = 'From mail-delivery system'; break } } }
51
+ if ($reason -eq '') { foreach ($d in $daemonName) { if ($sNameLc.Contains($d)) { $reason = 'From mail-delivery system'; break } } }
52
+ if ($reason -eq '') { foreach ($p in $phrases) { if ($subjLc.Contains($p)) { $reason = "Bounce subject phrase: '$p'"; break } } }
53
+ if ($reason -ne '') {
54
+ $bodyRaw = ''
55
+ try { if ($item.Body) { $bodyRaw = [string]$item.Body } } catch {}
56
+ if ($bodyRaw -ne '') {
57
+ $mm = [regex]::Matches($bodyRaw, '${bounceRules_1.BODY_ADDRESS_PATTERN}')
58
+ foreach ($m in $mm) {
59
+ $addr = $m.Value.ToLower().TrimEnd('.')
60
+ if ($addr -eq $targetLc) { continue }
61
+ if ($addr.Contains('mailer-daemon') -or $addr.Contains('postmaster') -or $addr.Contains('mail-daemon')) { continue }
62
+ if ($failedRcpts -notcontains $addr) { $failedRcpts += $addr }
63
+ if ($failedRcpts.Count -ge 5) { break }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ `;
69
+ /**
70
+ * Scan an account's inbox for bounce-back / non-delivery messages — Outlook NDRs,
71
+ * mail-daemon/postmaster rejections, and "Message blocked"-style Google/O365
72
+ * failure notices — and, unless previewing, move each to Deleted Items (recoverable).
73
+ *
74
+ * Classification is deliberately conservative so ordinary mail that merely
75
+ * mentions "delivery" is never caught: an item matches only when its
76
+ * MessageClass is an NDR report, its sender fingerprints as a mail-delivery
77
+ * daemon/postmaster, or its subject contains a specific bounce phrase.
78
+ *
79
+ * Matched item references are snapshotted before any Delete() call — deleting
80
+ * mutates the folder collection, so deleting mid-enumeration would skip items
81
+ * (same pattern as sendAllDrafts).
82
+ */
83
+ async function cleanUndeliverableEmails(emailAccount, daysBack = 30, dryRun = true) {
84
+ (0, run_1.requireWindows)();
85
+ const days = (0, mail_1.clamp)(daysBack, 1, 365);
86
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
87
+ $dryRun = ${(0, run_1.psBool)(dryRun)}
88
+ ${(0, scripts_1.namedStoreScript)(emailAccount)}
89
+ $inbox = $store.GetDefaultFolder(6)
90
+ $cutoff = (Get-Date).AddDays(-${days}).ToString('MM/dd/yyyy HH:mm')
91
+ $filtered = $inbox.Items.Restrict("[ReceivedTime] >= '$cutoff'")
92
+ ${BOUNCE_LISTS_PS}
93
+ $targetLc = $target.ToLower()
94
+ $matched = @() # snapshot of COM item refs to (optionally) delete afterward
95
+ $report = @()
96
+ $count = $filtered.Count
97
+ for ($i = 1; $i -le $count; $i++) {
98
+ $item = $null
99
+ try { $item = $filtered.Item($i) } catch { continue }
100
+ if ($item -eq $null) { continue }
101
+ ${BOUNCE_CLASSIFY_PS}
102
+ if ($reason -eq '') { continue }
103
+
104
+ $rt = ''
105
+ try { $rt = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
106
+ $report += [PSCustomObject]@{
107
+ entryId = $item.EntryID
108
+ subject = $subj.Trim()
109
+ senderName = $sName
110
+ senderEmail = $sEmail
111
+ receivedTime = $rt
112
+ matchedReason = $reason
113
+ failedRecipients = @($failedRcpts)
114
+ }
115
+ $matched += $item
116
+ }
117
+
118
+ $deleted = 0
119
+ $failed = @()
120
+ if (-not $dryRun) {
121
+ foreach ($m in $matched) {
122
+ $s = ''
123
+ try { $s = [string]$m.Subject } catch {}
124
+ try { $m.Delete(); $deleted++ }
125
+ catch { $failed += [PSCustomObject]@{ subject = $s; error = $_.Exception.Message } }
126
+ }
127
+ }
128
+
129
+ [PSCustomObject]@{
130
+ account = $target
131
+ scannedDays = ${days}
132
+ dryRun = $dryRun
133
+ matchedCount = $report.Count
134
+ deletedCount = $deleted
135
+ matched = @($report)
136
+ failed = @($failed)
137
+ } | ConvertTo-Json -Depth 4
138
+ `;
139
+ const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
140
+ return {
141
+ account: (0, json_1.str)(parsed.account) || emailAccount,
142
+ scannedDays: (0, json_1.num)(parsed.scannedDays) || days,
143
+ dryRun: parsed.dryRun !== false,
144
+ matchedCount: (0, json_1.num)(parsed.matchedCount),
145
+ deletedCount: (0, json_1.num)(parsed.deletedCount),
146
+ matched: (0, json_1.toArray)(parsed.matched).map(m => {
147
+ const e = (0, json_1.record)(m);
148
+ return {
149
+ entryId: (0, json_1.str)(e.entryId),
150
+ subject: (0, json_1.str)(e.subject),
151
+ senderName: (0, json_1.str)(e.senderName),
152
+ senderEmail: (0, json_1.str)(e.senderEmail),
153
+ receivedTime: (0, json_1.str)(e.receivedTime),
154
+ matchedReason: (0, json_1.str)(e.matchedReason),
155
+ failedRecipients: (0, json_1.toArray)(e.failedRecipients).map(json_1.str),
156
+ };
157
+ }),
158
+ failed: (0, json_1.toArray)(parsed.failed).map(f => {
159
+ const e = (0, json_1.record)(f);
160
+ return { subject: (0, json_1.str)(e.subject), error: (0, json_1.str)(e.error) };
161
+ }),
162
+ };
163
+ }
164
+ /**
165
+ * Read-only scan of an account's Inbox (and, when scanDeleted, its Deleted Items)
166
+ * for bounce messages, returning just the deduped set of failed recipient
167
+ * addresses. Uses the same conservative classifier as cleanUndeliverableEmails.
168
+ *
169
+ * Deleted Items is included so this still works after cleanUndeliverableEmails has
170
+ * already filed the bounces there — the blacklist step doesn't depend on running
171
+ * before the cleanup. This never deletes anything.
172
+ */
173
+ async function collectBouncedRecipients(emailAccount, daysBack = 30, scanDeleted = true) {
174
+ if (process.platform !== 'win32')
175
+ return [];
176
+ const days = (0, mail_1.clamp)(daysBack, 1, 365);
177
+ // 6 = Inbox, 3 = Deleted Items.
178
+ const folderIds = scanDeleted ? '@(6, 3)' : '@(6)';
179
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
180
+ ${scripts_1.DELIVERY_STORE_PS}
181
+ $cutoff = (Get-Date).AddDays(-${days}).ToString('MM/dd/yyyy HH:mm')
182
+ ${BOUNCE_LISTS_PS}
183
+ $targetLc = $target.ToLower()
184
+ $found = @{}
185
+ foreach ($fid in ${folderIds}) {
186
+ $folder = $null
187
+ try { $folder = $store.GetDefaultFolder($fid) } catch {}
188
+ if ($folder -eq $null) { continue }
189
+ $filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
190
+ $count = $filtered.Count
191
+ for ($i = 1; $i -le $count; $i++) {
192
+ $item = $null
193
+ try { $item = $filtered.Item($i) } catch { continue }
194
+ if ($item -eq $null) { continue }
195
+ ${BOUNCE_CLASSIFY_PS}
196
+ if ($reason -eq '') { continue }
197
+ foreach ($a in $failedRcpts) { $found[$a] = $true }
198
+ }
199
+ }
200
+ ConvertTo-Json @($found.Keys) -Depth 2
201
+ `;
202
+ return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 300000)).map(json_1.str).filter(Boolean);
203
+ }
204
+ /**
205
+ * Read the account's Sent Items within the window, returning each mail with the
206
+ * full SMTP address set it was sent to (To + CC + BCC), newest first. This is how
207
+ * the "was every address for this contact tried?" question gets answered: where a
208
+ * blast sends one email per organization addressed to all of its addresses, a sent
209
+ * message's recipient set IS that organization's full address set.
210
+ */
211
+ async function readSentRecipientGroups(emailAccount, daysBack = 30, limit = 3000) {
212
+ if (process.platform !== 'win32')
213
+ return [];
214
+ const days = (0, mail_1.clamp)(daysBack, 1, 365);
215
+ const cap = (0, mail_1.clamp)(limit, 1, 10000);
216
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
217
+ $sent = $account.DeliveryStore.GetDefaultFolder(5) # olFolderSentMail
218
+ $cutoff = (Get-Date).AddDays(-${days}).ToString('MM/dd/yyyy HH:mm')
219
+ $items = $sent.Items.Restrict("[SentOn] >= '$cutoff'")
220
+ $items.Sort('[SentOn]', $true)
221
+ $results = @()
222
+ $count = [Math]::Min($items.Count, ${cap})
223
+ for ($i = 1; $i -le $count; $i++) {
224
+ $m = $null
225
+ try { $m = $items.Item($i) } catch { continue }
226
+ if ($m -eq $null) { continue }
227
+ $cls = 0
228
+ try { $cls = [int]$m.Class } catch {}
229
+ if ($cls -ne 43) { continue } # olMail only
230
+ $addrs = @()
231
+ try {
232
+ foreach ($r in $m.Recipients) {
233
+ $addr = ''
234
+ try { $addr = [string]$r.Address } catch {}
235
+ # Internal recipients resolve to an Exchange DN, not SMTP — recover the
236
+ # real address so external correlation still works.
237
+ if ($addr -notlike '*@*') {
238
+ try { $addr = [string]$r.AddressEntry.GetExchangeUser().PrimarySmtpAddress } catch {}
239
+ }
240
+ if ($addr -like '*@*') { $addrs += $addr.ToLower() }
241
+ }
242
+ } catch {}
243
+ if ($addrs.Count -eq 0) { continue }
244
+ $sentOn = ''
245
+ try { $sentOn = $m.SentOn.ToString('yyyy-MM-dd HH:mm') } catch {}
246
+ $subj = ''
247
+ try { if ($m.Subject) { $subj = [string]$m.Subject } } catch {}
248
+ $results += [PSCustomObject]@{
249
+ entryId = $m.EntryID
250
+ subject = $subj.Trim()
251
+ sentOn = $sentOn
252
+ recipients = @($addrs)
253
+ }
254
+ }
255
+ ConvertTo-Json @($results) -Depth 4
256
+ `;
257
+ return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 300000)).map(it => {
258
+ const e = (0, json_1.record)(it);
259
+ return {
260
+ entryId: (0, json_1.str)(e.entryId),
261
+ subject: (0, json_1.str)(e.subject),
262
+ sentOn: (0, json_1.str)(e.sentOn),
263
+ recipients: (0, json_1.toArray)(e.recipients).map(json_1.str),
264
+ };
265
+ });
266
+ }
267
+ //# sourceMappingURL=bounces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounces.js","sourceRoot":"","sources":["../../src/windows/bounces.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { DeleteMailOptions, DeleteMailResult, PurgeDeletedItemsResult } from '../types';
2
+ /**
3
+ * Delete mail by EntryID from anywhere in the account. Delete() moves each item to
4
+ * Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
5
+ *
6
+ * ⚠️ This is the one tool here that can reach received mail, and EntryIDs are a
7
+ * genuinely unsafe key for it: GetItemFromID silently returns a DIFFERENT message
8
+ * when handed a stale or wrong id, which is common precisely when many replies share
9
+ * one subject (see saveEmailAttachmentDetailed). Three things hold the line:
10
+ *
11
+ * - **Inbox and Sent Items are refused by default, INCLUDING their subfolders** —
12
+ * a filed subfolder is still received mail. `allowProtected` lifts that, and is
13
+ * the caller explicitly taking responsibility.
14
+ * - **`dryRun` resolves and reports without deleting**, so the exact subjects and
15
+ * folders can be shown to the user before anything happens. Use it first.
16
+ * - **Every outcome echoes the subject and folderPath** of the item actually
17
+ * resolved, so a wrong id is visible after the fact rather than silent.
18
+ */
19
+ export declare function deleteOutlookEmails(emailAccount: string, entryIds: string[], options?: DeleteMailOptions): Promise<DeleteMailResult>;
20
+ /**
21
+ * Permanently remove items from the account's Deleted Items folder. This is the ONE
22
+ * genuinely irreversible operation here — nothing recovers from it — which is why it
23
+ * is folder-scoped rather than keyed on an EntryID: it can only ever destroy what the
24
+ * user already threw away.
25
+ *
26
+ * `olderThanDays` keeps recent items (0 = purge everything). Iterates backwards, as
27
+ * deleting mutates the collection and a forward walk would skip every other item.
28
+ */
29
+ export declare function purgeDeletedItems(emailAccount: string, olderThanDays?: number, dryRun?: boolean): Promise<PurgeDeletedItemsResult>;
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deleteOutlookEmails = deleteOutlookEmails;
4
+ exports.purgeDeletedItems = purgeDeletedItems;
5
+ // Deleting mail, and emptying what was already deleted.
6
+ const run_1 = require("./run");
7
+ const scripts_1 = require("./scripts");
8
+ const json_1 = require("../shared/json");
9
+ const mail_1 = require("../mail");
10
+ /** Folders whose contents are received or already-sent mail, not working state. */
11
+ const PROTECTED_FOLDER_IDS = [6, 5];
12
+ /** Walks an item's parent chain, so Inbox/Sent protection covers their subfolders too. */
13
+ const FOLDER_CHAIN_PS = `
14
+ function Get-FolderChainIds($folder) {
15
+ $ids = @()
16
+ $cur = $folder
17
+ $guard = 0
18
+ while ($cur -ne $null -and $guard -lt 25) {
19
+ $cid = $null
20
+ try { $cid = $cur.EntryID } catch { $cid = $null }
21
+ if (-not $cid) { break }
22
+ $ids += $cid
23
+ $parent = $null
24
+ try { $parent = $cur.Parent } catch { $parent = $null }
25
+ if ($parent -eq $null) { break }
26
+ $cur = $parent
27
+ $guard++
28
+ }
29
+ return $ids
30
+ }
31
+ `;
32
+ /**
33
+ * Delete mail by EntryID from anywhere in the account. Delete() moves each item to
34
+ * Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
35
+ *
36
+ * ⚠️ This is the one tool here that can reach received mail, and EntryIDs are a
37
+ * genuinely unsafe key for it: GetItemFromID silently returns a DIFFERENT message
38
+ * when handed a stale or wrong id, which is common precisely when many replies share
39
+ * one subject (see saveEmailAttachmentDetailed). Three things hold the line:
40
+ *
41
+ * - **Inbox and Sent Items are refused by default, INCLUDING their subfolders** —
42
+ * a filed subfolder is still received mail. `allowProtected` lifts that, and is
43
+ * the caller explicitly taking responsibility.
44
+ * - **`dryRun` resolves and reports without deleting**, so the exact subjects and
45
+ * folders can be shown to the user before anything happens. Use it first.
46
+ * - **Every outcome echoes the subject and folderPath** of the item actually
47
+ * resolved, so a wrong id is visible after the fact rather than silent.
48
+ */
49
+ async function deleteOutlookEmails(emailAccount, entryIds, options = {}) {
50
+ (0, run_1.requireWindows)();
51
+ const { allowProtected = false, dryRun = false } = options;
52
+ if (entryIds.length === 0) {
53
+ return { dryRun, deleted: 0, refused: 0, failed: 0, items: [] };
54
+ }
55
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
56
+ ${scripts_1.DELIVERY_STORE_PS}
57
+ ${FOLDER_CHAIN_PS}
58
+ $protectedIds = @{}
59
+ foreach ($fid in @(${PROTECTED_FOLDER_IDS.join(',')})) {
60
+ try { $protectedIds[$store.GetDefaultFolder($fid).EntryID] = $true } catch {}
61
+ }
62
+
63
+ $items = @()
64
+ $deleted = 0
65
+ $refused = 0
66
+ $failed = 0
67
+ foreach ($id in @(${(0, run_1.psList)(entryIds)})) {
68
+ $subject = ''
69
+ $folderPath = ''
70
+ try {
71
+ $it = $null
72
+ try { $it = $ns.GetItemFromID($id, $store.StoreID) } catch { $it = $null }
73
+ if ($it -eq $null) { try { $it = $ns.GetItemFromID($id) } catch { $it = $null } }
74
+ if ($it -eq $null) { throw "no item with that EntryID in this account" }
75
+ try { $subject = $it.Subject } catch {}
76
+ $parent = $null
77
+ try { $parent = $it.Parent } catch {}
78
+ if ($parent -eq $null) { throw "item has no parent folder - refusing to delete" }
79
+ try { $folderPath = $parent.FolderPath } catch {}
80
+
81
+ $chain = Get-FolderChainIds $parent
82
+ $isProtected = $false
83
+ foreach ($cid in $chain) { if ($protectedIds.ContainsKey($cid)) { $isProtected = $true; break } }
84
+
85
+ if ($isProtected -and -not ${(0, run_1.psBool)(allowProtected)}) {
86
+ $refused++
87
+ $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'refused'; reason = '${(0, run_1.psEscape)(mail_1.PROTECTED_MAIL_REASON)}' }
88
+ } elseif (${(0, run_1.psBool)(dryRun)}) {
89
+ $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'would-delete'; reason = '' }
90
+ } else {
91
+ $it.Delete()
92
+ $deleted++
93
+ $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'deleted'; reason = '' }
94
+ }
95
+ } catch {
96
+ $failed++
97
+ $items += [pscustomobject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'failed'; reason = $_.Exception.Message }
98
+ }
99
+ }
100
+ ConvertTo-Json @{ dryRun = ${(0, run_1.psBool)(dryRun)}; deleted = $deleted; refused = $refused; failed = $failed; items = @($items) } -Depth 4
101
+ `;
102
+ const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 300000));
103
+ return {
104
+ dryRun,
105
+ deleted: (0, json_1.num)(parsed.deleted),
106
+ refused: (0, json_1.num)(parsed.refused),
107
+ failed: (0, json_1.num)(parsed.failed),
108
+ items: (0, json_1.toArray)(parsed.items).map(i => {
109
+ const e = (0, json_1.record)(i);
110
+ return {
111
+ entryId: (0, json_1.str)(e.entryId),
112
+ subject: (0, json_1.str)(e.subject),
113
+ folderPath: (0, json_1.str)(e.folderPath),
114
+ status: ((0, json_1.str)(e.status) || 'failed'),
115
+ reason: (0, json_1.str)(e.reason),
116
+ };
117
+ }),
118
+ };
119
+ }
120
+ /**
121
+ * Permanently remove items from the account's Deleted Items folder. This is the ONE
122
+ * genuinely irreversible operation here — nothing recovers from it — which is why it
123
+ * is folder-scoped rather than keyed on an EntryID: it can only ever destroy what the
124
+ * user already threw away.
125
+ *
126
+ * `olderThanDays` keeps recent items (0 = purge everything). Iterates backwards, as
127
+ * deleting mutates the collection and a forward walk would skip every other item.
128
+ */
129
+ async function purgeDeletedItems(emailAccount, olderThanDays = 0, dryRun = false) {
130
+ (0, run_1.requireWindows)();
131
+ const script = `${(0, scripts_1.accountScript)(emailAccount)}
132
+ $folder = $account.DeliveryStore.GetDefaultFolder(3)
133
+ $cutoff = (Get-Date).AddDays(-${olderThanDays})
134
+ $matched = 0
135
+ $purged = 0
136
+ $kept = 0
137
+ $failed = 0
138
+ # Bound once: every $folder.Items is a COM property get that materialises a
139
+ # fresh collection object, and this loop runs once per message in Deleted Items.
140
+ $folderItems = $folder.Items
141
+ for ($i = $folderItems.Count; $i -ge 1; $i--) {
142
+ $it = $null
143
+ try { $it = $folderItems.Item($i) } catch { continue }
144
+ $stamp = $null
145
+ foreach ($p in @('ReceivedTime','LastModificationTime','CreationTime')) {
146
+ try { $stamp = $it.$p; if ($stamp -ne $null) { break } } catch {}
147
+ }
148
+ if (${olderThanDays} -gt 0 -and $stamp -ne $null -and $stamp -gt $cutoff) { $kept++; continue }
149
+ $matched++
150
+ if (${(0, run_1.psBool)(dryRun)}) { continue }
151
+ try { $it.Delete(); $purged++ } catch { $failed++ }
152
+ }
153
+ ConvertTo-Json @{ folderPath = $folder.FolderPath; matched = $matched; purged = $purged; kept = $kept; failed = $failed } -Depth 3
154
+ `;
155
+ const parsed = (0, json_1.parseObject)(await (0, run_1.runPowerShell)(script, 600000));
156
+ return {
157
+ folderPath: (0, json_1.str)(parsed.folderPath),
158
+ dryRun,
159
+ matched: (0, json_1.num)(parsed.matched),
160
+ purged: (0, json_1.num)(parsed.purged),
161
+ kept: (0, json_1.num)(parsed.kept),
162
+ failed: (0, json_1.num)(parsed.failed),
163
+ };
164
+ }
165
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/windows/cleanup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { DeleteDraftsResult, ListDraftsResult, SendAllDraftsResult } from '../types';
2
+ /**
3
+ * Send the mail drafts that belong to `emailAccount`, wherever Outlook filed them.
4
+ * Which drafts those are is DRAFTS_SCAN_PS's rule, not this function's.
5
+ *
6
+ * Item references are snapshotted before any Send() call: sending moves an item out
7
+ * of Drafts, and mutating the collection mid-enumeration would skip every other one.
8
+ */
9
+ export declare function sendAllDrafts(emailAccount: string): Promise<SendAllDraftsResult>;
10
+ /**
11
+ * List the mail drafts belonging to `emailAccount`, newest first. Which drafts
12
+ * those are is DRAFTS_SCAN_PS's rule.
13
+ *
14
+ * Deliberately returns no StoreID: deleteOutlookDrafts re-resolves each EntryID
15
+ * against the same folders, so repeating a ~700-char store id on every row would be
16
+ * pure payload. Bodies are previewed, never returned whole — a templated reply body
17
+ * runs to tens of thousands of characters and a folder's worth would blow the cap.
18
+ */
19
+ export declare function listOutlookDrafts(emailAccount: string, limit?: number, previewChars?: number): Promise<ListDraftsResult>;
20
+ /**
21
+ * Delete mail drafts by EntryID. Outlook's Delete() moves the item to Deleted Items
22
+ * rather than destroying it, so a mistaken call stays recoverable from there.
23
+ *
24
+ * Every id must resolve to a mail item sitting in one of THIS account's Drafts
25
+ * folders before anything is deleted; an id pointing at ordinary mail, or at another
26
+ * account's draft, is refused and reported. Without that gate this would be a
27
+ * general-purpose "delete any email by id" tool, which is not what it is for.
28
+ */
29
+ export declare function deleteOutlookDrafts(emailAccount: string, entryIds: string[]): Promise<DeleteDraftsResult>;