@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.
- package/LICENSE +190 -0
- package/README.md +253 -0
- package/dist/OutlookService.d.ts +43 -0
- package/dist/OutlookService.js +137 -0
- package/dist/OutlookService.js.map +1 -0
- package/dist/errors.d.ts +112 -0
- package/dist/errors.js +140 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/mac/accounts.d.ts +5 -0
- package/dist/mac/accounts.js +17 -0
- package/dist/mac/accounts.js.map +1 -0
- package/dist/mac/attachments.d.ts +28 -0
- package/dist/mac/attachments.js +120 -0
- package/dist/mac/attachments.js.map +1 -0
- package/dist/mac/bounces.d.ts +32 -0
- package/dist/mac/bounces.js +285 -0
- package/dist/mac/bounces.js.map +1 -0
- package/dist/mac/cleanup.d.ts +29 -0
- package/dist/mac/cleanup.js +219 -0
- package/dist/mac/cleanup.js.map +1 -0
- package/dist/mac/drafts.d.ts +27 -0
- package/dist/mac/drafts.js +214 -0
- package/dist/mac/drafts.js.map +1 -0
- package/dist/mac/folders.d.ts +28 -0
- package/dist/mac/folders.js +127 -0
- package/dist/mac/folders.js.map +1 -0
- package/dist/mac/index.d.ts +22 -0
- package/dist/mac/index.js +119 -0
- package/dist/mac/index.js.map +1 -0
- package/dist/mac/read.d.ts +46 -0
- package/dist/mac/read.js +406 -0
- package/dist/mac/read.js.map +1 -0
- package/dist/mac/run.d.ts +64 -0
- package/dist/mac/run.js +250 -0
- package/dist/mac/run.js.map +1 -0
- package/dist/mac/scripts.d.ts +137 -0
- package/dist/mac/scripts.js +337 -0
- package/dist/mac/scripts.js.map +1 -0
- package/dist/mac/send.d.ts +23 -0
- package/dist/mac/send.js +135 -0
- package/dist/mac/send.js.map +1 -0
- package/dist/mac/signatures.d.ts +4 -0
- package/dist/mac/signatures.js +36 -0
- package/dist/mac/signatures.js.map +1 -0
- package/dist/mac/templates.d.ts +34 -0
- package/dist/mac/templates.js +266 -0
- package/dist/mac/templates.js.map +1 -0
- package/dist/mail.d.ts +72 -0
- package/dist/mail.js +131 -0
- package/dist/mail.js.map +1 -0
- package/dist/outlookTemplateSections.d.ts +62 -0
- package/dist/outlookTemplateSections.js +313 -0
- package/dist/outlookTemplateSections.js.map +1 -0
- package/dist/runtime.d.ts +76 -0
- package/dist/runtime.js +123 -0
- package/dist/runtime.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +32 -0
- package/dist/shared/attachmentMatch.js +50 -0
- package/dist/shared/attachmentMatch.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +33 -0
- package/dist/shared/bounceRules.js +104 -0
- package/dist/shared/bounceRules.js.map +1 -0
- package/dist/shared/capabilities.d.ts +3 -0
- package/dist/shared/capabilities.js +9 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/json.d.ts +11 -0
- package/dist/shared/json.js +45 -0
- package/dist/shared/json.js.map +1 -0
- package/dist/shared/replyBody.d.ts +49 -0
- package/dist/shared/replyBody.js +113 -0
- package/dist/shared/replyBody.js.map +1 -0
- package/dist/types.d.ts +365 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/windows/accounts.d.ts +2 -0
- package/dist/windows/accounts.js +22 -0
- package/dist/windows/accounts.js.map +1 -0
- package/dist/windows/attachments.d.ts +28 -0
- package/dist/windows/attachments.js +141 -0
- package/dist/windows/attachments.js.map +1 -0
- package/dist/windows/bounces.d.ts +34 -0
- package/dist/windows/bounces.js +267 -0
- package/dist/windows/bounces.js.map +1 -0
- package/dist/windows/cleanup.d.ts +29 -0
- package/dist/windows/cleanup.js +165 -0
- package/dist/windows/cleanup.js.map +1 -0
- package/dist/windows/drafts.d.ts +29 -0
- package/dist/windows/drafts.js +253 -0
- package/dist/windows/drafts.js.map +1 -0
- package/dist/windows/folders.d.ts +16 -0
- package/dist/windows/folders.js +88 -0
- package/dist/windows/folders.js.map +1 -0
- package/dist/windows/index.d.ts +27 -0
- package/dist/windows/index.js +100 -0
- package/dist/windows/index.js.map +1 -0
- package/dist/windows/read.d.ts +54 -0
- package/dist/windows/read.js +372 -0
- package/dist/windows/read.js.map +1 -0
- package/dist/windows/run.d.ts +54 -0
- package/dist/windows/run.js +161 -0
- package/dist/windows/run.js.map +1 -0
- package/dist/windows/scripts.d.ts +56 -0
- package/dist/windows/scripts.js +163 -0
- package/dist/windows/scripts.js.map +1 -0
- package/dist/windows/send.d.ts +11 -0
- package/dist/windows/send.js +114 -0
- package/dist/windows/send.js.map +1 -0
- package/dist/windows/signatures.d.ts +14 -0
- package/dist/windows/signatures.js +69 -0
- package/dist/windows/signatures.js.map +1 -0
- package/dist/windows/templates.d.ts +25 -0
- package/dist/windows/templates.js +246 -0
- package/dist/windows/templates.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,285 @@
|
|
|
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
|
+
//
|
|
9
|
+
// Windows classifies inside the generated PowerShell, where Restrict has already
|
|
10
|
+
// narrowed the set server-side. AppleScript has no Restrict, so the folder is
|
|
11
|
+
// indexed with bulk property reads and the SAME rules — imported from
|
|
12
|
+
// shared/bounceRules — are applied here in TypeScript. Only messages that match
|
|
13
|
+
// have their bodies read, which is the property that keeps either version
|
|
14
|
+
// affordable on a real inbox.
|
|
15
|
+
const run_1 = require("./run");
|
|
16
|
+
const scripts_1 = require("./scripts");
|
|
17
|
+
const bounceRules_1 = require("../shared/bounceRules");
|
|
18
|
+
const mail_1 = require("../mail");
|
|
19
|
+
/**
|
|
20
|
+
* Index one well-known folder's messages within the window: everything the
|
|
21
|
+
* bounce classifier needs, and nothing that costs a per-message event.
|
|
22
|
+
*/
|
|
23
|
+
async function indexFolder(emailAccount, folderTerm, days) {
|
|
24
|
+
// Sender is a record, and a bulk `sender of every message` read hands back a
|
|
25
|
+
// list of them that AppleScript then reads locally — so the whole index costs
|
|
26
|
+
// four Apple events rather than one per message.
|
|
27
|
+
const script = `tell application "Microsoft Outlook"
|
|
28
|
+
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
29
|
+
set scanFolder to ${folderTerm} of targetAcct
|
|
30
|
+
set idList to id of every message of scanFolder
|
|
31
|
+
set subjList to subject of every message of scanFolder
|
|
32
|
+
try
|
|
33
|
+
set timeList to time received of every message of scanFolder
|
|
34
|
+
on error
|
|
35
|
+
set timeList to {}
|
|
36
|
+
end try
|
|
37
|
+
try
|
|
38
|
+
set sndList to sender of every message of scanFolder
|
|
39
|
+
on error
|
|
40
|
+
set sndList to {}
|
|
41
|
+
end try
|
|
42
|
+
set cutoff to (current date) - (${days} * days)
|
|
43
|
+
set out to ""
|
|
44
|
+
repeat with i from 1 to (count of idList)
|
|
45
|
+
set stamp to missing value
|
|
46
|
+
if (count of timeList) is (count of idList) then set stamp to item i of timeList
|
|
47
|
+
if stamp is missing value or stamp is greater than or equal to cutoff then
|
|
48
|
+
set sndName to ""
|
|
49
|
+
set sndAddr to ""
|
|
50
|
+
if (count of sndList) is (count of idList) then
|
|
51
|
+
set snd to item i of sndList
|
|
52
|
+
try
|
|
53
|
+
set sndAddr to (address of snd) as string
|
|
54
|
+
end try
|
|
55
|
+
try
|
|
56
|
+
set sndName to (name of snd) as string
|
|
57
|
+
end try
|
|
58
|
+
end if
|
|
59
|
+
set out to out & ${(0, run_1.asRow)([
|
|
60
|
+
'(item i of idList as string)',
|
|
61
|
+
'(item i of subjList)',
|
|
62
|
+
'sndName',
|
|
63
|
+
'sndAddr',
|
|
64
|
+
'my isoDate(stamp)',
|
|
65
|
+
])}
|
|
66
|
+
end if
|
|
67
|
+
end repeat
|
|
68
|
+
return out
|
|
69
|
+
end tell`;
|
|
70
|
+
return (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(script, 300000)).map(record => {
|
|
71
|
+
const parts = (0, run_1.splitFields)(record);
|
|
72
|
+
return {
|
|
73
|
+
id: (0, run_1.field)(parts, 0),
|
|
74
|
+
subject: (0, run_1.field)(parts, 1).trim(),
|
|
75
|
+
senderName: (0, run_1.field)(parts, 2),
|
|
76
|
+
senderEmail: (0, run_1.field)(parts, 3),
|
|
77
|
+
receivedTime: (0, run_1.field)(parts, 4),
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/** Read the plain-text bodies of the given messages, keyed by id. */
|
|
82
|
+
async function readBodies(ids) {
|
|
83
|
+
if (ids.length === 0)
|
|
84
|
+
return new Map();
|
|
85
|
+
const script = `tell application "Microsoft Outlook"
|
|
86
|
+
set out to ""
|
|
87
|
+
repeat with theId in {${ids.join(', ')}}
|
|
88
|
+
set theMsg to missing value
|
|
89
|
+
try
|
|
90
|
+
set theMsg to message id theId
|
|
91
|
+
end try
|
|
92
|
+
if theMsg is not missing value then
|
|
93
|
+
set bodyText to ""
|
|
94
|
+
try
|
|
95
|
+
set bodyText to (plain text content of theMsg) as string
|
|
96
|
+
end try
|
|
97
|
+
set out to out & ${(0, run_1.asRow)(['(theId as string)', 'bodyText'])}
|
|
98
|
+
end if
|
|
99
|
+
end repeat
|
|
100
|
+
return out
|
|
101
|
+
end tell`;
|
|
102
|
+
const bodies = new Map();
|
|
103
|
+
for (const record of (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(script, 300000))) {
|
|
104
|
+
const parts = (0, run_1.splitFields)(record);
|
|
105
|
+
bodies.set((0, run_1.field)(parts, 0), (0, run_1.field)(parts, 1));
|
|
106
|
+
}
|
|
107
|
+
return bodies;
|
|
108
|
+
}
|
|
109
|
+
/** Classify an index, then fill each match's failed recipients from its body. */
|
|
110
|
+
async function collectBounces(indexed, accountAddress) {
|
|
111
|
+
const matched = indexed
|
|
112
|
+
.map(message => ({ message, reason: (0, bounceRules_1.bounceReason)(message) }))
|
|
113
|
+
.filter(entry => entry.reason !== '');
|
|
114
|
+
if (matched.length === 0)
|
|
115
|
+
return [];
|
|
116
|
+
const bodies = await readBodies(matched.map(entry => entry.message.id));
|
|
117
|
+
return matched.map(({ message, reason }) => ({
|
|
118
|
+
entryId: message.id,
|
|
119
|
+
subject: message.subject,
|
|
120
|
+
senderName: message.senderName,
|
|
121
|
+
senderEmail: message.senderEmail,
|
|
122
|
+
receivedTime: message.receivedTime,
|
|
123
|
+
matchedReason: reason,
|
|
124
|
+
failedRecipients: (0, bounceRules_1.failedRecipients)(bodies.get(message.id) || '', accountAddress),
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Scan an account's inbox for bounce-back / non-delivery messages — mail-daemon
|
|
129
|
+
* and postmaster rejections, and "Message blocked"-style failure notices — and,
|
|
130
|
+
* unless previewing, move each to Deleted Items (recoverable).
|
|
131
|
+
*
|
|
132
|
+
* Classification is deliberately conservative so ordinary mail that merely
|
|
133
|
+
* mentions "delivery" is never caught: an item matches only when its sender
|
|
134
|
+
* fingerprints as a mail-delivery daemon/postmaster or its subject contains a
|
|
135
|
+
* specific bounce phrase. macOS has no MessageClass, so the NDR-report signal
|
|
136
|
+
* Windows can also use is unavailable here — an Exchange NDR still matches on
|
|
137
|
+
* its "Undeliverable:" subject, which is what it carries.
|
|
138
|
+
*/
|
|
139
|
+
async function cleanUndeliverableEmails(emailAccount, daysBack = 30, dryRun = true) {
|
|
140
|
+
const days = (0, mail_1.clamp)(daysBack, 1, 365);
|
|
141
|
+
const indexed = await indexFolder(emailAccount, 'inbox', days);
|
|
142
|
+
const matched = await collectBounces(indexed, emailAccount);
|
|
143
|
+
let deletedCount = 0;
|
|
144
|
+
const failed = [];
|
|
145
|
+
if (!dryRun && matched.length > 0) {
|
|
146
|
+
const script = `tell application "Microsoft Outlook"
|
|
147
|
+
set deletedCount to 0
|
|
148
|
+
set out to ""
|
|
149
|
+
repeat with theId in {${matched.map(m => m.entryId).join(', ')}}
|
|
150
|
+
set subj to ""
|
|
151
|
+
try
|
|
152
|
+
set theMsg to missing value
|
|
153
|
+
try
|
|
154
|
+
set theMsg to message id theId
|
|
155
|
+
end try
|
|
156
|
+
if theMsg is missing value then error "message is no longer in the mailbox"
|
|
157
|
+
try
|
|
158
|
+
set subj to (subject of theMsg) as string
|
|
159
|
+
end try
|
|
160
|
+
delete theMsg
|
|
161
|
+
set deletedCount to deletedCount + 1
|
|
162
|
+
on error errText
|
|
163
|
+
set out to out & ${(0, run_1.asRow)(['subj', 'errText'])}
|
|
164
|
+
end try
|
|
165
|
+
end repeat
|
|
166
|
+
return ${(0, run_1.asRow)(['(deletedCount as string)'])} & out
|
|
167
|
+
end tell`;
|
|
168
|
+
const records = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(script, 300000));
|
|
169
|
+
deletedCount = (0, run_1.intField)((0, run_1.splitFields)(records[0] || ''), 0);
|
|
170
|
+
for (const record of records.slice(1)) {
|
|
171
|
+
const parts = (0, run_1.splitFields)(record);
|
|
172
|
+
failed.push({ subject: (0, run_1.field)(parts, 0), error: (0, run_1.field)(parts, 1) });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
account: emailAccount,
|
|
177
|
+
scannedDays: days,
|
|
178
|
+
dryRun,
|
|
179
|
+
matchedCount: matched.length,
|
|
180
|
+
deletedCount,
|
|
181
|
+
matched,
|
|
182
|
+
failed,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Read-only scan of an account's Inbox (and, when scanDeleted, its Deleted Items)
|
|
187
|
+
* for bounce messages, returning just the deduped set of failed recipient
|
|
188
|
+
* addresses. Uses the same conservative classifier as cleanUndeliverableEmails.
|
|
189
|
+
*
|
|
190
|
+
* Deleted Items is included so this still works after cleanUndeliverableEmails has
|
|
191
|
+
* already filed the bounces there — the blacklist step doesn't depend on running
|
|
192
|
+
* before the cleanup. This never deletes anything.
|
|
193
|
+
*/
|
|
194
|
+
async function collectBouncedRecipients(emailAccount, daysBack = 30, scanDeleted = true) {
|
|
195
|
+
const days = (0, mail_1.clamp)(daysBack, 1, 365);
|
|
196
|
+
const terms = scanDeleted ? ['inbox', 'deleted items'] : ['inbox'];
|
|
197
|
+
const found = new Set();
|
|
198
|
+
for (const term of terms) {
|
|
199
|
+
const bounces = await collectBounces(await indexFolder(emailAccount, term, days), emailAccount);
|
|
200
|
+
for (const bounce of bounces) {
|
|
201
|
+
for (const address of bounce.failedRecipients)
|
|
202
|
+
found.add(address);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return [...found];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Read the account's Sent Items within the window, returning each mail with the
|
|
209
|
+
* full SMTP address set it was sent to (To + CC + BCC), newest first. This is how
|
|
210
|
+
* the "was every address for this contact tried?" question gets answered: where a
|
|
211
|
+
* blast sends one email per organization addressed to all of its addresses, a sent
|
|
212
|
+
* message's recipient set IS that organization's full address set.
|
|
213
|
+
*/
|
|
214
|
+
async function readSentRecipientGroups(emailAccount, daysBack = 30, limit = 3000) {
|
|
215
|
+
const days = (0, mail_1.clamp)(daysBack, 1, 365);
|
|
216
|
+
const cap = (0, mail_1.clamp)(limit, 1, 10000);
|
|
217
|
+
// Pass 1 — index Sent Items on `time sent`, which is the stamp outgoing mail
|
|
218
|
+
// actually carries; indexing it on `time received` yields an empty set that
|
|
219
|
+
// looks exactly like an empty folder.
|
|
220
|
+
const indexScript = `tell application "Microsoft Outlook"
|
|
221
|
+
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
222
|
+
set sentFolder to sent items of targetAcct
|
|
223
|
+
set idList to id of every message of sentFolder
|
|
224
|
+
try
|
|
225
|
+
set timeList to time sent of every message of sentFolder
|
|
226
|
+
on error
|
|
227
|
+
set timeList to {}
|
|
228
|
+
end try
|
|
229
|
+
set subjList to subject of every message of sentFolder
|
|
230
|
+
set cutoff to (current date) - (${days} * days)
|
|
231
|
+
set out to ""
|
|
232
|
+
repeat with i from 1 to (count of idList)
|
|
233
|
+
set stamp to missing value
|
|
234
|
+
if (count of timeList) is (count of idList) then set stamp to item i of timeList
|
|
235
|
+
if stamp is missing value or stamp is greater than or equal to cutoff then
|
|
236
|
+
set out to out & ${(0, run_1.asRow)([
|
|
237
|
+
'(item i of idList as string)',
|
|
238
|
+
'(item i of subjList)',
|
|
239
|
+
'my isoDate(stamp)',
|
|
240
|
+
])}
|
|
241
|
+
end if
|
|
242
|
+
end repeat
|
|
243
|
+
return out
|
|
244
|
+
end tell`;
|
|
245
|
+
const indexed = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(indexScript, 300000)).map(record => {
|
|
246
|
+
const parts = (0, run_1.splitFields)(record);
|
|
247
|
+
return { id: (0, run_1.field)(parts, 0), subject: (0, run_1.field)(parts, 1).trim(), sentOn: (0, run_1.field)(parts, 2) };
|
|
248
|
+
});
|
|
249
|
+
indexed.sort((a, b) => b.sentOn.localeCompare(a.sentOn));
|
|
250
|
+
const chosen = indexed.slice(0, cap);
|
|
251
|
+
if (chosen.length === 0)
|
|
252
|
+
return [];
|
|
253
|
+
// Pass 2 — recipients, which can only be read per message.
|
|
254
|
+
const detailScript = `tell application "Microsoft Outlook"
|
|
255
|
+
set out to ""
|
|
256
|
+
repeat with theId in {${chosen.map(row => row.id).join(', ')}}
|
|
257
|
+
set theMsg to missing value
|
|
258
|
+
try
|
|
259
|
+
set theMsg to message id theId
|
|
260
|
+
end try
|
|
261
|
+
if theMsg is not missing value then
|
|
262
|
+
${(0, scripts_1.allRecipientsSnippet)('theMsg', ' ')}
|
|
263
|
+
set out to out & ${(0, run_1.asRow)([
|
|
264
|
+
'(theId as string)',
|
|
265
|
+
'my sanitizeList(addrList)',
|
|
266
|
+
])}
|
|
267
|
+
end if
|
|
268
|
+
end repeat
|
|
269
|
+
return out
|
|
270
|
+
end tell`;
|
|
271
|
+
const recipientsById = new Map();
|
|
272
|
+
for (const record of (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(detailScript, 300000))) {
|
|
273
|
+
const parts = (0, run_1.splitFields)(record);
|
|
274
|
+
recipientsById.set((0, run_1.field)(parts, 0), (0, run_1.splitList)((0, run_1.field)(parts, 1)).map(address => address.toLowerCase()));
|
|
275
|
+
}
|
|
276
|
+
const groups = [];
|
|
277
|
+
for (const row of chosen) {
|
|
278
|
+
const recipients = recipientsById.get(row.id) || [];
|
|
279
|
+
if (recipients.length === 0)
|
|
280
|
+
continue;
|
|
281
|
+
groups.push({ entryId: row.id, subject: row.subject, sentOn: row.sentOn, recipients });
|
|
282
|
+
}
|
|
283
|
+
return groups;
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=bounces.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounces.js","sourceRoot":"","sources":["../../src/mac/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 message id from anywhere in the account. `delete` files each
|
|
4
|
+
* item in Deleted Items, so this is recoverable — purgeDeletedItems is what
|
|
5
|
+
* destroys.
|
|
6
|
+
*
|
|
7
|
+
* ⚠️ This is the one tool here that can reach received mail. Three things hold
|
|
8
|
+
* the line, exactly as on Windows:
|
|
9
|
+
*
|
|
10
|
+
* - **Inbox and Sent Items are refused by default, INCLUDING their subfolders** —
|
|
11
|
+
* a filed subfolder is still received mail. `allowProtected` lifts that, and is
|
|
12
|
+
* the caller explicitly taking responsibility.
|
|
13
|
+
* - **`dryRun` resolves and reports without deleting**, so the exact subjects and
|
|
14
|
+
* folders can be shown to the user before anything happens. Use it first.
|
|
15
|
+
* - **Every outcome echoes the subject and folderPath** of the item actually
|
|
16
|
+
* resolved, so a wrong id is visible after the fact rather than silent.
|
|
17
|
+
*/
|
|
18
|
+
export declare function deleteOutlookEmails(emailAccount: string, entryIds: string[], options?: DeleteMailOptions): Promise<DeleteMailResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Permanently remove items from the account's Deleted Items folder. This is the
|
|
21
|
+
* ONE genuinely irreversible operation here — nothing recovers from it — which
|
|
22
|
+
* is why it is folder-scoped rather than keyed on a message id: it can only ever
|
|
23
|
+
* destroy what the user already threw away.
|
|
24
|
+
*
|
|
25
|
+
* `olderThanDays` keeps recent items (0 = purge everything). The folder is
|
|
26
|
+
* indexed first and destroyed by id afterwards, so deleting can't shift the
|
|
27
|
+
* collection out from under the walk.
|
|
28
|
+
*/
|
|
29
|
+
export declare function purgeDeletedItems(emailAccount: string, olderThanDays?: number, dryRun?: boolean): Promise<PurgeDeletedItemsResult>;
|
|
@@ -0,0 +1,219 @@
|
|
|
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 mail_1 = require("../mail");
|
|
9
|
+
/**
|
|
10
|
+
* Walk a folder's `container` chain to the top, as the list `chainNames`
|
|
11
|
+
* (nearest first). Requires `theFolder`.
|
|
12
|
+
*
|
|
13
|
+
* This is what makes the Inbox/Sent Items refusal cover their SUBFOLDERS too: a
|
|
14
|
+
* filed subfolder is still received mail. The chain's last element is the folder
|
|
15
|
+
* directly under the account, and an account cannot hold two top-level folders
|
|
16
|
+
* of one name, so comparing that name is exact rather than a guess.
|
|
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 own root folder has a 'name' of missing value, and coercing
|
|
24
|
+
${indent} -- that yields the STRING "missing value" — which sailed past an empty
|
|
25
|
+
${indent} -- check and became the chain's root, so nothing was ever recognised as
|
|
26
|
+
${indent} -- living under the Inbox. Test before coercing.
|
|
27
|
+
${indent} set rawName to missing value
|
|
28
|
+
${indent} try
|
|
29
|
+
${indent} set rawName to name of cur
|
|
30
|
+
${indent} end try
|
|
31
|
+
${indent} if rawName is missing value then exit repeat
|
|
32
|
+
${indent} set nm to rawName as string
|
|
33
|
+
${indent} if nm is "" then exit repeat
|
|
34
|
+
${indent} set end of chainNames to nm
|
|
35
|
+
${indent} set nextUp to missing value
|
|
36
|
+
${indent} try
|
|
37
|
+
${indent} set nextUp to container of cur
|
|
38
|
+
${indent} end try
|
|
39
|
+
${indent} set cur to nextUp
|
|
40
|
+
${indent} set guard to guard + 1
|
|
41
|
+
${indent}end repeat`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Delete mail by message id from anywhere in the account. `delete` files each
|
|
45
|
+
* item in Deleted Items, so this is recoverable — purgeDeletedItems is what
|
|
46
|
+
* destroys.
|
|
47
|
+
*
|
|
48
|
+
* ⚠️ This is the one tool here that can reach received mail. Three things hold
|
|
49
|
+
* the line, exactly as on Windows:
|
|
50
|
+
*
|
|
51
|
+
* - **Inbox and Sent Items are refused by default, INCLUDING their subfolders** —
|
|
52
|
+
* a filed subfolder is still received mail. `allowProtected` lifts that, and is
|
|
53
|
+
* the caller explicitly taking responsibility.
|
|
54
|
+
* - **`dryRun` resolves and reports without deleting**, so the exact subjects and
|
|
55
|
+
* folders can be shown to the user before anything happens. Use it first.
|
|
56
|
+
* - **Every outcome echoes the subject and folderPath** of the item actually
|
|
57
|
+
* resolved, so a wrong id is visible after the fact rather than silent.
|
|
58
|
+
*/
|
|
59
|
+
async function deleteOutlookEmails(emailAccount, entryIds, options = {}) {
|
|
60
|
+
const { allowProtected = false, dryRun = false } = options;
|
|
61
|
+
if (entryIds.length === 0) {
|
|
62
|
+
return { dryRun, deleted: 0, refused: 0, failed: 0, items: [] };
|
|
63
|
+
}
|
|
64
|
+
const { valid, invalid } = (0, scripts_1.partitionMessageIds)(entryIds);
|
|
65
|
+
const items = invalid.map(bad => ({
|
|
66
|
+
entryId: bad.entryId,
|
|
67
|
+
subject: '',
|
|
68
|
+
folderPath: '',
|
|
69
|
+
status: 'failed',
|
|
70
|
+
reason: bad.error,
|
|
71
|
+
}));
|
|
72
|
+
if (valid.length > 0) {
|
|
73
|
+
const script = `tell application "Microsoft Outlook"
|
|
74
|
+
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
75
|
+
set inboxName to (name of (inbox of targetAcct)) as string
|
|
76
|
+
set sentName to (name of (sent items of targetAcct)) as string
|
|
77
|
+
set out to ""
|
|
78
|
+
repeat with theId in {${valid.join(', ')}}
|
|
79
|
+
set subj to ""
|
|
80
|
+
set pathText to ""
|
|
81
|
+
try
|
|
82
|
+
set theMsg to missing value
|
|
83
|
+
try
|
|
84
|
+
set theMsg to message id theId
|
|
85
|
+
end try
|
|
86
|
+
if theMsg is missing value then error "no item with that message id in this Outlook"
|
|
87
|
+
try
|
|
88
|
+
set subj to (subject of theMsg) as string
|
|
89
|
+
end try
|
|
90
|
+
set theFolder to missing value
|
|
91
|
+
try
|
|
92
|
+
set theFolder to folder of theMsg
|
|
93
|
+
end try
|
|
94
|
+
if theFolder is missing value then error "item has no parent folder - refusing to delete"
|
|
95
|
+
${folderChainSnippet(' ')}
|
|
96
|
+
set pathText to my sanitizeList(my reverseList(chainNames))
|
|
97
|
+
set rootName to item (count of chainNames) of chainNames
|
|
98
|
+
set isProtected to (rootName is inboxName) or (rootName is sentName)
|
|
99
|
+
if isProtected and not ${allowProtected} then
|
|
100
|
+
set out to out & ${(0, run_1.asRow)([
|
|
101
|
+
'(theId as string)',
|
|
102
|
+
'subj',
|
|
103
|
+
'pathText',
|
|
104
|
+
'"refused"',
|
|
105
|
+
`"${(0, run_1.asEscape)(mail_1.PROTECTED_MAIL_REASON)}"`,
|
|
106
|
+
])}
|
|
107
|
+
else if ${dryRun} then
|
|
108
|
+
set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'pathText', '"would-delete"', '""'])}
|
|
109
|
+
else
|
|
110
|
+
delete theMsg
|
|
111
|
+
set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'pathText', '"deleted"', '""'])}
|
|
112
|
+
end if
|
|
113
|
+
on error errText
|
|
114
|
+
set out to out & ${(0, run_1.asRow)(['(theId as string)', 'subj', 'pathText', '"failed"', 'errText'])}
|
|
115
|
+
end try
|
|
116
|
+
end repeat
|
|
117
|
+
return out
|
|
118
|
+
end tell
|
|
119
|
+
|
|
120
|
+
on reverseList(lst)
|
|
121
|
+
set out to {}
|
|
122
|
+
repeat with i from (count of lst) to 1 by -1
|
|
123
|
+
set end of out to item i of lst
|
|
124
|
+
end repeat
|
|
125
|
+
return out
|
|
126
|
+
end reverseList`;
|
|
127
|
+
for (const record of (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(script, 300000))) {
|
|
128
|
+
const parts = (0, run_1.splitFields)(record);
|
|
129
|
+
const segments = (0, run_1.splitList)((0, run_1.field)(parts, 2));
|
|
130
|
+
items.push({
|
|
131
|
+
entryId: (0, run_1.field)(parts, 0),
|
|
132
|
+
subject: (0, run_1.field)(parts, 1).trim(),
|
|
133
|
+
folderPath: segments.length
|
|
134
|
+
? (0, scripts_1.macFolderPath)(emailAccount, segments[0], segments.slice(1))
|
|
135
|
+
: '',
|
|
136
|
+
status: ((0, run_1.field)(parts, 3) || 'failed'),
|
|
137
|
+
reason: (0, run_1.field)(parts, 4),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
dryRun,
|
|
143
|
+
deleted: items.filter(i => i.status === 'deleted').length,
|
|
144
|
+
refused: items.filter(i => i.status === 'refused').length,
|
|
145
|
+
failed: items.filter(i => i.status === 'failed').length,
|
|
146
|
+
items,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Permanently remove items from the account's Deleted Items folder. This is the
|
|
151
|
+
* ONE genuinely irreversible operation here — nothing recovers from it — which
|
|
152
|
+
* is why it is folder-scoped rather than keyed on a message id: it can only ever
|
|
153
|
+
* destroy what the user already threw away.
|
|
154
|
+
*
|
|
155
|
+
* `olderThanDays` keeps recent items (0 = purge everything). The folder is
|
|
156
|
+
* indexed first and destroyed by id afterwards, so deleting can't shift the
|
|
157
|
+
* collection out from under the walk.
|
|
158
|
+
*/
|
|
159
|
+
async function purgeDeletedItems(emailAccount, olderThanDays = 0, dryRun = false) {
|
|
160
|
+
const days = Math.max(0, Math.floor(olderThanDays));
|
|
161
|
+
const folderPath = (0, scripts_1.macFolderPath)(emailAccount, 'Deleted Items', []);
|
|
162
|
+
const indexScript = `tell application "Microsoft Outlook"
|
|
163
|
+
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
164
|
+
set trashFolder to deleted items of targetAcct
|
|
165
|
+
set idList to id of every message of trashFolder
|
|
166
|
+
try
|
|
167
|
+
set timeList to time received of every message of trashFolder
|
|
168
|
+
on error
|
|
169
|
+
set timeList to {}
|
|
170
|
+
end try
|
|
171
|
+
set cutoff to (current date) - (${days} * days)
|
|
172
|
+
set out to ""
|
|
173
|
+
repeat with i from 1 to (count of idList)
|
|
174
|
+
set stamp to missing value
|
|
175
|
+
if (count of timeList) is (count of idList) then set stamp to item i of timeList
|
|
176
|
+
set keepIt to false
|
|
177
|
+
if ${days} > 0 and stamp is not missing value and stamp is greater than cutoff then set keepIt to true
|
|
178
|
+
set out to out & ${(0, run_1.asRow)(['(item i of idList as string)', '(keepIt as string)'])}
|
|
179
|
+
end repeat
|
|
180
|
+
return out
|
|
181
|
+
end tell`;
|
|
182
|
+
const rows = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(indexScript, 300000)).map(record => {
|
|
183
|
+
const parts = (0, run_1.splitFields)(record);
|
|
184
|
+
return { id: (0, run_1.field)(parts, 0), keep: (0, run_1.boolField)(parts, 1) };
|
|
185
|
+
});
|
|
186
|
+
const doomed = rows.filter(row => !row.keep);
|
|
187
|
+
const kept = rows.length - doomed.length;
|
|
188
|
+
if (dryRun || doomed.length === 0) {
|
|
189
|
+
return { folderPath, dryRun, matched: doomed.length, purged: 0, kept, failed: 0 };
|
|
190
|
+
}
|
|
191
|
+
const purgeScript = `tell application "Microsoft Outlook"
|
|
192
|
+
set purgedCount to 0
|
|
193
|
+
set failedCount to 0
|
|
194
|
+
repeat with theId in {${doomed.map(row => row.id).join(', ')}}
|
|
195
|
+
try
|
|
196
|
+
set theMsg to missing value
|
|
197
|
+
try
|
|
198
|
+
set theMsg to message id theId
|
|
199
|
+
end try
|
|
200
|
+
if theMsg is missing value then error "gone"
|
|
201
|
+
permanently delete theMsg
|
|
202
|
+
set purgedCount to purgedCount + 1
|
|
203
|
+
on error
|
|
204
|
+
set failedCount to failedCount + 1
|
|
205
|
+
end try
|
|
206
|
+
end repeat
|
|
207
|
+
return ${(0, run_1.asRow)(['(purgedCount as string)', '(failedCount as string)'])}
|
|
208
|
+
end tell`;
|
|
209
|
+
const summary = (0, run_1.summaryFields)(await (0, run_1.runOsaScript)(purgeScript, 600000));
|
|
210
|
+
return {
|
|
211
|
+
folderPath,
|
|
212
|
+
dryRun,
|
|
213
|
+
matched: doomed.length,
|
|
214
|
+
purged: (0, run_1.intField)(summary, 0),
|
|
215
|
+
kept,
|
|
216
|
+
failed: (0, run_1.intField)(summary, 1),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/mac/cleanup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { DeleteDraftsResult, ListDraftsResult, SendAllDraftsResult } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* List the mail drafts belonging to `emailAccount`, newest first.
|
|
4
|
+
*
|
|
5
|
+
* Bodies are previewed, never returned whole — a templated reply body runs to
|
|
6
|
+
* tens of thousands of characters and a folder's worth would blow the cap.
|
|
7
|
+
*/
|
|
8
|
+
export declare function listOutlookDrafts(emailAccount: string, limit?: number, previewChars?: number): Promise<ListDraftsResult>;
|
|
9
|
+
/**
|
|
10
|
+
* Send the mail drafts that belong to `emailAccount`.
|
|
11
|
+
*
|
|
12
|
+
* The ids are snapshotted before the first send: sending moves an item out of
|
|
13
|
+
* Drafts, and mutating the collection mid-enumeration would skip every other one
|
|
14
|
+
* — the same trap the Windows implementation avoids by snapshotting item refs.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sendAllDrafts(emailAccount: string): Promise<SendAllDraftsResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Delete mail drafts by message id. `delete` files the item in Deleted Items
|
|
19
|
+
* rather than destroying it, so a mistaken call stays recoverable from there —
|
|
20
|
+
* matching the Windows contract.
|
|
21
|
+
*
|
|
22
|
+
* Every id must resolve to a message sitting in THIS account's Drafts folder
|
|
23
|
+
* before anything is deleted; an id pointing at ordinary mail, or at another
|
|
24
|
+
* account's draft, is refused and reported. Without that gate this would be a
|
|
25
|
+
* general-purpose "delete any email by id" tool, which is not what it is for.
|
|
26
|
+
*/
|
|
27
|
+
export declare function deleteOutlookDrafts(emailAccount: string, entryIds: string[]): Promise<DeleteDraftsResult>;
|