import fs from 'node:fs'; import path from 'node:path'; import { injectBeforeDocumentClosingBody } from './html-document-injection.mjs'; import { parseMindspaceCoverMeta, upsertMindspaceCoverMeta } from './mindspace-cover-meta.mjs'; import { extractFaqFromHeadings, parseMindspaceGeoMeta, resolveGeoFaq, resolveGeoKeywords, } from './mindspace-geo-meta.mjs'; function htmlWithoutEmbeddedCode(html) { return String(html ?? '') .replace(//gi, ' ') .replace(//gi, ' '); } import { ensurePlatformBrandFooter, injectPlatformBrandVisibilityStyle } from './mindspace-page-tag.mjs'; import { PLATFORM_BRAND_ICON_PATH } from './mindspace-og-tags.mjs'; import { extractCoverSignals } from './mindspace-thumbnails.mjs'; import { extractSeoDescription } from './mindspace-seo-tags.mjs'; import { hasRasterShareImageHint } from './wechat/verify/share-preview.mjs'; export const DEFAULT_RASTER_COVER_URL = 'https://m.tkmind.cn/brand/tkmind-icon.png'; function escapeMetaAttribute(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('"', '"') .replaceAll('<', '<'); } function stripHtml(value) { return String(value ?? '') .replace(/<[^>]+>/g, ' ') .replace(/\s+/g, ' ') .trim(); } function rawTitleFromHtml(html) { return String(html ?? '').match(/]*>([^<]*)<\/title>/i)?.[1]?.trim() ?? ''; } function rawH1FromHtml(html) { const match = String(html ?? '').match(/]*>([\s\S]*?)<\/h1>/i); return match ? stripHtml(match[1]) : ''; } function rawFirstParagraph(html) { const match = String(html ?? '').match(/]*>([\s\S]*?)<\/p>/i); return match ? stripHtml(match[1]) : ''; } function hasMeta(html, name) { return new RegExp(`]+name=["']${name}["']`, 'i').test(String(html ?? '')); } function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } function countH2(html) { return (String(html ?? '').match(/ 0; const hasTag = /]*name=["']mindspace-geo["']/i.test(source); if (hasTag && hasValidSummary) return source; return upsertMindspaceGeoMeta(source, buildMindspaceGeoPayload(source), { forceReplace: true }); } export function buildMindspaceGeoPayload(html) { const source = htmlWithoutEmbeddedCode(html); const title = rawH1FromHtml(source) || rawTitleFromHtml(source) || 'TKMind 页面'; const cover = extractCoverSignals(source); const summary = String(parseMindspaceGeoMeta(source).summary ?? '').trim() || extractSeoDescription(source) || String(cover.subtitle ?? '').trim() || rawFirstParagraph(source) || title; let keywords = resolveGeoKeywords(source); if (keywords.length < 2) { keywords = [title, cover.tag, 'TKMind'].map((item) => String(item ?? '').trim()).filter(Boolean); } keywords = [...new Set(keywords)].slice(0, 8); let faq = resolveGeoFaq(source); if (faq.length < 1) { faq = extractFaqFromHeadings(source); } if (faq.length < 1 && summary) { faq = [{ q: `${title}讲什么?`, a: summary.slice(0, 220) }]; } return { summary: summary.slice(0, 320), keywords, faq: faq.slice(0, 6), }; } export function upsertMindspaceGeoMeta(html, geoPayload, { forceReplace = false } = {}) { const payload = forceReplace ? { ...(geoPayload ?? {}) } : { ...buildMindspaceGeoPayload(html), ...(geoPayload && typeof geoPayload === 'object' ? geoPayload : {}), }; payload.summary = String(payload.summary ?? '').trim().slice(0, 320); payload.keywords = Array.isArray(payload.keywords) ? payload.keywords.map((item) => String(item ?? '').trim()).filter(Boolean).slice(0, 12) : []; payload.faq = Array.isArray(payload.faq) ? payload.faq .map((item) => ({ q: String(item?.q ?? '').trim().slice(0, 120), a: String(item?.a ?? '').trim().slice(0, 500), })) .filter((item) => item.q && item.a && item.a.length >= 4) .slice(0, 6) : []; if (payload.faq.length < 1 && payload.summary) { const topic = String(rawH1FromHtml(html) || rawTitleFromHtml(html) || payload.summary).trim(); payload.faq = [{ q: `${topic}讲什么?`, a: payload.summary.slice(0, 220) }]; } const metaTag = ``; const source = String(html ?? ''); if (hasMeta(source, 'mindspace-geo')) { return source.replace(/]*name=["']mindspace-geo["'][^>]*>\s*/i, `${metaTag.trim()}\n `); } if (/]*name=["']mindspace-cover["'][^>]*>/i.test(source)) { return source.replace( /(]*name=["']mindspace-cover["'][^>]*>)/i, `$1\n ${metaTag.trim()}`, ); } if (/]*name=["']description["'][^>]*>/i.test(source)) { return source.replace( /(]*name=["']description["'][^>]*>)/i, `$1\n ${metaTag.trim()}`, ); } if (/]*>/i.test(source)) { return source.replace(/]*)>/i, `\n ${metaTag.trim()}`); } return `${metaTag.trim()}\n${source}`; } export function upsertDescriptionMeta(html, description) { const text = String(description ?? '').trim(); if (!text || hasMeta(html, 'description')) return String(html ?? ''); const metaTag = ``; const source = String(html ?? ''); if (/]*>/i.test(source)) { return source.replace(/(]*>[^<]*<\/title>)/i, `$1\n ${metaTag}`); } if (/]*>/i.test(source)) { return source.replace(/]*)>/i, `\n ${metaTag}`); } return `${metaTag}\n${source}`; } export function buildMinimalCoverMeta(html, { title = '' } = {}) { const cover = parseMindspaceCoverMeta(html); if (cover) return cover; const signals = extractCoverSignals(String(html ?? '')); const resolvedTitle = title || rawH1FromHtml(html) || rawTitleFromHtml(html) || 'TKMind 页面'; const description = extractSeoDescription(html) || rawFirstParagraph(html) || resolvedTitle; return { tag: signals.tag || '报告', emoji: '📄', accent: signals.accent || '#3b82f6', accent2: signals.accent2 || '#1e293b', subtitle: signals.subtitle || description.slice(0, 80), }; } function humanizeFilename(filePath) { const base = path.basename(String(filePath ?? ''), '.html'); const decoded = decodeURIComponent(base); return decoded .replace(/[-_]+/g, ' ') .replace(/\s+/g, ' ') .trim(); } function isWeakTitle(title) { const value = String(title ?? '').trim(); if (!value || value.length < 2) return true; if (/^(页面|我的页面|未命名|demo|test|index|MindSpace 页面)$/i.test(value)) return true; if (/^[\x00-\x7F]+$/.test(value) && value.length < 4) return true; return false; } function buildFallbackTitle(html, htmlFilePath = '') { const cover = parseMindspaceCoverMeta(html); const fromCover = [cover?.tag, cover?.subtitle] .map((item) => String(item ?? '').trim()) .filter(Boolean) .join(' · '); if (fromCover && !isWeakTitle(fromCover)) return fromCover; const fromH1 = rawH1FromHtml(html); if (fromH1 && !isWeakTitle(fromH1)) return fromH1; const fromFile = humanizeFilename(htmlFilePath); if (fromFile && !isWeakTitle(fromFile)) return fromFile; if (fromFile) return `TKMind · ${fromFile}`; return 'TKMind 精选页面'; } export function improveTitleFromContext(html, htmlFilePath = '') { const source = String(html ?? ''); const current = rawTitleFromHtml(source); if (!isWeakTitle(current)) return source; const candidate = [rawH1FromHtml(source), parseMindspaceCoverMeta(source)?.subtitle, buildFallbackTitle(source, htmlFilePath)] .map((item) => String(item ?? '').trim()) .find((item) => item && !isWeakTitle(item)) ?? ''; if (!candidate) return source; if (/]*>[^<]*<\/title>/i.test(source)) { return source.replace(/]*>[^<]*<\/title>/i, `${candidate}`); } if (/]*>/i.test(source)) { return source.replace(/]*)>/i, `\n ${candidate}`); } return source; } export function ensureH1FromTitle(html) { const source = String(html ?? ''); if (/]*>/i.test(source)) { return source.replace(/]*)>/i, `\n

${title}

`); } return `${source}\n

${title}

`; } export function fixBrokenLocalCover(html, htmlFilePath = '') { const coverMeta = parseMindspaceCoverMeta(html); if (!coverMeta || !htmlFilePath) return String(html ?? ''); const cover = String(coverMeta.cover ?? coverMeta.image ?? '').trim(); if (!cover || /^https?:\/\//i.test(cover) || cover.startsWith('data:')) { return String(html ?? ''); } const clean = cover.split('?')[0].split('#')[0].replace(/^\.\//, ''); const base = path.dirname(htmlFilePath); const target = path.resolve(base, clean); if (target.startsWith(`${base}${path.sep}`) && fs.existsSync(target)) { return String(html ?? ''); } const nextMeta = { ...coverMeta }; delete nextMeta.cover; delete nextMeta.image; return upsertMindspaceCoverMeta(html, nextMeta, { replace: true }); } export function ensureDefaultRasterCover(html, { coverUrl = DEFAULT_RASTER_COVER_URL } = {}) { const source = String(html ?? ''); if (hasRasterShareImageHint(source)) return source; const coverMeta = parseMindspaceCoverMeta(source) ?? buildMinimalCoverMeta(source); return upsertMindspaceCoverMeta(source, { ...coverMeta, cover: coverUrl || DEFAULT_RASTER_COVER_URL, }); } export function buildSeoOutlineSection(html) { const geo = parseMindspaceGeoMeta(html); const title = rawH1FromHtml(html) || rawTitleFromHtml(html) || '本页'; const summary = String(geo.summary ?? extractSeoDescription(html) ?? title).trim(); const faq = resolveGeoFaq(html, geo); const blocks = []; if (summary) { blocks.push( `

内容概要

`, `

${escapeHtml(summary)}

`, ); } for (const item of faq.slice(0, 2)) { blocks.push( `

${escapeHtml(item.q)}

`, `

${escapeHtml(item.a)}

`, ); } if (countH2(blocks.join('\n')) < 2) { blocks.push( `

关于${escapeHtml(title)}

`, `

${escapeHtml(summary || `${title} 由 TKMind 生成,可在浏览器中直接阅读与分享。`)}

`, ); } if (blocks.length === 0) return ''; return `
${blocks.join('\n ')}
`; } export function ensureSeoHeadingStructure(html) { const source = String(html ?? ''); if (countH2(source) >= 2 || /data-mindspace-seo-outline=/i.test(source)) return source; const outline = buildSeoOutlineSection(source); if (!outline) return source; return injectBeforeDocumentClosingBody(source, `\n${outline}\n`); } export function ensureSeoGeoWarningsFixed(html, options = {}) { let next = String(html ?? ''); if (options.fixPlatformBrand !== false) { next = injectPlatformBrandVisibilityStyle(ensurePlatformBrandFooter(next)); } if (options.fixRasterCover !== false) { next = ensureDefaultRasterCover(next, { coverUrl: options.coverUrl }); } if (options.fixHeadingStructure !== false) { next = ensureSeoHeadingStructure(next); } return next; } export function backfillMindspaceSeoGeoHtml(html, options = {}) { const htmlFilePath = String(options.htmlFilePath ?? ''); let next = String(html ?? ''); next = improveTitleFromContext(next, htmlFilePath); next = fixBrokenLocalCover(next, htmlFilePath); next = ensureH1FromTitle(next); const title = rawH1FromHtml(next) || rawTitleFromHtml(next); let geoPayload = buildMindspaceGeoPayload(next); if (!parseMindspaceCoverMeta(next) && options.ensureCover !== false) { next = upsertMindspaceCoverMeta(next, buildMinimalCoverMeta(next, { title })); } next = upsertDescriptionMeta(next, geoPayload.summary); const existingGeo = parseMindspaceGeoMeta(next); const needsGeoUpsert = !hasMeta(next, 'mindspace-geo') || options.forceGeo || !String(existingGeo.summary ?? '').trim(); if (needsGeoUpsert) { next = upsertMindspaceGeoMeta(next, geoPayload, { forceReplace: options.forceGeo || !String(existingGeo.summary ?? '').trim(), }); } next = repairMindspaceGeoMeta(next); if (options.fixWarnings !== false) { next = ensureSeoGeoWarningsFixed(next, options); } return next; }