/** * 公众号草稿统一 layout:阅读原文 + 底部关注区。 * 标准定义见 wechat-draft-publication-standard.mjs 与 docs/wechat-draft-publication-standard.md */ import fs from 'node:fs'; import path from 'node:path'; import { fetch as undiciFetch } from 'undici'; import { PLATFORM_BRAND_ICON_PATH } from './mindspace-og-tags.mjs'; import { resolveMpFollowQrcodePath, uploadWechatArticleContentImage, } from './wechat-news-morning-draft.mjs'; import { WECHAT_DRAFT_PUBLICATION_STANDARD } from './wechat-draft-publication-standard.mjs'; const WECHAT_DRAFT_CONTENT_CHAR_LIMIT = WECHAT_DRAFT_PUBLICATION_STANDARD.limits.maxContentChars; const FOLLOW_QRCODE_SIZE_PX = 120; const MP_FOLLOW_QRCODE_PUBLIC_PATH = '/assets/mp-follow-qrcode.png'; export function resolveMpFollowQrcodePublicUrl(env = process.env) { const base = String(env.H5_PUBLIC_BASE_URL ?? 'https://m.tkmind.cn').replace(/\/$/, ''); return `${base}${MP_FOLLOW_QRCODE_PUBLIC_PATH}`; } export function clipWechatDraftBodyToFitFooter( body, footer, limit = WECHAT_DRAFT_CONTENT_CHAR_LIMIT, ) { const source = String(body ?? ''); const suffix = String(footer ?? ''); const separator = source && suffix ? '\n' : ''; const maxBody = Math.max(0, Number(limit) - suffix.length - separator.length); if (source.length <= maxBody) return source; let clipped = source.slice(0, maxBody); const lastLt = clipped.lastIndexOf('<'); const lastGt = clipped.lastIndexOf('>'); if (lastLt > lastGt) clipped = clipped.slice(0, lastLt); return clipped.trimEnd(); } export function renderReadOriginalLink(publicUrl) { if (!publicUrl) return ''; return [ '
', '

✨ 完整网页版更精彩

', '

动画 · 互动 · 高清大图 · 完整内容

', '

⬇ ⬇ ⬇

', '

', `📖 阅读原文`, '

', '

👆 点击上方按钮,查看完整精彩页面

', '

➡️ ➡️ ➡️

', '
', ].join(''); } /** @deprecated 保留别名,避免外部引用断裂 */ export const renderReadOriginalTop = renderReadOriginalLink; export function renderFollowTkMindSection({ qrcodeImageUrl = '', } = {}) { const qrcodeBlock = qrcodeImageUrl ? `TKMind 服务号二维码` : ''; return [ '
', '

欢迎关注 TKMind 智趣

', qrcodeBlock, qrcodeImageUrl ? '

长按扫描关注

' : '

关注 TKMind 服务号

', '
', ].filter(Boolean).join(''); } export function applyWechatDraftArticleLayout( content, { publicUrl = '', qrcodeImageUrl = '' } = {}, ) { const footerParts = []; if (publicUrl) { footerParts.push(renderReadOriginalLink(publicUrl)); } footerParts.push(renderFollowTkMindSection({ qrcodeImageUrl })); const footer = footerParts.filter(Boolean).join('\n'); const body = clipWechatDraftBodyToFitFooter(String(content ?? '').trim(), footer); return [body, footer].filter(Boolean).join(body && footer ? '\n' : ''); } export function resolveTkMindBrandIconPath(memindLibRoot = process.cwd()) { const candidates = [ path.join(memindLibRoot, 'public', PLATFORM_BRAND_ICON_PATH.replace(/^\//, '')), path.join(memindLibRoot, 'dist', PLATFORM_BRAND_ICON_PATH.replace(/^\//, '')), path.join(process.cwd(), 'public', PLATFORM_BRAND_ICON_PATH.replace(/^\//, '')), ]; return candidates.find((candidate) => fs.existsSync(candidate)) ?? null; } export function resolveMpFollowQrcodeAssetPath(memindLibRoot = process.cwd()) { const fromHelper = resolveMpFollowQrcodePath(memindLibRoot); if (fromHelper) return fromHelper; const publicAsset = path.join(memindLibRoot, 'public', MP_FOLLOW_QRCODE_PUBLIC_PATH.replace(/^\//, '')); if (fs.existsSync(publicAsset)) return publicAsset; const cwdPublicAsset = path.join(process.cwd(), 'public', MP_FOLLOW_QRCODE_PUBLIC_PATH.replace(/^\//, '')); return fs.existsSync(cwdPublicAsset) ? cwdPublicAsset : null; } export async function uploadWechatDraftBrandAssets({ accessToken, wechatFetch = undiciFetch, memindLibRoot = process.cwd(), env = process.env, } = {}) { const qrcodePath = resolveMpFollowQrcodeAssetPath(memindLibRoot); const publicFallbackUrl = resolveMpFollowQrcodePublicUrl(env); let qrcodeImageUrl = ''; if (!qrcodePath) { return { qrcodeImageUrl }; } if (!accessToken) { return { qrcodeImageUrl: publicFallbackUrl }; } try { qrcodeImageUrl = await uploadWechatArticleContentImage( accessToken, fs.readFileSync(qrcodePath), { wechatFetch, filename: 'mp-follow-qrcode.png' }, ); } catch { qrcodeImageUrl = publicFallbackUrl; } return { qrcodeImageUrl }; } export async function finalizeWechatDraftArticleForPush({ content, publicUrl = '', accessToken = null, wechatFetch = undiciFetch, memindLibRoot = process.cwd(), } = {}) { const brandAssets = await uploadWechatDraftBrandAssets({ accessToken, wechatFetch, memindLibRoot, }); return { content: applyWechatDraftArticleLayout(content, { publicUrl, ...brandAssets, }), ...brandAssets, }; }