Spaces:
Sleeping
Sleeping
| import http from 'http'; | |
| import { URL } from 'url'; | |
| import crypto from 'crypto'; | |
| import fs from 'fs'; | |
| import path from 'path'; | |
| import { fileURLToPath } from 'url'; | |
| import log from '../src/utils/logger.js'; | |
| import axios from 'axios'; | |
| import config from '../src/config/config.js'; | |
| import { generateProjectId } from '../src/utils/idGenerator.js'; | |
| const __filename = fileURLToPath(import.meta.url); | |
| const __dirname = path.dirname(__filename); | |
| const ACCOUNTS_FILE = path.join(__dirname, '..', 'data', 'accounts.json'); | |
| const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'; | |
| const CLIENT_SECRET = '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'; | |
| const STATE = crypto.randomUUID(); | |
| const SCOPES = [ | |
| 'https://www.googleapis.com/auth/cloud-platform', | |
| 'https://www.googleapis.com/auth/userinfo.email', | |
| 'https://www.googleapis.com/auth/userinfo.profile', | |
| 'https://www.googleapis.com/auth/cclog', | |
| 'https://www.googleapis.com/auth/experimentsandconfigs' | |
| ]; | |
| function generateAuthUrl(port) { | |
| const params = new URLSearchParams({ | |
| access_type: 'offline', | |
| client_id: CLIENT_ID, | |
| prompt: 'consent', | |
| redirect_uri: `http://localhost:${port}/oauth-callback`, | |
| response_type: 'code', | |
| scope: SCOPES.join(' '), | |
| state: STATE | |
| }); | |
| return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`; | |
| } | |
| function getAxiosConfig() { | |
| const axiosConfig = { timeout: config.timeout }; | |
| if (config.proxy) { | |
| const proxyUrl = new URL(config.proxy); | |
| axiosConfig.proxy = { | |
| protocol: proxyUrl.protocol.replace(':', ''), | |
| host: proxyUrl.hostname, | |
| port: parseInt(proxyUrl.port) | |
| }; | |
| } | |
| return axiosConfig; | |
| } | |
| async function exchangeCodeForToken(code, port) { | |
| const postData = new URLSearchParams({ | |
| code, | |
| client_id: CLIENT_ID, | |
| client_secret: CLIENT_SECRET, | |
| redirect_uri: `http://localhost:${port}/oauth-callback`, | |
| grant_type: 'authorization_code' | |
| }); | |
| const response = await axios({ | |
| method: 'POST', | |
| url: 'https://oauth2.googleapis.com/token', | |
| headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, | |
| data: postData.toString(), | |
| ...getAxiosConfig() | |
| }); | |
| return response.data; | |
| } | |
| async function fetchProjectId(accessToken) { | |
| const response = await axios({ | |
| method: 'POST', | |
| url: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:loadCodeAssist', | |
| headers: { | |
| 'Host': 'daily-cloudcode-pa.sandbox.googleapis.com', | |
| 'User-Agent': 'antigravity/1.11.9 windows/amd64', | |
| 'Authorization': `Bearer ${accessToken}`, | |
| 'Content-Type': 'application/json', | |
| 'Accept-Encoding': 'gzip' | |
| }, | |
| data: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }), | |
| ...getAxiosConfig() | |
| }); | |
| return response.data?.cloudaicompanionProject; | |
| } | |
| const server = http.createServer((req, res) => { | |
| const port = server.address().port; | |
| const url = new URL(req.url, `http://localhost:${port}`); | |
| if (url.pathname === '/oauth-callback') { | |
| const code = url.searchParams.get('code'); | |
| const error = url.searchParams.get('error'); | |
| if (code) { | |
| log.info('收到授权码,正在交换 Token...'); | |
| exchangeCodeForToken(code, port).then(async (tokenData) => { | |
| const account = { | |
| access_token: tokenData.access_token, | |
| refresh_token: tokenData.refresh_token, | |
| expires_in: tokenData.expires_in, | |
| timestamp: Date.now() | |
| }; | |
| if (config.skipProjectIdFetch) { | |
| account.projectId = generateProjectId(); | |
| account.enable = true; | |
| log.info('已跳过API验证,使用随机生成的projectId: ' + account.projectId); | |
| } else { | |
| log.info('正在验证账号资格...'); | |
| try { | |
| const projectId = await fetchProjectId(account.access_token); | |
| if (projectId === undefined) { | |
| log.warn('该账号无资格使用(无法获取projectId),已跳过保存'); | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end('<h1>账号无资格</h1><p>该账号无法获取projectId,未保存。</p>'); | |
| setTimeout(() => server.close(), 1000); | |
| return; | |
| } | |
| account.projectId = projectId; | |
| account.enable = true; | |
| log.info('账号验证通过'); | |
| } catch (err) { | |
| log.error('验证账号资格失败:', err.message); | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end('<h1>验证失败</h1><p>无法验证账号资格,请查看控制台。</p>'); | |
| setTimeout(() => server.close(), 1000); | |
| return; | |
| } | |
| } | |
| let accounts = []; | |
| try { | |
| if (fs.existsSync(ACCOUNTS_FILE)) { | |
| accounts = JSON.parse(fs.readFileSync(ACCOUNTS_FILE, 'utf-8')); | |
| } | |
| } catch (err) { | |
| log.warn('读取 accounts.json 失败,将创建新文件'); | |
| } | |
| accounts.push(account); | |
| const dir = path.dirname(ACCOUNTS_FILE); | |
| if (!fs.existsSync(dir)) { | |
| fs.mkdirSync(dir, { recursive: true }); | |
| } | |
| fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2)); | |
| log.info(`Token 已保存到 ${ACCOUNTS_FILE}`); | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end('<h1>授权成功!</h1><p>Token 已保存,可以关闭此页面。</p>'); | |
| setTimeout(() => server.close(), 1000); | |
| }).catch(err => { | |
| log.error('Token 交换失败:', err.message); | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end('<h1>Token 获取失败</h1><p>查看控制台错误信息</p>'); | |
| setTimeout(() => server.close(), 1000); | |
| }); | |
| } else { | |
| log.error('授权失败:', error || '未收到授权码'); | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end('<h1>授权失败</h1>'); | |
| setTimeout(() => server.close(), 1000); | |
| } | |
| } else { | |
| res.writeHead(404); | |
| res.end('Not Found'); | |
| } | |
| }); | |
| server.listen(0, () => { | |
| const port = server.address().port; | |
| const authUrl = generateAuthUrl(port); | |
| log.info(`服务器运行在 http://localhost:${port}`); | |
| log.info('请在浏览器中打开以下链接进行登录:'); | |
| console.log(`\n${authUrl}\n`); | |
| log.info('等待授权回调...'); | |
| }); | |