import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import axios from 'axios'; import { log } from '../utils/logger.js'; import { generateSessionId, generateProjectId } from '../utils/idGenerator.js'; import config from '../config/config.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'; const CLIENT_SECRET = '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'; class TokenManager { constructor(filePath = path.join(__dirname,'..','..','data' ,'accounts.json')) { this.filePath = filePath; this.tokens = []; this.currentIndex = 0; this.ensureFileExists(); this.initialize(); } ensureFileExists() { const dir = path.dirname(this.filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } if (!fs.existsSync(this.filePath)) { fs.writeFileSync(this.filePath, '[]', 'utf8'); log.info('✓ 已创建账号配置文件'); } } async initialize() { try { log.info('正在初始化token管理器...'); const data = fs.readFileSync(this.filePath, 'utf8'); let tokenArray = JSON.parse(data); this.tokens = tokenArray.filter(token => token.enable !== false).map(token => ({ ...token, sessionId: generateSessionId() })); this.currentIndex = 0; if (this.tokens.length === 0) { log.warn('⚠ 暂无可用账号,请使用以下方式添加:'); log.warn(' 方式1: 运行 npm run login 命令登录'); log.warn(' 方式2: 访问前端管理页面添加账号'); } else { log.info(`成功加载 ${this.tokens.length} 个可用token`); } } catch (error) { log.error('初始化token失败:', error.message); this.tokens = []; } } async fetchProjectId(token) { 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 ${token.access_token}`, 'Content-Type': 'application/json', 'Accept-Encoding': 'gzip' }, data: JSON.stringify({ metadata: { ideType: 'ANTIGRAVITY' } }), timeout: config.timeout, proxy: config.proxy ? (() => { const proxyUrl = new URL(config.proxy); return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) }; })() : false }); return response.data?.cloudaicompanionProject; } isExpired(token) { if (!token.timestamp || !token.expires_in) return true; const expiresAt = token.timestamp + (token.expires_in * 1000); return Date.now() >= expiresAt - 300000; } async refreshToken(token) { log.info('正在刷新token...'); const body = new URLSearchParams({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, grant_type: 'refresh_token', refresh_token: token.refresh_token }); try { const response = await axios({ method: 'POST', url: 'https://oauth2.googleapis.com/token', headers: { 'Host': 'oauth2.googleapis.com', 'User-Agent': 'Go-http-client/1.1', 'Content-Type': 'application/x-www-form-urlencoded', 'Accept-Encoding': 'gzip' }, data: body.toString(), timeout: config.timeout, proxy: config.proxy ? (() => { const proxyUrl = new URL(config.proxy); return { protocol: proxyUrl.protocol.replace(':', ''), host: proxyUrl.hostname, port: parseInt(proxyUrl.port) }; })() : false }); token.access_token = response.data.access_token; token.expires_in = response.data.expires_in; token.timestamp = Date.now(); this.saveToFile(); return token; } catch (error) { throw { statusCode: error.response?.status, message: error.response?.data || error.message }; } } saveToFile() { try { const data = fs.readFileSync(this.filePath, 'utf8'); const allTokens = JSON.parse(data); this.tokens.forEach(memToken => { const index = allTokens.findIndex(t => t.refresh_token === memToken.refresh_token); if (index !== -1) { const { sessionId, ...tokenToSave } = memToken; allTokens[index] = tokenToSave; } }); fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8'); } catch (error) { log.error('保存文件失败:', error.message); } } disableToken(token) { log.warn(`禁用token ...${token.access_token.slice(-8)}`) token.enable = false; this.saveToFile(); this.tokens = this.tokens.filter(t => t.refresh_token !== token.refresh_token); this.currentIndex = this.currentIndex % Math.max(this.tokens.length, 1); } async getToken() { if (this.tokens.length === 0) return null; //const startIndex = this.currentIndex; const totalTokens = this.tokens.length; for (let i = 0; i < totalTokens; i++) { const token = this.tokens[this.currentIndex]; try { if (this.isExpired(token)) { await this.refreshToken(token); } if (!token.projectId) { if (config.skipProjectIdFetch) { token.projectId = generateProjectId(); this.saveToFile(); log.info(`...${token.access_token.slice(-8)}: 使用随机生成的projectId: ${token.projectId}`); } else { try { const projectId = await this.fetchProjectId(token); if (projectId === undefined) { log.warn(`...${token.access_token.slice(-8)}: 无资格获取projectId,跳过保存`); this.disableToken(token); if (this.tokens.length === 0) return null; continue; } token.projectId = projectId; this.saveToFile(); } catch (error) { log.error(`...${token.access_token.slice(-8)}: 获取projectId失败:`, error.message); this.currentIndex = (this.currentIndex + 1) % this.tokens.length; continue; } } } this.currentIndex = (this.currentIndex + 1) % this.tokens.length; return token; } catch (error) { if (error.statusCode === 403 || error.statusCode === 400) { log.warn(`...${token.access_token.slice(-8)}: Token 已失效或错误,已自动禁用该账号`); this.disableToken(token); if (this.tokens.length === 0) return null; } else { log.error(`...${token.access_token.slice(-8)} 刷新失败:`, error.message); this.currentIndex = (this.currentIndex + 1) % this.tokens.length; } } } return null; } disableCurrentToken(token) { const found = this.tokens.find(t => t.access_token === token.access_token); if (found) { this.disableToken(found); } } // API管理方法 async reload() { await this.initialize(); log.info('Token已热重载'); } addToken(tokenData) { try { this.ensureFileExists(); const data = fs.readFileSync(this.filePath, 'utf8'); const allTokens = JSON.parse(data); const newToken = { access_token: tokenData.access_token, refresh_token: tokenData.refresh_token, expires_in: tokenData.expires_in || 3599, timestamp: tokenData.timestamp || Date.now(), enable: tokenData.enable !== undefined ? tokenData.enable : true }; if (tokenData.projectId) { newToken.projectId = tokenData.projectId; } allTokens.push(newToken); fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8'); this.reload(); return { success: true, message: 'Token添加成功' }; } catch (error) { log.error('添加Token失败:', error.message); return { success: false, message: error.message }; } } updateToken(refreshToken, updates) { try { this.ensureFileExists(); const data = fs.readFileSync(this.filePath, 'utf8'); const allTokens = JSON.parse(data); const index = allTokens.findIndex(t => t.refresh_token === refreshToken); if (index === -1) { return { success: false, message: 'Token不存在' }; } allTokens[index] = { ...allTokens[index], ...updates }; fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8'); this.reload(); return { success: true, message: 'Token更新成功' }; } catch (error) { log.error('更新Token失败:', error.message); return { success: false, message: error.message }; } } deleteToken(refreshToken) { try { this.ensureFileExists(); const data = fs.readFileSync(this.filePath, 'utf8'); const allTokens = JSON.parse(data); const filteredTokens = allTokens.filter(t => t.refresh_token !== refreshToken); if (filteredTokens.length === allTokens.length) { return { success: false, message: 'Token不存在' }; } fs.writeFileSync(this.filePath, JSON.stringify(filteredTokens, null, 2), 'utf8'); this.reload(); return { success: true, message: 'Token删除成功' }; } catch (error) { log.error('删除Token失败:', error.message); return { success: false, message: error.message }; } } getTokenList() { try { this.ensureFileExists(); const data = fs.readFileSync(this.filePath, 'utf8'); const allTokens = JSON.parse(data); return allTokens.map(token => ({ refresh_token: token.refresh_token, access_token_suffix: token.access_token ? `...${token.access_token.slice(-8)}` : 'N/A', expires_in: token.expires_in, timestamp: token.timestamp, enable: token.enable !== false, projectId: token.projectId || null })); } catch (error) { log.error('获取Token列表失败:', error.message); return []; } } } const tokenManager = new TokenManager(); export default tokenManager;