import path from "node:path"; import type { AttachmentAnalysisState, AttachmentKind, MessageAttachment, } from "@/lib/boss-data"; const LARGE_ATTACHMENT_THRESHOLD_BYTES = 20 * 1024 * 1024; const MAX_ATTACHMENT_TEXT_EXCERPT_CHARS = 12_000; function extensionOf(fileName: string) { return path.extname(fileName).toLowerCase(); } export function sanitizeFileName(fileName: string) { const normalized = path .basename(fileName || "attachment") .normalize("NFKC") .replace(/[\u0000-\u001f\u007f]/g, "") .replace(/[<>:"|?*]+/g, "-") .replace(/[\\/]+/g, "-") .replace(/\s+/g, " ") .trim() .replace(/^\.+/, ""); return normalized || "attachment"; } export function detectAttachmentKind(fileName: string, mimeType: string): AttachmentKind { const normalizedMime = (mimeType || "").toLowerCase(); const ext = extensionOf(fileName); if (normalizedMime.startsWith("image/")) return "image"; if (normalizedMime.startsWith("video/")) return "video"; if (normalizedMime === "application/pdf" || ext === ".pdf") return "pdf"; if (normalizedMime.startsWith("text/")) return "text"; if ( normalizedMime.includes("officedocument") || normalizedMime.includes("msword") || normalizedMime.includes("spreadsheet") || normalizedMime.includes("presentation") || [".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".odt", ".ods", ".odp"].includes(ext) ) { return "office"; } if ( [".txt", ".md", ".csv", ".log", ".json", ".yaml", ".yml", ".xml", ".html", ".htm"].includes( ext, ) ) { return "text"; } return "binary"; } export function resolveAttachmentAnalysisState( kind: AttachmentKind, fileSizeBytes: number, ): AttachmentAnalysisState { if (fileSizeBytes > LARGE_ATTACHMENT_THRESHOLD_BYTES) { return "ready_manual"; } if (kind === "image" || kind === "pdf" || kind === "text") { return "queued_auto"; } return "ready_manual"; } export function buildAttachmentDownloadHeaders(attachment: MessageAttachment) { const safeName = sanitizeFileName(attachment.fileName); const encodedName = encodeURIComponent(safeName); return { "Content-Type": attachment.mimeType || "application/octet-stream", "Content-Disposition": `inline; filename="${safeName}"; filename*=UTF-8''${encodedName}`, "Cache-Control": "private, no-store, max-age=0", "X-Content-Type-Options": "nosniff", }; } export function canExtractAttachmentText(attachment: Pick) { return ( attachment.attachmentKind === "text" || (attachment.mimeType || "").toLowerCase().startsWith("text/") ); } export function extractAttachmentTextExcerpt(buffer: Buffer | Uint8Array) { const normalized = Buffer.from(buffer) .toString("utf8") .replace(/\u0000/g, "") .trim(); if (!normalized) { return ""; } if (normalized.length <= MAX_ATTACHMENT_TEXT_EXCERPT_CHARS) { return normalized; } return `${normalized.slice(0, MAX_ATTACHMENT_TEXT_EXCERPT_CHARS)}\n...[已截断]`; }