120 lines · 5.2 KB
import type { DeliveryResult, MailAdapter, OwnerRef, ParsedMail } from "../../core/types.ts";
import { createDM, DiscordApiError, sendMessage, type DiscordFile } from "./discord-rest.ts";
import { htmlToText } from "./html-to-text.ts";

const DISCORD_MESSAGE_CAP = 2000;
const INLINE_BODY_CAP = 1500;
const DISCORD_PAYLOAD_CAP = 25 * 1024 * 1024;

// Email HTML is attacker-controlled: anyone who learns an address can send
// to it, and the tag-matching in htmlToText scales quadratically with input
// size. Cloudflare accepts messages far larger than any real email body, so
// bound what gets parsed to keep worst-case CPU well inside Worker limits.
const MAX_HTML_LENGTH = 256 * 1024;

// If the extracted text is thin but the source HTML was substantial, the
// email is likely all-image content (a common marketing-email pattern) that
// htmlToText correctly has nothing to show for. Rather than leaving the
// user with an empty-looking message, fall back to attaching the raw HTML
// so it's still viewable (open it in a browser, the image URLs it
// references still resolve).
const SPARSE_TEXT_THRESHOLD = 200;
const SUBSTANTIAL_HTML_THRESHOLD = 1500;

// A hard character-count slice can land in the middle of a link's "label
// (url)" text, leaving a dangling fragment. Cut on whole-line boundaries
// instead. Every link produced by htmlToText lives entirely within a
// single line, so this never splits one.
function truncateAtLineBoundary(text: string, maxLength: number): string {
  const lines = text.split("\n");
  let result = "";
  for (const line of lines) {
    const candidate = result ? `${result}\n${line}` : line;
    if (candidate.length > maxLength) {
      break;
    }
    result = candidate;
  }
  return result || text.slice(0, maxLength);
}