Files
boss/src/lib/boss-attachments.ts
2026-03-29 17:06:54 +08:00

99 lines
3.0 KiB
TypeScript

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<MessageAttachment, "attachmentKind" | "mimeType">) {
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...[已截断]`;
}