[WIP] Start tinkering with IMAP
This commit is contained in:
0
src/mail/read.ts
Normal file
0
src/mail/read.ts
Normal file
133
src/mail/replies.ts
Normal file
133
src/mail/replies.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
// EmailReplyParser is a library to parse plain text email content.
|
||||
// The goal is to identify quoted text, signatures, or original content.
|
||||
|
||||
export class ReplyParser {
|
||||
static VERSION = "0.5.11";
|
||||
|
||||
// Splits an email body into a list of Fragments.
|
||||
static read(text: string): Email {
|
||||
return new Email().read(text);
|
||||
}
|
||||
|
||||
// Get the text of the visible portions of the given email body.
|
||||
static parseReply(text: string): string {
|
||||
return this.read(text).visibleText();
|
||||
}
|
||||
}
|
||||
|
||||
export class Email {
|
||||
private fragments: Fragment[] = [];
|
||||
private foundVisible: boolean = false;
|
||||
|
||||
// Gets the combined text of the visible fragments of the email body.
|
||||
visibleText(): string {
|
||||
return this.fragments
|
||||
.filter((fragment) => !fragment.hidden)
|
||||
.map((fragment) => fragment.toString())
|
||||
.join("\n")
|
||||
.trimEnd();
|
||||
}
|
||||
|
||||
// Splits the given text into a list of Fragments.
|
||||
read(text: string): this {
|
||||
let modifiedText = text.slice();
|
||||
|
||||
// Normalize line endings.
|
||||
modifiedText = modifiedText.replace(/\r\n/g, "\n");
|
||||
|
||||
// Handle multi-line reply headers.
|
||||
const multiLineHeaderRegex = /^(?!On.*On\s.+?wrote:)(On\s(.+?)wrote:)$/m;
|
||||
modifiedText = modifiedText.replace(multiLineHeaderRegex, (match) =>
|
||||
match.replace(/\n/g, " ")
|
||||
);
|
||||
|
||||
// Ensure proper splitting for lines of underscores.
|
||||
modifiedText = modifiedText.replace(/([^\n])(?=\n_{7}_+)$/m, "$1\n");
|
||||
|
||||
// Reverse the text for parsing.
|
||||
modifiedText = modifiedText.split("").reverse().join("");
|
||||
|
||||
this.foundVisible = false;
|
||||
let fragment: Fragment | null = null;
|
||||
|
||||
const lines = modifiedText.split("\n");
|
||||
for (const line of lines) {
|
||||
const processedLine = line.trimEnd();
|
||||
const isQuoted = processedLine.endsWith(">");
|
||||
|
||||
if (fragment && processedLine === "") {
|
||||
if (Fragment.isSignature(fragment.lines[fragment.lines.length - 1])) {
|
||||
fragment.signature = true;
|
||||
this.finishFragment(fragment);
|
||||
fragment = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
fragment &&
|
||||
(fragment.quoted === isQuoted ||
|
||||
(fragment.quoted &&
|
||||
(Fragment.isQuoteHeader(processedLine) || processedLine === "")))
|
||||
) {
|
||||
fragment.lines.push(processedLine);
|
||||
} else {
|
||||
if (fragment) {
|
||||
this.finishFragment(fragment);
|
||||
}
|
||||
fragment = new Fragment(isQuoted, processedLine);
|
||||
}
|
||||
}
|
||||
|
||||
if (fragment) {
|
||||
this.finishFragment(fragment);
|
||||
}
|
||||
|
||||
this.fragments.reverse();
|
||||
return this;
|
||||
}
|
||||
|
||||
private finishFragment(fragment: Fragment): void {
|
||||
fragment.finish();
|
||||
if (!this.foundVisible) {
|
||||
if (fragment.quoted || fragment.signature || fragment.toString().trim() === "") {
|
||||
fragment.hidden = true;
|
||||
} else {
|
||||
this.foundVisible = true;
|
||||
}
|
||||
}
|
||||
this.fragments.push(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
class Fragment {
|
||||
static SIGNATURE_REGEX = /(--\s*$|__\s*$|\w-$)|(^(\w+\s+){1,3}ym morf tneS$)/m;
|
||||
static QUOTE_HEADER_REGEX = /^:etorw.*nO$|^.*:(morF|tneS|oT|tcejbuS)$/;
|
||||
|
||||
quoted: boolean;
|
||||
signature: boolean = false;
|
||||
hidden: boolean = false;
|
||||
lines: string[];
|
||||
private content: string | null = null;
|
||||
|
||||
constructor(quoted: boolean, firstLine: string) {
|
||||
this.quoted = quoted;
|
||||
this.lines = [firstLine];
|
||||
}
|
||||
|
||||
static isSignature(line: string): boolean {
|
||||
return this.SIGNATURE_REGEX.test(line);
|
||||
}
|
||||
|
||||
static isQuoteHeader(line: string): boolean {
|
||||
return this.QUOTE_HEADER_REGEX.test(line);
|
||||
}
|
||||
|
||||
finish(): void {
|
||||
this.content = this.lines.join("\n").split("").reverse().join("");
|
||||
this.lines = [];
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.content || "";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user