_grant-test-account.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env node
  2. /**
  3. * 测试账号授权脚本(仅内部使用,不部署到客户侧)
  4. *
  5. * 通过 Parse Master Key:
  6. * 1. 确认目标 _User 存在
  7. * 2. 检查/创建 APIGAuth(user=<userId>, api=<apigId>) 并充值
  8. * 3. 通过 POST /sessions 为该用户创建 session token
  9. * 4. 写入 ~/.openclaw/voc-credentials.json
  10. *
  11. * 用法:
  12. * node scripts/tools/_grant-test-account.js <userObjectId> [apigId] [count]
  13. * node scripts/tools/_grant-test-account.js Z0PxzHjYzB Vo3ROWEvDy 500
  14. *
  15. * 默认: apigId=Vo3ROWEvDy (voc-social), count=500
  16. */
  17. const fs = require('fs');
  18. const os = require('os');
  19. const path = require('path');
  20. const APP_ID = 'ncloudmaster';
  21. const MASTER_KEY = 'SnkK12*&sunq2#@20!';
  22. const BASE = 'https://server.fmode.cn/parse';
  23. const userId = process.argv[2];
  24. const apigId = process.argv[3] || 'Vo3ROWEvDy';
  25. const count = parseInt(process.argv[4] || '500', 10);
  26. if (!userId) {
  27. console.log('用法: node _grant-test-account.js <userObjectId> [apigId=Vo3ROWEvDy] [count=500]');
  28. process.exit(1);
  29. }
  30. const masterHeaders = {
  31. 'Content-Type': 'application/json',
  32. 'X-Parse-Application-Id': APP_ID,
  33. 'X-Parse-Master-Key': MASTER_KEY,
  34. };
  35. async function main() {
  36. console.log(`== 测试账号授权 ==`);
  37. console.log(` user: ${userId}`);
  38. console.log(` apig: ${apigId}`);
  39. console.log(` count: ${count}`);
  40. // 1) 确认 _User 存在
  41. console.log(`\n[1/4] 查询 _User/${userId} ...`);
  42. const r1 = await fetch(`${BASE}/users/${userId}`, { headers: masterHeaders });
  43. const user = await r1.json();
  44. if (!r1.ok || !user.objectId) {
  45. console.error('❌ 找不到该用户:', JSON.stringify(user));
  46. process.exit(2);
  47. }
  48. console.log(` ✓ 用户: ${user.username} (${user.objectId}) created=${user.createdAt}`);
  49. // 2) 查 APIGAuth
  50. console.log(`\n[2/4] 查 APIGAuth(user=${userId}, api=${apigId}) ...`);
  51. const where = encodeURIComponent(JSON.stringify({
  52. user: { __type: 'Pointer', className: '_User', objectId: userId },
  53. api: { __type: 'Pointer', className: 'APIG', objectId: apigId },
  54. }));
  55. const r2 = await fetch(`${BASE}/classes/APIGAuth?where=${where}&limit=1`, { headers: masterHeaders });
  56. const d2 = await r2.json();
  57. let authId;
  58. if (d2.results && d2.results.length > 0) {
  59. const a = d2.results[0];
  60. authId = a.objectId;
  61. console.log(` 找到: ${authId} · count=${a.count ?? 0} · used=${a.used ?? 0}`);
  62. // 更新 count
  63. console.log(` -> 更新 count=${count}, used=0`);
  64. const r2b = await fetch(`${BASE}/classes/APIGAuth/${authId}`, {
  65. method: 'PUT',
  66. headers: masterHeaders,
  67. body: JSON.stringify({ count, used: 0 }),
  68. });
  69. const d2b = await r2b.json();
  70. if (!r2b.ok) {
  71. console.error('❌ 更新失败:', JSON.stringify(d2b));
  72. } else {
  73. console.log(` ✓ APIGAuth 更新: ${JSON.stringify(d2b)}`);
  74. }
  75. } else {
  76. console.log(` 未找到 -> 创建 APIGAuth(count=${count})`);
  77. const r2c = await fetch(`${BASE}/classes/APIGAuth`, {
  78. method: 'POST',
  79. headers: masterHeaders,
  80. body: JSON.stringify({
  81. user: { __type: 'Pointer', className: '_User', objectId: userId },
  82. api: { __type: 'Pointer', className: 'APIG', objectId: apigId },
  83. count,
  84. used: 0,
  85. }),
  86. });
  87. const d2c = await r2c.json();
  88. if (!r2c.ok || !d2c.objectId) {
  89. console.error('❌ 创建失败:', JSON.stringify(d2c));
  90. process.exit(3);
  91. }
  92. authId = d2c.objectId;
  93. console.log(` ✓ 新 APIGAuth: ${authId}`);
  94. }
  95. // 3) 创建 Session (Parse restricted session)
  96. console.log(`\n[3/4] POST /sessions 创建 session ...`);
  97. // Parse cloud sessions require user pointer and authData; master key bypass available
  98. const r3 = await fetch(`${BASE}/sessions`, {
  99. method: 'POST',
  100. headers: masterHeaders,
  101. body: JSON.stringify({
  102. user: { __type: 'Pointer', className: '_User', objectId: userId },
  103. createdWith: { action: 'login', authProvider: 'master-key-script' },
  104. restricted: false,
  105. expiresAt: { __type: 'Date', iso: new Date(Date.now() + 30 * 24 * 3600 * 1000).toISOString() },
  106. installationId: 'openclaw-admin-grant',
  107. }),
  108. });
  109. const d3 = await r3.json();
  110. console.log(` status=${r3.status}`);
  111. console.log(` resp: ${JSON.stringify(d3).slice(0, 300)}`);
  112. let sessionToken = d3.sessionToken;
  113. if (!sessionToken) {
  114. // 尝试方法 2: POST /classes/_Session
  115. console.log(` /sessions 失败, 尝试 /classes/_Session ...`);
  116. const r3b = await fetch(`${BASE}/classes/_Session`, {
  117. method: 'POST',
  118. headers: masterHeaders,
  119. body: JSON.stringify({
  120. user: { __type: 'Pointer', className: '_User', objectId: userId },
  121. sessionToken: 'r:' + randomHex(32),
  122. createdWith: { action: 'login', authProvider: 'master-key-script' },
  123. restricted: false,
  124. expiresAt: { __type: 'Date', iso: new Date(Date.now() + 30 * 24 * 3600 * 1000).toISOString() },
  125. installationId: 'openclaw-admin-grant',
  126. }),
  127. });
  128. const d3b = await r3b.json();
  129. console.log(` status=${r3b.status} resp=${JSON.stringify(d3b).slice(0, 300)}`);
  130. // The sessionToken was sent in request; query back to confirm it was stored
  131. if (r3b.ok && d3b.objectId) {
  132. const r3c = await fetch(`${BASE}/classes/_Session/${d3b.objectId}`, { headers: masterHeaders });
  133. const d3c = await r3c.json();
  134. sessionToken = d3c.sessionToken;
  135. console.log(` query-back token: ${sessionToken ? sessionToken.slice(0, 10) + '...' : 'none'}`);
  136. }
  137. }
  138. if (!sessionToken) {
  139. console.error('\n❌ 无法拿到 sessionToken');
  140. console.log('建议手工在控制台为用户创建 session,或让用户自行登录获取');
  141. process.exit(4);
  142. }
  143. // 4) 写入 voc-credentials.json
  144. const credPath = path.join(os.homedir(), '.openclaw', 'voc-credentials.json');
  145. fs.mkdirSync(path.dirname(credPath), { recursive: true });
  146. let cur = {};
  147. if (fs.existsSync(credPath)) {
  148. try { cur = JSON.parse(fs.readFileSync(credPath, 'utf-8')); } catch (e) {}
  149. }
  150. cur.vocToken = sessionToken;
  151. fs.writeFileSync(credPath, JSON.stringify(cur, null, 2) + '\n', 'utf-8');
  152. console.log(`\n[4/4] ✓ sessionToken 写入 ${credPath}`);
  153. console.log(` token: ${sessionToken.slice(0, 10)}...${sessionToken.slice(-6)}`);
  154. // 5) 最终校验:用该 token 调 /users/me
  155. console.log(`\n== 校验 ==`);
  156. const verify = await fetch(`${BASE}/users/me`, {
  157. headers: {
  158. 'X-Parse-Application-Id': APP_ID,
  159. 'X-Parse-Session-Token': sessionToken,
  160. },
  161. });
  162. const vdata = await verify.json();
  163. console.log(` /users/me status=${verify.status}`);
  164. console.log(` returned user: ${vdata.username} (${vdata.objectId})`);
  165. console.log(`\n🎉 测试账号已就绪: ${user.username}`);
  166. console.log(` - APIG ${apigId} 余额: ${count} 次`);
  167. console.log(` - Token 已写入 ${credPath}`);
  168. console.log(` - 可执行 node scripts/tools/collect-jiangzhong-liver-deep.js --batch=1 --force 重跑抖音`);
  169. }
  170. function randomHex(n) {
  171. let s = '';
  172. const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
  173. for (let i = 0; i < n; i++) s += chars[Math.floor(Math.random() * chars.length)];
  174. return s;
  175. }
  176. main().catch((e) => { console.error('❌', e); process.exit(9); });