6a88b98044
Co-authored-by: Cursor <cursoragent@cursor.com>
139 lines
5.1 KiB
JavaScript
139 lines
5.1 KiB
JavaScript
#!/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 /<div\b[^>]*class="[^"]*\bmedia\b[^"]*"[^>]*>[\s\S]*?<img\b/i.test(raw);
|
||
}
|
||
|
||
function stripRanges(source, ranges) {
|
||
let output = source;
|
||
for (const range of [...ranges].sort((a, b) => 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(
|
||
/<a\b[^>]*class="[^"]*\blead-card\b[^"]*"[^>]*>[\s\S]*?<\/a>\s*(?=<ol class="brief")/i,
|
||
'',
|
||
);
|
||
const candidates = extractNews002StoryCards(output)
|
||
.filter((card) => 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(
|
||
/^<div class="card"/i,
|
||
'<div class="lead-card"',
|
||
);
|
||
output = output.slice(0, card.start) + promoted + output.slice(card.end);
|
||
}
|
||
return output;
|
||
}
|
||
|
||
function applyCanonicalNews002Design(html) {
|
||
const withoutStyles = String(html).replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '');
|
||
const withStyle = withoutStyles.replace('</head>', `${NEWS002_STYLE_BLOCK}
|
||
<style>
|
||
.wrap{max-width:720px;margin:0 auto;padding:0 14px 36px}
|
||
.anchors{position:sticky;top:0;z-index:50;display:flex;gap:6px;overflow-x:auto;
|
||
padding:9px 10px;margin:0 -14px 18px;background:rgba(250,247,242,.95);
|
||
border-top:1px solid var(--line);border-bottom:1px solid var(--line);backdrop-filter:blur(12px)}
|
||
.anchors a{flex-shrink:0;padding:5px 10px;border-radius:999px;color:var(--ink-2);
|
||
font-size:13px;text-decoration:none}
|
||
.wrap>.brief{margin:0 0 28px}
|
||
footer{padding:22px 16px 34px;text-align:center;font-size:12px;color:var(--ink-3);
|
||
line-height:1.9;border-top:1px solid var(--line)}
|
||
.sample-note{max-width:720px;margin:12px auto 0;padding:10px 16px;border-radius:12px;
|
||
background:#172F57;color:#fff;font-size:12px;line-height:1.7;text-align:center}
|
||
.sample-note b{color:#FFE3A3}
|
||
</style>
|
||
</head>`);
|
||
return withStyle
|
||
.replace(/<title>([\s\S]*?)<\/title>/i, '<title>news002 真实新闻视觉样图 · $1</title>')
|
||
.replace(
|
||
/<body([^>]*)>/i,
|
||
'<body$1><div class="sample-note"><b>news002 视觉样图</b> · 正文来自当天真实新闻,图片来自对应文章来源;尚未推送微信。</div>',
|
||
);
|
||
}
|
||
|
||
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);
|
||
});
|