feat(wechat): 早报草稿改用内联模板并去掉正文大封面

让定时任务按新版式推送草稿:热门旅游城市天气、无 Hero logo,并约束生成页必须写城市天气卡。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-16 15:20:44 +08:00
parent 96d8e119ed
commit b8bcbcb764
6 changed files with 1167 additions and 504 deletions
File diff suppressed because it is too large Load Diff
+145
View File
@@ -0,0 +1,145 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
convertDailyNewsHtmlToWechatInlineArticle,
wechatDailyNewsInlineInternals,
} from './wechat-daily-news-inline.mjs';
const WEATHER_HTML = `<!DOCTYPE html><html><body><div class="container">
<div class="hero"><h1>📰 每日新闻早报</h1></div>
<div class="quick-links"><a href="#weather">🌤️ 天气</a><a href="#world">🌍 国际</a></div>
<div class="section" id="headlines"><div class="section-title">要闻</div><div class="card"><h3>头条</h3><p>正文</p></div></div>
<div class="section" id="weather">
<div class="section-title">🌤️ 今日天气</div>
<div class="weather-today">
<div class="wt-row"><div><div class="wt-city">📍 北京</div><div class="wt-detail">☀️ 晴 · 温差大</div></div><div><div class="wt-temp-big">30°</div></div></div>
<div style="display:flex;gap:12px;"><span>🌧️ 华南有雨</span><span>☀️ 华北晴好</span></div>
<div style="background:rgba(255,255,255,0.5);padding:8px;"><strong>中央气象台:</strong>未来三天降水减弱。</div>
</div>
<div class="weather-grid">
<div class="weather-card w-hot"><div class="w-left"><span class="w-city">北京</span><span class="w-cond">☀️ 晴</span></div><div class="w-right"><span class="w-temp">30°</span></div></div>
<div class="weather-card w-rain"><div class="w-left"><span class="w-city">上海</span><span class="w-cond">🌧️ 小雨</span></div><div class="w-right"><span class="w-temp">26°</span></div></div>
</div>
<p style="font-size:12px;color:#a0aec0;">数据来源:中央气象台</p>
</div>
</div></body></html>`;
test('splitSections preserves section ids after opening tag split', () => {
const sections = wechatDailyNewsInlineInternals.splitSections(WEATHER_HTML);
assert.deepEqual(sections.map((item) => item.id), ['headlines', 'weather']);
});
test('renderWeatherSection uses tourist city title and two-column city cards', () => {
const sections = wechatDailyNewsInlineInternals.splitSections(WEATHER_HTML);
const weather = sections.find((item) => item.id === 'weather');
const rendered = wechatDailyNewsInlineInternals.renderWeatherSection(weather.html);
assert.match(rendered, /热门旅游城市天气/u);
assert.match(rendered, /📍 北京/u);
assert.match(rendered, /☀️ 晴 · 温差大/u);
assert.match(rendered, /width:50%;box-sizing:border-box/u);
assert.match(rendered, /display:inline-block/u);
assert.doesNotMatch(rendered, /display:flex/u);
assert.match(rendered, /30°/u);
assert.match(rendered, /26°/u);
assert.doesNotMatch(rendered, /中央气象台/u);
assert.doesNotMatch(rendered, /今日天气/u);
});
test('renderWeatherSection skips regional fallback cards and national summary', () => {
const regionalWeatherHtml = `<div class="section" id="weather">
<div class="section-title">🌤️ 今日天气 · 9月16日</div>
<div class="weather-today">
<div class="wt-row"><div><div class="wt-city">📍 全国概况 · 江汉西南阴雨开场</div><div class="wt-detail">🌧️ 湖北西部局地暴雨</div></div></div>
</div>
<div class="weather-grid">
<div class="weather-card"><div class="w-left"><span class="w-city">湖北西部</span><span class="w-cond">🌧️ 暴雨</span></div><div class="w-right"><span class="w-temp">雨</span></div></div>
<div class="weather-card"><div class="w-left"><span class="w-city">北京</span><span class="w-cond">☀️ 晴</span></div><div class="w-right"><span class="w-temp">30°</span></div></div>
<div class="weather-card"><div class="w-left"><span class="w-city">上海</span><span class="w-cond">🌧️ 小雨</span></div><div class="w-right"><span class="w-temp">26°</span></div></div>
</div>
</div>`;
const rendered = wechatDailyNewsInlineInternals.renderWeatherSection(regionalWeatherHtml);
assert.match(rendered, /热门旅游城市天气 · 9月16日/u);
assert.match(rendered, /北京/u);
assert.match(rendered, /上海/u);
assert.doesNotMatch(rendered, /湖北西部/u);
assert.doesNotMatch(rendered, /全国概况/u);
});
test('buildWechatInlineContent renders weather and optional quick links', () => {
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(WEATHER_HTML, {
includeFixedLayout: false,
trimProfile: { skipHistory: true, skipQuickLinks: false },
});
assert.match(built.content, /🌤️ 天气/u);
assert.match(built.content, /📍 北京/u);
assert.match(built.content, /30°/u);
});
test('convertDailyNewsHtmlToWechatInlineArticle keeps weather when trimming', () => {
const article = convertDailyNewsHtmlToWechatInlineArticle(WEATHER_HTML, {
includeFixedLayout: false,
fixedLayoutLength: 1200,
});
assert.match(article.content, /📍 北京/u);
assert.match(article.content, /30°/u);
});
test('trimWechatInlineBodyByRemovingCards drops tail cards instead of hard slicing', () => {
const html = `${WEATHER_HTML.slice(0, -24)}<div class="section" id="world"><div class="section-title">国际</div><div class="card"><h3>国际一</h3><p>正文一</p></div><div class="card"><h3>国际二</h3><p>正文二</p></div></div></div></body></html>`;
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(html, {
includeFixedLayout: false,
});
const beforeCards = wechatDailyNewsInlineInternals.countInlineCards(built.content);
const trimmed = wechatDailyNewsInlineInternals.trimWechatInlineBodyByRemovingCards(
built.content,
built.content.length - 260,
{ minCards: 1 },
);
const afterCards = wechatDailyNewsInlineInternals.countInlineCards(trimmed);
assert.ok(beforeCards >= 3);
assert.ok(afterCards < beforeCards);
assert.ok(trimmed.length < built.content.length);
assert.match(trimmed, /<\/section>\s*$/u);
});
test('section list numbering starts at 01 after feature cards', () => {
const html = `<!doctype html><html><body>
<div class="hero"><h1>每日新闻早报</h1><div class="subtitle">头条一 · 头条二</div></div>
<div class="section" id="headlines">
<div class="section-title">今日要闻</div>
<div class="card highlight-box"><h3>头条卡</h3><p>重点摘要</p></div>
<div class="card"><h3>普通要闻</h3><p>普通摘要</p></div>
</div>
<div class="section" id="world">
<div class="section-title">国际热点</div>
<div class="card"><h3>国际一</h3><p>国际摘要一</p></div>
</div>
</body></html>`;
const built = wechatDailyNewsInlineInternals.buildWechatInlineContent(html, {
includeFixedLayout: false,
});
assert.match(built.content, /头条卡[\s\S]*?01<\/span> <strong>普通要闻/u);
assert.match(built.content, /WORLD[\s\S]*?01<\/span> <strong>国际一/u);
assert.doesNotMatch(built.content, /02<\/span> <strong>普通要闻/u);
});
test('hero highlights render as a separate readable digest', () => {
const digest = wechatDailyNewsInlineInternals.renderTodayDigest(
'<div class="subtitle">平陆运河通航 · 海上火箭发射 · 全球市场聚焦议息</div>',
);
assert.match(digest, /今日导读/u);
assert.match(digest, /平陆运河通航<br>/u);
assert.match(digest, /海上火箭发射<br>/u);
});
test('renderHero does not insert the cover image as a large logo', () => {
const html = `<div class="hero"><h1>每日新闻早报</h1><div class="date-badge">9月16日</div></div>
<meta name="mindspace-cover" content='{"cover":"https://mmbiz.qpic.cn/logo.png"}'>`;
const rendered = wechatDailyNewsInlineInternals.renderHero(html, new Map([
['https://mmbiz.qpic.cn/logo.png', 'https://mmbiz.qpic.cn/logo.png'],
]));
assert.match(rendered, /每日新闻早报/u);
assert.doesNotMatch(rendered, /<img\b/u);
assert.doesNotMatch(rendered, /TKMIND · DAILY BRIEF/u);
});
+13 -13
View File
@@ -42,15 +42,14 @@ export function clipWechatDraftBodyToFitFooter(
export function renderReadOriginalLink(publicUrl) {
if (!publicUrl) return '';
return [
'<section style="text-align:center;margin:26px 0 14px;padding:20px 16px 18px;background:linear-gradient(180deg,#eef4ff 0%,#fff 100%);border:2px dashed #576b95;border-radius:16px;box-shadow:0 6px 18px rgba(87,107,149,.12);">',
'<p style="margin:0;font-size:14px;color:#4a5568;line-height:1.6;font-weight:600;">✨ 完整网页版更精彩</p>',
'<p style="margin:6px 0 0;font-size:12px;color:#718096;line-height:1.5;">动画 · 互动 · 高清大图 · 完整内容</p>',
'<p style="margin:14px 0 0;font-size:26px;line-height:1;color:#576b95;letter-spacing:6px;font-weight:700;">⬇ ⬇ ⬇</p>',
'<p style="margin:12px 0 0;">',
`<a href="${publicUrl}" style="display:inline-block;padding:12px 32px;border-radius:999px;background:linear-gradient(135deg,#576b95,#3d5a80);color:#fff;font-size:16px;font-weight:700;text-decoration:none;box-shadow:0 6px 16px rgba(87,107,149,.35);">📖 阅读原文</a>`,
'<section style="text-align:center;margin:28px 0 14px;padding:24px 18px;background:#f4f8ff;border:1px dashed #1a4a8a;border-radius:16px">',
'<p style="margin:0;font-size:15px;font-weight:700;color:#0d2b5c;line-height:1.6">完整网页版更精彩</p>',
'<p style="margin:8px 0 0;font-size:12px;color:#3a5f92;line-height:1.6">全部资讯 · 高清配图 · 来源直达 · 历史早报</p>',
'<p style="margin:15px 0 0;font-size:19px;line-height:1;color:#1a4a8a;letter-spacing:8px">⬇ ⬇ ⬇</p>',
'<p style="margin:15px 0 0">',
`<a href="${publicUrl}" style="display:inline-block;padding:13px 36px;border-radius:999px;background:linear-gradient(135deg,#0d2b5c,#1a4a8a);color:#fff;font-size:16px;font-weight:700;text-decoration:none">📖 阅读原文</a>`,
'</p>',
'<p style="margin:12px 0 0;font-size:13px;color:#576b95;font-weight:700;line-height:1.5;">👆 点击上方按钮,查看完整精彩页面</p>',
'<p style="margin:8px 0 0;font-size:22px;line-height:1;color:#576b95;letter-spacing:4px;">➡️ ➡️ ➡️</p>',
'<p style="margin:14px 0 0;font-size:12px;color:#3a5f92;line-height:1.5">点击上方按钮,查看完整早报</p>',
'</section>',
].join('');
}
@@ -62,15 +61,16 @@ export function renderFollowTkMindSection({
qrcodeImageUrl = '',
} = {}) {
const qrcodeBlock = qrcodeImageUrl
? `<img src="${qrcodeImageUrl}" alt="TKMind 服务号二维码" style="width:${FOLLOW_QRCODE_SIZE_PX}px;height:${FOLLOW_QRCODE_SIZE_PX}px;display:block;margin:12px auto 0;border-radius:10px;border:1px solid #eee;" />`
? `<img src="${qrcodeImageUrl}" alt="TKMind 服务号二维码" style="width:${FOLLOW_QRCODE_SIZE_PX}px;height:${FOLLOW_QRCODE_SIZE_PX}px;display:block;margin:14px auto 0;border-radius:10px" />`
: '';
return [
'<section style="text-align:center;padding:22px 16px 18px;margin:18px 0 8px;background:linear-gradient(180deg,#fff8f8,#fff);border:1px solid #ffcdd2;border-radius:12px;">',
'<p style="margin:0 0 6px;font-size:16px;font-weight:700;color:#b71c1c;line-height:1.5;">欢迎关注 TKMind 智趣</p>',
'<section style="text-align:center;padding:24px 16px;margin:18px 0 8px;background:#fff5f6;border-radius:14px">',
'<p style="margin:0;font-size:16px;font-weight:700;color:#c8102e;line-height:1.5">欢迎关注 TKMind 智趣</p>',
'<p style="margin:8px 0 0;font-size:12px;color:#9b9b9b;line-height:1.6">每日早报 · 智能创作 · 一键成页</p>',
qrcodeBlock,
qrcodeImageUrl
? '<p style="margin:12px 0 0;font-size:12px;color:#a0aec0;">长按扫描关注</p>'
: '<p style="margin:12px 0 0;font-size:12px;color:#a0aec0;">关注 TKMind 服务号</p>',
? '<p style="margin:12px 0 0;font-size:12px;color:#9b9b9b">长按扫描关注</p>'
: '<p style="margin:12px 0 0;font-size:12px;color:#9b9b9b">关注 TKMind 服务号</p>',
'</section>',
].filter(Boolean).join('');
}
+1 -1
View File
@@ -37,7 +37,7 @@ test('applyWechatDraftArticleLayout renders guided read-original section and sma
});
assert.match(content, /完整网页版更精彩/);
assert.match(content, /⬇ ⬇ ⬇/);
assert.match(content, /点击上方按钮,查看完整精彩页面/);
assert.match(content, /点击上方按钮,查看完整早报/);
assert.match(content, /width:120px;height:120px/);
assert.match(content, /📖 阅读原文/);
const check = verifyWechatDraftPublicationContent(content, { publicUrl: PUBLIC_URL });
+117 -15
View File
@@ -10,8 +10,16 @@ import {
} from './user-publish.mjs';
import { extractMindspaceCoverPath } from './wechat/verify/generated-thumbnail.mjs';
import { extractPageTitle } from './wechat/verify/share-preview-repair.mjs';
import { convertDailyNewsHtmlToWechatInlineArticle } from './wechat-daily-news-inline.mjs';
import { uploadWechatDraftBrandAssets } from './wechat-draft-article-layout.mjs';
import {
convertDailyNewsHtmlToWechatInlineArticle,
renderCreatePortalCTA,
trimWechatInlineBodyByRemovingCards,
} from './wechat-daily-news-inline.mjs';
import {
applyWechatDraftArticleLayout,
clipWechatDraftBodyToFitFooter,
uploadWechatDraftBrandAssets,
} from './wechat-draft-article-layout.mjs';
import { getLocalParts, localDateKey, startOfLocalDay } from './schedule-time.mjs';
const CONFIG_TABLE = 'h5_wechat_admin_config';
@@ -22,7 +30,7 @@ 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-15';
export const NEWS_MORNING_TEMPLATE_VERSION = '2026-09-16';
/** 0910 新闻早报页面结构说明,供定时任务 taskSpec 与草稿转换共用。 */
export const NEWS_MORNING_TEMPLATE_0910_SPEC = [
@@ -33,7 +41,7 @@ export const NEWS_MORNING_TEMPLATE_0910_SPEC = [
'4. 可选 weather、knowledge、history 等区块;',
'5. footer 品牌;',
'6. 文件名 daily-news-MMDD.html(如 daily-news-0910.html),并生成同名 .thumbnail.png',
'7. 推送微信草稿时转为内联 style HTML(参考公众号排版),禁止整页长图;正文需控制在 2 万字符内。',
'7. 推送微信草稿时转为内联 style 版式(与 daily-news-0904 公众号已发版一致,微信不支持 <style> 标签),禁止整页长图;底部追加统一「阅读原文 + 二维码」layout。',
].join('\n');
/** 每日正文必须新搜,禁止把历史早报当新闻来源。 */
@@ -45,7 +53,7 @@ export const NEWS_MORNING_FRESH_CONTENT_SPEC = [
'4. 禁止复制、改写、微调昨日或任何历史 daily-news-*.html 的新闻标题与正文。',
'5. 最近一版 daily-news 只允许当作版式参考(hero / section / card 结构);读完后必须丢弃其中的条目内容。',
'6. 每条卡片必须是当日可核验事件,并写明来源;title、date-badge、导语必须写明当日日期。要闻栏至少 6 条,其中国内、国际、热门事件都要有代表条目,并用 highlight-box 突出头条。',
'7. 必须包含天气区块(section id=weatherweather-today + weather-card)。若搜索失败,明确写出未能获取当日新闻,禁止用旧稿充数。',
'7. 必须包含天气区块(section id=weatherweather-today + weather-card)。天气卡片必须写热门旅游城市当日天气(北京、上海、广州、深圳、成都、杭州、西安、重庆、武汉、南京、厦门、三亚等),禁止用区域预报(西部/大部/平原/秋收区)顶替城市卡。若搜索失败,明确写出未能获取当日新闻,禁止用旧稿充数。',
].join('\n');
function normalizeBoolean(value, fallback = false) {
@@ -276,18 +284,85 @@ export function rewriteDailyNewsHtmlLinks(html, { publicBaseUrl, userId }) {
});
}
export function buildDailyNewsWechatHtmlContent(html, { publicUrl = '', publicBaseUrl = '', userId = '' } = {}) {
const source = String(html ?? '');
const styleBlock = source.match(/<style>([\s\S]*?)<\/style>/i)?.[0] ?? '';
const bodyBlock = source.match(/<body>([\s\S]*?)<\/body>/i)?.[0] ?? '';
let content = `${styleBlock}${bodyBlock}`;
function stripMindspaceChromeFromDailyNewsBody(bodyHtml) {
let body = String(bodyHtml ?? '');
body = body.replace(/<div[^>]*data-mindspace-public-share[\s\S]*$/i, '');
body = body.replace(/<section[^>]*data-mindspace-seo-outline[\s\S]*$/i, '');
body = body.replace(/<p[^>]*>\s*✨\s*一起创作[\s\S]*?<\/p>/gi, '');
return body.trim();
}
function extractDailyNewsStyleBlocks(html) {
const blocks = [];
for (const match of String(html ?? '').matchAll(/<style([^>]*)>([\s\S]*?)<\/style>/gi)) {
const attrs = match[1] ?? '';
const block = match[0];
if (/mindspace-public-share/i.test(`${attrs}${block}`)) continue;
blocks.push(block);
}
return blocks.join('\n');
}
function replaceDailyNewsHtmlImageRefs(html, imageUrlMap = new Map()) {
if (!imageUrlMap || imageUrlMap.size === 0) return String(html ?? '');
return String(html).replace(/(<img[^>]+src=["'])([^"']+)(["'])/gi, (match, prefix, src, suffix) => {
const mapped =
imageUrlMap.get(src)
?? imageUrlMap.get(src.replace(/^\.\//, ''))
?? imageUrlMap.get(src.replace(/^\//, ''));
return mapped ? `${prefix}${mapped}${suffix}` : match;
});
}
export function buildDailyNewsWechatHtmlContent(
html,
{
publicUrl = '',
publicBaseUrl = '',
userId = '',
imageUrlMap = new Map(),
appendLegacyReadOriginalLink = false,
} = {},
) {
const source = String(html ?? '')
.replace(/<script[\s\S]*?<\/script>/gi, '');
const styleBlock = extractDailyNewsStyleBlocks(source);
const bodyInner = stripMindspaceChromeFromDailyNewsBody(
source.match(/<body[^>]*>([\s\S]*?)<\/body>/i)?.[1] ?? source,
);
let content = `${styleBlock}<body>${bodyInner}</body>`;
content = rewriteDailyNewsHtmlLinks(content, { publicBaseUrl, userId });
if (publicUrl) {
content = replaceDailyNewsHtmlImageRefs(content, imageUrlMap);
if (appendLegacyReadOriginalLink && publicUrl) {
content += `<p style="margin:16px 0 0;font-size:13px;color:#718096;text-align:center;">阅读原文:<a href="${publicUrl}">${publicUrl}</a></p>`;
}
return content;
}
function estimateDailyNewsDraftFooterLength({
publicUrl = '',
qrcodeImageUrl = '',
portalUrl = 'https://m.tkmind.cn',
} = {}) {
const footer = applyWechatDraftArticleLayout('', { publicUrl, qrcodeImageUrl });
const cta = renderCreatePortalCTA(portalUrl);
return footer.length + (cta ? cta.length + 1 : 0);
}
export function applyDailyNewsWechatDraftFooter(
body,
{ publicUrl = '', qrcodeImageUrl = '', portalUrl = 'https://m.tkmind.cn' } = {},
) {
const footer = applyWechatDraftArticleLayout('', { publicUrl, qrcodeImageUrl });
const cta = renderCreatePortalCTA(portalUrl);
const footerWithCta = [footer, cta].filter(Boolean).join('\n');
const separator = body && footerWithCta ? '\n' : '';
const maxBody = Math.max(0, 20000 - footerWithCta.length - separator.length);
let fittedBody = trimWechatInlineBodyByRemovingCards(String(body ?? '').trim(), maxBody);
fittedBody = clipWechatDraftBodyToFitFooter(fittedBody, footerWithCta);
return [fittedBody, footerWithCta].filter(Boolean).join('\n');
}
export function extractDailyNewsArticleMeta(html) {
const title = extractPageTitle(html) || '每日新闻早报';
const digest = (extractMetaDescription(html) || title).slice(0, 120);
@@ -419,17 +494,38 @@ export function buildDailyNewsWechatDraftArticle({
publicUrl,
qrcodeImageUrl = '',
imageUrlMap = new Map(),
publicBaseUrl = '',
userId = '',
} = {}) {
const article = convertDailyNewsHtmlToWechatInlineArticle(html, {
const meta = extractDailyNewsArticleMeta(html);
const footerLength = estimateDailyNewsDraftFooterLength({ publicUrl, qrcodeImageUrl });
const inline = convertDailyNewsHtmlToWechatInlineArticle(html, {
publicUrl,
qrcodeImageUrl,
imageUrlMap,
includeFixedLayout: false,
fixedLayoutLength: footerLength,
});
let content = applyDailyNewsWechatDraftFooter(inline.content, {
publicUrl,
qrcodeImageUrl,
});
if (publicBaseUrl && userId) {
content = rewriteDailyNewsHtmlLinks(content, { publicBaseUrl, userId });
}
return {
...article,
cardCount: extractCardArticles(html).length,
title: meta.title.slice(0, 64),
digest: meta.digest,
content,
contentSourceUrl: publicUrl || undefined,
contentMode: 'inline_html',
contentLength: content.length,
cardCount: inline.cardCount ?? extractCardArticles(html).length,
statsCount: extractStats(html).length,
uploadedImageCount: imageUrlMap.size,
trimLevel: inline.trimLevel,
trimmed: inline.trimmed,
usedFallbackTrim: inline.usedFallbackTrim,
};
}
@@ -831,7 +927,12 @@ export function createWechatNewsMorningDraftService(
}
const html = fs.readFileSync(page.localPath, 'utf8');
const article = isDailyNewsFormat(html)
? buildDailyNewsWechatDraftArticle({ html, publicUrl: pageSummary.publicUrl })
? buildDailyNewsWechatDraftArticle({
html,
publicUrl: pageSummary.publicUrl,
publicBaseUrl: resolveDailyNewsPublicBaseUrl(env),
userId: config.sourceUserId,
})
: convertNewsPageHtmlToWechatArticle(html, { publicUrl: pageSummary.publicUrl });
return {
config,
@@ -1194,6 +1295,7 @@ export const wechatNewsMorningDraftInternals = {
slugPatternToRegExp,
convertNewsPageHtmlToWechatArticle,
buildDailyNewsWechatHtmlContent,
applyDailyNewsWechatDraftFooter,
buildDailyNewsWechatDraftArticle,
findLatestNewsMorningPage,
isNewsMorningPageForToday,
+5 -3
View File
@@ -82,14 +82,15 @@ function createPool(seedRow = null) {
};
}
test('convertNewsPageHtmlToWechatArticle converts daily-news to inline html', () => {
test('convertNewsPageHtmlToWechatArticle converts daily-news to inline html with footer', () => {
const article = convertNewsPageHtmlToWechatArticle(SAMPLE_HTML, {
publicUrl: 'https://m.tkmind.cn/MindSpace/demo/public/daily-news-0910.html',
qrcodeImageUrl: 'https://m.tkmind.cn/assets/mp-follow-qrcode.png',
});
assert.match(article.title, /2026年9月10日/u);
assert.equal(article.contentMode, 'inline_html');
assert.match(article.content, /每日新闻早报/u);
assert.match(article.content, /linear-gradient\(135deg,#c8102e/u);
assert.doesNotMatch(article.content, /<style>/u);
assert.match(article.content, /示例热点/u);
assert.match(article.content, /阅读原文/u);
assert.match(article.content, /完整网页版更精彩/u);
@@ -98,7 +99,7 @@ test('convertNewsPageHtmlToWechatArticle converts daily-news to inline html', ()
assert.match(article.content, /mp-follow-qrcode\.png/u);
assert.match(article.content, /一起创作/u);
assert.match(article.content, /点我/u);
assert.doesNotMatch(article.content, /近7天新闻早报/u);
assert.doesNotMatch(article.content, /data-mindspace-public-share/u);
});
test('buildDailyNewsWechatHtmlContent preserves style and body markup', () => {
@@ -214,6 +215,7 @@ test('buildNewsMorningAutoGenerationTask requires fresh daily search', () => {
assert.match(task.taskSpec, /国内热点/u);
assert.match(task.taskSpec, /国际热点/u);
assert.match(task.taskSpec, /热门事件/u);
assert.match(task.taskSpec, /热门旅游城市/u);
assert.match(task.taskSpec, /2026-09-15/u);
assert.match(task.taskSpec, /daily-news-0915\.html/u);
assert.doesNotMatch(task.taskSpec, /可以用昨日正文/u);