index.html 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. <!doctype html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8"/>
  5. <title>Amazon Q 账号投喂 · URL 登录</title>
  6. <meta name="viewport" content="width=device-width,initial-scale=1"/>
  7. <style>
  8. :root {
  9. --bg:#0a0e1a;
  10. --panel:#0f1420;
  11. --muted:#8b95a8;
  12. --text:#e8f0ff;
  13. --accent:#4f8fff;
  14. --border:#1a2332;
  15. --glow:rgba(79,143,255,.15);
  16. }
  17. * { box-sizing:border-box; }
  18. html, body { height:100%; margin:0; }
  19. body {
  20. padding:0 0 40px;
  21. background:radial-gradient(ellipse at top, #0f1624 0%, #0a0e1a 100%);
  22. color:var(--text);
  23. font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Noto Sans,Arial,sans-serif;
  24. line-height:1.6;
  25. }
  26. h1,h2 { font-weight:700; letter-spacing:-.02em; margin:0; }
  27. h1 { font-size:28px; margin:24px 0 12px; background:linear-gradient(135deg,#4f8fff,#7b9fff); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text; }
  28. h2 { font-size:18px; margin:20px 0 16px; color:#c5d4ff; }
  29. .container { max-width:800px; margin:0 auto; padding:20px; }
  30. .panel {
  31. background:linear-gradient(145deg,rgba(15,20,32,.8),rgba(10,14,26,.9));
  32. border:1px solid var(--border);
  33. border-radius:16px;
  34. padding:24px;
  35. box-shadow:0 20px 60px rgba(0,0,0,.4),0 0 0 1px rgba(79,143,255,.08),inset 0 1px 0 rgba(255,255,255,.03);
  36. backdrop-filter:blur(12px);
  37. transition:transform .2s,box-shadow .2s;
  38. }
  39. .panel:hover { transform:translateY(-2px); box-shadow:0 24px 70px rgba(0,0,0,.5),0 0 0 1px rgba(79,143,255,.12),inset 0 1px 0 rgba(255,255,255,.04); }
  40. .row { display:flex; gap:12px; align-items:center; flex-wrap:wrap; }
  41. label { color:var(--muted); font-size:13px; font-weight:500; letter-spacing:.01em; }
  42. .field { display:flex; flex-direction:column; gap:8px; flex:1; min-width:200px; }
  43. input {
  44. background:rgba(12,16,28,.6);
  45. color:var(--text);
  46. border:1px solid var(--border);
  47. border-radius:12px;
  48. padding:12px 14px;
  49. outline:none;
  50. transition:all .2s;
  51. font-size:14px;
  52. box-shadow:inset 0 1px 2px rgba(0,0,0,.2);
  53. }
  54. input:focus {
  55. border-color:var(--accent);
  56. box-shadow:0 0 0 3px var(--glow),inset 0 1px 2px rgba(0,0,0,.2);
  57. background:rgba(12,16,28,.8);
  58. }
  59. button {
  60. background:linear-gradient(135deg,#2563eb,#1e40af);
  61. color:#fff;
  62. border:none;
  63. border-radius:12px;
  64. padding:12px 20px;
  65. font-weight:600;
  66. font-size:14px;
  67. cursor:pointer;
  68. transition:all .2s;
  69. box-shadow:0 4px 16px rgba(37,99,235,.3),inset 0 1px 0 rgba(255,255,255,.1);
  70. position:relative;
  71. overflow:hidden;
  72. }
  73. button:before {
  74. content:'';
  75. position:absolute;
  76. top:0;left:0;right:0;bottom:0;
  77. background:linear-gradient(135deg,rgba(255,255,255,.1),transparent);
  78. opacity:0;
  79. transition:opacity .2s;
  80. }
  81. button:hover { transform:translateY(-1px); box-shadow:0 6px 20px rgba(37,99,235,.4),inset 0 1px 0 rgba(255,255,255,.15); }
  82. button:hover:before { opacity:1; }
  83. button:active { transform:translateY(0); }
  84. button:disabled { opacity:.5; cursor:not-allowed; transform:none; }
  85. .btn-secondary { background:linear-gradient(135deg,#1e293b,#0f172a); box-shadow:0 4px 16px rgba(15,23,42,.3),inset 0 1px 0 rgba(255,255,255,.05); }
  86. .btn-secondary:hover { box-shadow:0 6px 20px rgba(15,23,42,.4),inset 0 1px 0 rgba(255,255,255,.08); }
  87. .switch { position:relative; display:inline-block; width:50px; height:26px; }
  88. .switch input { opacity:0; width:0; height:0; }
  89. .slider {
  90. position:absolute;
  91. cursor:pointer;
  92. top:0;left:0;right:0;bottom:0;
  93. background:linear-gradient(135deg,#374151,#1f2937);
  94. transition:.3s;
  95. border-radius:26px;
  96. border:1px solid var(--border);
  97. box-shadow:inset 0 2px 4px rgba(0,0,0,.3);
  98. }
  99. .slider:before {
  100. position:absolute;
  101. content:"";
  102. height:20px;
  103. width:20px;
  104. left:3px;
  105. bottom:2px;
  106. background:linear-gradient(135deg,#f3f4f6,#e5e7eb);
  107. transition:.3s;
  108. border-radius:50%;
  109. box-shadow:0 2px 6px rgba(0,0,0,.3);
  110. }
  111. input:checked+.slider { background:linear-gradient(135deg,#3b82f6,#2563eb); box-shadow:0 0 12px rgba(59,130,246,.4),inset 0 2px 4px rgba(0,0,0,.2); }
  112. input:checked+.slider:before { transform:translateX(24px); }
  113. .code {
  114. background:rgba(13,18,24,.8);
  115. border:1px solid var(--border);
  116. border-radius:12px;
  117. padding:14px;
  118. color:#d8e8ff;
  119. max-height:400px;
  120. overflow:auto;
  121. white-space:pre-wrap;
  122. font-size:13px;
  123. line-height:1.6;
  124. box-shadow:inset 0 2px 4px rgba(0,0,0,.3);
  125. font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;
  126. }
  127. .code::-webkit-scrollbar { width:8px; height:8px; }
  128. .code::-webkit-scrollbar-track { background:rgba(0,0,0,.2); border-radius:4px; }
  129. .code::-webkit-scrollbar-thumb { background:rgba(79,143,255,.3); border-radius:4px; }
  130. @keyframes fadeIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
  131. .panel { animation:fadeIn .4s ease-out; }
  132. .info-box {
  133. background:rgba(79,143,255,.1);
  134. border:1px solid rgba(79,143,255,.3);
  135. border-radius:12px;
  136. padding:16px;
  137. margin-bottom:20px;
  138. color:#c5d4ff;
  139. font-size:14px;
  140. }
  141. .info-box strong { color:#4f8fff; }
  142. </style>
  143. </head>
  144. <body>
  145. <div class="container">
  146. <h1>🎁 Amazon Q 账号投喂</h1>
  147. <div class="info-box">
  148. <strong>使用说明:</strong><br><br>
  149. <strong>方式一:URL 登录</strong><br>
  150. 1. 填写账号标签(可选)<br>
  151. 2. 点击"开始登录"按钮,系统会自动打开授权页面<br>
  152. 3. 在授权页面完成登录<br>
  153. 4. 返回本页面,点击"等待授权并创建账号"按钮<br>
  154. 5. 等待最多5分钟,系统会自动创建账号并投喂到主服务<br><br>
  155. <strong>方式二:手动投喂</strong><br>
  156. 直接在下方表单输入 clientId、clientSecret、refreshToken 即可快速投喂
  157. </div>
  158. <div class="panel">
  159. <h2>URL 登录(5分钟超时)</h2>
  160. <div class="row">
  161. <div class="field">
  162. <label>账号标签(可选)</label>
  163. <input id="auth_label" placeholder="例如:张三的账号" />
  164. </div>
  165. </div>
  166. <div class="row">
  167. <button onclick="startAuth()">🚀 开始登录</button>
  168. <button class="btn-secondary" onclick="claimAuth()">⏳ 等待授权并创建账号</button>
  169. </div>
  170. <div class="field">
  171. <label>登录信息</label>
  172. <pre class="code" id="auth_info">尚未开始</pre>
  173. </div>
  174. </div>
  175. <div class="panel" style="margin-top:20px">
  176. <h2>📝 手动投喂账号</h2>
  177. <div class="row">
  178. <div class="field"><label>label(可选)</label><input id="manual_label" placeholder="账号标签" /></div>
  179. <div class="field"><label>clientId</label><input id="manual_clientId" placeholder="客户端ID" /></div>
  180. </div>
  181. <div class="row">
  182. <div class="field"><label>clientSecret</label><input id="manual_clientSecret" placeholder="客户端密钥" /></div>
  183. <div class="field"><label>refreshToken</label><input id="manual_refreshToken" placeholder="刷新令牌" /></div>
  184. </div>
  185. <div class="row">
  186. <div class="field"><label>accessToken(可选)</label><input id="manual_accessToken" placeholder="访问令牌" /></div>
  187. </div>
  188. <div class="row">
  189. <button onclick="createSingle()">➕ 创建单个账号</button>
  190. </div>
  191. <div class="field">
  192. <label>结果</label>
  193. <pre class="code" id="manual_result">等待操作</pre>
  194. </div>
  195. </div>
  196. <div class="panel" style="margin-top:20px">
  197. <h2>📦 批量投喂账号</h2>
  198. <div class="field">
  199. <label>JSON 数组(每个对象包含 label、clientId、clientSecret、refreshToken、accessToken)</label>
  200. <textarea id="batch_json" style="min-height:200px;font-family:ui-monospace,monospace;background:rgba(12,16,28,.6);color:var(--text);border:1px solid var(--border);border-radius:12px;padding:12px 14px;outline:none;resize:vertical" placeholder='[
  201. {
  202. "label": "账号1",
  203. "clientId": "xxx",
  204. "clientSecret": "xxx",
  205. "refreshToken": "xxx",
  206. "accessToken": ""
  207. }
  208. ]'></textarea>
  209. </div>
  210. <div class="row">
  211. <button onclick="createBatch()">🚀 批量创建账号</button>
  212. </div>
  213. <div class="field">
  214. <label>批量结果</label>
  215. <pre class="code" id="batch_result">等待操作</pre>
  216. </div>
  217. </div>
  218. </div>
  219. <script>
  220. let currentAuth = null;
  221. async function startAuth(){
  222. const body = {
  223. label: (document.getElementById('auth_label').value || '').trim() || null,
  224. enabled: true
  225. };
  226. try {
  227. const r = await fetch('/auth/start', {
  228. method: 'POST',
  229. headers: { 'content-type': 'application/json' },
  230. body: JSON.stringify(body)
  231. });
  232. if (!r.ok) throw new Error(await r.text());
  233. const j = await r.json();
  234. currentAuth = j;
  235. const info = [
  236. '✅ 授权流程已启动!',
  237. '',
  238. '📋 验证链接: ' + j.verificationUriComplete,
  239. '🔑 用户代码: ' + (j.userCode || ''),
  240. '🆔 会话ID: ' + j.authId,
  241. '⏱️ 过期时间: ' + j.expiresIn + '秒',
  242. '🔄 轮询间隔: ' + j.interval + '秒',
  243. '',
  244. '👉 请在新窗口中打开上述链接完成登录。',
  245. '👉 登录完成后,点击下方"等待授权并创建账号"按钮。'
  246. ].join('\n');
  247. const el = document.getElementById('auth_info');
  248. el.textContent = info;
  249. try { window.open(j.verificationUriComplete, '_blank'); } catch {}
  250. } catch(e){
  251. document.getElementById('auth_info').textContent = '❌ 启动失败:' + e;
  252. }
  253. }
  254. async function claimAuth(){
  255. if (!currentAuth || !currentAuth.authId) {
  256. alert('⚠️ 请先点击"开始登录"按钮');
  257. return;
  258. }
  259. const authInfo = document.getElementById('auth_info');
  260. const originalText = authInfo.textContent;
  261. authInfo.textContent = originalText + '\n\n⏳ 正在等待授权并创建账号(最多5分钟)...\n请耐心等待,不要关闭页面...';
  262. try{
  263. const r = await fetch('/auth/claim/' + encodeURIComponent(currentAuth.authId), { method: 'POST' });
  264. if (!r.ok) {
  265. const errorText = await r.text();
  266. throw new Error(errorText);
  267. }
  268. const j = await r.json();
  269. if (j.status === 'completed' && j.account) {
  270. authInfo.textContent = '🎉 账号创建成功!账号已投喂到主服务!\n\n' +
  271. '📋 账号信息:\n' +
  272. ' 标签:' + j.account.label + '\n' +
  273. ' ID:' + j.account.id + '\n' +
  274. ' 状态:' + (j.account.enabled ? '✅ 已启用' : '⚠️ 已禁用') + '\n\n' +
  275. '感谢您的投喂!🙏';
  276. // 清空表单
  277. document.getElementById('auth_label').value = '';
  278. currentAuth = null;
  279. } else {
  280. authInfo.textContent = '完成:\n' + JSON.stringify(j, null, 2);
  281. }
  282. } catch(e){
  283. authInfo.textContent = originalText + '\n\n❌ 失败:' + e.message + '\n\n请检查:\n1. 是否已在授权页面完成登录\n2. 主服务是否正常运行\n3. 网络连接是否正常';
  284. }
  285. }
  286. async function createSingle(){
  287. const label = document.getElementById('manual_label').value.trim();
  288. const clientId = document.getElementById('manual_clientId').value.trim();
  289. const clientSecret = document.getElementById('manual_clientSecret').value.trim();
  290. const refreshToken = document.getElementById('manual_refreshToken').value.trim();
  291. const accessToken = document.getElementById('manual_accessToken').value.trim();
  292. if (!clientId || !clientSecret || !refreshToken) {
  293. alert('⚠️ 请填写必填字段:clientId、clientSecret、refreshToken');
  294. return;
  295. }
  296. const result = document.getElementById('manual_result');
  297. result.textContent = '⏳ 正在创建账号...';
  298. try {
  299. const r = await fetch('/accounts/create', {
  300. method: 'POST',
  301. headers: { 'content-type': 'application/json' },
  302. body: JSON.stringify({
  303. label: label || null,
  304. clientId,
  305. clientSecret,
  306. refreshToken,
  307. accessToken: accessToken || null,
  308. enabled: true
  309. })
  310. });
  311. if (!r.ok) throw new Error(await r.text());
  312. const j = await r.json();
  313. result.textContent = '✅ 请求已提交!\n\n' +
  314. '主服务正在后台异步验证账号,请稍后在主服务后台查看最终状态。\n\n' +
  315. 'API 响应:\n' + JSON.stringify(j, null, 2);
  316. // 清空表单
  317. document.getElementById('manual_label').value = '';
  318. document.getElementById('manual_clientId').value = '';
  319. document.getElementById('manual_clientSecret').value = '';
  320. document.getElementById('manual_refreshToken').value = '';
  321. document.getElementById('manual_accessToken').value = '';
  322. } catch(e){
  323. result.textContent = '❌ 创建失败:' + e.message;
  324. }
  325. }
  326. async function createBatch(){
  327. const jsonText = document.getElementById('batch_json').value.trim();
  328. if (!jsonText) {
  329. alert('⚠️ 请输入 JSON 数组');
  330. return;
  331. }
  332. let accounts;
  333. try {
  334. accounts = JSON.parse(jsonText);
  335. if (!Array.isArray(accounts)) throw new Error('必须是数组格式');
  336. } catch(e){
  337. alert('❌ JSON 格式错误:' + e.message);
  338. return;
  339. }
  340. const result = document.getElementById('batch_result');
  341. result.textContent = `⏳ 开始批量创建 ${accounts.length} 个账号...\n`;
  342. try {
  343. const r = await fetch('/accounts/batch', {
  344. method: 'POST',
  345. headers: { 'content-type': 'application/json' },
  346. body: JSON.stringify({ accounts })
  347. });
  348. if (!r.ok) throw new Error(await r.text());
  349. const j = await r.json();
  350. result.textContent = '✅ 请求已提交!\n\n' +
  351. `主服务正在后台异步验证 ${accounts.length} 个账号,请稍后在主服务后台查看最终状态。\n\n` +
  352. 'API 响应:\n' + JSON.stringify(j, null, 2);
  353. } catch(e){
  354. result.textContent = '❌ 批量创建失败:' + e.message;
  355. }
  356. }
  357. </script>
  358. </body>
  359. </html>