import fs from 'node:fs/promises'; import path from 'node:path'; import { PUBLISH_ROOT_DIR } from './user-publish.mjs'; const URL_PATTERN = /https?:\/\/[^\s<>"')\]]+\/(?:MindSpace|temp)\/([a-z0-9._-]+)\/([^\s<>"')\]]+\.html)/gi; function decodePathSegment(segment) { try { return decodeURIComponent(segment); } catch { return segment; } } export function extractStaticPageLinks(content, { username } = {}) { const text = String(content ?? ''); const links = []; const seen = new Set(); for (const match of text.matchAll(URL_PATTERN)) { const owner = decodePathSegment(match[1]).toLowerCase(); const relativePath = decodePathSegment(match[2]); if (username && owner !== String(username).trim().toLowerCase()) continue; const key = `${owner}/${relativePath}`; if (seen.has(key)) continue; seen.add(key); links.push({ publicUrl: match[0], owner, relativePath, filename: path.basename(relativePath), }); } return links; } export function resolvePublishHtmlAbsolutePath(h5Root, username, relativePath) { const slug = String(username ?? '').trim().toLowerCase(); const clean = String(relativePath ?? '') .replace(/^\/+/, '') .split('/') .filter((part) => part && part !== '.' && part !== '..') .join('/'); if (!slug || !clean || !clean.toLowerCase().endsWith('.html')) { throw Object.assign(new Error('无效的页面路径'), { code: 'invalid_page_path' }); } const publishRoot = path.resolve(h5Root, PUBLISH_ROOT_DIR, slug); const absolute = path.resolve(publishRoot, clean); if (absolute !== publishRoot && !absolute.startsWith(`${publishRoot}${path.sep}`)) { throw Object.assign(new Error('页面路径越界'), { code: 'invalid_page_path' }); } return absolute; } export async function readPublishHtml(h5Root, username, relativePath) { const absolute = resolvePublishHtmlAbsolutePath(h5Root, username, relativePath); const content = await fs.readFile(absolute, 'utf8'); if (!content.trim()) { throw Object.assign(new Error('页面内容为空'), { code: 'empty_page_content' }); } return { absolute, content, relativePath, filename: path.basename(relativePath) }; } function titleFromHtml(html) { const match = String(html).match(/