const URL_PATTERN = /https?:\/\/[^\s<>"')\]]+\/(?:MindSpace|temp)\/([0-9a-f-]{36}|[a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi; export type MessageSaveKind = 'page' | 'article'; export type MessageSaveOwner = { userId?: string | null; username?: string | null; }; export type MessageSaveActions = { kind: MessageSaveKind; previewUrl: string | null; links: Array<{ publicUrl: string; filename: string }>; }; function decodeSegment(segment: string) { try { return decodeURIComponent(segment); } catch { return segment; } } function normalizeOwnerOptions(owner?: string | MessageSaveOwner): MessageSaveOwner { if (typeof owner === 'string') return { username: owner }; return owner ?? {}; } function normalizeStaticHtmlRelativePath(relativePath: string) { const parts = String(relativePath ?? '') .replace(/^\/+/, '') .split('/') .filter((part) => part && part !== '.' && part !== '..'); if (parts.length === 0) return ''; if (parts[0]?.toLowerCase() === 'public') return ['public', ...parts.slice(1)].join('/'); if (parts.length === 1 && parts[0].toLowerCase().endsWith('.html')) return `public/${parts[0]}`; return parts.join('/'); } function escapeRegExp(value: string) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function encodeUrlPath(relativePath: string) { return relativePath .split('/') .filter(Boolean) .map((part) => encodeURIComponent(part)) .join('/'); } function canonicalizeStaticPageUrl(publicUrl: string, originalRelativePath: string) { const canonicalRelativePath = normalizeStaticHtmlRelativePath(originalRelativePath); const originalClean = originalRelativePath.replace(/^\/+/, ''); if (!canonicalRelativePath || canonicalRelativePath === originalClean) return publicUrl; const suffix = escapeRegExp(encodeUrlPath(originalClean)); return publicUrl.replace(new RegExp(`${suffix}$`), encodeUrlPath(canonicalRelativePath)); } export function extractStaticPageLinks(content: string, owner?: string | MessageSaveOwner) { const { userId, username } = normalizeOwnerOptions(owner); const normalizedUserId = userId?.trim().toLowerCase(); const normalizedUsername = username?.trim().toLowerCase(); const links: Array<{ publicUrl: string; filename: string }> = []; const seen = new Set(); for (const match of content.matchAll(URL_PATTERN)) { const linkOwner = decodeSegment(match[1]).toLowerCase(); const relativePath = decodeSegment(match[2]); const filename = relativePath.split('/').pop() ?? relativePath; if (normalizedUserId) { if (linkOwner !== normalizedUserId && linkOwner !== normalizedUsername) continue; } else if (normalizedUsername && linkOwner !== normalizedUsername) { continue; } const publicUrl = canonicalizeStaticPageUrl(match[0], relativePath); if (seen.has(publicUrl)) continue; seen.add(publicUrl); links.push({ publicUrl, filename }); } return links; } export function getMessageSaveActions(content: string, owner?: string | MessageSaveOwner): MessageSaveActions { const links = extractStaticPageLinks(content, owner); return { kind: links.length > 0 ? 'page' : 'article', previewUrl: links[0]?.publicUrl ?? null, links, }; }