feat(wechat): require share preview meta before page link delivery
Block service-account page links when HTML lacks mindspace-cover and description so WeChat forwards always get TKMind branding and thumbnail. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import fs from 'node:fs';
|
||||
import { artifactFileExists } from './page-artifact.mjs';
|
||||
|
||||
const RASTER_IMAGE_PATTERN = /\.(png|jpe?g|webp|gif)(?:[?#]|$)/i;
|
||||
|
||||
export function hasMindspaceCoverMeta(html) {
|
||||
return /<meta[^>]*name=["']mindspace-cover["']/i.test(String(html ?? ''));
|
||||
}
|
||||
|
||||
export function hasShareDescription(html) {
|
||||
const source = String(html ?? '');
|
||||
return (
|
||||
/<meta[^>]+property=["']og:description["']/i.test(source) ||
|
||||
/<meta[^>]+name=["']description["']/i.test(source)
|
||||
);
|
||||
}
|
||||
|
||||
export function hasRasterShareImageHint(html) {
|
||||
const source = String(html ?? '');
|
||||
if (/<meta[^>]+property=["']og:image["'][^>]+content=["'][^"']+/i.test(source)) {
|
||||
const match =
|
||||
source.match(/<meta[^>]+property=["']og:image["'][^>]+content=["']([^"']+)["']/i) ??
|
||||
source.match(/<meta[^>]+content=["']([^"']+)["'][^>]+property=["']og:image["']/i);
|
||||
const url = match?.[1] ?? '';
|
||||
if (url && !/\.svg(?:[?#]|$)/i.test(url)) return true;
|
||||
}
|
||||
const coverMatch = source.match(/<meta[^>]*name=["']mindspace-cover["'][^>]*content=(["'])([\s\S]*?)\1/i);
|
||||
if (!coverMatch?.[2]) return false;
|
||||
try {
|
||||
const meta = JSON.parse(coverMatch[2].replaceAll('"', '"'));
|
||||
const cover = String(meta.cover ?? meta.image ?? '').trim();
|
||||
if (!cover) return false;
|
||||
if (/^https?:\/\//i.test(cover)) return RASTER_IMAGE_PATTERN.test(cover);
|
||||
return RASTER_IMAGE_PATTERN.test(cover) || !/\.svg(?:[?#]|$)/i.test(cover);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* WeChat link cards need mindspace-cover (for thumbnail.png) + description.
|
||||
* Visual pages should also reference a raster hero in cover/image when possible.
|
||||
*/
|
||||
export function verifySharePreviewMeta(html) {
|
||||
if (!hasMindspaceCoverMeta(html)) {
|
||||
return { ok: false, reason: 'missing_mindspace_cover' };
|
||||
}
|
||||
if (!hasShareDescription(html)) {
|
||||
return { ok: false, reason: 'missing_description' };
|
||||
}
|
||||
return { ok: true, reason: null };
|
||||
}
|
||||
|
||||
export function verifyArtifactSharePreview(artifact) {
|
||||
if (!artifactFileExists(artifact)) {
|
||||
return { ok: false, reason: 'missing_file' };
|
||||
}
|
||||
const content = fs.readFileSync(artifact.localPath, 'utf8');
|
||||
return verifySharePreviewMeta(content);
|
||||
}
|
||||
|
||||
export function filterSharePreviewReadyArtifacts(artifacts = []) {
|
||||
return artifacts.filter((artifact) => verifyArtifactSharePreview(artifact).ok);
|
||||
}
|
||||
Reference in New Issue
Block a user