@dawnlit/outlook-bridge 1.1.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -183
- package/dist/backend.d.ts +151 -0
- package/dist/backend.js +3 -0
- package/dist/backend.js.map +1 -0
- package/dist/bridge.d.ts +56 -0
- package/dist/bridge.js +507 -0
- package/dist/bridge.js.map +1 -0
- package/dist/errors.d.ts +79 -43
- package/dist/errors.js +93 -52
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +25 -52
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +7 -2
- package/dist/mac/accounts.js +23 -6
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.d.ts +5 -25
- package/dist/mac/attachments.js +40 -99
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.d.ts +12 -27
- package/dist/mac/bounces.js +96 -117
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -25
- package/dist/mac/cleanup.js +44 -107
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +15 -34
- package/dist/mac/drafts.js +113 -186
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +12 -21
- package/dist/mac/folders.js +44 -59
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/index.d.ts +2 -22
- package/dist/mac/index.js +19 -81
- package/dist/mac/index.js.map +1 -1
- package/dist/mac/profile.d.ts +13 -0
- package/dist/mac/profile.js +149 -0
- package/dist/mac/profile.js.map +1 -0
- package/dist/mac/read.d.ts +21 -37
- package/dist/mac/read.js +198 -216
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +51 -38
- package/dist/mac/run.js +138 -103
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +90 -91
- package/dist/mac/scripts.js +167 -126
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +10 -20
- package/dist/mac/send.js +99 -89
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/signatures.d.ts +2 -2
- package/dist/mac/signatures.js +9 -11
- package/dist/mac/signatures.js.map +1 -1
- package/dist/mac/templates.d.ts +14 -26
- package/dist/mac/templates.js +79 -120
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +80 -46
- package/dist/mail.js +179 -73
- package/dist/mail.js.map +1 -1
- package/dist/runtime.d.ts +76 -43
- package/dist/runtime.js +103 -48
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.d.ts +48 -0
- package/dist/shared/args.js +216 -0
- package/dist/shared/args.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +26 -20
- package/dist/shared/attachmentMatch.js +60 -23
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/bounceReport.d.ts +11 -0
- package/dist/shared/bounceReport.js +45 -0
- package/dist/shared/bounceReport.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +38 -10
- package/dist/shared/bounceRules.js +50 -32
- package/dist/shared/bounceRules.js.map +1 -1
- package/dist/shared/exec.d.ts +21 -0
- package/dist/shared/exec.js +47 -0
- package/dist/shared/exec.js.map +1 -0
- package/dist/shared/json.d.ts +17 -6
- package/dist/shared/json.js +35 -24
- package/dist/shared/json.js.map +1 -1
- package/dist/shared/locate.d.ts +31 -0
- package/dist/shared/locate.js +128 -0
- package/dist/shared/locate.js.map +1 -0
- package/dist/shared/replyBody.d.ts +31 -36
- package/dist/shared/replyBody.js +83 -72
- package/dist/shared/replyBody.js.map +1 -1
- package/dist/shared/templateLimits.d.ts +14 -0
- package/dist/shared/templateLimits.js +21 -0
- package/dist/shared/templateLimits.js.map +1 -0
- package/dist/templateBody.d.ts +69 -0
- package/dist/{outlookTemplateSections.js → templateBody.js} +80 -73
- package/dist/templateBody.js.map +1 -0
- package/dist/types.d.ts +381 -168
- package/dist/types.js +8 -9
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.d.ts +7 -12
- package/dist/windows/accounts.js +19 -34
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.d.ts +9 -24
- package/dist/windows/attachments.js +94 -119
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.d.ts +13 -26
- package/dist/windows/bounces.js +168 -200
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +23 -23
- package/dist/windows/cleanup.js +99 -113
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +16 -35
- package/dist/windows/drafts.js +156 -253
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.d.ts +8 -13
- package/dist/windows/folders.js +44 -52
- package/dist/windows/folders.js.map +1 -1
- package/dist/windows/index.d.ts +2 -27
- package/dist/windows/index.js +11 -66
- package/dist/windows/index.js.map +1 -1
- package/dist/windows/read.d.ts +24 -60
- package/dist/windows/read.js +265 -306
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +38 -40
- package/dist/windows/run.js +151 -112
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +65 -55
- package/dist/windows/scripts.js +109 -85
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +9 -9
- package/dist/windows/send.js +76 -88
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/signatures.d.ts +4 -10
- package/dist/windows/signatures.js +30 -37
- package/dist/windows/signatures.js.map +1 -1
- package/dist/windows/templates.d.ts +12 -18
- package/dist/windows/templates.js +117 -150
- package/dist/windows/templates.js.map +1 -1
- package/package.json +5 -4
- package/dist/OutlookService.d.ts +0 -43
- package/dist/OutlookService.js +0 -137
- package/dist/OutlookService.js.map +0 -1
- package/dist/outlookTemplateSections.d.ts +0 -62
- package/dist/outlookTemplateSections.js.map +0 -1
- package/dist/shared/capabilities.d.ts +0 -3
- package/dist/shared/capabilities.js +0 -9
- package/dist/shared/capabilities.js.map +0 -1
package/dist/mac/read.js
CHANGED
|
@@ -8,49 +8,32 @@ exports.openOutlookEmail = openOutlookEmail;
|
|
|
8
8
|
// Reading mail on macOS: one folder, one item, a whole Inbox tree, or whatever
|
|
9
9
|
// is selected in the running Outlook.
|
|
10
10
|
//
|
|
11
|
-
// Every reader
|
|
12
|
-
//
|
|
11
|
+
// Every reader is two passes. Pass 1 indexes a folder with bulk property reads
|
|
12
|
+
// (one Apple event each); pass 2 fetches the expensive fields — body,
|
|
13
13
|
// attachments, sender — only for the messages that survived. Reading
|
|
14
14
|
// per-message in a loop costs an event per property and is unusably slow on a
|
|
15
15
|
// real mailbox, which is the whole reason for the shape.
|
|
16
16
|
const run_1 = require("./run");
|
|
17
|
+
const profile_1 = require("./profile");
|
|
17
18
|
const scripts_1 = require("./scripts");
|
|
18
|
-
const mail_1 = require("../mail");
|
|
19
19
|
const errors_1 = require("../errors");
|
|
20
|
-
|
|
21
|
-
const PREVIEW_CHARS = 600;
|
|
20
|
+
const mail_1 = require("../mail");
|
|
22
21
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `entryId` here is Outlook for Mac's small integer message id (e.g. "779"), not
|
|
26
|
-
* the MAPI EntryID string Windows returns. The two are not interchangeable, so
|
|
27
|
-
* don't persist one and look it up on the other platform.
|
|
22
|
+
* Recent mail from the Inbox root or one folder, newest first. Entry ids here
|
|
23
|
+
* are Outlook for Mac's integer message ids, not Windows EntryIDs.
|
|
28
24
|
*/
|
|
29
|
-
async function readInboxEmails(
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (folder && folder.trim() && ref.segments.length === 0 && ref.rootId === 6
|
|
39
|
-
&& folder.trim().toLowerCase() !== 'inbox') {
|
|
40
|
-
throw new errors_1.NotFoundError('folder', `Folder '${folder}' does not name a folder under the Inbox.`);
|
|
41
|
-
}
|
|
42
|
-
const resolveScope = (0, scripts_1.mailScopeSnippet)(ref, folder || '');
|
|
43
|
-
const folderPath = (0, scripts_1.macFolderPath)(emailAccount, ref.rootLabel, ref.segments);
|
|
44
|
-
const isOutgoing = (0, mail_1.isOutgoingRoot)(ref.rootId);
|
|
45
|
-
// Pass 1 — index the folder. Folder order isn't documented, so sort here
|
|
46
|
-
// rather than trusting Outlook to hand back newest-first.
|
|
47
|
-
const indexScript = `tell application "Microsoft Outlook"
|
|
48
|
-
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
49
|
-
${resolveScope}
|
|
50
|
-
set cutoff to (current date) - (${days} * days)
|
|
25
|
+
async function readInboxEmails(request) {
|
|
26
|
+
const acct = await (0, scripts_1.resolveMacAccount)(request.account);
|
|
27
|
+
const folderPath = (0, scripts_1.macFolderPath)(request.account, request.folder.rootLabel, request.folder.segments);
|
|
28
|
+
const outgoing = (0, mail_1.isOutgoingRoot)(request.folder.rootId);
|
|
29
|
+
// Pass 1 — index the folder. Its order isn't documented, so it is sorted here.
|
|
30
|
+
const index = (await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
31
|
+
${(0, scripts_1.accountLookupSnippet)(acct)}
|
|
32
|
+
${(0, scripts_1.mailScopeSnippet)(acct, request.folder, request.folderLabel)}
|
|
33
|
+
set cutoff to (current date) - (${(0, run_1.asInt)(request.daysBack)} * days)
|
|
51
34
|
set idList to id of every message of scopeFolder
|
|
52
35
|
try
|
|
53
|
-
set timeList to ${(0, scripts_1.dateProperty)(
|
|
36
|
+
set timeList to ${(0, scripts_1.dateProperty)(request.folder.rootId)} of every message of scopeFolder
|
|
54
37
|
on error
|
|
55
38
|
set timeList to {}
|
|
56
39
|
end try
|
|
@@ -59,16 +42,14 @@ ${resolveScope}
|
|
|
59
42
|
repeat with i from 1 to (count of idList)
|
|
60
43
|
set d to missing value
|
|
61
44
|
if hasTimes then set d to item i of timeList
|
|
62
|
-
-- An item
|
|
63
|
-
-- mail folder) is skipped
|
|
64
|
-
-- Windows Restrict on the same property does with it.
|
|
45
|
+
-- An item with no timestamp of its own (an unsent draft filed into a
|
|
46
|
+
-- mail folder) is skipped, as the Windows date filter skips it.
|
|
65
47
|
if d is not missing value and d is greater than or equal to cutoff then
|
|
66
48
|
set out to out & (item i of idList as string) & tab & my isoDate(d) & linefeed
|
|
67
49
|
end if
|
|
68
50
|
end repeat
|
|
69
51
|
return out
|
|
70
|
-
end tell
|
|
71
|
-
const index = (await (0, run_1.runOsaScript)(indexScript, 60000))
|
|
52
|
+
end tell`, 'standard'))
|
|
72
53
|
.split('\n')
|
|
73
54
|
.map(line => line.trim())
|
|
74
55
|
.filter(Boolean)
|
|
@@ -76,59 +57,54 @@ end tell`;
|
|
|
76
57
|
const [id, receivedTime] = line.split('\t');
|
|
77
58
|
return { id, receivedTime: receivedTime || '' };
|
|
78
59
|
});
|
|
79
|
-
// 'yyyy-MM-dd HH:mm'
|
|
60
|
+
// 'yyyy-MM-dd HH:mm' sorts correctly as a string.
|
|
80
61
|
index.sort((a, b) => b.receivedTime.localeCompare(a.receivedTime));
|
|
81
|
-
const chosen = index.slice(0,
|
|
62
|
+
const chosen = index.slice(0, request.limit);
|
|
82
63
|
if (chosen.length === 0)
|
|
83
64
|
return [];
|
|
84
|
-
// Pass 2 — the expensive reads, only for messages
|
|
85
|
-
const
|
|
86
|
-
|
|
65
|
+
// Pass 2 — the expensive reads, only for the messages kept.
|
|
66
|
+
const preview = request.previewChars > 0
|
|
67
|
+
? ` set bodyText to ""
|
|
68
|
+
try
|
|
69
|
+
set bodyText to (plain text content of theMsg) as string
|
|
70
|
+
end try
|
|
71
|
+
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`
|
|
72
|
+
: ' set bodyText to ""';
|
|
73
|
+
const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
87
74
|
set out to ""
|
|
88
|
-
repeat with
|
|
75
|
+
repeat with theId in ${(0, run_1.asIdList)(chosen.map(c => c.id))}
|
|
89
76
|
set theMsg to missing value
|
|
90
77
|
try
|
|
91
|
-
set theMsg to message id
|
|
78
|
+
set theMsg to message id theId
|
|
92
79
|
end try
|
|
93
80
|
if theMsg is not missing value then
|
|
94
81
|
set subj to ""
|
|
95
82
|
try
|
|
96
83
|
set subj to (subject of theMsg) as string
|
|
97
84
|
end try
|
|
98
|
-
${
|
|
99
|
-
|
|
100
|
-
try
|
|
101
|
-
set bodyText to (plain text content of theMsg) as string
|
|
102
|
-
end try
|
|
103
|
-
if (length of bodyText) > ${PREVIEW_CHARS} then set bodyText to text 1 thru ${PREVIEW_CHARS} of bodyText
|
|
85
|
+
${outgoing ? (0, scripts_1.firstRecipientSnippet)(' ') : (0, scripts_1.senderSnippet)(' ')}
|
|
86
|
+
${preview}
|
|
104
87
|
set attNames to {}
|
|
105
88
|
try
|
|
106
89
|
set attNames to name of every attachment of theMsg
|
|
107
90
|
end try
|
|
108
|
-
set out to out & ${(0, run_1.asRow)([
|
|
109
|
-
'(id of theMsg as string)',
|
|
110
|
-
'subj',
|
|
111
|
-
'sndName',
|
|
112
|
-
'sndAddr',
|
|
113
|
-
'bodyText',
|
|
114
|
-
'my sanitizeList(attNames)',
|
|
115
|
-
])}
|
|
91
|
+
set out to out & ${(0, run_1.asRow)(['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'bodyText', 'my sanitizePositionalList(attNames)'])}
|
|
116
92
|
end if
|
|
117
93
|
end repeat
|
|
118
94
|
return out
|
|
119
|
-
end tell
|
|
120
|
-
const
|
|
121
|
-
return (0, run_1.splitRecords)(
|
|
95
|
+
end tell`, 'standard');
|
|
96
|
+
const receivedById = new Map(chosen.map(c => [c.id, c.receivedTime]));
|
|
97
|
+
return (0, run_1.splitRecords)(raw).map(record => {
|
|
122
98
|
const parts = (0, run_1.splitFields)(record);
|
|
123
99
|
const id = (0, run_1.field)(parts, 0);
|
|
124
|
-
const attachmentNames = (0, run_1.
|
|
100
|
+
const attachmentNames = (0, run_1.splitPositionalList)((0, run_1.field)(parts, 5));
|
|
125
101
|
return {
|
|
126
102
|
entryId: id,
|
|
127
|
-
storeId: '',
|
|
103
|
+
storeId: '',
|
|
128
104
|
subject: (0, run_1.field)(parts, 1).trim(),
|
|
129
105
|
senderName: (0, run_1.field)(parts, 2),
|
|
130
106
|
senderEmail: (0, run_1.field)(parts, 3),
|
|
131
|
-
receivedTime:
|
|
107
|
+
receivedTime: receivedById.get(id) || '',
|
|
132
108
|
bodyPreview: (0, run_1.field)(parts, 4),
|
|
133
109
|
attachmentNames,
|
|
134
110
|
attachmentCount: attachmentNames.length,
|
|
@@ -137,216 +113,198 @@ end tell`;
|
|
|
137
113
|
});
|
|
138
114
|
}
|
|
139
115
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* `storeId` is accepted for signature parity and ignored: macOS has no StoreID,
|
|
143
|
-
* and `message id N` resolves against the application rather than one folder, so
|
|
144
|
-
* the message is found wherever it currently sits — including a subfolder.
|
|
145
|
-
*
|
|
146
|
-
* Uses `plain text content`, matching the Windows reader's use of `.Body`: the
|
|
147
|
-
* same tradeoff applies, so an HTML table's rows flatten and tabular figures are
|
|
148
|
-
* better read from an attachment.
|
|
116
|
+
* One email's full plain-text body. `storeId` has no macOS meaning: `message id
|
|
117
|
+
* N` resolves against the application, wherever the message sits.
|
|
149
118
|
*/
|
|
150
|
-
async function readEmailBody(
|
|
151
|
-
const id = (0, scripts_1.macMessageId)(entryId);
|
|
152
|
-
const
|
|
119
|
+
async function readEmailBody(email) {
|
|
120
|
+
const id = (0, scripts_1.macMessageId)(email.entryId);
|
|
121
|
+
const parts = (0, run_1.summaryFields)(await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
153
122
|
${(0, scripts_1.messageLookupSnippet)(id)}
|
|
154
123
|
${(0, scripts_1.messageDetailSnippet)(true)}
|
|
155
124
|
return ${(0, run_1.asRow)((0, scripts_1.messageDetailFields)(true))}
|
|
156
|
-
end tell
|
|
157
|
-
// The body is emitted LAST so a stray separator in earlier fields can't shift it.
|
|
158
|
-
const parts = (0, run_1.summaryFields)(await (0, run_1.runOsaScript)(script, 60000));
|
|
159
|
-
const attachmentNames = (0, run_1.splitList)((0, run_1.field)(parts, scripts_1.MessageDetail.attachmentNames));
|
|
160
|
-
const { body, quoted, separator } = (0, mail_1.splitQuotedOriginal)((0, run_1.field)(parts, scripts_1.MessageDetail.body));
|
|
161
|
-
// The quoted thread is context, never the priced content, so it is capped
|
|
162
|
-
// harder than the reply itself — matching the Windows reader.
|
|
163
|
-
const quotedCap = Math.min(maxChars, 4000);
|
|
125
|
+
end tell`, 'quick'));
|
|
164
126
|
return {
|
|
165
127
|
entryId: (0, run_1.field)(parts, scripts_1.MessageDetail.id) || id,
|
|
166
128
|
subject: (0, run_1.field)(parts, scripts_1.MessageDetail.subject).trim(),
|
|
167
129
|
senderName: (0, run_1.field)(parts, scripts_1.MessageDetail.senderName),
|
|
168
130
|
senderEmail: (0, run_1.field)(parts, scripts_1.MessageDetail.senderEmail),
|
|
169
131
|
receivedTime: (0, run_1.field)(parts, scripts_1.MessageDetail.receivedTime),
|
|
170
|
-
body:
|
|
171
|
-
|
|
172
|
-
bodyLength: body.length,
|
|
173
|
-
quoteSeparator: separator,
|
|
174
|
-
quotedLength: quoted.length,
|
|
175
|
-
quotedOriginal: includeQuoted ? quoted.slice(0, quotedCap) : '',
|
|
176
|
-
attachmentNames,
|
|
177
|
-
attachmentCount: attachmentNames.length,
|
|
132
|
+
body: (0, run_1.field)(parts, scripts_1.MessageDetail.body),
|
|
133
|
+
attachmentNames: (0, run_1.splitPositionalList)((0, run_1.field)(parts, scripts_1.MessageDetail.attachmentNames)),
|
|
178
134
|
};
|
|
179
135
|
}
|
|
180
136
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* Windows hands `subjectLike` to Outlook's server-side Restrict; AppleScript has
|
|
184
|
-
* no equivalent, so the same pattern is applied here instead. `%` and `*` are
|
|
185
|
-
* the any-run wildcards, `_` and `?` match one character, and the match is
|
|
186
|
-
* anchored because that is what SQL `like` means.
|
|
187
|
-
*/
|
|
188
|
-
function likePattern(pattern) {
|
|
189
|
-
let source = '';
|
|
190
|
-
for (const ch of pattern) {
|
|
191
|
-
if (ch === '%' || ch === '*')
|
|
192
|
-
source += '.*';
|
|
193
|
-
else if (ch === '_' || ch === '?')
|
|
194
|
-
source += '.';
|
|
195
|
-
else
|
|
196
|
-
source += ch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
197
|
-
}
|
|
198
|
-
return new RegExp(`^${source}$`, 'i');
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Walk every folder under the Inbox (recursively) for one account and return the
|
|
202
|
-
* emails matching `filter` — full body included, attachments listed by name but
|
|
203
|
-
* not saved.
|
|
137
|
+
* Walk every folder under the Inbox for the mail matching the request.
|
|
204
138
|
*
|
|
205
|
-
* The filtering happens here rather than in Outlook:
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
* 0 over a large mailbox tree is correspondingly heavier here.
|
|
210
|
-
*
|
|
211
|
-
* `subjectPattern` is applied case-insensitively regardless of the RegExp's own
|
|
212
|
-
* flags, matching PowerShell's `-match`.
|
|
139
|
+
* The filtering happens here rather than in Outlook: there is no AppleScript
|
|
140
|
+
* counterpart to COM's server-side `Items.Restrict`, so each folder is indexed in
|
|
141
|
+
* bulk reads and the subject and date tests run on the index. The results match
|
|
142
|
+
* Windows; the cost doesn't, and a wide window over a big tree is heavier here.
|
|
213
143
|
*/
|
|
214
|
-
async function searchInboxByFilter(
|
|
215
|
-
const
|
|
144
|
+
async function searchInboxByFilter(request) {
|
|
145
|
+
const acct = await (0, scripts_1.resolveMacAccount)(request.account);
|
|
146
|
+
// AppleScript reaches a child folder by name, so a path entry narrows to its leaf.
|
|
147
|
+
const asNameList = (list) => `{${list.map(mail_1.folderLeafName).filter(Boolean).map(run_1.asString).join(', ')}}`;
|
|
148
|
+
const rootInScope = request.includeFolders.length === 0 ? 'true' : 'false';
|
|
149
|
+
// Sent, Drafts, Deleted and Junk hang UNDER the Inbox on an IMAP profile, so
|
|
150
|
+
// their ids are collected first and the walk never descends into them — by
|
|
151
|
+
// the profile's own id where it records one, through the account otherwise.
|
|
152
|
+
const skipIds = ['sent items', 'deleted items', 'drafts', 'junk mail']
|
|
153
|
+
.map(term => {
|
|
154
|
+
const id = acct.folderIds?.[term];
|
|
155
|
+
return id !== undefined
|
|
156
|
+
? ` set end of skipIds to ${id}`
|
|
157
|
+
: ` try
|
|
158
|
+
set end of skipIds to (id of (${term} of targetAcct))
|
|
159
|
+
end try`;
|
|
160
|
+
})
|
|
161
|
+
.join('\n');
|
|
216
162
|
// Pass 1 — index every folder under the Inbox: path, id, subject, received.
|
|
217
|
-
const
|
|
163
|
+
const indexed = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(`on scanFolder(theFolder, prefix, cutoff, skipIds, skipNames, onlyNames, inScope)
|
|
218
164
|
set out to ""
|
|
165
|
+
set idList to {}
|
|
166
|
+
set subjList to {}
|
|
167
|
+
set timeList to {}
|
|
168
|
+
-- A folder the walk only passes through is never read: the bulk reads are
|
|
169
|
+
-- the expensive part.
|
|
170
|
+
if inScope then
|
|
171
|
+
tell application "Microsoft Outlook"
|
|
172
|
+
set idList to id of every message of theFolder
|
|
173
|
+
set subjList to subject of every message of theFolder
|
|
174
|
+
try
|
|
175
|
+
set timeList to time received of every message of theFolder
|
|
176
|
+
on error
|
|
177
|
+
set timeList to {}
|
|
178
|
+
end try
|
|
179
|
+
end tell
|
|
180
|
+
end if
|
|
219
181
|
tell application "Microsoft Outlook"
|
|
220
|
-
set idList to id of every message of theFolder
|
|
221
|
-
set subjList to subject of every message of theFolder
|
|
222
|
-
try
|
|
223
|
-
set timeList to time received of every message of theFolder
|
|
224
|
-
on error
|
|
225
|
-
set timeList to {}
|
|
226
|
-
end try
|
|
227
182
|
set subs to mail folders of theFolder
|
|
228
183
|
end tell
|
|
229
184
|
set hasTimes to ((count of timeList) is (count of idList))
|
|
230
185
|
repeat with i from 1 to (count of idList)
|
|
231
186
|
set d to missing value
|
|
232
187
|
if hasTimes then set d to item i of timeList
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
-- it would look exactly like an empty folder.
|
|
238
|
-
set keep to not useCutoff
|
|
239
|
-
else if useCutoff and d is less than cutoff then
|
|
240
|
-
set keep to false
|
|
241
|
-
end if
|
|
242
|
-
if keep then
|
|
243
|
-
set out to out & ${(0, run_1.asRow)([
|
|
244
|
-
'prefix',
|
|
245
|
-
'(item i of idList as string)',
|
|
246
|
-
'(item i of subjList)',
|
|
247
|
-
'my isoDate(d)',
|
|
248
|
-
])}
|
|
188
|
+
-- An item with no readable receive time is kept whatever the window
|
|
189
|
+
-- says; dropping it would make a folder of them look empty.
|
|
190
|
+
if d is missing value or d is not less than cutoff then
|
|
191
|
+
set out to out & ${(0, run_1.asRow)(['prefix', '(item i of idList as string)', '(item i of subjList)', 'my isoDate(d)'])}
|
|
249
192
|
end if
|
|
250
193
|
end repeat
|
|
251
194
|
repeat with f in subs
|
|
252
|
-
set
|
|
195
|
+
set skipThis to false
|
|
196
|
+
try
|
|
197
|
+
if skipIds contains (id of f) then set skipThis to true
|
|
198
|
+
end try
|
|
253
199
|
try
|
|
254
|
-
|
|
200
|
+
if skipNames contains ((name of f) as string) then set skipThis to true
|
|
255
201
|
end try
|
|
256
|
-
|
|
202
|
+
if not skipThis then
|
|
203
|
+
set childName to ""
|
|
204
|
+
try
|
|
205
|
+
set childName to (name of f) as string
|
|
206
|
+
end try
|
|
207
|
+
set childScope to (count of onlyNames) is 0
|
|
208
|
+
try
|
|
209
|
+
if onlyNames contains childName then set childScope to true
|
|
210
|
+
end try
|
|
211
|
+
set out to out & my scanFolder(f, prefix & ${run_1.AS_LIST_SEP} & childName, cutoff, skipIds, skipNames, onlyNames, childScope)
|
|
212
|
+
end if
|
|
257
213
|
end repeat
|
|
258
214
|
return out
|
|
259
215
|
end scanFolder
|
|
260
216
|
|
|
261
217
|
tell application "Microsoft Outlook"
|
|
262
|
-
${(0, scripts_1.accountLookupSnippet)(
|
|
263
|
-
|
|
218
|
+
${(0, scripts_1.accountLookupSnippet)(acct)}
|
|
219
|
+
${(0, scripts_1.rootFolderSnippet)(acct, 'inbox', 'rootInbox')}
|
|
264
220
|
set rootName to (name of rootInbox) as string
|
|
221
|
+
set skipIds to {}
|
|
222
|
+
${skipIds}
|
|
223
|
+
set skipNames to ${asNameList(request.excludeFolders)}
|
|
224
|
+
set onlyNames to ${asNameList(request.includeFolders)}
|
|
265
225
|
end tell
|
|
266
|
-
set cutoff to (current date) - (${
|
|
267
|
-
return my scanFolder(rootInbox, rootName, cutoff,
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
226
|
+
set cutoff to (current date) - (${(0, run_1.asInt)(request.daysBack)} * days)
|
|
227
|
+
return my scanFolder(rootInbox, rootName, cutoff, skipIds, skipNames, onlyNames, ${rootInScope})`, 'scan'));
|
|
228
|
+
const subjectFiltered = !!(request.subjectLike || request.subjectPattern || request.excludeReplies);
|
|
229
|
+
const likeRe = request.subjectLike ? new RegExp((0, mail_1.subjectGlobSource)(request.subjectLike), 'i') : null;
|
|
230
|
+
// PowerShell's -match is case-insensitive, so this test is too, whatever the
|
|
231
|
+
// caller's flags. Only u and v are kept: they decide how the source parses,
|
|
232
|
+
// and without u, /\p{L}/u would match the literal text "p{L}". Keeping g or y
|
|
233
|
+
// would carry lastIndex from one subject to the next.
|
|
234
|
+
const patternRe = request.subjectPattern
|
|
235
|
+
? new RegExp(request.subjectPattern.source, 'i' + request.subjectPattern.flags.replace(/[^uv]/g, ''))
|
|
273
236
|
: null;
|
|
274
|
-
const candidates =
|
|
237
|
+
const candidates = indexed
|
|
275
238
|
.map(record => {
|
|
276
239
|
const parts = (0, run_1.splitFields)(record);
|
|
277
240
|
const segments = (0, run_1.splitList)((0, run_1.field)(parts, 0));
|
|
278
241
|
return {
|
|
279
|
-
folderPath: (0, scripts_1.macFolderPath)(
|
|
242
|
+
folderPath: (0, scripts_1.macFolderPath)(request.account, segments[0] || 'Inbox', segments.slice(1)),
|
|
280
243
|
id: (0, run_1.field)(parts, 1),
|
|
281
244
|
subject: (0, run_1.field)(parts, 2).trim(),
|
|
282
245
|
receivedTime: (0, run_1.field)(parts, 3),
|
|
283
246
|
};
|
|
284
247
|
})
|
|
285
248
|
.filter(candidate => {
|
|
286
|
-
if (!candidate.subject)
|
|
249
|
+
if (subjectFiltered && !candidate.subject)
|
|
287
250
|
return false;
|
|
288
251
|
if (likeRe && !likeRe.test(candidate.subject))
|
|
289
252
|
return false;
|
|
290
253
|
if (patternRe && !patternRe.test(candidate.subject))
|
|
291
254
|
return false;
|
|
292
|
-
if (
|
|
255
|
+
if (request.excludeReplies && mail_1.REPLY_PREFIX.test(candidate.subject))
|
|
293
256
|
return false;
|
|
294
257
|
return true;
|
|
295
258
|
});
|
|
296
259
|
if (candidates.length === 0)
|
|
297
260
|
return [];
|
|
298
|
-
// Pass 2 —
|
|
299
|
-
const
|
|
300
|
-
|
|
261
|
+
// Pass 2 — senders, attachment names and (when asked) bodies, for the survivors only.
|
|
262
|
+
const body = request.includeBody
|
|
263
|
+
? ` set bodyText to ""
|
|
264
|
+
try
|
|
265
|
+
set bodyText to (plain text content of theMsg) as string
|
|
266
|
+
end try`
|
|
267
|
+
: ' set bodyText to ""';
|
|
268
|
+
const raw = await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
301
269
|
set out to ""
|
|
302
|
-
repeat with
|
|
270
|
+
repeat with theId in ${(0, run_1.asIdList)(candidates.map(c => c.id))}
|
|
303
271
|
set theMsg to missing value
|
|
304
272
|
try
|
|
305
|
-
set theMsg to message id
|
|
273
|
+
set theMsg to message id theId
|
|
306
274
|
end try
|
|
307
275
|
if theMsg is not missing value then
|
|
308
|
-
${(0, scripts_1.senderSnippet)('
|
|
276
|
+
${(0, scripts_1.senderSnippet)(' ')}
|
|
309
277
|
set attNames to {}
|
|
310
278
|
try
|
|
311
279
|
set attNames to name of every attachment of theMsg
|
|
312
280
|
end try
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
set bodyText to (plain text content of theMsg) as string
|
|
316
|
-
end try
|
|
317
|
-
set out to out & ${(0, run_1.asRow)([
|
|
318
|
-
'(id of theMsg as string)',
|
|
319
|
-
'sndName',
|
|
320
|
-
'sndAddr',
|
|
321
|
-
'my sanitizeList(attNames)',
|
|
322
|
-
'bodyText',
|
|
323
|
-
])}
|
|
281
|
+
${body}
|
|
282
|
+
set out to out & ${(0, run_1.asRow)(['(id of theMsg as string)', 'sndName', 'sndAddr', 'my sanitizePositionalList(attNames)', 'bodyText'])}
|
|
324
283
|
end if
|
|
325
284
|
end repeat
|
|
326
285
|
return out
|
|
327
|
-
end tell
|
|
328
|
-
const details = new Map()
|
|
329
|
-
for (const record of (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(detailScript, 300000))) {
|
|
286
|
+
end tell`, 'scan');
|
|
287
|
+
const details = new Map((0, run_1.splitRecords)(raw).map(record => {
|
|
330
288
|
const parts = (0, run_1.splitFields)(record);
|
|
331
|
-
|
|
289
|
+
const detail = {
|
|
332
290
|
senderName: (0, run_1.field)(parts, 1),
|
|
333
291
|
senderEmail: (0, run_1.field)(parts, 2),
|
|
334
|
-
attachmentNames: (0, run_1.
|
|
292
|
+
attachmentNames: (0, run_1.splitPositionalList)((0, run_1.field)(parts, 3)),
|
|
335
293
|
body: (0, run_1.field)(parts, 4),
|
|
336
|
-
}
|
|
337
|
-
|
|
294
|
+
};
|
|
295
|
+
return [(0, run_1.field)(parts, 0), detail];
|
|
296
|
+
}));
|
|
338
297
|
const matches = [];
|
|
339
298
|
for (const candidate of candidates) {
|
|
340
299
|
const detail = details.get(candidate.id);
|
|
341
300
|
if (!detail)
|
|
342
301
|
continue;
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
if (filter.requireAttachment && detail.attachmentNames.length === 0)
|
|
302
|
+
// Attachment names only arrive with the details, so this test waits for them.
|
|
303
|
+
if (request.requireAttachment && detail.attachmentNames.length === 0)
|
|
346
304
|
continue;
|
|
347
305
|
matches.push({
|
|
348
306
|
entryId: candidate.id,
|
|
349
|
-
storeId: '',
|
|
307
|
+
storeId: '',
|
|
350
308
|
subject: candidate.subject,
|
|
351
309
|
senderName: detail.senderName,
|
|
352
310
|
senderEmail: detail.senderEmail,
|
|
@@ -359,50 +317,74 @@ end tell`;
|
|
|
359
317
|
return matches;
|
|
360
318
|
}
|
|
361
319
|
/**
|
|
362
|
-
*
|
|
363
|
-
*
|
|
320
|
+
* The email currently selected — or open — in Outlook. `current messages`
|
|
321
|
+
* covers both cases the Windows reader handles separately.
|
|
364
322
|
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
323
|
+
* Where it sits comes back as a second record, so the detail row keeps its body
|
|
324
|
+
* last. A mailbox with no account object answers `account of` with missing
|
|
325
|
+
* value; its root folder's id is then looked up in the profile database.
|
|
367
326
|
*/
|
|
368
327
|
async function readSelectedEmail() {
|
|
369
|
-
const
|
|
328
|
+
const records = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(`tell application "Microsoft Outlook"
|
|
370
329
|
set sel to {}
|
|
371
330
|
try
|
|
372
331
|
set sel to current messages
|
|
373
332
|
end try
|
|
374
|
-
if (count of sel) is 0 then error
|
|
333
|
+
if (count of sel) is 0 then error ${(0, run_1.asString)(`${(0, errors_1.failureTag)('NOT_FOUND', 'email')}No email is selected in Outlook. Select or open an email, then try again.`)}
|
|
375
334
|
set theMsg to item 1 of sel
|
|
376
335
|
${(0, scripts_1.messageDetailSnippet)(true)}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
336
|
+
set acctAddr to ""
|
|
337
|
+
try
|
|
338
|
+
set rawAddr to email address of (account of theMsg)
|
|
339
|
+
if rawAddr is not missing value then set acctAddr to rawAddr as string
|
|
340
|
+
end try
|
|
341
|
+
set chainNames to {}
|
|
342
|
+
set rootId to ""
|
|
343
|
+
set theFolder to missing value
|
|
344
|
+
try
|
|
345
|
+
set theFolder to folder of theMsg
|
|
346
|
+
end try
|
|
347
|
+
if theFolder is not missing value then
|
|
348
|
+
${(0, scripts_1.folderChainSnippet)(' ')}
|
|
349
|
+
try
|
|
350
|
+
set rootId to (id of cur) as string
|
|
351
|
+
end try
|
|
352
|
+
end if
|
|
353
|
+
return ${(0, run_1.asRow)((0, scripts_1.messageDetailFields)(true))} & ${(0, run_1.asRow)(['acctAddr', 'my sanitizeList(chainNames)', 'rootId'])}
|
|
354
|
+
end tell`, 'quick'));
|
|
355
|
+
if (records.length === 0)
|
|
356
|
+
throw new errors_1.NotFoundError('email', 'Outlook reported no selected email.');
|
|
383
357
|
const parts = (0, run_1.splitFields)(records[0]);
|
|
358
|
+
const where = (0, run_1.splitFields)(records[1] ?? '');
|
|
359
|
+
const chain = (0, run_1.splitList)((0, run_1.field)(where, 1)).reverse();
|
|
360
|
+
const account = (0, run_1.field)(where, 0) || (chain.length ? await accountByRootFolder((0, run_1.field)(where, 2)) : '');
|
|
384
361
|
return {
|
|
385
362
|
entryId: (0, run_1.field)(parts, scripts_1.MessageDetail.id),
|
|
386
|
-
storeId: '',
|
|
363
|
+
storeId: '',
|
|
387
364
|
subject: (0, run_1.field)(parts, scripts_1.MessageDetail.subject).trim(),
|
|
388
365
|
senderName: (0, run_1.field)(parts, scripts_1.MessageDetail.senderName),
|
|
389
366
|
senderEmail: (0, run_1.field)(parts, scripts_1.MessageDetail.senderEmail),
|
|
390
367
|
receivedTime: (0, run_1.field)(parts, scripts_1.MessageDetail.receivedTime),
|
|
391
368
|
body: (0, run_1.field)(parts, scripts_1.MessageDetail.body),
|
|
392
|
-
attachmentNames: (0, run_1.
|
|
369
|
+
attachmentNames: (0, run_1.splitPositionalList)((0, run_1.field)(parts, scripts_1.MessageDetail.attachmentNames)),
|
|
370
|
+
folderPath: account && chain.length ? (0, scripts_1.macFolderPath)(account, chain[0], chain.slice(1)) : '',
|
|
393
371
|
};
|
|
394
372
|
}
|
|
395
|
-
/**
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const id = (
|
|
400
|
-
|
|
373
|
+
/** The address of the account whose root folder has this id, per the profile database; '' if none. */
|
|
374
|
+
async function accountByRootFolder(rootId) {
|
|
375
|
+
if (!/^\d+$/.test(rootId))
|
|
376
|
+
return '';
|
|
377
|
+
const id = Number(rootId);
|
|
378
|
+
return (await (0, profile_1.readProfileAccounts)()).find(a => a.folderIds['root folder'] === id)?.emailAccount ?? '';
|
|
379
|
+
}
|
|
380
|
+
/** Open an email in Outlook and bring Outlook forward. */
|
|
381
|
+
async function openOutlookEmail(email) {
|
|
382
|
+
const id = (0, scripts_1.macMessageId)(email.entryId);
|
|
383
|
+
await (0, run_1.runOsaScript)(`
|
|
401
384
|
tell application "Microsoft Outlook"
|
|
402
385
|
${(0, scripts_1.messageLookupSnippet)(id)}
|
|
403
386
|
open theMsg
|
|
404
387
|
activate
|
|
405
|
-
end tell
|
|
406
|
-
await (0, run_1.runOsaScript)(script, 30000);
|
|
388
|
+
end tell`, 'quick');
|
|
407
389
|
}
|
|
408
390
|
//# sourceMappingURL=read.js.map
|