├── 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 |
84 |
85 |
86 |
87 |
88 | 共享已禁用
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
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 |
154 |
155 |
156 |
160 |
161 |
162 |
163 |
164 |
165 |
发起封禁投票
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
478 |
479 |
480 |
--------------------------------------------------------------------------------