62caf4134b
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>
191 lines
5.5 KiB
JavaScript
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);
|
|
},
|
|
};
|
|
}
|