b09b269199
Update scripts, docs, nginx configs, and release-gate safety checks after the Studio server public IP changed from 58.38.22.103. Co-authored-by: Cursor <cursoragent@cursor.com>
109 lines
3.9 KiB
JavaScript
109 lines
3.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* 向 103 生产「唐」微信用户推送一条早安随机话术预览。
|
|
*
|
|
* Usage:
|
|
* node scripts/send-morning-greeting-preview-103.mjs
|
|
* node scripts/send-morning-greeting-preview-103.mjs --user-id <uuid>
|
|
*/
|
|
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 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);
|
|
}
|