@dawnlit/outlook-bridge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/README.md +253 -0
- package/dist/OutlookService.d.ts +43 -0
- package/dist/OutlookService.js +137 -0
- package/dist/OutlookService.js.map +1 -0
- package/dist/errors.d.ts +112 -0
- package/dist/errors.js +140 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/mac/accounts.d.ts +5 -0
- package/dist/mac/accounts.js +17 -0
- package/dist/mac/accounts.js.map +1 -0
- package/dist/mac/attachments.d.ts +28 -0
- package/dist/mac/attachments.js +120 -0
- package/dist/mac/attachments.js.map +1 -0
- package/dist/mac/bounces.d.ts +32 -0
- package/dist/mac/bounces.js +285 -0
- package/dist/mac/bounces.js.map +1 -0
- package/dist/mac/cleanup.d.ts +29 -0
- package/dist/mac/cleanup.js +219 -0
- package/dist/mac/cleanup.js.map +1 -0
- package/dist/mac/drafts.d.ts +27 -0
- package/dist/mac/drafts.js +214 -0
- package/dist/mac/drafts.js.map +1 -0
- package/dist/mac/folders.d.ts +28 -0
- package/dist/mac/folders.js +127 -0
- package/dist/mac/folders.js.map +1 -0
- package/dist/mac/index.d.ts +22 -0
- package/dist/mac/index.js +119 -0
- package/dist/mac/index.js.map +1 -0
- package/dist/mac/read.d.ts +46 -0
- package/dist/mac/read.js +406 -0
- package/dist/mac/read.js.map +1 -0
- package/dist/mac/run.d.ts +64 -0
- package/dist/mac/run.js +250 -0
- package/dist/mac/run.js.map +1 -0
- package/dist/mac/scripts.d.ts +137 -0
- package/dist/mac/scripts.js +337 -0
- package/dist/mac/scripts.js.map +1 -0
- package/dist/mac/send.d.ts +23 -0
- package/dist/mac/send.js +135 -0
- package/dist/mac/send.js.map +1 -0
- package/dist/mac/signatures.d.ts +4 -0
- package/dist/mac/signatures.js +36 -0
- package/dist/mac/signatures.js.map +1 -0
- package/dist/mac/templates.d.ts +34 -0
- package/dist/mac/templates.js +266 -0
- package/dist/mac/templates.js.map +1 -0
- package/dist/mail.d.ts +72 -0
- package/dist/mail.js +131 -0
- package/dist/mail.js.map +1 -0
- package/dist/outlookTemplateSections.d.ts +62 -0
- package/dist/outlookTemplateSections.js +313 -0
- package/dist/outlookTemplateSections.js.map +1 -0
- package/dist/runtime.d.ts +76 -0
- package/dist/runtime.js +123 -0
- package/dist/runtime.js.map +1 -0
- package/dist/shared/attachmentMatch.d.ts +32 -0
- package/dist/shared/attachmentMatch.js +50 -0
- package/dist/shared/attachmentMatch.js.map +1 -0
- package/dist/shared/bounceRules.d.ts +33 -0
- package/dist/shared/bounceRules.js +104 -0
- package/dist/shared/bounceRules.js.map +1 -0
- package/dist/shared/capabilities.d.ts +3 -0
- package/dist/shared/capabilities.js +9 -0
- package/dist/shared/capabilities.js.map +1 -0
- package/dist/shared/json.d.ts +11 -0
- package/dist/shared/json.js +45 -0
- package/dist/shared/json.js.map +1 -0
- package/dist/shared/replyBody.d.ts +49 -0
- package/dist/shared/replyBody.js +113 -0
- package/dist/shared/replyBody.js.map +1 -0
- package/dist/types.d.ts +365 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/dist/windows/accounts.d.ts +2 -0
- package/dist/windows/accounts.js +22 -0
- package/dist/windows/accounts.js.map +1 -0
- package/dist/windows/attachments.d.ts +28 -0
- package/dist/windows/attachments.js +141 -0
- package/dist/windows/attachments.js.map +1 -0
- package/dist/windows/bounces.d.ts +34 -0
- package/dist/windows/bounces.js +267 -0
- package/dist/windows/bounces.js.map +1 -0
- package/dist/windows/cleanup.d.ts +29 -0
- package/dist/windows/cleanup.js +165 -0
- package/dist/windows/cleanup.js.map +1 -0
- package/dist/windows/drafts.d.ts +29 -0
- package/dist/windows/drafts.js +253 -0
- package/dist/windows/drafts.js.map +1 -0
- package/dist/windows/folders.d.ts +16 -0
- package/dist/windows/folders.js +88 -0
- package/dist/windows/folders.js.map +1 -0
- package/dist/windows/index.d.ts +27 -0
- package/dist/windows/index.js +100 -0
- package/dist/windows/index.js.map +1 -0
- package/dist/windows/read.d.ts +54 -0
- package/dist/windows/read.js +372 -0
- package/dist/windows/read.js.map +1 -0
- package/dist/windows/run.d.ts +54 -0
- package/dist/windows/run.js +161 -0
- package/dist/windows/run.js.map +1 -0
- package/dist/windows/scripts.d.ts +56 -0
- package/dist/windows/scripts.js +163 -0
- package/dist/windows/scripts.js.map +1 -0
- package/dist/windows/send.d.ts +11 -0
- package/dist/windows/send.js +114 -0
- package/dist/windows/send.js.map +1 -0
- package/dist/windows/signatures.d.ts +14 -0
- package/dist/windows/signatures.js +69 -0
- package/dist/windows/signatures.js.map +1 -0
- package/dist/windows/templates.d.ts +25 -0
- package/dist/windows/templates.js +246 -0
- package/dist/windows/templates.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readTemplateEmails = readTemplateEmails;
|
|
4
|
+
exports.saveTemplateEmail = saveTemplateEmail;
|
|
5
|
+
exports.editEmailTemplate = editEmailTemplate;
|
|
6
|
+
// Template emails: ordinary mail items kept in a mailbox folder and reused as
|
|
7
|
+
// reply bodies, plus the compose-window editor for one.
|
|
8
|
+
//
|
|
9
|
+
// Outlook for Mac serves a message's HTML through `content` when `has html` is
|
|
10
|
+
// true — the same thing COM's HTMLBody returns — which is what makes templates
|
|
11
|
+
// portable between the two platforms rather than a Windows-only feature.
|
|
12
|
+
const run_1 = require("./run");
|
|
13
|
+
const scripts_1 = require("./scripts");
|
|
14
|
+
const outlookTemplateSections_1 = require("../outlookTemplateSections");
|
|
15
|
+
const mail_1 = require("../mail");
|
|
16
|
+
const errors_1 = require("../errors");
|
|
17
|
+
/** How deep under the mailbox root a template folder is searched for. */
|
|
18
|
+
const TEMPLATE_SEARCH_DEPTH = 3;
|
|
19
|
+
/** The cap Windows applies to a template body; a Word-generated one is huge. */
|
|
20
|
+
const MAX_TEMPLATE_HTML = 100000;
|
|
21
|
+
/** How much plain text stands in for a body when one wasn't asked for. */
|
|
22
|
+
const PREVIEW_CHARS = 200;
|
|
23
|
+
/**
|
|
24
|
+
* Embedded (cid:) images live as attachments on the TEMPLATE item, so reusing
|
|
25
|
+
* the HTML on a new email shows a broken "linked image" placeholder, and
|
|
26
|
+
* rewriting the refs renders invisibly. Neither works, so the img tags go —
|
|
27
|
+
* template emails are text/HTML only.
|
|
28
|
+
*/
|
|
29
|
+
function stripEmbeddedImages(html) {
|
|
30
|
+
return html.replace(/<img[^>]*src="cid:[^"]*"[^>]*>/gi, '');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read the template emails saved in a mailbox folder (default "Templates"),
|
|
34
|
+
* returning each item's full HTML body. When the folder doesn't exist, returns
|
|
35
|
+
* folderFound:false plus the mailbox's folder names instead of throwing, so the
|
|
36
|
+
* caller can ask the user whether to create it rather than fail.
|
|
37
|
+
*
|
|
38
|
+
* Bodies are read in a second pass, for the chosen templates only. That is not
|
|
39
|
+
* a micro-optimisation: a folder of Word-sized templates is megabytes, and
|
|
40
|
+
* pulling all of them through stdout on a call that wanted one is what overruns
|
|
41
|
+
* the buffer — the same reason the Windows reader filters before reading.
|
|
42
|
+
*/
|
|
43
|
+
async function readTemplateEmails(emailAccount, folderName = 'Templates', limit = 20, includeBody = true, subject = '') {
|
|
44
|
+
const cap = (0, mail_1.clamp)(limit, 1, 50);
|
|
45
|
+
const wanted = (subject || '').trim().toLowerCase();
|
|
46
|
+
// Pass 1 — find the folder and index it: three bulk reads, no bodies.
|
|
47
|
+
const indexScript = `${scripts_1.FIND_FOLDER_HANDLER}
|
|
48
|
+
tell application "Microsoft Outlook"
|
|
49
|
+
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
50
|
+
set rootFolder to root folder of targetAcct
|
|
51
|
+
end tell
|
|
52
|
+
set theFolder to my findFolderByName(rootFolder, "${(0, run_1.asEscape)(folderName)}", ${TEMPLATE_SEARCH_DEPTH})
|
|
53
|
+
tell application "Microsoft Outlook"
|
|
54
|
+
if theFolder is missing value then
|
|
55
|
+
set names to {}
|
|
56
|
+
repeat with f in (mail folders of rootFolder)
|
|
57
|
+
try
|
|
58
|
+
set end of names to (name of f) as string
|
|
59
|
+
end try
|
|
60
|
+
end repeat
|
|
61
|
+
return ${(0, run_1.asRow)(['"0"', 'my sanitizeList(names)'])}
|
|
62
|
+
end if
|
|
63
|
+
set idList to id of every message of theFolder
|
|
64
|
+
set subjList to subject of every message of theFolder
|
|
65
|
+
try
|
|
66
|
+
set modList to modification date of every message of theFolder
|
|
67
|
+
on error
|
|
68
|
+
set modList to {}
|
|
69
|
+
end try
|
|
70
|
+
set hasMods to ((count of modList) is (count of idList))
|
|
71
|
+
set out to ${(0, run_1.asRow)(['"1"', '(name of theFolder as string)'])}
|
|
72
|
+
repeat with i from 1 to (count of idList)
|
|
73
|
+
set modAt to ""
|
|
74
|
+
if hasMods then set modAt to my isoDate(item i of modList)
|
|
75
|
+
set out to out & ${(0, run_1.asRow)([
|
|
76
|
+
'(item i of idList as string)',
|
|
77
|
+
'(item i of subjList)',
|
|
78
|
+
'modAt',
|
|
79
|
+
])}
|
|
80
|
+
end repeat
|
|
81
|
+
return out
|
|
82
|
+
end tell`;
|
|
83
|
+
const records = (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(indexScript, 120000));
|
|
84
|
+
const header = (0, run_1.splitFields)(records[0] || '');
|
|
85
|
+
if ((0, run_1.field)(header, 0) !== '1') {
|
|
86
|
+
return {
|
|
87
|
+
folderFound: false,
|
|
88
|
+
folderPath: '',
|
|
89
|
+
templates: [],
|
|
90
|
+
availableFolders: (0, run_1.splitList)((0, run_1.field)(header, 1)),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const folderPath = (0, scripts_1.macFolderPath)(emailAccount, (0, run_1.field)(header, 1) || folderName, []);
|
|
94
|
+
const indexed = records.slice(1).map(record => {
|
|
95
|
+
const parts = (0, run_1.splitFields)(record);
|
|
96
|
+
return {
|
|
97
|
+
id: (0, run_1.field)(parts, 0),
|
|
98
|
+
subject: (0, run_1.field)(parts, 1).trim(),
|
|
99
|
+
lastModified: (0, run_1.field)(parts, 2),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
// Newest first, matching the Windows reader's Sort on LastModificationTime —
|
|
103
|
+
// and done before the cap, so "the newest 20" means that rather than an
|
|
104
|
+
// arbitrary 20. 'yyyy-MM-dd HH:mm' sorts lexicographically.
|
|
105
|
+
indexed.sort((a, b) => b.lastModified.localeCompare(a.lastModified));
|
|
106
|
+
const chosen = indexed
|
|
107
|
+
.filter(item => !wanted || item.subject.toLowerCase() === wanted)
|
|
108
|
+
.slice(0, cap);
|
|
109
|
+
if (chosen.length === 0) {
|
|
110
|
+
return { folderFound: true, folderPath, templates: [], availableFolders: [] };
|
|
111
|
+
}
|
|
112
|
+
// Pass 2 — the bodies, for the chosen templates only.
|
|
113
|
+
const bodyProperty = includeBody ? 'content' : 'plain text content';
|
|
114
|
+
const bodyScript = `tell application "Microsoft Outlook"
|
|
115
|
+
set out to ""
|
|
116
|
+
repeat with theId in {${chosen.map(item => item.id).join(', ')}}
|
|
117
|
+
set theMsg to missing value
|
|
118
|
+
try
|
|
119
|
+
set theMsg to message id theId
|
|
120
|
+
end try
|
|
121
|
+
if theMsg is not missing value then
|
|
122
|
+
set bodyText to ""
|
|
123
|
+
try
|
|
124
|
+
set bodyText to (${bodyProperty} of theMsg) as string
|
|
125
|
+
end try
|
|
126
|
+
set out to out & ${(0, run_1.asRow)(['(theId as string)', 'bodyText'])}
|
|
127
|
+
end if
|
|
128
|
+
end repeat
|
|
129
|
+
return out
|
|
130
|
+
end tell`;
|
|
131
|
+
const bodies = new Map();
|
|
132
|
+
for (const record of (0, run_1.splitRecords)(await (0, run_1.runOsaScript)(bodyScript, 120000))) {
|
|
133
|
+
const parts = (0, run_1.splitFields)(record);
|
|
134
|
+
bodies.set((0, run_1.field)(parts, 0), (0, run_1.field)(parts, 1));
|
|
135
|
+
}
|
|
136
|
+
const templates = chosen.map(item => {
|
|
137
|
+
const body = bodies.get(item.id) || '';
|
|
138
|
+
const html = includeBody
|
|
139
|
+
? stripEmbeddedImages(body).slice(0, MAX_TEMPLATE_HTML)
|
|
140
|
+
: '';
|
|
141
|
+
// With a body in hand the HTML is authoritative (it survives Word splitting
|
|
142
|
+
// a marker across tags); without one, scan the plain-text body, where a
|
|
143
|
+
// marker cannot have been split.
|
|
144
|
+
const markers = (0, outlookTemplateSections_1.findTemplateMarkers)(includeBody ? html : body);
|
|
145
|
+
return {
|
|
146
|
+
entryId: item.id,
|
|
147
|
+
subject: item.subject,
|
|
148
|
+
htmlBody: html,
|
|
149
|
+
bodyPreview: includeBody ? '' : body.trim().slice(0, PREVIEW_CHARS),
|
|
150
|
+
sections: markers.sections,
|
|
151
|
+
placeholders: markers.placeholders,
|
|
152
|
+
lastModified: item.lastModified,
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
return { folderFound: true, folderPath, templates, availableFolders: [] };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Save a new template email into a mailbox folder (default "Templates"),
|
|
159
|
+
* creating the folder at the mailbox root if it doesn't exist. The item is a
|
|
160
|
+
* plain unsent mail (subject + HTML body) that can be edited in Outlook.
|
|
161
|
+
* Never overwrites an existing item — it always adds a new one, so check with
|
|
162
|
+
* readTemplateEmails first and only call after the user has agreed to create it.
|
|
163
|
+
*/
|
|
164
|
+
async function saveTemplateEmail(emailAccount, subject, htmlBody, folderName = 'Templates') {
|
|
165
|
+
const script = `${scripts_1.FIND_FOLDER_HANDLER}
|
|
166
|
+
tell application "Microsoft Outlook"
|
|
167
|
+
${(0, scripts_1.accountLookupSnippet)(emailAccount)}
|
|
168
|
+
set rootFolder to root folder of targetAcct
|
|
169
|
+
end tell
|
|
170
|
+
set theFolder to my findFolderByName(rootFolder, "${(0, run_1.asEscape)(folderName)}", ${TEMPLATE_SEARCH_DEPTH})
|
|
171
|
+
tell application "Microsoft Outlook"
|
|
172
|
+
set wasCreated to false
|
|
173
|
+
if theFolder is missing value then
|
|
174
|
+
set theFolder to (make new mail folder at rootFolder with properties {name:"${(0, run_1.asEscape)(folderName)}"})
|
|
175
|
+
set wasCreated to true
|
|
176
|
+
end if
|
|
177
|
+
set countBefore to count of messages of theFolder
|
|
178
|
+
set newMsg to make new outgoing message with properties {subject:"${(0, run_1.asEscape)(subject)}", content:"${(0, run_1.asEscape)(htmlBody)}"}
|
|
179
|
+
try
|
|
180
|
+
set account of newMsg to targetAcct
|
|
181
|
+
end try
|
|
182
|
+
-- 'move' reports success even where it silently does nothing, so a template
|
|
183
|
+
-- that was never filed must not be reported as saved.
|
|
184
|
+
move newMsg to theFolder
|
|
185
|
+
if (count of messages of theFolder) is not greater than countBefore then error "Outlook did not file the template in '" & (name of theFolder) & "'."
|
|
186
|
+
return ${(0, run_1.asRow)(['(name of theFolder as string)', '(wasCreated as string)'])}
|
|
187
|
+
end tell`;
|
|
188
|
+
const parts = (0, run_1.summaryFields)(await (0, run_1.runOsaScript)(script, 120000));
|
|
189
|
+
return {
|
|
190
|
+
folderPath: (0, scripts_1.macFolderPath)(emailAccount, (0, run_1.field)(parts, 0) || folderName, []),
|
|
191
|
+
folderCreated: (0, run_1.boolField)(parts, 1),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Open an Outlook compose window pre-filled with `currentHtml`, wait for the
|
|
196
|
+
* user to close it, and return the HTML they saved.
|
|
197
|
+
*
|
|
198
|
+
* Deliberately unbounded: the run lasts as long as the person is editing. Give
|
|
199
|
+
* it an AbortSignal (`bridge.withOptions({ signal })`) if the caller needs a way
|
|
200
|
+
* to give up on them.
|
|
201
|
+
*
|
|
202
|
+
* The macOS half of the round trip differs from Windows in one visible way: an
|
|
203
|
+
* edited draft is filed by answering Outlook's own "save this message?" prompt
|
|
204
|
+
* on close, so the subject tells the user to save rather than to press a key.
|
|
205
|
+
*/
|
|
206
|
+
async function editEmailTemplate(label, currentHtml) {
|
|
207
|
+
const subject = `${label} - Save and close when done`;
|
|
208
|
+
const script = `tell application "Microsoft Outlook"
|
|
209
|
+
set editSubject to "${(0, run_1.asEscape)(subject)}"
|
|
210
|
+
set draftMsg to make new outgoing message with properties {subject:editSubject, content:"${(0, run_1.asEscape)(currentHtml)}"}
|
|
211
|
+
open draftMsg
|
|
212
|
+
activate
|
|
213
|
+
end tell
|
|
214
|
+
|
|
215
|
+
-- Poll until the compose window is gone. A draft window carries the message it
|
|
216
|
+
-- is editing as its object, so the one we opened is identified by subject rather
|
|
217
|
+
-- than by counting windows the user may also have open.
|
|
218
|
+
set stillOpen to true
|
|
219
|
+
repeat while stillOpen
|
|
220
|
+
delay 0.7
|
|
221
|
+
set stillOpen to false
|
|
222
|
+
tell application "Microsoft Outlook"
|
|
223
|
+
try
|
|
224
|
+
repeat with w in draft windows
|
|
225
|
+
try
|
|
226
|
+
if ((subject of (object of w)) as string) is editSubject then
|
|
227
|
+
set stillOpen to true
|
|
228
|
+
exit repeat
|
|
229
|
+
end if
|
|
230
|
+
end try
|
|
231
|
+
end repeat
|
|
232
|
+
end try
|
|
233
|
+
end tell
|
|
234
|
+
end repeat
|
|
235
|
+
|
|
236
|
+
tell application "Microsoft Outlook"
|
|
237
|
+
-- Prefer the saved draft: it reflects what the user chose to keep, and it
|
|
238
|
+
-- survives the window closing. Any leftover editor drafts are removed so
|
|
239
|
+
-- they don't pile up in the Drafts folder.
|
|
240
|
+
set savedHtml to ""
|
|
241
|
+
try
|
|
242
|
+
repeat with acctList in {exchange accounts, imap accounts, pop accounts}
|
|
243
|
+
repeat with a in acctList
|
|
244
|
+
try
|
|
245
|
+
repeat with m in (messages of (drafts of a))
|
|
246
|
+
try
|
|
247
|
+
if ((subject of m) as string) is editSubject then
|
|
248
|
+
if savedHtml is "" then set savedHtml to (content of m) as string
|
|
249
|
+
delete m
|
|
250
|
+
end if
|
|
251
|
+
end try
|
|
252
|
+
end repeat
|
|
253
|
+
end try
|
|
254
|
+
end repeat
|
|
255
|
+
end repeat
|
|
256
|
+
end try
|
|
257
|
+
return my sanitize(savedHtml)
|
|
258
|
+
end tell`;
|
|
259
|
+
// 0 disables the timeout: this run is as long as the edit takes.
|
|
260
|
+
const saved = await (0, run_1.runOsaScript)(script, 0);
|
|
261
|
+
if (!saved) {
|
|
262
|
+
throw new errors_1.NotFoundError('template', 'No template saved. Did you save the draft before closing?');
|
|
263
|
+
}
|
|
264
|
+
return saved;
|
|
265
|
+
}
|
|
266
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/mac/templates.ts"],"names":[],"mappings":"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"}
|
package/dist/mail.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { MailFolderRef } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Clamp a caller's count to the range an operation actually accepts.
|
|
4
|
+
*
|
|
5
|
+
* These bounds ARE the argument contract — how far back a scan reaches, how many
|
|
6
|
+
* rows come back, how deep a folder walk goes — so they are stated here rather
|
|
7
|
+
* than once per platform, which is how the two came to disagree about what a
|
|
8
|
+
* `daysBack` of 0 means.
|
|
9
|
+
*/
|
|
10
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
11
|
+
/**
|
|
12
|
+
* The Outlook folders addressable by name rather than by walking from the Inbox,
|
|
13
|
+
* with their olDefaultFolders id. Sent Items is the one that earns this: "has this
|
|
14
|
+
* already gone out?" is otherwise unanswerable, and no amount of Inbox scanning
|
|
15
|
+
* substitutes for it.
|
|
16
|
+
*
|
|
17
|
+
* A well-known name always wins over a user folder of the same name — reach the
|
|
18
|
+
* latter by qualifying it (`Inbox\Drafts`).
|
|
19
|
+
*/
|
|
20
|
+
export declare const WELL_KNOWN_FOLDERS: Record<string, number>;
|
|
21
|
+
/**
|
|
22
|
+
* Why `deleteOutlookEmails` refuses a message, worded once for both platforms.
|
|
23
|
+
*
|
|
24
|
+
* It reaches the caller verbatim in a per-email `reason`, so the two generated
|
|
25
|
+
* scripts must not word it independently — the same reasoning that put the
|
|
26
|
+
* attachment "not found" sentence in `shared/attachmentMatch.ts`. It also names
|
|
27
|
+
* the option a consumer of THIS package actually passes: the text used to say
|
|
28
|
+
* `allow_protected`, which is the tool-layer spelling and not a name this
|
|
29
|
+
* package exposes.
|
|
30
|
+
*/
|
|
31
|
+
export declare const PROTECTED_MAIL_REASON = "received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override";
|
|
32
|
+
/**
|
|
33
|
+
* Roots whose items are OUTGOING mail.
|
|
34
|
+
*
|
|
35
|
+
* Outgoing mail carries a sent timestamp where received mail carries a received
|
|
36
|
+
* one, and both platforms have to pick the same folders out on that basis —
|
|
37
|
+
* which is why it lives beside `WELL_KNOWN_FOLDERS` rather than as raw
|
|
38
|
+
* `olDefaultFolders` ids compared inline in each reader.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isOutgoingRoot(rootId: number): boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a caller's folder string to a well-known root plus the segments below it.
|
|
43
|
+
*
|
|
44
|
+
* Accepts every shape a user has to hand: a bare name ("Invoices"), a relative
|
|
45
|
+
* path ("Invoices\\Paid"), a well-known folder ("Sent Items"), a path under one
|
|
46
|
+
* ("Deleted Items\\2026"), and the full FolderPath listInboxFolders prints
|
|
47
|
+
* ("\\\\team@x.com\\Inbox\\Invoices").
|
|
48
|
+
*
|
|
49
|
+
* An unrecognized first segment means Inbox-relative, which is what keeps a bare
|
|
50
|
+
* subfolder name working unchanged.
|
|
51
|
+
*/
|
|
52
|
+
export declare function mailFolderRef(folder: string): MailFolderRef;
|
|
53
|
+
/**
|
|
54
|
+
* Split a plain-text reply into the sender's own text and the quoted thread
|
|
55
|
+
* below it.
|
|
56
|
+
*
|
|
57
|
+
* This matters more than it looks: the quoted original of a reply carries the
|
|
58
|
+
* text of the message it answers. Handing a caller one blob invites a figure
|
|
59
|
+
* from the outgoing message to be read back as the sender's answer, which is a
|
|
60
|
+
* worse failure than the truncation this exists to fix.
|
|
61
|
+
*
|
|
62
|
+
* Scans for the earliest of the separators Outlook and the common webmail
|
|
63
|
+
* clients emit. If the split would leave no new text at all — a bottom-posted
|
|
64
|
+
* reply, or a body that opens on a quote — it is abandoned and everything is
|
|
65
|
+
* returned as `body`, since dropping the sender's actual words is the one
|
|
66
|
+
* outcome worth avoiding.
|
|
67
|
+
*/
|
|
68
|
+
export declare function splitQuotedOriginal(text: string): {
|
|
69
|
+
body: string;
|
|
70
|
+
quoted: string;
|
|
71
|
+
separator: string;
|
|
72
|
+
};
|
package/dist/mail.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROTECTED_MAIL_REASON = exports.WELL_KNOWN_FOLDERS = void 0;
|
|
4
|
+
exports.clamp = clamp;
|
|
5
|
+
exports.isOutgoingRoot = isOutgoingRoot;
|
|
6
|
+
exports.mailFolderRef = mailFolderRef;
|
|
7
|
+
exports.splitQuotedOriginal = splitQuotedOriginal;
|
|
8
|
+
/**
|
|
9
|
+
* Clamp a caller's count to the range an operation actually accepts.
|
|
10
|
+
*
|
|
11
|
+
* These bounds ARE the argument contract — how far back a scan reaches, how many
|
|
12
|
+
* rows come back, how deep a folder walk goes — so they are stated here rather
|
|
13
|
+
* than once per platform, which is how the two came to disagree about what a
|
|
14
|
+
* `daysBack` of 0 means.
|
|
15
|
+
*/
|
|
16
|
+
function clamp(value, min, max) {
|
|
17
|
+
return Math.max(min, Math.min(max, Math.floor(value)));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The Outlook folders addressable by name rather than by walking from the Inbox,
|
|
21
|
+
* with their olDefaultFolders id. Sent Items is the one that earns this: "has this
|
|
22
|
+
* already gone out?" is otherwise unanswerable, and no amount of Inbox scanning
|
|
23
|
+
* substitutes for it.
|
|
24
|
+
*
|
|
25
|
+
* A well-known name always wins over a user folder of the same name — reach the
|
|
26
|
+
* latter by qualifying it (`Inbox\Drafts`).
|
|
27
|
+
*/
|
|
28
|
+
exports.WELL_KNOWN_FOLDERS = {
|
|
29
|
+
inbox: 6,
|
|
30
|
+
'sent items': 5,
|
|
31
|
+
sent: 5,
|
|
32
|
+
'sent mail': 5,
|
|
33
|
+
drafts: 16,
|
|
34
|
+
'deleted items': 3,
|
|
35
|
+
deleted: 3,
|
|
36
|
+
trash: 3,
|
|
37
|
+
'junk email': 23,
|
|
38
|
+
junk: 23,
|
|
39
|
+
outbox: 4,
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Why `deleteOutlookEmails` refuses a message, worded once for both platforms.
|
|
43
|
+
*
|
|
44
|
+
* It reaches the caller verbatim in a per-email `reason`, so the two generated
|
|
45
|
+
* scripts must not word it independently — the same reasoning that put the
|
|
46
|
+
* attachment "not found" sentence in `shared/attachmentMatch.ts`. It also names
|
|
47
|
+
* the option a consumer of THIS package actually passes: the text used to say
|
|
48
|
+
* `allow_protected`, which is the tool-layer spelling and not a name this
|
|
49
|
+
* package exposes.
|
|
50
|
+
*/
|
|
51
|
+
exports.PROTECTED_MAIL_REASON = 'received or sent mail (Inbox/Sent Items or a subfolder) - pass allowProtected to override';
|
|
52
|
+
/**
|
|
53
|
+
* Roots whose items are OUTGOING mail.
|
|
54
|
+
*
|
|
55
|
+
* Outgoing mail carries a sent timestamp where received mail carries a received
|
|
56
|
+
* one, and both platforms have to pick the same folders out on that basis —
|
|
57
|
+
* which is why it lives beside `WELL_KNOWN_FOLDERS` rather than as raw
|
|
58
|
+
* `olDefaultFolders` ids compared inline in each reader.
|
|
59
|
+
*/
|
|
60
|
+
function isOutgoingRoot(rootId) {
|
|
61
|
+
return rootId === exports.WELL_KNOWN_FOLDERS['sent items']
|
|
62
|
+
|| rootId === exports.WELL_KNOWN_FOLDERS.outbox
|
|
63
|
+
|| rootId === exports.WELL_KNOWN_FOLDERS.drafts;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a caller's folder string to a well-known root plus the segments below it.
|
|
67
|
+
*
|
|
68
|
+
* Accepts every shape a user has to hand: a bare name ("Invoices"), a relative
|
|
69
|
+
* path ("Invoices\\Paid"), a well-known folder ("Sent Items"), a path under one
|
|
70
|
+
* ("Deleted Items\\2026"), and the full FolderPath listInboxFolders prints
|
|
71
|
+
* ("\\\\team@x.com\\Inbox\\Invoices").
|
|
72
|
+
*
|
|
73
|
+
* An unrecognized first segment means Inbox-relative, which is what keeps a bare
|
|
74
|
+
* subfolder name working unchanged.
|
|
75
|
+
*/
|
|
76
|
+
function mailFolderRef(folder) {
|
|
77
|
+
const rest = folder.trim().replace(/^\\\\[^\\/]+[\\/]/, '');
|
|
78
|
+
const segments = rest.split(/[\\/]+/).map(s => s.trim()).filter(Boolean);
|
|
79
|
+
if (segments.length > 0) {
|
|
80
|
+
const head = segments[0].toLowerCase();
|
|
81
|
+
if (Object.prototype.hasOwnProperty.call(exports.WELL_KNOWN_FOLDERS, head)) {
|
|
82
|
+
return { rootId: exports.WELL_KNOWN_FOLDERS[head], rootLabel: segments[0], segments: segments.slice(1) };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { rootId: 6, rootLabel: 'Inbox', segments };
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Split a plain-text reply into the sender's own text and the quoted thread
|
|
89
|
+
* below it.
|
|
90
|
+
*
|
|
91
|
+
* This matters more than it looks: the quoted original of a reply carries the
|
|
92
|
+
* text of the message it answers. Handing a caller one blob invites a figure
|
|
93
|
+
* from the outgoing message to be read back as the sender's answer, which is a
|
|
94
|
+
* worse failure than the truncation this exists to fix.
|
|
95
|
+
*
|
|
96
|
+
* Scans for the earliest of the separators Outlook and the common webmail
|
|
97
|
+
* clients emit. If the split would leave no new text at all — a bottom-posted
|
|
98
|
+
* reply, or a body that opens on a quote — it is abandoned and everything is
|
|
99
|
+
* returned as `body`, since dropping the sender's actual words is the one
|
|
100
|
+
* outcome worth avoiding.
|
|
101
|
+
*/
|
|
102
|
+
function splitQuotedOriginal(text) {
|
|
103
|
+
const lines = text.split(/\r?\n/);
|
|
104
|
+
const whole = { body: text, quoted: '', separator: '' };
|
|
105
|
+
for (let i = 0; i < lines.length; i++) {
|
|
106
|
+
const line = lines[i].trim();
|
|
107
|
+
let separator = '';
|
|
108
|
+
if (/^-{2,}\s*original message\s*-{2,}$/i.test(line))
|
|
109
|
+
separator = '-----Original Message-----';
|
|
110
|
+
// Outlook's HTML thread rule, which sits directly above the From:/Sent: block.
|
|
111
|
+
else if (/^_{10,}$/.test(line))
|
|
112
|
+
separator = 'Outlook thread rule';
|
|
113
|
+
// "On Tue, Jul 21, 2026 at 9:14 AM John Doe <j@x.com> wrote:"
|
|
114
|
+
else if (/^on\b.{5,300}\bwrote:$/i.test(line))
|
|
115
|
+
separator = 'On … wrote:';
|
|
116
|
+
else if (/^>/.test(lines[i]))
|
|
117
|
+
separator = '> quoted lines';
|
|
118
|
+
// A bare header block: "From: …" followed by another header line.
|
|
119
|
+
else if (/^from:\s*\S/i.test(line) && /^(sent|date|to|subject|cc):/i.test((lines[i + 1] || '').trim())) {
|
|
120
|
+
separator = 'From:/Sent: header block';
|
|
121
|
+
}
|
|
122
|
+
if (!separator)
|
|
123
|
+
continue;
|
|
124
|
+
const body = lines.slice(0, i).join('\n').trimEnd();
|
|
125
|
+
if (!body.trim())
|
|
126
|
+
return whole;
|
|
127
|
+
return { body, quoted: lines.slice(i).join('\n').trim(), separator };
|
|
128
|
+
}
|
|
129
|
+
return whole;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=mail.js.map
|
package/dist/mail.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mail.js","sourceRoot":"","sources":["../src/mail.ts"],"names":[],"mappings":";;;AAeA,sBAEC;AA8CD,wCAIC;AAaD,sCAUC;AAiBD,kDAsBC;AA1HD;;;;;;;GAOG;AACH,SAAgB,KAAK,CAAC,KAAa,EAAE,GAAW,EAAE,GAAW;IACzD,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;;;GAQG;AACU,QAAA,kBAAkB,GAA2B;IACtD,KAAK,EAAE,CAAC;IACR,YAAY,EAAE,CAAC;IACf,IAAI,EAAE,CAAC;IACP,WAAW,EAAE,CAAC;IACd,MAAM,EAAE,EAAE;IACV,eAAe,EAAE,CAAC;IAClB,OAAO,EAAE,CAAC;IACV,KAAK,EAAE,CAAC;IACR,YAAY,EAAE,EAAE;IAChB,IAAI,EAAE,EAAE;IACR,MAAM,EAAE,CAAC;CACZ,CAAC;AAEF;;;;;;;;;GASG;AACU,QAAA,qBAAqB,GAC9B,2FAA2F,CAAC;AAEhG;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,MAAc;IACzC,OAAO,MAAM,KAAK,0BAAkB,CAAC,YAAY,CAAC;WAC3C,MAAM,KAAK,0BAAkB,CAAC,MAAM;WACpC,MAAM,KAAK,0BAAkB,CAAC,MAAM,CAAC;AAChD,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,aAAa,CAAC,MAAc;IACxC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACzE,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QACvC,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,0BAAkB,EAAE,IAAI,CAAC,EAAE,CAAC;YACjE,OAAO,EAAE,MAAM,EAAE,0BAAkB,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACrG,CAAC;IACL,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,SAAgB,mBAAmB,CAAC,IAAY;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IACxD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,IAAI,SAAS,GAAG,EAAE,CAAC;QACnB,IAAI,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,4BAA4B,CAAC;QAC/F,+EAA+E;aAC1E,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,qBAAqB,CAAC;QAClE,8DAA8D;aACzD,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS,GAAG,aAAa,CAAC;aACpE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS,GAAG,gBAAgB,CAAC;QAC3D,kEAAkE;aAC7D,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,8BAA8B,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACrG,SAAS,GAAG,0BAA0B,CAAC;QAC3C,CAAC;QACD,IAAI,CAAC,SAAS;YAAE,SAAS;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QACpD,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,KAAK,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section + placeholder composition for the operator's Outlook template emails.
|
|
3
|
+
*
|
|
4
|
+
* A template email is a normal saved mail the operator edits in Outlook, so the
|
|
5
|
+
* greeting, closing and signature are theirs. Where one template serves several
|
|
6
|
+
* reply types, the variants live inside it between markers:
|
|
7
|
+
*
|
|
8
|
+
* Hello,
|
|
9
|
+
* [[QUOTE]] Thank you for your quote... [[/QUOTE]]
|
|
10
|
+
* [[NOQUOTE]] Thank you for letting us know... [[/NOQUOTE]]
|
|
11
|
+
* [[CLARIFY]] {{QUESTIONS}} [[/CLARIFY]]
|
|
12
|
+
* Best regards, <signature>
|
|
13
|
+
*
|
|
14
|
+
* Composing keeps the requested section, drops the others, and substitutes any
|
|
15
|
+
* {{PLACEHOLDER}} — so the shared wording is written once and the caller only
|
|
16
|
+
* names a section instead of carrying (and editing) 40-50k of Word HTML.
|
|
17
|
+
*
|
|
18
|
+
* Everything here works on the raw HTML string rather than a parsed tree: Word's
|
|
19
|
+
* markup carries conditional comments and namespaced tags that a round-trip
|
|
20
|
+
* through a parser would rewrite, and the reply body must stay byte-identical to
|
|
21
|
+
* what the operator wrote.
|
|
22
|
+
*/
|
|
23
|
+
export interface TemplateMarkers {
|
|
24
|
+
/** Section names with both an opening and a closing marker, in document order. */
|
|
25
|
+
sections: string[];
|
|
26
|
+
/** `{{NAME}}` placeholders found anywhere in the template. */
|
|
27
|
+
placeholders: string[];
|
|
28
|
+
}
|
|
29
|
+
/** Find the markers in a template body (HTML or the plain-text body Outlook derives). */
|
|
30
|
+
export declare function findTemplateMarkers(body: string): TemplateMarkers;
|
|
31
|
+
/** Replace every occurrence of `token` with `value`, which is never rescanned. */
|
|
32
|
+
export declare function replaceToken(html: string, token: string, value: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Remove `token` along with the line it sits on — the table row or paragraph,
|
|
35
|
+
* label included. For an optional detail this is the difference between the email
|
|
36
|
+
* not mentioning it at all and it carrying a heading with nothing under it.
|
|
37
|
+
* Falls back to deleting just the token when it isn't inside a line element.
|
|
38
|
+
*/
|
|
39
|
+
export declare function removeTokenLine(html: string, token: string): string;
|
|
40
|
+
/** Which of `tokens` the template actually uses — what the caller has to supply. */
|
|
41
|
+
export declare function findTokens(html: string, tokens: string[]): string[];
|
|
42
|
+
/**
|
|
43
|
+
* `{Token}`-shaped text still in a filled-in body: a placeholder misspelled in
|
|
44
|
+
* Outlook, or one the app has no field for. Reads the visible text only, so the
|
|
45
|
+
* braces in a Word <style> block are not mistaken for placeholders.
|
|
46
|
+
*/
|
|
47
|
+
export declare function findUnfilledTokens(html: string): string[];
|
|
48
|
+
export interface ComposeOptions {
|
|
49
|
+
/** Section to keep; the other sections are dropped along with their markers. */
|
|
50
|
+
section?: string;
|
|
51
|
+
/** `{{NAME}}` → HTML to substitute. Names are matched case-insensitively. */
|
|
52
|
+
placeholders?: Record<string, string>;
|
|
53
|
+
/** Template name, for error messages. */
|
|
54
|
+
label?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a template body down to the one reply that goes out: keep `section`,
|
|
58
|
+
* drop the rest, fill the placeholders. Throws rather than guessing — a template
|
|
59
|
+
* the operator edited into an unusable state must stop the run, not quietly send
|
|
60
|
+
* a reply with `[[QUOTE]]` or an empty question list in it.
|
|
61
|
+
*/
|
|
62
|
+
export declare function composeTemplateBody(html: string, options?: ComposeOptions): string;
|