feat(wechat): 早报草稿改用内联模板并去掉正文大封面
让定时任务按新版式推送草稿:热门旅游城市天气、无 Hero logo,并约束生成页必须写城市天气卡。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+886
-472
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
});
|
||||
@@ -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('');
|
||||
}
|
||||
|
||||
@@ -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
@@ -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=weather,weather-today + weather-card)。若搜索失败,明确写出未能获取当日新闻,禁止用旧稿充数。',
|
||||
'7. 必须包含天气区块(section id=weather,weather-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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user