028bb18dc0
Scheduled tasks can finish once the expected HTML is on disk and stable, instead of waiting for Goose Finish. Adds news-item templates and scripts for generating a single-event page and committing it as a WeChat draft. Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
2.9 KiB
JavaScript
80 lines
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 阶段三:把用户确认过的正文原样提交到公众号草稿箱。
|
|
*
|
|
* 这里刻意不做任何 HTML 转换——content 就是预览页显示的那一份,由 news-engine 的
|
|
* 块渲染器产出。任何在这里的二次加工都会破坏「预览即草稿」。
|
|
*
|
|
* stdin { userId, title, author?, digest, content, contentSourceUrl, thumb: { url?, localPath? } }
|
|
* stdout { ok, draftMediaId }
|
|
*/
|
|
import fs from 'node:fs';
|
|
import process from 'node:process';
|
|
import {
|
|
addWechatDraftArticle,
|
|
uploadWechatPermanentThumb,
|
|
} from '../wechat-news-morning-draft.mjs';
|
|
import {
|
|
createLightContext,
|
|
loadPushEnvironment,
|
|
readStdinPayload,
|
|
runScript,
|
|
} from './news-item-wechat-context.mjs';
|
|
|
|
const PLACEHOLDER_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="900" height="900">'
|
|
+ '<rect width="900" height="900" fill="#1a1a2e"/>'
|
|
+ '<text x="50%" y="50%" fill="#fff" font-size="42" text-anchor="middle" dominant-baseline="middle">TKMind</text>'
|
|
+ '</svg>';
|
|
|
|
async function loadThumbBuffer(thumb) {
|
|
const sharp = (await import('sharp')).default;
|
|
let raw = null;
|
|
const localPath = String(thumb?.localPath ?? '').trim();
|
|
const url = String(thumb?.url ?? '').trim();
|
|
if (localPath && fs.existsSync(localPath)) {
|
|
raw = fs.readFileSync(localPath);
|
|
} else if (/^https?:\/\//i.test(url)) {
|
|
const response = await fetch(url, { signal: AbortSignal.timeout(20_000) });
|
|
if (response.ok) raw = Buffer.from(await response.arrayBuffer());
|
|
}
|
|
if (!raw) return sharp(Buffer.from(PLACEHOLDER_SVG)).png().toBuffer();
|
|
return sharp(raw).resize(900, 900, { fit: 'cover' }).png().toBuffer();
|
|
}
|
|
|
|
async function main() {
|
|
const env = loadPushEnvironment(process.env);
|
|
const payload = readStdinPayload();
|
|
const userId = String(payload.userId ?? '').trim();
|
|
const title = String(payload.title ?? '').trim();
|
|
const content = String(payload.content ?? '').trim();
|
|
if (!userId) throw new Error('缺少 userId');
|
|
if (!title) throw new Error('缺少草稿标题');
|
|
if (!content) throw new Error('缺少草稿正文');
|
|
|
|
const context = createLightContext(env);
|
|
try {
|
|
const { credentials, accessToken } = await context.resolveWechat(userId);
|
|
const { wechatFetch } = context;
|
|
const thumbMediaId = await uploadWechatPermanentThumb(
|
|
accessToken,
|
|
await loadThumbBuffer(payload.thumb),
|
|
{ wechatFetch },
|
|
);
|
|
const draft = await addWechatDraftArticle(accessToken, {
|
|
title: title.slice(0, 64),
|
|
author: String(payload.author ?? '').trim() || credentials.author || 'TKMind',
|
|
digest: String(payload.digest ?? '').slice(0, 120),
|
|
content,
|
|
content_source_url: String(payload.contentSourceUrl ?? '').trim() || undefined,
|
|
thumb_media_id: thumbMediaId,
|
|
need_open_comment: 0,
|
|
only_fans_can_comment: 0,
|
|
}, { wechatFetch });
|
|
return { draftMediaId: draft.draftMediaId, thumbMediaId };
|
|
} finally {
|
|
await context.close();
|
|
}
|
|
}
|
|
|
|
runScript(main);
|