diff --git a/scripts/trigger-news-morning-global-regenerate.mjs b/scripts/trigger-news-morning-global-regenerate.mjs new file mode 100644 index 0000000..19dabf4 --- /dev/null +++ b/scripts/trigger-news-morning-global-regenerate.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node +/** + * Enable global-source news morning template and trigger prod worker regenerate + push. + */ +import process from 'node:process'; +import mysql from 'mysql2/promise'; +import { loadH5Environment } from './load-env.mjs'; + +loadH5Environment(import.meta.dirname); + +const CONFIG_KEY = 'news_morning_draft'; +const CONFIG_TABLE = 'h5_wechat_admin_config'; + +async function main() { + if (!process.env.DATABASE_URL) { + console.error('DATABASE_URL missing'); + process.exit(1); + } + const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 }); + const [rows] = await pool.query( + `SELECT config_json FROM ${CONFIG_TABLE} WHERE config_key = ? LIMIT 1`, + [CONFIG_KEY], + ); + const current = rows[0]?.config_json + ? (typeof rows[0].config_json === 'string' ? JSON.parse(rows[0].config_json) : rows[0].config_json) + : {}; + const next = { + ...current, + enabled: current.enabled !== false, + forceGenerateOnce: true, + taskSpecAppend: [ + '【全球扩展栏目 · 必须生成】', + '除原有栏目外,必须新增并写满以下 4 个 section(quick-link + div.section):', + '1. id=google:「🔎 Google 热点 · Global Headlines」2~3 条,标题可英文,正文中文摘要,来源 Reuters/BBC/AP/Google News 等;', + '2. id=x:「𝕏 X 平台热点 · Trending」2~3 条,X/Twitter 热议或媒体转述,标题可中英混合;', + '3. id=universities:「🎓 全球高校 · Research & Campus」2~3 条,MIT/Stanford/Oxford 等科研或校园新闻,标题可英文;', + '4. id=global_markets:「📈 全球股市 · Markets」2~3 条,美股/欧股/日股/港股等,可含 Fed/Nasdaq/FTSE 等英文术语;', + '英文栏目标题可英文或中英并列,每条必须有中文摘要与来源 URL;禁止整段纯英文正文。', + ].join('\n'), + }; + const now = Date.now(); + await pool.query( + `INSERT INTO ${CONFIG_TABLE} (config_key, config_json, updated_by, updated_at) + VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE + config_json = VALUES(config_json), + updated_by = VALUES(updated_by), + updated_at = VALUES(updated_at)`, + [CONFIG_KEY, JSON.stringify(next), 'global-source-regenerate', now], + ); + console.log(JSON.stringify({ + ok: true, + forceGenerateOnce: true, + taskSpecAppendChars: next.taskSpecAppend.length, + templateNote: 'Worker on 143 must run code with taskSpecAppend + new search queries for full effect.', + }, null, 2)); + await pool.end(); +} + +main().catch((error) => { + console.error(error instanceof Error ? error.message : error); + process.exit(1); +}); diff --git a/wechat-news-morning-draft-worker.mjs b/wechat-news-morning-draft-worker.mjs index 145131e..3b507d6 100644 --- a/wechat-news-morning-draft-worker.mjs +++ b/wechat-news-morning-draft-worker.mjs @@ -6,6 +6,7 @@ import { buildNewsMorningAutoGenerationTask, findLatestNewsMorningPage, isLocalScheduleDue, + isNewsMorningPageDegraded, isNewsMorningPageForToday, isWithinNewsMorningGenerateLeadWindow, } from './wechat-news-morning-draft.mjs'; @@ -157,6 +158,54 @@ export function startWechatNewsMorningDraftWorker({ const toleranceMinutes = Math.max(1, Math.ceil(Number(intervalMs) / 60_000)); let todayPage = resolveTodayPage(config, h5Root, now); + const todayPageDegraded = todayPage && isNewsMorningPageDegraded(todayPage); + if ( + todayPageDegraded + && generationInFlightDateKey !== dateKey + && ( + config.forceGenerateOnce + || isWithinNewsMorningGenerateLeadWindow(config, now) + || isLocalScheduleDue( + { hour: config.pushHour, minute: config.pushMinute, timezone }, + now, + toleranceMinutes + 180, + ) + ) + ) { + logger.warn?.('[NewsMorningDraft] today page degraded, regenerating', { + slug: todayPage.slug, + userId: config.sourceUserId, + dateKey, + }); + try { + todayPage = await ensureTodayPage(config, dateKey, { now, force: true }); + if ( + todayPage + && config.autoPushEnabled + && !isNewsMorningPageDegraded(todayPage) + && isLocalScheduleDue( + { hour: config.pushHour, minute: config.pushMinute, timezone }, + now, + toleranceMinutes + 180, + ) + ) { + const result = await wechatNewsMorningDraftService.pushDraft({ + triggeredBy: 'degraded-regenerate', + now, + }); + logger.log?.('[NewsMorningDraft] degraded regenerate push succeeded', { + draftMediaId: result.draftMediaId, + pageSlug: result.preview?.page?.slug ?? todayPage.slug, + }); + } + } catch (error) { + logger.warn?.( + '[NewsMorningDraft] degraded page regenerate failed:', + error instanceof Error ? error.message : error, + ); + } + } + if (config.forceGenerateOnce && generationInFlightDateKey !== dateKey) { try { todayPage = await ensureTodayPage(config, dateKey, { now, force: true }); diff --git a/wechat-news-morning-draft.mjs b/wechat-news-morning-draft.mjs index 360ece1..462bf7d 100644 --- a/wechat-news-morning-draft.mjs +++ b/wechat-news-morning-draft.mjs @@ -30,30 +30,50 @@ const DEFAULT_DRAFT_ADD_URL = 'https://api.weixin.qq.com/cgi-bin/draft/add'; const DEFAULT_MATERIAL_ADD_URL = 'https://api.weixin.qq.com/cgi-bin/material/add_material'; const DEFAULT_UPLOAD_IMG_URL = 'https://api.weixin.qq.com/cgi-bin/media/uploadimg'; const MAX_THUMB_BYTES = 64 * 1024; -export const NEWS_MORNING_TEMPLATE_VERSION = '2026-09-16'; +export const NEWS_MORNING_TEMPLATE_REFERENCE_URL = + 'https://m.tkmind.cn/MindSpace/a70ff537-8908-486e-9b6c-042e07cc25db/public/daily-news-0915.html'; -/** 0910 新闻早报页面结构说明,供定时任务 taskSpec 与草稿转换共用。 */ -export const NEWS_MORNING_TEMPLATE_0910_SPEC = [ - '生成「每日新闻早报」静态 HTML 页面,版式对齐 2026-09-10 生产版(daily-news-MMDD):', - '1. Hero 渐变区:主标题「📰 每日新闻早报」、date-badge、subtitle 导语;', - '2. quick-links 锚点导航(要闻/天气/国际/国内/财经/科技/体育等);', - '3. 多个 section:section-title + card(tag、h3 标题、p 正文、source 来源);', - '4. 可选 weather、knowledge、history 等区块;', - '5. footer 品牌;', - '6. 文件名 daily-news-MMDD.html(如 daily-news-0910.html),并生成同名 .thumbnail.png;', - '7. 推送微信草稿时转为内联 style 版式(与 daily-news-0904 公众号已发版一致,微信不支持