/** * Turns an emailed reply into comment text: keeps what the person wrote and drops the quoted original, * signatures and mobile footers that mail clients append. */ const WROTE_LINE = /^On\b.*\bwrote:\s*$/i; const WROTE_END = /\bwrote:\s*$/i; const ON_START = /^On\b/i; const ORIGINAL_MESSAGE = /^-{2,}\s*(Original|Forwarded) Message\s*-{2,}/i; const OUTLOOK_RULE = /^_{10,}\s*$/; const HEADER_BLOCK_START = /^From:\s/i; const HEADER_BLOCK_FIELD = /^(Sent|Date|To|Subject|Cc):\s/i; const SIGNATURE = /^-- ?$/; const MOBILE_FOOTER = /^Sent from my\s/i; const HEADER_LOOKAHEAD = 4; const WRAPPED_LOOKAHEAD = 2; const HTML_ENTITIES: Record = { "&": "&", "<": "<", ">": ">", """: '"', "'": "'", " ": " ", }; /** Plain text for HTML-only mail; quoted blocks are dropped before tags are stripped. */ export function htmlToReplyText(html: string): string { return html .replace(/<(script|style)[\s\S]*?<\/\1>/gi, "") .replace(//gi, "") .replace(/]*class="[^"]*(gmail_quote|moz-cite-prefix)[^"]*"[\s\S]*$/i, "") .replace(//gi, "\n") .replace(/<\/(p|div|li|h[1-6])>/gi, "\n") .replace(/<[^>]+>/g, "") .replace(/&(amp|lt|gt|quot|#39|nbsp);/g, (entity) => HTML_ENTITIES[entity] ?? entity); } function startsQuote(lines: string[], index: number): boolean { const line = lines[index].trim(); if (WROTE_LINE.test(line) || ORIGINAL_MESSAGE.test(line) || OUTLOOK_RULE.test(line)) return true; if (SIGNATURE.test(lines[index]) || MOBILE_FOOTER.test(line)) return true; if (ON_START.test(line)) { for (let next = index + 1; next <= index + WRAPPED_LOOKAHEAD && next < lines.length; next++) { if (WROTE_END.test(lines[next].trim())) return true; } } if (HEADER_BLOCK_START.test(line)) { for (let next = index + 1; next <= index + HEADER_LOOKAHEAD && next < lines.length; next++) { if (HEADER_BLOCK_FIELD.test(lines[next].trim())) return true; } } return false; } export function extractReplyText(text: string | null, html: string | null): string { const source = text?.trim() ? text : html ? htmlToReplyText(html) : ""; const lines = source.replace(/\r\n?/g, "\n").split("\n"); const kept: string[] = []; for (let index = 0; index < lines.length; index++) { if (startsQuote(lines, index)) break; if (lines[index].trimStart().startsWith(">")) continue; kept.push(lines[index].replace(/\s+$/, "")); } return kept .join("\n") .replace(/\n{3,}/g, "\n\n") .trim(); }