#!/usr/bin/env node import { execSync } from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); const HOST = 'john@180.159.29.143'; const REMOTE_ROOT = '/Users/john/Project/Memind'; const NODE103 = '/opt/homebrew/opt/node@24/bin/node'; const TANG = 'a70ff537-8908-486e-9b6c-042e07cc25db'; const remoteScript = ` import path from 'node:path'; import mysql from 'mysql2/promise'; import { createSubscribeMorningReminderPendingStore, handleSubscribeMorningReminderTurn, subscribeMorningReminderSchedule, } from './wechat/subscribe-morning-reminder.mjs'; import { createScheduleService } from './schedule-service.mjs'; process.loadEnvFile(path.join('${REMOTE_ROOT}', '.env')); const pool = mysql.createPool({ uri: process.env.DATABASE_URL, connectionLimit: 2 }); const appId = process.env.H5_WECHAT_MP_APP_ID ?? process.env.WECHAT_MP_APP_ID; const appSecret = process.env.H5_WECHAT_MP_APP_SECRET ?? process.env.WECHAT_MP_APP_SECRET; const timezone = process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai'; const { hour, minute } = subscribeMorningReminderSchedule({ env: process.env }); const [ident] = await pool.query( 'SELECT openid FROM h5_user_wechat_identities WHERE user_id = ? AND app_id = ? LIMIT 1', ['${TANG}', appId], ); const openid = ident?.[0]?.openid; if (!openid) throw new Error('openid missing'); const pendingStore = createSubscribeMorningReminderPendingStore({ mysqlPool: pool }); const scheduleService = createScheduleService(pool, { defaultTimezone: timezone }); await pendingStore.setPending({ appId, openid }); async function sendWechatText(text) { const tokenPayload = await ( await fetch('https://api.weixin.qq.com/cgi-bin/stable_token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ grant_type: 'client_credential', appid: appId, secret: appSecret }), }) ).json(); const sendPayload = await ( await fetch( 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' + encodeURIComponent(tokenPayload.access_token), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ touser: openid, msgtype: 'text', text: { content: text } }), }, ) ).json(); if (Number(sendPayload.errcode ?? 0) !== 0) throw new Error(JSON.stringify(sendPayload)); } const reply = await handleSubscribeMorningReminderTurn({ appId, openid, text: '1', pendingStore, scheduleService, boundUser: { userId: '${TANG}' }, timezone, hour, minute, sourceMessageId: 'e2e-retry', }); if (reply) await sendWechatText(reply); const [items] = await pool.query( \`SELECT id, title, metadata_json FROM h5_schedule_items WHERE user_id = ? AND deleted_at IS NULL AND JSON_UNQUOTE(JSON_EXTRACT(metadata_json, '$.source')) = 'subscribe_morning_reminder' ORDER BY created_at DESC LIMIT 1\`, ['${TANG}'], ); const [reminders] = items[0] ? await pool.query( 'SELECT id, remind_at, channel, status FROM h5_schedule_reminders WHERE item_id = ? ORDER BY created_at DESC LIMIT 1', [items[0].id], ) : [[]]; console.log(JSON.stringify({ ok: Boolean(items[0]), reply, item: items[0] ?? null, reminder: reminders[0] ?? null }, null, 2)); await pool.end(); `.trim(); execSync(`scp -q ${path.join(root, 'wechat/subscribe-morning-reminder.mjs')} ${HOST}:${REMOTE_ROOT}/wechat/subscribe-morning-reminder.mjs`, { stdio: 'inherit' }); const local = path.join(root, '.tmp-retry-tang-morning.mjs'); fs.writeFileSync(local, remoteScript); execSync(`scp -q ${local} ${HOST}:${REMOTE_ROOT}/.tmp-retry-tang-morning.mjs`, { stdio: 'inherit' }); execSync(`ssh -o BatchMode=yes ${HOST} 'cd ${REMOTE_ROOT} && ${NODE103} .tmp-retry-tang-morning.mjs && rm -f .tmp-retry-tang-morning.mjs'`, { stdio: 'inherit' }); fs.unlinkSync(local);