@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/windows/read.js
CHANGED
|
@@ -13,291 +13,270 @@ const json_1 = require("../shared/json");
|
|
|
13
13
|
const mail_1 = require("../mail");
|
|
14
14
|
const errors_1 = require("../errors");
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* The COM property carrying each kind of folder's timestamp. Sent Items and
|
|
17
|
+
* Drafts have no ReceivedTime, and filtering them on it returns an empty set
|
|
18
|
+
* that looks exactly like an empty folder.
|
|
19
19
|
*/
|
|
20
|
-
function
|
|
21
|
-
|
|
20
|
+
function dateProperty(rootId) {
|
|
21
|
+
if (rootId === mail_1.FolderId.Drafts)
|
|
22
|
+
return 'LastModificationTime';
|
|
23
|
+
return (0, mail_1.isOutgoingRoot)(rootId) ? 'SentOn' : 'ReceivedTime';
|
|
22
24
|
}
|
|
23
|
-
// Outgoing roots keep their timestamp under a different COM property; anything
|
|
24
|
-
// else carries ReceivedTime.
|
|
25
|
-
const OUTGOING_DATE_PROPS = {
|
|
26
|
-
[mail_1.WELL_KNOWN_FOLDERS['sent items']]: 'SentOn',
|
|
27
|
-
[mail_1.WELL_KNOWN_FOLDERS.outbox]: 'SentOn',
|
|
28
|
-
[mail_1.WELL_KNOWN_FOLDERS.drafts]: 'LastModificationTime',
|
|
29
|
-
};
|
|
30
25
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* `folder` scopes the read to one folder under the Inbox instead of the Inbox
|
|
36
|
-
* root. Without it, mail already filed away (Inbox\\Invoices) is unreachable —
|
|
37
|
-
* which is the normal state of any mailbox its owner keeps tidy. Segments are
|
|
38
|
-
* matched as direct children; a bare name that isn't a direct child falls back to
|
|
39
|
-
* the same recursive by-name search moveOutlookEmails uses, so one folder string
|
|
40
|
-
* works in both. Only the named folder is read — its own subfolders are not.
|
|
26
|
+
* Bodies cross the PowerShell boundary base64-encoded: they carry quotes,
|
|
27
|
+
* control characters and non-ASCII that would otherwise have to survive JSON
|
|
28
|
+
* escaping in two languages intact.
|
|
41
29
|
*/
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
// Which timestamp the folder's items actually carry (see the note in the script).
|
|
55
|
-
const dateProp = OUTGOING_DATE_PROPS[ref.rootId] ?? 'ReceivedTime';
|
|
56
|
-
const isOutgoing = (0, run_1.psBool)((0, mail_1.isOutgoingRoot)(ref.rootId));
|
|
57
|
-
const script = `${(0, scripts_1.accountScript)(emailAccount)}
|
|
58
|
-
${(0, scripts_1.namedStoreScript)(emailAccount)}
|
|
59
|
-
${(0, scripts_1.mailScopeScript)(ref, folder || '')}
|
|
60
|
-
$scopePath = $scope.FolderPath
|
|
61
|
-
$cutoff = (Get-Date).AddDays(-${daysBack}).ToString('MM/dd/yyyy HH:mm')
|
|
62
|
-
# Sent Items and Drafts carry no ReceivedTime, so restricting on it there returns
|
|
63
|
-
# an empty set that looks exactly like an empty folder. Filter each on the date
|
|
64
|
-
# property it actually has.
|
|
65
|
-
$filtered = $scope.Items.Restrict("[${dateProp}] >= '$cutoff'")
|
|
30
|
+
const BODY_B64_PS = `[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([string]$bodyRaw))`;
|
|
31
|
+
/** Recent mail from the Inbox root or one folder, newest first. */
|
|
32
|
+
async function readInboxEmails(request) {
|
|
33
|
+
const dateProp = dateProperty(request.folder.rootId);
|
|
34
|
+
const outgoing = (0, mail_1.isOutgoingRoot)(request.folder.rootId);
|
|
35
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
36
|
+
${scripts_1.NAMED_STORE_PS}
|
|
37
|
+
${(0, scripts_1.mailScopeScript)(request.folder, request.folderLabel)}
|
|
38
|
+
$scopePath = [string]$scope.FolderPath
|
|
39
|
+
${(0, scripts_1.cutoffScript)(request.daysBack)}
|
|
40
|
+
${(0, scripts_1.itemsSinceScript)('scope', dateProp)}
|
|
66
41
|
$filtered.Sort('[${dateProp}]', $true)
|
|
67
|
-
$
|
|
68
|
-
$
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
$
|
|
73
|
-
|
|
74
|
-
$
|
|
75
|
-
|
|
76
|
-
$stamp =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
42
|
+
$limit = ${(0, run_1.psInt)(request.limit)}
|
|
43
|
+
$previewChars = ${(0, run_1.psInt)(request.previewChars)}
|
|
44
|
+
$rows = @()
|
|
45
|
+
$count = $filtered.Count
|
|
46
|
+
for ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {
|
|
47
|
+
$item = $null
|
|
48
|
+
try { $item = $filtered.Item($i) } catch {}
|
|
49
|
+
if ($item -eq $null) { continue }
|
|
50
|
+
$stamp = $null
|
|
51
|
+
try { $stamp = $item.${dateProp} } catch {}
|
|
52
|
+
# Sorted newest first, so the first item older than the window ends it.
|
|
53
|
+
if ($stamp -ne $null -and $stamp -lt $cutoff) { break }
|
|
54
|
+
$preview = ''
|
|
55
|
+
if ($previewChars -gt 0) {
|
|
56
|
+
$bodyText = ''
|
|
57
|
+
try { $bodyText = [string]$item.Body } catch {}
|
|
58
|
+
$preview = if ($bodyText.Length -gt $previewChars) { $bodyText.Substring(0, $previewChars) } else { $bodyText }
|
|
59
|
+
}
|
|
60
|
+
$attachmentNames = @()
|
|
61
|
+
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
80
62
|
$who = ''
|
|
81
|
-
|
|
82
|
-
$
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
63
|
+
$whoEmail = ''
|
|
64
|
+
if (${(0, run_1.psBool)(outgoing)}) {
|
|
65
|
+
# Outgoing mail reports who it went TO; "from" is always the mailbox itself.
|
|
66
|
+
try { $who = [string]$item.To } catch {}
|
|
67
|
+
$whoEmail = $who
|
|
68
|
+
} else {
|
|
69
|
+
try { $who = [string]$item.SenderName } catch {}
|
|
70
|
+
try { $whoEmail = [string]$item.SenderEmailAddress } catch {}
|
|
71
|
+
}
|
|
72
|
+
$rows += [PSCustomObject]@{
|
|
73
|
+
entryId = [string]$item.EntryID
|
|
74
|
+
storeId = $storeId
|
|
75
|
+
folderPath = $scopePath
|
|
76
|
+
subject = ([string]$item.Subject).Trim()
|
|
77
|
+
senderName = $who
|
|
78
|
+
senderEmail = $whoEmail
|
|
79
|
+
receivedTime = if ($stamp -ne $null) { $stamp.ToString('yyyy-MM-dd HH:mm') } else { '' }
|
|
80
|
+
bodyPreview = $preview
|
|
81
|
+
attachmentNames = @($attachmentNames)
|
|
93
82
|
}
|
|
94
83
|
}
|
|
95
|
-
ConvertTo-Json
|
|
96
|
-
|
|
97
|
-
return (0, json_1.
|
|
98
|
-
const e = (0, json_1.record)(
|
|
84
|
+
ConvertTo-Json -Depth 4 -InputObject @($rows)
|
|
85
|
+
`, 'standard');
|
|
86
|
+
return (0, json_1.toArray)(output).map(row => {
|
|
87
|
+
const e = (0, json_1.record)(row);
|
|
88
|
+
const attachmentNames = (0, json_1.toArray)(e.attachmentNames).map(json_1.str);
|
|
99
89
|
return {
|
|
100
90
|
entryId: (0, json_1.str)(e.entryId),
|
|
101
91
|
storeId: (0, json_1.str)(e.storeId),
|
|
102
|
-
folderPath: (0, json_1.str)(e.folderPath),
|
|
103
92
|
subject: (0, json_1.str)(e.subject),
|
|
104
93
|
senderName: (0, json_1.str)(e.senderName),
|
|
105
94
|
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
106
95
|
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
107
96
|
bodyPreview: (0, json_1.str)(e.bodyPreview),
|
|
108
|
-
attachmentNames
|
|
109
|
-
attachmentCount:
|
|
97
|
+
attachmentNames,
|
|
98
|
+
attachmentCount: attachmentNames.length,
|
|
99
|
+
folderPath: (0, json_1.str)(e.folderPath),
|
|
110
100
|
};
|
|
111
101
|
});
|
|
112
102
|
}
|
|
113
103
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* Uses `MailItem.Body`, not `HTMLBody`: Outlook's own plain-text rendering is
|
|
117
|
-
* what every other reader here consumes, and the markup costs an order of
|
|
118
|
-
* magnitude more for the same sentences. The tradeoff is that HTML tables
|
|
119
|
-
* flatten, so figures broken out in a table lose their row/column pairing — read
|
|
120
|
-
* those from the attachment where there is one.
|
|
104
|
+
* One email's full plain-text body, by id.
|
|
121
105
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* `includeQuoted` is off by default — see splitQuotedOriginal for why the
|
|
127
|
-
* quoted thread is a hazard to a caller reading figures out of a reply.
|
|
128
|
-
* `quotedLength` is reported either way so a caller can tell it exists and ask again.
|
|
106
|
+
* `Body`, not `HTMLBody`: Outlook's own plain-text rendering is what every other
|
|
107
|
+
* reader here consumes, and markup costs an order of magnitude more for the same
|
|
108
|
+
* sentences. The tradeoff is that an HTML table's rows flatten.
|
|
129
109
|
*/
|
|
130
|
-
async function readEmailBody(
|
|
131
|
-
(0, run_1.
|
|
132
|
-
|
|
133
|
-
$
|
|
134
|
-
$
|
|
135
|
-
|
|
136
|
-
$
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
try { $cls = [int]$item.Class } catch {}
|
|
140
|
-
if ($cls -ne 43 -and $cls -ne 46) { throw "The item for this EntryID is not an email (Class=$cls). Re-run readInboxEmails for a current EntryID." }
|
|
110
|
+
async function readEmailBody(email) {
|
|
111
|
+
const output = await (0, run_1.runPowerShellJson)(`${scripts_1.SESSION_PS}
|
|
112
|
+
${(0, scripts_1.itemLookupScript)(email)}
|
|
113
|
+
$class = 0
|
|
114
|
+
try { $class = [int]$item.Class } catch {}
|
|
115
|
+
# 43 = olMail, 46 = olReport (a non-delivery report is still worth reading).
|
|
116
|
+
if ($class -ne 43 -and $class -ne 46) {
|
|
117
|
+
throw "${(0, errors_1.failureTag)('NOT_FOUND', 'email')}The item with entry id '$lookupId' is not an email (class $class)."
|
|
118
|
+
}
|
|
141
119
|
$bodyRaw = ''
|
|
142
|
-
try {
|
|
143
|
-
$
|
|
144
|
-
try { foreach ($att in $item.Attachments) { $
|
|
120
|
+
try { $bodyRaw = [string]$item.Body } catch {}
|
|
121
|
+
$attachmentNames = @()
|
|
122
|
+
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
145
123
|
${scripts_1.SENDER_SMTP_PS}
|
|
146
124
|
$received = ''
|
|
147
125
|
try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
148
|
-
|
|
149
|
-
entryId = $item.EntryID
|
|
150
|
-
subject =
|
|
151
|
-
senderName =
|
|
126
|
+
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
127
|
+
entryId = [string]$item.EntryID
|
|
128
|
+
subject = ([string]$item.Subject).Trim()
|
|
129
|
+
senderName = [string]$item.SenderName
|
|
152
130
|
senderEmail = $senderSmtp
|
|
153
131
|
receivedTime = $received
|
|
154
|
-
body =
|
|
155
|
-
attachmentNames = $
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const raw = await (0, run_1.runPowerShell)(script, 20000);
|
|
160
|
-
if (!raw || !raw.trim())
|
|
161
|
-
throw new errors_1.NotFoundError('email', 'Failed to read email body.');
|
|
162
|
-
const parsed = (0, json_1.parseObject)(raw);
|
|
163
|
-
const { body, quoted, separator } = (0, mail_1.splitQuotedOriginal)(decodeBody(parsed.body));
|
|
164
|
-
const attachmentNames = (0, json_1.toArray)(parsed.attachmentNames).map(json_1.str);
|
|
165
|
-
// The quoted thread is context, never the sender's own answer, so it is capped
|
|
166
|
-
// harder than the reply itself — its useful part (what was asked) is at the
|
|
167
|
-
// top of it.
|
|
168
|
-
const quotedCap = Math.min(maxChars, 4000);
|
|
132
|
+
body = ${BODY_B64_PS}
|
|
133
|
+
attachmentNames = @($attachmentNames)
|
|
134
|
+
})
|
|
135
|
+
`, 'quick');
|
|
136
|
+
const e = (0, json_1.record)(output);
|
|
169
137
|
return {
|
|
170
|
-
entryId: (0, json_1.str)(
|
|
171
|
-
subject: (0, json_1.str)(
|
|
172
|
-
senderName: (0, json_1.str)(
|
|
173
|
-
senderEmail: (0, json_1.str)(
|
|
174
|
-
receivedTime: (0, json_1.str)(
|
|
175
|
-
body:
|
|
176
|
-
|
|
177
|
-
bodyLength: body.length,
|
|
178
|
-
quoteSeparator: separator,
|
|
179
|
-
quotedLength: quoted.length,
|
|
180
|
-
quotedOriginal: includeQuoted ? quoted.slice(0, quotedCap) : '',
|
|
181
|
-
attachmentNames,
|
|
182
|
-
attachmentCount: attachmentNames.length,
|
|
138
|
+
entryId: (0, json_1.str)(e.entryId) || email.entryId,
|
|
139
|
+
subject: (0, json_1.str)(e.subject),
|
|
140
|
+
senderName: (0, json_1.str)(e.senderName),
|
|
141
|
+
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
142
|
+
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
143
|
+
body: (0, json_1.base64Text)(e.body),
|
|
144
|
+
attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
|
|
183
145
|
};
|
|
184
146
|
}
|
|
185
147
|
/**
|
|
186
|
-
* Walk every folder under the Inbox
|
|
187
|
-
* the emails matching `filter` — full body included, attachments listed by
|
|
188
|
-
* name but not saved.
|
|
148
|
+
* Walk every folder under the Inbox for the mail matching the request.
|
|
189
149
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* `
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* behavior, and is only sane paired with `filter.subjectLike` so Restrict can
|
|
197
|
-
* narrow the set server-side before anything crosses COM. Where the store
|
|
198
|
-
* won't run that subject query — an IMAP mailbox accepts it and then matches
|
|
199
|
-
* nothing — an unbounded scan falls back to the last 60 days rather than
|
|
200
|
-
* walking every item the mailbox has ever held.
|
|
150
|
+
* The window is enforced twice — a date Restrict per folder, then a per-item
|
|
151
|
+
* check — because Restrict is not reliable across stores, and a scan that
|
|
152
|
+
* silently widens on some mailboxes is the failure this guards against.
|
|
153
|
+
* `subjectLike` also drives a server-side prefilter, purely as an optimization:
|
|
154
|
+
* every subject is re-checked, so a store that can't run the query (IMAP stores
|
|
155
|
+
* accept it and match nothing) returns the same set, only slower.
|
|
201
156
|
*/
|
|
202
|
-
async function searchInboxByFilter(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
$
|
|
215
|
-
$
|
|
216
|
-
${(0,
|
|
217
|
-
${(0,
|
|
218
|
-
$
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
$
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
|
|
157
|
+
async function searchInboxByFilter(request) {
|
|
158
|
+
const subjectFiltered = !!(request.subjectLike || request.subjectPattern || request.excludeReplies);
|
|
159
|
+
// The server-side prefilter speaks DASL, whose `like` wildcards are % and _.
|
|
160
|
+
// A literal % or _ in the glob is left as a wildcard there: that can only
|
|
161
|
+
// widen the prefilter, and the exact test below narrows it again.
|
|
162
|
+
const subjectDasl = request.subjectLike ? request.subjectLike.replace(/\*/g, '%').replace(/\?/g, '_') : '';
|
|
163
|
+
const output = await (0, run_1.runPowerShellJson)(`${(0, scripts_1.accountScript)(request.account)}
|
|
164
|
+
${scripts_1.NAMED_STORE_PS}
|
|
165
|
+
$inbox = $store.GetDefaultFolder(${(0, run_1.psInt)(mail_1.FolderId.Inbox)})
|
|
166
|
+
$subjectLike = ${(0, run_1.psString)(request.subjectLike ? (0, mail_1.subjectGlobSource)(request.subjectLike) : '')}
|
|
167
|
+
$subjectDasl = ${(0, run_1.psString)(subjectDasl)}
|
|
168
|
+
$subjectPattern = ${(0, run_1.psString)(request.subjectPattern?.source ?? '')}
|
|
169
|
+
$replyPattern = ${(0, run_1.psString)(request.excludeReplies ? mail_1.REPLY_PREFIX_SOURCE : '')}
|
|
170
|
+
$requireSubject = ${(0, run_1.psBool)(subjectFiltered)}
|
|
171
|
+
$requireAttachment = ${(0, run_1.psBool)(request.requireAttachment)}
|
|
172
|
+
$includeBody = ${(0, run_1.psBool)(request.includeBody)}
|
|
173
|
+
${(0, scripts_1.cutoffScript)(request.daysBack)}
|
|
174
|
+
# Sent, Drafts, Deleted and Junk are siblings of the Inbox on an Exchange profile
|
|
175
|
+
# and CHILDREN of it on an IMAP one. They are cut by id — their names are
|
|
176
|
+
# localized — before the walk, so each takes its whole subtree with it.
|
|
177
|
+
$skipIds = @{}
|
|
178
|
+
foreach ($role in @(${mail_1.NON_INCOMING_ROOTS.map(run_1.psInt).join(', ')})) {
|
|
179
|
+
try {
|
|
180
|
+
$roleFolder = $store.GetDefaultFolder($role)
|
|
181
|
+
if ($roleFolder -ne $null) { $skipIds[[string]$roleFolder.EntryID] = $true }
|
|
182
|
+
} catch {}
|
|
226
183
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
# top of it. The pattern is CONCATENATED into the query, never interpolated:
|
|
233
|
-
# interpolating would let a caller's $(...) run, and would break the query
|
|
234
|
-
# outright on an apostrophe.
|
|
235
|
-
$subjQuery = '@SQL=' + [char]34 + 'urn:schemas:httpmail:subject' + [char]34 + ' like ' + [char]39 + $subjDasl + [char]39
|
|
236
|
-
$daysBack = ${daysBack}
|
|
237
|
-
foreach ($folder in $folders) {
|
|
238
|
-
if ($daysBack -gt 0) {
|
|
239
|
-
$cutoff = (Get-Date).AddDays(-$daysBack).ToString('MM/dd/yyyy HH:mm')
|
|
240
|
-
$filtered = $folder.Items.Restrict("[ReceivedTime] >= '$cutoff'")
|
|
241
|
-
} else {
|
|
242
|
-
$filtered = $folder.Items
|
|
184
|
+
$skipNames = ${(0, run_1.psArray)(request.excludeFolders)}
|
|
185
|
+
$onlyNames = ${(0, run_1.psArray)(request.includeFolders)}
|
|
186
|
+
function Test-FolderNamed([string]$name, [string]$path, $list) {
|
|
187
|
+
foreach ($entry in $list) {
|
|
188
|
+
if ($name -ieq $entry -or $path -ieq $entry) { return $true }
|
|
243
189
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
190
|
+
return $false
|
|
191
|
+
}
|
|
192
|
+
# Traversal and scanning are separate, and the two lists are not symmetric: an
|
|
193
|
+
# excluded folder is never queued, so its subtree goes with it, while an
|
|
194
|
+
# included one only marks a folder the walk reached anyway — the walk still
|
|
195
|
+
# passes through folders nobody named to reach a nested one somebody did.
|
|
196
|
+
$rootName = ''
|
|
197
|
+
try { $rootName = [string]$inbox.Name } catch {}
|
|
198
|
+
$rootPath = ''
|
|
199
|
+
try { $rootPath = [string]$inbox.FolderPath } catch {}
|
|
200
|
+
$folders = [System.Collections.ArrayList]@($inbox)
|
|
201
|
+
$inScope = [System.Collections.ArrayList]@(($onlyNames.Count -eq 0) -or (Test-FolderNamed $rootName $rootPath $onlyNames))
|
|
202
|
+
$walked = 0
|
|
203
|
+
while ($walked -lt $folders.Count) {
|
|
250
204
|
try {
|
|
251
|
-
$
|
|
252
|
-
|
|
205
|
+
foreach ($sub in $folders[$walked].Folders) {
|
|
206
|
+
if ($skipIds.ContainsKey([string]$sub.EntryID)) { continue }
|
|
207
|
+
$subName = ''
|
|
208
|
+
try { $subName = [string]$sub.Name } catch {}
|
|
209
|
+
$subPath = ''
|
|
210
|
+
try { $subPath = [string]$sub.FolderPath } catch {}
|
|
211
|
+
if (Test-FolderNamed $subName $subPath $skipNames) { continue }
|
|
212
|
+
[void]$folders.Add($sub)
|
|
213
|
+
[void]$inScope.Add(($onlyNames.Count -eq 0) -or (Test-FolderNamed $subName $subPath $onlyNames))
|
|
214
|
+
}
|
|
253
215
|
} catch {}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
216
|
+
$walked++
|
|
217
|
+
}
|
|
218
|
+
# The subject prefilter is DASL rather than Jet: some stores reject Jet's
|
|
219
|
+
# "[Subject] like" outright, while DASL is accepted wherever Restrict is. The
|
|
220
|
+
# two syntaxes can't share one filter, so it is a second Restrict on top of the
|
|
221
|
+
# date one. The pattern is concatenated into the query, never interpolated.
|
|
222
|
+
$subjectQuery = '@SQL=' + [char]34 + 'urn:schemas:httpmail:subject' + [char]34 + ' like ' + [char]39 + $subjectDasl.Replace("'", "''") + [char]39
|
|
223
|
+
# A list, not an array: += copies the whole array per row, and a wide scan returns thousands.
|
|
224
|
+
$rows = New-Object System.Collections.ArrayList
|
|
225
|
+
$seen = @{}
|
|
226
|
+
for ($fx = 0; $fx -lt $folders.Count; $fx++) {
|
|
227
|
+
if (-not $inScope[$fx]) { continue }
|
|
228
|
+
$folder = $folders[$fx]
|
|
229
|
+
${(0, scripts_1.itemsSinceScript)('folder', 'ReceivedTime').trim()}
|
|
230
|
+
if ($subjectDasl) {
|
|
231
|
+
try {
|
|
232
|
+
$candidate = $filtered.Restrict($subjectQuery)
|
|
233
|
+
if ($candidate.Count -gt 0) { $filtered = $candidate }
|
|
234
|
+
} catch {}
|
|
261
235
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
$
|
|
267
|
-
$
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
$
|
|
272
|
-
|
|
273
|
-
$
|
|
274
|
-
$
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
$
|
|
236
|
+
$folderPath = ''
|
|
237
|
+
try { $folderPath = [string]$folder.FolderPath } catch {}
|
|
238
|
+
$count = $filtered.Count
|
|
239
|
+
for ($i = 1; $i -le $count; $i++) {
|
|
240
|
+
$item = $null
|
|
241
|
+
try { $item = $filtered.Item($i) } catch {}
|
|
242
|
+
# A restricted collection can hand back nothing for an index, and an item
|
|
243
|
+
# with no id can't be acted on by anything this package does.
|
|
244
|
+
if ($item -eq $null) { continue }
|
|
245
|
+
$entryId = [string]$item.EntryID
|
|
246
|
+
if (-not $entryId -or $seen.ContainsKey($entryId)) { continue }
|
|
247
|
+
$seen[$entryId] = $true
|
|
248
|
+
$subject = ''
|
|
249
|
+
try { $subject = ([string]$item.Subject).Trim() } catch {}
|
|
250
|
+
if ($requireSubject -and -not $subject) { continue }
|
|
251
|
+
if ($subjectLike -and $subject -notmatch $subjectLike) { continue }
|
|
252
|
+
if ($subjectPattern -and $subject -notmatch $subjectPattern) { continue }
|
|
253
|
+
if ($replyPattern -and $subject -imatch $replyPattern) { continue }
|
|
254
|
+
$received = $null
|
|
255
|
+
try { $received = $item.ReceivedTime } catch {}
|
|
256
|
+
if ($received -ne $null -and $received -lt $cutoff) { continue }
|
|
257
|
+
$attachmentNames = @()
|
|
258
|
+
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
259
|
+
if ($requireAttachment -and $attachmentNames.Count -eq 0) { continue }
|
|
260
|
+
$bodyRaw = ''
|
|
261
|
+
if ($includeBody) { try { $bodyRaw = [string]$item.Body } catch {} }
|
|
278
262
|
${scripts_1.SENDER_SMTP_PS}
|
|
279
|
-
$
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
folderPath = $folder.FolderPath
|
|
291
|
-
}
|
|
263
|
+
[void]$rows.Add([PSCustomObject]@{
|
|
264
|
+
entryId = $entryId
|
|
265
|
+
storeId = $storeId
|
|
266
|
+
subject = $subject
|
|
267
|
+
senderName = [string]$item.SenderName
|
|
268
|
+
senderEmail = $senderSmtp
|
|
269
|
+
receivedTime = if ($received -ne $null) { $received.ToString('yyyy-MM-dd HH:mm') } else { '' }
|
|
270
|
+
body = ${BODY_B64_PS}
|
|
271
|
+
attachmentNames = @($attachmentNames)
|
|
272
|
+
folderPath = $folderPath
|
|
273
|
+
})
|
|
292
274
|
}
|
|
293
275
|
}
|
|
294
|
-
ConvertTo-Json
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
// matches thousands of emails.
|
|
299
|
-
return (0, json_1.parseArray)(await (0, run_1.runPowerShell)(script, 300000)).map(item => {
|
|
300
|
-
const e = (0, json_1.record)(item);
|
|
276
|
+
ConvertTo-Json -Depth 4 -InputObject @($rows)
|
|
277
|
+
`, 'scan');
|
|
278
|
+
return (0, json_1.toArray)(output).map(row => {
|
|
279
|
+
const e = (0, json_1.record)(row);
|
|
301
280
|
return {
|
|
302
281
|
entryId: (0, json_1.str)(e.entryId),
|
|
303
282
|
storeId: (0, json_1.str)(e.storeId),
|
|
@@ -305,64 +284,57 @@ ConvertTo-Json $results -Depth 3
|
|
|
305
284
|
senderName: (0, json_1.str)(e.senderName),
|
|
306
285
|
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
307
286
|
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
308
|
-
body:
|
|
287
|
+
body: (0, json_1.base64Text)(e.body),
|
|
309
288
|
attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
|
|
310
289
|
folderPath: (0, json_1.str)(e.folderPath),
|
|
311
290
|
};
|
|
312
291
|
});
|
|
313
292
|
}
|
|
314
|
-
/**
|
|
315
|
-
* Read the email currently selected (or open) in Outlook — full body,
|
|
316
|
-
* attachments listed by name but not saved.
|
|
317
|
-
*/
|
|
293
|
+
/** The email currently selected — or open — in Outlook. */
|
|
318
294
|
async function readSelectedEmail() {
|
|
319
|
-
(0, run_1.
|
|
320
|
-
const script = `
|
|
295
|
+
const output = await (0, run_1.runPowerShellJson)(`
|
|
321
296
|
$outlook = New-Object -ComObject Outlook.Application
|
|
322
297
|
$item = $null
|
|
323
298
|
try {
|
|
324
299
|
$explorer = $outlook.ActiveExplorer()
|
|
325
|
-
if ($explorer -ne $null) {
|
|
326
|
-
$sel = $explorer.Selection
|
|
327
|
-
if ($sel -ne $null -and $sel.Count -ge 1) { $item = $sel.Item(1) }
|
|
328
|
-
}
|
|
300
|
+
if ($explorer -ne $null -and $explorer.Selection.Count -ge 1) { $item = $explorer.Selection.Item(1) }
|
|
329
301
|
} catch {}
|
|
330
302
|
if ($item -eq $null) {
|
|
331
303
|
try {
|
|
332
|
-
$
|
|
333
|
-
if ($
|
|
304
|
+
$inspector = $outlook.ActiveInspector()
|
|
305
|
+
if ($inspector -ne $null) { $item = $inspector.CurrentItem }
|
|
334
306
|
} catch {}
|
|
335
307
|
}
|
|
336
|
-
if ($item -eq $null) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
$
|
|
341
|
-
|
|
342
|
-
$
|
|
343
|
-
$
|
|
308
|
+
if ($item -eq $null) {
|
|
309
|
+
throw "${(0, errors_1.failureTag)('NOT_FOUND', 'email')}No email is selected in Outlook. Select or open an email, then try again."
|
|
310
|
+
}
|
|
311
|
+
if ([string]$item.MessageClass -notlike 'IPM.Note*') {
|
|
312
|
+
throw "${(0, errors_1.failureTag)('INVALID_REQUEST')}The item selected in Outlook is not an email."
|
|
313
|
+
}
|
|
314
|
+
$attachmentNames = @()
|
|
315
|
+
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
316
|
+
$bodyRaw = ''
|
|
317
|
+
try { $bodyRaw = [string]$item.Body } catch {}
|
|
344
318
|
$storeId = ''
|
|
345
|
-
try { $storeId = $item.Parent.Store.StoreID } catch {}
|
|
319
|
+
try { $storeId = [string]$item.Parent.Store.StoreID } catch {}
|
|
320
|
+
$folderPath = ''
|
|
321
|
+
try { $folderPath = [string]$item.Parent.FolderPath } catch {}
|
|
346
322
|
${scripts_1.SENDER_SMTP_PS}
|
|
347
323
|
$received = ''
|
|
348
324
|
try { $received = $item.ReceivedTime.ToString('yyyy-MM-dd HH:mm') } catch {}
|
|
349
|
-
|
|
350
|
-
entryId
|
|
351
|
-
storeId
|
|
352
|
-
subject
|
|
353
|
-
senderName
|
|
354
|
-
senderEmail
|
|
355
|
-
receivedTime
|
|
356
|
-
body
|
|
357
|
-
attachmentNames = $
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
if (!raw || raw.trim() === '' || raw.trim() === 'null') {
|
|
363
|
-
throw new errors_1.NotFoundError('email', 'Failed to read the selected Outlook email.');
|
|
364
|
-
}
|
|
365
|
-
const e = (0, json_1.parseObject)(raw);
|
|
325
|
+
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
326
|
+
entryId = [string]$item.EntryID
|
|
327
|
+
storeId = $storeId
|
|
328
|
+
subject = ([string]$item.Subject).Trim()
|
|
329
|
+
senderName = [string]$item.SenderName
|
|
330
|
+
senderEmail = $senderSmtp
|
|
331
|
+
receivedTime = $received
|
|
332
|
+
body = ${BODY_B64_PS}
|
|
333
|
+
attachmentNames = @($attachmentNames)
|
|
334
|
+
folderPath = $folderPath
|
|
335
|
+
})
|
|
336
|
+
`, 'quick');
|
|
337
|
+
const e = (0, json_1.record)(output);
|
|
366
338
|
return {
|
|
367
339
|
entryId: (0, json_1.str)(e.entryId),
|
|
368
340
|
storeId: (0, json_1.str)(e.storeId),
|
|
@@ -370,35 +342,22 @@ ConvertTo-Json $result -Depth 3
|
|
|
370
342
|
senderName: (0, json_1.str)(e.senderName),
|
|
371
343
|
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
372
344
|
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
373
|
-
body:
|
|
374
|
-
attachmentNames: (0, json_1.
|
|
345
|
+
body: (0, json_1.base64Text)(e.body),
|
|
346
|
+
attachmentNames: (0, json_1.strList)(e.attachmentNames),
|
|
347
|
+
folderPath: (0, json_1.str)(e.folderPath),
|
|
375
348
|
};
|
|
376
349
|
}
|
|
377
350
|
/**
|
|
378
|
-
* Open an email in Outlook
|
|
379
|
-
*
|
|
380
|
-
* `storeId` disambiguates across mailboxes — without one, `GetItemFromID` only
|
|
381
|
-
* looks in the default store, so an id from a shared or secondary mailbox
|
|
382
|
-
* simply isn't found. It's tried first and the bare lookup is the fallback, so
|
|
383
|
-
* a caller holding a stale StoreID (or none) still resolves whatever it can.
|
|
351
|
+
* Open an email in Outlook.
|
|
384
352
|
*
|
|
385
|
-
*
|
|
386
|
-
* before its mail was filed can
|
|
387
|
-
*
|
|
388
|
-
* this throws a plain not-found rather than guessing.
|
|
353
|
+
* Entry ids are rewritten when an item MOVES between folders, so an id recorded
|
|
354
|
+
* before its mail was filed can stop resolving. That fails as NOT_FOUND rather
|
|
355
|
+
* than guessing — the caller is the one who knows what the email was.
|
|
389
356
|
*/
|
|
390
|
-
async function openOutlookEmail(
|
|
391
|
-
(0, run_1.
|
|
392
|
-
|
|
393
|
-
$outlook = New-Object -ComObject Outlook.Application
|
|
394
|
-
$ns = $outlook.GetNamespace('mapi')
|
|
395
|
-
$ns.Logon()
|
|
396
|
-
$item = $null
|
|
397
|
-
${storeId ? `try { ${(0, scripts_1.getItemScript)(entryId, storeId)} } catch { $item = $null }` : ''}
|
|
398
|
-
if ($item -eq $null) { try { ${(0, scripts_1.getItemScript)(entryId)} } catch { $item = $null } }
|
|
399
|
-
if ($item -eq $null) { throw "Email not found for EntryID '${(0, run_1.psEscape)(entryId)}'" }
|
|
357
|
+
async function openOutlookEmail(email) {
|
|
358
|
+
await (0, run_1.runPowerShell)(`${scripts_1.SESSION_PS}
|
|
359
|
+
${(0, scripts_1.itemLookupScript)(email)}
|
|
400
360
|
$item.Display()
|
|
401
|
-
|
|
402
|
-
await (0, run_1.runPowerShell)(script);
|
|
361
|
+
`, 'quick');
|
|
403
362
|
}
|
|
404
363
|
//# sourceMappingURL=read.js.map
|