Files
memind/wechat-news-morning-draft-worker.mjs
T
john 62caf4134b feat(wechat): add Portal worker for daily news morning draft auto push
Run scheduled page generation before push time and write WeChat drafts from admin config so news morning reports no longer require manual pushes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-11 08:48:02 +08:00

191 lines
5.5 KiB
JavaScript

import { executeScheduledTask } from './scheduled-task-executor.mjs';
import { localDateKey } from './schedule-time.mjs';
import {
buildNewsMorningAutoGenerationTask,
findLatestNewsMorningPage,
isLocalScheduleDue,
isNewsMorningPageForToday,
isWithinNewsMorningGenerateLeadWindow,
} from './wechat-news-morning-draft.mjs';
import {
isWechatNewsMorningDraftWorkerEnabled,
wechatNewsMorningDraftExecutionTimeoutMs,
wechatNewsMorningDraftWorkerIntervalMs,
} from './wechat-news-morning-draft-worker-config.mjs';
import { isPassiveCanaryRuntime } from './server/portal-runtime-role.mjs';
function resolveTodayPage(config, h5Root, now = Date.now()) {
const page = findLatestNewsMorningPage({
h5Root,
userId: config.sourceUserId,
slugPattern: config.pageSlugPattern,
date: new Date(now),
timezone: config.timezone,
});
if (!page || !isNewsMorningPageForToday(page, { date: new Date(now), timezone: config.timezone })) {
return null;
}
return page;
}
export function startWechatNewsMorningDraftWorker({
wechatNewsMorningDraftService = null,
mpConfig = null,
userAuth = null,
tkmindProxy = null,
agentRunGateway = null,
cursorExecutorPolicyService = null,
sessionSnapshotService = null,
pool = null,
h5Root = null,
env = process.env,
executeTask = executeScheduledTask,
logger = console,
intervalMs = wechatNewsMorningDraftWorkerIntervalMs(env),
executionTimeoutMs = wechatNewsMorningDraftExecutionTimeoutMs(env),
runOnStart = true,
setIntervalFn = setInterval,
} = {}) {
if (
isPassiveCanaryRuntime(env)
|| !isWechatNewsMorningDraftWorkerEnabled(env)
|| !wechatNewsMorningDraftService
|| !mpConfig?.enabled
|| !userAuth
|| !tkmindProxy
|| !h5Root
) {
return { stop() {} };
}
let stopped = false;
let running = false;
let generationInFlightDateKey = null;
const ensureTodayPage = async (config, dateKey) => {
if (generationInFlightDateKey === dateKey) return null;
generationInFlightDateKey = dateKey;
try {
const task = buildNewsMorningAutoGenerationTask(config);
await executeTask(task, {
userAuth,
tkmindProxy,
agentRunGateway,
cursorExecutorPolicyService,
sessionSnapshotService,
pool,
h5Root,
timeoutMs: executionTimeoutMs,
logger,
});
const page = resolveTodayPage(config, h5Root);
if (!page) {
throw new Error('新闻早报页面生成未完成');
}
await wechatNewsMorningDraftService.recordAutoGenerationRun({
status: 'success',
pageSlug: page.slug,
});
logger.log?.('[NewsMorningDraft] auto-generated today page', {
slug: page.slug,
userId: config.sourceUserId,
});
return page;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
await wechatNewsMorningDraftService.recordAutoGenerationRun({
status: 'failed',
errorMessage: message,
}).catch(() => {});
logger.warn?.('[NewsMorningDraft] auto-generate failed:', message);
throw error;
} finally {
if (generationInFlightDateKey === dateKey) {
generationInFlightDateKey = null;
}
}
};
const runOnce = async () => {
if (running || stopped) return;
running = true;
try {
const config = await wechatNewsMorningDraftService.getConfig();
if (!config.enabled || !config.autoPushEnabled || !config.sourceUserId) return;
const now = Date.now();
const timezone = config.timezone;
const dateKey = localDateKey(now, timezone);
const toleranceMinutes = Math.max(1, Math.ceil(Number(intervalMs) / 60_000));
let todayPage = resolveTodayPage(config, h5Root, now);
const shouldGenerate =
config.autoGenerateEnabled !== false
&& !todayPage
&& (
isWithinNewsMorningGenerateLeadWindow(config, now)
|| isLocalScheduleDue(
{ hour: config.pushHour, minute: config.pushMinute, timezone },
now,
toleranceMinutes + 120,
)
);
if (shouldGenerate && generationInFlightDateKey !== dateKey) {
try {
todayPage = await ensureTodayPage(config, dateKey);
} catch {
// keep trying during generate window on later scans
}
}
if (
!isLocalScheduleDue(
{ hour: config.pushHour, minute: config.pushMinute, timezone },
now,
toleranceMinutes,
)
) {
return;
}
if (await wechatNewsMorningDraftService.hasSuccessfulAutoPushToday({ now })) {
return;
}
todayPage = todayPage ?? resolveTodayPage(config, h5Root, now);
if (!todayPage) {
logger.warn?.('[NewsMorningDraft] skip auto push: today page missing', {
userId: config.sourceUserId,
dateKey,
});
return;
}
const result = await wechatNewsMorningDraftService.pushDraft({
triggeredBy: 'auto-push',
});
logger.log?.('[NewsMorningDraft] auto push succeeded', {
draftMediaId: result.draftMediaId,
pageSlug: result.preview?.page?.slug ?? todayPage.slug,
});
} catch (error) {
logger.warn?.('[NewsMorningDraft] auto worker failed:', error);
} finally {
running = false;
}
};
const timer = setIntervalFn(runOnce, Math.max(5_000, Number(intervalMs) || 30_000));
timer.unref?.();
if (runOnStart) void runOnce();
return {
runOnce,
stop() {
stopped = true;
clearInterval(timer);
},
};
}