diff options
author | Yuqian Yang <crupest@crupest.life> | 2025-06-30 14:25:02 +0800 |
---|---|---|
committer | Yuqian Yang <crupest@crupest.life> | 2025-06-30 14:25:02 +0800 |
commit | e18d101cae1dfcef29abd102d2908d429f4688d5 (patch) | |
tree | 49c0b1d1c237c674fe603db23d2e174acdea6979 /deno/mail-relay/mail.ts | |
parent | 66e2d76b75ed04ae8a43baefdb970f4cb89c5925 (diff) | |
download | crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.tar.gz crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.tar.bz2 crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.zip |
mail: revert removing.
Diffstat (limited to 'deno/mail-relay/mail.ts')
-rw-r--r-- | deno/mail-relay/mail.ts | 234 |
1 files changed, 0 insertions, 234 deletions
diff --git a/deno/mail-relay/mail.ts b/deno/mail-relay/mail.ts deleted file mode 100644 index 9cc591c..0000000 --- a/deno/mail-relay/mail.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { encodeBase64 } from "@std/encoding/base64"; -import { parse } from "@std/csv/parse"; -import { simpleParseMail } from "./mail-parsing.ts"; - -export class Mail { - #raw; - #parsed; - - constructor(raw: string) { - this.#raw = raw; - this.#parsed = simpleParseMail(raw); - } - - get raw() { - return this.#raw; - } - - set raw(value) { - this.#raw = value; - this.#parsed = simpleParseMail(value); - } - - get parsed() { - return this.#parsed; - } - - toUtf8Bytes(): Uint8Array { - const utf8Encoder = new TextEncoder(); - return utf8Encoder.encode(this.raw); - } - - toBase64(): string { - return encodeBase64(this.raw); - } - - simpleFindAllAddresses(): string[] { - const re = /,?\<?([a-z0-9_'+\-\.]+\@[a-z0-9_'+\-\.]+)\>?,?/gi; - return [...this.raw.matchAll(re)].map((m) => m[1]); - } -} - -export interface MailDeliverRecipientResult { - kind: "success" | "failure"; - message?: string; - cause?: unknown; -} - -export class MailDeliverResult { - message?: string; - smtpMessage?: string; - recipients = new Map<string, MailDeliverRecipientResult>(); - constructor(public mail: Mail) {} - - get hasFailure() { - return this.recipients.values().some((v) => v.kind !== "success"); - } - - generateLogMessage(prefix: string) { - const lines = []; - if (this.message != null) lines.push(`${prefix} message: ${this.message}`); - if (this.smtpMessage != null) { - lines.push(`${prefix} smtpMessage: ${this.smtpMessage}`); - } - for (const [name, result] of this.recipients.entries()) { - const { kind, message } = result; - lines.push(`${prefix} (${name}): ${kind} ${message}`); - } - return lines.join("\n"); - } -} - -export class MailDeliverContext { - readonly recipients: Set<string> = new Set(); - readonly result; - - constructor(public logTag: string, public mail: Mail) { - this.result = new MailDeliverResult(this.mail); - } -} - -export interface MailDeliverHook { - callback(context: MailDeliverContext): Promise<void>; -} - -export abstract class MailDeliverer { - #counter = 1; - #last?: Promise<void>; - - abstract name: string; - preHooks: MailDeliverHook[] = []; - postHooks: MailDeliverHook[] = []; - - constructor(public sync: boolean) {} - - protected abstract doDeliver( - mail: Mail, - context: MailDeliverContext, - ): Promise<void>; - - async deliverRaw(rawMail: string) { - return await this.deliver({ mail: new Mail(rawMail) }); - } - - async #deliverCore(context: MailDeliverContext) { - for (const hook of this.preHooks) { - await hook.callback(context); - } - - await this.doDeliver(context.mail, context); - - for (const hook of this.postHooks) { - await hook.callback(context); - } - } - - async deliver(options: { - mail: Mail; - recipients?: string[]; - logTag?: string; - }): Promise<MailDeliverResult> { - const logTag = options.logTag ?? `[${this.name} ${this.#counter}]`; - this.#counter++; - - if (this.#last != null) { - console.info(logTag, "Wait for last delivering done..."); - await this.#last; - } - - const context = new MailDeliverContext( - logTag, - options.mail, - ); - options.recipients?.forEach((r) => context.recipients.add(r)); - - console.info(context.logTag, "Begin to deliver mail..."); - - const deliverPromise = this.#deliverCore(context); - - if (this.sync) { - this.#last = deliverPromise.then(() => {}, () => {}); - } - - await deliverPromise; - this.#last = undefined; - - console.info(context.logTag, "Deliver result:"); - console.info(context.result.generateLogMessage(context.logTag)); - - if (context.result.hasFailure) { - throw new Error("Failed to deliver to some recipients."); - } - - return context.result; - } -} - -export class RecipientFromHeadersHook implements MailDeliverHook { - constructor(public mailDomain: string) {} - - callback(context: MailDeliverContext) { - if (context.recipients.size !== 0) { - console.warn( - context.logTag, - "Recipients are already filled, skip inferring from headers.", - ); - } else { - [...context.mail.parsed.recipients].filter((r) => - r.endsWith("@" + this.mailDomain) - ).forEach((r) => context.recipients.add(r)); - - console.info( - context.logTag, - "Use recipients inferred from mail headers:", - [...context.recipients].join(", "), - ); - } - return Promise.resolve(); - } -} - -export class FallbackRecipientHook implements MailDeliverHook { - constructor(public fallback: Set<string> = new Set()) {} - - callback(context: MailDeliverContext) { - if (context.recipients.size === 0) { - console.info( - context.logTag, - "Use fallback recipients:" + [...this.fallback].join(", "), - ); - this.fallback.forEach((a) => context.recipients.add(a)); - } - return Promise.resolve(); - } -} - -export class AliasRecipientMailHook implements MailDeliverHook { - #aliasFile; - - constructor(aliasFile: string) { - this.#aliasFile = aliasFile; - } - - async #parseAliasFile(logTag: string): Promise<Map<string, string>> { - const result = new Map(); - if ((await Deno.stat(this.#aliasFile)).isFile) { - const text = await Deno.readTextFile(this.#aliasFile); - const csv = parse(text); - for (const [real, ...aliases] of csv) { - aliases.forEach((a) => result.set(a, real)); - } - } else { - console.warn( - logTag, - `Recipient alias file ${this.#aliasFile} is not found.`, - ); - } - return result; - } - - async callback(context: MailDeliverContext) { - const aliases = await this.#parseAliasFile(context.logTag); - for (const recipient of [...context.recipients]) { - const realRecipients = aliases.get(recipient); - if (realRecipients != null) { - console.info( - context.logTag, - `Recipient alias resolved: ${recipient} => ${realRecipients}.`, - ); - context.recipients.delete(recipient); - context.recipients.add(realRecipients); - } - } - } -} |