From 6a88b98044180ac6bf86e72e2d8c3ac9758b98c0 Mon Sep 17 00:00:00 2001 From: john Date: Mon, 21 Sep 2026 11:03:55 +0800 Subject: [PATCH] feat: enforce real imagery for news002 Co-authored-by: Cursor --- docs/news-morning-templates.md | 13 +- news-morning-templates.mjs | 40 +- scripts/build-news002-real-design-sample.mjs | 138 +++++++ scripts/preview-news002-template.mjs | 112 +++++- scripts/push-news002-live-draft.mjs | 138 +++++++ scripts/push-news002-preview-draft.mjs | 133 +++++++ scripts/trigger-news002-generate-draft.mjs | 98 +++++ wechat-daily-news-inline.mjs | 40 +- wechat-daily-news-inline.test.mjs | 20 + wechat-news-morning-draft-worker.mjs | 32 ++ wechat-news-morning-draft.mjs | 94 ++++- wechat-news-morning-images.mjs | 367 +++++++++++++++++++ wechat-news-morning-images.test.mjs | 104 ++++++ wechat-news-morning-search.mjs | 13 +- wechat-news-morning-search.test.mjs | 7 + 15 files changed, 1293 insertions(+), 56 deletions(-) create mode 100644 scripts/build-news002-real-design-sample.mjs create mode 100644 scripts/push-news002-live-draft.mjs create mode 100644 scripts/push-news002-preview-draft.mjs create mode 100644 scripts/trigger-news002-generate-draft.mjs create mode 100644 wechat-news-morning-images.mjs create mode 100644 wechat-news-morning-images.test.mjs diff --git a/docs/news-morning-templates.md b/docs/news-morning-templates.md index 70c1e85..5ee7647 100644 --- a/docs/news-morning-templates.md +++ b/docs/news-morning-templates.md @@ -98,9 +98,12 @@ CSS 固化在 `NEWS002_STYLE_BLOCK`,直接内嵌进 taskSpec,Agent 照抄即 | 日期行 | `.date-badge` | `.masthead .meta` | | 导语 | `.subtitle` | `.lede` | -**已知缺口**:news002 的 per-card 配图目前不会上传到微信正文。 -`uploadDailyNewsBodyImagesForWechat()` 只处理页面里已有的 ``,而 inline 渲染的 -`renderListCard` / `renderFeatureCard` 不输出图片。公众号侧配图需要单独一轮改造。 +news002 的卡片图会先上传到微信正文素材接口,再由 inline 渲染器输出微信 CDN 地址。 +任一新闻卡缺少真实同源图片、图片上传失败、头条不是 3~4 条、事件卡超过 28 条, +或页面仍混有 news001 DOM,都会由生成/推送闸门阻断,不允许进入草稿箱。 + +真实图片来源按优先级取文章来源的 `og:image:secure_url`、`og:image`、 +`twitter:image`;不使用与事件无法核验关系的泛图片搜索结果。 ## 预览与验证 @@ -108,8 +111,12 @@ CSS 固化在 `NEWS002_STYLE_BLOCK`,直接内嵌进 taskSpec,Agent 照抄即 # 渲染一张 news002 静态预览页(样例数据,占位图) node scripts/preview-news002-template.mjs /tmp/news002-preview.html +# 用当天真实新闻 + 对应文章来源图片生成视觉样图(不推微信、不写生产目录) +node scripts/build-news002-real-design-sample.mjs + # 相关单测 node --test news-morning-templates.test.mjs \ + wechat-news-morning-images.test.mjs \ wechat-news-morning-dedup.test.mjs \ wechat-news-morning-draft.test.mjs \ wechat-news-morning-draft-worker.test.mjs \ diff --git a/news-morning-templates.mjs b/news-morning-templates.mjs index 5aabc89..717f16d 100644 --- a/news-morning-templates.mjs +++ b/news-morning-templates.mjs @@ -124,25 +124,34 @@ export const 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); +}); diff --git a/scripts/preview-news002-template.mjs b/scripts/preview-news002-template.mjs index e67773b..97039b6 100644 --- a/scripts/preview-news002-template.mjs +++ b/scripts/preview-news002-template.mjs @@ -3,13 +3,14 @@ * 用固定样例数据渲染一张 news002 静态预览页,用于人工评审版式/色调/排版。 * 生产页由 Agent 按 NEWS002_TEMPLATE_SPEC 生成,这里只保证 CSS 骨架可视化。 * - * 用法:node scripts/preview-news002-template.mjs [输出路径] + * 用法: + * node scripts/preview-news002-template.mjs [输出路径] + * import { renderNews002PreviewHtml, writeNews002PreviewFile } from './preview-news002-template.mjs'; */ import fs from 'node:fs'; import path from 'node:path'; import { NEWS002_STYLE_BLOCK } from '../news-morning-templates.mjs'; - -const OUT = process.argv[2] || path.join(process.cwd(), 'news002-preview.html'); +import { getLocalParts, normalizeTimezone } from '../schedule-time.mjs'; /** 预览用占位图:避免评审时依赖外网图片。生产页用的是新闻原文配图。 */ function placeholder(label, from, to) { @@ -124,19 +125,73 @@ const KNOWLEDGE = [ ['🛢️', '布伦特与WTI的差价', '两者分别对应北海与美国内陆原油,价差主要反映运输成本与区域供需,常被用作地缘风险指标。'], ]; -const img = (src, alt, emoji) => (src - ? `
${alt}
` - : `
${emoji}
`); +const WEEKDAY_SHORT = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; -const source = (name, time) => `
${name}${time}
`; +function pad2(value) { + return String(value).padStart(2, '0'); +} -const section = (id, icon, label, inner, count = '') => ` +export function buildNews002PreviewContext({ + now = Date.now(), + timezone = normalizeTimezone(), + previewMode = false, +} = {}) { + const parts = getLocalParts(now, timezone); + const dateLabel = `${parts.year}年${parts.month}月${parts.day}日`; + const weekdayLabel = new Intl.DateTimeFormat('zh-CN', { + timeZone: timezone, + weekday: 'long', + }).format(new Date(now)); + const mmdd = `${pad2(parts.month)}${pad2(parts.day)}`; + const slug = `daily-news-${mmdd}-news002-preview`; + const headlineCount = BRIEF.length + LEADS.length; + const footerTime = `${parts.year}-${pad2(parts.month)}-${pad2(parts.day)} 05:30 (UTC+8)`; + const historyDays = Array.from({ length: 7 }, (_, index) => { + const dayMs = now - index * 24 * 60 * 60 * 1000; + const dayParts = getLocalParts(dayMs, timezone); + return { + label: `${pad2(dayParts.month)}-${pad2(dayParts.day)}`, + weekday: WEEKDAY_SHORT[new Date(dayMs).getDay()], + today: index === 0, + }; + }); + return { + now, + timezone, + previewMode, + dateLabel, + weekdayLabel, + mmdd, + slug, + headlineCount, + footerTime, + historyDays, + pageTitle: previewMode + ? `每日新闻早报 · news002预览 · ${dateLabel} · TKMind` + : `每日新闻早报 · ${dateLabel} · TKMind`, + draftTitle: `[news002预览] 每日新闻早报 · ${dateLabel}`, + metaDescription: previewMode + ? 'news002 版式预览草稿:样例数据,仅供评审版式/色调/排版,非当日 Agent 全量生成。' + : 'news002 版式预览:事件唯一化、每条配图、报头版式、支持深色模式。', + }; +} + +export function renderNews002PreviewHtml(context = buildNews002PreviewContext()) { + const ctx = context.slug ? context : buildNews002PreviewContext(context); + + const img = (src, alt, emoji) => (src + ? `
${alt}
` + : `
${emoji}
`); + + const source = (name, time) => `
${name}${time}
`; + + const section = (id, icon, label, inner, count = '') => `
${icon}

${label}

${count ? `${count}` : ''}
${inner}
`; -const renderCards = (items, emoji) => items.map((c) => ` + const renderCards = (items, emoji) => items.map((c) => `
${img(c.img, c.title, emoji)}
@@ -148,23 +203,30 @@ const renderCards = (items, emoji) => items.map((c) => `
`).join(''); -const html = ` + const previewBanner = ctx.previewMode + ? `
+ 这是 news002 模板预览草稿,内容为固定样例数据,用于评审新版式。满意后再决定是否切换正式模板并发布。 +
` + : ''; + + return ` -每日新闻早报 · 2026年9月18日 · TKMind - +${ctx.pageTitle} + ${NEWS002_STYLE_BLOCK}
-
TKMIND DAILY
+
TKMIND DAILY${ctx.previewMode ? ' · PREVIEW' : ''}

每日新闻早报

-
2026年9月18日星期五第 218 期今日 11 条
+
${ctx.dateLabel}${ctx.weekdayLabel}${ctx.previewMode ? 'news002 预览' : `第 ${ctx.headlineCount} 条速读`}样例 ${ctx.headlineCount} 条
+${previewBanner}
中美外长再次通话,是今天最值得看的一件事 —— 它决定了后面一周市场对地缘风险的定价。