#!/usr/bin/env node /** * 向 103 生产「唐」微信用户推送一条早安随机话术预览。 * * Usage: * node scripts/send-morning-greeting-preview-103.mjs * node scripts/send-morning-greeting-preview-103.mjs --user-id */ 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@114.85.107.50'; const REMOTE_ROOT = '/Users/john/Project/Memind'; const NODE103 = '/opt/homebrew/opt/node@24/bin/node'; const DEFAULT_TANG_USER_ID = 'a70ff537-8908-486e-9b6c-042e07cc25db'; function parseUserId(argv) { for (let i = 0; i < argv.length; i += 1) { if (argv[i] === '--user-id') return String(argv[i + 1] ?? '').trim(); } return DEFAULT_TANG_USER_ID; } function sh(cmd) { execSync(cmd, { stdio: 'inherit' }); } const userId = parseUserId(process.argv.slice(2)); const remoteLib = `${REMOTE_ROOT}/wechat/morning-greeting-library.mjs`; const remoteRunner = `${REMOTE_ROOT}/.tmp-send-morning-greeting-preview.mjs`; const remoteScript = ` import path from 'node:path'; import mysql from 'mysql2/promise'; import { formatMorningGreetingDeliveryText } from './wechat/morning-greeting-library.mjs'; const USER_ID = ${JSON.stringify(userId)}; 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; if (!appId || !appSecret) throw new Error('missing wechat credentials'); const [users] = await pool.query( 'SELECT id, username, display_name FROM h5_users WHERE id = ? LIMIT 1', [USER_ID], ); const user = users?.[0]; if (!user) throw new Error('user not found: ' + USER_ID); const [ident] = await pool.query( 'SELECT openid FROM h5_user_wechat_identities WHERE user_id = ? AND app_id = ? LIMIT 1', [USER_ID, appId], ); const openid = ident?.[0]?.openid; if (!openid) throw new Error('user not bound to wechat: ' + USER_ID); const text = formatMorningGreetingDeliveryText({ userId: USER_ID, timezone: process.env.H5_DEFAULT_TIMEZONE || 'Asia/Shanghai', }); const tokenRes = 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 }), }); const tokenPayload = await tokenRes.json(); if (!tokenPayload.access_token) throw new Error(JSON.stringify(tokenPayload)); const sendRes = 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 } }), }, ); const sendPayload = await sendRes.json(); if (Number(sendPayload.errcode ?? 0) !== 0) throw new Error(JSON.stringify(sendPayload)); console.log(JSON.stringify({ ok: true, userId: USER_ID, username: user.username, displayName: user.display_name, openid: openid.slice(0, 8) + '...', preview: text, }, null, 2)); await pool.end(); `.trim(); const localLib = path.join(root, 'wechat/morning-greeting-library.mjs'); const localScheduleTime = path.join(root, 'schedule-time.mjs'); const localRunner = path.join(root, '.tmp-send-morning-greeting-preview.mjs'); fs.writeFileSync(localRunner, remoteScript); sh(`scp -q ${localLib} ${HOST}:${remoteLib}`); sh(`scp -q ${localScheduleTime} ${HOST}:${REMOTE_ROOT}/schedule-time.mjs`); sh(`scp -q ${localRunner} ${HOST}:${remoteRunner}`); try { sh(`ssh -o BatchMode=yes ${HOST} 'cd ${REMOTE_ROOT} && ${NODE103} ${remoteRunner} && rm -f ${remoteRunner}'`); } finally { fs.unlinkSync(localRunner); }