├── Antigravity ├── data │ ├── users.json │ ├── api_keys.json │ ├── app_logs.json │ ├── security.json │ └── ai_config.json ├── LICENSE ├── package.json ├── config.json ├── src │ ├── utils │ │ ├── logger.js │ │ ├── idle_manager.js │ │ └── utils.js │ ├── config │ │ └── config.js │ ├── admin │ │ ├── session.js │ │ ├── log_manager.js │ │ ├── monitor.js │ │ ├── announcement_manager.js │ │ ├── settings_manager.js │ │ ├── key_manager.js │ │ ├── security_manager.js │ │ ├── model_manager.js │ │ ├── token_admin.js │ │ ├── share_manager.js │ │ ├── ai_moderator.js │ │ └── user_manager.js │ ├── api │ │ └── client.js │ ├── auth │ │ └── token_manager.js │ └── server │ │ └── index.js ├── test │ └── test-transform.js ├── scripts │ └── oauth-server.js ├── README.md └── public │ └── share.html ├── Antigravity.zip └── README.md /Antigravity/data/users.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Antigravity/data/api_keys.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Antigravity/data/app_logs.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Antigravity/data/security.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Antigravity.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwl2005/Antigravity-/HEAD/Antigravity.zip -------------------------------------------------------------------------------- /Antigravity/data/ai_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "apiKey": "", 4 | "apiEndpoint": "", 5 | "model": "gemini-2.5-flash", 6 | "checkIntervalHours": 1, 7 | "autoModerateThreshold": 0.8, 8 | "systemPrompt": "你是一个共享资源滥用检测AI。分析用户行为数据,判断是否存在滥用行为。\n\n# 分析规则(严格执行)\n1. 使用频率:平均每天超过500次为异常\n2. 使用模式:短时间内大量请求(如1小时内超过300次)\n3. 时间分布:24小时持续高频使用,无正常休息时间\n4. 突增行为:使用量突然大幅增加(超过历史平均3倍)\n\n# 输出要求(必须严格遵守)\n只返回JSON格式,无其他文字。格式:\n{\"userId\":\"用户ID\",\"shouldBan\":true/false,\"confidence\":0-1,\"reason\":\"简短原因(15字内)\",\"evidence\":\"关键数据\"}\n\n# 令牌节省规则\n- 只输出JSON,无解释\n- reason必须15字内\n- evidence仅列关键数字\n- 不输出分析过程" 9 | } -------------------------------------------------------------------------------- /Antigravity/LICENSE: -------------------------------------------------------------------------------- 1 | ## 📜 许可证 2 | 本项目采用 CC BY-NC-SA 4.0 协议,仅供学习使用,禁止商用 3 | 4 | 知识共享 署名-非商业性使用-相同方式共享 4.0 国际许可协议 5 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 6 | 7 | 版权所有 © 2025 [liuw1535] 8 | 9 | 本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 10 | 11 | 您可以自由地: 12 | ✓ 共享 — 在任何媒介以任何形式复制、发行本作品 13 | ✓ 演绎 — 修改、转换或以本作品为基础进行创作 14 | 15 | 惟须遵守下列条件: 16 | ✓ 署名 — 您必须给出适当的署名,提供指向本许可协议的链接 17 | ✓ 非商业性使用 — 您不得将本作品用于商业目的 18 | ✓ 相同方式共享 — 如果您再混合、转换或者基于本作品进行创作, 19 | 您必须基于与原先许可协议相同的许可协议分发您贡献的作品 20 | 21 | 完整协议文本请访问: 22 | https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode.zh-Hans 23 | 24 | -------------------------------------------------------------------------------- /Antigravity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "antigravity-to-openai", 3 | "version": "1.0.0", 4 | "description": "Antigravity API 转 OpenAI 格式的代理服务", 5 | "type": "module", 6 | "main": "src/server/index.js", 7 | "scripts": { 8 | "start": "node src/server/index.js", 9 | "login": "node scripts/oauth-server.js", 10 | "dev": "node src/server/index.js" 11 | }, 12 | "keywords": [ 13 | "antigravity", 14 | "openai", 15 | "api", 16 | "proxy" 17 | ], 18 | "author": "", 19 | "license": "MIT", 20 | "dependencies": { 21 | "adm-zip": "^0.5.16", 22 | "archiver": "^7.0.1", 23 | "express": "^5.1.0", 24 | "multer": "^2.0.2" 25 | }, 26 | "engines": { 27 | "node": ">=18.0.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Antigravity/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "port": 8045, 4 | "host": "0.0.0.0" 5 | }, 6 | "api": { 7 | "url": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse", 8 | "modelsUrl": "https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels", 9 | "host": "daily-cloudcode-pa.sandbox.googleapis.com", 10 | "userAgent": "antigravity/1.11.3 windows/amd64" 11 | }, 12 | "defaults": { 13 | "temperature": 1, 14 | "top_p": 0.85, 15 | "top_k": 50, 16 | "max_tokens": 8096 17 | }, 18 | "security": { 19 | "maxRequestSize": "50mb", 20 | "apiKey": "sk-1234", 21 | "adminPassword": "admin123" 22 | }, 23 | "systemInstruction": "", 24 | "oauth": { 25 | "clientId": "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com", 26 | "clientSecret": "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" 27 | } 28 | } -------------------------------------------------------------------------------- /Antigravity/src/utils/logger.js: -------------------------------------------------------------------------------- 1 | const colors = { 2 | reset: '\x1b[0m', 3 | green: '\x1b[32m', 4 | yellow: '\x1b[33m', 5 | red: '\x1b[31m', 6 | cyan: '\x1b[36m', 7 | gray: '\x1b[90m' 8 | }; 9 | 10 | function logMessage(level, ...args) { 11 | const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false }); 12 | const color = { info: colors.green, warn: colors.yellow, error: colors.red }[level]; 13 | console.log(`${colors.gray}${timestamp}${colors.reset} ${color}[${level}]${colors.reset}`, ...args); 14 | } 15 | 16 | function logRequest(method, path, status, duration) { 17 | const statusColor = status >= 500 ? colors.red : status >= 400 ? colors.yellow : colors.green; 18 | console.log(`${colors.cyan}[${method}]${colors.reset} - ${path} ${statusColor}${status}${colors.reset} ${colors.gray}${duration}ms${colors.reset}`); 19 | } 20 | 21 | export const log = { 22 | info: (...args) => logMessage('info', ...args), 23 | warn: (...args) => logMessage('warn', ...args), 24 | error: (...args) => logMessage('error', ...args), 25 | request: logRequest 26 | }; 27 | 28 | export default log; 29 | -------------------------------------------------------------------------------- /Antigravity/src/config/config.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import log from '../utils/logger.js'; 3 | 4 | const defaultConfig = { 5 | server: { port: 8045, host: '127.0.0.1' }, 6 | api: { 7 | url: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse', 8 | modelsUrl: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels', 9 | host: 'daily-cloudcode-pa.sandbox.googleapis.com', 10 | userAgent: 'antigravity/1.11.3 windows/amd64' 11 | }, 12 | defaults: { temperature: 1, top_p: 0.85, top_k: 50, max_tokens: 8096 }, 13 | security: { maxRequestSize: '50mb', apiKey: null, adminPassword: 'admin123' }, 14 | systemInstruction: '你是聊天机器人,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演,也可以提供数学或者代码上的建议', 15 | oauth: { 16 | clientId: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com', 17 | clientSecret: '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' 18 | } 19 | }; 20 | 21 | let config; 22 | 23 | // 加载配置文件 24 | function loadConfig() { 25 | try { 26 | const newConfig = JSON.parse(fs.readFileSync('./config.json', 'utf8')); 27 | // 确保 oauth 配置存在 28 | if (!newConfig.oauth) { 29 | newConfig.oauth = defaultConfig.oauth; 30 | } 31 | return newConfig; 32 | } catch { 33 | return { ...defaultConfig }; 34 | } 35 | } 36 | 37 | // 初始加载 38 | config = loadConfig(); 39 | log.info('✓ 配置文件加载成功'); 40 | 41 | // 重新加载配置(热更新) 42 | export function reloadConfig() { 43 | const newConfig = loadConfig(); 44 | Object.assign(config, newConfig); 45 | log.info('✓ 配置文件已重新加载'); 46 | return config; 47 | } 48 | 49 | export default config; 50 | -------------------------------------------------------------------------------- /Antigravity/src/admin/session.js: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import config from '../config/config.js'; 3 | 4 | // 存储有效的会话 token 5 | const sessions = new Map(); 6 | 7 | // 会话过期时间(24小时) 8 | const SESSION_EXPIRY = 24 * 60 * 60 * 1000; 9 | 10 | // 生成会话 token 11 | export function createSession() { 12 | const token = crypto.randomBytes(32).toString('hex'); 13 | sessions.set(token, { 14 | created: Date.now(), 15 | lastAccess: Date.now() 16 | }); 17 | return token; 18 | } 19 | 20 | // 验证会话 21 | export function validateSession(token) { 22 | if (!token) return false; 23 | 24 | const session = sessions.get(token); 25 | if (!session) return false; 26 | 27 | // 检查是否过期 28 | if (Date.now() - session.created > SESSION_EXPIRY) { 29 | sessions.delete(token); 30 | return false; 31 | } 32 | 33 | // 更新最后访问时间 34 | session.lastAccess = Date.now(); 35 | return true; 36 | } 37 | 38 | // 删除会话 39 | export function destroySession(token) { 40 | sessions.delete(token); 41 | } 42 | 43 | // 验证密码 44 | export function verifyPassword(password) { 45 | const adminPassword = config.security?.adminPassword || 'admin123'; 46 | return password === adminPassword; 47 | } 48 | 49 | // 获取管理密码 50 | export function getAdminPassword() { 51 | return config.security?.adminPassword || 'admin123'; 52 | } 53 | 54 | // 清理过期会话 55 | function cleanupSessions() { 56 | const now = Date.now(); 57 | for (const [token, session] of sessions.entries()) { 58 | if (now - session.created > SESSION_EXPIRY) { 59 | sessions.delete(token); 60 | } 61 | } 62 | } 63 | 64 | // 每小时清理一次过期会话 65 | setInterval(cleanupSessions, 60 * 60 * 1000); 66 | 67 | // 管理员认证中间件 68 | export function adminAuth(req, res, next) { 69 | const token = req.headers['x-admin-token'] || req.headers['x-admin-session'] || req.query.token; 70 | 71 | if (validateSession(token)) { 72 | next(); 73 | } else { 74 | res.status(401).json({ error: '未授权,请先登录' }); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Antigravity/src/admin/log_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | 4 | const LOGS_FILE = path.join(process.cwd(), 'data', 'app_logs.json'); 5 | const MAX_LOGS = 200; // 最多保存 200 条日志(降低内存使用) 6 | 7 | // 内存缓存,避免频繁读取文件 8 | let logsCache = null; 9 | let lastCacheTime = 0; 10 | const CACHE_DURATION = 30000; // 缓存30秒 11 | 12 | // 确保数据目录存在 13 | async function ensureDataDir() { 14 | const dataDir = path.dirname(LOGS_FILE); 15 | try { 16 | await fs.access(dataDir); 17 | } catch { 18 | await fs.mkdir(dataDir, { recursive: true }); 19 | } 20 | } 21 | 22 | // 加载日志(带缓存) 23 | export async function loadLogs() { 24 | const now = Date.now(); 25 | 26 | // 如果缓存有效,直接返回缓存 27 | if (logsCache && (now - lastCacheTime) < CACHE_DURATION) { 28 | return logsCache; 29 | } 30 | 31 | await ensureDataDir(); 32 | try { 33 | const data = await fs.readFile(LOGS_FILE, 'utf-8'); 34 | logsCache = JSON.parse(data); 35 | lastCacheTime = now; 36 | return logsCache; 37 | } catch (error) { 38 | if (error.code === 'ENOENT') { 39 | logsCache = []; 40 | lastCacheTime = now; 41 | return []; 42 | } 43 | throw error; 44 | } 45 | } 46 | 47 | // 保存日志 48 | async function saveLogs(logs) { 49 | await ensureDataDir(); 50 | // 只保留最新的日志 51 | const recentLogs = logs.slice(-MAX_LOGS); 52 | await fs.writeFile(LOGS_FILE, JSON.stringify(recentLogs, null, 2), 'utf-8'); 53 | 54 | // 更新缓存 55 | logsCache = recentLogs; 56 | lastCacheTime = Date.now(); 57 | } 58 | 59 | // 添加日志 60 | export async function addLog(level, message) { 61 | const logs = await loadLogs(); 62 | logs.push({ 63 | timestamp: new Date().toISOString(), 64 | level, 65 | message 66 | }); 67 | await saveLogs(logs); 68 | } 69 | 70 | // 清空日志 71 | export async function clearLogs() { 72 | await saveLogs([]); 73 | } 74 | 75 | // 获取最近的日志 76 | export async function getRecentLogs(limit = 100) { 77 | const logs = await loadLogs(); 78 | return logs.slice(-limit).reverse(); 79 | } 80 | -------------------------------------------------------------------------------- /Antigravity/src/admin/monitor.js: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | import idleManager from '../utils/idle_manager.js'; 3 | 4 | const startTime = Date.now(); 5 | let requestCount = 0; 6 | 7 | // 增加请求计数 8 | export function incrementRequestCount() { 9 | requestCount++; 10 | } 11 | 12 | // 获取系统状态 13 | export function getSystemStatus() { 14 | const uptime = Date.now() - startTime; 15 | const uptimeSeconds = Math.floor(uptime / 1000); 16 | const hours = Math.floor(uptimeSeconds / 3600); 17 | const minutes = Math.floor((uptimeSeconds % 3600) / 60); 18 | const seconds = uptimeSeconds % 60; 19 | 20 | const memUsage = process.memoryUsage(); 21 | const totalMem = os.totalmem(); 22 | const freeMem = os.freemem(); 23 | const usedMem = totalMem - freeMem; 24 | 25 | // 获取空闲状态 26 | const idleStatus = idleManager.getStatus(); 27 | 28 | return { 29 | cpu: getCpuUsage(), 30 | memory: formatBytes(memUsage.heapUsed) + ' / ' + formatBytes(memUsage.heapTotal), 31 | uptime: `${hours}时${minutes}分${seconds}秒`, 32 | requests: requestCount, 33 | nodeVersion: process.version, 34 | platform: `${os.platform()} ${os.arch()}`, 35 | pid: process.pid, 36 | systemMemory: formatBytes(usedMem) + ' / ' + formatBytes(totalMem), 37 | idle: idleStatus.isIdle ? '空闲模式' : '活跃', 38 | idleTime: idleStatus.idleTimeSeconds 39 | }; 40 | } 41 | 42 | // 获取 CPU 使用率(简化版本) 43 | function getCpuUsage() { 44 | const cpus = os.cpus(); 45 | let totalIdle = 0; 46 | let totalTick = 0; 47 | 48 | cpus.forEach(cpu => { 49 | for (let type in cpu.times) { 50 | totalTick += cpu.times[type]; 51 | } 52 | totalIdle += cpu.times.idle; 53 | }); 54 | 55 | const idle = totalIdle / cpus.length; 56 | const total = totalTick / cpus.length; 57 | const usage = 100 - ~~(100 * idle / total); 58 | 59 | return usage; 60 | } 61 | 62 | // 格式化字节数 63 | function formatBytes(bytes) { 64 | if (bytes === 0) return '0 B'; 65 | const k = 1024; 66 | const sizes = ['B', 'KB', 'MB', 'GB']; 67 | const i = Math.floor(Math.log(bytes) / Math.log(k)); 68 | return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; 69 | } 70 | -------------------------------------------------------------------------------- /Antigravity/test/test-transform.js: -------------------------------------------------------------------------------- 1 | import { generateRequestBody } from './utils.js'; 2 | 3 | // 测试场景:user -> assistant -> assistant(工具调用,无content) -> tool1结果 -> tool2结果 4 | const testMessages = [ 5 | { 6 | role: "user", 7 | content: "帮我查询天气和新闻" 8 | }, 9 | { 10 | role: "assistant", 11 | content: "好的,我来帮你查询。" 12 | }, 13 | { 14 | role: "assistant", 15 | content: "", 16 | tool_calls: [ 17 | { 18 | id: "call_001", 19 | type: "function", 20 | function: { 21 | name: "get_weather", 22 | arguments: JSON.stringify({ city: "北京" }) 23 | } 24 | }, 25 | { 26 | id: "call_002", 27 | type: "function", 28 | function: { 29 | name: "get_news", 30 | arguments: JSON.stringify({ category: "科技" }) 31 | } 32 | } 33 | ] 34 | }, 35 | { 36 | role: "tool", 37 | tool_call_id: "call_001", 38 | content: "北京今天晴,温度25度" 39 | }, 40 | { 41 | role: "tool", 42 | tool_call_id: "call_002", 43 | content: "最新科技新闻:AI技术突破" 44 | } 45 | ]; 46 | 47 | const testTools = [ 48 | { 49 | type: "function", 50 | function: { 51 | name: "get_weather", 52 | description: "获取天气信息", 53 | parameters: { 54 | type: "object", 55 | properties: { 56 | city: { type: "string" } 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | type: "function", 63 | function: { 64 | name: "get_news", 65 | description: "获取新闻", 66 | parameters: { 67 | type: "object", 68 | properties: { 69 | category: { type: "string" } 70 | } 71 | } 72 | } 73 | } 74 | ]; 75 | 76 | console.log("=== 测试消息转换 ===\n"); 77 | console.log("输入 OpenAI 格式消息:"); 78 | console.log(JSON.stringify(testMessages, null, 2)); 79 | 80 | const result = generateRequestBody(testMessages, "claude-sonnet-4-5", {}, testTools); 81 | 82 | console.log("\n=== 转换后的 Antigravity 格式 ===\n"); 83 | console.log(JSON.stringify(result.request.contents, null, 2)); 84 | 85 | console.log("\n=== 验证结果 ==="); 86 | const contents = result.request.contents; 87 | console.log(`✓ 消息数量: ${contents.length}`); 88 | console.log(`✓ 第1条 (user): ${contents[0]?.role === 'user' ? '✓' : '✗'}`); 89 | console.log(`✓ 第2条 (model): ${contents[1]?.role === 'model' ? '✓' : '✗'}`); 90 | console.log(`✓ 第3条 (model+tools): ${contents[2]?.role === 'model' && contents[2]?.parts?.length === 2 ? '✓' : '✗'}`); 91 | console.log(`✓ 第4条 (tool1 response): ${contents[3]?.role === 'user' && contents[3]?.parts[0]?.functionResponse ? '✓' : '✗'}`); 92 | console.log(`✓ 第5条 (tool2 response): ${contents[4]?.role === 'user' && contents[4]?.parts[0]?.functionResponse ? '✓' : '✗'}`); 93 | -------------------------------------------------------------------------------- /Antigravity/src/utils/idle_manager.js: -------------------------------------------------------------------------------- 1 | import logger from './logger.js'; 2 | 3 | /** 4 | * 空闲模式管理器 5 | * 在没有请求时降低内存使用,减少后台活动 6 | */ 7 | class IdleManager { 8 | constructor() { 9 | this.lastRequestTime = Date.now(); 10 | this.idleTimeout = 30 * 1000; // 30秒无请求后进入空闲模式(极致优化) 11 | this.isIdle = false; 12 | this.gcInterval = null; 13 | this.checkInterval = null; 14 | 15 | // 启动空闲检查 16 | this.startIdleCheck(); 17 | 18 | // 10秒后立即检查是否应该进入空闲模式 19 | setTimeout(() => { 20 | const idleTime = Date.now() - this.lastRequestTime; 21 | if (idleTime > this.idleTimeout) { 22 | this.enterIdleMode(); 23 | } 24 | }, 10000); 25 | } 26 | 27 | /** 28 | * 记录请求活动 29 | */ 30 | recordActivity() { 31 | this.lastRequestTime = Date.now(); 32 | 33 | // 如果之前是空闲状态,现在恢复活跃 34 | if (this.isIdle) { 35 | this.exitIdleMode(); 36 | } 37 | } 38 | 39 | /** 40 | * 启动空闲检查 41 | */ 42 | startIdleCheck() { 43 | // 每15秒检查一次是否应该进入空闲模式 44 | this.checkInterval = setInterval(() => { 45 | const idleTime = Date.now() - this.lastRequestTime; 46 | 47 | if (!this.isIdle && idleTime > this.idleTimeout) { 48 | this.enterIdleMode(); 49 | } 50 | }, 15000); // 每15秒检查一次(更频繁) 51 | 52 | // 不阻止进程退出 53 | this.checkInterval.unref(); 54 | } 55 | 56 | /** 57 | * 进入空闲模式 58 | */ 59 | enterIdleMode() { 60 | if (this.isIdle) return; 61 | 62 | logger.info('⏸️ 进入空闲模式 - 降低资源使用'); 63 | this.isIdle = true; 64 | 65 | // 触发垃圾回收 66 | if (global.gc) { 67 | global.gc(); 68 | logger.info('🗑️ 已触发垃圾回收'); 69 | } else { 70 | // 如果没有启用 --expose-gc,尝试通过其他方式释放内存 71 | logger.warn('⚠️ 未启用 --expose-gc,建议使用 node --expose-gc 启动以获得更好的内存优化'); 72 | } 73 | 74 | // 在空闲模式下,每2分钟进行一次垃圾回收(更频繁) 75 | this.gcInterval = setInterval(() => { 76 | if (global.gc) { 77 | global.gc(); 78 | logger.info('🗑️ 空闲模式:定期垃圾回收'); 79 | } 80 | }, 2 * 60 * 1000); // 每2分钟一次 81 | 82 | // 不阻止进程退出 83 | this.gcInterval.unref(); 84 | } 85 | 86 | /** 87 | * 退出空闲模式 88 | */ 89 | exitIdleMode() { 90 | if (!this.isIdle) return; 91 | 92 | logger.info('▶️ 退出空闲模式 - 恢复正常运行'); 93 | this.isIdle = false; 94 | 95 | // 清除空闲模式的定时器 96 | if (this.gcInterval) { 97 | clearInterval(this.gcInterval); 98 | this.gcInterval = null; 99 | } 100 | 101 | // 触发一次垃圾回收,清理空闲期间的内存 102 | if (global.gc) { 103 | global.gc(); 104 | } 105 | } 106 | 107 | /** 108 | * 获取当前状态 109 | */ 110 | getStatus() { 111 | const idleTime = Date.now() - this.lastRequestTime; 112 | return { 113 | isIdle: this.isIdle, 114 | idleTimeSeconds: Math.floor(idleTime / 1000), 115 | lastRequestTime: new Date(this.lastRequestTime).toISOString() 116 | }; 117 | } 118 | 119 | /** 120 | * 清理资源 121 | */ 122 | destroy() { 123 | if (this.checkInterval) { 124 | clearInterval(this.checkInterval); 125 | } 126 | if (this.gcInterval) { 127 | clearInterval(this.gcInterval); 128 | } 129 | } 130 | } 131 | 132 | const idleManager = new IdleManager(); 133 | export default idleManager; 134 | -------------------------------------------------------------------------------- /Antigravity/src/admin/announcement_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import crypto from 'crypto'; 4 | import logger from '../utils/logger.js'; 5 | 6 | const ANNOUNCEMENTS_FILE = path.join(process.cwd(), 'data', 'announcements.json'); 7 | 8 | // 读取所有公告 9 | export async function loadAnnouncements() { 10 | try { 11 | const data = await fs.readFile(ANNOUNCEMENTS_FILE, 'utf-8'); 12 | return JSON.parse(data); 13 | } catch (error) { 14 | if (error.code === 'ENOENT') { 15 | return []; 16 | } 17 | throw error; 18 | } 19 | } 20 | 21 | // 保存公告 22 | async function saveAnnouncements(announcements) { 23 | const dir = path.dirname(ANNOUNCEMENTS_FILE); 24 | try { 25 | await fs.access(dir); 26 | } catch { 27 | await fs.mkdir(dir, { recursive: true }); 28 | } 29 | await fs.writeFile(ANNOUNCEMENTS_FILE, JSON.stringify(announcements, null, 2), 'utf-8'); 30 | } 31 | 32 | // 创建公告 33 | export async function createAnnouncement(data) { 34 | const announcements = await loadAnnouncements(); 35 | 36 | const newAnnouncement = { 37 | id: crypto.randomBytes(8).toString('hex'), 38 | title: data.title, 39 | content: data.content, 40 | type: data.type || 'info', // info, success, warning, danger 41 | images: data.images || [], 42 | pinned: data.pinned || false, 43 | created: Date.now(), 44 | updated: Date.now(), 45 | enabled: true 46 | }; 47 | 48 | announcements.unshift(newAnnouncement); 49 | await saveAnnouncements(announcements); 50 | 51 | logger.info(`创建新公告: ${data.title}`); 52 | 53 | return newAnnouncement; 54 | } 55 | 56 | // 更新公告 57 | export async function updateAnnouncement(id, data) { 58 | const announcements = await loadAnnouncements(); 59 | const announcement = announcements.find(a => a.id === id); 60 | 61 | if (!announcement) { 62 | throw new Error('公告不存在'); 63 | } 64 | 65 | if (data.title !== undefined) announcement.title = data.title; 66 | if (data.content !== undefined) announcement.content = data.content; 67 | if (data.type !== undefined) announcement.type = data.type; 68 | if (data.images !== undefined) announcement.images = data.images; 69 | if (data.pinned !== undefined) announcement.pinned = data.pinned; 70 | if (data.enabled !== undefined) announcement.enabled = data.enabled; 71 | 72 | announcement.updated = Date.now(); 73 | 74 | await saveAnnouncements(announcements); 75 | 76 | logger.info(`更新公告: ${announcement.title}`); 77 | 78 | return announcement; 79 | } 80 | 81 | // 删除公告 82 | export async function deleteAnnouncement(id) { 83 | const announcements = await loadAnnouncements(); 84 | const index = announcements.findIndex(a => a.id === id); 85 | 86 | if (index === -1) { 87 | throw new Error('公告不存在'); 88 | } 89 | 90 | const deleted = announcements.splice(index, 1)[0]; 91 | await saveAnnouncements(announcements); 92 | 93 | logger.info(`删除公告: ${deleted.title}`); 94 | 95 | return true; 96 | } 97 | 98 | // 获取所有启用的公告(用户端) 99 | export async function getActiveAnnouncements() { 100 | const announcements = await loadAnnouncements(); 101 | 102 | // 只返回启用的公告,置顶的排在前面 103 | return announcements 104 | .filter(a => a.enabled) 105 | .sort((a, b) => { 106 | if (a.pinned && !b.pinned) return -1; 107 | if (!a.pinned && b.pinned) return 1; 108 | return b.created - a.created; 109 | }); 110 | } 111 | 112 | // 获取单个公告 113 | export async function getAnnouncementById(id) { 114 | const announcements = await loadAnnouncements(); 115 | return announcements.find(a => a.id === id); 116 | } 117 | -------------------------------------------------------------------------------- /Antigravity/src/admin/settings_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import logger from '../utils/logger.js'; 4 | import { reloadConfig } from '../config/config.js'; 5 | 6 | const CONFIG_FILE = path.join(process.cwd(), 'config.json'); 7 | 8 | // 加载设置 9 | export async function loadSettings() { 10 | try { 11 | const data = await fs.readFile(CONFIG_FILE, 'utf-8'); 12 | return JSON.parse(data); 13 | } catch (error) { 14 | logger.error('读取配置文件失败:', error); 15 | // 返回默认配置 16 | return { 17 | server: { port: 8045, host: '0.0.0.0' }, 18 | api: { 19 | url: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:streamGenerateContent?alt=sse', 20 | modelsUrl: 'https://daily-cloudcode-pa.sandbox.googleapis.com/v1internal:fetchAvailableModels', 21 | host: 'daily-cloudcode-pa.sandbox.googleapis.com', 22 | userAgent: 'antigravity/1.11.3 windows/amd64' 23 | }, 24 | defaults: { temperature: 1, top_p: 0.85, top_k: 50, max_tokens: 8096 }, 25 | security: { maxRequestSize: '50mb', apiKey: 'sk-text', adminPassword: 'admin123' }, 26 | systemInstruction: '你是聊天机器人,专门为用户提供聊天和情绪价值,协助进行小说创作或者角色扮演,也可以提供数学或者代码上的建议' 27 | }; 28 | } 29 | } 30 | 31 | // 保存设置 32 | export async function saveSettings(newSettings) { 33 | try { 34 | // 读取现有配置 35 | let config; 36 | try { 37 | const data = await fs.readFile(CONFIG_FILE, 'utf-8'); 38 | config = JSON.parse(data); 39 | } catch { 40 | config = {}; 41 | } 42 | 43 | // 合并设置 44 | config.server = config.server || {}; 45 | config.security = config.security || {}; 46 | config.defaults = config.defaults || {}; 47 | 48 | // 更新服务器配置 49 | if (newSettings.server) { 50 | if (newSettings.server.port !== undefined) { 51 | config.server.port = parseInt(newSettings.server.port) || config.server.port; 52 | } 53 | if (newSettings.server.host !== undefined) { 54 | config.server.host = newSettings.server.host; 55 | } 56 | } 57 | 58 | // 更新安全配置 59 | if (newSettings.security) { 60 | // 使用 !== undefined 判断,允许保存空字符串 61 | if (newSettings.security.apiKey !== undefined) { 62 | config.security.apiKey = newSettings.security.apiKey; 63 | } 64 | if (newSettings.security.adminPassword !== undefined) { 65 | config.security.adminPassword = newSettings.security.adminPassword; 66 | } 67 | if (newSettings.security.maxRequestSize !== undefined) { 68 | config.security.maxRequestSize = newSettings.security.maxRequestSize; 69 | } 70 | } 71 | 72 | // 更新默认参数 73 | if (newSettings.defaults) { 74 | config.defaults.temperature = parseFloat(newSettings.defaults.temperature) ?? config.defaults.temperature; 75 | config.defaults.top_p = parseFloat(newSettings.defaults.top_p) ?? config.defaults.top_p; 76 | config.defaults.top_k = parseInt(newSettings.defaults.top_k) ?? config.defaults.top_k; 77 | config.defaults.max_tokens = parseInt(newSettings.defaults.max_tokens) ?? config.defaults.max_tokens; 78 | } 79 | 80 | // 更新系统指令 81 | if (newSettings.systemInstruction !== undefined) { 82 | config.systemInstruction = newSettings.systemInstruction; 83 | } 84 | 85 | // 更新 OAuth 配置 86 | if (newSettings.oauth) { 87 | config.oauth = config.oauth || {}; 88 | if (newSettings.oauth.clientId !== undefined) { 89 | config.oauth.clientId = newSettings.oauth.clientId; 90 | } 91 | if (newSettings.oauth.clientSecret !== undefined) { 92 | config.oauth.clientSecret = newSettings.oauth.clientSecret; 93 | } 94 | } 95 | 96 | // 写入文件 97 | await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8'); 98 | logger.info('配置文件已保存'); 99 | 100 | // 热更新内存中的配置 101 | reloadConfig(); 102 | 103 | return { success: true, message: '设置已保存并生效' }; 104 | } catch (error) { 105 | logger.error('保存配置文件失败:', error); 106 | throw new Error('保存配置失败: ' + error.message); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Antigravity/src/admin/key_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import crypto from 'crypto'; 4 | import logger from '../utils/logger.js'; 5 | 6 | const KEYS_FILE = path.join(process.cwd(), 'data', 'api_keys.json'); 7 | 8 | // 确保数据目录存在 9 | async function ensureDataDir() { 10 | const dataDir = path.dirname(KEYS_FILE); 11 | try { 12 | await fs.access(dataDir); 13 | } catch { 14 | await fs.mkdir(dataDir, { recursive: true }); 15 | } 16 | } 17 | 18 | // 生成随机 API 密钥 19 | function generateApiKey() { 20 | return 'sk-' + crypto.randomBytes(32).toString('hex'); 21 | } 22 | 23 | // 加载所有密钥 24 | export async function loadKeys() { 25 | await ensureDataDir(); 26 | try { 27 | const data = await fs.readFile(KEYS_FILE, 'utf-8'); 28 | return JSON.parse(data); 29 | } catch (error) { 30 | if (error.code === 'ENOENT') { 31 | return []; 32 | } 33 | throw error; 34 | } 35 | } 36 | 37 | // 保存密钥 38 | async function saveKeys(keys) { 39 | await ensureDataDir(); 40 | await fs.writeFile(KEYS_FILE, JSON.stringify(keys, null, 2), 'utf-8'); 41 | } 42 | 43 | // 创建新密钥 44 | export async function createKey(name = '未命名', rateLimit = null) { 45 | const keys = await loadKeys(); 46 | const newKey = { 47 | key: generateApiKey(), 48 | name, 49 | created: new Date().toISOString(), 50 | lastUsed: null, 51 | requests: 0, 52 | rateLimit: rateLimit || { enabled: false, maxRequests: 100, windowMs: 60000 }, // 默认 100 次/分钟 53 | usage: {} // 用于存储使用记录 { timestamp: count } 54 | }; 55 | keys.push(newKey); 56 | await saveKeys(keys); 57 | logger.info(`新密钥已创建: ${name}`); 58 | return newKey; 59 | } 60 | 61 | // 删除密钥 62 | export async function deleteKey(keyToDelete) { 63 | const keys = await loadKeys(); 64 | const filtered = keys.filter(k => k.key !== keyToDelete); 65 | if (filtered.length === keys.length) { 66 | throw new Error('密钥不存在'); 67 | } 68 | await saveKeys(filtered); 69 | logger.info(`密钥已删除: ${keyToDelete.substring(0, 10)}...`); 70 | return true; 71 | } 72 | 73 | // 验证密钥 74 | export async function validateKey(keyToCheck) { 75 | const keys = await loadKeys(); 76 | const key = keys.find(k => k.key === keyToCheck); 77 | if (key) { 78 | // 更新使用信息 79 | key.lastUsed = new Date().toISOString(); 80 | key.requests = (key.requests || 0) + 1; 81 | await saveKeys(keys); 82 | return true; 83 | } 84 | return false; 85 | } 86 | 87 | // 获取密钥统计 88 | export async function getKeyStats() { 89 | const keys = await loadKeys(); 90 | return { 91 | total: keys.length, 92 | active: keys.filter(k => k.lastUsed).length, 93 | totalRequests: keys.reduce((sum, k) => sum + (k.requests || 0), 0) 94 | }; 95 | } 96 | 97 | // 更新密钥频率限制 98 | export async function updateKeyRateLimit(keyToUpdate, rateLimit) { 99 | const keys = await loadKeys(); 100 | const key = keys.find(k => k.key === keyToUpdate); 101 | if (!key) { 102 | throw new Error('密钥不存在'); 103 | } 104 | key.rateLimit = rateLimit; 105 | await saveKeys(keys); 106 | logger.info(`密钥频率限制已更新: ${keyToUpdate.substring(0, 10)}...`); 107 | return key; 108 | } 109 | 110 | // 检查频率限制 111 | export async function checkRateLimit(keyToCheck) { 112 | const keys = await loadKeys(); 113 | const key = keys.find(k => k.key === keyToCheck); 114 | 115 | if (!key) { 116 | return { allowed: false, error: '密钥不存在' }; 117 | } 118 | 119 | // 如果未启用频率限制,直接允许 120 | if (!key.rateLimit || !key.rateLimit.enabled) { 121 | return { allowed: true }; 122 | } 123 | 124 | const now = Date.now(); 125 | const windowMs = key.rateLimit.windowMs || 60000; 126 | const maxRequests = key.rateLimit.maxRequests || 100; 127 | 128 | // 清理过期的使用记录 129 | key.usage = key.usage || {}; 130 | const cutoffTime = now - windowMs; 131 | 132 | // 计算当前时间窗口内的请求数 133 | let requestCount = 0; 134 | for (const [timestamp, count] of Object.entries(key.usage)) { 135 | if (parseInt(timestamp) >= cutoffTime) { 136 | requestCount += count; 137 | } else { 138 | delete key.usage[timestamp]; // 清理过期记录 139 | } 140 | } 141 | 142 | // 检查是否超过限制 143 | if (requestCount >= maxRequests) { 144 | const resetTime = Math.min(...Object.keys(key.usage).map(t => parseInt(t))) + windowMs; 145 | const waitSeconds = Math.ceil((resetTime - now) / 1000); 146 | return { 147 | allowed: false, 148 | error: '请求频率超限', 149 | resetIn: waitSeconds, 150 | limit: maxRequests, 151 | remaining: 0 152 | }; 153 | } 154 | 155 | // 记录本次请求 156 | const minute = Math.floor(now / 10000) * 10000; // 按10秒分组 157 | key.usage[minute] = (key.usage[minute] || 0) + 1; 158 | 159 | await saveKeys(keys); 160 | 161 | return { 162 | allowed: true, 163 | limit: maxRequests, 164 | remaining: maxRequests - requestCount - 1 165 | }; 166 | } 167 | -------------------------------------------------------------------------------- /Antigravity/src/api/client.js: -------------------------------------------------------------------------------- 1 | import tokenManager from '../auth/token_manager.js'; 2 | import config from '../config/config.js'; 3 | import { getUserOrSharedToken } from '../admin/user_manager.js'; 4 | 5 | export async function generateAssistantResponse(requestBody, tokenSource, callback) { 6 | let token; 7 | 8 | if (tokenSource && tokenSource.type === 'user') { 9 | // 用户 API Key - 使用用户自己的 Token 或共享 Token 10 | token = await getUserOrSharedToken(tokenSource.userId); 11 | if (!token) { 12 | throw new Error('没有可用的 Token。请在用户中心添加 Google Token 或使用共享 Token'); 13 | } 14 | } else { 15 | // 管理员密钥 - 使用管理员 Token 池 16 | token = await tokenManager.getToken(); 17 | if (!token) { 18 | throw new Error('没有可用的token,请运行 npm run login 获取token'); 19 | } 20 | } 21 | 22 | const url = config.api.url; 23 | 24 | const response = await fetch(url, { 25 | method: 'POST', 26 | headers: { 27 | 'Host': config.api.host, 28 | 'User-Agent': config.api.userAgent, 29 | 'Authorization': `Bearer ${token.access_token}`, 30 | 'Content-Type': 'application/json', 31 | 'Accept-Encoding': 'gzip' 32 | }, 33 | body: JSON.stringify(requestBody) 34 | }); 35 | 36 | if (!response.ok) { 37 | const errorText = await response.text(); 38 | if (response.status === 403) { 39 | tokenManager.disableCurrentToken(token); 40 | throw new Error(`该账号没有使用权限,已自动禁用。错误详情: ${errorText}`); 41 | } 42 | throw new Error(`API请求失败 (${response.status}): ${errorText}`); 43 | } 44 | 45 | const reader = response.body.getReader(); 46 | const decoder = new TextDecoder(); 47 | let thinkingStarted = false; 48 | let toolCalls = []; 49 | 50 | while (true) { 51 | const { done, value } = await reader.read(); 52 | if (done) break; 53 | 54 | const chunk = decoder.decode(value); 55 | const lines = chunk.split('\n').filter(line => line.startsWith('data: ')); 56 | 57 | for (const line of lines) { 58 | const jsonStr = line.slice(6); 59 | try { 60 | const data = JSON.parse(jsonStr); 61 | const parts = data.response?.candidates?.[0]?.content?.parts; 62 | if (parts) { 63 | for (const part of parts) { 64 | if (part.thought === true) { 65 | if (!thinkingStarted) { 66 | callback({ type: 'thinking', content: '\n' }); 67 | thinkingStarted = true; 68 | } 69 | callback({ type: 'thinking', content: part.text || '' }); 70 | } else if (part.text !== undefined) { 71 | if (thinkingStarted) { 72 | callback({ type: 'thinking', content: '\n\n' }); 73 | thinkingStarted = false; 74 | } 75 | callback({ type: 'text', content: part.text }); 76 | } else if (part.functionCall) { 77 | toolCalls.push({ 78 | id: part.functionCall.id, 79 | type: 'function', 80 | function: { 81 | name: part.functionCall.name, 82 | arguments: JSON.stringify(part.functionCall.args) 83 | } 84 | }); 85 | } 86 | } 87 | } 88 | 89 | // 当遇到 finishReason 时,发送所有收集的工具调用 90 | if (data.response?.candidates?.[0]?.finishReason && toolCalls.length > 0) { 91 | if (thinkingStarted) { 92 | callback({ type: 'thinking', content: '\n\n' }); 93 | thinkingStarted = false; 94 | } 95 | callback({ type: 'tool_calls', tool_calls: toolCalls }); 96 | toolCalls = []; 97 | } 98 | } catch (e) { 99 | // 忽略解析错误 100 | } 101 | } 102 | } 103 | } 104 | 105 | export async function getAvailableModels(tokenSource) { 106 | let token; 107 | 108 | if (tokenSource && tokenSource.type === 'user') { 109 | // 用户 API Key - 使用用户自己的 Token 或共享 Token 110 | token = await getUserOrSharedToken(tokenSource.userId); 111 | if (!token) { 112 | throw new Error('没有可用的 Token。请在用户中心添加 Google Token 或使用共享 Token'); 113 | } 114 | } else { 115 | // 管理员密钥 - 使用管理员 Token 池 116 | token = await tokenManager.getToken(); 117 | if (!token) { 118 | throw new Error('没有可用的token,请运行 npm run login 获取token'); 119 | } 120 | } 121 | 122 | const response = await fetch(config.api.modelsUrl, { 123 | method: 'POST', 124 | headers: { 125 | 'Host': config.api.host, 126 | 'User-Agent': config.api.userAgent, 127 | 'Authorization': `Bearer ${token.access_token}`, 128 | 'Content-Type': 'application/json', 129 | 'Accept-Encoding': 'gzip' 130 | }, 131 | body: JSON.stringify({}) 132 | }); 133 | 134 | const data = await response.json(); 135 | 136 | return { 137 | object: 'list', 138 | data: Object.keys(data.models).map(id => ({ 139 | id, 140 | object: 'model', 141 | created: Math.floor(Date.now() / 1000), 142 | owned_by: 'google' 143 | })) 144 | }; 145 | } 146 | -------------------------------------------------------------------------------- /Antigravity/scripts/oauth-server.js: -------------------------------------------------------------------------------- 1 | import http from 'http'; 2 | import https from 'https'; 3 | import { URL } from 'url'; 4 | import crypto from 'crypto'; 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import { fileURLToPath } from 'url'; 8 | import log from '../src/utils/logger.js'; 9 | 10 | const __filename = fileURLToPath(import.meta.url); 11 | const __dirname = path.dirname(__filename); 12 | const ACCOUNTS_FILE = path.join(__dirname, '..', 'data', 'accounts.json'); 13 | const CONFIG_FILE = path.join(__dirname, '..', 'config.json'); 14 | 15 | // 从配置文件读取 OAuth 配置 16 | function loadOAuthConfig() { 17 | try { 18 | const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')); 19 | return { 20 | clientId: config.oauth?.clientId || '', 21 | clientSecret: config.oauth?.clientSecret || '' 22 | }; 23 | } catch { 24 | return { clientId: '', clientSecret: '' }; 25 | } 26 | } 27 | 28 | const oauthConfig = loadOAuthConfig(); 29 | const CLIENT_ID = oauthConfig.clientId; 30 | const CLIENT_SECRET = oauthConfig.clientSecret; 31 | const STATE = crypto.randomUUID(); 32 | 33 | // 检查 OAuth 配置 34 | if (!CLIENT_ID || !CLIENT_SECRET) { 35 | log.error('错误:未配置 OAuth Client ID 或 Client Secret'); 36 | log.error('请在管理后台的"系统设置"中配置 Google OAuth 信息'); 37 | process.exit(1); 38 | } 39 | 40 | const SCOPES = [ 41 | 'https://www.googleapis.com/auth/cloud-platform', 42 | 'https://www.googleapis.com/auth/userinfo.email', 43 | 'https://www.googleapis.com/auth/userinfo.profile', 44 | 'https://www.googleapis.com/auth/cclog', 45 | 'https://www.googleapis.com/auth/experimentsandconfigs' 46 | ]; 47 | 48 | function generateAuthUrl(port) { 49 | const params = new URLSearchParams({ 50 | access_type: 'offline', 51 | client_id: CLIENT_ID, 52 | prompt: 'consent', 53 | redirect_uri: `http://localhost:${port}/oauth-callback`, 54 | response_type: 'code', 55 | scope: SCOPES.join(' '), 56 | state: STATE 57 | }); 58 | return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`; 59 | } 60 | 61 | function exchangeCodeForToken(code, port) { 62 | return new Promise((resolve, reject) => { 63 | const postData = new URLSearchParams({ 64 | code: code, 65 | client_id: CLIENT_ID, 66 | redirect_uri: `http://localhost:${port}/oauth-callback`, 67 | grant_type: 'authorization_code' 68 | }); 69 | 70 | if (CLIENT_SECRET) { 71 | postData.append('client_secret', CLIENT_SECRET); 72 | } 73 | 74 | const data = postData.toString(); 75 | 76 | const options = { 77 | hostname: 'oauth2.googleapis.com', 78 | path: '/token', 79 | method: 'POST', 80 | headers: { 81 | 'Content-Type': 'application/x-www-form-urlencoded', 82 | 'Content-Length': Buffer.byteLength(data) 83 | } 84 | }; 85 | 86 | const req = https.request(options, (res) => { 87 | let body = ''; 88 | res.on('data', chunk => body += chunk); 89 | res.on('end', () => { 90 | if (res.statusCode === 200) { 91 | resolve(JSON.parse(body)); 92 | } else { 93 | reject(new Error(`HTTP ${res.statusCode}: ${body}`)); 94 | } 95 | }); 96 | }); 97 | 98 | req.on('error', reject); 99 | req.write(data); 100 | req.end(); 101 | }); 102 | } 103 | 104 | const server = http.createServer((req, res) => { 105 | const port = server.address().port; 106 | const url = new URL(req.url, `http://localhost:${port}`); 107 | 108 | if (url.pathname === '/oauth-callback') { 109 | const code = url.searchParams.get('code'); 110 | const error = url.searchParams.get('error'); 111 | 112 | if (code) { 113 | log.info('收到授权码,正在交换 Token...'); 114 | exchangeCodeForToken(code, port).then(tokenData => { 115 | const account = { 116 | access_token: tokenData.access_token, 117 | refresh_token: tokenData.refresh_token, 118 | expires_in: tokenData.expires_in, 119 | timestamp: Date.now() 120 | }; 121 | 122 | let accounts = []; 123 | try { 124 | if (fs.existsSync(ACCOUNTS_FILE)) { 125 | accounts = JSON.parse(fs.readFileSync(ACCOUNTS_FILE, 'utf-8')); 126 | } 127 | } catch (err) { 128 | log.warn('读取 accounts.json 失败,将创建新文件'); 129 | } 130 | 131 | accounts.push(account); 132 | 133 | const dir = path.dirname(ACCOUNTS_FILE); 134 | if (!fs.existsSync(dir)) { 135 | fs.mkdirSync(dir, { recursive: true }); 136 | } 137 | 138 | fs.writeFileSync(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2)); 139 | 140 | log.info(`Token 已保存到 ${ACCOUNTS_FILE}`); 141 | //log.info(`过期时间: ${account.expires_in}秒`); 142 | 143 | res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); 144 | res.end('

授权成功!

Token 已保存,可以关闭此页面。

'); 145 | 146 | setTimeout(() => server.close(), 1000); 147 | }).catch(err => { 148 | log.error('Token 交换失败:', err.message); 149 | 150 | res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); 151 | res.end('

Token 获取失败

查看控制台错误信息

'); 152 | 153 | setTimeout(() => server.close(), 1000); 154 | }); 155 | } else { 156 | log.error('授权失败:', error || '未收到授权码'); 157 | res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); 158 | res.end('

授权失败

'); 159 | setTimeout(() => server.close(), 1000); 160 | } 161 | } else { 162 | res.writeHead(404); 163 | res.end('Not Found'); 164 | } 165 | }); 166 | 167 | // 使用固定端口,需要在 Google Cloud Console 配置对应的重定向 URI 168 | const OAUTH_PORT = 8099; 169 | 170 | server.listen(OAUTH_PORT, () => { 171 | const port = server.address().port; 172 | const authUrl = generateAuthUrl(port); 173 | log.info(`服务器运行在 http://localhost:${port}`); 174 | log.info('请在浏览器中打开以下链接进行登录:'); 175 | console.log(`\n${authUrl}\n`); 176 | log.info('等待授权回调...'); 177 | log.info('注意:需要在 Google Cloud Console 中添加重定向 URI: http://localhost:8099/oauth-callback'); 178 | }); 179 | -------------------------------------------------------------------------------- /Antigravity/src/admin/security_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import crypto from 'crypto'; 4 | import logger from '../utils/logger.js'; 5 | 6 | const SECURITY_FILE = path.join(process.cwd(), 'data', 'security.json'); 7 | 8 | // 加载安全数据 9 | async function loadSecurityData() { 10 | try { 11 | const data = await fs.readFile(SECURITY_FILE, 'utf-8'); 12 | return JSON.parse(data); 13 | } catch (error) { 14 | if (error.code === 'ENOENT') { 15 | return { 16 | ipRegistrations: {}, // IP -> [{timestamp, userId}] 17 | deviceRegistrations: {}, // deviceId -> [{timestamp, userId}] 18 | bannedIPs: {}, // IP -> {reason, bannedAt} 19 | bannedDevices: {}, // deviceId -> {reason, bannedAt} 20 | suspiciousAttempts: {} // IP -> count 21 | }; 22 | } 23 | throw error; 24 | } 25 | } 26 | 27 | // 保存安全数据 28 | async function saveSecurityData(data) { 29 | const dir = path.dirname(SECURITY_FILE); 30 | try { 31 | await fs.access(dir); 32 | } catch { 33 | await fs.mkdir(dir, { recursive: true }); 34 | } 35 | await fs.writeFile(SECURITY_FILE, JSON.stringify(data, null, 2), 'utf-8'); 36 | } 37 | 38 | // 生成设备指纹 39 | export function generateDeviceFingerprint(userAgent, acceptLanguage, screenResolution, timezone, platform) { 40 | const data = `${userAgent}|${acceptLanguage}|${screenResolution}|${timezone}|${platform}`; 41 | return crypto.createHash('sha256').update(data).digest('hex'); 42 | } 43 | 44 | // 检查IP是否被封禁 45 | export async function isIPBanned(ip) { 46 | const security = await loadSecurityData(); 47 | return !!security.bannedIPs[ip]; 48 | } 49 | 50 | // 检查设备是否被封禁 51 | export async function isDeviceBanned(deviceId) { 52 | const security = await loadSecurityData(); 53 | return !!security.bannedDevices[deviceId]; 54 | } 55 | 56 | // 检查IP注册限制 57 | export async function checkIPRegistrationLimit(ip) { 58 | const security = await loadSecurityData(); 59 | 60 | // 检查是否被封禁 61 | if (security.bannedIPs[ip]) { 62 | throw new Error(`该 IP 已被封禁:${security.bannedIPs[ip].reason}`); 63 | } 64 | 65 | const now = Date.now(); 66 | const dayAgo = now - 24 * 60 * 60 * 1000; 67 | 68 | // 获取24小时内的注册记录 69 | if (!security.ipRegistrations[ip]) { 70 | security.ipRegistrations[ip] = []; 71 | } 72 | 73 | // 清理过期记录 74 | security.ipRegistrations[ip] = security.ipRegistrations[ip].filter( 75 | record => record.timestamp > dayAgo 76 | ); 77 | 78 | // 检查注册数量 79 | if (security.ipRegistrations[ip].length >= 5) { 80 | // 记录可疑尝试 81 | if (!security.suspiciousAttempts[ip]) { 82 | security.suspiciousAttempts[ip] = 0; 83 | } 84 | security.suspiciousAttempts[ip]++; 85 | 86 | // 如果尝试次数超过3次,封禁IP 87 | if (security.suspiciousAttempts[ip] >= 3) { 88 | security.bannedIPs[ip] = { 89 | reason: '短时间内注册次数过多(超过限制3次以上)', 90 | bannedAt: now 91 | }; 92 | await saveSecurityData(security); 93 | logger.warn(`IP ${ip} 已被封禁:注册尝试次数过多`); 94 | throw new Error('该 IP 已被封禁:注册次数过多'); 95 | } 96 | 97 | await saveSecurityData(security); 98 | throw new Error('24小时内该 IP 已注册5个账号,请稍后再试'); 99 | } 100 | 101 | return true; 102 | } 103 | 104 | // 检查设备注册限制 105 | export async function checkDeviceRegistrationLimit(deviceId) { 106 | const security = await loadSecurityData(); 107 | 108 | // 检查是否被封禁 109 | if (security.bannedDevices[deviceId]) { 110 | throw new Error(`该设备已被封禁:${security.bannedDevices[deviceId].reason}`); 111 | } 112 | 113 | const now = Date.now(); 114 | const dayAgo = now - 24 * 60 * 60 * 1000; 115 | 116 | // 获取24小时内的注册记录 117 | if (!security.deviceRegistrations[deviceId]) { 118 | security.deviceRegistrations[deviceId] = []; 119 | } 120 | 121 | // 清理过期记录 122 | security.deviceRegistrations[deviceId] = security.deviceRegistrations[deviceId].filter( 123 | record => record.timestamp > dayAgo 124 | ); 125 | 126 | // 检查注册数量 127 | if (security.deviceRegistrations[deviceId].length >= 5) { 128 | // 封禁设备 129 | security.bannedDevices[deviceId] = { 130 | reason: '短时间内同一设备注册次数过多', 131 | bannedAt: now 132 | }; 133 | await saveSecurityData(security); 134 | logger.warn(`设备 ${deviceId} 已被封禁:注册尝试次数过多`); 135 | throw new Error('该设备已被封禁:注册次数过多'); 136 | } 137 | 138 | return true; 139 | } 140 | 141 | // 记录注册 142 | export async function recordRegistration(ip, deviceId, userId) { 143 | const security = await loadSecurityData(); 144 | const now = Date.now(); 145 | 146 | // 记录IP注册 147 | if (!security.ipRegistrations[ip]) { 148 | security.ipRegistrations[ip] = []; 149 | } 150 | security.ipRegistrations[ip].push({ timestamp: now, userId }); 151 | 152 | // 记录设备注册 153 | if (deviceId) { 154 | if (!security.deviceRegistrations[deviceId]) { 155 | security.deviceRegistrations[deviceId] = []; 156 | } 157 | security.deviceRegistrations[deviceId].push({ timestamp: now, userId }); 158 | } 159 | 160 | await saveSecurityData(security); 161 | logger.info(`记录注册:IP=${ip}, 设备=${deviceId}, 用户=${userId}`); 162 | } 163 | 164 | // 清理长时间未登录的账号(超过15天) 165 | export async function cleanupInactiveUsers(users) { 166 | const now = Date.now(); 167 | const inactivePeriod = 15 * 24 * 60 * 60 * 1000; // 15天 168 | const deletedUsers = []; 169 | 170 | const activeUsers = users.filter(user => { 171 | const lastActivity = user.lastLogin || user.created; 172 | const inactive = now - lastActivity > inactivePeriod; 173 | 174 | if (inactive) { 175 | deletedUsers.push({ 176 | username: user.username, 177 | lastActivity: new Date(lastActivity).toLocaleString() 178 | }); 179 | return false; 180 | } 181 | return true; 182 | }); 183 | 184 | if (deletedUsers.length > 0) { 185 | logger.info(`自动清理 ${deletedUsers.length} 个长时间未登录账号:${deletedUsers.map(u => u.username).join(', ')}`); 186 | } 187 | 188 | return { users: activeUsers, deletedCount: deletedUsers.length, deletedUsers }; 189 | } 190 | 191 | // 获取安全统计 192 | export async function getSecurityStats() { 193 | const security = await loadSecurityData(); 194 | 195 | return { 196 | bannedIPsCount: Object.keys(security.bannedIPs).length, 197 | bannedDevicesCount: Object.keys(security.bannedDevices).length, 198 | bannedIPs: security.bannedIPs, 199 | bannedDevices: security.bannedDevices 200 | }; 201 | } 202 | 203 | // 解封IP 204 | export async function unbanIP(ip) { 205 | const security = await loadSecurityData(); 206 | 207 | if (security.bannedIPs[ip]) { 208 | delete security.bannedIPs[ip]; 209 | delete security.suspiciousAttempts[ip]; 210 | await saveSecurityData(security); 211 | logger.info(`IP ${ip} 已解封`); 212 | return true; 213 | } 214 | 215 | return false; 216 | } 217 | 218 | // 解封设备 219 | export async function unbanDevice(deviceId) { 220 | const security = await loadSecurityData(); 221 | 222 | if (security.bannedDevices[deviceId]) { 223 | delete security.bannedDevices[deviceId]; 224 | await saveSecurityData(security); 225 | logger.info(`设备 ${deviceId} 已解封`); 226 | return true; 227 | } 228 | 229 | return false; 230 | } 231 | -------------------------------------------------------------------------------- /Antigravity/src/auth/token_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import { log } from '../utils/logger.js'; 5 | import config from '../config/config.js'; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | 10 | const CLIENT_ID = config.oauth.clientId; 11 | const CLIENT_SECRET = config.oauth.clientSecret; 12 | 13 | class TokenManager { 14 | constructor(filePath = path.join(__dirname,'..','..','data' ,'accounts.json')) { 15 | this.filePath = filePath; 16 | this.tokens = []; 17 | this.currentIndex = 0; 18 | this.lastLoadTime = 0; 19 | this.loadInterval = 60000; // 1分钟内不重复加载 20 | this.cachedData = null; // 缓存文件数据,减少磁盘读取 21 | this.usageStats = new Map(); // Token 使用统计 { refresh_token -> { requests, lastUsed } } 22 | this.loadTokens(); 23 | } 24 | 25 | loadTokens() { 26 | try { 27 | // 避免频繁加载,1分钟内使用缓存 28 | if (Date.now() - this.lastLoadTime < this.loadInterval && this.tokens.length > 0) { 29 | return; 30 | } 31 | 32 | log.info('正在加载token...'); 33 | const data = fs.readFileSync(this.filePath, 'utf8'); 34 | const tokenArray = JSON.parse(data); 35 | this.cachedData = tokenArray; // 缓存原始数据 36 | this.tokens = tokenArray.filter(token => token.enable !== false); 37 | this.currentIndex = 0; 38 | this.lastLoadTime = Date.now(); 39 | log.info(`成功加载 ${this.tokens.length} 个可用token`); 40 | 41 | // 触发垃圾回收(如果可用) 42 | if (global.gc) { 43 | global.gc(); 44 | } 45 | } catch (error) { 46 | log.error('加载token失败:', error.message); 47 | this.tokens = []; 48 | } 49 | } 50 | 51 | isExpired(token) { 52 | if (!token.timestamp || !token.expires_in) return true; 53 | const expiresAt = token.timestamp + (token.expires_in * 1000); 54 | return Date.now() >= expiresAt - 300000; 55 | } 56 | 57 | async refreshToken(token) { 58 | log.info('正在刷新token...'); 59 | const body = new URLSearchParams({ 60 | client_id: CLIENT_ID, 61 | client_secret: CLIENT_SECRET, 62 | grant_type: 'refresh_token', 63 | refresh_token: token.refresh_token 64 | }); 65 | 66 | const response = await fetch('https://oauth2.googleapis.com/token', { 67 | method: 'POST', 68 | headers: { 69 | 'Host': 'oauth2.googleapis.com', 70 | 'User-Agent': 'Go-http-client/1.1', 71 | 'Content-Length': body.toString().length.toString(), 72 | 'Content-Type': 'application/x-www-form-urlencoded', 73 | 'Accept-Encoding': 'gzip' 74 | }, 75 | body: body.toString() 76 | }); 77 | 78 | if (response.ok) { 79 | const data = await response.json(); 80 | token.access_token = data.access_token; 81 | token.expires_in = data.expires_in; 82 | token.timestamp = Date.now(); 83 | this.saveToFile(); 84 | return token; 85 | } else { 86 | throw { statusCode: response.status, message: await response.text() }; 87 | } 88 | } 89 | 90 | saveToFile() { 91 | try { 92 | // 使用缓存数据,减少磁盘读取 93 | let allTokens = this.cachedData; 94 | if (!allTokens) { 95 | const data = fs.readFileSync(this.filePath, 'utf8'); 96 | allTokens = JSON.parse(data); 97 | } 98 | 99 | this.tokens.forEach(memToken => { 100 | const index = allTokens.findIndex(t => t.refresh_token === memToken.refresh_token); 101 | if (index !== -1) allTokens[index] = memToken; 102 | }); 103 | 104 | fs.writeFileSync(this.filePath, JSON.stringify(allTokens, null, 2), 'utf8'); 105 | this.cachedData = allTokens; // 更新缓存 106 | } catch (error) { 107 | log.error('保存文件失败:', error.message); 108 | } 109 | } 110 | 111 | disableToken(token) { 112 | log.warn(`禁用token`) 113 | token.enable = false; 114 | this.saveToFile(); 115 | this.loadTokens(); 116 | } 117 | 118 | async getToken() { 119 | if (this.tokens.length === 0) return null; 120 | 121 | for (let i = 0; i < this.tokens.length; i++) { 122 | const token = this.tokens[this.currentIndex]; 123 | const tokenIndex = this.currentIndex; 124 | 125 | try { 126 | if (this.isExpired(token)) { 127 | await this.refreshToken(token); 128 | } 129 | this.currentIndex = (this.currentIndex + 1) % this.tokens.length; 130 | 131 | // 记录使用统计 132 | this.recordUsage(token); 133 | log.info(`🔄 轮询使用 Token #${tokenIndex} (总请求: ${this.getTokenRequests(token)})`); 134 | 135 | return token; 136 | } catch (error) { 137 | if (error.statusCode === 403) { 138 | log.warn(`Token ${this.currentIndex} 刷新失败(403),禁用并尝试下一个`); 139 | this.disableToken(token); 140 | } else { 141 | log.error(`Token ${this.currentIndex} 刷新失败:`, error.message); 142 | } 143 | this.currentIndex = (this.currentIndex + 1) % this.tokens.length; 144 | if (this.tokens.length === 0) return null; 145 | } 146 | } 147 | 148 | return null; 149 | } 150 | 151 | // 记录 Token 使用 152 | recordUsage(token) { 153 | const key = token.refresh_token; 154 | if (!this.usageStats.has(key)) { 155 | this.usageStats.set(key, { requests: 0, lastUsed: null }); 156 | } 157 | const stats = this.usageStats.get(key); 158 | stats.requests++; 159 | stats.lastUsed = Date.now(); 160 | } 161 | 162 | // 获取单个 Token 的请求次数 163 | getTokenRequests(token) { 164 | const stats = this.usageStats.get(token.refresh_token); 165 | return stats ? stats.requests : 0; 166 | } 167 | 168 | // 获取所有 Token 的使用统计 169 | getUsageStats() { 170 | const stats = []; 171 | this.tokens.forEach((token, index) => { 172 | const usage = this.usageStats.get(token.refresh_token) || { requests: 0, lastUsed: null }; 173 | stats.push({ 174 | index, 175 | requests: usage.requests, 176 | lastUsed: usage.lastUsed ? new Date(usage.lastUsed).toISOString() : null, 177 | isCurrent: index === this.currentIndex 178 | }); 179 | }); 180 | return { 181 | totalTokens: this.tokens.length, 182 | currentIndex: this.currentIndex, 183 | totalRequests: Array.from(this.usageStats.values()).reduce((sum, s) => sum + s.requests, 0), 184 | tokens: stats 185 | }; 186 | } 187 | 188 | disableCurrentToken(token) { 189 | const found = this.tokens.find(t => t.access_token === token.access_token); 190 | if (found) { 191 | this.disableToken(found); 192 | } 193 | } 194 | 195 | async handleRequestError(error, currentAccessToken) { 196 | if (error.statusCode === 403) { 197 | log.warn('请求遇到403错误,尝试刷新token'); 198 | const currentToken = this.tokens[this.currentIndex]; 199 | if (currentToken && currentToken.access_token === currentAccessToken) { 200 | try { 201 | await this.refreshToken(currentToken); 202 | log.info('Token刷新成功,返回新token'); 203 | return currentToken; 204 | } catch (refreshError) { 205 | if (refreshError.statusCode === 403) { 206 | log.warn('刷新token也遇到403,禁用并切换到下一个'); 207 | this.disableToken(currentToken); 208 | return await this.getToken(); 209 | } 210 | log.error('刷新token失败:', refreshError.message); 211 | } 212 | } 213 | return await this.getToken(); 214 | } 215 | return null; 216 | } 217 | } 218 | const tokenManager = new TokenManager(); 219 | export default tokenManager; 220 | -------------------------------------------------------------------------------- /Antigravity/src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import { randomUUID } from 'crypto'; 2 | import config from '../config/config.js'; 3 | 4 | function generateRequestId() { 5 | return `agent-${randomUUID()}`; 6 | } 7 | 8 | function generateSessionId() { 9 | return String(-Math.floor(Math.random() * 9e18)); 10 | } 11 | 12 | function generateProjectId() { 13 | const adjectives = ['useful', 'bright', 'swift', 'calm', 'bold']; 14 | const nouns = ['fuze', 'wave', 'spark', 'flow', 'core']; 15 | const randomAdj = adjectives[Math.floor(Math.random() * adjectives.length)]; 16 | const randomNoun = nouns[Math.floor(Math.random() * nouns.length)]; 17 | const randomNum = Math.random().toString(36).substring(2, 7); 18 | return `${randomAdj}-${randomNoun}-${randomNum}`; 19 | } 20 | function extractImagesFromContent(content) { 21 | const result = { text: '', images: [] }; 22 | 23 | // 如果content是字符串,直接返回 24 | if (typeof content === 'string') { 25 | result.text = content; 26 | return result; 27 | } 28 | 29 | // 如果content是数组(multimodal格式) 30 | if (Array.isArray(content)) { 31 | for (const item of content) { 32 | if (item.type === 'text') { 33 | result.text += item.text; 34 | } else if (item.type === 'image_url') { 35 | // 提取base64图片数据 36 | const imageUrl = item.image_url?.url || ''; 37 | 38 | // 匹配 data:image/{format};base64,{data} 格式 39 | const match = imageUrl.match(/^data:image\/(\w+);base64,(.+)$/); 40 | if (match) { 41 | const format = match[1]; // 例如 png, jpeg, jpg 42 | const base64Data = match[2]; 43 | result.images.push({ 44 | inlineData: { 45 | mimeType: `image/${format}`, 46 | data: base64Data 47 | } 48 | }) 49 | } 50 | } 51 | } 52 | } 53 | 54 | return result; 55 | } 56 | function handleUserMessage(extracted, antigravityMessages){ 57 | antigravityMessages.push({ 58 | role: "user", 59 | parts: [ 60 | { 61 | text: extracted.text 62 | }, 63 | ...extracted.images 64 | ] 65 | }) 66 | } 67 | function handleAssistantMessage(message, antigravityMessages){ 68 | const lastMessage = antigravityMessages[antigravityMessages.length - 1]; 69 | const hasToolCalls = message.tool_calls && message.tool_calls.length > 0; 70 | const hasContent = message.content && message.content.trim() !== ''; 71 | 72 | const antigravityTools = hasToolCalls ? message.tool_calls.map(toolCall => ({ 73 | functionCall: { 74 | id: toolCall.id, 75 | name: toolCall.function.name, 76 | args: { 77 | query: toolCall.function.arguments 78 | } 79 | } 80 | })) : []; 81 | 82 | if (lastMessage?.role === "model" && hasToolCalls && !hasContent){ 83 | lastMessage.parts.push(...antigravityTools) 84 | }else{ 85 | const parts = []; 86 | if (hasContent) parts.push({ text: message.content }); 87 | parts.push(...antigravityTools); 88 | 89 | antigravityMessages.push({ 90 | role: "model", 91 | parts 92 | }) 93 | } 94 | } 95 | function handleToolCall(message, antigravityMessages){ 96 | // 从之前的 model 消息中找到对应的 functionCall name 97 | let functionName = ''; 98 | for (let i = antigravityMessages.length - 1; i >= 0; i--) { 99 | if (antigravityMessages[i].role === 'model') { 100 | const parts = antigravityMessages[i].parts; 101 | for (const part of parts) { 102 | if (part.functionCall && part.functionCall.id === message.tool_call_id) { 103 | functionName = part.functionCall.name; 104 | break; 105 | } 106 | } 107 | if (functionName) break; 108 | } 109 | } 110 | 111 | const lastMessage = antigravityMessages[antigravityMessages.length - 1]; 112 | const functionResponse = { 113 | functionResponse: { 114 | id: message.tool_call_id, 115 | name: functionName, 116 | response: { 117 | output: message.content 118 | } 119 | } 120 | }; 121 | 122 | // 如果上一条消息是 user 且包含 functionResponse,则合并 123 | if (lastMessage?.role === "user" && lastMessage.parts.some(p => p.functionResponse)) { 124 | lastMessage.parts.push(functionResponse); 125 | } else { 126 | antigravityMessages.push({ 127 | role: "user", 128 | parts: [functionResponse] 129 | }); 130 | } 131 | } 132 | function openaiMessageToAntigravity(openaiMessages){ 133 | const antigravityMessages = []; 134 | for (const message of openaiMessages) { 135 | if (message.role === "user" || message.role === "system") { 136 | const extracted = extractImagesFromContent(message.content); 137 | handleUserMessage(extracted, antigravityMessages); 138 | } else if (message.role === "assistant") { 139 | handleAssistantMessage(message, antigravityMessages); 140 | } else if (message.role === "tool") { 141 | handleToolCall(message, antigravityMessages); 142 | } 143 | } 144 | 145 | return antigravityMessages; 146 | } 147 | function generateGenerationConfig(parameters, enableThinking, actualModelName){ 148 | const generationConfig = { 149 | topP: parameters.top_p ?? config.defaults.top_p, 150 | topK: parameters.top_k ?? config.defaults.top_k, 151 | temperature: parameters.temperature ?? config.defaults.temperature, 152 | candidateCount: 1, 153 | maxOutputTokens: parameters.max_tokens ?? config.defaults.max_tokens, 154 | stopSequences: [ 155 | "<|user|>", 156 | "<|bot|>", 157 | "<|context_request|>", 158 | "<|endoftext|>", 159 | "<|end_of_turn|>" 160 | ], 161 | thinkingConfig: { 162 | includeThoughts: enableThinking, 163 | thinkingBudget: enableThinking ? 1024 : 0 164 | } 165 | } 166 | if (enableThinking && actualModelName.includes("claude")){ 167 | delete generationConfig.topP; 168 | } 169 | return generationConfig 170 | } 171 | function convertOpenAIToolsToAntigravity(openaiTools){ 172 | if (!openaiTools || openaiTools.length === 0) return []; 173 | return openaiTools.map((tool)=>{ 174 | delete tool.function.parameters.$schema; 175 | return { 176 | functionDeclarations: [ 177 | { 178 | name: tool.function.name, 179 | description: tool.function.description, 180 | parameters: tool.function.parameters 181 | } 182 | ] 183 | } 184 | }) 185 | } 186 | function generateRequestBody(openaiMessages,modelName,parameters,openaiTools){ 187 | const enableThinking = modelName.endsWith('-thinking') || 188 | modelName === 'gemini-2.5-pro' || 189 | modelName.startsWith('gemini-3-pro-') || 190 | modelName === "rev19-uic3-1p" || 191 | modelName === "gpt-oss-120b-medium" 192 | const actualModelName = modelName.endsWith('-thinking') ? modelName.slice(0, -9) : modelName; 193 | 194 | return{ 195 | project: generateProjectId(), 196 | requestId: generateRequestId(), 197 | request: { 198 | contents: openaiMessageToAntigravity(openaiMessages), 199 | systemInstruction: { 200 | role: "user", 201 | parts: [{ text: config.systemInstruction }] 202 | }, 203 | tools: convertOpenAIToolsToAntigravity(openaiTools), 204 | toolConfig: { 205 | functionCallingConfig: { 206 | mode: "VALIDATED" 207 | } 208 | }, 209 | generationConfig: generateGenerationConfig(parameters, enableThinking, actualModelName), 210 | sessionId: generateSessionId() 211 | }, 212 | model: actualModelName, 213 | userAgent: "antigravity" 214 | } 215 | } 216 | // HTML转义函数,防止XSS攻击 217 | function escapeHtml(unsafe) { 218 | if (!unsafe) return ''; 219 | return unsafe 220 | .toString() 221 | .replace(/&/g, "&") 222 | .replace(//g, ">") 224 | .replace(/"/g, """) 225 | .replace(/'/g, "'"); 226 | } 227 | 228 | export{ 229 | generateRequestId, 230 | generateSessionId, 231 | generateProjectId, 232 | generateRequestBody, 233 | escapeHtml 234 | } 235 | -------------------------------------------------------------------------------- /Antigravity/src/admin/model_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import logger from '../utils/logger.js'; 4 | import { getAvailableModels } from '../api/client.js'; 5 | 6 | const MODELS_FILE = path.join(process.cwd(), 'data', 'models.json'); 7 | const MODEL_USAGE_FILE = path.join(process.cwd(), 'data', 'model_usage.json'); 8 | 9 | // 默认模型配置(每日额度) 10 | const DEFAULT_MODEL_QUOTAS = { 11 | 'gemini-2.0-flash-exp': 100, 12 | 'gemini-1.5-flash': 100, 13 | 'gemini-1.5-flash-8b': 150, 14 | 'gemini-1.5-pro': 50, 15 | 'gemini-exp-1206': 30, 16 | 'default': 100 // 未配置模型的默认额度 17 | }; 18 | 19 | // 读取模型列表 20 | export async function loadModels() { 21 | try { 22 | const data = await fs.readFile(MODELS_FILE, 'utf-8'); 23 | return JSON.parse(data); 24 | } catch (error) { 25 | if (error.code === 'ENOENT') { 26 | return []; 27 | } 28 | throw error; 29 | } 30 | } 31 | 32 | // 保存模型列表 33 | async function saveModels(models) { 34 | const dir = path.dirname(MODELS_FILE); 35 | try { 36 | await fs.access(dir); 37 | } catch { 38 | await fs.mkdir(dir, { recursive: true }); 39 | } 40 | await fs.writeFile(MODELS_FILE, JSON.stringify(models, null, 2), 'utf-8'); 41 | } 42 | 43 | // 读取模型使用记录 44 | async function loadModelUsage() { 45 | try { 46 | const data = await fs.readFile(MODEL_USAGE_FILE, 'utf-8'); 47 | return JSON.parse(data); 48 | } catch (error) { 49 | if (error.code === 'ENOENT') { 50 | return {}; 51 | } 52 | throw error; 53 | } 54 | } 55 | 56 | // 保存模型使用记录 57 | async function saveModelUsage(usage) { 58 | const dir = path.dirname(MODEL_USAGE_FILE); 59 | try { 60 | await fs.access(dir); 61 | } catch { 62 | await fs.mkdir(dir, { recursive: true }); 63 | } 64 | await fs.writeFile(MODEL_USAGE_FILE, JSON.stringify(usage, null, 2), 'utf-8'); 65 | } 66 | 67 | // 自动获取并保存模型 68 | export async function fetchAndSaveModels() { 69 | try { 70 | // 使用管理员权限获取模型列表 71 | const modelsData = await getAvailableModels({ type: 'admin' }); 72 | 73 | if (!modelsData || !modelsData.data) { 74 | throw new Error('获取模型列表失败'); 75 | } 76 | 77 | const models = modelsData.data.map(model => ({ 78 | id: model.id, 79 | name: model.id, 80 | quota: DEFAULT_MODEL_QUOTAS[model.id] || DEFAULT_MODEL_QUOTAS.default, 81 | enabled: true, 82 | created: Date.now(), 83 | updated: Date.now() 84 | })); 85 | 86 | await saveModels(models); 87 | logger.info(`成功获取并保存了 ${models.length} 个模型`); 88 | 89 | return models; 90 | } catch (error) { 91 | logger.error(`获取模型失败: ${error.message}`); 92 | throw error; 93 | } 94 | } 95 | 96 | // 更新模型配额 97 | export async function updateModelQuota(modelId, quota) { 98 | const models = await loadModels(); 99 | const model = models.find(m => m.id === modelId); 100 | 101 | if (!model) { 102 | throw new Error('模型不存在'); 103 | } 104 | 105 | model.quota = quota; 106 | model.updated = Date.now(); 107 | 108 | await saveModels(models); 109 | logger.info(`更新模型 ${modelId} 配额为 ${quota}`); 110 | 111 | return model; 112 | } 113 | 114 | // 启用/禁用模型 115 | export async function toggleModel(modelId, enabled) { 116 | const models = await loadModels(); 117 | const model = models.find(m => m.id === modelId); 118 | 119 | if (!model) { 120 | throw new Error('模型不存在'); 121 | } 122 | 123 | model.enabled = enabled; 124 | model.updated = Date.now(); 125 | 126 | await saveModels(models); 127 | logger.info(`模型 ${modelId} 已${enabled ? '启用' : '禁用'}`); 128 | 129 | return model; 130 | } 131 | 132 | // 记录模型使用 133 | export async function recordModelUsage(userId, modelId) { 134 | const usage = await loadModelUsage(); 135 | const today = new Date().toISOString().split('T')[0]; 136 | 137 | // 初始化用户记录 138 | if (!usage[userId]) { 139 | usage[userId] = {}; 140 | } 141 | 142 | // 初始化日期记录 143 | if (!usage[userId][today]) { 144 | usage[userId][today] = {}; 145 | } 146 | 147 | // 初始化模型记录 148 | if (!usage[userId][today][modelId]) { 149 | usage[userId][today][modelId] = 0; 150 | } 151 | 152 | // 增加使用次数 153 | usage[userId][today][modelId]++; 154 | 155 | await saveModelUsage(usage); 156 | return usage[userId][today][modelId]; 157 | } 158 | 159 | // 获取用户今日模型使用情况 160 | export async function getUserModelUsage(userId) { 161 | const usage = await loadModelUsage(); 162 | const today = new Date().toISOString().split('T')[0]; 163 | 164 | if (!usage[userId] || !usage[userId][today]) { 165 | return {}; 166 | } 167 | 168 | return usage[userId][today]; 169 | } 170 | 171 | // 检查用户模型配额 172 | export async function checkModelQuota(userId, modelId) { 173 | const models = await loadModels(); 174 | const model = models.find(m => m.id === modelId); 175 | 176 | if (!model) { 177 | // 模型不存在,使用默认配额 178 | const defaultQuota = DEFAULT_MODEL_QUOTAS.default; 179 | const usage = await getUserModelUsage(userId); 180 | const used = usage[modelId] || 0; 181 | 182 | return { 183 | allowed: used < defaultQuota, 184 | quota: defaultQuota, 185 | used, 186 | remaining: Math.max(0, defaultQuota - used) 187 | }; 188 | } 189 | 190 | if (!model.enabled) { 191 | return { 192 | allowed: false, 193 | quota: model.quota, 194 | used: 0, 195 | remaining: 0, 196 | error: '该模型已被禁用' 197 | }; 198 | } 199 | 200 | const usage = await getUserModelUsage(userId); 201 | const used = usage[modelId] || 0; 202 | 203 | return { 204 | allowed: used < model.quota, 205 | quota: model.quota, 206 | used, 207 | remaining: Math.max(0, model.quota - used) 208 | }; 209 | } 210 | 211 | // 获取模型统计信息 212 | export async function getModelStats() { 213 | const models = await loadModels(); 214 | const usage = await loadModelUsage(); 215 | const today = new Date().toISOString().split('T')[0]; 216 | 217 | const stats = models.map(model => { 218 | let totalUsageToday = 0; 219 | let userCount = 0; 220 | 221 | // 统计今日所有用户对该模型的使用 222 | Object.keys(usage).forEach(userId => { 223 | if (usage[userId][today] && usage[userId][today][model.id]) { 224 | totalUsageToday += usage[userId][today][model.id]; 225 | userCount++; 226 | } 227 | }); 228 | 229 | return { 230 | id: model.id, 231 | name: model.name, 232 | quota: model.quota, 233 | enabled: model.enabled, 234 | usageToday: totalUsageToday, 235 | userCount, 236 | created: model.created 237 | }; 238 | }); 239 | 240 | return { 241 | models: stats, 242 | totalModels: models.length, 243 | enabledModels: models.filter(m => m.enabled).length, 244 | totalUsageToday: stats.reduce((sum, m) => sum + m.usageToday, 0) 245 | }; 246 | } 247 | 248 | // 清理过期使用记录(保留最近30天) 249 | export async function cleanupOldUsage() { 250 | const usage = await loadModelUsage(); 251 | const cutoffDate = new Date(); 252 | cutoffDate.setDate(cutoffDate.getDate() - 30); 253 | const cutoffDateStr = cutoffDate.toISOString().split('T')[0]; 254 | 255 | let cleaned = 0; 256 | 257 | Object.keys(usage).forEach(userId => { 258 | const userDates = Object.keys(usage[userId]); 259 | userDates.forEach(date => { 260 | if (date < cutoffDateStr) { 261 | delete usage[userId][date]; 262 | cleaned++; 263 | } 264 | }); 265 | 266 | // 如果用户没有任何记录,删除用户 267 | if (Object.keys(usage[userId]).length === 0) { 268 | delete usage[userId]; 269 | } 270 | }); 271 | 272 | if (cleaned > 0) { 273 | await saveModelUsage(usage); 274 | logger.info(`清理了 ${cleaned} 条过期的模型使用记录`); 275 | } 276 | 277 | return cleaned; 278 | } 279 | 280 | // 设置用户特定模型配额(可选功能,覆盖默认配额) 281 | export async function setUserModelQuota(userId, modelId, quota) { 282 | const USER_QUOTAS_FILE = path.join(process.cwd(), 'data', 'user_model_quotas.json'); 283 | 284 | let userQuotas = {}; 285 | try { 286 | const data = await fs.readFile(USER_QUOTAS_FILE, 'utf-8'); 287 | userQuotas = JSON.parse(data); 288 | } catch (error) { 289 | if (error.code !== 'ENOENT') throw error; 290 | } 291 | 292 | if (!userQuotas[userId]) { 293 | userQuotas[userId] = {}; 294 | } 295 | 296 | userQuotas[userId][modelId] = quota; 297 | 298 | await fs.writeFile(USER_QUOTAS_FILE, JSON.stringify(userQuotas, null, 2), 'utf-8'); 299 | logger.info(`为用户 ${userId} 设置模型 ${modelId} 配额为 ${quota}`); 300 | 301 | return { userId, modelId, quota }; 302 | } 303 | 304 | // 获取用户特定模型配额 305 | export async function getUserModelQuota(userId, modelId) { 306 | const USER_QUOTAS_FILE = path.join(process.cwd(), 'data', 'user_model_quotas.json'); 307 | 308 | try { 309 | const data = await fs.readFile(USER_QUOTAS_FILE, 'utf-8'); 310 | const userQuotas = JSON.parse(data); 311 | 312 | if (userQuotas[userId] && userQuotas[userId][modelId] !== undefined) { 313 | return userQuotas[userId][modelId]; 314 | } 315 | } catch (error) { 316 | if (error.code !== 'ENOENT') throw error; 317 | } 318 | 319 | // 返回默认配额 320 | const models = await loadModels(); 321 | const model = models.find(m => m.id === modelId); 322 | return model ? model.quota : DEFAULT_MODEL_QUOTAS.default; 323 | } 324 | -------------------------------------------------------------------------------- /Antigravity/README.md: -------------------------------------------------------------------------------- 1 | # Antigravity to OpenAI API 代理服务 2 | 3 | 将 Google Antigravity API 转换为 OpenAI 兼容格式的代理服务,支持流式响应、工具调用、多账号管理和完整的用户管理系统。 4 | 5 | 测试地址 6 | https://ggt333.zeabur.app/user.html 7 | 8 | ## ✨ 核心功能 9 | 10 | ### API 代理 11 | - ✅ OpenAI API 完全兼容格式 12 | - ✅ 流式和非流式响应 13 | - ✅ 工具调用(Function Calling)支持 14 | - ✅ 多账号自动轮换 15 | - ✅ Token 自动刷新 16 | - ✅ API Key 认证 17 | - ✅ 思维链(Thinking)输出 18 | - ✅ 图片输入支持(Base64 编码) 19 | 20 | ### 管理系统 21 | - 🎛️ **Web管理后台** - 完整的可视化管理界面 22 | - 👥 **用户系统** - 用户注册、登录、API密钥管理 23 | - 🔑 **密钥管理** - API密钥生成、频率限制 24 | - 📢 **公告系统** - 系统公告发布和管理 25 | - 📊 **模型配额** - 每日模型使用限制 26 | - 🔐 **安全防护** - IP/设备封禁、注册限制 27 | 28 | ### 共享系统 29 | - 🌐 **Token共享中心** - 用户可共享自己的Token供社区使用 30 | - 🚫 **滥用防护** - 自动检测和封禁滥用用户 31 | - 🗳️ **社区投票** - 社区投票封禁滥用者 32 | - 📈 **使用统计** - 实时显示每个用户的使用情况 33 | - ⚫ **黑名单系统** - Token所有者可屏蔽特定用户 34 | 35 | ### 🤖 AI自动管理 36 | - 🔍 **智能分析** - AI自动分析用户使用模式 37 | - ⚡ **自动封禁** - 基于AI判断自动封禁异常用户 38 | - ⏱️ **定时任务** - 可配置每小时自动审核 39 | - 📊 **详细日志** - 完整的审核历史和决策记录 40 | - 🎯 **置信度控制** - 可调整自动封禁阈值 41 | 42 | ## 环境要求 43 | 44 | - Node.js >= 18.0.0 45 | - pnpm 或 npm 46 | 47 | ## 快速开始 48 | 49 | ### 1. 安装依赖 50 | 51 | ```bash 52 | pnpm install 53 | # 或 54 | npm install 55 | ``` 56 | 57 | ### 2. 配置文件 58 | 59 | 编辑 `config.json` 配置服务器和 API 参数: 60 | 61 | ```json 62 | { 63 | "server": { 64 | "port": 8045, 65 | "host": "0.0.0.0" 66 | }, 67 | "security": { 68 | "apiKey": "sk-text", 69 | "adminPassword": "admin123", 70 | "maxRequestSize": "50mb" 71 | }, 72 | "defaults": { 73 | "temperature": 1, 74 | "top_p": 0.85, 75 | "top_k": 50, 76 | "max_tokens": 8096 77 | } 78 | } 79 | ``` 80 | 81 | ### 3. 启动服务 82 | 83 | ```bash 84 | pnpm dev 85 | # 或 86 | npm run dev 87 | ``` 88 | 89 | 服务将在 `http://localhost:8045` 启动。 90 | 91 | ### 4. 访问管理后台 92 | 93 | 打开浏览器访问 `http://localhost:8045`,使用配置的管理员密码登录。 94 | 95 | ## 📖 完整功能指南 96 | 97 | ### 管理后台功能 98 | 99 | #### 1. Token 管理 100 | - 添加/删除 Google Token 101 | - 查看 Token 使用统计 102 | - 启用/禁用 Token 103 | - 导入/导出 Token 104 | 105 | #### 2. 密钥管理 106 | - 生成管理员 API 密钥 107 | - 设置密钥频率限制(每分钟/每小时/每天) 108 | - 查看密钥使用统计 109 | 110 | #### 3. 用户管理 111 | - 查看所有注册用户 112 | - 启用/禁用用户 113 | - 查看用户Token和使用情况 114 | - 设置用户模型配额 115 | - 查看用户共享统计 116 | 117 | #### 4. 公告管理 118 | - 创建/编辑/删除公告 119 | - 设置公告优先级 120 | - 启用/禁用公告 121 | 122 | #### 5. 模型管理 123 | - 从Google获取最新模型列表 124 | - 设置默认模型配额 125 | - 查看每个模型的使用统计 126 | 127 | #### 6. 系统监控 128 | - 实时请求统计 129 | - 系统资源使用 130 | - Token使用情况 131 | - 错误日志查看 132 | 133 | ### 用户中心功能 134 | 135 | 访问 `http://localhost:8045/user.html` 进入用户中心: 136 | 137 | - **账号注册/登录** - 独立的用户系统 138 | - **API密钥管理** - 生成个人API密钥 139 | - **Token管理** - 添加/共享自己的Google Token 140 | - **使用统计** - 查看个人使用情况 141 | - **模型配额** - 查看每日模型使用限额 142 | 143 | ### Token共享中心 144 | 145 | 访问 `http://localhost:8045/share.html` 进入共享中心: 146 | 147 | - **Token列表** - 查看所有共享的Token及可用额度 148 | - **用户统计** - 查看所有用户的使用排行 149 | - 平均日使用量 150 | - 今日使用量 151 | - 最大日使用量 152 | - 使用天数统计 153 | - 异常用户标识 154 | - **投票封禁** - 发起或参与封禁投票 155 | - **投票历史** - 查看所有投票记录 156 | 157 | ### AI自动管理系统 158 | 159 | 在管理后台的"AI 管理"标签页配置: 160 | 161 | #### 配置项 162 | - **启用AI审核** - 开启/关闭自动审核 163 | - **API端点** - AI服务的API地址 164 | - **API密钥** - AI服务的认证密钥 165 | - **模型** - 使用的AI模型(如 gemini-2.0-flash-exp) 166 | - **检查间隔** - 审核频率(1-24小时) 167 | - **置信度阈值** - 自动封禁的最低置信度(0-1) 168 | - **系统提示词** - AI分析的指导规则 169 | 170 | #### AI审核规则 171 | 系统默认分析以下指标: 172 | 1. **使用频率** - 平均每天超过50次为异常 173 | 2. **使用模式** - 短时间内大量请求 174 | 3. **时间分布** - 24小时持续高频使用 175 | 4. **突增行为** - 使用量突然大幅增加 176 | 177 | #### 操作功能 178 | - **立即运行审核** - 手动触发一次审核 179 | - **查看审核日志** - 显示最新50条审核记录 180 | - **统计信息** - 总审核次数、封禁数、标记数 181 | 182 | ## API 使用 183 | 184 | ### 获取模型列表 185 | 186 | ```bash 187 | curl http://localhost:8045/v1/models \ 188 | -H "Authorization: Bearer sk-text" 189 | ``` 190 | 191 | ### 聊天补全(流式) 192 | 193 | ```bash 194 | curl http://localhost:8045/v1/chat/completions \ 195 | -H "Content-Type: application/json" \ 196 | -H "Authorization: Bearer sk-text" \ 197 | -d '{ 198 | "model": "gemini-2.0-flash-exp", 199 | "messages": [{"role": "user", "content": "你好"}], 200 | "stream": true 201 | }' 202 | ``` 203 | 204 | ### 工具调用示例 205 | 206 | ```bash 207 | curl http://localhost:8045/v1/chat/completions \ 208 | -H "Content-Type: application/json" \ 209 | -H "Authorization: Bearer sk-text" \ 210 | -d '{ 211 | "model": "gemini-2.0-flash-exp", 212 | "messages": [{"role": "user", "content": "北京天气怎么样"}], 213 | "tools": [{ 214 | "type": "function", 215 | "function": { 216 | "name": "get_weather", 217 | "description": "获取天气信息", 218 | "parameters": { 219 | "type": "object", 220 | "properties": { 221 | "location": {"type": "string", "description": "城市名称"} 222 | } 223 | } 224 | } 225 | }] 226 | }' 227 | ``` 228 | 229 | ### 图片输入示例 230 | 231 | ```bash 232 | curl http://localhost:8045/v1/chat/completions \ 233 | -H "Content-Type: application/json" \ 234 | -H "Authorization: Bearer sk-text" \ 235 | -d '{ 236 | "model": "gemini-2.0-flash-exp", 237 | "messages": [{ 238 | "role": "user", 239 | "content": [ 240 | {"type": "text", "text": "这张图片里有什么?"}, 241 | { 242 | "type": "image_url", 243 | "image_url": { 244 | "url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..." 245 | } 246 | } 247 | ] 248 | }], 249 | "stream": true 250 | }' 251 | ``` 252 | 253 | 支持的图片格式:JPEG、PNG、GIF、WebP 254 | 255 | ## 项目结构 256 | 257 | ``` 258 | . 259 | ├── data/ # 数据目录(自动生成) 260 | │ ├── accounts.json # Token 存储 261 | │ ├── users.json # 用户数据 262 | │ ├── share_data.json # 共享系统数据 263 | │ ├── ai_config.json # AI配置 264 | │ └── ai_moderation_logs.json # AI审核日志 265 | ├── public/ # 前端页面 266 | │ ├── index.html # 管理后台 267 | │ ├── user.html # 用户中心 268 | │ └── share.html # 共享中心 269 | ├── scripts/ 270 | │ └── oauth-server.js # OAuth 登录服务 271 | ├── src/ 272 | │ ├── admin/ # 管理模块 273 | │ │ ├── routes.js # 管理路由 274 | │ │ ├── user_manager.js # 用户管理 275 | │ │ ├── share_manager.js # 共享管理 276 | │ │ ├── ai_moderator.js # AI自动管理 277 | │ │ ├── key_manager.js # 密钥管理 278 | │ │ ├── model_manager.js # 模型管理 279 | │ │ └── ... # 其他管理模块 280 | │ ├── api/ 281 | │ │ └── client.js # API 调用逻辑 282 | │ ├── auth/ 283 | │ │ └── token_manager.js # Token 管理 284 | │ ├── config/ 285 | │ │ └── config.js # 配置加载 286 | │ ├── server/ 287 | │ │ └── index.js # 主服务器 288 | │ └── utils/ 289 | │ ├── logger.js # 日志模块 290 | │ └── utils.js # 工具函数 291 | ├── config.json # 配置文件 292 | └── package.json # 项目配置 293 | ``` 294 | 295 | ## 安全特性 296 | 297 | ### 用户安全 298 | - 密码加密存储(PBKDF2) 299 | - 会话Token管理 300 | - API密钥认证 301 | - 频率限制保护 302 | 303 | ### 防滥用机制 304 | - IP注册限制(每IP每天最多注册数) 305 | - 设备注册限制(基于指纹识别) 306 | - 自动清理长期未登录账号 307 | - 共享使用量监控 308 | 309 | ### AI自动防护 310 | - 智能检测异常使用模式 311 | - 自动封禁滥用账号 312 | - 渐进式封禁时长(1天→3天→7天→14天→30天→90天) 313 | - 社区投票机制 314 | 315 | ## 配置说明 316 | 317 | ### config.json 完整配置 318 | 319 | ```json 320 | { 321 | "server": { 322 | "port": 8045, 323 | "host": "0.0.0.0" 324 | }, 325 | "security": { 326 | "apiKey": "sk-text", 327 | "adminPassword": "admin123", 328 | "maxRequestSize": "50mb" 329 | }, 330 | "defaults": { 331 | "temperature": 1, 332 | "top_p": 0.85, 333 | "top_k": 50, 334 | "max_tokens": 8096 335 | }, 336 | "systemInstruction": "你是一个有帮助的AI助手" 337 | } 338 | ``` 339 | 340 | | 配置项 | 说明 | 默认值 | 341 | |--------|------|--------| 342 | | `server.port` | 服务端口 | 8045 | 343 | | `server.host` | 监听地址 | 0.0.0.0 | 344 | | `security.apiKey` | 管理员API密钥 | sk-text | 345 | | `security.adminPassword` | 管理后台密码 | admin123 | 346 | | `security.maxRequestSize` | 最大请求体大小 | 50mb | 347 | | `defaults.temperature` | 默认温度参数 | 1 | 348 | | `defaults.top_p` | 默认 top_p | 0.85 | 349 | | `defaults.top_k` | 默认 top_k | 50 | 350 | | `defaults.max_tokens` | 默认最大token数 | 8096 | 351 | | `systemInstruction` | 系统提示词 | - | 352 | 353 | ## 开发命令 354 | 355 | ```bash 356 | # 启动服务 357 | npm start 358 | 359 | # 开发模式(自动重启) 360 | npm run dev 361 | 362 | # 登录获取 Token 363 | npm run login 364 | ``` 365 | 366 | ## 常见问题 367 | 368 | ### 1. 如何获取Google Token? 369 | 370 | 运行 `npm run login` 启动OAuth服务器,在浏览器中完成Google登录。 371 | 372 | ### 2. 如何配置AI自动管理? 373 | 374 | 1. 访问管理后台的"AI 管理"标签 375 | 2. 配置API端点(如本地服务地址) 376 | 3. 设置API密钥 377 | 4. 调整置信度阈值和检查间隔 378 | 5. 启用AI审核 379 | 380 | ### 3. 用户如何共享Token? 381 | 382 | 1. 用户登录用户中心 383 | 2. 在"我的Token"中添加Google Token 384 | 3. 勾选"共享此Token" 385 | 4. Token会出现在共享中心供其他用户使用 386 | 387 | ### 4. 如何处理滥用用户? 388 | 389 | 系统提供三种方式: 390 | - **自动封禁** - AI检测到异常自动封禁 391 | - **手动封禁** - 在用户管理中手动操作 392 | - **投票封禁** - 社区投票决定是否封禁 393 | 394 | ### 5. 封禁时长规则是什么? 395 | 396 | 采用渐进式封禁: 397 | - 第1次:1天 398 | - 第2次:3天 399 | - 第3次:7天 400 | - 第4次:14天 401 | - 第5次:30天 402 | - 第6次及以后:90天 403 | 404 | ## 注意事项 405 | 406 | 1. 首次使用需要运行 `npm run login` 获取 Token 407 | 2. `data/` 目录包含敏感信息,请勿泄露 408 | 3. 建议修改默认的管理员密码 409 | 4. AI自动管理需要配置有效的AI服务端点 410 | 5. 共享Token的用户需自行承担配额使用 411 | 6. 定期查看AI审核日志,确保系统正常运行 412 | 413 | ## 许可证 414 | 415 | MIT License 416 | 417 | ## 贡献 418 | 419 | 欢迎提交 Issue 和 Pull Request! 420 | 421 | ## 更新日志 422 | 423 | ### v2.0.0 424 | - ✨ 新增完整的Web管理后台 425 | - ✨ 新增用户系统和用户中心 426 | - ✨ 新增Token共享系统 427 | - ✨ 新增AI自动管理功能 428 | - ✨ 新增社区投票封禁机制 429 | - ✨ 新增用户使用统计展示 430 | - 🔧 优化Token轮换机制 431 | - 🔧 完善安全防护措施 432 | 433 | ### v1.0.0 434 | - 🎉 初始版本发布 435 | - ✅ OpenAI API兼容 436 | - ✅ 流式响应支持 437 | - ✅ 工具调用支持 438 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Antigravity to OpenAI API 代理服务 2 | 3 | 将 Google Antigravity API 转换为 OpenAI 兼容格式的代理服务,支持流式响应、工具调用、多账号管理和完整的用户管理系统。 4 | 5 | 测试地址 6 | https://ggt333.zeabur.app/user.html 7 | 8 | 这个错误是 redirect_uri_mismatch,意思是 Google OAuth 的回调地址不匹配。 原因:你需要在 Google Cloud Console 中添加正确的重定向 URI。 解决方法: 9 | 打开 Google Cloud Console 10 | 进入 APIs & Services → Credentials 11 | 找到你的 OAuth 2.0 Client ID,点击编辑 12 | 在 Authorized redirect URIs 中添加你的回调地址: 13 | 如果是本地开发:http://localhost:端口/admin/oauth-callback 14 | 如果是生产环境:https://你的域名/admin/oauth-callback 15 | 查看当前使用的 redirect_uri: 你可以看一下浏览器地址栏中 Google 授权页面的 URL,找到 redirect_uri= 参数,把那个值添加到 Google Cloud Console 中。 16 | 17 | 18 | ## ✨ 核心功能 19 | 20 | ### API 代理 21 | - ✅ OpenAI API 完全兼容格式 22 | - ✅ 流式和非流式响应 23 | - ✅ 工具调用(Function Calling)支持 24 | - ✅ 多账号自动轮换 25 | - ✅ Token 自动刷新 26 | - ✅ API Key 认证 27 | - ✅ 思维链(Thinking)输出 28 | - ✅ 图片输入支持(Base64 编码) 29 | 30 | ### 管理系统 31 | - 🎛️ **Web管理后台** - 完整的可视化管理界面 32 | - 👥 **用户系统** - 用户注册、登录、API密钥管理 33 | - 🔑 **密钥管理** - API密钥生成、频率限制 34 | - 📢 **公告系统** - 系统公告发布和管理 35 | - 📊 **模型配额** - 每日模型使用限制 36 | - 🔐 **安全防护** - IP/设备封禁、注册限制 37 | 38 | ### 共享系统 39 | - 🌐 **Token共享中心** - 用户可共享自己的Token供社区使用 40 | - 🚫 **滥用防护** - 自动检测和封禁滥用用户 41 | - 🗳️ **社区投票** - 社区投票封禁滥用者 42 | - 📈 **使用统计** - 实时显示每个用户的使用情况 43 | - ⚫ **黑名单系统** - Token所有者可屏蔽特定用户 44 | 45 | ### 🤖 AI自动管理 46 | - 🔍 **智能分析** - AI自动分析用户使用模式 47 | - ⚡ **自动封禁** - 基于AI判断自动封禁异常用户 48 | - ⏱️ **定时任务** - 可配置每小时自动审核 49 | - 📊 **详细日志** - 完整的审核历史和决策记录 50 | - 🎯 **置信度控制** - 可调整自动封禁阈值 51 | 52 | ## 环境要求 53 | 54 | - Node.js >= 18.0.0 55 | - pnpm 或 npm 56 | 57 | ## 快速开始 58 | 59 | ### 1. 安装依赖 60 | 61 | ```bash 62 | pnpm install 63 | # 或 64 | npm install 65 | ``` 66 | 67 | ### 2. 配置文件 68 | 69 | 编辑 `config.json` 配置服务器和 API 参数: 70 | 71 | ```json 72 | { 73 | "server": { 74 | "port": 8045, 75 | "host": "0.0.0.0" 76 | }, 77 | "security": { 78 | "apiKey": "sk-text", 79 | "adminPassword": "admin123", 80 | "maxRequestSize": "50mb" 81 | }, 82 | "defaults": { 83 | "temperature": 1, 84 | "top_p": 0.85, 85 | "top_k": 50, 86 | "max_tokens": 8096 87 | } 88 | } 89 | ``` 90 | 91 | ### 3. 启动服务 92 | 93 | ```bash 94 | pnpm dev 95 | # 或 96 | npm run dev 97 | ``` 98 | 99 | 服务将在 `http://localhost:8045` 启动。 100 | 101 | ### 4. 访问管理后台 102 | 103 | 打开浏览器访问 `http://localhost:8045`,使用配置的管理员密码登录。 104 | 105 | ## 📖 完整功能指南 106 | 107 | ### 管理后台功能 108 | 109 | #### 1. Token 管理 110 | - 添加/删除 Google Token 111 | - 查看 Token 使用统计 112 | - 启用/禁用 Token 113 | - 导入/导出 Token 114 | 115 | #### 2. 密钥管理 116 | - 生成管理员 API 密钥 117 | - 设置密钥频率限制(每分钟/每小时/每天) 118 | - 查看密钥使用统计 119 | 120 | #### 3. 用户管理 121 | - 查看所有注册用户 122 | - 启用/禁用用户 123 | - 查看用户Token和使用情况 124 | - 设置用户模型配额 125 | - 查看用户共享统计 126 | 127 | #### 4. 公告管理 128 | - 创建/编辑/删除公告 129 | - 设置公告优先级 130 | - 启用/禁用公告 131 | 132 | #### 5. 模型管理 133 | - 从Google获取最新模型列表 134 | - 设置默认模型配额 135 | - 查看每个模型的使用统计 136 | 137 | #### 6. 系统监控 138 | - 实时请求统计 139 | - 系统资源使用 140 | - Token使用情况 141 | - 错误日志查看 142 | 143 | ### 用户中心功能 144 | 145 | 访问 `http://localhost:8045/user.html` 进入用户中心: 146 | 147 | - **账号注册/登录** - 独立的用户系统 148 | - **API密钥管理** - 生成个人API密钥 149 | - **Token管理** - 添加/共享自己的Google Token 150 | - **使用统计** - 查看个人使用情况 151 | - **模型配额** - 查看每日模型使用限额 152 | 153 | ### Token共享中心 154 | 155 | 访问 `http://localhost:8045/share.html` 进入共享中心: 156 | 157 | - **Token列表** - 查看所有共享的Token及可用额度 158 | - **用户统计** - 查看所有用户的使用排行 159 | - 平均日使用量 160 | - 今日使用量 161 | - 最大日使用量 162 | - 使用天数统计 163 | - 异常用户标识 164 | - **投票封禁** - 发起或参与封禁投票 165 | - **投票历史** - 查看所有投票记录 166 | 167 | ### AI自动管理系统 168 | 169 | 在管理后台的"AI 管理"标签页配置: 170 | 171 | #### 配置项 172 | - **启用AI审核** - 开启/关闭自动审核 173 | - **API端点** - AI服务的API地址 174 | - **API密钥** - AI服务的认证密钥 175 | - **模型** - 使用的AI模型(如 gemini-2.0-flash-exp) 176 | - **检查间隔** - 审核频率(1-24小时) 177 | - **置信度阈值** - 自动封禁的最低置信度(0-1) 178 | - **系统提示词** - AI分析的指导规则 179 | 180 | #### AI审核规则 181 | 系统默认分析以下指标: 182 | 1. **使用频率** - 平均每天超过50次为异常 183 | 2. **使用模式** - 短时间内大量请求 184 | 3. **时间分布** - 24小时持续高频使用 185 | 4. **突增行为** - 使用量突然大幅增加 186 | 187 | #### 操作功能 188 | - **立即运行审核** - 手动触发一次审核 189 | - **查看审核日志** - 显示最新50条审核记录 190 | - **统计信息** - 总审核次数、封禁数、标记数 191 | 192 | ## API 使用 193 | 194 | ### 获取模型列表 195 | 196 | ```bash 197 | curl http://localhost:8045/v1/models \ 198 | -H "Authorization: Bearer sk-text" 199 | ``` 200 | 201 | ### 聊天补全(流式) 202 | 203 | ```bash 204 | curl http://localhost:8045/v1/chat/completions \ 205 | -H "Content-Type: application/json" \ 206 | -H "Authorization: Bearer sk-text" \ 207 | -d '{ 208 | "model": "gemini-2.0-flash-exp", 209 | "messages": [{"role": "user", "content": "你好"}], 210 | "stream": true 211 | }' 212 | ``` 213 | 214 | ### 工具调用示例 215 | 216 | ```bash 217 | curl http://localhost:8045/v1/chat/completions \ 218 | -H "Content-Type: application/json" \ 219 | -H "Authorization: Bearer sk-text" \ 220 | -d '{ 221 | "model": "gemini-2.0-flash-exp", 222 | "messages": [{"role": "user", "content": "北京天气怎么样"}], 223 | "tools": [{ 224 | "type": "function", 225 | "function": { 226 | "name": "get_weather", 227 | "description": "获取天气信息", 228 | "parameters": { 229 | "type": "object", 230 | "properties": { 231 | "location": {"type": "string", "description": "城市名称"} 232 | } 233 | } 234 | } 235 | }] 236 | }' 237 | ``` 238 | 239 | ### 图片输入示例 240 | 241 | ```bash 242 | curl http://localhost:8045/v1/chat/completions \ 243 | -H "Content-Type: application/json" \ 244 | -H "Authorization: Bearer sk-text" \ 245 | -d '{ 246 | "model": "gemini-2.0-flash-exp", 247 | "messages": [{ 248 | "role": "user", 249 | "content": [ 250 | {"type": "text", "text": "这张图片里有什么?"}, 251 | { 252 | "type": "image_url", 253 | "image_url": { 254 | "url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..." 255 | } 256 | } 257 | ] 258 | }], 259 | "stream": true 260 | }' 261 | ``` 262 | 263 | 支持的图片格式:JPEG、PNG、GIF、WebP 264 | 265 | ## 项目结构 266 | 267 | ``` 268 | . 269 | ├── data/ # 数据目录(自动生成) 270 | │ ├── accounts.json # Token 存储 271 | │ ├── users.json # 用户数据 272 | │ ├── share_data.json # 共享系统数据 273 | │ ├── ai_config.json # AI配置 274 | │ └── ai_moderation_logs.json # AI审核日志 275 | ├── public/ # 前端页面 276 | │ ├── index.html # 管理后台 277 | │ ├── user.html # 用户中心 278 | │ └── share.html # 共享中心 279 | ├── scripts/ 280 | │ └── oauth-server.js # OAuth 登录服务 281 | ├── src/ 282 | │ ├── admin/ # 管理模块 283 | │ │ ├── routes.js # 管理路由 284 | │ │ ├── user_manager.js # 用户管理 285 | │ │ ├── share_manager.js # 共享管理 286 | │ │ ├── ai_moderator.js # AI自动管理 287 | │ │ ├── key_manager.js # 密钥管理 288 | │ │ ├── model_manager.js # 模型管理 289 | │ │ └── ... # 其他管理模块 290 | │ ├── api/ 291 | │ │ └── client.js # API 调用逻辑 292 | │ ├── auth/ 293 | │ │ └── token_manager.js # Token 管理 294 | │ ├── config/ 295 | │ │ └── config.js # 配置加载 296 | │ ├── server/ 297 | │ │ └── index.js # 主服务器 298 | │ └── utils/ 299 | │ ├── logger.js # 日志模块 300 | │ └── utils.js # 工具函数 301 | ├── config.json # 配置文件 302 | └── package.json # 项目配置 303 | ``` 304 | 305 | ## 安全特性 306 | 307 | ### 用户安全 308 | - 密码加密存储(PBKDF2) 309 | - 会话Token管理 310 | - API密钥认证 311 | - 频率限制保护 312 | 313 | ### 防滥用机制 314 | - IP注册限制(每IP每天最多注册数) 315 | - 设备注册限制(基于指纹识别) 316 | - 自动清理长期未登录账号 317 | - 共享使用量监控 318 | 319 | ### AI自动防护 320 | - 智能检测异常使用模式 321 | - 自动封禁滥用账号 322 | - 渐进式封禁时长(1天→3天→7天→14天→30天→90天) 323 | - 社区投票机制 324 | 325 | ## 配置说明 326 | 327 | ### config.json 完整配置 328 | 329 | ```json 330 | { 331 | "server": { 332 | "port": 8045, 333 | "host": "0.0.0.0" 334 | }, 335 | "security": { 336 | "apiKey": "sk-text", 337 | "adminPassword": "admin123", 338 | "maxRequestSize": "50mb" 339 | }, 340 | "defaults": { 341 | "temperature": 1, 342 | "top_p": 0.85, 343 | "top_k": 50, 344 | "max_tokens": 8096 345 | }, 346 | "systemInstruction": "你是一个有帮助的AI助手" 347 | } 348 | ``` 349 | 350 | | 配置项 | 说明 | 默认值 | 351 | |--------|------|--------| 352 | | `server.port` | 服务端口 | 8045 | 353 | | `server.host` | 监听地址 | 0.0.0.0 | 354 | | `security.apiKey` | 管理员API密钥 | sk-text | 355 | | `security.adminPassword` | 管理后台密码 | admin123 | 356 | | `security.maxRequestSize` | 最大请求体大小 | 50mb | 357 | | `defaults.temperature` | 默认温度参数 | 1 | 358 | | `defaults.top_p` | 默认 top_p | 0.85 | 359 | | `defaults.top_k` | 默认 top_k | 50 | 360 | | `defaults.max_tokens` | 默认最大token数 | 8096 | 361 | | `systemInstruction` | 系统提示词 | - | 362 | 363 | ## 开发命令 364 | 365 | ```bash 366 | # 启动服务 367 | npm start 368 | 369 | # 开发模式(自动重启) 370 | npm run dev 371 | 372 | # 登录获取 Token 373 | npm run login 374 | ``` 375 | 376 | ## 常见问题 377 | 378 | ### 1. 如何获取Google Token? 379 | 380 | 运行 `npm run login` 启动OAuth服务器,在浏览器中完成Google登录。 381 | 382 | ### 2. 如何配置AI自动管理? 383 | 384 | 1. 访问管理后台的"AI 管理"标签 385 | 2. 配置API端点(如本地服务地址) 386 | 3. 设置API密钥 387 | 4. 调整置信度阈值和检查间隔 388 | 5. 启用AI审核 389 | 390 | ### 3. 用户如何共享Token? 391 | 392 | 1. 用户登录用户中心 393 | 2. 在"我的Token"中添加Google Token 394 | 3. 勾选"共享此Token" 395 | 4. Token会出现在共享中心供其他用户使用 396 | 397 | ### 4. 如何处理滥用用户? 398 | 399 | 系统提供三种方式: 400 | - **自动封禁** - AI检测到异常自动封禁 401 | - **手动封禁** - 在用户管理中手动操作 402 | - **投票封禁** - 社区投票决定是否封禁 403 | 404 | ### 5. 封禁时长规则是什么? 405 | 406 | 采用渐进式封禁: 407 | - 第1次:1天 408 | - 第2次:3天 409 | - 第3次:7天 410 | - 第4次:14天 411 | - 第5次:30天 412 | - 第6次及以后:90天 413 | 414 | ## 注意事项 415 | 416 | 1. 首次使用需要运行 `npm run login` 获取 Token 417 | 2. `data/` 目录包含敏感信息,请勿泄露 418 | 3. 建议修改默认的管理员密码 419 | 4. AI自动管理需要配置有效的AI服务端点 420 | 5. 共享Token的用户需自行承担配额使用 421 | 6. 定期查看AI审核日志,确保系统正常运行 422 | 423 | ## 许可证 424 | 425 | MIT License 426 | 427 | ## 贡献 428 | 429 | 欢迎提交 Issue 和 Pull Request! 430 | 431 | ## 更新日志 432 | 433 | ### v2.0.0 434 | - ✨ 新增完整的Web管理后台 435 | - ✨ 新增用户系统和用户中心 436 | - ✨ 新增Token共享系统 437 | - ✨ 新增AI自动管理功能 438 | - ✨ 新增社区投票封禁机制 439 | - ✨ 新增用户使用统计展示 440 | - 🔧 优化Token轮换机制 441 | - 🔧 完善安全防护措施 442 | 443 | ### v1.0.0 444 | - 🎉 初始版本发布 445 | - ✅ OpenAI API兼容 446 | - ✅ 流式响应支持 447 | - ✅ 工具调用支持 448 | -------------------------------------------------------------------------------- /Antigravity/src/admin/token_admin.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import AdmZip from 'adm-zip'; 3 | import path from 'path'; 4 | import logger from '../utils/logger.js'; 5 | import config from '../config/config.js'; 6 | import crypto from 'crypto'; 7 | 8 | const ACCOUNTS_FILE = path.join(process.cwd(), 'data', 'accounts.json'); 9 | 10 | // 读取所有账号 11 | export async function loadAccounts() { 12 | try { 13 | const data = await fs.readFile(ACCOUNTS_FILE, 'utf-8'); 14 | return JSON.parse(data); 15 | } catch (error) { 16 | if (error.code === 'ENOENT') { 17 | return []; 18 | } 19 | throw error; 20 | } 21 | } 22 | 23 | // 保存账号 24 | async function saveAccounts(accounts) { 25 | const dir = path.dirname(ACCOUNTS_FILE); 26 | try { 27 | await fs.access(dir); 28 | } catch { 29 | await fs.mkdir(dir, { recursive: true }); 30 | } 31 | await fs.writeFile(ACCOUNTS_FILE, JSON.stringify(accounts, null, 2), 'utf-8'); 32 | } 33 | 34 | // 删除账号 35 | export async function deleteAccount(index) { 36 | const accounts = await loadAccounts(); 37 | if (index < 0 || index >= accounts.length) { 38 | throw new Error('无效的账号索引'); 39 | } 40 | accounts.splice(index, 1); 41 | await saveAccounts(accounts); 42 | logger.info(`账号 ${index} 已删除`); 43 | return true; 44 | } 45 | 46 | // 启用/禁用账号 47 | export async function toggleAccount(index, enable) { 48 | const accounts = await loadAccounts(); 49 | if (index < 0 || index >= accounts.length) { 50 | throw new Error('无效的账号索引'); 51 | } 52 | accounts[index].enable = enable; 53 | await saveAccounts(accounts); 54 | logger.info(`账号 ${index} 已${enable ? '启用' : '禁用'}`); 55 | return true; 56 | } 57 | 58 | // 触发登录流程 59 | export async function triggerLogin(customRedirectUri = null, customState = null) { 60 | logger.info('生成 Google OAuth 授权 URL...'); 61 | 62 | // 检查 OAuth 配置 63 | if (!config.oauth || !config.oauth.clientId) { 64 | throw new Error('OAuth 配置未设置,请在系统设置中配置 Google OAuth'); 65 | } 66 | 67 | const clientId = config.oauth.clientId; 68 | // 如果提供了自定义 redirect_uri,使用它;否则使用默认值 69 | const redirectUri = customRedirectUri || 'http://localhost:8099/oauth-callback'; 70 | // 如果提供了自定义 state(包含用户信息),使用它;否则生成随机 UUID 71 | const state = customState || crypto.randomUUID(); 72 | const scopes = [ 73 | 'https://www.googleapis.com/auth/cloud-platform', 74 | 'https://www.googleapis.com/auth/userinfo.email', 75 | 'https://www.googleapis.com/auth/userinfo.profile', 76 | 'https://www.googleapis.com/auth/cclog', 77 | 'https://www.googleapis.com/auth/experimentsandconfigs' 78 | ]; 79 | 80 | const params = new URLSearchParams({ 81 | access_type: 'offline', 82 | client_id: clientId, 83 | prompt: 'consent', 84 | redirect_uri: redirectUri, 85 | response_type: 'code', 86 | scope: scopes.join(' '), 87 | state: state 88 | }); 89 | 90 | const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`; 91 | 92 | logger.info('授权 URL 已生成'); 93 | logger.info('注意:需要在 Google Cloud Console 中添加重定向 URI: ' + redirectUri); 94 | 95 | return { 96 | success: true, 97 | authUrl, 98 | redirectUri, // 返回实际使用的 redirect_uri 99 | message: '请在浏览器中完成 Google 授权' 100 | }; 101 | } 102 | 103 | // 获取账号统计信息 104 | export async function getAccountStats() { 105 | const accounts = await loadAccounts(); 106 | return { 107 | total: accounts.length, 108 | enabled: accounts.filter(a => a.enable !== false).length, 109 | disabled: accounts.filter(a => a.enable === false).length 110 | }; 111 | } 112 | 113 | // 从回调链接手动添加 Token 114 | import https from 'https'; 115 | 116 | const CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com'; 117 | const CLIENT_SECRET = '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'; 118 | 119 | // 获取 Google 账号信息 120 | export async function getAccountName(accessToken) { 121 | return new Promise((resolve, reject) => { 122 | const options = { 123 | hostname: 'www.googleapis.com', 124 | path: '/oauth2/v2/userinfo', 125 | method: 'GET', 126 | headers: { 127 | 'Authorization': `Bearer ${accessToken}` 128 | } 129 | }; 130 | 131 | const req = https.request(options, (res) => { 132 | let body = ''; 133 | res.on('data', chunk => body += chunk); 134 | res.on('end', () => { 135 | if (res.statusCode === 200) { 136 | const data = JSON.parse(body); 137 | resolve({ 138 | email: data.email, 139 | name: data.name || data.email 140 | }); 141 | } else { 142 | resolve({ email: 'Unknown', name: 'Unknown' }); 143 | } 144 | }); 145 | }); 146 | 147 | req.on('error', () => resolve({ email: 'Unknown', name: 'Unknown' })); 148 | req.end(); 149 | }); 150 | } 151 | 152 | export async function addTokenFromCallback(callbackUrl) { 153 | // 解析回调链接 154 | const url = new URL(callbackUrl); 155 | const code = url.searchParams.get('code'); 156 | const port = url.port || '80'; 157 | 158 | if (!code) { 159 | throw new Error('回调链接中没有找到授权码 (code)'); 160 | } 161 | 162 | logger.info(`正在使用授权码换取 Token...`); 163 | 164 | // 使用授权码换取 Token 165 | const tokenData = await exchangeCodeForToken(code, port, url.origin); 166 | 167 | // 保存账号 168 | const account = { 169 | access_token: tokenData.access_token, 170 | refresh_token: tokenData.refresh_token, 171 | expires_in: tokenData.expires_in, 172 | timestamp: Date.now(), 173 | enable: true 174 | }; 175 | 176 | const accounts = await loadAccounts(); 177 | accounts.push(account); 178 | await saveAccounts(accounts); 179 | 180 | logger.info('Token 已成功保存'); 181 | return { success: true, message: 'Token 已成功添加' }; 182 | } 183 | 184 | function exchangeCodeForToken(code, port, origin) { 185 | return new Promise((resolve, reject) => { 186 | const redirectUri = `${origin}/oauth-callback`; 187 | 188 | const postData = new URLSearchParams({ 189 | code: code, 190 | client_id: CLIENT_ID, 191 | client_secret: CLIENT_SECRET, 192 | redirect_uri: redirectUri, 193 | grant_type: 'authorization_code' 194 | }).toString(); 195 | 196 | const options = { 197 | hostname: 'oauth2.googleapis.com', 198 | path: '/token', 199 | method: 'POST', 200 | headers: { 201 | 'Content-Type': 'application/x-www-form-urlencoded', 202 | 'Content-Length': Buffer.byteLength(postData) 203 | } 204 | }; 205 | 206 | const req = https.request(options, (res) => { 207 | let body = ''; 208 | res.on('data', chunk => body += chunk); 209 | res.on('end', () => { 210 | if (res.statusCode === 200) { 211 | resolve(JSON.parse(body)); 212 | } else { 213 | logger.error(`Token 交换失败: ${body}`); 214 | reject(new Error(`Token 交换失败: ${res.statusCode} - ${body}`)); 215 | } 216 | }); 217 | }); 218 | 219 | req.on('error', reject); 220 | req.write(postData); 221 | req.end(); 222 | }); 223 | } 224 | 225 | // 直接添加 Token 226 | export async function addDirectToken(tokenData) { 227 | try { 228 | const { access_token, refresh_token, expires_in } = tokenData; 229 | 230 | // 验证必填字段 231 | if (!access_token) { 232 | throw new Error('access_token 是必填项'); 233 | } 234 | 235 | logger.info('正在添加直接输入的 Token...'); 236 | 237 | // 加载现有账号 238 | const accounts = await loadAccounts(); 239 | 240 | // 检查是否已存在相同的 access_token 241 | const exists = accounts.some(acc => acc.access_token === access_token); 242 | if (exists) { 243 | logger.warn('Token 已存在,跳过添加'); 244 | return { 245 | success: false, 246 | error: '该 Token 已存在于账号列表中' 247 | }; 248 | } 249 | 250 | // 创建新账号 251 | const newAccount = { 252 | access_token, 253 | refresh_token: refresh_token || null, 254 | expires_in: expires_in || 3600, 255 | timestamp: Date.now(), 256 | enable: true 257 | }; 258 | 259 | // 添加到账号列表 260 | accounts.push(newAccount); 261 | 262 | // 保存账号 263 | await saveAccounts(accounts); 264 | 265 | logger.info('Token 添加成功'); 266 | return { 267 | success: true, 268 | message: 'Token 添加成功', 269 | index: accounts.length - 1 270 | }; 271 | } catch (error) { 272 | logger.error('添加 Token 失败:', error); 273 | throw error; 274 | } 275 | } 276 | 277 | // 批量导入 Token 278 | export async function importTokens(filePath) { 279 | try { 280 | logger.info('开始导入 Token...'); 281 | 282 | // 检查是否是 ZIP 文件 283 | if (filePath.endsWith('.zip') || true) { 284 | const zip = new AdmZip(filePath); 285 | const zipEntries = zip.getEntries(); 286 | 287 | // 查找 tokens.json 288 | const tokensEntry = zipEntries.find(entry => entry.entryName === 'tokens.json'); 289 | if (!tokensEntry) { 290 | throw new Error('ZIP 文件中没有找到 tokens.json'); 291 | } 292 | 293 | const tokensContent = tokensEntry.getData().toString('utf8'); 294 | const importedTokens = JSON.parse(tokensContent); 295 | 296 | // 验证数据格式 297 | if (!Array.isArray(importedTokens)) { 298 | throw new Error('tokens.json 格式错误:应该是一个数组'); 299 | } 300 | 301 | // 加载现有账号 302 | const accounts = await loadAccounts(); 303 | 304 | // 添加新账号 305 | let addedCount = 0; 306 | for (const token of importedTokens) { 307 | // 检查是否已存在 308 | const exists = accounts.some(acc => acc.access_token === token.access_token); 309 | if (!exists) { 310 | accounts.push({ 311 | access_token: token.access_token, 312 | refresh_token: token.refresh_token, 313 | expires_in: token.expires_in, 314 | timestamp: token.timestamp || Date.now(), 315 | enable: token.enable !== false 316 | }); 317 | addedCount++; 318 | } 319 | } 320 | 321 | // 保存账号 322 | await saveAccounts(accounts); 323 | 324 | // 清理上传的文件 325 | try { 326 | await fs.unlink(filePath); 327 | } catch (e) { 328 | logger.warn('清理上传文件失败:', e); 329 | } 330 | 331 | logger.info(`成功导入 ${addedCount} 个 Token 账号`); 332 | return { 333 | success: true, 334 | count: addedCount, 335 | total: importedTokens.length, 336 | skipped: importedTokens.length - addedCount, 337 | message: `成功导入 ${addedCount} 个 Token 账号${importedTokens.length - addedCount > 0 ? `,跳过 ${importedTokens.length - addedCount} 个重复账号` : ''}` 338 | }; 339 | } 340 | } catch (error) { 341 | logger.error('导入 Token 失败:', error); 342 | // 清理上传的文件 343 | try { 344 | await fs.unlink(filePath); 345 | } catch (e) {} 346 | throw error; 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /Antigravity/src/admin/share_manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 共享系统管理器 3 | * 处理共享滥用检测、封禁、投票等功能 4 | */ 5 | 6 | import fs from 'fs'; 7 | import path from 'path'; 8 | import crypto from 'crypto'; 9 | import logger from '../utils/logger.js'; 10 | 11 | const SHARE_DATA_FILE = path.join(process.cwd(), 'data', 'share_data.json'); 12 | 13 | // 默认数据结构 14 | const defaultData = { 15 | // 用户共享封禁记录 16 | userBans: {}, 17 | // 用户使用记录(用于计算平均用量) 18 | usageHistory: {}, 19 | // 投票记录 20 | votes: [], 21 | // Token 黑名单(共享者设置的禁止调用列表) 22 | tokenBlacklists: {} 23 | }; 24 | 25 | // 加载共享数据 26 | export async function loadShareData() { 27 | try { 28 | if (fs.existsSync(SHARE_DATA_FILE)) { 29 | const data = JSON.parse(fs.readFileSync(SHARE_DATA_FILE, 'utf-8')); 30 | return { ...defaultData, ...data }; 31 | } 32 | } catch (error) { 33 | logger.error('加载共享数据失败:', error); 34 | } 35 | return { ...defaultData }; 36 | } 37 | 38 | // 保存共享数据 39 | export async function saveShareData(data) { 40 | try { 41 | const dir = path.dirname(SHARE_DATA_FILE); 42 | if (!fs.existsSync(dir)) { 43 | fs.mkdirSync(dir, { recursive: true }); 44 | } 45 | fs.writeFileSync(SHARE_DATA_FILE, JSON.stringify(data, null, 2)); 46 | } catch (error) { 47 | logger.error('保存共享数据失败:', error); 48 | } 49 | } 50 | 51 | // ==================== 用户封禁系统 ==================== 52 | 53 | // 封禁时长配置(毫秒) 54 | const BAN_DURATIONS = [ 55 | 1 * 24 * 60 * 60 * 1000, // 第1次:1天 56 | 3 * 24 * 60 * 60 * 1000, // 第2次:3天 57 | 7 * 24 * 60 * 60 * 1000, // 第3次:7天 58 | 14 * 24 * 60 * 60 * 1000, // 第4次:14天 59 | 30 * 24 * 60 * 60 * 1000, // 第5次:30天 60 | 90 * 24 * 60 * 60 * 1000, // 第6次及以后:90天 61 | ]; 62 | 63 | // 平均用量阈值(超过此值触发封禁) 64 | const USAGE_THRESHOLD = 50; // 每天平均使用超过50次 65 | 66 | // 检查用户是否被封禁使用共享 67 | export async function isUserBanned(userId) { 68 | const data = await loadShareData(); 69 | const ban = data.userBans[userId]; 70 | 71 | if (!ban || !ban.banned) return { banned: false }; 72 | 73 | // 检查封禁是否已过期 74 | if (ban.banUntil && Date.now() > ban.banUntil) { 75 | // 解除封禁 76 | ban.banned = false; 77 | await saveShareData(data); 78 | return { banned: false }; 79 | } 80 | 81 | return { 82 | banned: true, 83 | banUntil: ban.banUntil, 84 | banCount: ban.banCount, 85 | reason: ban.reason, 86 | remainingTime: ban.banUntil - Date.now() 87 | }; 88 | } 89 | 90 | // 封禁用户使用共享 91 | export async function banUserFromSharing(userId, reason = '滥用共享资源') { 92 | const data = await loadShareData(); 93 | 94 | if (!data.userBans[userId]) { 95 | data.userBans[userId] = { banCount: 0 }; 96 | } 97 | 98 | const ban = data.userBans[userId]; 99 | ban.banCount++; 100 | ban.banned = true; 101 | ban.reason = reason; 102 | ban.lastBanTime = Date.now(); 103 | 104 | // 计算封禁时长 105 | const durationIndex = Math.min(ban.banCount - 1, BAN_DURATIONS.length - 1); 106 | const duration = BAN_DURATIONS[durationIndex]; 107 | ban.banUntil = Date.now() + duration; 108 | 109 | await saveShareData(data); 110 | 111 | const durationDays = Math.round(duration / (24 * 60 * 60 * 1000)); 112 | logger.info(`用户 ${userId} 被封禁使用共享 ${durationDays} 天,原因: ${reason}`); 113 | 114 | return { 115 | banCount: ban.banCount, 116 | banUntil: ban.banUntil, 117 | durationDays 118 | }; 119 | } 120 | 121 | // 解除封禁 122 | export async function unbanUser(userId) { 123 | const data = await loadShareData(); 124 | if (data.userBans[userId]) { 125 | data.userBans[userId].banned = false; 126 | await saveShareData(data); 127 | logger.info(`用户 ${userId} 的共享封禁已解除`); 128 | } 129 | return true; 130 | } 131 | 132 | // 记录用户使用共享 133 | export async function recordShareUsage(userId) { 134 | const data = await loadShareData(); 135 | const today = new Date().toDateString(); 136 | 137 | if (!data.usageHistory[userId]) { 138 | data.usageHistory[userId] = { dailyUsage: {}, totalDays: 0 }; 139 | } 140 | 141 | const history = data.usageHistory[userId]; 142 | if (!history.dailyUsage[today]) { 143 | history.dailyUsage[today] = 0; 144 | history.totalDays++; 145 | } 146 | history.dailyUsage[today]++; 147 | 148 | // 只保留最近30天的记录 149 | const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000; 150 | for (const date in history.dailyUsage) { 151 | if (new Date(date).getTime() < thirtyDaysAgo) { 152 | delete history.dailyUsage[date]; 153 | } 154 | } 155 | 156 | await saveShareData(data); 157 | 158 | return history.dailyUsage[today]; 159 | } 160 | 161 | // 获取用户平均使用量 162 | export async function getUserAverageUsage(userId) { 163 | const data = await loadShareData(); 164 | const history = data.usageHistory[userId]; 165 | 166 | if (!history || !history.dailyUsage) return 0; 167 | 168 | const days = Object.keys(history.dailyUsage); 169 | if (days.length === 0) return 0; 170 | 171 | const total = Object.values(history.dailyUsage).reduce((sum, v) => sum + v, 0); 172 | return Math.round(total / days.length); 173 | } 174 | 175 | // 检查并执行滥用封禁 176 | export async function checkAndBanAbuser(userId) { 177 | const avgUsage = await getUserAverageUsage(userId); 178 | 179 | if (avgUsage > USAGE_THRESHOLD) { 180 | const result = await banUserFromSharing(userId, `平均用量过高 (${avgUsage}次/天)`); 181 | return { 182 | banned: true, 183 | avgUsage, 184 | ...result 185 | }; 186 | } 187 | 188 | return { banned: false, avgUsage }; 189 | } 190 | 191 | // ==================== Token 黑名单系统 ==================== 192 | 193 | // 将用户添加到 Token 的黑名单 194 | export async function addToTokenBlacklist(ownerId, tokenIndex, targetUserId) { 195 | const data = await loadShareData(); 196 | const key = `${ownerId}_${tokenIndex}`; 197 | 198 | if (!data.tokenBlacklists[key]) { 199 | data.tokenBlacklists[key] = []; 200 | } 201 | 202 | if (!data.tokenBlacklists[key].includes(targetUserId)) { 203 | data.tokenBlacklists[key].push(targetUserId); 204 | await saveShareData(data); 205 | logger.info(`用户 ${targetUserId} 被添加到 ${ownerId} 的 Token #${tokenIndex} 黑名单`); 206 | } 207 | 208 | return data.tokenBlacklists[key]; 209 | } 210 | 211 | // 从 Token 黑名单移除用户 212 | export async function removeFromTokenBlacklist(ownerId, tokenIndex, targetUserId) { 213 | const data = await loadShareData(); 214 | const key = `${ownerId}_${tokenIndex}`; 215 | 216 | if (data.tokenBlacklists[key]) { 217 | data.tokenBlacklists[key] = data.tokenBlacklists[key].filter(id => id !== targetUserId); 218 | await saveShareData(data); 219 | } 220 | 221 | return data.tokenBlacklists[key] || []; 222 | } 223 | 224 | // 检查用户是否在 Token 黑名单中 225 | export async function isUserBlacklisted(ownerId, tokenIndex, userId) { 226 | const data = await loadShareData(); 227 | const key = `${ownerId}_${tokenIndex}`; 228 | 229 | return data.tokenBlacklists[key]?.includes(userId) || false; 230 | } 231 | 232 | // 获取 Token 的黑名单 233 | export async function getTokenBlacklist(ownerId, tokenIndex) { 234 | const data = await loadShareData(); 235 | const key = `${ownerId}_${tokenIndex}`; 236 | return data.tokenBlacklists[key] || []; 237 | } 238 | 239 | // ==================== 投票封禁系统 ==================== 240 | 241 | // 创建投票 242 | export async function createVote(targetUserId, reason, createdBy) { 243 | const data = await loadShareData(); 244 | 245 | // 检查是否已有针对该用户的活跃投票 246 | const existingVote = data.votes.find(v => 247 | v.targetUserId === targetUserId && v.status === 'active' 248 | ); 249 | if (existingVote) { 250 | return { error: '已存在针对该用户的投票', existingVote }; 251 | } 252 | 253 | const vote = { 254 | id: `vote_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`, 255 | targetUserId, 256 | reason, 257 | createdBy, 258 | createdAt: Date.now(), 259 | expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24小时后过期 260 | votes: {}, // { userId: 'ban' | 'unban' } 261 | comments: [], // [{ userId, content, time }] 262 | status: 'active' // 'active' | 'passed' | 'rejected' | 'expired' 263 | }; 264 | 265 | data.votes.push(vote); 266 | await saveShareData(data); 267 | 268 | logger.info(`用户 ${createdBy} 发起了对 ${targetUserId} 的封禁投票`); 269 | 270 | return { success: true, vote }; 271 | } 272 | 273 | // 投票 274 | export async function castVote(voteId, userId, decision) { 275 | const data = await loadShareData(); 276 | const vote = data.votes.find(v => v.id === voteId); 277 | 278 | if (!vote) return { error: '投票不存在' }; 279 | if (vote.status !== 'active') return { error: '投票已结束' }; 280 | if (Date.now() > vote.expiresAt) { 281 | vote.status = 'expired'; 282 | await saveShareData(data); 283 | return { error: '投票已过期' }; 284 | } 285 | if (vote.targetUserId === userId) return { error: '不能对自己投票' }; 286 | 287 | vote.votes[userId] = decision; // 'ban' 或 'unban' 288 | await saveShareData(data); 289 | 290 | return { success: true, vote }; 291 | } 292 | 293 | // 添加评论 294 | export async function addVoteComment(voteId, userId, content) { 295 | const data = await loadShareData(); 296 | const vote = data.votes.find(v => v.id === voteId); 297 | 298 | if (!vote) return { error: '投票不存在' }; 299 | 300 | vote.comments.push({ 301 | userId, 302 | content, 303 | time: Date.now() 304 | }); 305 | 306 | await saveShareData(data); 307 | return { success: true, vote }; 308 | } 309 | 310 | // 获取投票结果并处理 311 | export async function processVoteResult(voteId) { 312 | const data = await loadShareData(); 313 | const vote = data.votes.find(v => v.id === voteId); 314 | 315 | if (!vote) return { error: '投票不存在' }; 316 | if (vote.status !== 'active') return { status: vote.status }; 317 | 318 | // 检查是否到期 319 | if (Date.now() < vote.expiresAt) { 320 | return { error: '投票尚未结束', remainingTime: vote.expiresAt - Date.now() }; 321 | } 322 | 323 | // 计算投票结果 324 | const voteValues = Object.values(vote.votes); 325 | const banVotes = voteValues.filter(v => v === 'ban').length; 326 | const unbanVotes = voteValues.filter(v => v === 'unban').length; 327 | 328 | // 需要至少3票且封禁票数超过半数才通过 329 | if (voteValues.length >= 3 && banVotes > voteValues.length / 2) { 330 | vote.status = 'passed'; 331 | // 执行封禁 332 | await banUserFromSharing(vote.targetUserId, `社区投票封禁 (${banVotes}/${voteValues.length})`); 333 | } else { 334 | vote.status = 'rejected'; 335 | } 336 | 337 | await saveShareData(data); 338 | 339 | return { 340 | status: vote.status, 341 | banVotes, 342 | unbanVotes, 343 | totalVotes: voteValues.length 344 | }; 345 | } 346 | 347 | // 获取所有活跃投票 348 | export async function getActiveVotes() { 349 | const data = await loadShareData(); 350 | const now = Date.now(); 351 | 352 | // 处理过期的投票 353 | for (const vote of data.votes) { 354 | if (vote.status === 'active' && now > vote.expiresAt) { 355 | await processVoteResult(vote.id); 356 | } 357 | } 358 | 359 | return data.votes.filter(v => v.status === 'active'); 360 | } 361 | 362 | // 获取投票详情 363 | export async function getVoteById(voteId) { 364 | const data = await loadShareData(); 365 | return data.votes.find(v => v.id === voteId); 366 | } 367 | 368 | // 获取用户的投票历史 369 | export async function getUserVoteHistory(userId) { 370 | const data = await loadShareData(); 371 | return data.votes.filter(v => v.targetUserId === userId); 372 | } 373 | 374 | // 获取所有投票(包括历史) 375 | export async function getAllVotes() { 376 | const data = await loadShareData(); 377 | return data.votes; 378 | } 379 | 380 | // 获取用户共享状态摘要 381 | export async function getUserShareStatus(userId) { 382 | const banStatus = await isUserBanned(userId); 383 | const avgUsage = await getUserAverageUsage(userId); 384 | const data = await loadShareData(); 385 | 386 | // 获取针对该用户的投票 387 | const activeVotes = data.votes.filter(v => 388 | v.targetUserId === userId && v.status === 'active' 389 | ); 390 | 391 | return { 392 | ...banStatus, 393 | avgUsage, 394 | activeVotes: activeVotes.length, 395 | usageHistory: data.usageHistory[userId]?.dailyUsage || {} 396 | }; 397 | } 398 | -------------------------------------------------------------------------------- /Antigravity/src/server/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import { fileURLToPath } from 'url'; 5 | import { generateAssistantResponse, getAvailableModels } from '../api/client.js'; 6 | import { generateRequestBody } from '../utils/utils.js'; 7 | import logger from '../utils/logger.js'; 8 | import config from '../config/config.js'; 9 | import adminRoutes, { incrementRequestCount, addLog, checkModelQuota, recordModelUsage } from '../admin/routes.js'; 10 | import { validateKey, checkRateLimit } from '../admin/key_manager.js'; 11 | import { validateUserApiKey, getUserById, startInactiveUsersCleanup, stopInactiveUsersCleanup } from '../admin/user_manager.js'; 12 | import { startAIScheduler, stopAIScheduler } from '../admin/ai_moderator.js'; 13 | import idleManager from '../utils/idle_manager.js'; 14 | 15 | const __filename = fileURLToPath(import.meta.url); 16 | const __dirname = path.dirname(__filename); 17 | 18 | // 确保必要的目录存在 19 | const ensureDirectories = () => { 20 | const dirs = ['data', 'uploads']; 21 | dirs.forEach(dir => { 22 | const dirPath = path.join(process.cwd(), dir); 23 | if (!fs.existsSync(dirPath)) { 24 | fs.mkdirSync(dirPath, { recursive: true }); 25 | logger.info(`创建目录: ${dir}`); 26 | } 27 | }); 28 | }; 29 | 30 | ensureDirectories(); 31 | 32 | const app = express(); 33 | 34 | app.use(express.json({ limit: config.security.maxRequestSize })); 35 | 36 | // 静态文件服务 - 提供管理控制台页面 37 | app.use(express.static(path.join(process.cwd(), 'public'))); 38 | 39 | app.use((err, req, res, next) => { 40 | if (err.type === 'entity.too.large') { 41 | return res.status(413).json({ error: `请求体过大,最大支持 ${config.security.maxRequestSize}` }); 42 | } 43 | next(err); 44 | }); 45 | 46 | // 请求日志中间件 47 | app.use((req, res, next) => { 48 | // 记录请求活动,管理空闲状态 49 | if (req.path.startsWith('/v1/')) { 50 | idleManager.recordActivity(); 51 | } 52 | 53 | const start = Date.now(); 54 | res.on('finish', () => { 55 | const duration = Date.now() - start; 56 | logger.request(req.method, req.path, res.statusCode, duration); 57 | 58 | // 记录到管理日志 59 | if (req.path.startsWith('/v1/')) { 60 | incrementRequestCount(); 61 | addLog('info', `${req.method} ${req.path} - ${res.statusCode} (${duration}ms)`); 62 | } 63 | }); 64 | next(); 65 | }); 66 | 67 | // API 密钥验证和频率限制中间件 68 | app.use(async (req, res, next) => { 69 | if (req.path.startsWith('/v1/')) { 70 | const apiKey = config.security?.apiKey; 71 | if (apiKey) { 72 | const authHeader = req.headers.authorization; 73 | const providedKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : authHeader; 74 | 75 | // 先检查配置文件中的密钥(不受频率限制) 76 | if (providedKey === apiKey) { 77 | req.tokenSource = { type: 'admin' }; 78 | return next(); 79 | } 80 | 81 | // 再检查管理员数据库中的密钥 82 | const isValid = await validateKey(providedKey); 83 | if (isValid) { 84 | // 检查频率限制 85 | const rateLimitCheck = await checkRateLimit(providedKey); 86 | if (!rateLimitCheck.allowed) { 87 | logger.warn(`频率限制: ${req.method} ${req.path} - ${rateLimitCheck.error}`); 88 | await addLog('warn', `频率限制触发: ${providedKey.substring(0, 10)}...`); 89 | 90 | res.setHeader('X-RateLimit-Limit', rateLimitCheck.limit || 0); 91 | res.setHeader('X-RateLimit-Remaining', 0); 92 | res.setHeader('X-RateLimit-Reset', rateLimitCheck.resetIn || 0); 93 | 94 | return res.status(429).json({ 95 | error: { 96 | message: rateLimitCheck.error, 97 | type: 'rate_limit_exceeded', 98 | reset_in_seconds: rateLimitCheck.resetIn 99 | } 100 | }); 101 | } 102 | 103 | // 设置频率限制响应头 104 | if (rateLimitCheck.limit) { 105 | res.setHeader('X-RateLimit-Limit', rateLimitCheck.limit); 106 | res.setHeader('X-RateLimit-Remaining', rateLimitCheck.remaining); 107 | } 108 | req.tokenSource = { type: 'admin' }; 109 | return next(); 110 | } 111 | 112 | // 检查用户 API 密钥 113 | const userKeyResult = await validateUserApiKey(providedKey); 114 | if (userKeyResult.valid) { 115 | req.tokenSource = { type: 'user', userId: userKeyResult.userId }; 116 | return next(); 117 | } 118 | 119 | // 都没有找到有效密钥 120 | logger.warn(`API Key 验证失败: ${req.method} ${req.path}`); 121 | await addLog('warn', `API Key 验证失败: ${req.method} ${req.path}`); 122 | return res.status(401).json({ error: 'Invalid API Key' }); 123 | } 124 | } 125 | next(); 126 | }); 127 | 128 | // 管理路由 129 | app.use('/admin', adminRoutes); 130 | 131 | app.get('/v1/models', async (req, res) => { 132 | try { 133 | const models = await getAvailableModels(req.tokenSource); 134 | res.json(models); 135 | } catch (error) { 136 | logger.error('获取模型列表失败:', error.message); 137 | res.status(500).json({ error: error.message }); 138 | } 139 | }); 140 | 141 | app.post('/v1/chat/completions', async (req, res) => { 142 | let { messages, model, stream = true, tools, ...params} = req.body; 143 | try { 144 | 145 | if (!messages) { 146 | return res.status(400).json({ error: 'messages is required' }); 147 | } 148 | 149 | // 如果是用户 API Key,注入用户的系统提示词并检查模型配额 150 | if (req.tokenSource && req.tokenSource.type === 'user') { 151 | try { 152 | const user = await getUserById(req.tokenSource.userId); 153 | if (user && user.systemPrompt) { 154 | // 检查是否已经有系统消息 155 | const hasSystemMessage = messages.some(msg => msg.role === 'system'); 156 | 157 | if (!hasSystemMessage) { 158 | // 在消息数组开头添加系统提示词 159 | messages = [ 160 | { role: 'system', content: user.systemPrompt }, 161 | ...messages 162 | ]; 163 | } else { 164 | // 如果已有系统消息,将用户的系统提示词追加到第一个系统消息 165 | const systemMsgIndex = messages.findIndex(msg => msg.role === 'system'); 166 | messages[systemMsgIndex].content = user.systemPrompt + '\n\n' + messages[systemMsgIndex].content; 167 | } 168 | } 169 | } catch (error) { 170 | logger.warn(`获取用户系统提示词失败: ${error.message}`); 171 | // 继续执行,不影响正常请求 172 | } 173 | 174 | // 检查模型配额 175 | try { 176 | const quotaCheck = await checkModelQuota(req.tokenSource.userId, model || 'gemini-2.0-flash-exp'); 177 | if (!quotaCheck.allowed) { 178 | logger.warn(`用户 ${req.tokenSource.userId} 模型 ${model} 配额已用尽`); 179 | return res.status(429).json({ 180 | error: { 181 | message: quotaCheck.error || `模型 ${model} 今日配额已用尽,剩余: ${quotaCheck.remaining}/${quotaCheck.quota}`, 182 | type: 'quota_exceeded', 183 | quota: quotaCheck.quota, 184 | used: quotaCheck.used, 185 | remaining: quotaCheck.remaining 186 | } 187 | }); 188 | } 189 | } catch (error) { 190 | logger.warn(`检查模型配额失败: ${error.message}`); 191 | // 继续执行,不影响正常请求 192 | } 193 | } 194 | 195 | const requestBody = generateRequestBody(messages, model, params, tools); 196 | //console.log(JSON.stringify(requestBody,null,2)); 197 | 198 | if (stream) { 199 | res.setHeader('Content-Type', 'text/event-stream'); 200 | res.setHeader('Cache-Control', 'no-cache'); 201 | res.setHeader('Connection', 'keep-alive'); 202 | 203 | const id = `chatcmpl-${Date.now()}`; 204 | const created = Math.floor(Date.now() / 1000); 205 | let hasToolCall = false; 206 | 207 | await generateAssistantResponse(requestBody, req.tokenSource, (data) => { 208 | if (data.type === 'tool_calls') { 209 | hasToolCall = true; 210 | res.write(`data: ${JSON.stringify({ 211 | id, 212 | object: 'chat.completion.chunk', 213 | created, 214 | model, 215 | choices: [{ index: 0, delta: { tool_calls: data.tool_calls }, finish_reason: null }] 216 | })}\n\n`); 217 | } else { 218 | res.write(`data: ${JSON.stringify({ 219 | id, 220 | object: 'chat.completion.chunk', 221 | created, 222 | model, 223 | choices: [{ index: 0, delta: { content: data.content }, finish_reason: null }] 224 | })}\n\n`); 225 | } 226 | }); 227 | 228 | res.write(`data: ${JSON.stringify({ 229 | id, 230 | object: 'chat.completion.chunk', 231 | created, 232 | model, 233 | choices: [{ index: 0, delta: {}, finish_reason: hasToolCall ? 'tool_calls' : 'stop' }] 234 | })}\n\n`); 235 | res.write('data: [DONE]\n\n'); 236 | res.end(); 237 | 238 | // 记录模型使用(用户 API Key) 239 | if (req.tokenSource && req.tokenSource.type === 'user') { 240 | try { 241 | await recordModelUsage(req.tokenSource.userId, model || 'gemini-2.0-flash-exp'); 242 | } catch (error) { 243 | logger.warn(`记录模型使用失败: ${error.message}`); 244 | } 245 | } 246 | } else { 247 | let fullContent = ''; 248 | let toolCalls = []; 249 | await generateAssistantResponse(requestBody, req.tokenSource, (data) => { 250 | if (data.type === 'tool_calls') { 251 | toolCalls = data.tool_calls; 252 | } else { 253 | fullContent += data.content; 254 | } 255 | }); 256 | 257 | const message = { role: 'assistant', content: fullContent }; 258 | if (toolCalls.length > 0) { 259 | message.tool_calls = toolCalls; 260 | } 261 | 262 | res.json({ 263 | id: `chatcmpl-${Date.now()}`, 264 | object: 'chat.completion', 265 | created: Math.floor(Date.now() / 1000), 266 | model, 267 | choices: [{ 268 | index: 0, 269 | message, 270 | finish_reason: toolCalls.length > 0 ? 'tool_calls' : 'stop' 271 | }] 272 | }); 273 | 274 | // 记录模型使用(用户 API Key) 275 | if (req.tokenSource && req.tokenSource.type === 'user') { 276 | try { 277 | await recordModelUsage(req.tokenSource.userId, model || 'gemini-2.0-flash-exp'); 278 | } catch (error) { 279 | logger.warn(`记录模型使用失败: ${error.message}`); 280 | } 281 | } 282 | } 283 | } catch (error) { 284 | logger.error('生成响应失败:', error.message); 285 | if (!res.headersSent) { 286 | if (stream) { 287 | res.setHeader('Content-Type', 'text/event-stream'); 288 | res.setHeader('Cache-Control', 'no-cache'); 289 | res.setHeader('Connection', 'keep-alive'); 290 | const id = `chatcmpl-${Date.now()}`; 291 | const created = Math.floor(Date.now() / 1000); 292 | res.write(`data: ${JSON.stringify({ 293 | id, 294 | object: 'chat.completion.chunk', 295 | created, 296 | model, 297 | choices: [{ index: 0, delta: { content: `错误: ${error.message}` }, finish_reason: null }] 298 | })}\n\n`); 299 | res.write(`data: ${JSON.stringify({ 300 | id, 301 | object: 'chat.completion.chunk', 302 | created, 303 | model, 304 | choices: [{ index: 0, delta: {}, finish_reason: 'stop' }] 305 | })}\n\n`); 306 | res.write('data: [DONE]\n\n'); 307 | res.end(); 308 | } else { 309 | res.status(500).json({ error: error.message }); 310 | } 311 | } 312 | } 313 | }); 314 | 315 | const server = app.listen(config.server.port, config.server.host, () => { 316 | logger.info(`服务器已启动: ${config.server.host}:${config.server.port}`); 317 | 318 | // 启动账号自动清理任务 319 | startInactiveUsersCleanup(); 320 | 321 | // 启动AI自动审核调度器 322 | startAIScheduler(); 323 | }); 324 | 325 | server.on('error', (error) => { 326 | if (error.code === 'EADDRINUSE') { 327 | logger.error(`端口 ${config.server.port} 已被占用`); 328 | process.exit(1); 329 | } else if (error.code === 'EACCES') { 330 | logger.error(`端口 ${config.server.port} 无权限访问`); 331 | process.exit(1); 332 | } else { 333 | logger.error('服务器启动失败:', error.message); 334 | process.exit(1); 335 | } 336 | }); 337 | 338 | const shutdown = () => { 339 | logger.info('正在关闭服务器...'); 340 | 341 | // 停止账号自动清理任务 342 | stopInactiveUsersCleanup(); 343 | 344 | // 停止AI调度器 345 | stopAIScheduler(); 346 | 347 | // 清理空闲管理器 348 | idleManager.destroy(); 349 | 350 | server.close(() => { 351 | logger.info('服务器已关闭'); 352 | process.exit(0); 353 | }); 354 | setTimeout(() => process.exit(0), 5000); 355 | }; 356 | 357 | process.on('SIGINT', shutdown); 358 | process.on('SIGTERM', shutdown); 359 | -------------------------------------------------------------------------------- /Antigravity/src/admin/ai_moderator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AI 自动管理系统 3 | * 使用AI自动分析共享中心玩家数据,检测异常行为并自动封禁 4 | */ 5 | 6 | import fs from 'fs/promises'; 7 | import path from 'path'; 8 | import log from '../utils/logger.js'; 9 | import * as shareManager from './share_manager.js'; 10 | import { loadUsers } from './user_manager.js'; 11 | 12 | const AI_CONFIG_FILE = path.join(process.cwd(), 'data', 'ai_config.json'); 13 | const AI_LOGS_FILE = path.join(process.cwd(), 'data', 'ai_moderation_logs.json'); 14 | 15 | // 默认AI配置 16 | const defaultConfig = { 17 | enabled: false, 18 | apiKey: '', 19 | apiEndpoint: '', 20 | model: 'gemini-2.0-flash-exp', 21 | checkIntervalHours: 1, 22 | autoModerateThreshold: 0.8, // AI判断置信度阈值(0-1) 23 | systemPrompt: `你是一个共享资源滥用检测AI。分析用户行为数据,判断是否存在滥用行为。 24 | 25 | # 分析规则(严格执行) 26 | 1. 使用频率:平均每天超过50次为异常 27 | 2. 使用模式:短时间内大量请求(如1小时内超过30次) 28 | 3. 时间分布:24小时持续高频使用,无正常休息时间 29 | 4. 突增行为:使用量突然大幅增加(超过历史平均3倍) 30 | 31 | # 输出要求(必须严格遵守) 32 | 只返回JSON格式,无其他文字。格式: 33 | {"userId":"用户ID","shouldBan":true/false,"confidence":0-1,"reason":"简短原因(15字内)","evidence":"关键数据"} 34 | 35 | # 令牌节省规则 36 | - 只输出JSON,无解释 37 | - reason必须15字内 38 | - evidence仅列关键数字 39 | - 不输出分析过程` 40 | }; 41 | 42 | // 加载AI配置 43 | export async function loadAIConfig() { 44 | try { 45 | const data = await fs.readFile(AI_CONFIG_FILE, 'utf-8'); 46 | return { ...defaultConfig, ...JSON.parse(data) }; 47 | } catch (error) { 48 | if (error.code === 'ENOENT') { 49 | await saveAIConfig(defaultConfig); 50 | return defaultConfig; 51 | } 52 | log.error('加载AI配置失败:', error); 53 | return defaultConfig; 54 | } 55 | } 56 | 57 | // 保存AI配置 58 | export async function saveAIConfig(config) { 59 | try { 60 | const dir = path.dirname(AI_CONFIG_FILE); 61 | try { 62 | await fs.access(dir); 63 | } catch { 64 | await fs.mkdir(dir, { recursive: true }); 65 | } 66 | await fs.writeFile(AI_CONFIG_FILE, JSON.stringify(config, null, 2)); 67 | log.info('AI配置已保存'); 68 | } catch (error) { 69 | log.error('保存AI配置失败:', error); 70 | throw error; 71 | } 72 | } 73 | 74 | // 加载AI审核日志 75 | async function loadAILogs() { 76 | try { 77 | const data = await fs.readFile(AI_LOGS_FILE, 'utf-8'); 78 | return JSON.parse(data); 79 | } catch (error) { 80 | if (error.code === 'ENOENT') { 81 | return []; 82 | } 83 | return []; 84 | } 85 | } 86 | 87 | // 保存AI审核日志 88 | async function saveAILogs(logs) { 89 | try { 90 | const dir = path.dirname(AI_LOGS_FILE); 91 | try { 92 | await fs.access(dir); 93 | } catch { 94 | await fs.mkdir(dir, { recursive: true }); 95 | } 96 | // 只保留最近1000条日志 97 | const recentLogs = logs.slice(-1000); 98 | await fs.writeFile(AI_LOGS_FILE, JSON.stringify(recentLogs, null, 2)); 99 | } catch (error) { 100 | log.error('保存AI日志失败:', error); 101 | } 102 | } 103 | 104 | // 添加AI日志 105 | async function addAILog(entry) { 106 | const logs = await loadAILogs(); 107 | logs.push({ 108 | ...entry, 109 | timestamp: Date.now(), 110 | date: new Date().toISOString() 111 | }); 112 | await saveAILogs(logs); 113 | } 114 | 115 | // 获取所有共享用户的数据 116 | async function getAllShareUserData() { 117 | try { 118 | const users = await loadUsers(); 119 | const shareData = await shareManager.loadShareData(); 120 | 121 | const userData = []; 122 | 123 | for (const user of users) { 124 | const userId = user.id; 125 | 126 | // 获取用户使用历史 127 | const history = shareData.usageHistory[userId]; 128 | if (!history || !history.dailyUsage) continue; 129 | 130 | // 计算统计数据 131 | const dailyUsages = Object.values(history.dailyUsage); 132 | const dates = Object.keys(history.dailyUsage); 133 | 134 | if (dailyUsages.length === 0) continue; 135 | 136 | const totalUsage = dailyUsages.reduce((sum, v) => sum + v, 0); 137 | const avgUsage = Math.round(totalUsage / dailyUsages.length); 138 | const maxUsage = Math.max(...dailyUsages); 139 | const minUsage = Math.min(...dailyUsages); 140 | 141 | // 计算最近24小时的使用量 142 | const today = new Date().toDateString(); 143 | const todayUsage = history.dailyUsage[today] || 0; 144 | 145 | // 检查是否已被封禁 146 | const banStatus = await shareManager.isUserBanned(userId); 147 | 148 | userData.push({ 149 | userId, 150 | username: user.username, 151 | email: user.email || 'N/A', 152 | stats: { 153 | avgDailyUsage: avgUsage, 154 | totalDays: dates.length, 155 | totalUsage, 156 | maxDailyUsage: maxUsage, 157 | minDailyUsage: minUsage, 158 | todayUsage, 159 | dailyUsagePattern: history.dailyUsage 160 | }, 161 | currentlyBanned: banStatus.banned, 162 | banInfo: banStatus.banned ? { 163 | banCount: banStatus.banCount, 164 | reason: banStatus.reason, 165 | remainingTime: banStatus.remainingTime 166 | } : null 167 | }); 168 | } 169 | 170 | return userData; 171 | } catch (error) { 172 | log.error('获取共享用户数据失败:', error); 173 | return []; 174 | } 175 | } 176 | 177 | // 调用AI分析单个用户 178 | async function analyzeUserWithAI(userData, config) { 179 | try { 180 | const prompt = `分析以下用户数据: 181 | 用户: ${userData.username} (${userData.userId}) 182 | 平均日使用: ${userData.stats.avgDailyUsage}次 183 | 最大日使用: ${userData.stats.maxDailyUsage}次 184 | 今日使用: ${userData.stats.todayUsage}次 185 | 使用天数: ${userData.stats.totalDays}天 186 | 总使用: ${userData.stats.totalUsage}次 187 | 当前封禁: ${userData.currentlyBanned}`; 188 | 189 | const response = await fetch(config.apiEndpoint, { 190 | method: 'POST', 191 | headers: { 192 | 'Content-Type': 'application/json', 193 | 'Authorization': `Bearer ${config.apiKey}` 194 | }, 195 | body: JSON.stringify({ 196 | model: config.model, 197 | messages: [ 198 | { role: 'system', content: config.systemPrompt }, 199 | { role: 'user', content: prompt } 200 | ], 201 | temperature: 0.1, 202 | max_tokens: 150 203 | }) 204 | }); 205 | 206 | if (!response.ok) { 207 | throw new Error(`AI API响应错误: ${response.status}`); 208 | } 209 | 210 | const data = await response.json(); 211 | const content = data.choices?.[0]?.message?.content || ''; 212 | 213 | // 尝试解析JSON响应 214 | const jsonMatch = content.match(/\{[\s\S]*\}/); 215 | if (!jsonMatch) { 216 | throw new Error('AI响应格式错误'); 217 | } 218 | 219 | const result = JSON.parse(jsonMatch[0]); 220 | 221 | return { 222 | success: true, 223 | analysis: result 224 | }; 225 | } catch (error) { 226 | log.error(`AI分析用户 ${userData.userId} 失败:`, error.message); 227 | return { 228 | success: false, 229 | error: error.message 230 | }; 231 | } 232 | } 233 | 234 | // 执行AI自动审核 235 | export async function runAIModeration(manualTrigger = false) { 236 | const config = await loadAIConfig(); 237 | 238 | if (!config.enabled && !manualTrigger) { 239 | log.info('AI自动审核未启用'); 240 | return { success: false, message: 'AI自动审核未启用' }; 241 | } 242 | 243 | if (!config.apiKey || !config.apiEndpoint) { 244 | log.error('AI配置不完整:缺少API密钥或端点'); 245 | return { success: false, message: 'AI配置不完整' }; 246 | } 247 | 248 | log.info('开始执行AI自动审核...'); 249 | 250 | const startTime = Date.now(); 251 | const allUserData = await getAllShareUserData(); 252 | 253 | if (allUserData.length === 0) { 254 | log.info('没有需要审核的用户数据'); 255 | return { success: true, message: '没有需要审核的用户', checkedUsers: 0 }; 256 | } 257 | 258 | const results = { 259 | totalChecked: allUserData.length, 260 | bannedCount: 0, 261 | flaggedCount: 0, 262 | normalCount: 0, 263 | errorCount: 0, 264 | details: [] 265 | }; 266 | 267 | // 逐个分析用户 268 | for (const userData of allUserData) { 269 | // 跳过已被封禁的用户 270 | if (userData.currentlyBanned) { 271 | results.details.push({ 272 | userId: userData.userId, 273 | username: userData.username, 274 | status: 'already_banned', 275 | skipped: true 276 | }); 277 | continue; 278 | } 279 | 280 | const aiResult = await analyzeUserWithAI(userData, config); 281 | 282 | if (!aiResult.success) { 283 | results.errorCount++; 284 | results.details.push({ 285 | userId: userData.userId, 286 | username: userData.username, 287 | status: 'error', 288 | error: aiResult.error 289 | }); 290 | 291 | await addAILog({ 292 | userId: userData.userId, 293 | username: userData.username, 294 | action: 'analysis_failed', 295 | error: aiResult.error 296 | }); 297 | 298 | continue; 299 | } 300 | 301 | const analysis = aiResult.analysis; 302 | 303 | // 判断是否需要封禁 304 | if (analysis.shouldBan && analysis.confidence >= config.autoModerateThreshold) { 305 | try { 306 | const banResult = await shareManager.banUserFromSharing( 307 | userData.userId, 308 | `AI自动检测: ${analysis.reason}` 309 | ); 310 | 311 | results.bannedCount++; 312 | results.details.push({ 313 | userId: userData.userId, 314 | username: userData.username, 315 | status: 'banned', 316 | confidence: analysis.confidence, 317 | reason: analysis.reason, 318 | evidence: analysis.evidence, 319 | banInfo: banResult 320 | }); 321 | 322 | await addAILog({ 323 | userId: userData.userId, 324 | username: userData.username, 325 | action: 'auto_banned', 326 | confidence: analysis.confidence, 327 | reason: analysis.reason, 328 | evidence: analysis.evidence, 329 | banCount: banResult.banCount, 330 | banDays: banResult.durationDays 331 | }); 332 | 333 | log.warn(`AI自动封禁用户 ${userData.username}: ${analysis.reason} (置信度: ${analysis.confidence})`); 334 | } catch (error) { 335 | log.error(`封禁用户 ${userData.userId} 失败:`, error); 336 | results.errorCount++; 337 | } 338 | } else if (analysis.shouldBan) { 339 | // 置信度不足,只标记 340 | results.flaggedCount++; 341 | results.details.push({ 342 | userId: userData.userId, 343 | username: userData.username, 344 | status: 'flagged', 345 | confidence: analysis.confidence, 346 | reason: analysis.reason, 347 | evidence: analysis.evidence, 348 | note: '置信度不足,未自动封禁' 349 | }); 350 | 351 | await addAILog({ 352 | userId: userData.userId, 353 | username: userData.username, 354 | action: 'flagged', 355 | confidence: analysis.confidence, 356 | reason: analysis.reason, 357 | evidence: analysis.evidence 358 | }); 359 | 360 | log.info(`用户 ${userData.username} 被标记为可疑: ${analysis.reason} (置信度: ${analysis.confidence})`); 361 | } else { 362 | results.normalCount++; 363 | results.details.push({ 364 | userId: userData.userId, 365 | username: userData.username, 366 | status: 'normal', 367 | confidence: analysis.confidence 368 | }); 369 | } 370 | 371 | // 避免请求过快 372 | await new Promise(resolve => setTimeout(resolve, 1000)); 373 | } 374 | 375 | const duration = Date.now() - startTime; 376 | 377 | log.info(`AI审核完成: 检查${results.totalChecked}个用户, 封禁${results.bannedCount}个, 标记${results.flaggedCount}个, 正常${results.normalCount}个, 耗时${Math.round(duration/1000)}秒`); 378 | 379 | await addAILog({ 380 | action: 'moderation_completed', 381 | summary: { 382 | totalChecked: results.totalChecked, 383 | bannedCount: results.bannedCount, 384 | flaggedCount: results.flaggedCount, 385 | normalCount: results.normalCount, 386 | errorCount: results.errorCount, 387 | durationMs: duration 388 | } 389 | }); 390 | 391 | return { 392 | success: true, 393 | ...results, 394 | duration 395 | }; 396 | } 397 | 398 | // 获取AI审核日志 399 | export async function getAIModerationLogs(limit = 100) { 400 | const logs = await loadAILogs(); 401 | return logs.slice(-limit).reverse(); 402 | } 403 | 404 | // 获取AI统计信息 405 | export async function getAIStatistics() { 406 | const logs = await loadAILogs(); 407 | const config = await loadAIConfig(); 408 | 409 | // 统计最近30天的数据 410 | const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000; 411 | const recentLogs = logs.filter(log => log.timestamp > thirtyDaysAgo); 412 | 413 | const stats = { 414 | totalRuns: 0, 415 | totalBanned: 0, 416 | totalFlagged: 0, 417 | totalChecked: 0, 418 | avgDuration: 0, 419 | lastRun: null, 420 | config: { 421 | enabled: config.enabled, 422 | model: config.model, 423 | checkIntervalHours: config.checkIntervalHours, 424 | threshold: config.autoModerateThreshold 425 | } 426 | }; 427 | 428 | for (const log of recentLogs) { 429 | if (log.action === 'moderation_completed') { 430 | stats.totalRuns++; 431 | stats.totalChecked += log.summary.totalChecked || 0; 432 | stats.totalBanned += log.summary.bannedCount || 0; 433 | stats.totalFlagged += log.summary.flaggedCount || 0; 434 | stats.avgDuration += log.summary.durationMs || 0; 435 | if (!stats.lastRun || log.timestamp > stats.lastRun.timestamp) { 436 | stats.lastRun = { 437 | timestamp: log.timestamp, 438 | date: log.date, 439 | summary: log.summary 440 | }; 441 | } 442 | } 443 | } 444 | 445 | if (stats.totalRuns > 0) { 446 | stats.avgDuration = Math.round(stats.avgDuration / stats.totalRuns); 447 | } 448 | 449 | return stats; 450 | } 451 | 452 | // 定时任务调度器 453 | let schedulerInterval = null; 454 | 455 | export function startAIScheduler() { 456 | if (schedulerInterval) { 457 | log.warn('AI调度器已在运行'); 458 | return; 459 | } 460 | 461 | loadAIConfig().then(config => { 462 | if (!config.enabled) { 463 | log.info('AI自动审核未启用,调度器未启动'); 464 | return; 465 | } 466 | 467 | const intervalMs = config.checkIntervalHours * 60 * 60 * 1000; 468 | 469 | log.info(`AI调度器已启动,每${config.checkIntervalHours}小时执行一次审核`); 470 | 471 | // 立即执行一次 472 | runAIModeration().catch(err => log.error('AI审核失败:', err)); 473 | 474 | // 设置定时执行 475 | schedulerInterval = setInterval(() => { 476 | runAIModeration().catch(err => log.error('AI审核失败:', err)); 477 | }, intervalMs); 478 | }); 479 | } 480 | 481 | export function stopAIScheduler() { 482 | if (schedulerInterval) { 483 | clearInterval(schedulerInterval); 484 | schedulerInterval = null; 485 | log.info('AI调度器已停止'); 486 | } 487 | } 488 | 489 | // 重启调度器(配置更新后) 490 | export async function restartAIScheduler() { 491 | stopAIScheduler(); 492 | startAIScheduler(); 493 | } 494 | -------------------------------------------------------------------------------- /Antigravity/src/admin/user_manager.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | import path from 'path'; 3 | import crypto from 'crypto'; 4 | import logger from '../utils/logger.js'; 5 | import { cleanupInactiveUsers } from './security_manager.js'; 6 | import * as shareManager from './share_manager.js'; 7 | 8 | const USERS_FILE = path.join(process.cwd(), 'data', 'users.json'); 9 | 10 | // 读取所有用户 11 | export async function loadUsers() { 12 | try { 13 | const data = await fs.readFile(USERS_FILE, 'utf-8'); 14 | return JSON.parse(data); 15 | } catch (error) { 16 | if (error.code === 'ENOENT') { 17 | return []; 18 | } 19 | throw error; 20 | } 21 | } 22 | 23 | // 保存用户 24 | async function saveUsers(users) { 25 | const dir = path.dirname(USERS_FILE); 26 | try { 27 | await fs.access(dir); 28 | } catch { 29 | await fs.mkdir(dir, { recursive: true }); 30 | } 31 | await fs.writeFile(USERS_FILE, JSON.stringify(users, null, 2), 'utf-8'); 32 | } 33 | 34 | // 生成用户ID 35 | function generateUserId() { 36 | return 'user_' + crypto.randomBytes(8).toString('hex'); 37 | } 38 | 39 | // 生成API密钥 40 | function generateApiKey() { 41 | return 'sk-user-' + crypto.randomBytes(16).toString('hex'); 42 | } 43 | 44 | // 密码哈希 45 | function hashPassword(password) { 46 | const salt = crypto.randomBytes(16).toString('hex'); 47 | const hash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex'); 48 | return `${salt}:${hash}`; 49 | } 50 | 51 | // 验证密码 52 | function verifyPassword(password, storedHash) { 53 | const [salt, hash] = storedHash.split(':'); 54 | const verifyHash = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('hex'); 55 | return hash === verifyHash; 56 | } 57 | 58 | // 生成用户会话Token 59 | function generateSessionToken() { 60 | return crypto.randomBytes(32).toString('hex'); 61 | } 62 | 63 | // 用户注册 64 | export async function registerUser(username, password, email) { 65 | const users = await loadUsers(); 66 | 67 | // 验证用户名格式 68 | if (!username || username.length < 3 || username.length > 20) { 69 | throw new Error('用户名长度必须在3-20个字符之间'); 70 | } 71 | 72 | if (!/^[a-zA-Z0-9_]+$/.test(username)) { 73 | throw new Error('用户名只能包含字母、数字和下划线'); 74 | } 75 | 76 | // 验证密码强度 77 | if (!password || password.length < 6) { 78 | throw new Error('密码长度至少6个字符'); 79 | } 80 | 81 | // 验证邮箱格式 82 | if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { 83 | throw new Error('邮箱格式不正确'); 84 | } 85 | 86 | // 检查用户名是否已存在 87 | const existingUser = users.find(u => u.username.toLowerCase() === username.toLowerCase()); 88 | if (existingUser) { 89 | throw new Error('用户名已被使用'); 90 | } 91 | 92 | // 检查邮箱是否已存在 93 | if (email) { 94 | const existingEmail = users.find(u => u.email && u.email.toLowerCase() === email.toLowerCase()); 95 | if (existingEmail) { 96 | throw new Error('邮箱已被注册'); 97 | } 98 | } 99 | 100 | // 创建新用户 101 | const newUser = { 102 | id: generateUserId(), 103 | username, 104 | password: hashPassword(password), 105 | email: email || null, 106 | apiKeys: [], 107 | systemPrompt: null, 108 | created: Date.now(), 109 | lastLogin: null, 110 | enabled: true 111 | }; 112 | 113 | users.push(newUser); 114 | await saveUsers(users); 115 | 116 | logger.info(`新用户注册: ${username}`); 117 | 118 | return { 119 | id: newUser.id, 120 | username: newUser.username, 121 | email: newUser.email, 122 | created: newUser.created 123 | }; 124 | } 125 | 126 | // 用户登录 127 | export async function loginUser(username, password) { 128 | const users = await loadUsers(); 129 | 130 | const user = users.find(u => u.username.toLowerCase() === username.toLowerCase()); 131 | if (!user) { 132 | throw new Error('用户名或密码错误'); 133 | } 134 | 135 | if (!user.enabled) { 136 | throw new Error('账号已被禁用'); 137 | } 138 | 139 | if (!verifyPassword(password, user.password)) { 140 | throw new Error('用户名或密码错误'); 141 | } 142 | 143 | // 更新最后登录时间 144 | user.lastLogin = Date.now(); 145 | await saveUsers(users); 146 | 147 | // 生成会话Token 148 | const sessionToken = generateSessionToken(); 149 | 150 | logger.info(`用户登录: ${username}`); 151 | 152 | return { 153 | id: user.id, 154 | username: user.username, 155 | email: user.email, 156 | token: sessionToken 157 | }; 158 | } 159 | 160 | // 获取用户信息 161 | export async function getUserById(userId) { 162 | const users = await loadUsers(); 163 | const user = users.find(u => u.id === userId); 164 | 165 | if (!user) { 166 | return null; 167 | } 168 | 169 | return { 170 | id: user.id, 171 | username: user.username, 172 | email: user.email, 173 | apiKeys: user.apiKeys, 174 | systemPrompt: user.systemPrompt || null, 175 | created: user.created, 176 | lastLogin: user.lastLogin, 177 | enabled: user.enabled 178 | }; 179 | } 180 | 181 | // 获取用户通过用户名 182 | export async function getUserByUsername(username) { 183 | const users = await loadUsers(); 184 | return users.find(u => u.username.toLowerCase() === username.toLowerCase()); 185 | } 186 | 187 | // 生成用户API密钥 188 | export async function generateUserApiKey(userId, keyName) { 189 | const users = await loadUsers(); 190 | const user = users.find(u => u.id === userId); 191 | 192 | if (!user) { 193 | throw new Error('用户不存在'); 194 | } 195 | 196 | if (!user.enabled) { 197 | throw new Error('账号已被禁用'); 198 | } 199 | 200 | // 限制每个用户最多5个密钥 201 | if (user.apiKeys.length >= 5) { 202 | throw new Error('每个用户最多创建5个API密钥'); 203 | } 204 | 205 | const newKey = { 206 | id: crypto.randomBytes(8).toString('hex'), 207 | key: generateApiKey(), 208 | name: keyName || '未命名密钥', 209 | created: Date.now(), 210 | lastUsed: null, 211 | requests: 0 212 | }; 213 | 214 | user.apiKeys.push(newKey); 215 | await saveUsers(users); 216 | 217 | logger.info(`用户 ${user.username} 创建了新密钥: ${keyName}`); 218 | 219 | return newKey; 220 | } 221 | 222 | // 删除用户API密钥 223 | export async function deleteUserApiKey(userId, keyId) { 224 | const users = await loadUsers(); 225 | const user = users.find(u => u.id === userId); 226 | 227 | if (!user) { 228 | throw new Error('用户不存在'); 229 | } 230 | 231 | const keyIndex = user.apiKeys.findIndex(k => k.id === keyId); 232 | if (keyIndex === -1) { 233 | throw new Error('密钥不存在'); 234 | } 235 | 236 | user.apiKeys.splice(keyIndex, 1); 237 | await saveUsers(users); 238 | 239 | logger.info(`用户 ${user.username} 删除了密钥: ${keyId}`); 240 | 241 | return true; 242 | } 243 | 244 | // 获取用户所有API密钥 245 | export async function getUserApiKeys(userId) { 246 | const users = await loadUsers(); 247 | const user = users.find(u => u.id === userId); 248 | 249 | if (!user) { 250 | throw new Error('用户不存在'); 251 | } 252 | 253 | return user.apiKeys.map(key => ({ 254 | id: key.id, 255 | key: key.key, 256 | name: key.name, 257 | created: key.created, 258 | lastUsed: key.lastUsed, 259 | requests: key.requests 260 | })); 261 | } 262 | 263 | // 验证用户API密钥 264 | export async function validateUserApiKey(apiKey) { 265 | const users = await loadUsers(); 266 | 267 | for (const user of users) { 268 | if (!user.enabled) continue; 269 | 270 | const key = user.apiKeys.find(k => k.key === apiKey); 271 | if (key) { 272 | // 更新使用统计 273 | key.lastUsed = Date.now(); 274 | key.requests = (key.requests || 0) + 1; 275 | await saveUsers(users); 276 | 277 | return { 278 | valid: true, 279 | userId: user.id, 280 | username: user.username, 281 | keyId: key.id 282 | }; 283 | } 284 | } 285 | 286 | return { valid: false }; 287 | } 288 | 289 | // 更新用户信息 290 | export async function updateUser(userId, updates) { 291 | const users = await loadUsers(); 292 | const user = users.find(u => u.id === userId); 293 | 294 | if (!user) { 295 | throw new Error('用户不存在'); 296 | } 297 | 298 | // 更新允许的字段 299 | if (updates.email !== undefined) { 300 | if (updates.email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(updates.email)) { 301 | throw new Error('邮箱格式不正确'); 302 | } 303 | // 检查邮箱是否已被其他用户使用 304 | if (updates.email) { 305 | const existingEmail = users.find(u => u.id !== userId && u.email && u.email.toLowerCase() === updates.email.toLowerCase()); 306 | if (existingEmail) { 307 | throw new Error('邮箱已被其他用户使用'); 308 | } 309 | } 310 | user.email = updates.email || null; 311 | } 312 | 313 | if (updates.password) { 314 | if (updates.password.length < 6) { 315 | throw new Error('密码长度至少6个字符'); 316 | } 317 | user.password = hashPassword(updates.password); 318 | } 319 | 320 | if (updates.systemPrompt !== undefined) { 321 | user.systemPrompt = updates.systemPrompt || null; 322 | } 323 | 324 | await saveUsers(users); 325 | 326 | logger.info(`用户 ${user.username} 更新了个人信息`); 327 | 328 | return { 329 | id: user.id, 330 | username: user.username, 331 | email: user.email, 332 | systemPrompt: user.systemPrompt 333 | }; 334 | } 335 | 336 | // 删除用户 337 | export async function deleteUser(userId) { 338 | const users = await loadUsers(); 339 | const index = users.findIndex(u => u.id === userId); 340 | 341 | if (index === -1) { 342 | throw new Error('用户不存在'); 343 | } 344 | 345 | const username = users[index].username; 346 | users.splice(index, 1); 347 | await saveUsers(users); 348 | 349 | logger.info(`用户已删除: ${username}`); 350 | 351 | return true; 352 | } 353 | 354 | // 获取用户统计 355 | export async function getUserStats() { 356 | const users = await loadUsers(); 357 | 358 | return { 359 | total: users.length, 360 | enabled: users.filter(u => u.enabled).length, 361 | disabled: users.filter(u => !u.enabled).length, 362 | totalKeys: users.reduce((sum, u) => sum + u.apiKeys.length, 0) 363 | }; 364 | } 365 | 366 | // 获取所有用户(管理员用) 367 | export async function getAllUsers() { 368 | const users = await loadUsers(); 369 | 370 | return users.map(user => ({ 371 | id: user.id, 372 | username: user.username, 373 | email: user.email, 374 | apiKeysCount: user.apiKeys.length, 375 | created: user.created, 376 | lastLogin: user.lastLogin, 377 | enabled: user.enabled 378 | })); 379 | } 380 | 381 | // 启用/禁用用户(管理员用) 382 | export async function toggleUserStatus(userId, enabled) { 383 | const users = await loadUsers(); 384 | const user = users.find(u => u.id === userId); 385 | 386 | if (!user) { 387 | throw new Error('用户不存在'); 388 | } 389 | 390 | user.enabled = enabled; 391 | await saveUsers(users); 392 | 393 | logger.info(`用户 ${user.username} 已${enabled ? '启用' : '禁用'}`); 394 | 395 | return true; 396 | } 397 | 398 | // Google OAuth 登录/注册 399 | export async function loginOrRegisterWithGoogle(googleUser) { 400 | const { email, name } = googleUser; 401 | 402 | if (!email) { 403 | throw new Error('无法获取 Google 账号邮箱'); 404 | } 405 | 406 | const users = await loadUsers(); 407 | 408 | // 查找是否存在该邮箱的用户 409 | let user = users.find(u => u.email && u.email.toLowerCase() === email.toLowerCase()); 410 | 411 | if (user) { 412 | // 已存在用户,检查是否启用 413 | if (!user.enabled) { 414 | throw new Error('账号已被禁用'); 415 | } 416 | 417 | // 更新最后登录时间 418 | user.lastLogin = Date.now(); 419 | await saveUsers(users); 420 | 421 | logger.info(`用户通过 Google 登录: ${user.username}`); 422 | 423 | return { 424 | id: user.id, 425 | username: user.username, 426 | email: user.email, 427 | isNewUser: false 428 | }; 429 | } else { 430 | // 创建新用户 431 | // 使用邮箱前缀作为用户名,确保唯一性 432 | let baseUsername = email.split('@')[0].replace(/[^a-zA-Z0-9_]/g, '_'); 433 | let username = baseUsername; 434 | let counter = 1; 435 | 436 | // 确保用户名唯一 437 | while (users.find(u => u.username.toLowerCase() === username.toLowerCase())) { 438 | username = `${baseUsername}${counter}`; 439 | counter++; 440 | } 441 | 442 | // 生成随机密码(用户可以之后在设置中修改) 443 | const randomPassword = crypto.randomBytes(16).toString('hex'); 444 | 445 | const newUser = { 446 | id: generateUserId(), 447 | username, 448 | password: hashPassword(randomPassword), 449 | email: email, 450 | googleId: googleUser.id, 451 | apiKeys: [], 452 | created: Date.now(), 453 | lastLogin: Date.now(), 454 | enabled: true 455 | }; 456 | 457 | users.push(newUser); 458 | await saveUsers(users); 459 | 460 | logger.info(`新用户通过 Google 注册: ${username} (${email})`); 461 | 462 | return { 463 | id: newUser.id, 464 | username: newUser.username, 465 | email: newUser.email, 466 | isNewUser: true 467 | }; 468 | } 469 | } 470 | 471 | // 获取用户的 Google Tokens 472 | export async function getUserTokens(userId) { 473 | const users = await loadUsers(); 474 | const user = users.find(u => u.id === userId); 475 | 476 | if (!user) { 477 | throw new Error('用户不存在'); 478 | } 479 | 480 | return user.googleTokens || []; 481 | } 482 | 483 | // 添加用户 Google Token 484 | export async function addUserToken(userId, tokenData) { 485 | const users = await loadUsers(); 486 | const user = users.find(u => u.id === userId); 487 | 488 | if (!user) { 489 | throw new Error('用户不存在'); 490 | } 491 | 492 | if (!user.enabled) { 493 | throw new Error('账号已被禁用'); 494 | } 495 | 496 | // 初始化 googleTokens 数组 497 | if (!user.googleTokens) { 498 | user.googleTokens = []; 499 | } 500 | 501 | // 限制每个用户最多添加10个 Token 502 | if (user.googleTokens.length >= 10) { 503 | throw new Error('每个用户最多添加10个 Token'); 504 | } 505 | 506 | const newToken = { 507 | access_token: tokenData.access_token, 508 | refresh_token: tokenData.refresh_token || null, 509 | expires_in: tokenData.expires_in || 3600, 510 | timestamp: Date.now(), 511 | email: tokenData.email || null, 512 | enable: true, 513 | // 共享功能 514 | isShared: false, // 是否共享 515 | dailyLimit: 100, // 每日最大使用次数 516 | usageToday: 0, // 今日已使用次数 517 | lastResetDate: new Date().toDateString() // 上次重置日期 518 | }; 519 | 520 | user.googleTokens.push(newToken); 521 | await saveUsers(users); 522 | 523 | logger.info(`用户 ${user.username} 添加了新 Token`); 524 | 525 | return { success: true, index: user.googleTokens.length - 1 }; 526 | } 527 | 528 | // 删除用户 Google Token 529 | export async function deleteUserToken(userId, tokenIndex) { 530 | const users = await loadUsers(); 531 | const user = users.find(u => u.id === userId); 532 | 533 | if (!user) { 534 | throw new Error('用户不存在'); 535 | } 536 | 537 | if (!user.googleTokens || tokenIndex < 0 || tokenIndex >= user.googleTokens.length) { 538 | throw new Error('Token 不存在'); 539 | } 540 | 541 | user.googleTokens.splice(tokenIndex, 1); 542 | await saveUsers(users); 543 | 544 | logger.info(`用户 ${user.username} 删除了 Token #${tokenIndex}`); 545 | 546 | return { success: true }; 547 | } 548 | 549 | // 获取用户的随机可用 Token(用于 API 调用) 550 | export async function getUserAvailableToken(userId) { 551 | const users = await loadUsers(); 552 | const user = users.find(u => u.id === userId); 553 | 554 | if (!user || !user.googleTokens || user.googleTokens.length === 0) { 555 | return null; 556 | } 557 | 558 | // 筛选启用的 Token 559 | const enabledTokens = user.googleTokens.filter(t => t.enable !== false); 560 | 561 | if (enabledTokens.length === 0) { 562 | return null; 563 | } 564 | 565 | // 随机返回一个 566 | const randomIndex = Math.floor(Math.random() * enabledTokens.length); 567 | return enabledTokens[randomIndex]; 568 | } 569 | 570 | // 定期清理未登录账号任务 571 | let cleanupTimer = null; 572 | 573 | export function startInactiveUsersCleanup() { 574 | // 每天清理一次(24小时) 575 | const cleanupInterval = 24 * 60 * 60 * 1000; 576 | 577 | async function performCleanup() { 578 | try { 579 | logger.info('开始清理长时间未登录账号...'); 580 | const users = await loadUsers(); 581 | const result = await cleanupInactiveUsers(users); 582 | 583 | if (result.deletedCount > 0) { 584 | await saveUsers(result.users); 585 | logger.info(`已清理 ${result.deletedCount} 个长时间未登录账号`); 586 | } else { 587 | logger.info('没有需要清理的账号'); 588 | } 589 | } catch (error) { 590 | logger.error('清理账号失败:', error.message); 591 | } 592 | } 593 | 594 | // 立即执行一次 595 | performCleanup(); 596 | 597 | // 设置定时器 598 | cleanupTimer = setInterval(performCleanup, cleanupInterval); 599 | logger.info('账号自动清理任务已启动(每24小时执行一次)'); 600 | } 601 | 602 | export function stopInactiveUsersCleanup() { 603 | if (cleanupTimer) { 604 | clearInterval(cleanupTimer); 605 | cleanupTimer = null; 606 | logger.info('账号自动清理任务已停止'); 607 | } 608 | } 609 | 610 | // ========== Token 共享功能 ========== 611 | 612 | // 更新 Token 共享设置 613 | export async function updateTokenSharing(userId, tokenIndex, sharingSettings) { 614 | const users = await loadUsers(); 615 | const user = users.find(u => u.id === userId); 616 | 617 | if (!user) { 618 | throw new Error('用户不存在'); 619 | } 620 | 621 | if (!user.googleTokens || tokenIndex < 0 || tokenIndex >= user.googleTokens.length) { 622 | throw new Error('Token 不存在'); 623 | } 624 | 625 | const token = user.googleTokens[tokenIndex]; 626 | 627 | // 更新共享设置 628 | if (sharingSettings.isShared !== undefined) { 629 | token.isShared = sharingSettings.isShared; 630 | } 631 | 632 | if (sharingSettings.dailyLimit !== undefined) { 633 | token.dailyLimit = Math.max(1, Math.min(10000, parseInt(sharingSettings.dailyLimit))); 634 | } 635 | 636 | await saveUsers(users); 637 | 638 | logger.info(`用户 ${user.username} 更新了 Token #${tokenIndex} 的共享设置: ${token.isShared ? '已共享' : '未共享'}, 限制: ${token.dailyLimit}/天`); 639 | 640 | return { success: true, token }; 641 | } 642 | 643 | // 获取所有共享的 Token(来自所有用户) 644 | export async function getAllSharedTokens() { 645 | const users = await loadUsers(); 646 | const sharedTokens = []; 647 | 648 | for (const user of users) { 649 | if (!user.enabled || !user.googleTokens) continue; 650 | 651 | user.googleTokens.forEach((token, index) => { 652 | if (token.isShared && token.enable) { 653 | // 检查是否需要重置每日使用次数 654 | const today = new Date().toDateString(); 655 | if (token.lastResetDate !== today) { 656 | token.usageToday = 0; 657 | token.lastResetDate = today; 658 | } 659 | 660 | sharedTokens.push({ 661 | userId: user.id, 662 | username: user.username, 663 | tokenIndex: index, 664 | email: token.email, 665 | dailyLimit: token.dailyLimit, 666 | usageToday: token.usageToday, 667 | remainingToday: token.dailyLimit - token.usageToday, 668 | timestamp: token.timestamp, 669 | token: token 670 | }); 671 | } 672 | }); 673 | } 674 | 675 | return sharedTokens; 676 | } 677 | 678 | // 获取随机可用的共享 Token(带封禁和黑名单检查) 679 | export async function getRandomSharedToken(callerId = null) { 680 | // 检查调用者是否被封禁 681 | if (callerId) { 682 | const banStatus = await shareManager.isUserBanned(callerId); 683 | if (banStatus.banned) { 684 | logger.info(`用户 ${callerId} 被封禁使用共享,剩余时间: ${Math.round(banStatus.remainingTime / 3600000)}小时`); 685 | return { 686 | error: 'banned', 687 | banned: true, 688 | banUntil: banStatus.banUntil, 689 | remainingTime: banStatus.remainingTime, 690 | reason: banStatus.reason 691 | }; 692 | } 693 | } 694 | 695 | const users = await loadUsers(); 696 | const today = new Date().toDateString(); 697 | const availableTokens = []; 698 | 699 | for (const user of users) { 700 | if (!user.enabled || !user.googleTokens) continue; 701 | 702 | for (let index = 0; index < user.googleTokens.length; index++) { 703 | const token = user.googleTokens[index]; 704 | if (!token.isShared || !token.enable) continue; 705 | 706 | // 检查调用者是否在该 Token 的黑名单中 707 | if (callerId) { 708 | const isBlacklisted = await shareManager.isUserBlacklisted(user.id, index, callerId); 709 | if (isBlacklisted) { 710 | continue; // 跳过这个 Token 711 | } 712 | } 713 | 714 | // 重置每日使用次数 715 | if (token.lastResetDate !== today) { 716 | token.usageToday = 0; 717 | token.lastResetDate = today; 718 | } 719 | 720 | // 检查是否还有剩余使用次数 721 | if (token.usageToday < token.dailyLimit) { 722 | availableTokens.push({ 723 | user, 724 | tokenIndex: index, 725 | token 726 | }); 727 | } 728 | } 729 | } 730 | 731 | if (availableTokens.length === 0) { 732 | return null; 733 | } 734 | 735 | // 随机选择一个 736 | const randomIndex = Math.floor(Math.random() * availableTokens.length); 737 | const selected = availableTokens[randomIndex]; 738 | 739 | // 增加使用次数 740 | selected.token.usageToday++; 741 | await saveUsers(users); 742 | 743 | // 记录共享使用并检查滥用 744 | if (callerId) { 745 | await shareManager.recordShareUsage(callerId); 746 | // 异步检查是否需要封禁(不阻塞当前请求) 747 | shareManager.checkAndBanAbuser(callerId).catch(err => 748 | logger.error('检查滥用失败:', err) 749 | ); 750 | } 751 | 752 | logger.info(`共享 Token 被使用: ${selected.user.username} 的 Token #${selected.tokenIndex} (今日: ${selected.token.usageToday}/${selected.token.dailyLimit})`); 753 | 754 | return { 755 | access_token: selected.token.access_token, 756 | refresh_token: selected.token.refresh_token, 757 | expires_in: selected.token.expires_in, 758 | email: selected.token.email, 759 | owner: selected.user.username, 760 | ownerId: selected.user.id, 761 | usageToday: selected.token.usageToday, 762 | dailyLimit: selected.token.dailyLimit 763 | }; 764 | } 765 | 766 | // 获取共享统计信息 767 | export async function getSharedTokenStats() { 768 | const sharedTokens = await getAllSharedTokens(); 769 | 770 | const totalShared = sharedTokens.length; 771 | const totalAvailable = sharedTokens.filter(t => t.remainingToday > 0).length; 772 | const totalUsageToday = sharedTokens.reduce((sum, t) => sum + t.usageToday, 0); 773 | const totalLimitToday = sharedTokens.reduce((sum, t) => sum + t.dailyLimit, 0); 774 | 775 | return { 776 | totalShared, 777 | totalAvailable, 778 | totalUsageToday, 779 | totalLimitToday, 780 | remainingToday: totalLimitToday - totalUsageToday, 781 | tokens: sharedTokens.map(t => ({ 782 | username: t.username, 783 | email: t.email, 784 | usageToday: t.usageToday, 785 | dailyLimit: t.dailyLimit, 786 | remainingToday: t.remainingToday 787 | })) 788 | }; 789 | } 790 | 791 | // 获取用户或共享 Token(用于 API 调用) 792 | export async function getUserOrSharedToken(userId) { 793 | // 首先尝试使用用户自己的 Token 794 | const userToken = await getUserAvailableToken(userId); 795 | if (userToken) { 796 | logger.info(`使用用户自己的 Token: userId=${userId}`); 797 | return userToken; 798 | } 799 | 800 | // 如果用户没有可用 Token,使用共享池 801 | const sharedToken = await getRandomSharedToken(); 802 | if (sharedToken) { 803 | logger.info(`用户 ${userId} 使用共享 Token: owner=${sharedToken.owner}`); 804 | return sharedToken; 805 | } 806 | 807 | // 没有任何可用 Token 808 | return null; 809 | } 810 | -------------------------------------------------------------------------------- /Antigravity/public/share.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Antigravity API - 共享中心 7 | 77 | 78 | 79 |
80 |
81 |

Token 共享中心

82 |

社区用户分享的 Google Token,让所有人都能使用 AI 服务

83 |
84 | 85 | 86 | 93 | 94 | 95 |
96 |

共享 Token

0
97 |

可用

0
98 |

今日限额

0
99 |

今日已用

0
100 |

活跃投票

0
101 |
102 | 103 | 104 |
105 |
106 | 什么是 Token 共享?
107 | 社区用户自愿分享 Google Token,让没有 Token 的用户也能使用 AI 服务。发现滥用可发起投票封禁。前往 用户中心 共享您的 Token。 108 |
109 |
110 | 111 | 112 |
113 | 114 | 115 | 116 | 117 |
118 | 119 | 120 |
121 |
122 |

共享 Token 列表

123 |

正在加载...

124 |
125 |
126 | 127 | 128 |
129 |
130 |

📊 用户使用统计

131 |

显示所有使用共享Token的用户及其使用情况

132 |

正在加载...

133 |
134 |
135 | 136 | 137 |
138 |
139 |

140 | 活跃投票 141 | 142 |

143 |

暂无活跃投票

社区目前没有进行中的封禁投票

144 |
145 |
146 | 147 | 148 |
149 |
150 |

投票历史

151 |

暂无投票记录

152 |
153 |
154 | 155 | 156 |
157 | 前往用户中心 158 | 管理员入口 159 |
160 |
161 | 162 | 163 | 180 | 181 | 478 | 479 | 480 | --------------------------------------------------------------------------------