#!/usr/bin/env node /** * 用当天已生成的真实新闻页制作 news002 视觉样图: * - 正文与来源仍来自当天真实页 * - 配图只取对应来源文章的 og:image/twitter:image * - 无真实同源图片的卡片不进入样图 * - 不写生产目录,不调用微信 API */ import fs from 'node:fs'; import path from 'node:path'; import process from 'node:process'; import { NEWS002_STYLE_BLOCK } from '../news-morning-templates.mjs'; import { auditNews002ImageCoverage, enrichNews002HtmlWithSourceImages, extractNews002StoryCards, } from '../wechat-news-morning-images.mjs'; const DEFAULT_SOURCE = 'https://m.tkmind.cn/MindSpace/a70ff537-8908-486e-9b6c-042e07cc25db/public/daily-news-0921.html'; const sourceUrl = process.env.NEWS002_SAMPLE_SOURCE_URL || DEFAULT_SOURCE; const outputPath = path.resolve( process.argv[2] || path.join(process.cwd(), '.tmp', 'news002-real-design-sample.html'), ); function cardHasImage(raw) { return /]*class="[^"]*\bmedia\b[^"]*"[^>]*>[\s\S]*? b.start - a.start)) { output = output.slice(0, range.start) + output.slice(range.end); } return output; } function keepOnlyStrictCards(html, maxStoryCards = 28) { const cards = extractNews002StoryCards(html).filter((card) => card.title); const keep = []; const remove = []; for (const card of cards) { if (!cardHasImage(card.raw) || keep.length >= maxStoryCards) { remove.push(card); } else { keep.push(card); } } return { html: stripRanges(html, remove), keptCount: keep.length, removedCount: remove.length }; } function normalizeHeadlineCards(html) { let output = String(html).replace( /]*class="[^"]*\blead-card\b[^"]*"[^>]*>[\s\S]*?<\/a>\s*(?=
    card.title && cardHasImage(card.raw)) .slice(0, 3); for (const card of [...candidates].sort((a, b) => b.start - a.start)) { const promoted = card.raw.replace( /^
    ]*>[\s\S]*?<\/style>/gi, ''); const withStyle = withoutStyles.replace('', `${NEWS002_STYLE_BLOCK} `); return withStyle .replace(/([\s\S]*?)<\/title>/i, '<title>news002 真实新闻视觉样图 · $1') .replace( /]*)>/i, '
    news002 视觉样图 · 正文来自当天真实新闻,图片来自对应文章来源;尚未推送微信。
    ', ); } async function main() { const response = await fetch(sourceUrl, { signal: AbortSignal.timeout(20_000), headers: { accept: 'text/html', 'user-agent': 'TKMind-News002-Sample/1.0' }, }); if (!response.ok) throw new Error(`读取真实新闻页失败:HTTP ${response.status}`); const source = await response.text(); if (!/class="[^"]*\bmasthead\b/i.test(source)) { throw new Error('来源页不是 news002 masthead 结构,拒绝制作误导样图'); } const enriched = await enrichNews002HtmlWithSourceImages(source, { timeoutMs: 8000, concurrency: 8, }); const normalized = normalizeHeadlineCards(enriched.html); const strict = keepOnlyStrictCards(normalized, 28); const designed = applyCanonicalNews002Design(strict.html); const audit = auditNews002ImageCoverage(designed); if (!audit.ok) { throw new Error( `视觉样图未通过 news002 闸门:${audit.violations.join(', ')}` + `;尝试配图 ${enriched.attemptedCount},解析成功 ${enriched.resolvedCount},` + `保留卡片 ${strict.keptCount},头条 ${audit.leadCount}`, ); } fs.mkdirSync(path.dirname(outputPath), { recursive: true }); fs.writeFileSync(outputPath, designed); console.log(JSON.stringify({ outputPath, sourceUrl, attemptedImages: enriched.attemptedCount, resolvedImages: enriched.resolvedCount, removedCards: strict.removedCount, audit, }, null, 2)); } main().catch((error) => { console.error(error instanceof Error ? error.message : error); process.exit(1); });