#!/usr/bin/env node /** * 测试账号授权脚本(仅内部使用,不部署到客户侧) * * 通过 Parse Master Key: * 1. 确认目标 _User 存在 * 2. 检查/创建 APIGAuth(user=, api=) 并充值 * 3. 通过 POST /sessions 为该用户创建 session token * 4. 写入 ~/.openclaw/voc-credentials.json * * 用法: * node scripts/tools/_grant-test-account.js [apigId] [count] * node scripts/tools/_grant-test-account.js Z0PxzHjYzB Vo3ROWEvDy 500 * * 默认: apigId=Vo3ROWEvDy (voc-social), count=500 */ const fs = require('fs'); const os = require('os'); const path = require('path'); const APP_ID = 'ncloudmaster'; const MASTER_KEY = 'SnkK12*&sunq2#@20!'; const BASE = 'https://server.fmode.cn/parse'; const userId = process.argv[2]; const apigId = process.argv[3] || 'Vo3ROWEvDy'; const count = parseInt(process.argv[4] || '500', 10); if (!userId) { console.log('用法: node _grant-test-account.js [apigId=Vo3ROWEvDy] [count=500]'); process.exit(1); } const masterHeaders = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': APP_ID, 'X-Parse-Master-Key': MASTER_KEY, }; async function main() { console.log(`== 测试账号授权 ==`); console.log(` user: ${userId}`); console.log(` apig: ${apigId}`); console.log(` count: ${count}`); // 1) 确认 _User 存在 console.log(`\n[1/4] 查询 _User/${userId} ...`); const r1 = await fetch(`${BASE}/users/${userId}`, { headers: masterHeaders }); const user = await r1.json(); if (!r1.ok || !user.objectId) { console.error('❌ 找不到该用户:', JSON.stringify(user)); process.exit(2); } console.log(` ✓ 用户: ${user.username} (${user.objectId}) created=${user.createdAt}`); // 2) 查 APIGAuth console.log(`\n[2/4] 查 APIGAuth(user=${userId}, api=${apigId}) ...`); const where = encodeURIComponent(JSON.stringify({ user: { __type: 'Pointer', className: '_User', objectId: userId }, api: { __type: 'Pointer', className: 'APIG', objectId: apigId }, })); const r2 = await fetch(`${BASE}/classes/APIGAuth?where=${where}&limit=1`, { headers: masterHeaders }); const d2 = await r2.json(); let authId; if (d2.results && d2.results.length > 0) { const a = d2.results[0]; authId = a.objectId; console.log(` 找到: ${authId} · count=${a.count ?? 0} · used=${a.used ?? 0}`); // 更新 count console.log(` -> 更新 count=${count}, used=0`); const r2b = await fetch(`${BASE}/classes/APIGAuth/${authId}`, { method: 'PUT', headers: masterHeaders, body: JSON.stringify({ count, used: 0 }), }); const d2b = await r2b.json(); if (!r2b.ok) { console.error('❌ 更新失败:', JSON.stringify(d2b)); } else { console.log(` ✓ APIGAuth 更新: ${JSON.stringify(d2b)}`); } } else { console.log(` 未找到 -> 创建 APIGAuth(count=${count})`); const r2c = await fetch(`${BASE}/classes/APIGAuth`, { method: 'POST', headers: masterHeaders, body: JSON.stringify({ user: { __type: 'Pointer', className: '_User', objectId: userId }, api: { __type: 'Pointer', className: 'APIG', objectId: apigId }, count, used: 0, }), }); const d2c = await r2c.json(); if (!r2c.ok || !d2c.objectId) { console.error('❌ 创建失败:', JSON.stringify(d2c)); process.exit(3); } authId = d2c.objectId; console.log(` ✓ 新 APIGAuth: ${authId}`); } // 3) 创建 Session (Parse restricted session) console.log(`\n[3/4] POST /sessions 创建 session ...`); // Parse cloud sessions require user pointer and authData; master key bypass available const r3 = await fetch(`${BASE}/sessions`, { method: 'POST', headers: masterHeaders, body: JSON.stringify({ user: { __type: 'Pointer', className: '_User', objectId: userId }, createdWith: { action: 'login', authProvider: 'master-key-script' }, restricted: false, expiresAt: { __type: 'Date', iso: new Date(Date.now() + 30 * 24 * 3600 * 1000).toISOString() }, installationId: 'openclaw-admin-grant', }), }); const d3 = await r3.json(); console.log(` status=${r3.status}`); console.log(` resp: ${JSON.stringify(d3).slice(0, 300)}`); let sessionToken = d3.sessionToken; if (!sessionToken) { // 尝试方法 2: POST /classes/_Session console.log(` /sessions 失败, 尝试 /classes/_Session ...`); const r3b = await fetch(`${BASE}/classes/_Session`, { method: 'POST', headers: masterHeaders, body: JSON.stringify({ user: { __type: 'Pointer', className: '_User', objectId: userId }, sessionToken: 'r:' + randomHex(32), createdWith: { action: 'login', authProvider: 'master-key-script' }, restricted: false, expiresAt: { __type: 'Date', iso: new Date(Date.now() + 30 * 24 * 3600 * 1000).toISOString() }, installationId: 'openclaw-admin-grant', }), }); const d3b = await r3b.json(); console.log(` status=${r3b.status} resp=${JSON.stringify(d3b).slice(0, 300)}`); // The sessionToken was sent in request; query back to confirm it was stored if (r3b.ok && d3b.objectId) { const r3c = await fetch(`${BASE}/classes/_Session/${d3b.objectId}`, { headers: masterHeaders }); const d3c = await r3c.json(); sessionToken = d3c.sessionToken; console.log(` query-back token: ${sessionToken ? sessionToken.slice(0, 10) + '...' : 'none'}`); } } if (!sessionToken) { console.error('\n❌ 无法拿到 sessionToken'); console.log('建议手工在控制台为用户创建 session,或让用户自行登录获取'); process.exit(4); } // 4) 写入 voc-credentials.json const credPath = path.join(os.homedir(), '.openclaw', 'voc-credentials.json'); fs.mkdirSync(path.dirname(credPath), { recursive: true }); let cur = {}; if (fs.existsSync(credPath)) { try { cur = JSON.parse(fs.readFileSync(credPath, 'utf-8')); } catch (e) {} } cur.vocToken = sessionToken; fs.writeFileSync(credPath, JSON.stringify(cur, null, 2) + '\n', 'utf-8'); console.log(`\n[4/4] ✓ sessionToken 写入 ${credPath}`); console.log(` token: ${sessionToken.slice(0, 10)}...${sessionToken.slice(-6)}`); // 5) 最终校验:用该 token 调 /users/me console.log(`\n== 校验 ==`); const verify = await fetch(`${BASE}/users/me`, { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Session-Token': sessionToken, }, }); const vdata = await verify.json(); console.log(` /users/me status=${verify.status}`); console.log(` returned user: ${vdata.username} (${vdata.objectId})`); console.log(`\n🎉 测试账号已就绪: ${user.username}`); console.log(` - APIG ${apigId} 余额: ${count} 次`); console.log(` - Token 已写入 ${credPath}`); console.log(` - 可执行 node scripts/tools/collect-jiangzhong-liver-deep.js --batch=1 --force 重跑抖音`); } function randomHex(n) { let s = ''; const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; for (let i = 0; i < n; i++) s += chars[Math.floor(Math.random() * chars.length)]; return s; } main().catch((e) => { console.error('❌', e); process.exit(9); });