@dawnlit/outlook-bridge 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -9
- package/dist/backend.d.ts +2 -2
- package/dist/backend.js.map +1 -1
- package/dist/bridge.js +3 -3
- package/dist/bridge.js.map +1 -1
- package/dist/errors.d.ts +17 -8
- package/dist/errors.js +17 -13
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/mac/accounts.d.ts +4 -0
- package/dist/mac/accounts.js +10 -0
- package/dist/mac/accounts.js.map +1 -1
- package/dist/mac/attachments.js +1 -1
- package/dist/mac/attachments.js.map +1 -1
- package/dist/mac/bounces.js +18 -31
- package/dist/mac/bounces.js.map +1 -1
- package/dist/mac/cleanup.d.ts +11 -3
- package/dist/mac/cleanup.js +30 -58
- package/dist/mac/cleanup.js.map +1 -1
- package/dist/mac/drafts.d.ts +3 -1
- package/dist/mac/drafts.js +37 -49
- package/dist/mac/drafts.js.map +1 -1
- package/dist/mac/folders.d.ts +10 -3
- package/dist/mac/folders.js +31 -23
- package/dist/mac/folders.js.map +1 -1
- package/dist/mac/profile.d.ts +12 -0
- package/dist/mac/profile.js +75 -1
- package/dist/mac/profile.js.map +1 -1
- package/dist/mac/read.d.ts +4 -0
- package/dist/mac/read.js +68 -42
- package/dist/mac/read.js.map +1 -1
- package/dist/mac/run.d.ts +19 -5
- package/dist/mac/run.js +49 -9
- package/dist/mac/run.js.map +1 -1
- package/dist/mac/scripts.d.ts +67 -14
- package/dist/mac/scripts.js +150 -31
- package/dist/mac/scripts.js.map +1 -1
- package/dist/mac/send.d.ts +5 -4
- package/dist/mac/send.js +52 -14
- package/dist/mac/send.js.map +1 -1
- package/dist/mac/templates.js +16 -22
- package/dist/mac/templates.js.map +1 -1
- package/dist/mail.d.ts +11 -3
- package/dist/mail.js +25 -6
- package/dist/mail.js.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/shared/args.js.map +1 -1
- package/dist/shared/attachmentMatch.d.ts +0 -6
- package/dist/shared/attachmentMatch.js +0 -1
- package/dist/shared/attachmentMatch.js.map +1 -1
- package/dist/shared/exec.js.map +1 -1
- package/dist/shared/locate.d.ts +0 -8
- package/dist/shared/locate.js +0 -1
- package/dist/shared/locate.js.map +1 -1
- package/dist/shared/replyBody.d.ts +0 -15
- package/dist/shared/replyBody.js +0 -2
- 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 +6 -0
- package/dist/templateBody.js +8 -1
- package/dist/templateBody.js.map +1 -1
- package/dist/types.d.ts +43 -7
- package/dist/types.js.map +1 -1
- package/dist/windows/accounts.js +1 -1
- package/dist/windows/accounts.js.map +1 -1
- package/dist/windows/attachments.js +2 -2
- package/dist/windows/attachments.js.map +1 -1
- package/dist/windows/bounces.js +7 -7
- package/dist/windows/bounces.js.map +1 -1
- package/dist/windows/cleanup.d.ts +5 -1
- package/dist/windows/cleanup.js +18 -15
- package/dist/windows/cleanup.js.map +1 -1
- package/dist/windows/drafts.d.ts +4 -4
- package/dist/windows/drafts.js +22 -33
- package/dist/windows/drafts.js.map +1 -1
- package/dist/windows/folders.js +2 -2
- package/dist/windows/folders.js.map +1 -1
- package/dist/{shared → windows}/json.d.ts +11 -6
- package/dist/{shared → windows}/json.js +12 -7
- package/dist/windows/json.js.map +1 -0
- package/dist/windows/read.js +23 -16
- package/dist/windows/read.js.map +1 -1
- package/dist/windows/run.d.ts +0 -2
- package/dist/windows/run.js +1 -2
- package/dist/windows/run.js.map +1 -1
- package/dist/windows/scripts.d.ts +10 -4
- package/dist/windows/scripts.js +21 -12
- package/dist/windows/scripts.js.map +1 -1
- package/dist/windows/send.d.ts +13 -4
- package/dist/windows/send.js +24 -6
- package/dist/windows/send.js.map +1 -1
- package/dist/windows/templates.js +11 -17
- package/dist/windows/templates.js.map +1 -1
- package/package.json +2 -2
- package/dist/shared/json.js.map +0 -1
package/dist/windows/cleanup.js
CHANGED
|
@@ -5,18 +5,19 @@ exports.purgeDeletedItems = purgeDeletedItems;
|
|
|
5
5
|
// Deleting mail, and emptying what was already deleted.
|
|
6
6
|
const run_1 = require("./run");
|
|
7
7
|
const scripts_1 = require("./scripts");
|
|
8
|
-
const json_1 = require("
|
|
8
|
+
const json_1 = require("./json");
|
|
9
9
|
const mail_1 = require("../mail");
|
|
10
|
-
/** Folders whose contents are received or sent mail rather than working state. */
|
|
11
|
-
const PROTECTED_FOLDER_IDS = [mail_1.FolderId.Inbox, mail_1.FolderId.SentMail];
|
|
12
|
-
const OUTCOME_STATUSES = ['deleted', 'would-delete', 'refused', 'failed'];
|
|
13
10
|
/**
|
|
14
11
|
* Delete mail by entry id from anywhere in the account. Delete() moves each item
|
|
15
12
|
* to Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.
|
|
16
13
|
*
|
|
17
14
|
* This is the one operation that can reach received mail, and an entry id is an
|
|
18
15
|
* unsafe key for it: a stale or wrong id can resolve to a DIFFERENT message.
|
|
19
|
-
*
|
|
16
|
+
* Four things hold the line:
|
|
17
|
+
* - An id that resolves outside this account's store is refused. The bare
|
|
18
|
+
* lookup falls back to the default store, and the Inbox/Sent check below
|
|
19
|
+
* only knows this account's folders, so another mailbox's received mail
|
|
20
|
+
* would otherwise pass it.
|
|
20
21
|
* - Inbox and Sent Items are refused, INCLUDING their subfolders (a filed
|
|
21
22
|
* folder is still received mail), unless `allowProtected`.
|
|
22
23
|
* - `dryRun` resolves and reports without deleting, so the exact subjects and
|
|
@@ -30,7 +31,7 @@ $allowProtected = ${(0, run_1.psBool)(request.allowProtected)}
|
|
|
30
31
|
$dryRun = ${(0, run_1.psBool)(request.dryRun)}
|
|
31
32
|
$protectedReason = ${(0, run_1.psString)(mail_1.PROTECTED_MAIL_REASON)}
|
|
32
33
|
$protectedIds = @{}
|
|
33
|
-
foreach ($role in @(${
|
|
34
|
+
foreach ($role in @(${mail_1.PROTECTED_ROOTS.map(run_1.psInt).join(', ')})) {
|
|
34
35
|
try { $protectedIds[[string]$store.GetDefaultFolder($role).EntryID] = $true } catch {}
|
|
35
36
|
}
|
|
36
37
|
function Test-UnderProtectedFolder($folder) {
|
|
@@ -46,6 +47,14 @@ function Test-UnderProtectedFolder($folder) {
|
|
|
46
47
|
}
|
|
47
48
|
return $false
|
|
48
49
|
}
|
|
50
|
+
function Test-InAccountStore($folder) {
|
|
51
|
+
$folderStoreId = $null
|
|
52
|
+
try { $folderStoreId = [string]$folder.StoreID } catch {}
|
|
53
|
+
if (-not $folderStoreId) { return $false }
|
|
54
|
+
if ($folderStoreId -ieq $storeId) { return $true }
|
|
55
|
+
# One store can be named by two id strings; MAPI decides whether they match.
|
|
56
|
+
try { return [bool]$ns.CompareEntryIDs($folderStoreId, $storeId) } catch { return $false }
|
|
57
|
+
}
|
|
49
58
|
$items = @()
|
|
50
59
|
foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
|
|
51
60
|
$subject = ''
|
|
@@ -60,6 +69,7 @@ foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
|
|
|
60
69
|
try { $parent = $it.Parent } catch {}
|
|
61
70
|
if ($parent -eq $null) { throw "item has no parent folder - refusing to delete" }
|
|
62
71
|
try { $folderPath = [string]$parent.FolderPath } catch {}
|
|
72
|
+
if (-not (Test-InAccountStore $parent)) { throw "item is in another mailbox, not this account - refusing to delete" }
|
|
63
73
|
if ((Test-UnderProtectedFolder $parent) -and -not $allowProtected) {
|
|
64
74
|
$items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'refused'; reason = $protectedReason }
|
|
65
75
|
} elseif ($dryRun) {
|
|
@@ -76,22 +86,15 @@ ConvertTo-Json -Depth 3 -InputObject @($items)
|
|
|
76
86
|
`, 'scan');
|
|
77
87
|
const items = (0, json_1.toArray)(output).map(row => {
|
|
78
88
|
const e = (0, json_1.record)(row);
|
|
79
|
-
const status = OUTCOME_STATUSES.find(s => s === (0, json_1.str)(e.status)) ?? 'failed';
|
|
80
89
|
return {
|
|
81
90
|
entryId: (0, json_1.str)(e.entryId),
|
|
82
91
|
subject: (0, json_1.str)(e.subject),
|
|
83
92
|
folderPath: (0, json_1.str)(e.folderPath),
|
|
84
|
-
status,
|
|
93
|
+
status: (0, mail_1.deleteStatus)((0, json_1.str)(e.status)),
|
|
85
94
|
reason: (0, json_1.str)(e.reason),
|
|
86
95
|
};
|
|
87
96
|
});
|
|
88
|
-
return
|
|
89
|
-
dryRun: request.dryRun,
|
|
90
|
-
deleted: items.filter(i => i.status === 'deleted').length,
|
|
91
|
-
refused: items.filter(i => i.status === 'refused').length,
|
|
92
|
-
failed: items.filter(i => i.status === 'failed').length,
|
|
93
|
-
items,
|
|
94
|
-
};
|
|
97
|
+
return (0, mail_1.deleteMailResult)(request.dryRun, items);
|
|
95
98
|
}
|
|
96
99
|
/**
|
|
97
100
|
* Permanently remove items from the account's Deleted Items. The ONE irreversible
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/windows/cleanup.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/windows/cleanup.ts"],"names":[],"mappings":";;AAyBA,kDAuEC;AAUD,8CA0CC;AApJD,wDAAwD;AACxD,+BAA4E;AAC5E,uCAA2E;AAC3E,iCAAmD;AACnD,kCAA2G;AAI3G;;;;;;;;;;;;;;;;GAgBG;AACI,KAAK,UAAU,mBAAmB,CAAC,OAA4B;IAClE,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAiB,EAAC,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,CAAC;EAC1E,2BAAiB;oBACC,IAAA,YAAM,EAAC,OAAO,CAAC,cAAc,CAAC;YACtC,IAAA,YAAM,EAAC,OAAO,CAAC,MAAM,CAAC;qBACb,IAAA,cAAQ,EAAC,4BAAqB,CAAC;;sBAE9B,sBAAe,CAAC,GAAG,CAAC,WAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;kBAyBzC,IAAA,aAAO,EAAC,OAAO,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2B1C,EAAE,MAAM,CAAC,CAAC;IACP,MAAM,KAAK,GAAwB,IAAA,cAAO,EAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QACzD,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,GAAG,CAAC,CAAC;QACtB,OAAO;YACH,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;YACvB,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;YACvB,UAAU,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,UAAU,CAAC;YAC7B,MAAM,EAAE,IAAA,mBAAY,EAAC,IAAA,UAAG,EAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACnC,MAAM,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,MAAM,CAAC;SACxB,CAAC;IACN,CAAC,CAAC,CAAC;IACH,OAAO,IAAA,uBAAgB,EAAC,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,iBAAiB,CAAC,OAAqB;IACzD,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAiB,EAAC,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,CAAC;EAC1E,2BAAiB;oCACiB,IAAA,WAAK,EAAC,eAAQ,CAAC,YAAY,CAAC;mBAC7C,IAAA,WAAK,EAAC,OAAO,CAAC,aAAa,CAAC;YACnC,IAAA,YAAM,EAAC,OAAO,CAAC,MAAM,CAAC;EAChC,IAAA,sBAAY,EAAC,OAAO,CAAC,aAAa,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BpC,EAAE,OAAO,CAAC,CAAC;IACR,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC;IACzB,OAAO;QACH,UAAU,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,UAAU,CAAC;QAC7B,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,OAAO,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,OAAO,CAAC;QACvB,MAAM,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,MAAM,CAAC;QACrB,IAAI,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,IAAI,CAAC;QACjB,MAAM,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,MAAM,CAAC;KACxB,CAAC;AACN,CAAC","sourcesContent":["// Deleting mail, and emptying what was already deleted.\nimport { psArray, psBool, psInt, psString, runPowerShellJson } from './run';\nimport { accountScript, cutoffScript, DELIVERY_STORE_PS } from './scripts';\nimport { num, record, str, toArray } from './json';\nimport { deleteMailResult, deleteStatus, FolderId, PROTECTED_MAIL_REASON, PROTECTED_ROOTS } from '../mail';\nimport type { DeleteEmailsRequest, PurgeRequest } from '../backend';\nimport type { DeleteMailOutcome, DeleteMailResult, PurgeDeletedItemsResult } from '../types';\n\n/**\n * Delete mail by entry id from anywhere in the account. Delete() moves each item\n * to Deleted Items, so this is recoverable — purgeDeletedItems is what destroys.\n *\n * This is the one operation that can reach received mail, and an entry id is an\n * unsafe key for it: a stale or wrong id can resolve to a DIFFERENT message.\n * Four things hold the line:\n * - An id that resolves outside this account's store is refused. The bare\n * lookup falls back to the default store, and the Inbox/Sent check below\n * only knows this account's folders, so another mailbox's received mail\n * would otherwise pass it.\n * - Inbox and Sent Items are refused, INCLUDING their subfolders (a filed\n * folder is still received mail), unless `allowProtected`.\n * - `dryRun` resolves and reports without deleting, so the exact subjects and\n * folders can be checked before anything happens.\n * - Every outcome echoes the subject and folder of the item actually resolved.\n */\nexport async function deleteOutlookEmails(request: DeleteEmailsRequest): Promise<DeleteMailResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$allowProtected = ${psBool(request.allowProtected)}\n$dryRun = ${psBool(request.dryRun)}\n$protectedReason = ${psString(PROTECTED_MAIL_REASON)}\n$protectedIds = @{}\nforeach ($role in @(${PROTECTED_ROOTS.map(psInt).join(', ')})) {\n try { $protectedIds[[string]$store.GetDefaultFolder($role).EntryID] = $true } catch {}\n}\nfunction Test-UnderProtectedFolder($folder) {\n $current = $folder\n for ($guard = 0; $current -ne $null -and $guard -lt 25; $guard++) {\n $folderId = $null\n try { $folderId = [string]$current.EntryID } catch {}\n if (-not $folderId) { return $false }\n if ($protectedIds.ContainsKey($folderId)) { return $true }\n $parent = $null\n try { $parent = $current.Parent } catch {}\n $current = $parent\n }\n return $false\n}\nfunction Test-InAccountStore($folder) {\n $folderStoreId = $null\n try { $folderStoreId = [string]$folder.StoreID } catch {}\n if (-not $folderStoreId) { return $false }\n if ($folderStoreId -ieq $storeId) { return $true }\n # One store can be named by two id strings; MAPI decides whether they match.\n try { return [bool]$ns.CompareEntryIDs($folderStoreId, $storeId) } catch { return $false }\n}\n$items = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n $folderPath = ''\n try {\n $it = $null\n try { $it = $ns.GetItemFromID($id, $store.StoreID) } catch { $it = $null }\n if ($it -eq $null) { try { $it = $ns.GetItemFromID($id) } catch { $it = $null } }\n if ($it -eq $null) { throw \"no item with that entry id in this account\" }\n try { $subject = [string]$it.Subject } catch {}\n $parent = $null\n try { $parent = $it.Parent } catch {}\n if ($parent -eq $null) { throw \"item has no parent folder - refusing to delete\" }\n try { $folderPath = [string]$parent.FolderPath } catch {}\n if (-not (Test-InAccountStore $parent)) { throw \"item is in another mailbox, not this account - refusing to delete\" }\n if ((Test-UnderProtectedFolder $parent) -and -not $allowProtected) {\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'refused'; reason = $protectedReason }\n } elseif ($dryRun) {\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'would-delete'; reason = '' }\n } else {\n $it.Delete()\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'deleted'; reason = '' }\n }\n } catch {\n $items += [PSCustomObject]@{ entryId = $id; subject = $subject; folderPath = $folderPath; status = 'failed'; reason = $_.Exception.Message }\n }\n}\nConvertTo-Json -Depth 3 -InputObject @($items)\n`, 'scan');\n const items: DeleteMailOutcome[] = toArray(output).map(row => {\n const e = record(row);\n return {\n entryId: str(e.entryId),\n subject: str(e.subject),\n folderPath: str(e.folderPath),\n status: deleteStatus(str(e.status)),\n reason: str(e.reason),\n };\n });\n return deleteMailResult(request.dryRun, items);\n}\n\n/**\n * Permanently remove items from the account's Deleted Items. The ONE irreversible\n * operation here, which is why it is folder-scoped rather than keyed on ids: it\n * can only ever destroy what was already thrown away.\n *\n * Walks backwards, since deleting shifts the collection and a forward walk would\n * skip every other item.\n */\nexport async function purgeDeletedItems(request: PurgeRequest): Promise<PurgeDeletedItemsResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$folder = $store.GetDefaultFolder(${psInt(FolderId.DeletedItems)})\n$olderThanDays = ${psInt(request.olderThanDays)}\n$dryRun = ${psBool(request.dryRun)}\n${cutoffScript(request.olderThanDays)}\n$matched = 0\n$purged = 0\n$kept = 0\n$failed = 0\n# Bound once: each $folder.Items is a COM call that builds a fresh collection.\n$folderItems = $folder.Items\nfor ($i = $folderItems.Count; $i -ge 1; $i--) {\n $it = $null\n try { $it = $folderItems.Item($i) } catch { continue }\n $stamp = $null\n foreach ($property in @('ReceivedTime', 'LastModificationTime', 'CreationTime')) {\n try { $stamp = $it.$property; if ($stamp -ne $null) { break } } catch {}\n }\n if ($olderThanDays -gt 0 -and $stamp -ne $null -and $stamp -gt $cutoff) { $kept++; continue }\n $matched++\n if ($dryRun) { continue }\n try { $it.Delete(); $purged++ } catch { $failed++ }\n}\nConvertTo-Json -Compress -InputObject ([PSCustomObject]@{\n folderPath = [string]$folder.FolderPath\n matched = $matched\n purged = $purged\n kept = $kept\n failed = $failed\n})\n`, 'purge');\n const e = record(output);\n return {\n folderPath: str(e.folderPath),\n dryRun: request.dryRun,\n matched: num(e.matched),\n purged: num(e.purged),\n kept: num(e.kept),\n failed: num(e.failed),\n };\n}\n"]}
|
package/dist/windows/drafts.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../back
|
|
|
2
2
|
import type { DeleteDraftsResult, ListDraftsResult, SendDraftsResult } from '../types';
|
|
3
3
|
/** The account's drafts, newest first. Bodies are previewed, never returned whole. */
|
|
4
4
|
export declare function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult>;
|
|
5
|
-
/** Send the named drafts
|
|
5
|
+
/** Send the named drafts. */
|
|
6
6
|
export declare function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult>;
|
|
7
7
|
/**
|
|
8
8
|
* Send every draft belonging to the account.
|
|
@@ -13,9 +13,9 @@ export declare function sendDrafts(request: EntryIdsRequest): Promise<SendDrafts
|
|
|
13
13
|
*/
|
|
14
14
|
export declare function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult>;
|
|
15
15
|
/**
|
|
16
|
-
* Delete the named drafts
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Delete the named drafts. Outlook's Delete() moves an item to Deleted Items,
|
|
17
|
+
* so a mistake stays recoverable. Without the proof that each is this
|
|
18
|
+
* account's draft, this would be a general "delete any email by id" call,
|
|
19
19
|
* which is not what it is for.
|
|
20
20
|
*/
|
|
21
21
|
export declare function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult>;
|
package/dist/windows/drafts.js
CHANGED
|
@@ -7,7 +7,7 @@ exports.deleteOutlookDrafts = deleteOutlookDrafts;
|
|
|
7
7
|
// Drafts: which ones belong to an account, and listing, sending or deleting them.
|
|
8
8
|
const run_1 = require("./run");
|
|
9
9
|
const scripts_1 = require("./scripts");
|
|
10
|
-
const json_1 = require("
|
|
10
|
+
const json_1 = require("./json");
|
|
11
11
|
const mail_1 = require("../mail");
|
|
12
12
|
/**
|
|
13
13
|
* Resolve the Drafts folders belonging to one account into `$scan`, and define
|
|
@@ -96,7 +96,7 @@ foreach ($entry in $scan) {
|
|
|
96
96
|
if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }
|
|
97
97
|
}
|
|
98
98
|
$modified = ''
|
|
99
|
-
try { $modified = $it.LastModificationTime
|
|
99
|
+
try { $modified = ${(0, scripts_1.stampPs)('$it.LastModificationTime')} } catch {}
|
|
100
100
|
$hasAttachments = $false
|
|
101
101
|
try { $hasAttachments = ($it.Attachments.Count -gt 0) } catch {}
|
|
102
102
|
$rows += [PSCustomObject]@{
|
|
@@ -143,26 +143,31 @@ ConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{
|
|
|
143
143
|
}),
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
-
/**
|
|
147
|
-
async function
|
|
146
|
+
/** Run `verb` over the named drafts of the account, each proved to be its own first. */
|
|
147
|
+
async function actOnDrafts(request, verb) {
|
|
148
148
|
const output = await (0, run_1.runPowerShellJson)(`${draftsScript(request.account)}
|
|
149
|
-
$
|
|
149
|
+
$done = 0
|
|
150
150
|
$failed = @()
|
|
151
151
|
foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
|
|
152
152
|
$subject = ''
|
|
153
153
|
try {
|
|
154
|
-
$draft = Resolve-OwnDraft $id '
|
|
154
|
+
$draft = Resolve-OwnDraft $id '${verb}'
|
|
155
155
|
try { $subject = [string]$draft.Subject } catch {}
|
|
156
|
-
$draft
|
|
157
|
-
$
|
|
156
|
+
$draft.${verb === 'send' ? 'Send' : 'Delete'}()
|
|
157
|
+
$done++
|
|
158
158
|
} catch {
|
|
159
|
-
$
|
|
159
|
+
${(0, json_1.itemFailurePs)('$id')}
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
-
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
162
|
+
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ done = $done; failed = @($failed) })
|
|
163
163
|
`, 'scan');
|
|
164
164
|
const e = (0, json_1.record)(output);
|
|
165
|
-
return {
|
|
165
|
+
return { done: (0, json_1.num)(e.done), failed: (0, json_1.itemFailures)(e.failed) };
|
|
166
|
+
}
|
|
167
|
+
/** Send the named drafts. */
|
|
168
|
+
async function sendDrafts(request) {
|
|
169
|
+
const { done, failed } = await actOnDrafts(request, 'send');
|
|
170
|
+
return { sent: done, failed };
|
|
166
171
|
}
|
|
167
172
|
/**
|
|
168
173
|
* Send every draft belonging to the account.
|
|
@@ -190,7 +195,7 @@ foreach ($draft in $drafts) {
|
|
|
190
195
|
$draft.Send()
|
|
191
196
|
$sent++
|
|
192
197
|
} catch {
|
|
193
|
-
$
|
|
198
|
+
${(0, json_1.itemFailurePs)('$entryId')}
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
201
|
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })
|
|
@@ -199,29 +204,13 @@ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent;
|
|
|
199
204
|
return { sent: (0, json_1.num)(e.sent), failed: (0, json_1.itemFailures)(e.failed) };
|
|
200
205
|
}
|
|
201
206
|
/**
|
|
202
|
-
* Delete the named drafts
|
|
203
|
-
*
|
|
204
|
-
*
|
|
207
|
+
* Delete the named drafts. Outlook's Delete() moves an item to Deleted Items,
|
|
208
|
+
* so a mistake stays recoverable. Without the proof that each is this
|
|
209
|
+
* account's draft, this would be a general "delete any email by id" call,
|
|
205
210
|
* which is not what it is for.
|
|
206
211
|
*/
|
|
207
212
|
async function deleteOutlookDrafts(request) {
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
$failed = @()
|
|
211
|
-
foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
|
|
212
|
-
$subject = ''
|
|
213
|
-
try {
|
|
214
|
-
$draft = Resolve-OwnDraft $id 'delete'
|
|
215
|
-
try { $subject = [string]$draft.Subject } catch {}
|
|
216
|
-
$draft.Delete()
|
|
217
|
-
$deleted++
|
|
218
|
-
} catch {
|
|
219
|
-
$failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ deleted = $deleted; failed = @($failed) })
|
|
223
|
-
`, 'scan');
|
|
224
|
-
const e = (0, json_1.record)(output);
|
|
225
|
-
return { deleted: (0, json_1.num)(e.deleted), failed: (0, json_1.itemFailures)(e.failed) };
|
|
213
|
+
const { done, failed } = await actOnDrafts(request, 'delete');
|
|
214
|
+
return { deleted: done, failed };
|
|
226
215
|
}
|
|
227
216
|
//# sourceMappingURL=drafts.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/windows/drafts.ts"],"names":[],"mappings":"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","sourcesContent":["// Drafts: which ones belong to an account, and listing, sending or deleting them.\nimport { psArray, psInt, runPowerShellJson } from './run';\nimport { accountScript, DELIVERY_STORE_PS } from './scripts';\nimport { bool, itemFailures, num, record, str, strList, toArray } from '../shared/json';\nimport { FolderId } from '../mail';\nimport type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../backend';\nimport type { DeleteDraftsResult, ListDraftsResult, SendDraftsResult } from '../types';\n\n/**\n * Resolve the Drafts folders belonging to one account into `$scan`, and define\n * `Test-DraftMatches` over them. Emitted into every drafts script, so listing,\n * sending and deleting can never disagree about which drafts are this account's.\n *\n * Two Drafts folders can hold one account's drafts, so both are scanned:\n * - the account's OWN store's Drafts, where a draft composed in that mailbox\n * lands. A draft there with no explicit sending account still belongs to it.\n * - the DEFAULT store's Drafts, where a program's `Save()` files a draft\n * whatever account it sends from. Only drafts stamped with this account\n * match there; an unstamped one belongs to the default account.\n * When the two are the same folder it is scanned once, under the first rule.\n *\n * Non-mail items (meeting requests, reports) never match.\n */\nconst DRAFTS_SCAN_PS = `\n$scan = @()\n$seenFolders = @{}\n$homeDrafts = $null\ntry { $homeDrafts = $store.GetDefaultFolder(${psInt(FolderId.Drafts)}) } catch {}\nif ($homeDrafts -ne $null) {\n $seenFolders[\"$($homeDrafts.StoreID)|$($homeDrafts.EntryID)\"] = $true\n $scan += [PSCustomObject]@{ folder = $homeDrafts; includeUnstamped = $true }\n}\n$defaultDrafts = $null\ntry { $defaultDrafts = $ns.GetDefaultFolder(${psInt(FolderId.Drafts)}) } catch {}\nif ($defaultDrafts -ne $null -and -not $seenFolders.ContainsKey(\"$($defaultDrafts.StoreID)|$($defaultDrafts.EntryID)\")) {\n $scan += [PSCustomObject]@{ folder = $defaultDrafts; includeUnstamped = $false }\n}\nfunction Test-DraftMatches($item, [bool]$includeUnstamped) {\n if ($item.Class -ne 43) { return $false } # olMail only\n $sender = $null\n try { $sender = $item.SendUsingAccount } catch {}\n if ($sender -eq $null) { return $includeUnstamped }\n return ([string]$sender.SmtpAddress -ieq $target)\n}\n# An id is acted on only once it is PROVED to be one of this account's drafts:\n# resolved in one of the scanned folders' stores, sitting in that folder, and\n# bound to this account (or unbound, in its own store).\n$draftFolderRules = @{}\nforeach ($entry in $scan) { $draftFolderRules[[string]$entry.folder.EntryID] = $entry.includeUnstamped }\nfunction Resolve-OwnDraft([string]$id, [string]$verb) {\n $draft = $null\n foreach ($entry in $scan) {\n try { $draft = $ns.GetItemFromID($id, $entry.folder.StoreID) } catch { $draft = $null }\n if ($draft -ne $null) { break }\n }\n if ($draft -eq $null) { throw \"no such item in this account's Drafts folders\" }\n $parentId = ''\n try { $parentId = [string]$draft.Parent.EntryID } catch {}\n if (-not $draftFolderRules.ContainsKey($parentId)) { throw \"item is not in this account's Drafts folder - refusing to $verb\" }\n if (-not (Test-DraftMatches $draft $draftFolderRules[$parentId])) { throw \"draft is not bound to $target - refusing to $verb\" }\n return $draft\n}\n`;\n\nfunction draftsScript(account: string): string {\n return accountScript(account) + DELIVERY_STORE_PS + DRAFTS_SCAN_PS;\n}\n\n/** The account's drafts, newest first. Bodies are previewed, never returned whole. */\nexport async function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$previewChars = ${psInt(request.previewChars)}\n$limit = ${psInt(request.limit)}\n$rows = @()\n$folderPaths = @()\nforeach ($entry in $scan) {\n $folderPath = ''\n try { $folderPath = [string]$entry.folder.FolderPath } catch {}\n $folderPaths += $folderPath\n foreach ($it in $entry.folder.Items) {\n if (-not (Test-DraftMatches $it $entry.includeUnstamped)) { continue }\n $addresses = @()\n try {\n foreach ($r in $it.Recipients) {\n $address = ''\n try { $address = [string]$r.Address } catch {}\n # An unresolved Exchange entry has a DN for an address; its name reads better.\n if ($address.StartsWith('/')) { try { $address = [string]$r.Name } catch {} }\n if ($address) { $addresses += $address }\n }\n } catch {}\n $preview = ''\n if ($previewChars -gt 0) {\n try { $preview = ([string]$it.Body -replace '\\\\s+', ' ').Trim() } catch {}\n if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }\n }\n $modified = ''\n try { $modified = $it.LastModificationTime.ToString('yyyy-MM-dd HH:mm') } catch {}\n $hasAttachments = $false\n try { $hasAttachments = ($it.Attachments.Count -gt 0) } catch {}\n $rows += [PSCustomObject]@{\n entryId = [string]$it.EntryID\n subject = [string]$it.Subject\n to = [string]$it.To\n toEmails = @($addresses)\n bodyPreview = $preview\n hasAttachments = $hasAttachments\n lastModified = $modified\n folderPath = $folderPath\n }\n }\n}\n$sorted = @($rows | Sort-Object -Property lastModified -Descending)\n$total = $sorted.Count\nif ($total -gt $limit) { $sorted = @($sorted[0..($limit - 1)]) }\nConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{\n account = $target\n foldersScanned = @($folderPaths)\n count = $total\n truncated = ($total -gt $limit)\n drafts = @($sorted)\n})\n`, 'scan');\n const e = record(output);\n return {\n account: str(e.account) || request.account,\n foldersScanned: toArray(e.foldersScanned).map(str),\n count: num(e.count),\n truncated: bool(e.truncated),\n drafts: toArray(e.drafts).map(row => {\n const d = record(row);\n return {\n entryId: str(d.entryId),\n subject: str(d.subject),\n to: str(d.to),\n toEmails: strList(d.toEmails),\n bodyPreview: str(d.bodyPreview),\n hasAttachments: bool(d.hasAttachments),\n lastModified: str(d.lastModified),\n folderPath: str(d.folderPath),\n };\n }),\n };\n}\n\n/** Send the named drafts, each proved to be this account's first. */\nexport async function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$sent = 0\n$failed = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n try {\n $draft = Resolve-OwnDraft $id 'send'\n try { $subject = [string]$draft.Subject } catch {}\n $draft.Send()\n $sent++\n } catch {\n $failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return {sent: num(e.sent), failed: itemFailures(e.failed)};\n}\n\n/**\n * Send every draft belonging to the account.\n *\n * The matching items are snapshotted before the first send: sending moves an\n * item out of Drafts, and sending during a live enumeration would skip every\n * other one.\n */\nexport async function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$drafts = @()\nforeach ($entry in $scan) {\n foreach ($it in $entry.folder.Items) {\n if (Test-DraftMatches $it $entry.includeUnstamped) { $drafts += $it }\n }\n}\n$sent = 0\n$failed = @()\nforeach ($draft in $drafts) {\n $entryId = ''\n $subject = ''\n try { $entryId = [string]$draft.EntryID } catch {}\n try { $subject = [string]$draft.Subject } catch {}\n try {\n $draft.Send()\n $sent++\n } catch {\n $failed += [PSCustomObject]@{ entryId = $entryId; subject = $subject; error = $_.Exception.Message }\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return {sent: num(e.sent), failed: itemFailures(e.failed)};\n}\n\n/**\n * Delete the named drafts, each proved to be this account's first. Outlook's\n * Delete() moves an item to Deleted Items, so a mistake stays recoverable.\n * Without the proof this would be a general \"delete any email by id\" call,\n * which is not what it is for.\n */\nexport async function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$deleted = 0\n$failed = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n try {\n $draft = Resolve-OwnDraft $id 'delete'\n try { $subject = [string]$draft.Subject } catch {}\n $draft.Delete()\n $deleted++\n } catch {\n $failed += [PSCustomObject]@{ entryId = $id; subject = $subject; error = $_.Exception.Message }\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ deleted = $deleted; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return {deleted: num(e.deleted), failed: itemFailures(e.failed)};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../src/windows/drafts.ts"],"names":[],"mappings":"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","sourcesContent":["// Drafts: which ones belong to an account, and listing, sending or deleting them.\nimport { psArray, psInt, runPowerShellJson } from './run';\nimport { accountScript, DELIVERY_STORE_PS, stampPs } from './scripts';\nimport { bool, itemFailurePs, itemFailures, num, record, str, strList, toArray } from './json';\nimport { FolderId } from '../mail';\nimport type { AccountRequest, EntryIdsRequest, ListDraftsRequest } from '../backend';\nimport type { DeleteDraftsResult, ItemFailure, ListDraftsResult, SendDraftsResult } from '../types';\n\n/**\n * Resolve the Drafts folders belonging to one account into `$scan`, and define\n * `Test-DraftMatches` over them. Emitted into every drafts script, so listing,\n * sending and deleting can never disagree about which drafts are this account's.\n *\n * Two Drafts folders can hold one account's drafts, so both are scanned:\n * - the account's OWN store's Drafts, where a draft composed in that mailbox\n * lands. A draft there with no explicit sending account still belongs to it.\n * - the DEFAULT store's Drafts, where a program's `Save()` files a draft\n * whatever account it sends from. Only drafts stamped with this account\n * match there; an unstamped one belongs to the default account.\n * When the two are the same folder it is scanned once, under the first rule.\n *\n * Non-mail items (meeting requests, reports) never match.\n */\nconst DRAFTS_SCAN_PS = `\n$scan = @()\n$seenFolders = @{}\n$homeDrafts = $null\ntry { $homeDrafts = $store.GetDefaultFolder(${psInt(FolderId.Drafts)}) } catch {}\nif ($homeDrafts -ne $null) {\n $seenFolders[\"$($homeDrafts.StoreID)|$($homeDrafts.EntryID)\"] = $true\n $scan += [PSCustomObject]@{ folder = $homeDrafts; includeUnstamped = $true }\n}\n$defaultDrafts = $null\ntry { $defaultDrafts = $ns.GetDefaultFolder(${psInt(FolderId.Drafts)}) } catch {}\nif ($defaultDrafts -ne $null -and -not $seenFolders.ContainsKey(\"$($defaultDrafts.StoreID)|$($defaultDrafts.EntryID)\")) {\n $scan += [PSCustomObject]@{ folder = $defaultDrafts; includeUnstamped = $false }\n}\nfunction Test-DraftMatches($item, [bool]$includeUnstamped) {\n if ($item.Class -ne 43) { return $false } # olMail only\n $sender = $null\n try { $sender = $item.SendUsingAccount } catch {}\n if ($sender -eq $null) { return $includeUnstamped }\n return ([string]$sender.SmtpAddress -ieq $target)\n}\n# An id is acted on only once it is PROVED to be one of this account's drafts:\n# resolved in one of the scanned folders' stores, sitting in that folder, and\n# bound to this account (or unbound, in its own store).\n$draftFolderRules = @{}\nforeach ($entry in $scan) { $draftFolderRules[[string]$entry.folder.EntryID] = $entry.includeUnstamped }\nfunction Resolve-OwnDraft([string]$id, [string]$verb) {\n $draft = $null\n foreach ($entry in $scan) {\n try { $draft = $ns.GetItemFromID($id, $entry.folder.StoreID) } catch { $draft = $null }\n if ($draft -ne $null) { break }\n }\n if ($draft -eq $null) { throw \"no such item in this account's Drafts folders\" }\n $parentId = ''\n try { $parentId = [string]$draft.Parent.EntryID } catch {}\n if (-not $draftFolderRules.ContainsKey($parentId)) { throw \"item is not in this account's Drafts folder - refusing to $verb\" }\n if (-not (Test-DraftMatches $draft $draftFolderRules[$parentId])) { throw \"draft is not bound to $target - refusing to $verb\" }\n return $draft\n}\n`;\n\nfunction draftsScript(account: string): string {\n return accountScript(account) + DELIVERY_STORE_PS + DRAFTS_SCAN_PS;\n}\n\n/** The account's drafts, newest first. Bodies are previewed, never returned whole. */\nexport async function listOutlookDrafts(request: ListDraftsRequest): Promise<ListDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$previewChars = ${psInt(request.previewChars)}\n$limit = ${psInt(request.limit)}\n$rows = @()\n$folderPaths = @()\nforeach ($entry in $scan) {\n $folderPath = ''\n try { $folderPath = [string]$entry.folder.FolderPath } catch {}\n $folderPaths += $folderPath\n foreach ($it in $entry.folder.Items) {\n if (-not (Test-DraftMatches $it $entry.includeUnstamped)) { continue }\n $addresses = @()\n try {\n foreach ($r in $it.Recipients) {\n $address = ''\n try { $address = [string]$r.Address } catch {}\n # An unresolved Exchange entry has a DN for an address; its name reads better.\n if ($address.StartsWith('/')) { try { $address = [string]$r.Name } catch {} }\n if ($address) { $addresses += $address }\n }\n } catch {}\n $preview = ''\n if ($previewChars -gt 0) {\n try { $preview = ([string]$it.Body -replace '\\\\s+', ' ').Trim() } catch {}\n if ($preview.Length -gt $previewChars) { $preview = $preview.Substring(0, $previewChars) }\n }\n $modified = ''\n try { $modified = ${stampPs('$it.LastModificationTime')} } catch {}\n $hasAttachments = $false\n try { $hasAttachments = ($it.Attachments.Count -gt 0) } catch {}\n $rows += [PSCustomObject]@{\n entryId = [string]$it.EntryID\n subject = [string]$it.Subject\n to = [string]$it.To\n toEmails = @($addresses)\n bodyPreview = $preview\n hasAttachments = $hasAttachments\n lastModified = $modified\n folderPath = $folderPath\n }\n }\n}\n$sorted = @($rows | Sort-Object -Property lastModified -Descending)\n$total = $sorted.Count\nif ($total -gt $limit) { $sorted = @($sorted[0..($limit - 1)]) }\nConvertTo-Json -Depth 4 -InputObject ([PSCustomObject]@{\n account = $target\n foldersScanned = @($folderPaths)\n count = $total\n truncated = ($total -gt $limit)\n drafts = @($sorted)\n})\n`, 'scan');\n const e = record(output);\n return {\n account: str(e.account) || request.account,\n foldersScanned: toArray(e.foldersScanned).map(str),\n count: num(e.count),\n truncated: bool(e.truncated),\n drafts: toArray(e.drafts).map(row => {\n const d = record(row);\n return {\n entryId: str(d.entryId),\n subject: str(d.subject),\n to: str(d.to),\n toEmails: strList(d.toEmails),\n bodyPreview: str(d.bodyPreview),\n hasAttachments: bool(d.hasAttachments),\n lastModified: str(d.lastModified),\n folderPath: str(d.folderPath),\n };\n }),\n };\n}\n\n/** Run `verb` over the named drafts of the account, each proved to be its own first. */\nasync function actOnDrafts(\n request: EntryIdsRequest,\n verb: 'send' | 'delete',\n): Promise<{ done: number; failed: ItemFailure[] }> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$done = 0\n$failed = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n try {\n $draft = Resolve-OwnDraft $id '${verb}'\n try { $subject = [string]$draft.Subject } catch {}\n $draft.${verb === 'send' ? 'Send' : 'Delete'}()\n $done++\n } catch {\n ${itemFailurePs('$id')}\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ done = $done; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return { done: num(e.done), failed: itemFailures(e.failed) };\n}\n\n/** Send the named drafts. */\nexport async function sendDrafts(request: EntryIdsRequest): Promise<SendDraftsResult> {\n const { done, failed } = await actOnDrafts(request, 'send');\n return { sent: done, failed };\n}\n\n/**\n * Send every draft belonging to the account.\n *\n * The matching items are snapshotted before the first send: sending moves an\n * item out of Drafts, and sending during a live enumeration would skip every\n * other one.\n */\nexport async function sendAllDrafts(request: AccountRequest): Promise<SendDraftsResult> {\n const output = await runPowerShellJson(`${draftsScript(request.account)}\n$drafts = @()\nforeach ($entry in $scan) {\n foreach ($it in $entry.folder.Items) {\n if (Test-DraftMatches $it $entry.includeUnstamped) { $drafts += $it }\n }\n}\n$sent = 0\n$failed = @()\nforeach ($draft in $drafts) {\n $entryId = ''\n $subject = ''\n try { $entryId = [string]$draft.EntryID } catch {}\n try { $subject = [string]$draft.Subject } catch {}\n try {\n $draft.Send()\n $sent++\n } catch {\n ${itemFailurePs('$entryId')}\n }\n}\nConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{ sent = $sent; failed = @($failed) })\n`, 'scan');\n const e = record(output);\n return { sent: num(e.sent), failed: itemFailures(e.failed) };\n}\n\n/**\n * Delete the named drafts. Outlook's Delete() moves an item to Deleted Items,\n * so a mistake stays recoverable. Without the proof that each is this\n * account's draft, this would be a general \"delete any email by id\" call,\n * which is not what it is for.\n */\nexport async function deleteOutlookDrafts(request: EntryIdsRequest): Promise<DeleteDraftsResult> {\n const { done, failed } = await actOnDrafts(request, 'delete');\n return { deleted: done, failed };\n}\n"]}
|
package/dist/windows/folders.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.moveOutlookEmails = moveOutlookEmails;
|
|
|
5
5
|
// Listing the folders under an Inbox, and filing mail into one.
|
|
6
6
|
const run_1 = require("./run");
|
|
7
7
|
const scripts_1 = require("./scripts");
|
|
8
|
-
const json_1 = require("
|
|
8
|
+
const json_1 = require("./json");
|
|
9
9
|
const mail_1 = require("../mail");
|
|
10
10
|
/** The folders under an account's Inbox, depth-first. */
|
|
11
11
|
async function listInboxFolders(request) {
|
|
@@ -60,7 +60,7 @@ foreach ($id in ${(0, run_1.psArray)(request.entryIds)}) {
|
|
|
60
60
|
[void]$it.Move($scope)
|
|
61
61
|
$moved++
|
|
62
62
|
} catch {
|
|
63
|
-
$
|
|
63
|
+
${(0, json_1.itemFailurePs)('$id')}
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
ConvertTo-Json -Depth 3 -InputObject ([PSCustomObject]@{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/windows/folders.ts"],"names":[],"mappings":";;AASA,4CA+BC;AAQD,8CA+BC;AA/ED,gEAAgE;AAChE,+BAA0D;AAC1D,uCAA8E;AAC9E,
|
|
1
|
+
{"version":3,"file":"folders.js","sourceRoot":"","sources":["../../src/windows/folders.ts"],"names":[],"mappings":";;AASA,4CA+BC;AAQD,8CA+BC;AA/ED,gEAAgE;AAChE,+BAA0D;AAC1D,uCAA8E;AAC9E,iCAAsF;AACtF,kCAAmC;AAInC,yDAAyD;AAClD,KAAK,UAAU,gBAAgB,CAAC,OAA2B;IAC9D,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAiB,EAAC,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,CAAC;EAC1E,2BAAiB;mCACgB,IAAA,WAAK,EAAC,eAAQ,CAAC,KAAK,CAAC;cAC1C,IAAA,WAAK,EAAC,OAAO,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;CAiBpC,EAAE,UAAU,CAAC,CAAC;IACX,OAAO,IAAA,cAAO,EAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC7B,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,GAAG,CAAC,CAAC;QACtB,OAAO;YACH,IAAI,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,IAAI,CAAC;YACjB,UAAU,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,UAAU,CAAC;YAC7B,SAAS,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,SAAS,CAAC;YAC3B,KAAK,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;SAC3B,CAAC;IACN,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,iBAAiB,CAAC,OAAoB;IACxD,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAiB,EAAC,GAAG,IAAA,uBAAa,EAAC,OAAO,CAAC,OAAO,CAAC;EAC1E,2BAAiB;EACjB,IAAA,yBAAe,EAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,eAAe,CAAC;;;kBAG7D,IAAA,aAAO,EAAC,OAAO,CAAC,QAAQ,CAAC;;;;;;;;UAQjC,IAAA,oBAAa,EAAC,KAAK,CAAC;;;;;;;;;CAS7B,EAAE,MAAM,CAAC,CAAC;IACP,MAAM,CAAC,GAAG,IAAA,aAAM,EAAC,MAAM,CAAC,CAAC;IACzB,OAAO;QACH,UAAU,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,UAAU,CAAC;QAC7B,aAAa,EAAE,IAAA,WAAI,EAAC,CAAC,CAAC,aAAa,CAAC;QACpC,KAAK,EAAE,IAAA,UAAG,EAAC,CAAC,CAAC,KAAK,CAAC;QACnB,MAAM,EAAE,IAAA,mBAAY,EAAC,CAAC,CAAC,MAAM,CAAC;KACjC,CAAC;AACN,CAAC","sourcesContent":["// Listing the folders under an Inbox, and filing mail into one.\nimport { psArray, psInt, runPowerShellJson } from './run';\nimport { accountScript, DELIVERY_STORE_PS, mailScopeScript } from './scripts';\nimport { bool, itemFailurePs, itemFailures, num, record, str, toArray } from './json';\nimport { FolderId } from '../mail';\nimport type { ListFoldersRequest, MoveRequest } from '../backend';\nimport type { InboxFolderInfo, MoveEmailsResult } from '../types';\n\n/** The folders under an account's Inbox, depth-first. */\nexport async function listInboxFolders(request: ListFoldersRequest): Promise<InboxFolderInfo[]> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n$inbox = $store.GetDefaultFolder(${psInt(FolderId.Inbox)})\n$maxDepth = ${psInt(request.maxDepth)}\n$rows = New-Object System.Collections.ArrayList\nfunction Add-Folders($folder, [int]$level) {\n foreach ($f in $folder.Folders) {\n $itemCount = 0\n try { $itemCount = [int]$f.Items.Count } catch {}\n [void]$rows.Add([PSCustomObject]@{\n name = [string]$f.Name\n folderPath = [string]$f.FolderPath\n itemCount = $itemCount\n depth = $level\n })\n if ($level -lt $maxDepth) { Add-Folders $f ($level + 1) }\n }\n}\nAdd-Folders $inbox 1\nConvertTo-Json -Depth 3 -InputObject @($rows)\n`, 'standard');\n return toArray(output).map(row => {\n const e = record(row);\n return {\n name: str(e.name),\n folderPath: str(e.folderPath),\n itemCount: num(e.itemCount),\n depth: num(e.depth) || 1,\n };\n });\n}\n\n/**\n * File emails into a folder of the account: one under the Inbox, or a\n * well-known one by name. Moving to Deleted Items is how mail is deleted\n * recoverably. Moving rewrites each item's entry id, so the ids passed in are\n * dead afterwards.\n */\nexport async function moveOutlookEmails(request: MoveRequest): Promise<MoveEmailsResult> {\n const output = await runPowerShellJson(`${accountScript(request.account)}\n${DELIVERY_STORE_PS}\n${mailScopeScript(request.folder, request.folderLabel, request.createIfMissing)}\n$moved = 0\n$failed = @()\nforeach ($id in ${psArray(request.entryIds)}) {\n $subject = ''\n try {\n $it = $ns.GetItemFromID($id, $store.StoreID)\n try { $subject = [string]$it.Subject } catch {}\n [void]$it.Move($scope)\n $moved++\n } catch {\n ${itemFailurePs('$id')}\n }\n}\nConvertTo-Json -Depth 3 -InputObject ([PSCustomObject]@{\n folderPath = [string]$scope.FolderPath\n folderCreated = $scopeCreated\n moved = $moved\n failed = @($failed)\n})\n`, 'scan');\n const e = record(output);\n return {\n folderPath: str(e.folderPath),\n folderCreated: bool(e.folderCreated),\n moved: num(e.moved),\n failed: itemFailures(e.failed),\n };\n}\n"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ItemFailure } from '../types';
|
|
1
2
|
/** One value as a list: an array stays, null and undefined become empty, anything else wraps. */
|
|
2
3
|
export declare function toArray(value: unknown): unknown[];
|
|
3
4
|
/** A value as a plain object, for reading fields off one row. */
|
|
@@ -9,9 +10,13 @@ export declare function bool(value: unknown): boolean;
|
|
|
9
10
|
export declare function strList(value: unknown): string[];
|
|
10
11
|
/** A base64 field decoded as UTF-8 — how scripts pass bodies that must survive intact. */
|
|
11
12
|
export declare function base64Text(value: unknown): string;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
/**
|
|
14
|
+
* The PowerShell that appends one item's failure to `$failed`, inside the
|
|
15
|
+
* `catch` of a per-item loop: the row `itemFailures` reads back, written once
|
|
16
|
+
* so no script can name its fields differently. `idVariable` holds the item's
|
|
17
|
+
* id. `$subject` must be reset before each item's `try`, or a failure reports
|
|
18
|
+
* the previous item's subject.
|
|
19
|
+
*/
|
|
20
|
+
export declare function itemFailurePs(idVariable: '$id' | '$entryId'): string;
|
|
21
|
+
/** A list of per-item failures, as `itemFailurePs` writes them. */
|
|
22
|
+
export declare function itemFailures(value: unknown): ItemFailure[];
|
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Coercing the JSON a PowerShell script prints into the package's types.
|
|
3
|
-
//
|
|
4
|
-
// ConvertTo-Json has two habits a caller must never see: it unwraps a
|
|
5
|
-
// single-element array into a bare object, and it renders an empty one as
|
|
6
|
-
// nothing at all. Every list crossing that boundary goes through `toArray`, and
|
|
7
|
-
// every scalar through a coercion that survives a missing property.
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
3
|
exports.toArray = toArray;
|
|
10
4
|
exports.record = record;
|
|
@@ -13,6 +7,7 @@ exports.num = num;
|
|
|
13
7
|
exports.bool = bool;
|
|
14
8
|
exports.strList = strList;
|
|
15
9
|
exports.base64Text = base64Text;
|
|
10
|
+
exports.itemFailurePs = itemFailurePs;
|
|
16
11
|
exports.itemFailures = itemFailures;
|
|
17
12
|
/** One value as a list: an array stays, null and undefined become empty, anything else wraps. */
|
|
18
13
|
function toArray(value) {
|
|
@@ -41,7 +36,17 @@ function strList(value) {
|
|
|
41
36
|
function base64Text(value) {
|
|
42
37
|
return Buffer.from(str(value), 'base64').toString('utf8');
|
|
43
38
|
}
|
|
44
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The PowerShell that appends one item's failure to `$failed`, inside the
|
|
41
|
+
* `catch` of a per-item loop: the row `itemFailures` reads back, written once
|
|
42
|
+
* so no script can name its fields differently. `idVariable` holds the item's
|
|
43
|
+
* id. `$subject` must be reset before each item's `try`, or a failure reports
|
|
44
|
+
* the previous item's subject.
|
|
45
|
+
*/
|
|
46
|
+
function itemFailurePs(idVariable) {
|
|
47
|
+
return `$failed += [PSCustomObject]@{ entryId = ${idVariable}; subject = $subject; error = $_.Exception.Message }`;
|
|
48
|
+
}
|
|
49
|
+
/** A list of per-item failures, as `itemFailurePs` writes them. */
|
|
45
50
|
function itemFailures(value) {
|
|
46
51
|
return toArray(value).map(row => {
|
|
47
52
|
const e = record(row);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/windows/json.ts"],"names":[],"mappings":";;AAUA,0BAEC;AAGD,wBAIC;AAED,kBAEC;AAED,kBAEC;AAED,oBAEC;AAGD,0BAEC;AAGD,gCAEC;AASD,sCAEC;AAGD,oCAKC;AAnDD,iGAAiG;AACjG,SAAgB,OAAO,CAAC,KAAc;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC;AAED,iEAAiE;AACjE,SAAgB,MAAM,CAAC,KAAc;IACjC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QACvE,CAAC,CAAC,KAAgC;QAClC,CAAC,CAAC,EAAE,CAAC;AACb,CAAC;AAED,SAAgB,GAAG,CAAC,KAAc;IAC9B,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,SAAgB,GAAG,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAgB,IAAI,CAAC,KAAc;IAC/B,OAAO,KAAK,KAAK,IAAI,CAAC;AAC1B,CAAC;AAED,mCAAmC;AACnC,SAAgB,OAAO,CAAC,KAAc;IAClC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACnD,CAAC;AAED,0FAA0F;AAC1F,SAAgB,UAAU,CAAC,KAAc;IACrC,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,UAA8B;IACxD,OAAO,2CAA2C,UAAU,sDAAsD,CAAC;AACvH,CAAC;AAED,mEAAmE;AACnE,SAAgB,YAAY,CAAC,KAAc;IACvC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;IACrF,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["// Coercing the JSON a PowerShell script prints into the package's types — the\n// Windows counterpart of the record framing in mac/run.ts.\n//\n// ConvertTo-Json has two habits a caller must never see: it unwraps a\n// single-element array into a bare object, and it renders an empty one as\n// nothing at all. Every list crossing that boundary goes through `toArray`, and\n// every scalar through a coercion that survives a missing property.\nimport type { ItemFailure } from '../types';\n\n/** One value as a list: an array stays, null and undefined become empty, anything else wraps. */\nexport function toArray(value: unknown): unknown[] {\n return Array.isArray(value) ? value : value == null ? [] : [value];\n}\n\n/** A value as a plain object, for reading fields off one row. */\nexport function record(value: unknown): Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nexport function str(value: unknown): string {\n return value == null ? '' : String(value);\n}\n\nexport function num(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nexport function bool(value: unknown): boolean {\n return value === true;\n}\n\n/** A list of non-empty strings. */\nexport function strList(value: unknown): string[] {\n return toArray(value).map(str).filter(Boolean);\n}\n\n/** A base64 field decoded as UTF-8 — how scripts pass bodies that must survive intact. */\nexport function base64Text(value: unknown): string {\n return Buffer.from(str(value), 'base64').toString('utf8');\n}\n\n/**\n * The PowerShell that appends one item's failure to `$failed`, inside the\n * `catch` of a per-item loop: the row `itemFailures` reads back, written once\n * so no script can name its fields differently. `idVariable` holds the item's\n * id. `$subject` must be reset before each item's `try`, or a failure reports\n * the previous item's subject.\n */\nexport function itemFailurePs(idVariable: '$id' | '$entryId'): string {\n return `$failed += [PSCustomObject]@{ entryId = ${idVariable}; subject = $subject; error = $_.Exception.Message }`;\n}\n\n/** A list of per-item failures, as `itemFailurePs` writes them. */\nexport function itemFailures(value: unknown): ItemFailure[] {\n return toArray(value).map(row => {\n const e = record(row);\n return { entryId: str(e.entryId), subject: str(e.subject), error: str(e.error) };\n });\n}\n"]}
|
package/dist/windows/read.js
CHANGED
|
@@ -9,7 +9,7 @@ exports.openOutlookEmail = openOutlookEmail;
|
|
|
9
9
|
// selected in the running Outlook.
|
|
10
10
|
const run_1 = require("./run");
|
|
11
11
|
const scripts_1 = require("./scripts");
|
|
12
|
-
const json_1 = require("
|
|
12
|
+
const json_1 = require("./json");
|
|
13
13
|
const mail_1 = require("../mail");
|
|
14
14
|
const errors_1 = require("../errors");
|
|
15
15
|
/**
|
|
@@ -28,6 +28,12 @@ function dateProperty(rootId) {
|
|
|
28
28
|
* escaping in two languages intact.
|
|
29
29
|
*/
|
|
30
30
|
const BODY_B64_PS = `[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([string]$bodyRaw))`;
|
|
31
|
+
/**
|
|
32
|
+
* The item's attachment file names as `$attachmentNames`, unnamed ones
|
|
33
|
+
* included: a name's position is its attachment's. Requires `$item`.
|
|
34
|
+
*/
|
|
35
|
+
const ATTACHMENT_NAMES_PS = `$attachmentNames = @()
|
|
36
|
+
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}`;
|
|
31
37
|
/** Recent mail from the Inbox root or one folder, newest first. */
|
|
32
38
|
async function readInboxEmails(request) {
|
|
33
39
|
const dateProp = dateProperty(request.folder.rootId);
|
|
@@ -37,7 +43,7 @@ ${scripts_1.NAMED_STORE_PS}
|
|
|
37
43
|
${(0, scripts_1.mailScopeScript)(request.folder, request.folderLabel)}
|
|
38
44
|
$scopePath = [string]$scope.FolderPath
|
|
39
45
|
${(0, scripts_1.cutoffScript)(request.daysBack)}
|
|
40
|
-
${(0, scripts_1.itemsSinceScript)('scope', dateProp
|
|
46
|
+
${(0, scripts_1.itemsSinceScript)('scope', dateProp)}
|
|
41
47
|
$filtered.Sort('[${dateProp}]', $true)
|
|
42
48
|
$limit = ${(0, run_1.psInt)(request.limit)}
|
|
43
49
|
$previewChars = ${(0, run_1.psInt)(request.previewChars)}
|
|
@@ -57,8 +63,7 @@ for ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {
|
|
|
57
63
|
try { $bodyText = [string]$item.Body } catch {}
|
|
58
64
|
$preview = if ($bodyText.Length -gt $previewChars) { $bodyText.Substring(0, $previewChars) } else { $bodyText }
|
|
59
65
|
}
|
|
60
|
-
|
|
61
|
-
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
66
|
+
${ATTACHMENT_NAMES_PS}
|
|
62
67
|
$who = ''
|
|
63
68
|
$whoEmail = ''
|
|
64
69
|
if (${(0, run_1.psBool)(outgoing)}) {
|
|
@@ -76,7 +81,7 @@ for ($i = 1; $i -le $count -and $rows.Count -lt $limit; $i++) {
|
|
|
76
81
|
subject = ([string]$item.Subject).Trim()
|
|
77
82
|
senderName = $who
|
|
78
83
|
senderEmail = $whoEmail
|
|
79
|
-
receivedTime = if ($stamp -ne $null) { $
|
|
84
|
+
receivedTime = if ($stamp -ne $null) { ${(0, scripts_1.stampPs)('$stamp')} } else { '' }
|
|
80
85
|
bodyPreview = $preview
|
|
81
86
|
attachmentNames = @($attachmentNames)
|
|
82
87
|
}
|
|
@@ -118,11 +123,10 @@ if ($class -ne 43 -and $class -ne 46) {
|
|
|
118
123
|
}
|
|
119
124
|
$bodyRaw = ''
|
|
120
125
|
try { $bodyRaw = [string]$item.Body } catch {}
|
|
121
|
-
$
|
|
122
|
-
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
126
|
+
${ATTACHMENT_NAMES_PS}
|
|
123
127
|
${scripts_1.SENDER_SMTP_PS}
|
|
124
128
|
$received = ''
|
|
125
|
-
try { $received = $item.ReceivedTime
|
|
129
|
+
try { $received = ${(0, scripts_1.stampPs)('$item.ReceivedTime')} } catch {}
|
|
126
130
|
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
127
131
|
entryId = [string]$item.EntryID
|
|
128
132
|
subject = ([string]$item.Subject).Trim()
|
|
@@ -226,7 +230,7 @@ $seen = @{}
|
|
|
226
230
|
for ($fx = 0; $fx -lt $folders.Count; $fx++) {
|
|
227
231
|
if (-not $inScope[$fx]) { continue }
|
|
228
232
|
$folder = $folders[$fx]
|
|
229
|
-
${(0, scripts_1.itemsSinceScript)('folder', 'ReceivedTime'
|
|
233
|
+
${(0, scripts_1.itemsSinceScript)('folder', 'ReceivedTime').trim()}
|
|
230
234
|
if ($subjectDasl) {
|
|
231
235
|
try {
|
|
232
236
|
$candidate = $filtered.Restrict($subjectQuery)
|
|
@@ -254,8 +258,7 @@ for ($fx = 0; $fx -lt $folders.Count; $fx++) {
|
|
|
254
258
|
$received = $null
|
|
255
259
|
try { $received = $item.ReceivedTime } catch {}
|
|
256
260
|
if ($received -ne $null -and $received -lt $cutoff) { continue }
|
|
257
|
-
|
|
258
|
-
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
261
|
+
${ATTACHMENT_NAMES_PS}
|
|
259
262
|
if ($requireAttachment -and $attachmentNames.Count -eq 0) { continue }
|
|
260
263
|
$bodyRaw = ''
|
|
261
264
|
if ($includeBody) { try { $bodyRaw = [string]$item.Body } catch {} }
|
|
@@ -266,7 +269,7 @@ ${scripts_1.SENDER_SMTP_PS}
|
|
|
266
269
|
subject = $subject
|
|
267
270
|
senderName = [string]$item.SenderName
|
|
268
271
|
senderEmail = $senderSmtp
|
|
269
|
-
receivedTime = if ($received -ne $null) { $
|
|
272
|
+
receivedTime = if ($received -ne $null) { ${(0, scripts_1.stampPs)('$received')} } else { '' }
|
|
270
273
|
body = ${BODY_B64_PS}
|
|
271
274
|
attachmentNames = @($attachmentNames)
|
|
272
275
|
folderPath = $folderPath
|
|
@@ -311,15 +314,16 @@ if ($item -eq $null) {
|
|
|
311
314
|
if ([string]$item.MessageClass -notlike 'IPM.Note*') {
|
|
312
315
|
throw "${(0, errors_1.failureTag)('INVALID_REQUEST')}The item selected in Outlook is not an email."
|
|
313
316
|
}
|
|
314
|
-
$
|
|
315
|
-
try { foreach ($att in $item.Attachments) { $attachmentNames += [string]$att.FileName } } catch {}
|
|
317
|
+
${ATTACHMENT_NAMES_PS}
|
|
316
318
|
$bodyRaw = ''
|
|
317
319
|
try { $bodyRaw = [string]$item.Body } catch {}
|
|
318
320
|
$storeId = ''
|
|
319
321
|
try { $storeId = [string]$item.Parent.Store.StoreID } catch {}
|
|
322
|
+
$folderPath = ''
|
|
323
|
+
try { $folderPath = [string]$item.Parent.FolderPath } catch {}
|
|
320
324
|
${scripts_1.SENDER_SMTP_PS}
|
|
321
325
|
$received = ''
|
|
322
|
-
try { $received = $item.ReceivedTime
|
|
326
|
+
try { $received = ${(0, scripts_1.stampPs)('$item.ReceivedTime')} } catch {}
|
|
323
327
|
ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
324
328
|
entryId = [string]$item.EntryID
|
|
325
329
|
storeId = $storeId
|
|
@@ -329,6 +333,7 @@ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
|
329
333
|
receivedTime = $received
|
|
330
334
|
body = ${BODY_B64_PS}
|
|
331
335
|
attachmentNames = @($attachmentNames)
|
|
336
|
+
folderPath = $folderPath
|
|
332
337
|
})
|
|
333
338
|
`, 'quick');
|
|
334
339
|
const e = (0, json_1.record)(output);
|
|
@@ -340,7 +345,9 @@ ConvertTo-Json -Compress -Depth 3 -InputObject ([PSCustomObject]@{
|
|
|
340
345
|
senderEmail: (0, json_1.str)(e.senderEmail),
|
|
341
346
|
receivedTime: (0, json_1.str)(e.receivedTime),
|
|
342
347
|
body: (0, json_1.base64Text)(e.body),
|
|
343
|
-
|
|
348
|
+
// Unnamed attachments keep their place, as in every other reader here.
|
|
349
|
+
attachmentNames: (0, json_1.toArray)(e.attachmentNames).map(json_1.str),
|
|
350
|
+
folderPath: (0, json_1.str)(e.folderPath),
|
|
344
351
|
};
|
|
345
352
|
}
|
|
346
353
|
/**
|