@zaclinley/dsh-mailharbor 0.11.0 → 0.12.1
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/CHANGELOG.md +30 -0
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +129 -107
- package/lib/client.js.map +2 -2
- package/lib/index.js +57 -7
- package/lib/index.js.map +2 -2
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -170,8 +170,15 @@ function flagsOf(value) {
|
|
|
170
170
|
function isSeen(flags) {
|
|
171
171
|
return flags.some((flag) => flag.toLowerCase() === "\\seen");
|
|
172
172
|
}
|
|
173
|
+
function isInlineImage(attachment) {
|
|
174
|
+
const type = String(attachment.contentType ?? "").toLowerCase();
|
|
175
|
+
if (!type.startsWith("image/")) return false;
|
|
176
|
+
const disposition = String(attachment.contentDisposition ?? "").toLowerCase();
|
|
177
|
+
const cid = String(attachment.cid ?? "");
|
|
178
|
+
return disposition === "inline" || cid !== "" && disposition !== "attachment";
|
|
179
|
+
}
|
|
173
180
|
function attachmentList(parsed) {
|
|
174
|
-
return (parsed.attachments ?? []).map((attachment, index) => ({
|
|
181
|
+
return (parsed.attachments ?? []).filter((attachment) => !isInlineImage(attachment)).map((attachment, index) => ({
|
|
175
182
|
id: String(index),
|
|
176
183
|
filename: String(attachment.filename ?? "attachment"),
|
|
177
184
|
contentType: String(attachment.contentType ?? "application/octet-stream"),
|
|
@@ -992,6 +999,13 @@ function ensureMailSchema(file) {
|
|
|
992
999
|
db.exec(FTS);
|
|
993
1000
|
} catch {
|
|
994
1001
|
}
|
|
1002
|
+
const columns = db.prepare("PRAGMA table_info(attachments)").all();
|
|
1003
|
+
if (!columns.some((column) => column.name === "content_id")) {
|
|
1004
|
+
db.exec("ALTER TABLE attachments ADD COLUMN content_id TEXT");
|
|
1005
|
+
}
|
|
1006
|
+
if (!columns.some((column) => column.name === "disposition")) {
|
|
1007
|
+
db.exec("ALTER TABLE attachments ADD COLUMN disposition TEXT");
|
|
1008
|
+
}
|
|
995
1009
|
} finally {
|
|
996
1010
|
db.close();
|
|
997
1011
|
}
|
|
@@ -1019,6 +1033,27 @@ var MailDatabase = class {
|
|
|
1019
1033
|
this.db.exec("PRAGMA busy_timeout = 3000");
|
|
1020
1034
|
return this.db;
|
|
1021
1035
|
}
|
|
1036
|
+
/** Whether the backup has added inline-image columns yet. */
|
|
1037
|
+
hasAttachmentColumns() {
|
|
1038
|
+
try {
|
|
1039
|
+
const columns = this.open().prepare("PRAGMA table_info(attachments)").all();
|
|
1040
|
+
return columns.some((column) => column.name === "content_id");
|
|
1041
|
+
} catch {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
attachmentCountSql() {
|
|
1046
|
+
if (!this.hasAttachmentColumns()) {
|
|
1047
|
+
return "(SELECT COUNT(*) FROM attachments a WHERE a.email_id = e.id) AS attachment_count";
|
|
1048
|
+
}
|
|
1049
|
+
return `(SELECT COUNT(*) FROM attachments a
|
|
1050
|
+
WHERE a.email_id = e.id
|
|
1051
|
+
AND NOT (a.content_type LIKE 'image/%' AND (
|
|
1052
|
+
a.disposition = 'inline'
|
|
1053
|
+
OR (a.content_id IS NOT NULL AND a.content_id <> '' AND a.disposition IS NOT 'attachment')
|
|
1054
|
+
OR lower(a.filename) GLOB 'image[0-9]*'
|
|
1055
|
+
))) AS attachment_count`;
|
|
1056
|
+
}
|
|
1022
1057
|
close() {
|
|
1023
1058
|
if (this.db === void 0) return;
|
|
1024
1059
|
try {
|
|
@@ -1089,7 +1124,7 @@ var MailDatabase = class {
|
|
|
1089
1124
|
const clause = where.length === 0 ? "" : `WHERE ${where.join(" AND ")}`;
|
|
1090
1125
|
const columns = `SELECT e.id, e.mailbox, e.uid, e.subject, e.from_name, e.from_addr, e.to_addr, e.cc,
|
|
1091
1126
|
e.message_id, e.date, length(e.body_text) AS text_length,
|
|
1092
|
-
(
|
|
1127
|
+
${this.attachmentCountSql()},
|
|
1093
1128
|
substr(COALESCE(e.body_text, ''), 1, 240) AS snippet`;
|
|
1094
1129
|
const total = Number(
|
|
1095
1130
|
db.prepare(`SELECT COUNT(*) AS count FROM emails e ${clause}`).get(...params)?.count ?? 0
|
|
@@ -1144,7 +1179,7 @@ var MailDatabase = class {
|
|
|
1144
1179
|
const clause = `WHERE ${where.join(" AND ")}`;
|
|
1145
1180
|
const columns = `SELECT e.id, e.mailbox, e.uid, e.subject, e.from_name, e.from_addr, e.to_addr, e.cc,
|
|
1146
1181
|
e.message_id, e.date, length(e.body_text) AS text_length,
|
|
1147
|
-
(
|
|
1182
|
+
${this.attachmentCountSql()},
|
|
1148
1183
|
substr(COALESCE(e.body_text, ''), 1, 240) AS snippet`;
|
|
1149
1184
|
const total = Number(
|
|
1150
1185
|
db.prepare(`SELECT COUNT(*) AS count FROM emails e ${clause}`).get(...params)?.count ?? 0
|
|
@@ -1160,7 +1195,20 @@ var MailDatabase = class {
|
|
|
1160
1195
|
FROM emails WHERE id = ?`
|
|
1161
1196
|
).get(localId);
|
|
1162
1197
|
if (row === void 0) return void 0;
|
|
1163
|
-
const
|
|
1198
|
+
const cidText = String(row.body_html ?? "").toLowerCase();
|
|
1199
|
+
const attachmentColumns = this.hasAttachmentColumns() ? "id, filename, content_type, size, content_id, disposition" : "id, filename, content_type, size";
|
|
1200
|
+
const attachments = db.prepare(`SELECT ${attachmentColumns} FROM attachments WHERE email_id = ? ORDER BY id`).all(localId).filter((item) => {
|
|
1201
|
+
const type = String(item.content_type ?? "").toLowerCase();
|
|
1202
|
+
if (!type.startsWith("image/")) return true;
|
|
1203
|
+
const disposition = String(item.disposition ?? "").toLowerCase();
|
|
1204
|
+
const contentId = String(item.content_id ?? "").toLowerCase();
|
|
1205
|
+
if (disposition === "inline") return false;
|
|
1206
|
+
if (contentId !== "" && disposition !== "attachment") return false;
|
|
1207
|
+
const filename = String(item.filename ?? "").toLowerCase();
|
|
1208
|
+
if (filename !== "" && cidText.includes(`cid:${filename}`)) return false;
|
|
1209
|
+
if (/^image\d+\.[a-z0-9]+$/.test(filename)) return false;
|
|
1210
|
+
return true;
|
|
1211
|
+
}).map((item) => ({
|
|
1164
1212
|
id: String(item.id),
|
|
1165
1213
|
filename: String(item.filename ?? "attachment"),
|
|
1166
1214
|
contentType: String(item.content_type ?? "application/octet-stream"),
|
|
@@ -1377,7 +1425,7 @@ var BackupEngine = class {
|
|
|
1377
1425
|
const emailId = Number(inserted.lastInsertRowid);
|
|
1378
1426
|
const attachments = parsed.attachments ?? [];
|
|
1379
1427
|
const insertAttachment = db.prepare(
|
|
1380
|
-
"INSERT INTO attachments (email_id, filename, content_type, size, hash) VALUES (?, ?, ?, ?, ?)"
|
|
1428
|
+
"INSERT INTO attachments (email_id, filename, content_type, size, hash, content_id, disposition) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
|
1381
1429
|
);
|
|
1382
1430
|
for (const attachment of attachments) {
|
|
1383
1431
|
const data = attachment.content;
|
|
@@ -1388,7 +1436,9 @@ var BackupEngine = class {
|
|
|
1388
1436
|
String(attachment.filename ?? "attachment"),
|
|
1389
1437
|
String(attachment.contentType ?? "application/octet-stream"),
|
|
1390
1438
|
stored.size,
|
|
1391
|
-
stored.hash
|
|
1439
|
+
stored.hash,
|
|
1440
|
+
attachment.cid === void 0 ? null : String(attachment.cid).replace(/^<|>$/g, ""),
|
|
1441
|
+
attachment.contentDisposition === void 0 ? null : String(attachment.contentDisposition)
|
|
1392
1442
|
);
|
|
1393
1443
|
}
|
|
1394
1444
|
try {
|
|
@@ -2444,7 +2494,7 @@ ${conversation.trim()}
|
|
|
2444
2494
|
}
|
|
2445
2495
|
|
|
2446
2496
|
// src/host/version.ts
|
|
2447
|
-
var VERSION = "0.
|
|
2497
|
+
var VERSION = "0.12.1";
|
|
2448
2498
|
|
|
2449
2499
|
// src/host/api.ts
|
|
2450
2500
|
init_smtp();
|