From 6915b2da745383c8d285a387c2c991c6090a791d Mon Sep 17 00:00:00 2001 From: john Date: Thu, 10 Sep 2026 09:40:51 +0800 Subject: [PATCH] feat(wechat): refine daily news draft layout and follow QR block Co-authored-by: Cursor --- wechat-daily-news-inline.mjs | 131 +++++++++++++++++++++++++---- wechat-news-morning-draft.mjs | 64 +++++++++++++- wechat-news-morning-draft.test.mjs | 3 + wechat/assets/mp-follow-qrcode.png | Bin 0 -> 27833 bytes 4 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 wechat/assets/mp-follow-qrcode.png diff --git a/wechat-daily-news-inline.mjs b/wechat-daily-news-inline.mjs index f3c9b96..b8e431d 100644 --- a/wechat-daily-news-inline.mjs +++ b/wechat-daily-news-inline.mjs @@ -2,6 +2,7 @@ import { extractPageTitle } from './wechat/verify/share-preview-repair.mjs'; const MAX_WECHAT_CONTENT_CHARS = 20000; const MAX_WECHAT_CONTENT_TARGET_CHARS = 19600; +const DEFAULT_PORTAL_URL = 'https://m.tkmind.cn'; const CARD_VARIANTS = { 'highlight-box': { @@ -128,7 +129,7 @@ function renderHero(html) { const dateBadge = sanitizeInlineHtml(String(html).match(/
]*>([\s\S]*?)<\/div>/i)?.[1] ?? ''); const subtitle = sanitizeInlineHtml(String(html).match(/
]*>([\s\S]*?)<\/div>/i)?.[1] ?? ''); return [ - '
', + '
', `

${title}

`, dateBadge ? `

${dateBadge}

` : '', subtitle ? `

${subtitle}

` : '', @@ -136,6 +137,39 @@ function renderHero(html) { ].filter(Boolean).join(''); } +function renderReadOriginalTop(publicUrl) { + if (!publicUrl) return ''; + return [ + '
', + `📖 点击阅读原文`, + '

查看完整排版、图片与更多栏目

', + '
', + ].join(''); +} + +function renderCreatePortalCTA(portalUrl = DEFAULT_PORTAL_URL) { + return `

✨ 一起创作 → 点我

`; +} + +function renderFollowMpSection(qrcodeImageUrl = '') { + if (qrcodeImageUrl) { + return [ + '
', + '

📣 关注 TKMind 服务号

', + '

长按扫描关注,每日早报不错过

', + `TKMind 服务号二维码`, + '

长按扫描关注

', + '
', + ].join(''); + } + return [ + '
', + '

📣 关注 TKMind 服务号

', + '

推送草稿时将嵌入公众号二维码

', + '
', + ].join(''); +} + function renderWeatherSection(sectionHtml) { const title = extractInnerHtml(sectionHtml, /
]*>([\s\S]*?)<\/div>/i); const summary = extractInnerHtml(sectionHtml, /
]*>([\s\S]*?)<\/div>/i); @@ -253,9 +287,23 @@ function resolveSectionCardLimit(section, sectionCardLimits) { return sectionCardLimits[section.id] ?? 3; } -function buildWechatInlineContent(html, { publicUrl = '', trimProfile = TRIM_PROFILES[0] } = {}) { +function estimateFixedLayoutLength({ publicUrl = '', qrcodeImageUrl = '', portalUrl = DEFAULT_PORTAL_URL } = {}) { + return ( + (publicUrl ? renderReadOriginalTop(publicUrl).length : 0) + + renderFollowMpSection(qrcodeImageUrl).length + + renderCreatePortalCTA(portalUrl).length + ); +} + +function buildWechatInlineContent(html, { + publicUrl = '', + qrcodeImageUrl = '', + portalUrl = DEFAULT_PORTAL_URL, + trimProfile = TRIM_PROFILES[0], + includeFixedLayout = true, +} = {}) { const sections = splitSections(html); - const parts = [renderHero(html)]; + const bodyParts = [renderHero(html)]; for (const section of sections) { if (trimProfile.skipHistory && shouldSkipSection(section, trimProfile)) { @@ -266,26 +314,27 @@ function buildWechatInlineContent(html, { publicUrl = '', trimProfile = TRIM_PRO } if (section.id === 'weather') { if (!trimProfile.skipWeather) { - parts.push(renderWeatherSection(section.html)); + bodyParts.push(renderWeatherSection(section.html)); } continue; } - parts.push(renderGenericSection(section.html, { + bodyParts.push(renderGenericSection(section.html, { maxCards: resolveSectionCardLimit(section, trimProfile.sectionCardLimits), maxBodyChars: trimProfile.maxBodyChars, })); } - parts.push(renderFooter(html)); + bodyParts.push(renderFooter(html)); - if (publicUrl) { - parts.push( - `

✨ 一起创作 → 点击阅读原文

`, - ); + const ordered = [...bodyParts]; + if (includeFixedLayout) { + if (publicUrl) ordered.splice(1, 0, renderReadOriginalTop(publicUrl)); + ordered.push(renderFollowMpSection(qrcodeImageUrl)); + ordered.push(renderCreatePortalCTA(portalUrl)); } return { - content: parts.join(''), + content: ordered.join(''), sectionCount: sections.length, renderedSectionCount: sections.filter((section) => { if (trimProfile.skipHistory && shouldSkipSection(section, trimProfile)) return false; @@ -305,11 +354,12 @@ function shouldSkipSection(section, trimProfile) { if (!section) return true; if (['history', 'knowledge'].includes(section.id)) return true; if (trimProfile?.skipSectionIds?.includes(section.id)) return true; - return /历史上的今天/u.test(section.title); + return /历史上的今天|近7天新闻早报/u.test(section.title); } function renderGenericSection(sectionHtml, { maxCards = null, maxBodyChars = null } = {}) { const title = extractInnerHtml(sectionHtml, /
]*>([\s\S]*?)<\/div>/i); + if (/近7天新闻早报/u.test(stripInlineText(title))) return ''; const cards = sectionHtml.split(/
= TRIM_PROFILES.length; - if (built.content.length <= MAX_WECHAT_CONTENT_TARGET_CHARS) { + if (built.content.length <= trimTargetChars) { break; } } + let selectedProfile = allProfiles[trimLevel] ?? allProfiles[allProfiles.length - 1]; + if (built) { + built = buildWechatInlineContent(html, { + publicUrl, + qrcodeImageUrl, + portalUrl, + trimProfile: selectedProfile, + includeFixedLayout: true, + }); + while (built.content.length > MAX_WECHAT_CONTENT_CHARS && (selectedProfile.maxBodyChars ?? 120) > 20) { + selectedProfile = { + ...selectedProfile, + skipWeather: true, + maxBodyChars: Math.max(20, (selectedProfile.maxBodyChars ?? 64) - 8), + sectionCardLimits: { + ...(selectedProfile.sectionCardLimits ?? {}), + headlines: Math.max(2, selectedProfile.sectionCardLimits?.headlines ?? 3), + world: Math.min(2, selectedProfile.sectionCardLimits?.world ?? 2), + domestic: Math.min(2, selectedProfile.sectionCardLimits?.domestic ?? 2), + google: 1, + finance: 2, + tech: 2, + sports: 1, + technews: 0, + hot: 0, + }, + }; + built = buildWechatInlineContent(html, { + publicUrl, + qrcodeImageUrl, + portalUrl, + trimProfile: selectedProfile, + includeFixedLayout: true, + }); + usedFallback = true; + } + } + if (!built || built.content.length > MAX_WECHAT_CONTENT_CHARS) { throw new Error( `微信正文超过 ${MAX_WECHAT_CONTENT_CHARS} 字符(当前 ${built?.content.length ?? 0}),请缩短 HTML 页面后重试`, diff --git a/wechat-news-morning-draft.mjs b/wechat-news-morning-draft.mjs index a8fff96..c4dc81b 100644 --- a/wechat-news-morning-draft.mjs +++ b/wechat-news-morning-draft.mjs @@ -17,6 +17,7 @@ const RUNS_TABLE = 'h5_wechat_news_draft_runs'; const DEFAULT_WECHAT_TOKEN_URL = 'https://api.weixin.qq.com/cgi-bin/stable_token'; 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-10'; @@ -211,11 +212,46 @@ export function extractDailyNewsArticleMeta(html) { return { title: title.slice(0, 64), digest }; } +export function resolveMpFollowQrcodePath(memindLibRoot = process.cwd()) { + const candidates = [ + path.join(memindLibRoot, 'wechat/assets/mp-follow-qrcode.png'), + path.join(process.cwd(), 'wechat/assets/mp-follow-qrcode.png'), + ]; + return candidates.find((candidate) => fs.existsSync(candidate)) ?? null; +} + +export async function uploadWechatArticleContentImage( + accessToken, + imageBuffer, + { wechatFetch = undiciFetch, uploadUrl = DEFAULT_UPLOAD_IMG_URL, filename = 'content.png' } = {}, +) { + if (!accessToken) throw new Error('缺少微信 access_token'); + if (!Buffer.isBuffer(imageBuffer) || imageBuffer.length === 0) { + throw new Error('正文图片为空'); + } + const form = new FormData(); + form.append( + 'media', + new Blob([imageBuffer], { type: 'image/png' }), + filename, + ); + const endpoint = new URL(uploadUrl); + endpoint.searchParams.set('access_token', accessToken); + const payload = await readJsonResponse( + await wechatFetch(endpoint.toString(), { method: 'POST', body: form }), + ); + if (Number(payload?.errcode ?? 0) !== 0 || !String(payload?.url ?? '').trim()) { + throw new Error(payload?.errmsg || '上传微信正文图片失败'); + } + return String(payload.url); +} + export function buildDailyNewsWechatDraftArticle({ html, publicUrl, + qrcodeImageUrl = '', } = {}) { - const article = convertDailyNewsHtmlToWechatInlineArticle(html, { publicUrl }); + const article = convertDailyNewsHtmlToWechatInlineArticle(html, { publicUrl, qrcodeImageUrl }); return { ...article, cardCount: extractCardArticles(html).length, @@ -223,6 +259,27 @@ export function buildDailyNewsWechatDraftArticle({ }; } +export async function buildDailyNewsWechatDraftArticleForPush({ + html, + publicUrl, + accessToken = null, + wechatFetch = undiciFetch, + memindLibRoot = process.cwd(), +} = {}) { + let qrcodeImageUrl = ''; + if (accessToken) { + const qrcodePath = resolveMpFollowQrcodePath(memindLibRoot); + if (qrcodePath) { + qrcodeImageUrl = await uploadWechatArticleContentImage( + accessToken, + fs.readFileSync(qrcodePath), + { wechatFetch, filename: 'mp-follow-qrcode.png' }, + ); + } + } + return buildDailyNewsWechatDraftArticle({ html, publicUrl, qrcodeImageUrl }); +} + export function convertNewsPageHtmlToWechatArticle(html, { publicUrl = '' } = {}) { const dailyNews = isDailyNewsFormat(html); if (dailyNews) { @@ -700,9 +757,12 @@ export function createWechatNewsMorningDraftService( const thumbMediaId = await uploadWechatPermanentThumb(accessToken, thumbBuffer, { wechatFetch }); const html = fs.readFileSync(page.localPath, 'utf8'); const article = isDailyNewsFormat(html) - ? buildDailyNewsWechatDraftArticle({ + ? await buildDailyNewsWechatDraftArticleForPush({ html, publicUrl: preview.page.publicUrl, + accessToken, + wechatFetch, + memindLibRoot, }) : preview.article; const draft = await addWechatDraftArticle( diff --git a/wechat-news-morning-draft.test.mjs b/wechat-news-morning-draft.test.mjs index 4924283..063ea40 100644 --- a/wechat-news-morning-draft.test.mjs +++ b/wechat-news-morning-draft.test.mjs @@ -91,6 +91,9 @@ test('convertNewsPageHtmlToWechatArticle converts daily-news to inline html', () assert.match(article.content, /每日新闻早报/u); assert.match(article.content, /示例热点/u); assert.match(article.content, /点击阅读原文/u); + assert.match(article.content, /一起创作/u); + assert.match(article.content, /点我/u); + assert.doesNotMatch(article.content, /近7天新闻早报/u); }); test('buildDailyNewsWechatHtmlContent preserves style and body markup', () => { diff --git a/wechat/assets/mp-follow-qrcode.png b/wechat/assets/mp-follow-qrcode.png new file mode 100644 index 0000000000000000000000000000000000000000..b35751c7c2d40332d3312483671010d937728141 GIT binary patch literal 27833 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 L|LBL6zVQD5x2K{= literal 0 HcmV?d00001