@bobfrankston/mailx-imap 0.1.84 → 0.1.86
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/index.js +54 -16
- package/package.json +11 -11
package/index.js
CHANGED
|
@@ -118,18 +118,30 @@ async function extractPreview(source) {
|
|
|
118
118
|
// remaining base64 data: URIs (rare: text/plain copies generated
|
|
119
119
|
// from a Quill compose pasted-image HTML) collapsed to [image] too.
|
|
120
120
|
let raw;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
// PREFER the plain-text rendering for the summary. The HTML→[image]
|
|
122
|
+
// path turns an image-heavy marketing/tracking email into a useless
|
|
123
|
+
// "[image] [image] [image]…" wall — the "[no summary]" Bob hit
|
|
124
|
+
// 2026-06-04. mailparser's `text` is the text/plain alternative when
|
|
125
|
+
// present, otherwise a tags-and-images-stripped rendering of the HTML,
|
|
126
|
+
// i.e. the actual words. Only fall back to HTML when there's no usable
|
|
127
|
+
// text at all (genuinely image-only mail).
|
|
128
|
+
const textCandidate = (bodyText || "").replace(/\s+/g, " ").trim();
|
|
129
|
+
if (textCandidate.length >= 3) {
|
|
130
|
+
raw = bodyText;
|
|
131
|
+
}
|
|
132
|
+
else if (bodyHtml) {
|
|
133
|
+
// No usable text part. Strip style/script/head + comments (CSS would
|
|
134
|
+
// otherwise leak into the preview — Bob 2026-05-31), then prefer an
|
|
135
|
+
// <img alt="…"> caption over the bare "[image]" token, and collapse
|
|
136
|
+
// runs of placeholders so even this fallback isn't a wall.
|
|
128
137
|
raw = bodyHtml
|
|
129
138
|
.replace(/<!--[\s\S]*?-->/g, " ")
|
|
130
139
|
.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, " ")
|
|
140
|
+
.replace(/<img\b[^>]*\balt\s*=\s*"([^"]+)"[^>]*>/gi, " $1 ")
|
|
141
|
+
.replace(/<img\b[^>]*\balt\s*=\s*'([^']+)'[^>]*>/gi, " $1 ")
|
|
131
142
|
.replace(/<img\b[^>]*>/gi, " [image] ")
|
|
132
|
-
.replace(/<[^>]+>/g, " ")
|
|
143
|
+
.replace(/<[^>]+>/g, " ")
|
|
144
|
+
.replace(/(\[image\]\s*){2,}/g, "[image] ");
|
|
133
145
|
}
|
|
134
146
|
else {
|
|
135
147
|
raw = bodyText;
|
|
@@ -4181,19 +4193,37 @@ export class ImapManager extends EventEmitter {
|
|
|
4181
4193
|
if (host !== this.hostname)
|
|
4182
4194
|
continue;
|
|
4183
4195
|
const pid = parseInt(pidStr);
|
|
4184
|
-
|
|
4185
|
-
|
|
4196
|
+
let ageMs = Infinity;
|
|
4197
|
+
try {
|
|
4198
|
+
ageMs = Date.now() - fs.statSync(path.join(dir, f)).mtimeMs;
|
|
4199
|
+
}
|
|
4200
|
+
catch { /* */ }
|
|
4201
|
+
if (pid === myPid) {
|
|
4202
|
+
// Our own claim. Normally we're actively sending it — leave
|
|
4203
|
+
// it. But the send is now bounded (60s APPEND timeout), so
|
|
4204
|
+
// an OWN claim older than STALE_CLAIM_MS means the send
|
|
4205
|
+
// hung past its timeout, or a prior tick orphaned it (the
|
|
4206
|
+
// release rename failed). Without reclaiming it the file
|
|
4207
|
+
// sits in `.sending-` forever — recovery used to skip every
|
|
4208
|
+
// own-PID claim unconditionally, so a transient connection
|
|
4209
|
+
// wedge pinned the message even after the link recovered
|
|
4210
|
+
// (Bob 2026-06-11: two messages stuck .sending-<ourpid>
|
|
4211
|
+
// while SELECT Outbox was already succeeding again).
|
|
4212
|
+
if (ageMs < STALE_CLAIM_MS)
|
|
4213
|
+
continue;
|
|
4214
|
+
try {
|
|
4215
|
+
fs.renameSync(path.join(dir, f), path.join(dir, original));
|
|
4216
|
+
console.log(` [outbox] Recovered our own stale claim ${f} → ${original} (hung ${Math.round(ageMs / 60_000)}m)`);
|
|
4217
|
+
}
|
|
4218
|
+
catch { /* ignore */ }
|
|
4219
|
+
continue;
|
|
4220
|
+
}
|
|
4186
4221
|
let alive = false;
|
|
4187
4222
|
try {
|
|
4188
4223
|
process.kill(pid, 0);
|
|
4189
4224
|
alive = true;
|
|
4190
4225
|
}
|
|
4191
4226
|
catch { /* dead */ }
|
|
4192
|
-
let ageMs = Infinity;
|
|
4193
|
-
try {
|
|
4194
|
-
ageMs = Date.now() - fs.statSync(path.join(dir, f)).mtimeMs;
|
|
4195
|
-
}
|
|
4196
|
-
catch { /* */ }
|
|
4197
4227
|
// Live PID + recent mtime → genuine sibling owner, leave it.
|
|
4198
4228
|
// Live PID + ancient mtime → recycled PID, sweep. Dead PID → sweep.
|
|
4199
4229
|
if (alive && ageMs < STALE_CLAIM_MS)
|
|
@@ -4326,7 +4356,15 @@ export class ImapManager extends EventEmitter {
|
|
|
4326
4356
|
}
|
|
4327
4357
|
try {
|
|
4328
4358
|
const raw = fs.readFileSync(claimedPath, "utf-8");
|
|
4329
|
-
|
|
4359
|
+
// Bound the APPEND. On a wedged connection (Dovecot
|
|
4360
|
+
// ETIMEDOUT storm) the bare await can hang the full
|
|
4361
|
+
// 300s inactivity timeout, pinning the file in
|
|
4362
|
+
// `.sending-` state the whole time and reading to the
|
|
4363
|
+
// user as "stuck, not sending" (Bob 2026-06-11). A
|
|
4364
|
+
// 60s cap force-closes the socket and throws, so the
|
|
4365
|
+
// catch below releases the claim and the next tick
|
|
4366
|
+
// retries instead of hanging for 5 minutes.
|
|
4367
|
+
await withTimeout(client.appendMessage(outboxPath, raw, ["\\Seen"]), 60_000, client, `outbox APPEND ${file}`);
|
|
4330
4368
|
fs.renameSync(claimedPath, path.join(sentDir, file));
|
|
4331
4369
|
console.log(` [outbox] Moved ${file} to IMAP Outbox → sent/`);
|
|
4332
4370
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/mailx-imap",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.86",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"@bobfrankston/mailx-types": "^0.1.18",
|
|
13
13
|
"@bobfrankston/mailx-settings": "^0.1.26",
|
|
14
14
|
"@bobfrankston/mailx-store": "^0.1.45",
|
|
15
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
16
|
-
"@bobfrankston/tcp-transport": "^0.1.
|
|
17
|
-
"@bobfrankston/smtp-direct": "^0.1.
|
|
18
|
-
"@bobfrankston/mailx-sync": "^0.1.
|
|
19
|
-
"@bobfrankston/oauthsupport": "^1.0.
|
|
15
|
+
"@bobfrankston/iflow-direct": "^0.1.53",
|
|
16
|
+
"@bobfrankston/tcp-transport": "^0.1.7",
|
|
17
|
+
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
18
|
+
"@bobfrankston/mailx-sync": "^0.1.20",
|
|
19
|
+
"@bobfrankston/oauthsupport": "^1.0.32"
|
|
20
20
|
},
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
"@bobfrankston/mailx-types": "^0.1.18",
|
|
41
41
|
"@bobfrankston/mailx-settings": "^0.1.26",
|
|
42
42
|
"@bobfrankston/mailx-store": "^0.1.45",
|
|
43
|
-
"@bobfrankston/iflow-direct": "^0.1.
|
|
44
|
-
"@bobfrankston/tcp-transport": "^0.1.
|
|
45
|
-
"@bobfrankston/smtp-direct": "^0.1.
|
|
46
|
-
"@bobfrankston/mailx-sync": "^0.1.
|
|
47
|
-
"@bobfrankston/oauthsupport": "^1.0.
|
|
43
|
+
"@bobfrankston/iflow-direct": "^0.1.53",
|
|
44
|
+
"@bobfrankston/tcp-transport": "^0.1.7",
|
|
45
|
+
"@bobfrankston/smtp-direct": "^0.1.9",
|
|
46
|
+
"@bobfrankston/mailx-sync": "^0.1.20",
|
|
47
|
+
"@bobfrankston/oauthsupport": "^1.0.32"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
}
|