fix(wechat): auto-repair missing share preview meta before page delivery
Memind CI / Test, build, and release guards (push) Failing after 3s

Pages with real HTML content but missing description, mindspace-cover, or
platform brand markers are patched in-place so WeChat link delivery no longer
fails after successful generation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-02 08:08:43 +08:00
parent b9f5720ce0
commit 0a71370bf4
4 changed files with 157 additions and 11 deletions
+107
View File
@@ -0,0 +1,107 @@
import fs from 'node:fs';
import { injectBeforeDocumentClosingHead } from '../../html-document-injection.mjs';
import { normalizeCoverMetaSuggestion, upsertMindspaceCoverMeta } from '../../mindspace-cover-meta.mjs';
import { preparePublishedPlatformBrand } from '../../mindspace-page-tag.mjs';
import { artifactFileExists, verifyPageArtifactContent } from './page-artifact.mjs';
import {
hasMindspaceCoverMeta,
hasPlatformBrandMarker,
hasShareDescription,
verifyArtifactSharePreview,
verifySharePreviewMeta,
} from './share-preview.mjs';
function escapeMetaAttribute(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('"', '&quot;')
.replaceAll('<', '&lt;');
}
export function extractPageTitle(html) {
const match = String(html ?? '').match(/<title[^>]*>([\s\S]*?)<\/title>/i);
return match?.[1]?.replace(/<[^>]+>/g, '').trim() ?? '';
}
export function inferCoverMeta({ title = '', topic = '' } = {}) {
const source = `${title} ${topic}`.trim();
let tag = '报告';
let emoji = '📄';
let accent = '#667eea';
let accent2 = '#764ba2';
if (/攻略|旅行|游记|半日|地图|景点|路线/.test(source)) {
tag = '旅行';
emoji = '🏞️';
accent = '#2e7d32';
accent2 = '#1565c0';
} else if (/新闻|早报|热点|资讯|日报/.test(source)) {
tag = '资讯';
emoji = '📰';
accent = '#2563eb';
accent2 = '#0f172a';
} else if (/诗|诗词|散文|文学/.test(source)) {
tag = '文艺';
emoji = '🌿';
accent = '#2d5016';
accent2 = '#1a2a3a';
}
const subtitle = (title || topic || 'MindSpace 页面').trim().slice(0, 80);
return normalizeCoverMetaSuggestion({ tag, emoji, accent, accent2, subtitle, cover: '' });
}
export function repairSharePreviewHtml(html, { title = '', topic = '' } = {}) {
let next = String(html ?? '');
const resolvedTitle = String(title || extractPageTitle(next) || '').trim();
const summary = (topic || resolvedTitle || 'MindSpace 页面').trim().slice(0, 160);
if (!hasShareDescription(next)) {
const meta = `<meta name="description" content="${escapeMetaAttribute(summary)}">`;
next = injectBeforeDocumentClosingHead(next, `${meta}\n`);
}
if (!hasMindspaceCoverMeta(next)) {
next = upsertMindspaceCoverMeta(next, inferCoverMeta({ title: resolvedTitle, topic }));
}
if (!hasPlatformBrandMarker(next)) {
next = preparePublishedPlatformBrand(next);
}
return next;
}
export function repairArtifactSharePreview(artifact, { topic = '' } = {}) {
if (!artifactFileExists(artifact)) {
return { ok: false, reason: 'missing_file', changes: [] };
}
if (!verifyPageArtifactContent(artifact).ok) {
return { ok: false, reason: 'not_repairable_artifact', changes: [] };
}
const localPath = String(artifact.localPath ?? '').trim();
if (!localPath) {
return { ok: false, reason: 'missing_local_path', changes: [] };
}
const before = fs.readFileSync(localPath, 'utf8');
if (verifySharePreviewMeta(before).ok) {
return { ok: true, reason: null, changes: [], alreadyReady: true };
}
const title = extractPageTitle(before);
const after = repairSharePreviewHtml(before, { title, topic });
if (after === before) {
return { ok: false, reason: 'repair_noop', changes: [] };
}
const changes = [];
if (!hasShareDescription(before) && hasShareDescription(after)) changes.push('description');
if (!hasMindspaceCoverMeta(before) && hasMindspaceCoverMeta(after)) changes.push('mindspace_cover');
if (!hasPlatformBrandMarker(before) && hasPlatformBrandMarker(after)) changes.push('platform_brand');
fs.writeFileSync(localPath, after, 'utf8');
const verified = verifyArtifactSharePreview(artifact);
if (!verified.ok) {
return { ok: false, reason: verified.reason ?? 'repair_incomplete', changes };
}
return { ok: true, reason: null, changes };
}