2026-04-14 15:11:00 +08:00
|
|
|
import MarkdownIt from "markdown-it";
|
|
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
export type WidgetMessageAssetPayload = {
|
|
|
|
|
assetId: string;
|
|
|
|
|
filename?: string;
|
|
|
|
|
fileSize?: number;
|
|
|
|
|
mimeType?: string;
|
|
|
|
|
url?: string;
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-14 15:11:00 +08:00
|
|
|
const messageMarkdown = new MarkdownIt({
|
|
|
|
|
html: false,
|
|
|
|
|
linkify: true,
|
|
|
|
|
breaks: true,
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
export function parseMessageAssetPayload(
|
|
|
|
|
payload?: string,
|
|
|
|
|
): WidgetMessageAssetPayload | null {
|
|
|
|
|
if (!payload?.trim()) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(payload) as WidgetMessageAssetPayload;
|
|
|
|
|
if (!parsed?.assetId?.trim()) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return parsed;
|
|
|
|
|
} catch {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function renderMessageHTML(message: {
|
|
|
|
|
messageType: string;
|
|
|
|
|
content: string;
|
|
|
|
|
payload?: string;
|
|
|
|
|
}) {
|
|
|
|
|
if (message.messageType === "html") {
|
|
|
|
|
return message.content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const asset = parseMessageAssetPayload(message.payload);
|
|
|
|
|
if (message.messageType === "image") {
|
|
|
|
|
if (asset?.url) {
|
|
|
|
|
return `<p><img src="${escapeHTMLAttr(asset.url)}" alt="${escapeHTMLAttr(
|
|
|
|
|
asset.filename || "image",
|
|
|
|
|
)}"></p>`;
|
|
|
|
|
}
|
|
|
|
|
return "<p>[图片]</p>";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (message.messageType === "attachment") {
|
|
|
|
|
if (asset?.url) {
|
|
|
|
|
const title = escapeHTML(asset.filename || message.content || "附件");
|
|
|
|
|
const meta = formatFileSize(asset.fileSize ?? 0);
|
|
|
|
|
const metaHTML = meta
|
|
|
|
|
? `<div class="im-attachment-meta">${escapeHTML(meta)}</div>`
|
|
|
|
|
: "";
|
|
|
|
|
return `<div class="im-attachment"><a href="${escapeHTMLAttr(
|
|
|
|
|
asset.url,
|
|
|
|
|
)}" target="_blank" rel="noreferrer" download="${escapeHTMLAttr(
|
|
|
|
|
asset.filename || "",
|
|
|
|
|
)}" class="im-attachment-link"><span class="im-attachment-icon" aria-hidden="true">${getAttachmentIconSVG()}</span><span class="im-attachment-content"><span class="im-attachment-title">${title}</span>${metaHTML}</span></a></div>`;
|
|
|
|
|
}
|
|
|
|
|
return `<p>${escapeHTML(message.content || "[附件]")}</p>`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 15:11:00 +08:00
|
|
|
return renderTextMessageHTML(message.content || "");
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function summarizeMessage(message: {
|
|
|
|
|
messageType: string;
|
|
|
|
|
content: string;
|
|
|
|
|
payload?: string;
|
|
|
|
|
}) {
|
|
|
|
|
if (message.messageType === "image") {
|
|
|
|
|
return "[图片]";
|
|
|
|
|
}
|
|
|
|
|
if (message.messageType === "attachment") {
|
|
|
|
|
const asset = parseMessageAssetPayload(message.payload);
|
|
|
|
|
return asset?.filename?.trim() ? `[附件] ${asset.filename.trim()}` : "[附件]";
|
|
|
|
|
}
|
|
|
|
|
if (message.messageType === "html") {
|
|
|
|
|
const text = extractTextFromHTML(message.content);
|
|
|
|
|
if (text.trim()) {
|
|
|
|
|
return text.substring(0, 100);
|
|
|
|
|
}
|
|
|
|
|
if (message.content.includes("<img")) {
|
|
|
|
|
return "[图片]";
|
|
|
|
|
}
|
|
|
|
|
return "[消息]";
|
|
|
|
|
}
|
|
|
|
|
return message.content?.substring(0, 100) || "[消息]";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatFileSize(size: number) {
|
|
|
|
|
if (!Number.isFinite(size) || size <= 0) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
const units = ["B", "KB", "MB", "GB"];
|
|
|
|
|
let value = size;
|
|
|
|
|
let index = 0;
|
|
|
|
|
while (value >= 1024 && index < units.length - 1) {
|
|
|
|
|
value /= 1024;
|
|
|
|
|
index += 1;
|
|
|
|
|
}
|
|
|
|
|
const digits = value >= 10 || index === 0 ? 0 : 1;
|
|
|
|
|
return `${value.toFixed(digits)} ${units[index]}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractTextFromHTML(html: string): string {
|
|
|
|
|
if (typeof document === "undefined") {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
const div = document.createElement("div");
|
|
|
|
|
div.innerHTML = html;
|
|
|
|
|
return div.textContent || div.innerText || "";
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 15:11:00 +08:00
|
|
|
function renderTextMessageHTML(content: string) {
|
|
|
|
|
const value = content.trim();
|
|
|
|
|
if (!value) {
|
|
|
|
|
return "<p></p>";
|
|
|
|
|
}
|
|
|
|
|
return messageMarkdown.render(value);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
function escapeHTML(value: string) {
|
|
|
|
|
return value
|
|
|
|
|
.replaceAll("&", "&")
|
|
|
|
|
.replaceAll("<", "<")
|
|
|
|
|
.replaceAll(">", ">")
|
|
|
|
|
.replaceAll('"', """)
|
|
|
|
|
.replaceAll("'", "'")
|
|
|
|
|
.replaceAll("\n", "<br>");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function escapeHTMLAttr(value: string) {
|
|
|
|
|
return value
|
|
|
|
|
.replaceAll("&", "&")
|
|
|
|
|
.replaceAll('"', """)
|
|
|
|
|
.replaceAll("<", "<")
|
|
|
|
|
.replaceAll(">", ">");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getAttachmentIconSVG() {
|
|
|
|
|
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><path d="M14 2v6h6"></path><path d="M9 15h6"></path><path d="M9 11h2"></path></svg>`;
|
|
|
|
|
}
|