Fix garbled ticket text from HTML emails (Outlook VML/entities)
extractBody() used a naive tag-stripping regex as a fallback, which
strips <style> tags but leaves their CSS content behind as visible
text, and never decodes HTML entities. Outlook's VML style block
(v\:* {behavior:url(#default#VML);} ...) and /« entities
were leaking straight into ticket bodies as a result.
Now uses html-to-text (already a transitive dep via mailparser,
promoted to direct) to convert the HTML part whenever present, which
correctly drops <head>/<style> content and decodes entities. Also
stopped trusting parsed.text unconditionally — some senders' plain-text
alternative isn't actually clean, so HTML is now the primary source
whenever available.
This commit is contained in:
1 parent
8844323cad
commit
87cf045715
4 files changed
+20
-2
No files matched your search
Generated
+1
@@ -12,6 +12,7 @@
|
|||||||
"better-sqlite3": "^12.11.1",
|
"better-sqlite3": "^12.11.1",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "^0.45.2",
|
||||||
"framer-motion": "^12.4.7",
|
"framer-motion": "^12.4.7",
|
||||||
|
"html-to-text": "^10.0.0",
|
||||||
"imapflow": "^1.5.0",
|
"imapflow": "^1.5.0",
|
||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
"lucide-react": "^0.545.0",
|
"lucide-react": "^0.545.0",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"better-sqlite3": "^12.11.1",
|
"better-sqlite3": "^12.11.1",
|
||||||
"drizzle-orm": "^0.45.2",
|
"drizzle-orm": "^0.45.2",
|
||||||
"framer-motion": "^12.4.7",
|
"framer-motion": "^12.4.7",
|
||||||
|
"html-to-text": "^10.0.0",
|
||||||
"imapflow": "^1.5.0",
|
"imapflow": "^1.5.0",
|
||||||
"ldapjs": "^3.0.7",
|
"ldapjs": "^3.0.7",
|
||||||
"lucide-react": "^0.545.0",
|
"lucide-react": "^0.545.0",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ImapFlow } from "imapflow";
|
import { ImapFlow } from "imapflow";
|
||||||
import { simpleParser, type ParsedMail } from "mailparser";
|
import { simpleParser, type ParsedMail } from "mailparser";
|
||||||
|
import { htmlToText } from "html-to-text";
|
||||||
import { getMailboxSettings, saveMailboxSettings, disableMailboxConfig, type MailboxSettings } from "@/lib/mail/config";
|
import { getMailboxSettings, saveMailboxSettings, disableMailboxConfig, type MailboxSettings } from "@/lib/mail/config";
|
||||||
import { recordEmailInboundMessage } from "@/lib/tickets/service";
|
import { recordEmailInboundMessage } from "@/lib/tickets/service";
|
||||||
import { saveAttachment } from "@/lib/attachments/storage";
|
import { saveAttachment } from "@/lib/attachments/storage";
|
||||||
@@ -28,11 +29,16 @@ function createClient(settings: MailboxSettings): ImapFlow {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefer converting the HTML part ourselves rather than trusting parsed.text
|
||||||
|
// blindly — some senders (Outlook in particular) produce a text/plain
|
||||||
|
// alternative that isn't actually clean, while html-to-text reliably drops
|
||||||
|
// <head>/<style> content (e.g. Outlook's VML behavior: url(...) rules) and
|
||||||
|
// decodes entities, which a naive tag-stripping regex cannot do.
|
||||||
function extractBody(parsed: ParsedMail): string {
|
function extractBody(parsed: ParsedMail): string {
|
||||||
if (parsed.text) return parsed.text;
|
|
||||||
if (typeof parsed.html === "string") {
|
if (typeof parsed.html === "string") {
|
||||||
return parsed.html.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
return htmlToText(parsed.html, { baseElements: { selectors: ["body"] }, wordwrap: false }).trim();
|
||||||
}
|
}
|
||||||
|
if (parsed.text) return parsed.text;
|
||||||
return "(пустое письмо)";
|
return "(пустое письмо)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
declare module "html-to-text" {
|
||||||
|
interface HtmlToTextOptions {
|
||||||
|
wordwrap?: number | false;
|
||||||
|
baseElements?: { selectors?: string[] };
|
||||||
|
[key: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function htmlToText(html: string, options?: HtmlToTextOptions): string;
|
||||||
|
export function convert(html: string, options?: HtmlToTextOptions): string;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user