#!/usr/bin/env node /** * Cleanup Tang's duplicate scheduled tasks on 103. * Dry-run by default; pass --apply to mutate rows. */ import process from 'node:process'; import mysql from 'mysql2/promise'; import { loadH5Environment } from './load-env.mjs'; loadH5Environment(import.meta.dirname); const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db'; const DUPLICATE_WEATHER_TASK_ID = '66f0ee11-7777-415f-affd-34422c772d0f'; const KEEP_WEATHER_TASK_ID = '23456db0-918e-4055-9ca3-e17e83b2dc24'; const fmt = (ms) => new Date(Number(ms)).toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false, }); async function main() { const apply = process.argv.includes('--apply'); const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 }); const now = Date.now(); const plan = []; const [dupRows] = await pool.query( `SELECT id, title, status, hour, minute, last_run_at, created_at FROM h5_scheduled_tasks WHERE id = ? AND user_id = ? LIMIT 1`, [DUPLICATE_WEATHER_TASK_ID, TANG], ); const dup = dupRows[0]; if (!dup) { plan.push({ taskId: DUPLICATE_WEATHER_TASK_ID, action: 'skip', reason: 'not_found' }); } else if (dup.status === 'cancelled') { plan.push({ taskId: DUPLICATE_WEATHER_TASK_ID, action: 'skip', reason: 'already_cancelled', title: dup.title }); } else { plan.push({ taskId: DUPLICATE_WEATHER_TASK_ID, title: dup.title, status: dup.status, schedule: `${dup.hour}:${String(dup.minute).padStart(2, '0')}`, keepTaskId: KEEP_WEATHER_TASK_ID, action: apply ? 'cancel_duplicate' : 'would_cancel_duplicate', }); if (apply) { await pool.query( `UPDATE h5_scheduled_tasks SET status = 'cancelled', updated_at = ?, last_error = NULL WHERE id = ? AND user_id = ?`, [now, DUPLICATE_WEATHER_TASK_ID, TANG], ); } } const [activeRows] = await pool.query( `SELECT id, title, status, hour, minute, next_run_at FROM h5_scheduled_tasks WHERE user_id = ? AND status IN ('active', 'locked') ORDER BY hour, minute, created_at`, [TANG], ); console.log(JSON.stringify({ apply, now: fmt(now), plan, remainingActive: activeRows.map((row) => ({ id: row.id, title: row.title, status: row.status, schedule: `${row.hour}:${String(row.minute).padStart(2, '0')}`, nextRunAt: fmt(row.next_run_at), })) }, null, 2)); await pool.end(); } main().catch((err) => { console.error(err); process.exit(1); });