@dawnlit/outlook-bridge 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +150 -183
  2. package/dist/backend.d.ts +151 -0
  3. package/dist/backend.js +3 -0
  4. package/dist/backend.js.map +1 -0
  5. package/dist/bridge.d.ts +56 -0
  6. package/dist/bridge.js +507 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/errors.d.ts +81 -43
  9. package/dist/errors.js +98 -52
  10. package/dist/errors.js.map +1 -1
  11. package/dist/index.d.ts +10 -10
  12. package/dist/index.js +25 -52
  13. package/dist/index.js.map +1 -1
  14. package/dist/mac/accounts.d.ts +7 -2
  15. package/dist/mac/accounts.js +23 -6
  16. package/dist/mac/accounts.js.map +1 -1
  17. package/dist/mac/attachments.d.ts +5 -25
  18. package/dist/mac/attachments.js +40 -99
  19. package/dist/mac/attachments.js.map +1 -1
  20. package/dist/mac/bounces.d.ts +12 -27
  21. package/dist/mac/bounces.js +95 -112
  22. package/dist/mac/bounces.js.map +1 -1
  23. package/dist/mac/cleanup.d.ts +11 -25
  24. package/dist/mac/cleanup.js +50 -76
  25. package/dist/mac/cleanup.js.map +1 -1
  26. package/dist/mac/drafts.d.ts +13 -34
  27. package/dist/mac/drafts.js +95 -169
  28. package/dist/mac/drafts.js.map +1 -1
  29. package/dist/mac/folders.d.ts +10 -21
  30. package/dist/mac/folders.js +33 -47
  31. package/dist/mac/folders.js.map +1 -1
  32. package/dist/mac/index.d.ts +2 -22
  33. package/dist/mac/index.js +19 -81
  34. package/dist/mac/index.js.map +1 -1
  35. package/dist/mac/profile.d.ts +13 -0
  36. package/dist/mac/profile.js +149 -0
  37. package/dist/mac/profile.js.map +1 -0
  38. package/dist/mac/read.d.ts +18 -38
  39. package/dist/mac/read.js +154 -210
  40. package/dist/mac/read.js.map +1 -1
  41. package/dist/mac/run.d.ts +35 -39
  42. package/dist/mac/run.js +97 -102
  43. package/dist/mac/run.js.map +1 -1
  44. package/dist/mac/scripts.d.ts +78 -86
  45. package/dist/mac/scripts.js +125 -115
  46. package/dist/mac/scripts.js.map +1 -1
  47. package/dist/mac/send.d.ts +10 -20
  48. package/dist/mac/send.js +64 -86
  49. package/dist/mac/send.js.map +1 -1
  50. package/dist/mac/signatures.d.ts +2 -2
  51. package/dist/mac/signatures.js +9 -11
  52. package/dist/mac/signatures.js.map +1 -1
  53. package/dist/mac/templates.d.ts +14 -26
  54. package/dist/mac/templates.js +75 -113
  55. package/dist/mac/templates.js.map +1 -1
  56. package/dist/mail.d.ts +77 -45
  57. package/dist/mail.js +166 -73
  58. package/dist/mail.js.map +1 -1
  59. package/dist/runtime.d.ts +76 -43
  60. package/dist/runtime.js +103 -48
  61. package/dist/runtime.js.map +1 -1
  62. package/dist/shared/args.d.ts +48 -0
  63. package/dist/shared/args.js +216 -0
  64. package/dist/shared/args.js.map +1 -0
  65. package/dist/shared/attachmentMatch.d.ts +28 -16
  66. package/dist/shared/attachmentMatch.js +60 -22
  67. package/dist/shared/attachmentMatch.js.map +1 -1
  68. package/dist/shared/bounceReport.d.ts +11 -0
  69. package/dist/shared/bounceReport.js +45 -0
  70. package/dist/shared/bounceReport.js.map +1 -0
  71. package/dist/shared/bounceRules.d.ts +38 -10
  72. package/dist/shared/bounceRules.js +50 -32
  73. package/dist/shared/bounceRules.js.map +1 -1
  74. package/dist/shared/exec.d.ts +21 -0
  75. package/dist/shared/exec.js +47 -0
  76. package/dist/shared/exec.js.map +1 -0
  77. package/dist/shared/json.d.ts +12 -6
  78. package/dist/shared/json.js +29 -23
  79. package/dist/shared/json.js.map +1 -1
  80. package/dist/shared/locate.d.ts +39 -0
  81. package/dist/shared/locate.js +129 -0
  82. package/dist/shared/locate.js.map +1 -0
  83. package/dist/shared/replyBody.d.ts +42 -32
  84. package/dist/shared/replyBody.js +84 -71
  85. package/dist/shared/replyBody.js.map +1 -1
  86. package/dist/templateBody.d.ts +63 -0
  87. package/dist/{outlookTemplateSections.js → templateBody.js} +72 -72
  88. package/dist/templateBody.js.map +1 -0
  89. package/dist/types.d.ts +360 -167
  90. package/dist/types.js +8 -9
  91. package/dist/types.js.map +1 -1
  92. package/dist/windows/accounts.d.ts +7 -12
  93. package/dist/windows/accounts.js +19 -34
  94. package/dist/windows/accounts.js.map +1 -1
  95. package/dist/windows/attachments.d.ts +9 -24
  96. package/dist/windows/attachments.js +94 -119
  97. package/dist/windows/attachments.js.map +1 -1
  98. package/dist/windows/bounces.d.ts +13 -26
  99. package/dist/windows/bounces.js +168 -200
  100. package/dist/windows/bounces.js.map +1 -1
  101. package/dist/windows/cleanup.d.ts +19 -23
  102. package/dist/windows/cleanup.js +92 -111
  103. package/dist/windows/cleanup.js.map +1 -1
  104. package/dist/windows/drafts.d.ts +16 -35
  105. package/dist/windows/drafts.js +167 -253
  106. package/dist/windows/drafts.js.map +1 -1
  107. package/dist/windows/folders.d.ts +8 -13
  108. package/dist/windows/folders.js +44 -52
  109. package/dist/windows/folders.js.map +1 -1
  110. package/dist/windows/index.d.ts +2 -27
  111. package/dist/windows/index.js +11 -66
  112. package/dist/windows/index.js.map +1 -1
  113. package/dist/windows/read.d.ts +24 -60
  114. package/dist/windows/read.js +261 -306
  115. package/dist/windows/read.js.map +1 -1
  116. package/dist/windows/run.d.ts +39 -39
  117. package/dist/windows/run.js +151 -111
  118. package/dist/windows/run.js.map +1 -1
  119. package/dist/windows/scripts.d.ts +65 -55
  120. package/dist/windows/scripts.js +109 -85
  121. package/dist/windows/scripts.js.map +1 -1
  122. package/dist/windows/send.d.ts +6 -11
  123. package/dist/windows/send.js +68 -88
  124. package/dist/windows/send.js.map +1 -1
  125. package/dist/windows/signatures.d.ts +4 -10
  126. package/dist/windows/signatures.js +30 -37
  127. package/dist/windows/signatures.js.map +1 -1
  128. package/dist/windows/templates.d.ts +12 -18
  129. package/dist/windows/templates.js +122 -149
  130. package/dist/windows/templates.js.map +1 -1
  131. package/package.json +4 -3
  132. package/dist/OutlookService.d.ts +0 -43
  133. package/dist/OutlookService.js +0 -137
  134. package/dist/OutlookService.js.map +0 -1
  135. package/dist/outlookTemplateSections.d.ts +0 -62
  136. package/dist/outlookTemplateSections.js.map +0 -1
  137. package/dist/shared/capabilities.d.ts +0 -3
  138. package/dist/shared/capabilities.js +0 -9
  139. package/dist/shared/capabilities.js.map +0 -1
@@ -1,106 +1,106 @@
1
- import type { MailFolderRef } from '../types';
1
+ import type { ItemFailure, MailFolderRef } from '../types';
2
2
  /**
3
- * Resolve the account whose SMTP address matches, or raise. Emitted into a
4
- * `tell application "Microsoft Outlook"` block; binds `targetAcct`.
3
+ * An account as the generated AppleScript reaches it.
5
4
  *
6
- * `every account` errors even in legacy mode, so probe the typed classes. A
7
- * `whose email address is ...` filter can't be used either: `email address` is
8
- * also a class name, and AppleScript resolves it as one ("into type specifier").
9
- *
10
- * The thrown sentence is the one `classifyRunFailure` recognises to raise
11
- * `AccountNotFoundError`, so its wording is load-bearing.
5
+ * `folderIds` carries what Outlook's profile database knows about the mailbox's
6
+ * well-known folders — the only handle on a mailbox the dictionary publishes no
7
+ * account object for (see profile.ts). It is filled in for every account the
8
+ * profile lists, because the generated script chooses between the account
9
+ * object and the folder id at run time; nothing here has to predict which kinds
10
+ * of account a given Outlook build exposes.
12
11
  */
13
- export declare function accountLookupSnippet(emailAccount: string): string;
12
+ export interface MacAccount {
13
+ readonly emailAccount: string;
14
+ readonly folderIds?: Readonly<Record<string, number>>;
15
+ }
16
+ /** Look an address up in Outlook's profile database, for the folder ids it can add. */
17
+ export declare function resolveMacAccount(emailAccount: string): Promise<MacAccount>;
14
18
  /**
15
- * Outlook for Mac's dictionary term for each well-known root, keyed by the
16
- * olDefaultFolders id `mailFolderRef` resolves.
19
+ * Resolve the account whose address matches into `targetAcct`, inside a
20
+ * `tell application "Microsoft Outlook"` block.
21
+ *
22
+ * `every account` errors even in legacy mode, so the typed classes are probed.
23
+ * A `whose email address is …` filter can't be used either: `email address` is
24
+ * also a class name, and AppleScript resolves it as one.
17
25
  *
18
- * Verified against the running app. The terms are NOT the ones the Windows ids
19
- * suggest — `sent mail` and `junk email` do not compile at all, while
20
- * `sent items` and `junk mail` do, and a bad term fails the whole script at
21
- * compile time rather than at the offending line.
26
+ * A mailbox the dictionary publishes no account object for leaves `targetAcct`
27
+ * as `missing value` rather than failing, provided the profile gave a folder id
28
+ * to reach it by. `needsAccountObject` is for composing mail, which can't work
29
+ * without the object: the account a message is sent from can only be set from
30
+ * it, and a message composed without it goes out from the default mailbox.
31
+ */
32
+ export declare function accountLookupSnippet(acct: MacAccount, needsAccountObject?: boolean): string;
33
+ /**
34
+ * Bind `variable` to one of the account's well-known folders: through the
35
+ * account object when the probe found one, else by the folder's own id. `mail
36
+ * folder id N` resolves against the application rather than an account, and N is
37
+ * the id Outlook's profile database records for that folder.
22
38
  */
23
- export declare const MAC_ROOT_TERMS: Record<number, string>;
24
- /** The date property a folder's items actually carry. */
39
+ export declare function rootFolderSnippet(acct: MacAccount, term: string, variable: string): string;
40
+ /**
41
+ * Outlook for Mac's dictionary term for each well-known root, by olDefaultFolders
42
+ * id. Verified against the running app: the terms are NOT what the Windows names
43
+ * suggest — `sent mail` and `junk email` don't compile, `sent items` and `junk
44
+ * mail` do — and one bad term fails the whole script at compile time.
45
+ */
46
+ export declare const MAC_ROOT_TERMS: Readonly<Record<number, string>>;
47
+ /** The date property a root's items carry. */
25
48
  export declare function dateProperty(rootId: number): string;
26
49
  /**
27
- * The Windows `FolderPath` shape (`\\mailbox\Inbox\Invoices`), built here rather
28
- * than in AppleScript — escaping backslashes through a template literal and then
29
- * an AppleScript literal is unreadable, and TS already knows the parts.
50
+ * The Windows folder-path shape (`\\mailbox\Inbox\Invoices`), assembled in
51
+ * TypeScript: escaping backslashes through a template literal and then an
52
+ * AppleScript literal is unreadable, and the parts are known here anyway.
30
53
  */
31
- export declare function macFolderPath(emailAccount: string, rootLabel: string, segments: string[]): string;
32
- /** The dictionary term for a root, or a NOT_IMPLEMENTED naming what was asked. */
54
+ export declare function macFolderPath(emailAccount: string, rootLabel: string, segments: readonly string[]): string;
55
+ /** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */
33
56
  export declare function rootTerm(ref: MailFolderRef, folderLabel?: string): string;
34
57
  /**
35
- * Emit the AppleScript that resolves `scopeFolder` from `targetAcct`, walking a
36
- * well-known root down through any further path segments. Also binds
37
- * `scopeCreated`.
58
+ * Resolve `scopeFolder` from `targetAcct`: a well-known root walked down through
59
+ * any further segments. Also binds `scopeCreated`.
38
60
  *
39
- * Folder names are compared with `is`, which is case-insensitive in AppleScript —
40
- * matching the Windows walk's `-ieq` so 'invoices' finds 'Invoices' on both.
41
- * A missing segment errors by name instead of falling back to the root: silently
42
- * returning the Inbox for a caller that scoped to one folder hands back the wrong
43
- * emails under a name that says otherwise. `createMissing` builds the whole
44
- * chain instead, so a nested destination is one call.
61
+ * Names compare with `is`, which is case-insensitive in AppleScript, matching
62
+ * the Windows walk. A missing segment fails as NOT_FOUND rather than falling
63
+ * back to the root; `createMissing` builds the chain instead.
45
64
  */
46
- export declare function mailScopeSnippet(ref: MailFolderRef, folderLabel?: string, createMissing?: boolean): string;
65
+ export declare function mailScopeSnippet(acct: MacAccount, ref: MailFolderRef, folderLabel?: string, createMissing?: boolean): string;
47
66
  /**
48
- * The message-id text for a script, or an InvalidRequestError.
67
+ * The message id text for a script, or INVALID_REQUEST.
49
68
  *
50
- * Outlook for Mac ids are small integers; a Windows MAPI EntryID is rejected up
51
- * front rather than left to fail as a confusing "not found", because a caller
52
- * that persisted one from a Windows run needs to be told exactly that.
69
+ * Outlook for Mac ids are small integers; a Windows EntryID is refused up front
70
+ * rather than left to fail as a confusing "not found", because a caller that
71
+ * persisted one from a Windows run needs to be told exactly that.
53
72
  */
54
73
  export declare function macMessageId(entryId: string): string;
55
74
  /**
56
- * Split ids into the ones a script can look up and the ones that can only fail.
57
- *
58
- * The per-id operations report failures rather than throwing, so an unusable id
59
- * belongs in the result's `failed` list beside a genuine lookup miss — not as an
60
- * exception that discards the ids that would have worked.
75
+ * Split ids into those a script can look up and those that can only fail. A
76
+ * batch reports an unusable id beside a genuine miss rather than throwing and
77
+ * discarding the ids that would have worked.
61
78
  */
62
79
  export declare function partitionMessageIds(entryIds: readonly string[]): {
63
80
  valid: string[];
64
- invalid: {
65
- entryId: string;
66
- error: string;
67
- }[];
81
+ invalid: ItemFailure[];
68
82
  };
69
83
  /**
70
- * Resolve `theMsg` from a macOS message id, or raise.
71
- *
72
- * `message id N` resolves against the application rather than one folder, so the
73
- * message is found wherever it currently sits — including a subfolder.
84
+ * Resolve `variable` from a message id, or fail as NOT_FOUND. `message id N`
85
+ * resolves against the application, so the message is found wherever it sits.
74
86
  */
75
87
  export declare function messageLookupSnippet(id: string, variable?: string): string;
76
88
  /**
77
- * The sender's display name and address, as `sndName` / `sndAddr`.
78
- *
79
- * `sender` yields a record, and `address of sender of m` fails to coerce, so the
80
- * record has to be bound first. `name` is absent when there is no display name.
89
+ * The sender's display name and address as `sndName` / `sndAddr`. `sender` is a
90
+ * record, and `address of sender of m` doesn't coerce, so the record is bound
91
+ * first.
81
92
  */
82
93
  export declare function senderSnippet(messageVariable?: string, indent?: string): string;
83
94
  /**
84
- * Everything a reader wants off ONE message already bound to `theMsg`, and the
85
- * row it produces — emitted together so the read order and the positions the
86
- * TypeScript decoders index into cannot drift apart.
87
- *
88
- * Three callers share it (`readEmailBody`, `readSelectedEmail`, and the
89
- * attachment resolver) and differ only in how they bind `theMsg`. They used to
90
- * share it by copy: thirty lines repeated, with the `time received` → `time sent`
91
- * fallback and the field order restated each time, so a dictionary fix reached
92
- * one reader and not the others.
93
- *
94
- * `bodyText` is emitted LAST when asked for, so a stray separator in an earlier
95
- * field cannot shift it.
95
+ * Everything a reader wants off one message bound to `theMsg`, emitted together
96
+ * with the row it produces (see `messageDetailFields`) so the read order and the
97
+ * positions the decoders index into cannot drift apart. `bodyText` is emitted
98
+ * LAST when asked for, so a stray separator in an earlier field can't shift it.
96
99
  */
97
100
  export declare function messageDetailSnippet(withBody?: boolean, indent?: string): string;
98
- /**
99
- * The fields `messageDetailSnippet` fills, in row order. The decoders read them
100
- * back by the positions in `MessageDetail`.
101
- */
101
+ /** The fields `messageDetailSnippet` fills, in row order — see `MessageDetail`. */
102
102
  export declare function messageDetailFields(withBody?: boolean): string[];
103
- /** Where each of those fields lands in the emitted row. */
103
+ /** Where each of those fields lands in the row. */
104
104
  export declare const MessageDetail: {
105
105
  readonly id: 0;
106
106
  readonly subject: 1;
@@ -111,27 +111,19 @@ export declare const MessageDetail: {
111
111
  readonly body: 6;
112
112
  };
113
113
  /**
114
- * The first recipient's name and address, as `sndName` / `sndAddr` — what
115
- * outgoing mail reports in place of a sender, matching the Windows reader's
116
- * contract for Sent Items (a folder of mail from yourself is unreadable).
114
+ * The first recipient's name and address as `sndName` / `sndAddr` — what
115
+ * outgoing mail reports in place of a sender.
117
116
  */
118
117
  export declare function firstRecipientSnippet(messageVariable?: string, indent?: string): string;
119
- /**
120
- * Every SMTP address a message was addressed to, as the list `addrList`.
121
- *
122
- * `every recipient` covers To, CC and BCC — the full set, which is the whole
123
- * point of readSentRecipientGroups.
124
- */
118
+ /** Every address a message was sent to — To, CC and BCC — as the list `addrList`. */
125
119
  export declare function allRecipientsSnippet(messageVariable?: string, indent?: string): string;
126
120
  /**
127
- * Recursive folder-by-name search from `startFolder`, case-insensitive and
128
- * depth-capped — the macOS counterpart of the Windows Find-FolderByName, used to
129
- * resolve a template folder anywhere in the mailbox tree.
121
+ * Recursive, case-insensitive, depth-capped folder search from `startFolder` —
122
+ * the macOS counterpart of the Windows Find-FolderByName.
130
123
  */
131
124
  export declare const FIND_FOLDER_HANDLER = "\non findFolderByName(startFolder, wantedName, depth)\n tell application \"Microsoft Outlook\"\n set subs to mail folders of startFolder\n end tell\n repeat with f in subs\n if (name of f as string) is wantedName then return f\n end repeat\n if depth is less than or equal to 1 then return missing value\n repeat with f in subs\n set hit to my findFolderByName(f, wantedName, depth - 1)\n if hit is not missing value then return hit\n end repeat\n return missing value\nend findFolderByName\n";
132
125
  /**
133
- * Account enumeration only works when Outlook runs in legacy mode; New Outlook
134
- * errors on `every account` and returns empty lists for the typed classes, so
135
- * each class is probed independently and failures are ignored.
126
+ * Enumerate the accounts, probing each class separately: legacy Outlook errors
127
+ * on `every account`, and New Outlook returns empty lists for the classes.
136
128
  */
137
129
  export declare const LIST_ACCOUNTS_SNIPPET = "\nset acctList to {}\ntell application \"Microsoft Outlook\"\n repeat with acctClass in {\"exchange\", \"imap\", \"pop\"}\n try\n if (acctClass as string) is \"exchange\" then\n set accts to exchange accounts\n else if (acctClass as string) is \"imap\" then\n set accts to imap accounts\n else\n set accts to pop accounts\n end if\n repeat with a in accts\n try\n set end of acctList to (email address of a as string)\n end try\n end repeat\n end try\n end repeat\nend tell\n";
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LIST_ACCOUNTS_SNIPPET = exports.FIND_FOLDER_HANDLER = exports.MessageDetail = exports.MAC_ROOT_TERMS = void 0;
4
+ exports.resolveMacAccount = resolveMacAccount;
4
5
  exports.accountLookupSnippet = accountLookupSnippet;
6
+ exports.rootFolderSnippet = rootFolderSnippet;
5
7
  exports.dateProperty = dateProperty;
6
8
  exports.macFolderPath = macFolderPath;
7
9
  exports.rootTerm = rootTerm;
@@ -16,125 +18,161 @@ exports.firstRecipientSnippet = firstRecipientSnippet;
16
18
  exports.allRecipientsSnippet = allRecipientsSnippet;
17
19
  // The AppleScript fragments more than one macOS operation is built from.
18
20
  const run_1 = require("./run");
21
+ const profile_1 = require("./profile");
19
22
  const errors_1 = require("../errors");
20
23
  const mail_1 = require("../mail");
24
+ /** Look an address up in Outlook's profile database, for the folder ids it can add. */
25
+ async function resolveMacAccount(emailAccount) {
26
+ const wanted = emailAccount.trim().toLowerCase();
27
+ const found = (await (0, profile_1.readProfileAccounts)())
28
+ .find(account => account.emailAccount.trim().toLowerCase() === wanted);
29
+ return found ? { emailAccount, folderIds: found.folderIds } : { emailAccount };
30
+ }
21
31
  /**
22
- * Resolve the account whose SMTP address matches, or raise. Emitted into a
23
- * `tell application "Microsoft Outlook"` block; binds `targetAcct`.
32
+ * Resolve the account whose address matches into `targetAcct`, inside a
33
+ * `tell application "Microsoft Outlook"` block.
24
34
  *
25
- * `every account` errors even in legacy mode, so probe the typed classes. A
26
- * `whose email address is ...` filter can't be used either: `email address` is
27
- * also a class name, and AppleScript resolves it as one ("into type specifier").
35
+ * `every account` errors even in legacy mode, so the typed classes are probed.
36
+ * A `whose email address is …` filter can't be used either: `email address` is
37
+ * also a class name, and AppleScript resolves it as one.
28
38
  *
29
- * The thrown sentence is the one `classifyRunFailure` recognises to raise
30
- * `AccountNotFoundError`, so its wording is load-bearing.
39
+ * A mailbox the dictionary publishes no account object for leaves `targetAcct`
40
+ * as `missing value` rather than failing, provided the profile gave a folder id
41
+ * to reach it by. `needsAccountObject` is for composing mail, which can't work
42
+ * without the object: the account a message is sent from can only be set from
43
+ * it, and a message composed without it goes out from the default mailbox.
31
44
  */
32
- function accountLookupSnippet(emailAccount) {
33
- return ` set targetAcct to missing value
45
+ function accountLookupSnippet(acct, needsAccountObject = false) {
46
+ const probe = ` set targetAcct to missing value
34
47
  try
35
48
  repeat with a in (exchange accounts & imap accounts & pop accounts)
36
- if (email address of a as string) is "${(0, run_1.asEscape)(emailAccount)}" then
49
+ if (email address of a as string) is ${(0, run_1.asString)(acct.emailAccount)} then
37
50
  set targetAcct to a
38
51
  exit repeat
39
52
  end if
40
53
  end repeat
41
- end try
42
- if targetAcct is missing value then error "Account '${(0, run_1.asEscape)(emailAccount)}' not found"`;
54
+ end try`;
55
+ if (acct.folderIds && !needsAccountObject)
56
+ return probe;
57
+ const complaint = acct.folderIds
58
+ ? `${(0, errors_1.failureTag)('INVALID_REQUEST')}Outlook publishes no account object for '${acct.emailAccount}', `
59
+ + 'so the account a message is sent from cannot be set. Its folders can be read; mail cannot be composed from it.'
60
+ : `${(0, errors_1.failureTag)('ACCOUNT_NOT_FOUND')}Account '${acct.emailAccount}' not found in Outlook.`;
61
+ return `${probe}
62
+ if targetAcct is missing value then error ${(0, run_1.asString)(complaint)}`;
43
63
  }
44
64
  /**
45
- * Outlook for Mac's dictionary term for each well-known root, keyed by the
46
- * olDefaultFolders id `mailFolderRef` resolves.
47
- *
48
- * Verified against the running app. The terms are NOT the ones the Windows ids
49
- * suggest — `sent mail` and `junk email` do not compile at all, while
50
- * `sent items` and `junk mail` do, and a bad term fails the whole script at
51
- * compile time rather than at the offending line.
65
+ * Bind `variable` to one of the account's well-known folders: through the
66
+ * account object when the probe found one, else by the folder's own id. `mail
67
+ * folder id N` resolves against the application rather than an account, and N is
68
+ * the id Outlook's profile database records for that folder.
69
+ */
70
+ function rootFolderSnippet(acct, term, variable) {
71
+ const viaAccount = ` set ${variable} to ${term} of targetAcct`;
72
+ const id = acct.folderIds?.[term];
73
+ if (id !== undefined) {
74
+ return ` if targetAcct is missing value then
75
+ set ${variable} to mail folder id ${id}
76
+ else
77
+ ${viaAccount}
78
+ end if`;
79
+ }
80
+ // The profile listed the mailbox but not this root, so the probe is the only
81
+ // way in — and it may have come up empty. Name the folder out of reach
82
+ // rather than let `<term> of missing value` say it.
83
+ if (acct.folderIds) {
84
+ const complaint = `${(0, errors_1.failureTag)('NOT_FOUND', 'folder')}Account '${acct.emailAccount}' has no ${term} folder AppleScript can reach.`;
85
+ return ` if targetAcct is missing value then error ${(0, run_1.asString)(complaint)}
86
+ ${viaAccount}`;
87
+ }
88
+ return viaAccount;
89
+ }
90
+ /**
91
+ * Outlook for Mac's dictionary term for each well-known root, by olDefaultFolders
92
+ * id. Verified against the running app: the terms are NOT what the Windows names
93
+ * suggest — `sent mail` and `junk email` don't compile, `sent items` and `junk
94
+ * mail` do — and one bad term fails the whole script at compile time.
52
95
  */
53
96
  exports.MAC_ROOT_TERMS = {
54
- 6: 'inbox',
55
- 5: 'sent items',
56
- 16: 'drafts',
57
- 3: 'deleted items',
58
- 4: 'outbox',
59
- 23: 'junk mail',
97
+ [mail_1.FolderId.Inbox]: 'inbox',
98
+ [mail_1.FolderId.SentMail]: 'sent items',
99
+ [mail_1.FolderId.Drafts]: 'drafts',
100
+ [mail_1.FolderId.DeletedItems]: 'deleted items',
101
+ [mail_1.FolderId.Outbox]: 'outbox',
102
+ [mail_1.FolderId.Junk]: 'junk mail',
60
103
  };
61
- /** The date property a folder's items actually carry. */
104
+ /** The date property a root's items carry. */
62
105
  function dateProperty(rootId) {
63
106
  return (0, mail_1.isOutgoingRoot)(rootId) ? 'time sent' : 'time received';
64
107
  }
65
108
  /**
66
- * The Windows `FolderPath` shape (`\\mailbox\Inbox\Invoices`), built here rather
67
- * than in AppleScript — escaping backslashes through a template literal and then
68
- * an AppleScript literal is unreadable, and TS already knows the parts.
109
+ * The Windows folder-path shape (`\\mailbox\Inbox\Invoices`), assembled in
110
+ * TypeScript: escaping backslashes through a template literal and then an
111
+ * AppleScript literal is unreadable, and the parts are known here anyway.
69
112
  */
70
113
  function macFolderPath(emailAccount, rootLabel, segments) {
71
114
  return ['\\\\' + emailAccount, rootLabel, ...segments].join('\\');
72
115
  }
73
- /** The dictionary term for a root, or a NOT_IMPLEMENTED naming what was asked. */
116
+ /** The dictionary term for a root, or NOT_IMPLEMENTED naming what was asked. */
74
117
  function rootTerm(ref, folderLabel = '') {
75
118
  const term = exports.MAC_ROOT_TERMS[ref.rootId];
76
119
  if (!term) {
77
120
  const asked = folderLabel ? ` (asked for '${folderLabel}')` : '';
78
- throw new errors_1.NotImplementedError(`folder root '${ref.rootLabel}'${asked}`, `macOS — readable roots are ${Object.values(exports.MAC_ROOT_TERMS).join(', ')}`);
121
+ throw new errors_1.NotImplementedError(`Reading the '${ref.rootLabel}' folder${asked}`, `macOS, whose readable roots are ${Object.values(exports.MAC_ROOT_TERMS).join(', ')}`);
79
122
  }
80
123
  return term;
81
124
  }
82
125
  /**
83
- * Emit the AppleScript that resolves `scopeFolder` from `targetAcct`, walking a
84
- * well-known root down through any further path segments. Also binds
85
- * `scopeCreated`.
126
+ * Resolve `scopeFolder` from `targetAcct`: a well-known root walked down through
127
+ * any further segments. Also binds `scopeCreated`.
86
128
  *
87
- * Folder names are compared with `is`, which is case-insensitive in AppleScript —
88
- * matching the Windows walk's `-ieq` so 'invoices' finds 'Invoices' on both.
89
- * A missing segment errors by name instead of falling back to the root: silently
90
- * returning the Inbox for a caller that scoped to one folder hands back the wrong
91
- * emails under a name that says otherwise. `createMissing` builds the whole
92
- * chain instead, so a nested destination is one call.
129
+ * Names compare with `is`, which is case-insensitive in AppleScript, matching
130
+ * the Windows walk. A missing segment fails as NOT_FOUND rather than falling
131
+ * back to the root; `createMissing` builds the chain instead.
93
132
  */
94
- function mailScopeSnippet(ref, folderLabel = '', createMissing = false) {
133
+ function mailScopeSnippet(acct, ref, folderLabel = '', createMissing = false) {
95
134
  const term = rootTerm(ref, folderLabel);
96
- const walk = ref.segments.map(seg => `
135
+ const walk = ref.segments.map(segment => {
136
+ const complaint = `${(0, errors_1.failureTag)('NOT_FOUND', 'folder')}Folder '${segment}' not found under '${ref.rootLabel}'.`;
137
+ const create = createMissing
138
+ ? ` set foundFolder to (make new mail folder at scopeFolder with properties {name:${(0, run_1.asString)(segment)}})
139
+ set scopeCreated to true`
140
+ : ` error ${(0, run_1.asString)(complaint)}`;
141
+ return `
97
142
  set foundFolder to missing value
98
143
  repeat with sf in (mail folders of scopeFolder)
99
- if (name of sf as string) is "${(0, run_1.asEscape)(seg)}" then
144
+ if (name of sf as string) is ${(0, run_1.asString)(segment)} then
100
145
  set foundFolder to sf
101
146
  exit repeat
102
147
  end if
103
148
  end repeat
104
149
  if foundFolder is missing value then
105
- if ${(0, run_1.asBool)(createMissing)} then
106
- set foundFolder to (make new mail folder at scopeFolder with properties {name:"${(0, run_1.asEscape)(seg)}"})
107
- set scopeCreated to true
108
- else
109
- error "Folder '${(0, run_1.asEscape)(seg)}' not found under '${(0, run_1.asEscape)(ref.rootLabel)}'"
110
- end if
150
+ ${create}
111
151
  end if
112
- set scopeFolder to foundFolder`).join('');
152
+ set scopeFolder to foundFolder`;
153
+ }).join('');
113
154
  return ` set scopeCreated to false
114
- set scopeFolder to ${term} of targetAcct${walk}`;
155
+ ${rootFolderSnippet(acct, term, 'scopeFolder')}${walk}`;
115
156
  }
116
157
  /**
117
- * The message-id text for a script, or an InvalidRequestError.
158
+ * The message id text for a script, or INVALID_REQUEST.
118
159
  *
119
- * Outlook for Mac ids are small integers; a Windows MAPI EntryID is rejected up
120
- * front rather than left to fail as a confusing "not found", because a caller
121
- * that persisted one from a Windows run needs to be told exactly that.
160
+ * Outlook for Mac ids are small integers; a Windows EntryID is refused up front
161
+ * rather than left to fail as a confusing "not found", because a caller that
162
+ * persisted one from a Windows run needs to be told exactly that.
122
163
  */
123
164
  function macMessageId(entryId) {
124
165
  const id = String(entryId ?? '').trim();
125
166
  if (!/^\d+$/.test(id)) {
126
167
  throw new errors_1.InvalidRequestError(`'${entryId}' is not an Outlook for Mac message id. Mac ids are small integers `
127
- + `(e.g. "1263") returned by readInboxEmails on this machine; a Windows MAPI `
128
- + `EntryID cannot be resolved here.`);
168
+ + '(e.g. "1263") listed on this machine; a Windows EntryID cannot be resolved here.');
129
169
  }
130
170
  return id;
131
171
  }
132
172
  /**
133
- * Split ids into the ones a script can look up and the ones that can only fail.
134
- *
135
- * The per-id operations report failures rather than throwing, so an unusable id
136
- * belongs in the result's `failed` list beside a genuine lookup miss — not as an
137
- * exception that discards the ids that would have worked.
173
+ * Split ids into those a script can look up and those that can only fail. A
174
+ * batch reports an unusable id beside a genuine miss rather than throwing and
175
+ * discarding the ids that would have worked.
138
176
  */
139
177
  function partitionMessageIds(entryIds) {
140
178
  const valid = [];
@@ -144,29 +182,27 @@ function partitionMessageIds(entryIds) {
144
182
  valid.push(macMessageId(entryId));
145
183
  }
146
184
  catch (error) {
147
- invalid.push({ entryId: String(entryId), error: error.message });
185
+ invalid.push({ entryId: String(entryId), subject: '', error: error.message });
148
186
  }
149
187
  }
150
188
  return { valid, invalid };
151
189
  }
152
190
  /**
153
- * Resolve `theMsg` from a macOS message id, or raise.
154
- *
155
- * `message id N` resolves against the application rather than one folder, so the
156
- * message is found wherever it currently sits — including a subfolder.
191
+ * Resolve `variable` from a message id, or fail as NOT_FOUND. `message id N`
192
+ * resolves against the application, so the message is found wherever it sits.
157
193
  */
158
194
  function messageLookupSnippet(id, variable = 'theMsg') {
195
+ const complaint = `${(0, errors_1.failureTag)('NOT_FOUND', 'email')}No email found for message id '${id}'. It may have been deleted - list the mail again for a current id.`;
159
196
  return ` set ${variable} to missing value
160
197
  try
161
198
  set ${variable} to message id ${id}
162
199
  end try
163
- if ${variable} is missing value then error "Email not found for message id '${id}'"`;
200
+ if ${variable} is missing value then error ${(0, run_1.asString)(complaint)}`;
164
201
  }
165
202
  /**
166
- * The sender's display name and address, as `sndName` / `sndAddr`.
167
- *
168
- * `sender` yields a record, and `address of sender of m` fails to coerce, so the
169
- * record has to be bound first. `name` is absent when there is no display name.
203
+ * The sender's display name and address as `sndName` / `sndAddr`. `sender` is a
204
+ * record, and `address of sender of m` doesn't coerce, so the record is bound
205
+ * first.
170
206
  */
171
207
  function senderSnippet(messageVariable = 'theMsg', indent = ' ') {
172
208
  return `${indent}set sndName to ""
@@ -182,18 +218,10 @@ ${indent} end try
182
218
  ${indent}end try`;
183
219
  }
184
220
  /**
185
- * Everything a reader wants off ONE message already bound to `theMsg`, and the
186
- * row it produces — emitted together so the read order and the positions the
187
- * TypeScript decoders index into cannot drift apart.
188
- *
189
- * Three callers share it (`readEmailBody`, `readSelectedEmail`, and the
190
- * attachment resolver) and differ only in how they bind `theMsg`. They used to
191
- * share it by copy: thirty lines repeated, with the `time received` → `time sent`
192
- * fallback and the field order restated each time, so a dictionary fix reached
193
- * one reader and not the others.
194
- *
195
- * `bodyText` is emitted LAST when asked for, so a stray separator in an earlier
196
- * field cannot shift it.
221
+ * Everything a reader wants off one message bound to `theMsg`, emitted together
222
+ * with the row it produces (see `messageDetailFields`) so the read order and the
223
+ * positions the decoders index into cannot drift apart. `bodyText` is emitted
224
+ * LAST when asked for, so a stray separator in an earlier field can't shift it.
197
225
  */
198
226
  function messageDetailSnippet(withBody = false, indent = ' ') {
199
227
  return `${indent}set subj to ""
@@ -218,24 +246,14 @@ ${indent}try
218
246
  ${indent} set bodyText to (plain text content of theMsg) as string
219
247
  ${indent}end try` : '');
220
248
  }
221
- /**
222
- * The fields `messageDetailSnippet` fills, in row order. The decoders read them
223
- * back by the positions in `MessageDetail`.
224
- */
249
+ /** The fields `messageDetailSnippet` fills, in row order — see `MessageDetail`. */
225
250
  function messageDetailFields(withBody = false) {
226
- const fields = [
227
- '(id of theMsg as string)',
228
- 'subj',
229
- 'sndName',
230
- 'sndAddr',
231
- 'recvd',
232
- 'my sanitizeList(attNames)',
233
- ];
251
+ const fields = ['(id of theMsg as string)', 'subj', 'sndName', 'sndAddr', 'recvd', 'my sanitizeList(attNames)'];
234
252
  if (withBody)
235
253
  fields.push('bodyText');
236
254
  return fields;
237
255
  }
238
- /** Where each of those fields lands in the emitted row. */
256
+ /** Where each of those fields lands in the row. */
239
257
  exports.MessageDetail = {
240
258
  id: 0,
241
259
  subject: 1,
@@ -246,9 +264,8 @@ exports.MessageDetail = {
246
264
  body: 6,
247
265
  };
248
266
  /**
249
- * The first recipient's name and address, as `sndName` / `sndAddr` — what
250
- * outgoing mail reports in place of a sender, matching the Windows reader's
251
- * contract for Sent Items (a folder of mail from yourself is unreadable).
267
+ * The first recipient's name and address as `sndName` / `sndAddr` — what
268
+ * outgoing mail reports in place of a sender.
252
269
  */
253
270
  function firstRecipientSnippet(messageVariable = 'theMsg', indent = ' ') {
254
271
  return `${indent}set sndName to ""
@@ -266,20 +283,15 @@ ${indent} end try
266
283
  ${indent} end if
267
284
  ${indent}end try`;
268
285
  }
269
- /**
270
- * Every SMTP address a message was addressed to, as the list `addrList`.
271
- *
272
- * `every recipient` covers To, CC and BCC — the full set, which is the whole
273
- * point of readSentRecipientGroups.
274
- */
286
+ /** Every address a message was sent to — To, CC and BCC — as the list `addrList`. */
275
287
  function allRecipientsSnippet(messageVariable = 'theMsg', indent = ' ') {
276
288
  return `${indent}set addrList to {}
277
289
  ${indent}try
278
290
  ${indent} repeat with r in (every recipient of ${messageVariable})
279
291
  ${indent} try
280
- ${indent} -- The record has to be bound before its field is read: a nested
281
- ${indent} -- 'address of (email address of r)' does not coerce, and fails
282
- ${indent} -- inside the try as an empty recipient list rather than an error.
292
+ ${indent} -- The record is bound before its field is read: a nested
293
+ ${indent} -- 'address of (email address of r)' doesn't coerce, and fails
294
+ ${indent} -- inside the try as an empty list rather than an error.
283
295
  ${indent} set ea to email address of r
284
296
  ${indent} set oneAddr to (address of ea) as string
285
297
  ${indent} if oneAddr is not "" then set end of addrList to oneAddr
@@ -288,9 +300,8 @@ ${indent} end repeat
288
300
  ${indent}end try`;
289
301
  }
290
302
  /**
291
- * Recursive folder-by-name search from `startFolder`, case-insensitive and
292
- * depth-capped — the macOS counterpart of the Windows Find-FolderByName, used to
293
- * resolve a template folder anywhere in the mailbox tree.
303
+ * Recursive, case-insensitive, depth-capped folder search from `startFolder` —
304
+ * the macOS counterpart of the Windows Find-FolderByName.
294
305
  */
295
306
  exports.FIND_FOLDER_HANDLER = `
296
307
  on findFolderByName(startFolder, wantedName, depth)
@@ -309,9 +320,8 @@ on findFolderByName(startFolder, wantedName, depth)
309
320
  end findFolderByName
310
321
  `;
311
322
  /**
312
- * Account enumeration only works when Outlook runs in legacy mode; New Outlook
313
- * errors on `every account` and returns empty lists for the typed classes, so
314
- * each class is probed independently and failures are ignored.
323
+ * Enumerate the accounts, probing each class separately: legacy Outlook errors
324
+ * on `every account`, and New Outlook returns empty lists for the classes.
315
325
  */
316
326
  exports.LIST_ACCOUNTS_SNIPPET = `
317
327
  set acctList to {}