0412Xu commited on
Commit
dc70e52
·
verified ·
1 Parent(s): f89a161

Upload 20 files

Browse files
.github/workflows/publish-docker-image.yml ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Publish Docker Image
2
+
3
+ on:
4
+ workflow_dispatch:
5
+
6
+ env:
7
+ REGISTRY: ghcr.io
8
+ IMAGE_NAME: ${{ github.repository }}
9
+ MAJOR: 1
10
+ MINOR: 0
11
+ PATCH: ${{github.run_number}}
12
+
13
+ jobs:
14
+ build-and-push-image:
15
+ runs-on: ubuntu-latest
16
+
17
+ # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
18
+ permissions:
19
+ contents: read
20
+ packages: write
21
+ attestations: write
22
+ id-token: write
23
+
24
+ steps:
25
+ - name: Checkout repository
26
+ uses: actions/checkout@v4
27
+
28
+ # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
29
+ - name: Log in to the Container registry
30
+ uses: docker/login-action@v3
31
+ with:
32
+ registry: ${{ env.REGISTRY }}
33
+ username: ${{ github.actor }}
34
+ password: ${{ secrets.GITHUB_TOKEN }}
35
+
36
+ # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
37
+ - name: Extract metadata (tags, labels) for Docker
38
+ id: meta
39
+ uses: docker/metadata-action@v5
40
+ with:
41
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
42
+ tags: |
43
+ type=ref,event=branch
44
+ type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
45
+ type=raw,value=v${{ env.MAJOR }}.${{ env.MINOR }}.${{ env.PATCH }}
46
+
47
+ # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
48
+ # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
49
+ # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
50
+ - name: Build and push Docker image
51
+ id: push
52
+ uses: docker/build-push-action@v6
53
+ with:
54
+ context: .
55
+ push: true
56
+ tags: ${{ steps.meta.outputs.tags }}
57
+ labels: ${{ steps.meta.outputs.labels }}
58
+ subject-digest: ${{ steps.push.outputs.digest }}
59
+ push-to-registry: true
60
+
data/admin.example.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "admin": {
3
+ "username": "your_admin_username",
4
+ "salt": "your_generated_salt",
5
+ "hash": "your_password_hash"
6
+ }
7
+ }
data/api_keys.example.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "sk-text@example": [
3
+ "user_XXXXXXXXXXXX%3A%3AeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
4
+ ]
5
+ }
data/invalid_cookies.example.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [
2
+ "user_XXXXXXXXXXXX%3A%3AeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
3
+ ]
scripts/create-admin.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const readline = require('readline');
5
+
6
+ const ADMIN_FILE = path.join(__dirname, '../data/admin.json');
7
+
8
+ // 创建readline接口
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout
12
+ });
13
+
14
+ // 生成盐值
15
+ function generateSalt() {
16
+ return crypto.randomBytes(16).toString('hex');
17
+ }
18
+
19
+ // 使用盐值哈希密码
20
+ function hashPassword(password, salt) {
21
+ return crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');
22
+ }
23
+
24
+ // 提示用户输入
25
+ function question(query) {
26
+ return new Promise((resolve) => {
27
+ rl.question(query, resolve);
28
+ });
29
+ }
30
+
31
+ async function main() {
32
+ try {
33
+ console.log('创建管理员账户\n');
34
+
35
+ // 获取用户输入
36
+ const username = await question('请输入管理员用户名: ');
37
+ const password = await question('请输入管理员密码: ');
38
+
39
+ // 生成盐值和密码哈希
40
+ const salt = generateSalt();
41
+ const hash = hashPassword(password, salt);
42
+
43
+ // 创建管理员数据
44
+ const adminData = {
45
+ admin: {
46
+ username,
47
+ salt,
48
+ hash
49
+ }
50
+ };
51
+
52
+ // 确保data目录存在
53
+ const dataDir = path.dirname(ADMIN_FILE);
54
+ if (!fs.existsSync(dataDir)) {
55
+ fs.mkdirSync(dataDir, { recursive: true });
56
+ }
57
+
58
+ // 写入文件
59
+ fs.writeFileSync(ADMIN_FILE, JSON.stringify(adminData, null, 2));
60
+
61
+ console.log('\n管理员账户创建成功!');
62
+ console.log('请妥善保管账户信息,不要将admin.json文件提交到版本控制系统。');
63
+
64
+ } catch (error) {
65
+ console.error('创建管理员账户失败:', error);
66
+ } finally {
67
+ rl.close();
68
+ }
69
+ }
70
+
71
+ main();
src/app.js ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 加载环境变量
2
+ require('dotenv').config();
3
+
4
+ // 环境检查
5
+ const envChecker = require('./utils/envChecker');
6
+ console.log('启动前检查环境配置...');
7
+ envChecker.enforceEnvCheck();
8
+
9
+ const express = require('express');
10
+ const morgan = require('morgan');
11
+ const path = require('path');
12
+ const cron = require('node-cron');
13
+ const app = express();
14
+
15
+ const config = require('./config/config');
16
+ const routes = require('./routes');
17
+ const keyManager = require('./utils/keyManager');
18
+ const cookieRefresher = require('./utils/cookieRefresher');
19
+ const authMiddleware = require('./middleware/auth');
20
+
21
+ // 初始化API Keys
22
+ console.log('初始化API Keys...');
23
+ keyManager.initializeApiKeys();
24
+
25
+ // 输出最终的API Keys配置
26
+ console.log('最终API Keys配置:', JSON.stringify(keyManager.getAllApiKeys().reduce((obj, key) => {
27
+ obj[key] = keyManager.getAllCookiesForApiKey(key);
28
+ return obj;
29
+ }, {}), null, 2));
30
+
31
+ // 添加CORS支持
32
+ app.use((req, res, next) => {
33
+ res.header('Access-Control-Allow-Origin', '*');
34
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
35
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
36
+
37
+ if (req.method === 'OPTIONS') {
38
+ return res.status(200).end();
39
+ }
40
+
41
+ next();
42
+ });
43
+
44
+ app.use(express.json({ limit: '50mb' }));
45
+ app.use(express.urlencoded({ extended: true, limit: '50mb' }));
46
+
47
+ app.use(morgan(process.env.MORGAN_FORMAT ?? 'tiny'));
48
+
49
+ // 添加静态文件支持
50
+ app.use(express.static(path.join(__dirname, 'public')));
51
+
52
+ // 添加根路由,重定向到登录页面
53
+ app.get('/', (req, res) => {
54
+ res.redirect('/login.html');
55
+ });
56
+
57
+ // 添加认证中间件
58
+ app.use(authMiddleware);
59
+
60
+ app.use("/", routes)
61
+
62
+ // 设置定时任务,自动刷新 Cookie
63
+ if (config.refresh.enabled) {
64
+ const cronSchedule = config.refresh.cron;
65
+ const minCookieCount = config.refresh.minCookieCount;
66
+
67
+ console.log(`启用自动刷新 Cookie,定时规则: ${cronSchedule},最小 Cookie 数量: ${minCookieCount}`);
68
+
69
+ cron.schedule(cronSchedule, async () => {
70
+ console.log('===== 自动刷新 Cookie 开始 =====');
71
+ console.log(`最小 Cookie 数量: ${minCookieCount}`);
72
+
73
+ try {
74
+ // 获取所有 API Key
75
+ const apiKeys = keyManager.getAllApiKeys();
76
+
77
+ if (apiKeys.length === 0) {
78
+ console.log('警告: 系统中没有找到任何 API Key');
79
+
80
+ // 检查环境变量中是否有 API Keys
81
+ const envApiKeys = Object.keys(config.apiKeys);
82
+ if (envApiKeys.length > 0) {
83
+ console.log(`检测到环境变量中有 ${envApiKeys.length} 个 API Key,但尚未加载到系统中`);
84
+ console.log('正在重新初始化 API Keys...');
85
+
86
+ // 重新获取 API Keys
87
+ const refreshedApiKeys = keyManager.getAllApiKeys();
88
+ if (refreshedApiKeys.length > 0) {
89
+ console.log(`成功加载 ${refreshedApiKeys.length} 个 API Key,继续刷新流程`);
90
+ // 继续执行后续刷新逻辑
91
+ } else {
92
+ console.log('初始化后仍未找到 API Key,请检查配置');
93
+ console.log('===== 自动刷新 Cookie 结束 =====');
94
+ return;
95
+ }
96
+ } else {
97
+ console.log('环境变量中也没有配置 API Key,请先添加 API Key');
98
+ console.log('===== 自动刷新 Cookie 结束 =====');
99
+ return;
100
+ }
101
+ }
102
+
103
+ // 重新获取最新的 API Keys(可能已经通过上面的初始化更新了)
104
+ const updatedApiKeys = keyManager.getAllApiKeys();
105
+ console.log(`系统中共有 ${updatedApiKeys.length} 个 API Key`);
106
+
107
+ // 按 Cookie 数量排序,优先处理 Cookie 数量少的 API Key
108
+ const sortedKeys = updatedApiKeys.sort((a, b) => {
109
+ const aCount = keyManager.getAllCookiesForApiKey(a).length;
110
+ const bCount = keyManager.getAllCookiesForApiKey(b).length;
111
+ return aCount - bCount; // 升序排列,Cookie 数量少的排在前面
112
+ });
113
+
114
+ // 检查每个 API Key 是否需要刷新
115
+ let refreshedCount = 0;
116
+ let needRefreshCount = 0;
117
+
118
+ for (const apiKey of sortedKeys) {
119
+ const cookies = keyManager.getAllCookiesForApiKey(apiKey);
120
+ console.log(`API Key: ${apiKey}, Cookie 数量: ${cookies.length}`);
121
+
122
+ if (cookies.length < minCookieCount) {
123
+ needRefreshCount++;
124
+ console.log(`API Key ${apiKey} 的 Cookie 数量不足,需要刷新`);
125
+
126
+ // 执行刷新
127
+ console.log(`开始自动刷新 Cookie,目标 API Key: ${apiKey},最小 Cookie 数量: ${minCookieCount}`);
128
+ const result = await cookieRefresher.autoRefreshCookies(apiKey, minCookieCount);
129
+
130
+ if (result.success) {
131
+ refreshedCount++;
132
+ console.log(`刷新结果: ${result.message}`);
133
+ } else {
134
+ console.error(`刷新失败: ${result.message}`);
135
+ }
136
+ } else {
137
+ console.log(`API Key ${apiKey} 的 Cookie 数量足够,不需要刷新`);
138
+ }
139
+ }
140
+
141
+ console.log('===== 自动刷新 Cookie 完成 =====');
142
+ console.log(`共有 ${needRefreshCount} 个 API Key 需要刷新,成功刷新 ${refreshedCount} 个`);
143
+ } catch (error) {
144
+ console.error('自动刷新 Cookie 任务执行失败:', error);
145
+ console.log('===== 自动刷新 Cookie 异常结束 =====');
146
+ }
147
+ });
148
+ } else {
149
+ console.log('未启用自动刷新 Cookie,如需启用请设置环境变量 ENABLE_AUTO_REFRESH=true');
150
+ }
151
+
152
+ app.listen(config.port, () => {
153
+ console.log(`The server listens port: ${config.port}`);
154
+ });
src/config/config.js ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 读取并解析API_KEYS环境变量
2
+ // 解析API Keys配置
3
+ let apiKeysConfig = {};
4
+ try {
5
+ if (process.env.API_KEYS) {
6
+ // 解析API Keys字符串为对象
7
+ apiKeysConfig = JSON.parse(process.env.API_KEYS);
8
+ console.log('正在从环境变量加载API Keys...');
9
+ console.log(`成功解析API Keys,包含 ${Object.keys(apiKeysConfig).length} 个键`);
10
+ } else {
11
+ console.log('警告: 环境变量API_KEYS未设置,系统将无法正常工作');
12
+ }
13
+ } catch (error) {
14
+ console.error('解析API_KEYS环境变量失败:', error.message);
15
+ console.error('请确保API_KEYS是有效的JSON格式');
16
+ }
17
+
18
+ // 导出配置
19
+ module.exports = {
20
+ port: process.env.PORT || 3010,
21
+ apiKeys: apiKeysConfig,
22
+ defaultRotationStrategy: process.env.ROTATION_STRATEGY || 'round-robin',
23
+
24
+ // GitHub相关配置
25
+ github: {
26
+ token: process.env.GITHUB_TOKEN,
27
+ owner: process.env.GITHUB_OWNER,
28
+ repo: process.env.GITHUB_REPO,
29
+ workflowId: process.env.GITHUB_WORKFLOW_ID,
30
+ triggerWorkflow: process.env.TRIGGER_WORKFLOW === 'true'
31
+ },
32
+
33
+ // 工作流参数
34
+ workflowParams: {
35
+ number: parseInt(process.env.REGISTER_NUMBER || '2', 10),
36
+ maxWorkers: parseInt(process.env.REGISTER_MAX_WORKERS || '1', 10),
37
+ emailServer: process.env.REGISTER_EMAIL_SERVER || 'TempEmail',
38
+ ingestToOneapi: process.env.REGISTER_INGEST_TO_ONEAPI === 'true',
39
+ uploadArtifact: process.env.REGISTER_UPLOAD_ARTIFACT === 'true',
40
+ useConfigFile: process.env.REGISTER_USE_CONFIG_FILE !== 'false',
41
+ emailConfigs: process.env.REGISTER_EMAIL_CONFIGS || '[]'
42
+ },
43
+
44
+ // 刷新配置
45
+ refresh: {
46
+ cron: process.env.REFRESH_CRON || '0 */6 * * *',
47
+ minCookieCount: parseInt(process.env.MIN_COOKIE_COUNT || '2', 10),
48
+ enabled: process.env.ENABLE_AUTO_REFRESH === 'true'
49
+ }
50
+ };
src/middleware/auth.js ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const admin = require('../models/admin');
2
+
3
+ // 验证管理员权限的中间件
4
+ function authMiddleware(req, res, next) {
5
+ // 跳过登录相关的路由
6
+ if (req.path.startsWith('/v1/admin/')) {
7
+ return next();
8
+ }
9
+
10
+ // 修改为:只对管理相关的API进行认证
11
+ if (req.path.startsWith('/v1/api-keys') ||
12
+ req.path.startsWith('/v1/invalid-cookies') ||
13
+ req.path.startsWith('/v1/refresh-cookies')) {
14
+ // 获取Authorization头
15
+ const authHeader = req.headers.authorization;
16
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
17
+ return res.status(401).json({
18
+ success: false,
19
+ message: '未提供认证token'
20
+ });
21
+ }
22
+
23
+ // 提取token
24
+ const token = authHeader.split(' ')[1];
25
+
26
+ // 验证token
27
+ const result = admin.verifyToken(token);
28
+ if (!result.success) {
29
+ return res.status(401).json({
30
+ success: false,
31
+ message: '无效的token'
32
+ });
33
+ }
34
+
35
+ // 将用户信息添加到请求对象
36
+ req.admin = {
37
+ username: result.username
38
+ };
39
+ }
40
+
41
+ next();
42
+ }
43
+
44
+ module.exports = authMiddleware;
src/models/admin.js ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const jwt = require('jsonwebtoken');
5
+
6
+ // 管理员数据文件路径
7
+ const ADMIN_FILE = path.join(__dirname, '../../data/admin.json');
8
+ const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
9
+
10
+ // 确保data目录存在
11
+ const dataDir = path.dirname(ADMIN_FILE);
12
+ if (!fs.existsSync(dataDir)) {
13
+ fs.mkdirSync(dataDir, { recursive: true });
14
+ }
15
+
16
+ // 确保admin.json文件存在
17
+ if (!fs.existsSync(ADMIN_FILE)) {
18
+ fs.writeFileSync(ADMIN_FILE, JSON.stringify({ admin: null }), 'utf8');
19
+ }
20
+
21
+ class Admin {
22
+ constructor() {
23
+ this.loadAdmin();
24
+ }
25
+
26
+ // 加载管理员数据
27
+ loadAdmin() {
28
+ try {
29
+ const data = fs.readFileSync(ADMIN_FILE, 'utf8');
30
+ this.admin = JSON.parse(data).admin;
31
+ } catch (error) {
32
+ console.error('加载管理员数据失败:', error);
33
+ this.admin = null;
34
+ }
35
+ }
36
+
37
+ // 保存管理员数据
38
+ saveAdmin() {
39
+ try {
40
+ fs.writeFileSync(ADMIN_FILE, JSON.stringify({ admin: this.admin }), 'utf8');
41
+ } catch (error) {
42
+ console.error('保存管理员数据失败:', error);
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ // 检查是否已有管理员
48
+ hasAdmin() {
49
+ return !!this.admin;
50
+ }
51
+
52
+ // 注册管理员
53
+ register(username, password) {
54
+ if (this.hasAdmin()) {
55
+ throw new Error('已存在管理员账号');
56
+ }
57
+
58
+ // 生成盐值
59
+ const salt = crypto.randomBytes(16).toString('hex');
60
+ // 使用盐值加密密码
61
+ const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');
62
+
63
+ this.admin = {
64
+ username,
65
+ salt,
66
+ hash
67
+ };
68
+
69
+ this.saveAdmin();
70
+ return this.generateToken(username);
71
+ }
72
+
73
+ // 验证密码
74
+ verifyPassword(password, salt, hash) {
75
+ const testHash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');
76
+ return testHash === hash;
77
+ }
78
+
79
+ // 登录验证
80
+ login(username, password) {
81
+ if (!this.admin || username !== this.admin.username) {
82
+ throw new Error('用户名或密码错误');
83
+ }
84
+
85
+ if (!this.verifyPassword(password, this.admin.salt, this.admin.hash)) {
86
+ throw new Error('用户名或密码错误');
87
+ }
88
+
89
+ return this.generateToken(username);
90
+ }
91
+
92
+ // 生成JWT token
93
+ generateToken(username) {
94
+ return jwt.sign({ username }, JWT_SECRET, { expiresIn: '24h' });
95
+ }
96
+
97
+ // 验证JWT token
98
+ verifyToken(token) {
99
+ try {
100
+ const decoded = jwt.verify(token, JWT_SECRET);
101
+ return {
102
+ success: true,
103
+ username: decoded.username
104
+ };
105
+ } catch (error) {
106
+ return {
107
+ success: false,
108
+ error: 'Invalid token'
109
+ };
110
+ }
111
+ }
112
+ }
113
+
114
+ module.exports = new Admin();
src/proto/message.js ADDED
@@ -0,0 +1,1918 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/
2
+ "use strict";
3
+
4
+ var $protobuf = require("protobufjs/minimal");
5
+
6
+ // Common aliases
7
+ var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
8
+
9
+ // Exported root namespace
10
+ var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});
11
+
12
+ $root.ChatMessage = (function() {
13
+
14
+ /**
15
+ * Properties of a ChatMessage.
16
+ * @exports IChatMessage
17
+ * @interface IChatMessage
18
+ * @property {Array.<ChatMessage.IUserMessage>|null} [userMessages] ChatMessage userMessages
19
+ * @property {ChatMessage.IInstructions|null} [instructions] ChatMessage instructions
20
+ * @property {ChatMessage.IModel|null} [model] ChatMessage model
21
+ * @property {number|null} [unknown13] ChatMessage unknown13
22
+ * @property {string|null} [conversationId] ChatMessage conversationId
23
+ * @property {number|null} [unknown16] ChatMessage unknown16
24
+ * @property {number|null} [unknown29] ChatMessage unknown29
25
+ * @property {number|null} [unknown31] ChatMessage unknown31
26
+ */
27
+
28
+ /**
29
+ * Constructs a new ChatMessage.
30
+ * @exports ChatMessage
31
+ * @classdesc Represents a ChatMessage.
32
+ * @implements IChatMessage
33
+ * @constructor
34
+ * @param {IChatMessage=} [properties] Properties to set
35
+ */
36
+ function ChatMessage(properties) {
37
+ this.userMessages = [];
38
+ if (properties)
39
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
40
+ if (properties[keys[i]] != null)
41
+ this[keys[i]] = properties[keys[i]];
42
+ }
43
+
44
+ /**
45
+ * ChatMessage userMessages.
46
+ * @member {Array.<ChatMessage.IUserMessage>} userMessages
47
+ * @memberof ChatMessage
48
+ * @instance
49
+ */
50
+ ChatMessage.prototype.userMessages = $util.emptyArray;
51
+
52
+ /**
53
+ * ChatMessage instructions.
54
+ * @member {ChatMessage.IInstructions|null|undefined} instructions
55
+ * @memberof ChatMessage
56
+ * @instance
57
+ */
58
+ ChatMessage.prototype.instructions = null;
59
+
60
+ /**
61
+ * ChatMessage model.
62
+ * @member {ChatMessage.IModel|null|undefined} model
63
+ * @memberof ChatMessage
64
+ * @instance
65
+ */
66
+ ChatMessage.prototype.model = null;
67
+
68
+ /**
69
+ * ChatMessage unknown13.
70
+ * @member {number} unknown13
71
+ * @memberof ChatMessage
72
+ * @instance
73
+ */
74
+ ChatMessage.prototype.unknown13 = 0;
75
+
76
+ /**
77
+ * ChatMessage conversationId.
78
+ * @member {string} conversationId
79
+ * @memberof ChatMessage
80
+ * @instance
81
+ */
82
+ ChatMessage.prototype.conversationId = "";
83
+
84
+ /**
85
+ * ChatMessage unknown16.
86
+ * @member {number} unknown16
87
+ * @memberof ChatMessage
88
+ * @instance
89
+ */
90
+ ChatMessage.prototype.unknown16 = 0;
91
+
92
+ /**
93
+ * ChatMessage unknown29.
94
+ * @member {number} unknown29
95
+ * @memberof ChatMessage
96
+ * @instance
97
+ */
98
+ ChatMessage.prototype.unknown29 = 0;
99
+
100
+ /**
101
+ * ChatMessage unknown31.
102
+ * @member {number} unknown31
103
+ * @memberof ChatMessage
104
+ * @instance
105
+ */
106
+ ChatMessage.prototype.unknown31 = 0;
107
+
108
+ /**
109
+ * Creates a new ChatMessage instance using the specified properties.
110
+ * @function create
111
+ * @memberof ChatMessage
112
+ * @static
113
+ * @param {IChatMessage=} [properties] Properties to set
114
+ * @returns {ChatMessage} ChatMessage instance
115
+ */
116
+ ChatMessage.create = function create(properties) {
117
+ return new ChatMessage(properties);
118
+ };
119
+
120
+ /**
121
+ * Encodes the specified ChatMessage message. Does not implicitly {@link ChatMessage.verify|verify} messages.
122
+ * @function encode
123
+ * @memberof ChatMessage
124
+ * @static
125
+ * @param {IChatMessage} message ChatMessage message or plain object to encode
126
+ * @param {$protobuf.Writer} [writer] Writer to encode to
127
+ * @returns {$protobuf.Writer} Writer
128
+ */
129
+ ChatMessage.encode = function encode(message, writer) {
130
+ if (!writer)
131
+ writer = $Writer.create();
132
+ if (message.userMessages != null && message.userMessages.length)
133
+ for (var i = 0; i < message.userMessages.length; ++i)
134
+ $root.ChatMessage.UserMessage.encode(message.userMessages[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim();
135
+ if (message.instructions != null && Object.hasOwnProperty.call(message, "instructions"))
136
+ $root.ChatMessage.Instructions.encode(message.instructions, writer.uint32(/* id 4, wireType 2 =*/34).fork()).ldelim();
137
+ if (message.model != null && Object.hasOwnProperty.call(message, "model"))
138
+ $root.ChatMessage.Model.encode(message.model, writer.uint32(/* id 7, wireType 2 =*/58).fork()).ldelim();
139
+ if (message.unknown13 != null && Object.hasOwnProperty.call(message, "unknown13"))
140
+ writer.uint32(/* id 13, wireType 0 =*/104).int32(message.unknown13);
141
+ if (message.conversationId != null && Object.hasOwnProperty.call(message, "conversationId"))
142
+ writer.uint32(/* id 15, wireType 2 =*/122).string(message.conversationId);
143
+ if (message.unknown16 != null && Object.hasOwnProperty.call(message, "unknown16"))
144
+ writer.uint32(/* id 16, wireType 0 =*/128).int32(message.unknown16);
145
+ if (message.unknown29 != null && Object.hasOwnProperty.call(message, "unknown29"))
146
+ writer.uint32(/* id 29, wireType 0 =*/232).int32(message.unknown29);
147
+ if (message.unknown31 != null && Object.hasOwnProperty.call(message, "unknown31"))
148
+ writer.uint32(/* id 31, wireType 0 =*/248).int32(message.unknown31);
149
+ return writer;
150
+ };
151
+
152
+ /**
153
+ * Encodes the specified ChatMessage message, length delimited. Does not implicitly {@link ChatMessage.verify|verify} messages.
154
+ * @function encodeDelimited
155
+ * @memberof ChatMessage
156
+ * @static
157
+ * @param {IChatMessage} message ChatMessage message or plain object to encode
158
+ * @param {$protobuf.Writer} [writer] Writer to encode to
159
+ * @returns {$protobuf.Writer} Writer
160
+ */
161
+ ChatMessage.encodeDelimited = function encodeDelimited(message, writer) {
162
+ return this.encode(message, writer).ldelim();
163
+ };
164
+
165
+ /**
166
+ * Decodes a ChatMessage message from the specified reader or buffer.
167
+ * @function decode
168
+ * @memberof ChatMessage
169
+ * @static
170
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
171
+ * @param {number} [length] Message length if known beforehand
172
+ * @returns {ChatMessage} ChatMessage
173
+ * @throws {Error} If the payload is not a reader or valid buffer
174
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
175
+ */
176
+ ChatMessage.decode = function decode(reader, length) {
177
+ if (!(reader instanceof $Reader))
178
+ reader = $Reader.create(reader);
179
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ChatMessage();
180
+ while (reader.pos < end) {
181
+ var tag = reader.uint32();
182
+ switch (tag >>> 3) {
183
+ case 2: {
184
+ if (!(message.userMessages && message.userMessages.length))
185
+ message.userMessages = [];
186
+ message.userMessages.push($root.ChatMessage.UserMessage.decode(reader, reader.uint32()));
187
+ break;
188
+ }
189
+ case 4: {
190
+ message.instructions = $root.ChatMessage.Instructions.decode(reader, reader.uint32());
191
+ break;
192
+ }
193
+ case 7: {
194
+ message.model = $root.ChatMessage.Model.decode(reader, reader.uint32());
195
+ break;
196
+ }
197
+ case 13: {
198
+ message.unknown13 = reader.int32();
199
+ break;
200
+ }
201
+ case 15: {
202
+ message.conversationId = reader.string();
203
+ break;
204
+ }
205
+ case 16: {
206
+ message.unknown16 = reader.int32();
207
+ break;
208
+ }
209
+ case 29: {
210
+ message.unknown29 = reader.int32();
211
+ break;
212
+ }
213
+ case 31: {
214
+ message.unknown31 = reader.int32();
215
+ break;
216
+ }
217
+ default:
218
+ reader.skipType(tag & 7);
219
+ break;
220
+ }
221
+ }
222
+ return message;
223
+ };
224
+
225
+ /**
226
+ * Decodes a ChatMessage message from the specified reader or buffer, length delimited.
227
+ * @function decodeDelimited
228
+ * @memberof ChatMessage
229
+ * @static
230
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
231
+ * @returns {ChatMessage} ChatMessage
232
+ * @throws {Error} If the payload is not a reader or valid buffer
233
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
234
+ */
235
+ ChatMessage.decodeDelimited = function decodeDelimited(reader) {
236
+ if (!(reader instanceof $Reader))
237
+ reader = new $Reader(reader);
238
+ return this.decode(reader, reader.uint32());
239
+ };
240
+
241
+ /**
242
+ * Verifies a ChatMessage message.
243
+ * @function verify
244
+ * @memberof ChatMessage
245
+ * @static
246
+ * @param {Object.<string,*>} message Plain object to verify
247
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
248
+ */
249
+ ChatMessage.verify = function verify(message) {
250
+ if (typeof message !== "object" || message === null)
251
+ return "object expected";
252
+ if (message.userMessages != null && message.hasOwnProperty("userMessages")) {
253
+ if (!Array.isArray(message.userMessages))
254
+ return "userMessages: array expected";
255
+ for (var i = 0; i < message.userMessages.length; ++i) {
256
+ var error = $root.ChatMessage.UserMessage.verify(message.userMessages[i]);
257
+ if (error)
258
+ return "userMessages." + error;
259
+ }
260
+ }
261
+ if (message.instructions != null && message.hasOwnProperty("instructions")) {
262
+ var error = $root.ChatMessage.Instructions.verify(message.instructions);
263
+ if (error)
264
+ return "instructions." + error;
265
+ }
266
+ if (message.model != null && message.hasOwnProperty("model")) {
267
+ var error = $root.ChatMessage.Model.verify(message.model);
268
+ if (error)
269
+ return "model." + error;
270
+ }
271
+ if (message.unknown13 != null && message.hasOwnProperty("unknown13"))
272
+ if (!$util.isInteger(message.unknown13))
273
+ return "unknown13: integer expected";
274
+ if (message.conversationId != null && message.hasOwnProperty("conversationId"))
275
+ if (!$util.isString(message.conversationId))
276
+ return "conversationId: string expected";
277
+ if (message.unknown16 != null && message.hasOwnProperty("unknown16"))
278
+ if (!$util.isInteger(message.unknown16))
279
+ return "unknown16: integer expected";
280
+ if (message.unknown29 != null && message.hasOwnProperty("unknown29"))
281
+ if (!$util.isInteger(message.unknown29))
282
+ return "unknown29: integer expected";
283
+ if (message.unknown31 != null && message.hasOwnProperty("unknown31"))
284
+ if (!$util.isInteger(message.unknown31))
285
+ return "unknown31: integer expected";
286
+ return null;
287
+ };
288
+
289
+ /**
290
+ * Creates a ChatMessage message from a plain object. Also converts values to their respective internal types.
291
+ * @function fromObject
292
+ * @memberof ChatMessage
293
+ * @static
294
+ * @param {Object.<string,*>} object Plain object
295
+ * @returns {ChatMessage} ChatMessage
296
+ */
297
+ ChatMessage.fromObject = function fromObject(object) {
298
+ if (object instanceof $root.ChatMessage)
299
+ return object;
300
+ var message = new $root.ChatMessage();
301
+ if (object.userMessages) {
302
+ if (!Array.isArray(object.userMessages))
303
+ throw TypeError(".ChatMessage.userMessages: array expected");
304
+ message.userMessages = [];
305
+ for (var i = 0; i < object.userMessages.length; ++i) {
306
+ if (typeof object.userMessages[i] !== "object")
307
+ throw TypeError(".ChatMessage.userMessages: object expected");
308
+ message.userMessages[i] = $root.ChatMessage.UserMessage.fromObject(object.userMessages[i]);
309
+ }
310
+ }
311
+ if (object.instructions != null) {
312
+ if (typeof object.instructions !== "object")
313
+ throw TypeError(".ChatMessage.instructions: object expected");
314
+ message.instructions = $root.ChatMessage.Instructions.fromObject(object.instructions);
315
+ }
316
+ if (object.model != null) {
317
+ if (typeof object.model !== "object")
318
+ throw TypeError(".ChatMessage.model: object expected");
319
+ message.model = $root.ChatMessage.Model.fromObject(object.model);
320
+ }
321
+ if (object.unknown13 != null)
322
+ message.unknown13 = object.unknown13 | 0;
323
+ if (object.conversationId != null)
324
+ message.conversationId = String(object.conversationId);
325
+ if (object.unknown16 != null)
326
+ message.unknown16 = object.unknown16 | 0;
327
+ if (object.unknown29 != null)
328
+ message.unknown29 = object.unknown29 | 0;
329
+ if (object.unknown31 != null)
330
+ message.unknown31 = object.unknown31 | 0;
331
+ return message;
332
+ };
333
+
334
+ /**
335
+ * Creates a plain object from a ChatMessage message. Also converts values to other types if specified.
336
+ * @function toObject
337
+ * @memberof ChatMessage
338
+ * @static
339
+ * @param {ChatMessage} message ChatMessage
340
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
341
+ * @returns {Object.<string,*>} Plain object
342
+ */
343
+ ChatMessage.toObject = function toObject(message, options) {
344
+ if (!options)
345
+ options = {};
346
+ var object = {};
347
+ if (options.arrays || options.defaults)
348
+ object.userMessages = [];
349
+ if (options.defaults) {
350
+ object.instructions = null;
351
+ object.model = null;
352
+ object.unknown13 = 0;
353
+ object.conversationId = "";
354
+ object.unknown16 = 0;
355
+ object.unknown29 = 0;
356
+ object.unknown31 = 0;
357
+ }
358
+ if (message.userMessages && message.userMessages.length) {
359
+ object.userMessages = [];
360
+ for (var j = 0; j < message.userMessages.length; ++j)
361
+ object.userMessages[j] = $root.ChatMessage.UserMessage.toObject(message.userMessages[j], options);
362
+ }
363
+ if (message.instructions != null && message.hasOwnProperty("instructions"))
364
+ object.instructions = $root.ChatMessage.Instructions.toObject(message.instructions, options);
365
+ if (message.model != null && message.hasOwnProperty("model"))
366
+ object.model = $root.ChatMessage.Model.toObject(message.model, options);
367
+ if (message.unknown13 != null && message.hasOwnProperty("unknown13"))
368
+ object.unknown13 = message.unknown13;
369
+ if (message.conversationId != null && message.hasOwnProperty("conversationId"))
370
+ object.conversationId = message.conversationId;
371
+ if (message.unknown16 != null && message.hasOwnProperty("unknown16"))
372
+ object.unknown16 = message.unknown16;
373
+ if (message.unknown29 != null && message.hasOwnProperty("unknown29"))
374
+ object.unknown29 = message.unknown29;
375
+ if (message.unknown31 != null && message.hasOwnProperty("unknown31"))
376
+ object.unknown31 = message.unknown31;
377
+ return object;
378
+ };
379
+
380
+ /**
381
+ * Converts this ChatMessage to JSON.
382
+ * @function toJSON
383
+ * @memberof ChatMessage
384
+ * @instance
385
+ * @returns {Object.<string,*>} JSON object
386
+ */
387
+ ChatMessage.prototype.toJSON = function toJSON() {
388
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
389
+ };
390
+
391
+ /**
392
+ * Gets the default type url for ChatMessage
393
+ * @function getTypeUrl
394
+ * @memberof ChatMessage
395
+ * @static
396
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
397
+ * @returns {string} The default type url
398
+ */
399
+ ChatMessage.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
400
+ if (typeUrlPrefix === undefined) {
401
+ typeUrlPrefix = "type.googleapis.com";
402
+ }
403
+ return typeUrlPrefix + "/ChatMessage";
404
+ };
405
+
406
+ ChatMessage.UserMessage = (function() {
407
+
408
+ /**
409
+ * Properties of a UserMessage.
410
+ * @memberof ChatMessage
411
+ * @interface IUserMessage
412
+ * @property {string|null} [content] UserMessage content
413
+ * @property {number|null} [role] UserMessage role
414
+ * @property {string|null} [messageId] UserMessage messageId
415
+ */
416
+
417
+ /**
418
+ * Constructs a new UserMessage.
419
+ * @memberof ChatMessage
420
+ * @classdesc Represents a UserMessage.
421
+ * @implements IUserMessage
422
+ * @constructor
423
+ * @param {ChatMessage.IUserMessage=} [properties] Properties to set
424
+ */
425
+ function UserMessage(properties) {
426
+ if (properties)
427
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
428
+ if (properties[keys[i]] != null)
429
+ this[keys[i]] = properties[keys[i]];
430
+ }
431
+
432
+ /**
433
+ * UserMessage content.
434
+ * @member {string} content
435
+ * @memberof ChatMessage.UserMessage
436
+ * @instance
437
+ */
438
+ UserMessage.prototype.content = "";
439
+
440
+ /**
441
+ * UserMessage role.
442
+ * @member {number} role
443
+ * @memberof ChatMessage.UserMessage
444
+ * @instance
445
+ */
446
+ UserMessage.prototype.role = 0;
447
+
448
+ /**
449
+ * UserMessage messageId.
450
+ * @member {string} messageId
451
+ * @memberof ChatMessage.UserMessage
452
+ * @instance
453
+ */
454
+ UserMessage.prototype.messageId = "";
455
+
456
+ /**
457
+ * Creates a new UserMessage instance using the specified properties.
458
+ * @function create
459
+ * @memberof ChatMessage.UserMessage
460
+ * @static
461
+ * @param {ChatMessage.IUserMessage=} [properties] Properties to set
462
+ * @returns {ChatMessage.UserMessage} UserMessage instance
463
+ */
464
+ UserMessage.create = function create(properties) {
465
+ return new UserMessage(properties);
466
+ };
467
+
468
+ /**
469
+ * Encodes the specified UserMessage message. Does not implicitly {@link ChatMessage.UserMessage.verify|verify} messages.
470
+ * @function encode
471
+ * @memberof ChatMessage.UserMessage
472
+ * @static
473
+ * @param {ChatMessage.IUserMessage} message UserMessage message or plain object to encode
474
+ * @param {$protobuf.Writer} [writer] Writer to encode to
475
+ * @returns {$protobuf.Writer} Writer
476
+ */
477
+ UserMessage.encode = function encode(message, writer) {
478
+ if (!writer)
479
+ writer = $Writer.create();
480
+ if (message.content != null && Object.hasOwnProperty.call(message, "content"))
481
+ writer.uint32(/* id 1, wireType 2 =*/10).string(message.content);
482
+ if (message.role != null && Object.hasOwnProperty.call(message, "role"))
483
+ writer.uint32(/* id 2, wireType 0 =*/16).int32(message.role);
484
+ if (message.messageId != null && Object.hasOwnProperty.call(message, "messageId"))
485
+ writer.uint32(/* id 13, wireType 2 =*/106).string(message.messageId);
486
+ return writer;
487
+ };
488
+
489
+ /**
490
+ * Encodes the specified UserMessage message, length delimited. Does not implicitly {@link ChatMessage.UserMessage.verify|verify} messages.
491
+ * @function encodeDelimited
492
+ * @memberof ChatMessage.UserMessage
493
+ * @static
494
+ * @param {ChatMessage.IUserMessage} message UserMessage message or plain object to encode
495
+ * @param {$protobuf.Writer} [writer] Writer to encode to
496
+ * @returns {$protobuf.Writer} Writer
497
+ */
498
+ UserMessage.encodeDelimited = function encodeDelimited(message, writer) {
499
+ return this.encode(message, writer).ldelim();
500
+ };
501
+
502
+ /**
503
+ * Decodes a UserMessage message from the specified reader or buffer.
504
+ * @function decode
505
+ * @memberof ChatMessage.UserMessage
506
+ * @static
507
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
508
+ * @param {number} [length] Message length if known beforehand
509
+ * @returns {ChatMessage.UserMessage} UserMessage
510
+ * @throws {Error} If the payload is not a reader or valid buffer
511
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
512
+ */
513
+ UserMessage.decode = function decode(reader, length) {
514
+ if (!(reader instanceof $Reader))
515
+ reader = $Reader.create(reader);
516
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ChatMessage.UserMessage();
517
+ while (reader.pos < end) {
518
+ var tag = reader.uint32();
519
+ switch (tag >>> 3) {
520
+ case 1: {
521
+ message.content = reader.string();
522
+ break;
523
+ }
524
+ case 2: {
525
+ message.role = reader.int32();
526
+ break;
527
+ }
528
+ case 13: {
529
+ message.messageId = reader.string();
530
+ break;
531
+ }
532
+ default:
533
+ reader.skipType(tag & 7);
534
+ break;
535
+ }
536
+ }
537
+ return message;
538
+ };
539
+
540
+ /**
541
+ * Decodes a UserMessage message from the specified reader or buffer, length delimited.
542
+ * @function decodeDelimited
543
+ * @memberof ChatMessage.UserMessage
544
+ * @static
545
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
546
+ * @returns {ChatMessage.UserMessage} UserMessage
547
+ * @throws {Error} If the payload is not a reader or valid buffer
548
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
549
+ */
550
+ UserMessage.decodeDelimited = function decodeDelimited(reader) {
551
+ if (!(reader instanceof $Reader))
552
+ reader = new $Reader(reader);
553
+ return this.decode(reader, reader.uint32());
554
+ };
555
+
556
+ /**
557
+ * Verifies a UserMessage message.
558
+ * @function verify
559
+ * @memberof ChatMessage.UserMessage
560
+ * @static
561
+ * @param {Object.<string,*>} message Plain object to verify
562
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
563
+ */
564
+ UserMessage.verify = function verify(message) {
565
+ if (typeof message !== "object" || message === null)
566
+ return "object expected";
567
+ if (message.content != null && message.hasOwnProperty("content"))
568
+ if (!$util.isString(message.content))
569
+ return "content: string expected";
570
+ if (message.role != null && message.hasOwnProperty("role"))
571
+ if (!$util.isInteger(message.role))
572
+ return "role: integer expected";
573
+ if (message.messageId != null && message.hasOwnProperty("messageId"))
574
+ if (!$util.isString(message.messageId))
575
+ return "messageId: string expected";
576
+ return null;
577
+ };
578
+
579
+ /**
580
+ * Creates a UserMessage message from a plain object. Also converts values to their respective internal types.
581
+ * @function fromObject
582
+ * @memberof ChatMessage.UserMessage
583
+ * @static
584
+ * @param {Object.<string,*>} object Plain object
585
+ * @returns {ChatMessage.UserMessage} UserMessage
586
+ */
587
+ UserMessage.fromObject = function fromObject(object) {
588
+ if (object instanceof $root.ChatMessage.UserMessage)
589
+ return object;
590
+ var message = new $root.ChatMessage.UserMessage();
591
+ if (object.content != null)
592
+ message.content = String(object.content);
593
+ if (object.role != null)
594
+ message.role = object.role | 0;
595
+ if (object.messageId != null)
596
+ message.messageId = String(object.messageId);
597
+ return message;
598
+ };
599
+
600
+ /**
601
+ * Creates a plain object from a UserMessage message. Also converts values to other types if specified.
602
+ * @function toObject
603
+ * @memberof ChatMessage.UserMessage
604
+ * @static
605
+ * @param {ChatMessage.UserMessage} message UserMessage
606
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
607
+ * @returns {Object.<string,*>} Plain object
608
+ */
609
+ UserMessage.toObject = function toObject(message, options) {
610
+ if (!options)
611
+ options = {};
612
+ var object = {};
613
+ if (options.defaults) {
614
+ object.content = "";
615
+ object.role = 0;
616
+ object.messageId = "";
617
+ }
618
+ if (message.content != null && message.hasOwnProperty("content"))
619
+ object.content = message.content;
620
+ if (message.role != null && message.hasOwnProperty("role"))
621
+ object.role = message.role;
622
+ if (message.messageId != null && message.hasOwnProperty("messageId"))
623
+ object.messageId = message.messageId;
624
+ return object;
625
+ };
626
+
627
+ /**
628
+ * Converts this UserMessage to JSON.
629
+ * @function toJSON
630
+ * @memberof ChatMessage.UserMessage
631
+ * @instance
632
+ * @returns {Object.<string,*>} JSON object
633
+ */
634
+ UserMessage.prototype.toJSON = function toJSON() {
635
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
636
+ };
637
+
638
+ /**
639
+ * Gets the default type url for UserMessage
640
+ * @function getTypeUrl
641
+ * @memberof ChatMessage.UserMessage
642
+ * @static
643
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
644
+ * @returns {string} The default type url
645
+ */
646
+ UserMessage.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
647
+ if (typeUrlPrefix === undefined) {
648
+ typeUrlPrefix = "type.googleapis.com";
649
+ }
650
+ return typeUrlPrefix + "/ChatMessage.UserMessage";
651
+ };
652
+
653
+ return UserMessage;
654
+ })();
655
+
656
+ ChatMessage.Instructions = (function() {
657
+
658
+ /**
659
+ * Properties of an Instructions.
660
+ * @memberof ChatMessage
661
+ * @interface IInstructions
662
+ * @property {string|null} [instruction] Instructions instruction
663
+ */
664
+
665
+ /**
666
+ * Constructs a new Instructions.
667
+ * @memberof ChatMessage
668
+ * @classdesc Represents an Instructions.
669
+ * @implements IInstructions
670
+ * @constructor
671
+ * @param {ChatMessage.IInstructions=} [properties] Properties to set
672
+ */
673
+ function Instructions(properties) {
674
+ if (properties)
675
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
676
+ if (properties[keys[i]] != null)
677
+ this[keys[i]] = properties[keys[i]];
678
+ }
679
+
680
+ /**
681
+ * Instructions instruction.
682
+ * @member {string} instruction
683
+ * @memberof ChatMessage.Instructions
684
+ * @instance
685
+ */
686
+ Instructions.prototype.instruction = "";
687
+
688
+ /**
689
+ * Creates a new Instructions instance using the specified properties.
690
+ * @function create
691
+ * @memberof ChatMessage.Instructions
692
+ * @static
693
+ * @param {ChatMessage.IInstructions=} [properties] Properties to set
694
+ * @returns {ChatMessage.Instructions} Instructions instance
695
+ */
696
+ Instructions.create = function create(properties) {
697
+ return new Instructions(properties);
698
+ };
699
+
700
+ /**
701
+ * Encodes the specified Instructions message. Does not implicitly {@link ChatMessage.Instructions.verify|verify} messages.
702
+ * @function encode
703
+ * @memberof ChatMessage.Instructions
704
+ * @static
705
+ * @param {ChatMessage.IInstructions} message Instructions message or plain object to encode
706
+ * @param {$protobuf.Writer} [writer] Writer to encode to
707
+ * @returns {$protobuf.Writer} Writer
708
+ */
709
+ Instructions.encode = function encode(message, writer) {
710
+ if (!writer)
711
+ writer = $Writer.create();
712
+ if (message.instruction != null && Object.hasOwnProperty.call(message, "instruction"))
713
+ writer.uint32(/* id 1, wireType 2 =*/10).string(message.instruction);
714
+ return writer;
715
+ };
716
+
717
+ /**
718
+ * Encodes the specified Instructions message, length delimited. Does not implicitly {@link ChatMessage.Instructions.verify|verify} messages.
719
+ * @function encodeDelimited
720
+ * @memberof ChatMessage.Instructions
721
+ * @static
722
+ * @param {ChatMessage.IInstructions} message Instructions message or plain object to encode
723
+ * @param {$protobuf.Writer} [writer] Writer to encode to
724
+ * @returns {$protobuf.Writer} Writer
725
+ */
726
+ Instructions.encodeDelimited = function encodeDelimited(message, writer) {
727
+ return this.encode(message, writer).ldelim();
728
+ };
729
+
730
+ /**
731
+ * Decodes an Instructions message from the specified reader or buffer.
732
+ * @function decode
733
+ * @memberof ChatMessage.Instructions
734
+ * @static
735
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
736
+ * @param {number} [length] Message length if known beforehand
737
+ * @returns {ChatMessage.Instructions} Instructions
738
+ * @throws {Error} If the payload is not a reader or valid buffer
739
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
740
+ */
741
+ Instructions.decode = function decode(reader, length) {
742
+ if (!(reader instanceof $Reader))
743
+ reader = $Reader.create(reader);
744
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ChatMessage.Instructions();
745
+ while (reader.pos < end) {
746
+ var tag = reader.uint32();
747
+ switch (tag >>> 3) {
748
+ case 1: {
749
+ message.instruction = reader.string();
750
+ break;
751
+ }
752
+ default:
753
+ reader.skipType(tag & 7);
754
+ break;
755
+ }
756
+ }
757
+ return message;
758
+ };
759
+
760
+ /**
761
+ * Decodes an Instructions message from the specified reader or buffer, length delimited.
762
+ * @function decodeDelimited
763
+ * @memberof ChatMessage.Instructions
764
+ * @static
765
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
766
+ * @returns {ChatMessage.Instructions} Instructions
767
+ * @throws {Error} If the payload is not a reader or valid buffer
768
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
769
+ */
770
+ Instructions.decodeDelimited = function decodeDelimited(reader) {
771
+ if (!(reader instanceof $Reader))
772
+ reader = new $Reader(reader);
773
+ return this.decode(reader, reader.uint32());
774
+ };
775
+
776
+ /**
777
+ * Verifies an Instructions message.
778
+ * @function verify
779
+ * @memberof ChatMessage.Instructions
780
+ * @static
781
+ * @param {Object.<string,*>} message Plain object to verify
782
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
783
+ */
784
+ Instructions.verify = function verify(message) {
785
+ if (typeof message !== "object" || message === null)
786
+ return "object expected";
787
+ if (message.instruction != null && message.hasOwnProperty("instruction"))
788
+ if (!$util.isString(message.instruction))
789
+ return "instruction: string expected";
790
+ return null;
791
+ };
792
+
793
+ /**
794
+ * Creates an Instructions message from a plain object. Also converts values to their respective internal types.
795
+ * @function fromObject
796
+ * @memberof ChatMessage.Instructions
797
+ * @static
798
+ * @param {Object.<string,*>} object Plain object
799
+ * @returns {ChatMessage.Instructions} Instructions
800
+ */
801
+ Instructions.fromObject = function fromObject(object) {
802
+ if (object instanceof $root.ChatMessage.Instructions)
803
+ return object;
804
+ var message = new $root.ChatMessage.Instructions();
805
+ if (object.instruction != null)
806
+ message.instruction = String(object.instruction);
807
+ return message;
808
+ };
809
+
810
+ /**
811
+ * Creates a plain object from an Instructions message. Also converts values to other types if specified.
812
+ * @function toObject
813
+ * @memberof ChatMessage.Instructions
814
+ * @static
815
+ * @param {ChatMessage.Instructions} message Instructions
816
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
817
+ * @returns {Object.<string,*>} Plain object
818
+ */
819
+ Instructions.toObject = function toObject(message, options) {
820
+ if (!options)
821
+ options = {};
822
+ var object = {};
823
+ if (options.defaults)
824
+ object.instruction = "";
825
+ if (message.instruction != null && message.hasOwnProperty("instruction"))
826
+ object.instruction = message.instruction;
827
+ return object;
828
+ };
829
+
830
+ /**
831
+ * Converts this Instructions to JSON.
832
+ * @function toJSON
833
+ * @memberof ChatMessage.Instructions
834
+ * @instance
835
+ * @returns {Object.<string,*>} JSON object
836
+ */
837
+ Instructions.prototype.toJSON = function toJSON() {
838
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
839
+ };
840
+
841
+ /**
842
+ * Gets the default type url for Instructions
843
+ * @function getTypeUrl
844
+ * @memberof ChatMessage.Instructions
845
+ * @static
846
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
847
+ * @returns {string} The default type url
848
+ */
849
+ Instructions.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
850
+ if (typeUrlPrefix === undefined) {
851
+ typeUrlPrefix = "type.googleapis.com";
852
+ }
853
+ return typeUrlPrefix + "/ChatMessage.Instructions";
854
+ };
855
+
856
+ return Instructions;
857
+ })();
858
+
859
+ ChatMessage.Model = (function() {
860
+
861
+ /**
862
+ * Properties of a Model.
863
+ * @memberof ChatMessage
864
+ * @interface IModel
865
+ * @property {string|null} [name] Model name
866
+ * @property {Uint8Array|null} [empty] Model empty
867
+ */
868
+
869
+ /**
870
+ * Constructs a new Model.
871
+ * @memberof ChatMessage
872
+ * @classdesc Represents a Model.
873
+ * @implements IModel
874
+ * @constructor
875
+ * @param {ChatMessage.IModel=} [properties] Properties to set
876
+ */
877
+ function Model(properties) {
878
+ if (properties)
879
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
880
+ if (properties[keys[i]] != null)
881
+ this[keys[i]] = properties[keys[i]];
882
+ }
883
+
884
+ /**
885
+ * Model name.
886
+ * @member {string} name
887
+ * @memberof ChatMessage.Model
888
+ * @instance
889
+ */
890
+ Model.prototype.name = "";
891
+
892
+ /**
893
+ * Model empty.
894
+ * @member {Uint8Array} empty
895
+ * @memberof ChatMessage.Model
896
+ * @instance
897
+ */
898
+ Model.prototype.empty = $util.newBuffer([]);
899
+
900
+ /**
901
+ * Creates a new Model instance using the specified properties.
902
+ * @function create
903
+ * @memberof ChatMessage.Model
904
+ * @static
905
+ * @param {ChatMessage.IModel=} [properties] Properties to set
906
+ * @returns {ChatMessage.Model} Model instance
907
+ */
908
+ Model.create = function create(properties) {
909
+ return new Model(properties);
910
+ };
911
+
912
+ /**
913
+ * Encodes the specified Model message. Does not implicitly {@link ChatMessage.Model.verify|verify} messages.
914
+ * @function encode
915
+ * @memberof ChatMessage.Model
916
+ * @static
917
+ * @param {ChatMessage.IModel} message Model message or plain object to encode
918
+ * @param {$protobuf.Writer} [writer] Writer to encode to
919
+ * @returns {$protobuf.Writer} Writer
920
+ */
921
+ Model.encode = function encode(message, writer) {
922
+ if (!writer)
923
+ writer = $Writer.create();
924
+ if (message.name != null && Object.hasOwnProperty.call(message, "name"))
925
+ writer.uint32(/* id 1, wireType 2 =*/10).string(message.name);
926
+ if (message.empty != null && Object.hasOwnProperty.call(message, "empty"))
927
+ writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.empty);
928
+ return writer;
929
+ };
930
+
931
+ /**
932
+ * Encodes the specified Model message, length delimited. Does not implicitly {@link ChatMessage.Model.verify|verify} messages.
933
+ * @function encodeDelimited
934
+ * @memberof ChatMessage.Model
935
+ * @static
936
+ * @param {ChatMessage.IModel} message Model message or plain object to encode
937
+ * @param {$protobuf.Writer} [writer] Writer to encode to
938
+ * @returns {$protobuf.Writer} Writer
939
+ */
940
+ Model.encodeDelimited = function encodeDelimited(message, writer) {
941
+ return this.encode(message, writer).ldelim();
942
+ };
943
+
944
+ /**
945
+ * Decodes a Model message from the specified reader or buffer.
946
+ * @function decode
947
+ * @memberof ChatMessage.Model
948
+ * @static
949
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
950
+ * @param {number} [length] Message length if known beforehand
951
+ * @returns {ChatMessage.Model} Model
952
+ * @throws {Error} If the payload is not a reader or valid buffer
953
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
954
+ */
955
+ Model.decode = function decode(reader, length) {
956
+ if (!(reader instanceof $Reader))
957
+ reader = $Reader.create(reader);
958
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ChatMessage.Model();
959
+ while (reader.pos < end) {
960
+ var tag = reader.uint32();
961
+ switch (tag >>> 3) {
962
+ case 1: {
963
+ message.name = reader.string();
964
+ break;
965
+ }
966
+ case 4: {
967
+ message.empty = reader.bytes();
968
+ break;
969
+ }
970
+ default:
971
+ reader.skipType(tag & 7);
972
+ break;
973
+ }
974
+ }
975
+ return message;
976
+ };
977
+
978
+ /**
979
+ * Decodes a Model message from the specified reader or buffer, length delimited.
980
+ * @function decodeDelimited
981
+ * @memberof ChatMessage.Model
982
+ * @static
983
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
984
+ * @returns {ChatMessage.Model} Model
985
+ * @throws {Error} If the payload is not a reader or valid buffer
986
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
987
+ */
988
+ Model.decodeDelimited = function decodeDelimited(reader) {
989
+ if (!(reader instanceof $Reader))
990
+ reader = new $Reader(reader);
991
+ return this.decode(reader, reader.uint32());
992
+ };
993
+
994
+ /**
995
+ * Verifies a Model message.
996
+ * @function verify
997
+ * @memberof ChatMessage.Model
998
+ * @static
999
+ * @param {Object.<string,*>} message Plain object to verify
1000
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
1001
+ */
1002
+ Model.verify = function verify(message) {
1003
+ if (typeof message !== "object" || message === null)
1004
+ return "object expected";
1005
+ if (message.name != null && message.hasOwnProperty("name"))
1006
+ if (!$util.isString(message.name))
1007
+ return "name: string expected";
1008
+ if (message.empty != null && message.hasOwnProperty("empty"))
1009
+ if (!(message.empty && typeof message.empty.length === "number" || $util.isString(message.empty)))
1010
+ return "empty: buffer expected";
1011
+ return null;
1012
+ };
1013
+
1014
+ /**
1015
+ * Creates a Model message from a plain object. Also converts values to their respective internal types.
1016
+ * @function fromObject
1017
+ * @memberof ChatMessage.Model
1018
+ * @static
1019
+ * @param {Object.<string,*>} object Plain object
1020
+ * @returns {ChatMessage.Model} Model
1021
+ */
1022
+ Model.fromObject = function fromObject(object) {
1023
+ if (object instanceof $root.ChatMessage.Model)
1024
+ return object;
1025
+ var message = new $root.ChatMessage.Model();
1026
+ if (object.name != null)
1027
+ message.name = String(object.name);
1028
+ if (object.empty != null)
1029
+ if (typeof object.empty === "string")
1030
+ $util.base64.decode(object.empty, message.empty = $util.newBuffer($util.base64.length(object.empty)), 0);
1031
+ else if (object.empty.length >= 0)
1032
+ message.empty = object.empty;
1033
+ return message;
1034
+ };
1035
+
1036
+ /**
1037
+ * Creates a plain object from a Model message. Also converts values to other types if specified.
1038
+ * @function toObject
1039
+ * @memberof ChatMessage.Model
1040
+ * @static
1041
+ * @param {ChatMessage.Model} message Model
1042
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
1043
+ * @returns {Object.<string,*>} Plain object
1044
+ */
1045
+ Model.toObject = function toObject(message, options) {
1046
+ if (!options)
1047
+ options = {};
1048
+ var object = {};
1049
+ if (options.defaults) {
1050
+ object.name = "";
1051
+ if (options.bytes === String)
1052
+ object.empty = "";
1053
+ else {
1054
+ object.empty = [];
1055
+ if (options.bytes !== Array)
1056
+ object.empty = $util.newBuffer(object.empty);
1057
+ }
1058
+ }
1059
+ if (message.name != null && message.hasOwnProperty("name"))
1060
+ object.name = message.name;
1061
+ if (message.empty != null && message.hasOwnProperty("empty"))
1062
+ object.empty = options.bytes === String ? $util.base64.encode(message.empty, 0, message.empty.length) : options.bytes === Array ? Array.prototype.slice.call(message.empty) : message.empty;
1063
+ return object;
1064
+ };
1065
+
1066
+ /**
1067
+ * Converts this Model to JSON.
1068
+ * @function toJSON
1069
+ * @memberof ChatMessage.Model
1070
+ * @instance
1071
+ * @returns {Object.<string,*>} JSON object
1072
+ */
1073
+ Model.prototype.toJSON = function toJSON() {
1074
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
1075
+ };
1076
+
1077
+ /**
1078
+ * Gets the default type url for Model
1079
+ * @function getTypeUrl
1080
+ * @memberof ChatMessage.Model
1081
+ * @static
1082
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
1083
+ * @returns {string} The default type url
1084
+ */
1085
+ Model.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
1086
+ if (typeUrlPrefix === undefined) {
1087
+ typeUrlPrefix = "type.googleapis.com";
1088
+ }
1089
+ return typeUrlPrefix + "/ChatMessage.Model";
1090
+ };
1091
+
1092
+ return Model;
1093
+ })();
1094
+
1095
+ return ChatMessage;
1096
+ })();
1097
+
1098
+ $root.ResMessage = (function() {
1099
+
1100
+ /**
1101
+ * Properties of a ResMessage.
1102
+ * @exports IResMessage
1103
+ * @interface IResMessage
1104
+ * @property {string|null} [content] ResMessage content
1105
+ * @property {Uint8Array|null} [empty] ResMessage empty
1106
+ * @property {string|null} [prompt] ResMessage prompt
1107
+ */
1108
+
1109
+ /**
1110
+ * Constructs a new ResMessage.
1111
+ * @exports ResMessage
1112
+ * @classdesc Represents a ResMessage.
1113
+ * @implements IResMessage
1114
+ * @constructor
1115
+ * @param {IResMessage=} [properties] Properties to set
1116
+ */
1117
+ function ResMessage(properties) {
1118
+ if (properties)
1119
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
1120
+ if (properties[keys[i]] != null)
1121
+ this[keys[i]] = properties[keys[i]];
1122
+ }
1123
+
1124
+ /**
1125
+ * ResMessage content.
1126
+ * @member {string} content
1127
+ * @memberof ResMessage
1128
+ * @instance
1129
+ */
1130
+ ResMessage.prototype.content = "";
1131
+
1132
+ /**
1133
+ * ResMessage empty.
1134
+ * @member {Uint8Array} empty
1135
+ * @memberof ResMessage
1136
+ * @instance
1137
+ */
1138
+ ResMessage.prototype.empty = $util.newBuffer([]);
1139
+
1140
+ /**
1141
+ * ResMessage prompt.
1142
+ * @member {string} prompt
1143
+ * @memberof ResMessage
1144
+ * @instance
1145
+ */
1146
+ ResMessage.prototype.prompt = "";
1147
+
1148
+ /**
1149
+ * Creates a new ResMessage instance using the specified properties.
1150
+ * @function create
1151
+ * @memberof ResMessage
1152
+ * @static
1153
+ * @param {IResMessage=} [properties] Properties to set
1154
+ * @returns {ResMessage} ResMessage instance
1155
+ */
1156
+ ResMessage.create = function create(properties) {
1157
+ return new ResMessage(properties);
1158
+ };
1159
+
1160
+ /**
1161
+ * Encodes the specified ResMessage message. Does not implicitly {@link ResMessage.verify|verify} messages.
1162
+ * @function encode
1163
+ * @memberof ResMessage
1164
+ * @static
1165
+ * @param {IResMessage} message ResMessage message or plain object to encode
1166
+ * @param {$protobuf.Writer} [writer] Writer to encode to
1167
+ * @returns {$protobuf.Writer} Writer
1168
+ */
1169
+ ResMessage.encode = function encode(message, writer) {
1170
+ if (!writer)
1171
+ writer = $Writer.create();
1172
+ if (message.content != null && Object.hasOwnProperty.call(message, "content"))
1173
+ writer.uint32(/* id 1, wireType 2 =*/10).string(message.content);
1174
+ if (message.empty != null && Object.hasOwnProperty.call(message, "empty"))
1175
+ writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.empty);
1176
+ if (message.prompt != null && Object.hasOwnProperty.call(message, "prompt"))
1177
+ writer.uint32(/* id 5, wireType 2 =*/42).string(message.prompt);
1178
+ return writer;
1179
+ };
1180
+
1181
+ /**
1182
+ * Encodes the specified ResMessage message, length delimited. Does not implicitly {@link ResMessage.verify|verify} messages.
1183
+ * @function encodeDelimited
1184
+ * @memberof ResMessage
1185
+ * @static
1186
+ * @param {IResMessage} message ResMessage message or plain object to encode
1187
+ * @param {$protobuf.Writer} [writer] Writer to encode to
1188
+ * @returns {$protobuf.Writer} Writer
1189
+ */
1190
+ ResMessage.encodeDelimited = function encodeDelimited(message, writer) {
1191
+ return this.encode(message, writer).ldelim();
1192
+ };
1193
+
1194
+ /**
1195
+ * Decodes a ResMessage message from the specified reader or buffer.
1196
+ * @function decode
1197
+ * @memberof ResMessage
1198
+ * @static
1199
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
1200
+ * @param {number} [length] Message length if known beforehand
1201
+ * @returns {ResMessage} ResMessage
1202
+ * @throws {Error} If the payload is not a reader or valid buffer
1203
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
1204
+ */
1205
+ ResMessage.decode = function decode(reader, length) {
1206
+ if (!(reader instanceof $Reader))
1207
+ reader = $Reader.create(reader);
1208
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.ResMessage();
1209
+ while (reader.pos < end) {
1210
+ var tag = reader.uint32();
1211
+ switch (tag >>> 3) {
1212
+ case 1: {
1213
+ message.content = reader.string();
1214
+ break;
1215
+ }
1216
+ case 4: {
1217
+ message.empty = reader.bytes();
1218
+ break;
1219
+ }
1220
+ case 5: {
1221
+ message.prompt = reader.string();
1222
+ break;
1223
+ }
1224
+ default:
1225
+ reader.skipType(tag & 7);
1226
+ break;
1227
+ }
1228
+ }
1229
+ return message;
1230
+ };
1231
+
1232
+ /**
1233
+ * Decodes a ResMessage message from the specified reader or buffer, length delimited.
1234
+ * @function decodeDelimited
1235
+ * @memberof ResMessage
1236
+ * @static
1237
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
1238
+ * @returns {ResMessage} ResMessage
1239
+ * @throws {Error} If the payload is not a reader or valid buffer
1240
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
1241
+ */
1242
+ ResMessage.decodeDelimited = function decodeDelimited(reader) {
1243
+ if (!(reader instanceof $Reader))
1244
+ reader = new $Reader(reader);
1245
+ return this.decode(reader, reader.uint32());
1246
+ };
1247
+
1248
+ /**
1249
+ * Verifies a ResMessage message.
1250
+ * @function verify
1251
+ * @memberof ResMessage
1252
+ * @static
1253
+ * @param {Object.<string,*>} message Plain object to verify
1254
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
1255
+ */
1256
+ ResMessage.verify = function verify(message) {
1257
+ if (typeof message !== "object" || message === null)
1258
+ return "object expected";
1259
+ if (message.content != null && message.hasOwnProperty("content"))
1260
+ if (!$util.isString(message.content))
1261
+ return "content: string expected";
1262
+ if (message.empty != null && message.hasOwnProperty("empty"))
1263
+ if (!(message.empty && typeof message.empty.length === "number" || $util.isString(message.empty)))
1264
+ return "empty: buffer expected";
1265
+ if (message.prompt != null && message.hasOwnProperty("prompt"))
1266
+ if (!$util.isString(message.prompt))
1267
+ return "prompt: string expected";
1268
+ return null;
1269
+ };
1270
+
1271
+ /**
1272
+ * Creates a ResMessage message from a plain object. Also converts values to their respective internal types.
1273
+ * @function fromObject
1274
+ * @memberof ResMessage
1275
+ * @static
1276
+ * @param {Object.<string,*>} object Plain object
1277
+ * @returns {ResMessage} ResMessage
1278
+ */
1279
+ ResMessage.fromObject = function fromObject(object) {
1280
+ if (object instanceof $root.ResMessage)
1281
+ return object;
1282
+ var message = new $root.ResMessage();
1283
+ if (object.content != null)
1284
+ message.content = String(object.content);
1285
+ if (object.empty != null)
1286
+ if (typeof object.empty === "string")
1287
+ $util.base64.decode(object.empty, message.empty = $util.newBuffer($util.base64.length(object.empty)), 0);
1288
+ else if (object.empty.length >= 0)
1289
+ message.empty = object.empty;
1290
+ if (object.prompt != null)
1291
+ message.prompt = String(object.prompt);
1292
+ return message;
1293
+ };
1294
+
1295
+ /**
1296
+ * Creates a plain object from a ResMessage message. Also converts values to other types if specified.
1297
+ * @function toObject
1298
+ * @memberof ResMessage
1299
+ * @static
1300
+ * @param {ResMessage} message ResMessage
1301
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
1302
+ * @returns {Object.<string,*>} Plain object
1303
+ */
1304
+ ResMessage.toObject = function toObject(message, options) {
1305
+ if (!options)
1306
+ options = {};
1307
+ var object = {};
1308
+ if (options.defaults) {
1309
+ object.content = "";
1310
+ if (options.bytes === String)
1311
+ object.empty = "";
1312
+ else {
1313
+ object.empty = [];
1314
+ if (options.bytes !== Array)
1315
+ object.empty = $util.newBuffer(object.empty);
1316
+ }
1317
+ object.prompt = "";
1318
+ }
1319
+ if (message.content != null && message.hasOwnProperty("content"))
1320
+ object.content = message.content;
1321
+ if (message.empty != null && message.hasOwnProperty("empty"))
1322
+ object.empty = options.bytes === String ? $util.base64.encode(message.empty, 0, message.empty.length) : options.bytes === Array ? Array.prototype.slice.call(message.empty) : message.empty;
1323
+ if (message.prompt != null && message.hasOwnProperty("prompt"))
1324
+ object.prompt = message.prompt;
1325
+ return object;
1326
+ };
1327
+
1328
+ /**
1329
+ * Converts this ResMessage to JSON.
1330
+ * @function toJSON
1331
+ * @memberof ResMessage
1332
+ * @instance
1333
+ * @returns {Object.<string,*>} JSON object
1334
+ */
1335
+ ResMessage.prototype.toJSON = function toJSON() {
1336
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
1337
+ };
1338
+
1339
+ /**
1340
+ * Gets the default type url for ResMessage
1341
+ * @function getTypeUrl
1342
+ * @memberof ResMessage
1343
+ * @static
1344
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
1345
+ * @returns {string} The default type url
1346
+ */
1347
+ ResMessage.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
1348
+ if (typeUrlPrefix === undefined) {
1349
+ typeUrlPrefix = "type.googleapis.com";
1350
+ }
1351
+ return typeUrlPrefix + "/ResMessage";
1352
+ };
1353
+
1354
+ return ResMessage;
1355
+ })();
1356
+
1357
+ $root.AvailableModelsResponse = (function() {
1358
+
1359
+ /**
1360
+ * Properties of an AvailableModelsResponse.
1361
+ * @exports IAvailableModelsResponse
1362
+ * @interface IAvailableModelsResponse
1363
+ * @property {Array.<AvailableModelsResponse.IAvailableModel>|null} [models] AvailableModelsResponse models
1364
+ * @property {Array.<string>|null} [modelNames] AvailableModelsResponse modelNames
1365
+ */
1366
+
1367
+ /**
1368
+ * Constructs a new AvailableModelsResponse.
1369
+ * @exports AvailableModelsResponse
1370
+ * @classdesc Represents an AvailableModelsResponse.
1371
+ * @implements IAvailableModelsResponse
1372
+ * @constructor
1373
+ * @param {IAvailableModelsResponse=} [properties] Properties to set
1374
+ */
1375
+ function AvailableModelsResponse(properties) {
1376
+ this.models = [];
1377
+ this.modelNames = [];
1378
+ if (properties)
1379
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
1380
+ if (properties[keys[i]] != null)
1381
+ this[keys[i]] = properties[keys[i]];
1382
+ }
1383
+
1384
+ /**
1385
+ * AvailableModelsResponse models.
1386
+ * @member {Array.<AvailableModelsResponse.IAvailableModel>} models
1387
+ * @memberof AvailableModelsResponse
1388
+ * @instance
1389
+ */
1390
+ AvailableModelsResponse.prototype.models = $util.emptyArray;
1391
+
1392
+ /**
1393
+ * AvailableModelsResponse modelNames.
1394
+ * @member {Array.<string>} modelNames
1395
+ * @memberof AvailableModelsResponse
1396
+ * @instance
1397
+ */
1398
+ AvailableModelsResponse.prototype.modelNames = $util.emptyArray;
1399
+
1400
+ /**
1401
+ * Creates a new AvailableModelsResponse instance using the specified properties.
1402
+ * @function create
1403
+ * @memberof AvailableModelsResponse
1404
+ * @static
1405
+ * @param {IAvailableModelsResponse=} [properties] Properties to set
1406
+ * @returns {AvailableModelsResponse} AvailableModelsResponse instance
1407
+ */
1408
+ AvailableModelsResponse.create = function create(properties) {
1409
+ return new AvailableModelsResponse(properties);
1410
+ };
1411
+
1412
+ /**
1413
+ * Encodes the specified AvailableModelsResponse message. Does not implicitly {@link AvailableModelsResponse.verify|verify} messages.
1414
+ * @function encode
1415
+ * @memberof AvailableModelsResponse
1416
+ * @static
1417
+ * @param {IAvailableModelsResponse} message AvailableModelsResponse message or plain object to encode
1418
+ * @param {$protobuf.Writer} [writer] Writer to encode to
1419
+ * @returns {$protobuf.Writer} Writer
1420
+ */
1421
+ AvailableModelsResponse.encode = function encode(message, writer) {
1422
+ if (!writer)
1423
+ writer = $Writer.create();
1424
+ if (message.modelNames != null && message.modelNames.length)
1425
+ for (var i = 0; i < message.modelNames.length; ++i)
1426
+ writer.uint32(/* id 1, wireType 2 =*/10).string(message.modelNames[i]);
1427
+ if (message.models != null && message.models.length)
1428
+ for (var i = 0; i < message.models.length; ++i)
1429
+ $root.AvailableModelsResponse.AvailableModel.encode(message.models[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim();
1430
+ return writer;
1431
+ };
1432
+
1433
+ /**
1434
+ * Encodes the specified AvailableModelsResponse message, length delimited. Does not implicitly {@link AvailableModelsResponse.verify|verify} messages.
1435
+ * @function encodeDelimited
1436
+ * @memberof AvailableModelsResponse
1437
+ * @static
1438
+ * @param {IAvailableModelsResponse} message AvailableModelsResponse message or plain object to encode
1439
+ * @param {$protobuf.Writer} [writer] Writer to encode to
1440
+ * @returns {$protobuf.Writer} Writer
1441
+ */
1442
+ AvailableModelsResponse.encodeDelimited = function encodeDelimited(message, writer) {
1443
+ return this.encode(message, writer).ldelim();
1444
+ };
1445
+
1446
+ /**
1447
+ * Decodes an AvailableModelsResponse message from the specified reader or buffer.
1448
+ * @function decode
1449
+ * @memberof AvailableModelsResponse
1450
+ * @static
1451
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
1452
+ * @param {number} [length] Message length if known beforehand
1453
+ * @returns {AvailableModelsResponse} AvailableModelsResponse
1454
+ * @throws {Error} If the payload is not a reader or valid buffer
1455
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
1456
+ */
1457
+ AvailableModelsResponse.decode = function decode(reader, length) {
1458
+ if (!(reader instanceof $Reader))
1459
+ reader = $Reader.create(reader);
1460
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.AvailableModelsResponse();
1461
+ while (reader.pos < end) {
1462
+ var tag = reader.uint32();
1463
+ switch (tag >>> 3) {
1464
+ case 2: {
1465
+ if (!(message.models && message.models.length))
1466
+ message.models = [];
1467
+ message.models.push($root.AvailableModelsResponse.AvailableModel.decode(reader, reader.uint32()));
1468
+ break;
1469
+ }
1470
+ case 1: {
1471
+ if (!(message.modelNames && message.modelNames.length))
1472
+ message.modelNames = [];
1473
+ message.modelNames.push(reader.string());
1474
+ break;
1475
+ }
1476
+ default:
1477
+ reader.skipType(tag & 7);
1478
+ break;
1479
+ }
1480
+ }
1481
+ return message;
1482
+ };
1483
+
1484
+ /**
1485
+ * Decodes an AvailableModelsResponse message from the specified reader or buffer, length delimited.
1486
+ * @function decodeDelimited
1487
+ * @memberof AvailableModelsResponse
1488
+ * @static
1489
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
1490
+ * @returns {AvailableModelsResponse} AvailableModelsResponse
1491
+ * @throws {Error} If the payload is not a reader or valid buffer
1492
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
1493
+ */
1494
+ AvailableModelsResponse.decodeDelimited = function decodeDelimited(reader) {
1495
+ if (!(reader instanceof $Reader))
1496
+ reader = new $Reader(reader);
1497
+ return this.decode(reader, reader.uint32());
1498
+ };
1499
+
1500
+ /**
1501
+ * Verifies an AvailableModelsResponse message.
1502
+ * @function verify
1503
+ * @memberof AvailableModelsResponse
1504
+ * @static
1505
+ * @param {Object.<string,*>} message Plain object to verify
1506
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
1507
+ */
1508
+ AvailableModelsResponse.verify = function verify(message) {
1509
+ if (typeof message !== "object" || message === null)
1510
+ return "object expected";
1511
+ if (message.models != null && message.hasOwnProperty("models")) {
1512
+ if (!Array.isArray(message.models))
1513
+ return "models: array expected";
1514
+ for (var i = 0; i < message.models.length; ++i) {
1515
+ var error = $root.AvailableModelsResponse.AvailableModel.verify(message.models[i]);
1516
+ if (error)
1517
+ return "models." + error;
1518
+ }
1519
+ }
1520
+ if (message.modelNames != null && message.hasOwnProperty("modelNames")) {
1521
+ if (!Array.isArray(message.modelNames))
1522
+ return "modelNames: array expected";
1523
+ for (var i = 0; i < message.modelNames.length; ++i)
1524
+ if (!$util.isString(message.modelNames[i]))
1525
+ return "modelNames: string[] expected";
1526
+ }
1527
+ return null;
1528
+ };
1529
+
1530
+ /**
1531
+ * Creates an AvailableModelsResponse message from a plain object. Also converts values to their respective internal types.
1532
+ * @function fromObject
1533
+ * @memberof AvailableModelsResponse
1534
+ * @static
1535
+ * @param {Object.<string,*>} object Plain object
1536
+ * @returns {AvailableModelsResponse} AvailableModelsResponse
1537
+ */
1538
+ AvailableModelsResponse.fromObject = function fromObject(object) {
1539
+ if (object instanceof $root.AvailableModelsResponse)
1540
+ return object;
1541
+ var message = new $root.AvailableModelsResponse();
1542
+ if (object.models) {
1543
+ if (!Array.isArray(object.models))
1544
+ throw TypeError(".AvailableModelsResponse.models: array expected");
1545
+ message.models = [];
1546
+ for (var i = 0; i < object.models.length; ++i) {
1547
+ if (typeof object.models[i] !== "object")
1548
+ throw TypeError(".AvailableModelsResponse.models: object expected");
1549
+ message.models[i] = $root.AvailableModelsResponse.AvailableModel.fromObject(object.models[i]);
1550
+ }
1551
+ }
1552
+ if (object.modelNames) {
1553
+ if (!Array.isArray(object.modelNames))
1554
+ throw TypeError(".AvailableModelsResponse.modelNames: array expected");
1555
+ message.modelNames = [];
1556
+ for (var i = 0; i < object.modelNames.length; ++i)
1557
+ message.modelNames[i] = String(object.modelNames[i]);
1558
+ }
1559
+ return message;
1560
+ };
1561
+
1562
+ /**
1563
+ * Creates a plain object from an AvailableModelsResponse message. Also converts values to other types if specified.
1564
+ * @function toObject
1565
+ * @memberof AvailableModelsResponse
1566
+ * @static
1567
+ * @param {AvailableModelsResponse} message AvailableModelsResponse
1568
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
1569
+ * @returns {Object.<string,*>} Plain object
1570
+ */
1571
+ AvailableModelsResponse.toObject = function toObject(message, options) {
1572
+ if (!options)
1573
+ options = {};
1574
+ var object = {};
1575
+ if (options.arrays || options.defaults) {
1576
+ object.modelNames = [];
1577
+ object.models = [];
1578
+ }
1579
+ if (message.modelNames && message.modelNames.length) {
1580
+ object.modelNames = [];
1581
+ for (var j = 0; j < message.modelNames.length; ++j)
1582
+ object.modelNames[j] = message.modelNames[j];
1583
+ }
1584
+ if (message.models && message.models.length) {
1585
+ object.models = [];
1586
+ for (var j = 0; j < message.models.length; ++j)
1587
+ object.models[j] = $root.AvailableModelsResponse.AvailableModel.toObject(message.models[j], options);
1588
+ }
1589
+ return object;
1590
+ };
1591
+
1592
+ /**
1593
+ * Converts this AvailableModelsResponse to JSON.
1594
+ * @function toJSON
1595
+ * @memberof AvailableModelsResponse
1596
+ * @instance
1597
+ * @returns {Object.<string,*>} JSON object
1598
+ */
1599
+ AvailableModelsResponse.prototype.toJSON = function toJSON() {
1600
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
1601
+ };
1602
+
1603
+ /**
1604
+ * Gets the default type url for AvailableModelsResponse
1605
+ * @function getTypeUrl
1606
+ * @memberof AvailableModelsResponse
1607
+ * @static
1608
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
1609
+ * @returns {string} The default type url
1610
+ */
1611
+ AvailableModelsResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
1612
+ if (typeUrlPrefix === undefined) {
1613
+ typeUrlPrefix = "type.googleapis.com";
1614
+ }
1615
+ return typeUrlPrefix + "/AvailableModelsResponse";
1616
+ };
1617
+
1618
+ AvailableModelsResponse.AvailableModel = (function() {
1619
+
1620
+ /**
1621
+ * Properties of an AvailableModel.
1622
+ * @memberof AvailableModelsResponse
1623
+ * @interface IAvailableModel
1624
+ * @property {string|null} [name] AvailableModel name
1625
+ * @property {boolean|null} [defaultOn] AvailableModel defaultOn
1626
+ * @property {boolean|null} [isLongContextOnly] AvailableModel isLongContextOnly
1627
+ * @property {boolean|null} [isChatOnly] AvailableModel isChatOnly
1628
+ */
1629
+
1630
+ /**
1631
+ * Constructs a new AvailableModel.
1632
+ * @memberof AvailableModelsResponse
1633
+ * @classdesc Represents an AvailableModel.
1634
+ * @implements IAvailableModel
1635
+ * @constructor
1636
+ * @param {AvailableModelsResponse.IAvailableModel=} [properties] Properties to set
1637
+ */
1638
+ function AvailableModel(properties) {
1639
+ if (properties)
1640
+ for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
1641
+ if (properties[keys[i]] != null)
1642
+ this[keys[i]] = properties[keys[i]];
1643
+ }
1644
+
1645
+ /**
1646
+ * AvailableModel name.
1647
+ * @member {string} name
1648
+ * @memberof AvailableModelsResponse.AvailableModel
1649
+ * @instance
1650
+ */
1651
+ AvailableModel.prototype.name = "";
1652
+
1653
+ /**
1654
+ * AvailableModel defaultOn.
1655
+ * @member {boolean} defaultOn
1656
+ * @memberof AvailableModelsResponse.AvailableModel
1657
+ * @instance
1658
+ */
1659
+ AvailableModel.prototype.defaultOn = false;
1660
+
1661
+ /**
1662
+ * AvailableModel isLongContextOnly.
1663
+ * @member {boolean|null|undefined} isLongContextOnly
1664
+ * @memberof AvailableModelsResponse.AvailableModel
1665
+ * @instance
1666
+ */
1667
+ AvailableModel.prototype.isLongContextOnly = null;
1668
+
1669
+ /**
1670
+ * AvailableModel isChatOnly.
1671
+ * @member {boolean|null|undefined} isChatOnly
1672
+ * @memberof AvailableModelsResponse.AvailableModel
1673
+ * @instance
1674
+ */
1675
+ AvailableModel.prototype.isChatOnly = null;
1676
+
1677
+ // OneOf field names bound to virtual getters and setters
1678
+ var $oneOfFields;
1679
+
1680
+ // Virtual OneOf for proto3 optional field
1681
+ Object.defineProperty(AvailableModel.prototype, "_isLongContextOnly", {
1682
+ get: $util.oneOfGetter($oneOfFields = ["isLongContextOnly"]),
1683
+ set: $util.oneOfSetter($oneOfFields)
1684
+ });
1685
+
1686
+ // Virtual OneOf for proto3 optional field
1687
+ Object.defineProperty(AvailableModel.prototype, "_isChatOnly", {
1688
+ get: $util.oneOfGetter($oneOfFields = ["isChatOnly"]),
1689
+ set: $util.oneOfSetter($oneOfFields)
1690
+ });
1691
+
1692
+ /**
1693
+ * Creates a new AvailableModel instance using the specified properties.
1694
+ * @function create
1695
+ * @memberof AvailableModelsResponse.AvailableModel
1696
+ * @static
1697
+ * @param {AvailableModelsResponse.IAvailableModel=} [properties] Properties to set
1698
+ * @returns {AvailableModelsResponse.AvailableModel} AvailableModel instance
1699
+ */
1700
+ AvailableModel.create = function create(properties) {
1701
+ return new AvailableModel(properties);
1702
+ };
1703
+
1704
+ /**
1705
+ * Encodes the specified AvailableModel message. Does not implicitly {@link AvailableModelsResponse.AvailableModel.verify|verify} messages.
1706
+ * @function encode
1707
+ * @memberof AvailableModelsResponse.AvailableModel
1708
+ * @static
1709
+ * @param {AvailableModelsResponse.IAvailableModel} message AvailableModel message or plain object to encode
1710
+ * @param {$protobuf.Writer} [writer] Writer to encode to
1711
+ * @returns {$protobuf.Writer} Writer
1712
+ */
1713
+ AvailableModel.encode = function encode(message, writer) {
1714
+ if (!writer)
1715
+ writer = $Writer.create();
1716
+ if (message.name != null && Object.hasOwnProperty.call(message, "name"))
1717
+ writer.uint32(/* id 1, wireType 2 =*/10).string(message.name);
1718
+ if (message.defaultOn != null && Object.hasOwnProperty.call(message, "defaultOn"))
1719
+ writer.uint32(/* id 2, wireType 0 =*/16).bool(message.defaultOn);
1720
+ if (message.isLongContextOnly != null && Object.hasOwnProperty.call(message, "isLongContextOnly"))
1721
+ writer.uint32(/* id 3, wireType 0 =*/24).bool(message.isLongContextOnly);
1722
+ if (message.isChatOnly != null && Object.hasOwnProperty.call(message, "isChatOnly"))
1723
+ writer.uint32(/* id 4, wireType 0 =*/32).bool(message.isChatOnly);
1724
+ return writer;
1725
+ };
1726
+
1727
+ /**
1728
+ * Encodes the specified AvailableModel message, length delimited. Does not implicitly {@link AvailableModelsResponse.AvailableModel.verify|verify} messages.
1729
+ * @function encodeDelimited
1730
+ * @memberof AvailableModelsResponse.AvailableModel
1731
+ * @static
1732
+ * @param {AvailableModelsResponse.IAvailableModel} message AvailableModel message or plain object to encode
1733
+ * @param {$protobuf.Writer} [writer] Writer to encode to
1734
+ * @returns {$protobuf.Writer} Writer
1735
+ */
1736
+ AvailableModel.encodeDelimited = function encodeDelimited(message, writer) {
1737
+ return this.encode(message, writer).ldelim();
1738
+ };
1739
+
1740
+ /**
1741
+ * Decodes an AvailableModel message from the specified reader or buffer.
1742
+ * @function decode
1743
+ * @memberof AvailableModelsResponse.AvailableModel
1744
+ * @static
1745
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
1746
+ * @param {number} [length] Message length if known beforehand
1747
+ * @returns {AvailableModelsResponse.AvailableModel} AvailableModel
1748
+ * @throws {Error} If the payload is not a reader or valid buffer
1749
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
1750
+ */
1751
+ AvailableModel.decode = function decode(reader, length) {
1752
+ if (!(reader instanceof $Reader))
1753
+ reader = $Reader.create(reader);
1754
+ var end = length === undefined ? reader.len : reader.pos + length, message = new $root.AvailableModelsResponse.AvailableModel();
1755
+ while (reader.pos < end) {
1756
+ var tag = reader.uint32();
1757
+ switch (tag >>> 3) {
1758
+ case 1: {
1759
+ message.name = reader.string();
1760
+ break;
1761
+ }
1762
+ case 2: {
1763
+ message.defaultOn = reader.bool();
1764
+ break;
1765
+ }
1766
+ case 3: {
1767
+ message.isLongContextOnly = reader.bool();
1768
+ break;
1769
+ }
1770
+ case 4: {
1771
+ message.isChatOnly = reader.bool();
1772
+ break;
1773
+ }
1774
+ default:
1775
+ reader.skipType(tag & 7);
1776
+ break;
1777
+ }
1778
+ }
1779
+ return message;
1780
+ };
1781
+
1782
+ /**
1783
+ * Decodes an AvailableModel message from the specified reader or buffer, length delimited.
1784
+ * @function decodeDelimited
1785
+ * @memberof AvailableModelsResponse.AvailableModel
1786
+ * @static
1787
+ * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from
1788
+ * @returns {AvailableModelsResponse.AvailableModel} AvailableModel
1789
+ * @throws {Error} If the payload is not a reader or valid buffer
1790
+ * @throws {$protobuf.util.ProtocolError} If required fields are missing
1791
+ */
1792
+ AvailableModel.decodeDelimited = function decodeDelimited(reader) {
1793
+ if (!(reader instanceof $Reader))
1794
+ reader = new $Reader(reader);
1795
+ return this.decode(reader, reader.uint32());
1796
+ };
1797
+
1798
+ /**
1799
+ * Verifies an AvailableModel message.
1800
+ * @function verify
1801
+ * @memberof AvailableModelsResponse.AvailableModel
1802
+ * @static
1803
+ * @param {Object.<string,*>} message Plain object to verify
1804
+ * @returns {string|null} `null` if valid, otherwise the reason why it is not
1805
+ */
1806
+ AvailableModel.verify = function verify(message) {
1807
+ if (typeof message !== "object" || message === null)
1808
+ return "object expected";
1809
+ var properties = {};
1810
+ if (message.name != null && message.hasOwnProperty("name"))
1811
+ if (!$util.isString(message.name))
1812
+ return "name: string expected";
1813
+ if (message.defaultOn != null && message.hasOwnProperty("defaultOn"))
1814
+ if (typeof message.defaultOn !== "boolean")
1815
+ return "defaultOn: boolean expected";
1816
+ if (message.isLongContextOnly != null && message.hasOwnProperty("isLongContextOnly")) {
1817
+ properties._isLongContextOnly = 1;
1818
+ if (typeof message.isLongContextOnly !== "boolean")
1819
+ return "isLongContextOnly: boolean expected";
1820
+ }
1821
+ if (message.isChatOnly != null && message.hasOwnProperty("isChatOnly")) {
1822
+ properties._isChatOnly = 1;
1823
+ if (typeof message.isChatOnly !== "boolean")
1824
+ return "isChatOnly: boolean expected";
1825
+ }
1826
+ return null;
1827
+ };
1828
+
1829
+ /**
1830
+ * Creates an AvailableModel message from a plain object. Also converts values to their respective internal types.
1831
+ * @function fromObject
1832
+ * @memberof AvailableModelsResponse.AvailableModel
1833
+ * @static
1834
+ * @param {Object.<string,*>} object Plain object
1835
+ * @returns {AvailableModelsResponse.AvailableModel} AvailableModel
1836
+ */
1837
+ AvailableModel.fromObject = function fromObject(object) {
1838
+ if (object instanceof $root.AvailableModelsResponse.AvailableModel)
1839
+ return object;
1840
+ var message = new $root.AvailableModelsResponse.AvailableModel();
1841
+ if (object.name != null)
1842
+ message.name = String(object.name);
1843
+ if (object.defaultOn != null)
1844
+ message.defaultOn = Boolean(object.defaultOn);
1845
+ if (object.isLongContextOnly != null)
1846
+ message.isLongContextOnly = Boolean(object.isLongContextOnly);
1847
+ if (object.isChatOnly != null)
1848
+ message.isChatOnly = Boolean(object.isChatOnly);
1849
+ return message;
1850
+ };
1851
+
1852
+ /**
1853
+ * Creates a plain object from an AvailableModel message. Also converts values to other types if specified.
1854
+ * @function toObject
1855
+ * @memberof AvailableModelsResponse.AvailableModel
1856
+ * @static
1857
+ * @param {AvailableModelsResponse.AvailableModel} message AvailableModel
1858
+ * @param {$protobuf.IConversionOptions} [options] Conversion options
1859
+ * @returns {Object.<string,*>} Plain object
1860
+ */
1861
+ AvailableModel.toObject = function toObject(message, options) {
1862
+ if (!options)
1863
+ options = {};
1864
+ var object = {};
1865
+ if (options.defaults) {
1866
+ object.name = "";
1867
+ object.defaultOn = false;
1868
+ }
1869
+ if (message.name != null && message.hasOwnProperty("name"))
1870
+ object.name = message.name;
1871
+ if (message.defaultOn != null && message.hasOwnProperty("defaultOn"))
1872
+ object.defaultOn = message.defaultOn;
1873
+ if (message.isLongContextOnly != null && message.hasOwnProperty("isLongContextOnly")) {
1874
+ object.isLongContextOnly = message.isLongContextOnly;
1875
+ if (options.oneofs)
1876
+ object._isLongContextOnly = "isLongContextOnly";
1877
+ }
1878
+ if (message.isChatOnly != null && message.hasOwnProperty("isChatOnly")) {
1879
+ object.isChatOnly = message.isChatOnly;
1880
+ if (options.oneofs)
1881
+ object._isChatOnly = "isChatOnly";
1882
+ }
1883
+ return object;
1884
+ };
1885
+
1886
+ /**
1887
+ * Converts this AvailableModel to JSON.
1888
+ * @function toJSON
1889
+ * @memberof AvailableModelsResponse.AvailableModel
1890
+ * @instance
1891
+ * @returns {Object.<string,*>} JSON object
1892
+ */
1893
+ AvailableModel.prototype.toJSON = function toJSON() {
1894
+ return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
1895
+ };
1896
+
1897
+ /**
1898
+ * Gets the default type url for AvailableModel
1899
+ * @function getTypeUrl
1900
+ * @memberof AvailableModelsResponse.AvailableModel
1901
+ * @static
1902
+ * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com")
1903
+ * @returns {string} The default type url
1904
+ */
1905
+ AvailableModel.getTypeUrl = function getTypeUrl(typeUrlPrefix) {
1906
+ if (typeUrlPrefix === undefined) {
1907
+ typeUrlPrefix = "type.googleapis.com";
1908
+ }
1909
+ return typeUrlPrefix + "/AvailableModelsResponse.AvailableModel";
1910
+ };
1911
+
1912
+ return AvailableModel;
1913
+ })();
1914
+
1915
+ return AvailableModelsResponse;
1916
+ })();
1917
+
1918
+ module.exports = $root;
src/proto/message.proto ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ syntax = "proto3";
2
+
3
+ message ChatMessage {
4
+ message UserMessage {
5
+ string content = 1;
6
+ int32 role = 2;
7
+ string messageId = 13;
8
+ }
9
+
10
+ message Instructions {
11
+ string instruction = 1;
12
+ }
13
+
14
+ message Model {
15
+ string name = 1;
16
+ bytes empty = 4;
17
+ }
18
+
19
+ repeated UserMessage userMessages = 2;
20
+ Instructions instructions = 4;
21
+ Model model = 7;
22
+ int32 unknown13 = 13;
23
+ string conversationId = 15;
24
+ int32 unknown16 = 16;
25
+ int32 unknown29 = 29;
26
+ int32 unknown31 = 31;
27
+ }
28
+
29
+ message ResMessage {
30
+ string content = 1;
31
+ bytes empty = 4;
32
+ string prompt = 5;
33
+ }
34
+
35
+ message AvailableModelsResponse {
36
+ message AvailableModel {
37
+ string name = 1;
38
+ bool default_on = 2;
39
+ optional bool is_long_context_only = 3;
40
+ optional bool is_chat_only = 4;
41
+ }
42
+ repeated AvailableModel models = 2;
43
+ repeated string model_names = 1;
44
+ }
src/public/index.html ADDED
@@ -0,0 +1,921 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cursor To OpenAI - API Key 管理</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Arial', sans-serif;
10
+ line-height: 1.6;
11
+ margin: 0;
12
+ padding: 20px;
13
+ color: #333;
14
+ max-width: 1200px;
15
+ margin: 0 auto;
16
+ }
17
+ h1, h2 {
18
+ color: #2c3e50;
19
+ }
20
+ .container {
21
+ display: flex;
22
+ flex-direction: column;
23
+ gap: 20px;
24
+ }
25
+ .card {
26
+ background: #fff;
27
+ border-radius: 8px;
28
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
29
+ padding: 20px;
30
+ }
31
+ .form-group {
32
+ margin-bottom: 15px;
33
+ }
34
+ label {
35
+ display: block;
36
+ margin-bottom: 5px;
37
+ font-weight: bold;
38
+ }
39
+ input, textarea {
40
+ width: 100%;
41
+ padding: 8px;
42
+ border: 1px solid #ddd;
43
+ border-radius: 4px;
44
+ font-size: 16px;
45
+ }
46
+ textarea {
47
+ min-height: 100px;
48
+ font-family: monospace;
49
+ }
50
+ button {
51
+ background: #3498db;
52
+ color: white;
53
+ border: none;
54
+ padding: 10px 15px;
55
+ border-radius: 4px;
56
+ cursor: pointer;
57
+ font-size: 16px;
58
+ }
59
+ button:hover {
60
+ background: #2980b9;
61
+ }
62
+ table {
63
+ width: 100%;
64
+ border-collapse: collapse;
65
+ }
66
+ th, td {
67
+ padding: 12px;
68
+ text-align: left;
69
+ border-bottom: 1px solid #ddd;
70
+ }
71
+ th {
72
+ background-color: #f2f2f2;
73
+ }
74
+ .action-btn {
75
+ background: #e74c3c;
76
+ margin-right: 5px;
77
+ }
78
+ .action-btn:hover {
79
+ background: #c0392b;
80
+ }
81
+ .edit-btn {
82
+ background: #f39c12;
83
+ margin-right: 5px;
84
+ }
85
+ .edit-btn:hover {
86
+ background: #d35400;
87
+ }
88
+ .info {
89
+ background-color: #d4edda;
90
+ color: #155724;
91
+ padding: 10px;
92
+ border-radius: 4px;
93
+ margin-bottom: 15px;
94
+ }
95
+ .error {
96
+ background-color: #f8d7da;
97
+ color: #721c24;
98
+ padding: 10px;
99
+ border-radius: 4px;
100
+ margin-bottom: 15px;
101
+ }
102
+ .modal {
103
+ display: none;
104
+ position: fixed;
105
+ z-index: 1;
106
+ left: 0;
107
+ top: 0;
108
+ width: 100%;
109
+ height: 100%;
110
+ overflow: auto;
111
+ background-color: rgba(0,0,0,0.4);
112
+ }
113
+ .modal-content {
114
+ background-color: #fefefe;
115
+ margin: 15% auto;
116
+ padding: 20px;
117
+ border: 1px solid #888;
118
+ width: 80%;
119
+ max-width: 600px;
120
+ border-radius: 8px;
121
+ }
122
+ .close {
123
+ color: #aaa;
124
+ float: right;
125
+ font-size: 28px;
126
+ font-weight: bold;
127
+ cursor: pointer;
128
+ }
129
+ .close:hover,
130
+ .close:focus {
131
+ color: black;
132
+ text-decoration: none;
133
+ }
134
+ /* 无效Cookie样式 */
135
+ .cookie-text {
136
+ max-width: 80%;
137
+ word-break: break-all;
138
+ }
139
+ </style>
140
+ </head>
141
+ <body>
142
+ <div class="container">
143
+ <div class="card">
144
+ <h1>Cursor To OpenAI - API Key 管理</h1>
145
+ <p>在此页面上,您可以管理自定义 API Key 与 Cursor Cookie 的映射关系。</p>
146
+ <div style="margin-top: 10px; display: flex; justify-content: space-between; align-items: center;">
147
+ <div>
148
+ <button id="testApiBtn" style="margin-right: 10px;">测试API连接</button>
149
+ <button id="clearCacheBtn">清除缓存并刷新</button>
150
+ </div>
151
+ <div>
152
+ <span id="adminUsername" style="margin-right: 10px;"></span>
153
+ <button id="logoutBtn" style="background: #e74c3c;">退出登录</button>
154
+ </div>
155
+ </div>
156
+ <div id="testApiResult" style="margin-top: 10px;"></div>
157
+ </div>
158
+
159
+ <div class="card">
160
+ <h2>添加/更新 API Key</h2>
161
+ <div id="addKeyMessage"></div>
162
+ <form id="addKeyForm">
163
+ <div class="form-group">
164
+ <label for="apiKey">API Key(自定义)</label>
165
+ <input type="text" id="apiKey" name="apiKey" placeholder="输入您想使用的自定义 API Key" required>
166
+ </div>
167
+ <div class="form-group">
168
+ <label for="cookieValues">Cursor Cookie 值(多个值请用逗号分隔)</label>
169
+ <textarea id="cookieValues" name="cookieValues" placeholder="输入 WorkosCursorSessionToken 值,多个值请用逗号分隔" required></textarea>
170
+ </div>
171
+ <button type="submit">保存</button>
172
+ </form>
173
+ </div>
174
+
175
+ <div class="card">
176
+ <h2>现有 API Key</h2>
177
+ <div id="keyListMessage"></div>
178
+ <table id="keyTable">
179
+ <thead>
180
+ <tr>
181
+ <th>API Key</th>
182
+ <th>Cookie 数量</th>
183
+ <th>操作</th>
184
+ </tr>
185
+ </thead>
186
+ <tbody id="keyList">
187
+ <!-- 数据将通过 JavaScript 动态加载 -->
188
+ </tbody>
189
+ </table>
190
+ </div>
191
+
192
+ <div class="card">
193
+ <h2>使用说明</h2>
194
+ <ol>
195
+ <li>添加自定义 API Key 和对应的 Cursor Cookie 值。</li>
196
+ <li>使用自定义 API Key 作为 OpenAI API 的认证凭证。</li>
197
+ <li>系统将自动在多个 Cookie 之间进行轮询。</li>
198
+ <li>API 端点:
199
+ <ul>
200
+ <li>模型列表:<code>/v1/models</code></li>
201
+ <li>聊天补全:<code>/v1/chat/completions</code></li>
202
+ </ul>
203
+ </li>
204
+ </ol>
205
+ </div>
206
+ </div>
207
+
208
+ <!-- 修改 Cookie 的模态框 -->
209
+ <div id="editModal" class="modal">
210
+ <div class="modal-content">
211
+ <span class="close">&times;</span>
212
+ <h2>修改 API Key 的 Cookie</h2>
213
+ <div id="editModalMessage"></div>
214
+ <form id="editCookieForm">
215
+ <input type="hidden" id="editApiKey" name="editApiKey">
216
+ <div class="form-group">
217
+ <label for="editCookieValues">Cursor Cookie 值(多个值请用逗号分隔)</label>
218
+ <textarea id="editCookieValues" name="editCookieValues" placeholder="输入 WorkosCursorSessionToken 值,多个值请用逗号分隔" required></textarea>
219
+ </div>
220
+ <button type="submit">保存修改</button>
221
+ </form>
222
+ </div>
223
+ </div>
224
+
225
+ <div class="card">
226
+ <h2>无效Cookie管理</h2>
227
+ <div class="form-group">
228
+ <div class="info">
229
+ 以下是系统自动检测到的无效Cookie列表。这些Cookie在请求过程中被发现无效,已被自动从API Key中移除。
230
+ </div>
231
+ <div id="invalidCookiesContainer">
232
+ <div style="text-align: center; padding: 20px;">
233
+ <div>加载中...</div>
234
+ </div>
235
+ </div>
236
+ <button id="clearAllInvalidCookies" style="background: #e74c3c;">清除所有无效Cookie</button>
237
+ </div>
238
+ </div>
239
+
240
+ <!-- 新增:Cookie刷新功能 -->
241
+ <div class="card">
242
+ <h2>Cookie自动刷新</h2>
243
+ <div class="form-group">
244
+ <div class="info">
245
+ 系统支持自动刷新Cookie,确保API Key始终有足够的可用Cookie。您可以在此手动触发刷新操作。
246
+ </div>
247
+ <div id="refreshCookieMessage"></div>
248
+ <div style="margin-top: 15px;">
249
+ <div class="form-group">
250
+ <label for="refreshApiKey">选择要刷新的API Key(不选则刷新所有)</label>
251
+ <select id="refreshApiKey" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 16px;">
252
+ <option value="">所有API Key</option>
253
+ <!-- 选项将通过JavaScript动态加载 -->
254
+ </select>
255
+ </div>
256
+ <button id="refreshCookieBtn" style="background: #27ae60;">刷新Cookie</button>
257
+ </div>
258
+ <div id="refreshStatusContainer" style="margin-top: 15px; display: none;">
259
+ <div class="info">
260
+ <div>刷新状态:<span id="refreshStatus">准备中...</span></div>
261
+ <div style="margin-top: 10px;">
262
+ <progress id="refreshProgress" value="0" max="100" style="width: 100%;"></progress>
263
+ </div>
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </div>
268
+
269
+ <script>
270
+ // 获取模态框元素
271
+ const modal = document.getElementById('editModal');
272
+ const closeBtn = document.getElementsByClassName('close')[0];
273
+
274
+ // 关闭模态框
275
+ closeBtn.onclick = function() {
276
+ modal.style.display = 'none';
277
+ }
278
+
279
+ // 点击模态框外部关闭
280
+ window.onclick = function(event) {
281
+ if (event.target == modal) {
282
+ modal.style.display = 'none';
283
+ }
284
+ }
285
+
286
+ // 加载现有 API Key
287
+ async function loadApiKeys() {
288
+ try {
289
+ console.log('开始加载API Keys...');
290
+ const response = await fetch('/v1/api-keys', {
291
+ method: 'GET',
292
+ headers: {
293
+ 'Content-Type': 'application/json',
294
+ 'Cache-Control': 'no-cache'
295
+ }
296
+ });
297
+
298
+ if (!response.ok) {
299
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
300
+ }
301
+
302
+ console.log('API响应状态:', response.status);
303
+ const data = await response.json();
304
+ console.log('获取到的数据:', data);
305
+
306
+ const keyList = document.getElementById('keyList');
307
+ keyList.innerHTML = '';
308
+
309
+ if (data.success && data.apiKeys.length > 0) {
310
+ data.apiKeys.forEach(key => {
311
+ const row = document.createElement('tr');
312
+ row.innerHTML = `
313
+ <td>${key.key}</td>
314
+ <td>${key.cookieCount}</td>
315
+ <td>
316
+ <button class="edit-btn" onclick="editApiKey('${key.key}')">修改</button>
317
+ <button class="action-btn" onclick="deleteApiKey('${key.key}')">删除</button>
318
+ </td>
319
+ `;
320
+ keyList.appendChild(row);
321
+ });
322
+ } else {
323
+ keyList.innerHTML = '<tr><td colspan="3">暂无 API Key</td></tr>';
324
+ }
325
+ } catch (error) {
326
+ console.error('加载 API Key 失败:', error);
327
+ document.getElementById('keyListMessage').innerHTML = `
328
+ <div class="error">加载 API Key 失败: ${error.message}</div>
329
+ `;
330
+ }
331
+ }
332
+
333
+ // 添加/更新 API Key
334
+ document.getElementById('addKeyForm').addEventListener('submit', async function(e) {
335
+ e.preventDefault();
336
+
337
+ const apiKey = document.getElementById('apiKey').value.trim();
338
+ const cookieValuesText = document.getElementById('cookieValues').value.trim();
339
+
340
+ if (!apiKey || !cookieValuesText) {
341
+ document.getElementById('addKeyMessage').innerHTML = `
342
+ <div class="error">API Key 和 Cookie 值不能为空</div>
343
+ `;
344
+ return;
345
+ }
346
+
347
+ // 将逗号分隔的 Cookie 值转换为数组
348
+ const cookieValues = cookieValuesText.split(',').map(cookie => cookie.trim()).filter(cookie => cookie);
349
+
350
+ try {
351
+ const response = await fetch('/v1/api-keys', {
352
+ method: 'POST',
353
+ headers: {
354
+ 'Content-Type': 'application/json',
355
+ },
356
+ body: JSON.stringify({
357
+ apiKey,
358
+ cookieValues,
359
+ }),
360
+ });
361
+
362
+ const data = await response.json();
363
+
364
+ if (data.success) {
365
+ document.getElementById('addKeyMessage').innerHTML = `
366
+ <div class="info">API Key 添加/更新成功</div>
367
+ `;
368
+ document.getElementById('apiKey').value = '';
369
+ document.getElementById('cookieValues').value = '';
370
+ loadApiKeys();
371
+ } else {
372
+ document.getElementById('addKeyMessage').innerHTML = `
373
+ <div class="error">API Key 添加/更新失败: ${data.error}</div>
374
+ `;
375
+ }
376
+ } catch (error) {
377
+ console.error('添加/更新 API Key 失败:', error);
378
+ document.getElementById('addKeyMessage').innerHTML = `
379
+ <div class="error">添加/更新 API Key 失败: ${error.message}</div>
380
+ `;
381
+ }
382
+ });
383
+
384
+ // 删除 API Key
385
+ async function deleteApiKey(apiKey) {
386
+ if (!confirm(`确定要删除 API Key "${apiKey}" 吗?`)) {
387
+ return;
388
+ }
389
+
390
+ try {
391
+ const response = await fetch(`/v1/api-keys/${encodeURIComponent(apiKey)}`, {
392
+ method: 'DELETE',
393
+ });
394
+
395
+ const data = await response.json();
396
+
397
+ if (data.success) {
398
+ document.getElementById('keyListMessage').innerHTML = `
399
+ <div class="info">API Key 删除成功</div>
400
+ `;
401
+ loadApiKeys();
402
+ } else {
403
+ document.getElementById('keyListMessage').innerHTML = `
404
+ <div class="error">API Key ���除失败: ${data.error}</div>
405
+ `;
406
+ }
407
+ } catch (error) {
408
+ console.error('删除 API Key 失败:', error);
409
+ document.getElementById('keyListMessage').innerHTML = `
410
+ <div class="error">删除 API Key 失败: ${error.message}</div>
411
+ `;
412
+ }
413
+ }
414
+
415
+ // 获取API Key的Cookie值
416
+ async function getCookiesForApiKey(apiKey) {
417
+ try {
418
+ const response = await fetch(`/v1/api-keys/${encodeURIComponent(apiKey)}/cookies`, {
419
+ method: 'GET',
420
+ headers: {
421
+ 'Content-Type': 'application/json',
422
+ 'Cache-Control': 'no-cache'
423
+ }
424
+ });
425
+
426
+ if (!response.ok) {
427
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
428
+ }
429
+
430
+ const data = await response.json();
431
+ return data.cookies;
432
+ } catch (error) {
433
+ console.error(`获取 ${apiKey} 的Cookie值失败:`, error);
434
+ throw error;
435
+ }
436
+ }
437
+
438
+ // 修改 API Key
439
+ async function editApiKey(apiKey) {
440
+ try {
441
+ document.getElementById('editModalMessage').innerHTML = '';
442
+ document.getElementById('editApiKey').value = apiKey;
443
+
444
+ // 获取当前Cookie值
445
+ const cookies = await getCookiesForApiKey(apiKey);
446
+ document.getElementById('editCookieValues').value = cookies.join(',');
447
+
448
+ // 显示模态框
449
+ modal.style.display = 'block';
450
+ } catch (error) {
451
+ alert(`获取 ${apiKey} 的Cookie值失败: ${error.message}`);
452
+ }
453
+ }
454
+
455
+ // 提交修改表单
456
+ document.getElementById('editCookieForm').addEventListener('submit', async function(e) {
457
+ e.preventDefault();
458
+
459
+ const apiKey = document.getElementById('editApiKey').value.trim();
460
+ const cookieValuesText = document.getElementById('editCookieValues').value.trim();
461
+
462
+ if (!apiKey || !cookieValuesText) {
463
+ document.getElementById('editModalMessage').innerHTML = `
464
+ <div class="error">API Key 和 Cookie 值不能为空</div>
465
+ `;
466
+ return;
467
+ }
468
+
469
+ // 将逗号分隔的 Cookie 值转换为数组
470
+ const cookieValues = cookieValuesText.split(',').map(cookie => cookie.trim()).filter(cookie => cookie);
471
+
472
+ try {
473
+ const response = await fetch('/v1/api-keys', {
474
+ method: 'POST',
475
+ headers: {
476
+ 'Content-Type': 'application/json',
477
+ },
478
+ body: JSON.stringify({
479
+ apiKey,
480
+ cookieValues,
481
+ }),
482
+ });
483
+
484
+ const data = await response.json();
485
+
486
+ if (data.success) {
487
+ document.getElementById('editModalMessage').innerHTML = `
488
+ <div class="info">Cookie 修改成功</div>
489
+ `;
490
+ setTimeout(() => {
491
+ modal.style.display = 'none';
492
+ loadApiKeys();
493
+ }, 1500);
494
+ } else {
495
+ document.getElementById('editModalMessage').innerHTML = `
496
+ <div class="error">Cookie 修改失败: ${data.error}</div>
497
+ `;
498
+ }
499
+ } catch (error) {
500
+ console.error('修改 Cookie 失败:', error);
501
+ document.getElementById('editModalMessage').innerHTML = `
502
+ <div class="error">修改 Cookie 失败: ${error.message}</div>
503
+ `;
504
+ }
505
+ });
506
+
507
+ // 测试API连接
508
+ document.getElementById('testApiBtn').addEventListener('click', async function() {
509
+ const resultDiv = document.getElementById('testApiResult');
510
+ resultDiv.innerHTML = '<div class="info">正在测试API连接...</div>';
511
+
512
+ try {
513
+ const response = await fetch('/v1/api-keys', {
514
+ method: 'GET',
515
+ headers: {
516
+ 'Content-Type': 'application/json',
517
+ 'Cache-Control': 'no-cache'
518
+ }
519
+ });
520
+
521
+ resultDiv.innerHTML = `<div class="info">API响应状态: ${response.status}</div>`;
522
+
523
+ if (!response.ok) {
524
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
525
+ }
526
+
527
+ const data = await response.json();
528
+ resultDiv.innerHTML += `<div class="info">获取到的数据: ${JSON.stringify(data)}</div>`;
529
+ } catch (error) {
530
+ console.error('测试API失败:', error);
531
+ resultDiv.innerHTML = `<div class="error">测试API失败: ${error.message}</div>`;
532
+ }
533
+ });
534
+
535
+ // 清除缓存并刷新
536
+ document.getElementById('clearCacheBtn').addEventListener('click', function() {
537
+ // 清除缓存
538
+ if ('caches' in window) {
539
+ caches.keys().then(function(names) {
540
+ for (let name of names) {
541
+ caches.delete(name);
542
+ }
543
+ });
544
+ }
545
+
546
+ // 强制刷新页面(绕过缓存)
547
+ window.location.reload(true);
548
+ });
549
+
550
+ // 获取无效Cookie列表
551
+ async function getInvalidCookies() {
552
+ try {
553
+ const response = await fetch('/v1/invalid-cookies', {
554
+ method: 'GET',
555
+ headers: {
556
+ 'Content-Type': 'application/json',
557
+ 'Cache-Control': 'no-cache'
558
+ }
559
+ });
560
+
561
+ if (!response.ok) {
562
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
563
+ }
564
+
565
+ const data = await response.json();
566
+ return data.invalidCookies;
567
+ } catch (error) {
568
+ console.error('获取无效Cookie失败:', error);
569
+ throw error;
570
+ }
571
+ }
572
+
573
+ // 清除特定无效Cookie
574
+ async function clearInvalidCookie(cookie) {
575
+ try {
576
+ const response = await fetch(`/v1/invalid-cookies/${encodeURIComponent(cookie)}`, {
577
+ method: 'DELETE',
578
+ headers: {
579
+ 'Content-Type': 'application/json',
580
+ 'Cache-Control': 'no-cache'
581
+ }
582
+ });
583
+
584
+ if (!response.ok) {
585
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
586
+ }
587
+
588
+ const data = await response.json();
589
+ return data.success;
590
+ } catch (error) {
591
+ console.error(`清除无效Cookie失败:`, error);
592
+ throw error;
593
+ }
594
+ }
595
+
596
+ // 清除所有无效Cookie
597
+ async function clearAllInvalidCookies() {
598
+ try {
599
+ const response = await fetch('/v1/invalid-cookies', {
600
+ method: 'DELETE',
601
+ headers: {
602
+ 'Content-Type': 'application/json',
603
+ 'Cache-Control': 'no-cache'
604
+ }
605
+ });
606
+
607
+ if (!response.ok) {
608
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
609
+ }
610
+
611
+ const data = await response.json();
612
+ return data.success;
613
+ } catch (error) {
614
+ console.error('清除所有无效Cookie失败:', error);
615
+ throw error;
616
+ }
617
+ }
618
+
619
+ // 渲染无效Cookie列表
620
+ async function renderInvalidCookies() {
621
+ const container = document.getElementById('invalidCookiesContainer');
622
+
623
+ try {
624
+ const invalidCookies = await getInvalidCookies();
625
+
626
+ if (invalidCookies.length === 0) {
627
+ container.innerHTML = '<div class="info">没有检测到无效Cookie</div>';
628
+ return;
629
+ }
630
+
631
+ let html = '<table><thead><tr><th>无效Cookie</th><th>操作</th></tr></thead><tbody>';
632
+
633
+ invalidCookies.forEach(cookie => {
634
+ // 截断显示cookie,避免页面过长
635
+ const displayCookie = cookie.length > 50 ? cookie.substring(0, 50) + '...' : cookie;
636
+
637
+ html += `
638
+ <tr>
639
+ <td class="cookie-text" title="${cookie}">${displayCookie}</td>
640
+ <td>
641
+ <button class="action-btn clear-invalid-cookie" data-cookie="${cookie}">
642
+ 清除
643
+ </button>
644
+ </td>
645
+ </tr>
646
+ `;
647
+ });
648
+
649
+ html += '</tbody></table>';
650
+ container.innerHTML = html;
651
+
652
+ // 添加清除按钮事件监听
653
+ document.querySelectorAll('.clear-invalid-cookie').forEach(button => {
654
+ button.addEventListener('click', async function() {
655
+ const cookie = this.getAttribute('data-cookie');
656
+
657
+ try {
658
+ await clearInvalidCookie(cookie);
659
+ showMessage('invalidCookiesContainer', '无效Cookie已清除', 'info');
660
+ renderInvalidCookies(); // 重新渲染列表
661
+ } catch (error) {
662
+ showMessage('invalidCookiesContainer', `清除失败: ${error.message}`, 'error');
663
+ }
664
+ });
665
+ });
666
+
667
+ } catch (error) {
668
+ container.innerHTML = `<div class="error">加载失败: ${error.message}</div>`;
669
+ }
670
+ }
671
+
672
+ // 清除所有无效Cookie按钮事件
673
+ document.getElementById('clearAllInvalidCookies').addEventListener('click', async function() {
674
+ try {
675
+ await clearAllInvalidCookies();
676
+ showMessage('invalidCookiesContainer', '所有无效Cookie已清除', 'info');
677
+ renderInvalidCookies(); // 重新渲染列表
678
+ } catch (error) {
679
+ showMessage('invalidCookiesContainer', `清除失败: ${error.message}`, 'error');
680
+ }
681
+ });
682
+
683
+ // 页面加载时获取 API Key 列表和无效Cookie列表
684
+ document.addEventListener('DOMContentLoaded', function() {
685
+ checkAuth();
686
+ loadApiKeys();
687
+ renderInvalidCookies();
688
+ populateRefreshApiKeySelect();
689
+ });
690
+
691
+ // 显示消息的通用函数
692
+ function showMessage(containerId, message, type) {
693
+ const container = document.getElementById(containerId);
694
+ container.innerHTML = `<div class="${type}">${message}</div>`;
695
+ }
696
+
697
+ // 填充刷新API Key的下拉选择框
698
+ async function populateRefreshApiKeySelect() {
699
+ try {
700
+ const apiKeys = await getApiKeys();
701
+ const select = document.getElementById('refreshApiKey');
702
+
703
+ // 清空现有选项(保留"所有API Key"选项)
704
+ while (select.options.length > 1) {
705
+ select.remove(1);
706
+ }
707
+
708
+ // 添加API Key选项
709
+ apiKeys.forEach(key => {
710
+ const option = document.createElement('option');
711
+ option.value = key.key;
712
+ option.textContent = `${key.key} (${key.cookieCount} 个Cookie)`;
713
+ select.appendChild(option);
714
+ });
715
+ } catch (error) {
716
+ console.error('加载API Key选项失败:', error);
717
+ }
718
+ }
719
+
720
+ // 获取API Keys的辅助函数
721
+ async function getApiKeys() {
722
+ const response = await fetch('/v1/api-keys', {
723
+ method: 'GET',
724
+ headers: {
725
+ 'Content-Type': 'application/json',
726
+ 'Cache-Control': 'no-cache'
727
+ }
728
+ });
729
+
730
+ if (!response.ok) {
731
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
732
+ }
733
+
734
+ const data = await response.json();
735
+ return data.success ? data.apiKeys : [];
736
+ }
737
+
738
+ // 刷新Cookie按钮事件
739
+ document.getElementById('refreshCookieBtn').addEventListener('click', async function() {
740
+ const refreshBtn = this;
741
+ const apiKey = document.getElementById('refreshApiKey').value;
742
+ const statusContainer = document.getElementById('refreshStatusContainer');
743
+ const statusText = document.getElementById('refreshStatus');
744
+ const progressBar = document.getElementById('refreshProgress');
745
+
746
+ // 禁用按钮,显示状态容器
747
+ refreshBtn.disabled = true;
748
+ statusContainer.style.display = 'block';
749
+ statusText.textContent = '正在发送刷新请求...';
750
+ progressBar.value = 10;
751
+
752
+ try {
753
+ // 构建请求URL
754
+ let url = '/v1/refresh-cookies';
755
+ if (apiKey) {
756
+ url += `?apiKey=${encodeURIComponent(apiKey)}`;
757
+ }
758
+
759
+ // 发送刷新请求
760
+ statusText.textContent = '正在发送刷新请求...';
761
+ progressBar.value = 20;
762
+
763
+ const response = await fetch(url, {
764
+ method: 'POST',
765
+ headers: {
766
+ 'Content-Type': 'application/json',
767
+ 'Cache-Control': 'no-cache'
768
+ }
769
+ });
770
+
771
+ if (!response.ok) {
772
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
773
+ }
774
+
775
+ // 显示长时间等待提示
776
+ statusText.textContent = '刷新请求已发送,请耐心等待2-12分钟...';
777
+ progressBar.value = 50;
778
+ showMessage('refreshCookieMessage', '刷新请求已发送,由于需要访问Cursor官网获取新Cookie,整个过程可能需要2-12分钟,请耐心等待。您可以关闭此页面,稍后再来查看结果。', 'info');
779
+
780
+ // 启动定时检查刷新状态
781
+ let checkInterval = setInterval(async () => {
782
+ try {
783
+ const statusResponse = await fetch('/v1/refresh-status', {
784
+ method: 'GET',
785
+ headers: {
786
+ 'Cache-Control': 'no-cache'
787
+ }
788
+ });
789
+
790
+ if (!statusResponse.ok) {
791
+ throw new Error(`HTTP错误: ${statusResponse.status} ${statusResponse.statusText}`);
792
+ }
793
+
794
+ const statusData = await statusResponse.json();
795
+ const refreshData = statusData.data;
796
+
797
+ // 更新状态信息
798
+ statusText.textContent = refreshData.message || '正在刷新...';
799
+
800
+ // 根据状态更新进度条和UI
801
+ if (refreshData.status === 'completed') {
802
+ // 刷新完成
803
+ progressBar.value = 100;
804
+ statusText.textContent = `刷新完成: ${refreshData.message}`;
805
+ clearInterval(checkInterval);
806
+
807
+ // 重新加载API Key列表
808
+ await loadApiKeys();
809
+ await populateRefreshApiKeySelect();
810
+
811
+ // 显示成功消息
812
+ showMessage('refreshCookieMessage', `刷新完成: ${refreshData.message}`, 'success');
813
+
814
+ // 启用按钮
815
+ refreshBtn.disabled = false;
816
+
817
+ // 3秒后隐藏状态容器
818
+ setTimeout(() => {
819
+ statusContainer.style.display = 'none';
820
+ }, 3000);
821
+ } else if (refreshData.status === 'failed') {
822
+ // 刷新失败
823
+ progressBar.value = 0;
824
+ statusText.textContent = `刷新失败: ${refreshData.message}`;
825
+ clearInterval(checkInterval);
826
+
827
+ // 显示错误消息
828
+ showMessage('refreshCookieMessage', `刷新失败: ${refreshData.message}`, 'error');
829
+
830
+ // 启用按钮
831
+ refreshBtn.disabled = false;
832
+ } else if (refreshData.status === 'running') {
833
+ // 正在刷新
834
+ progressBar.value = 75;
835
+ } else if (!refreshData.isRunning) {
836
+ // 未知状态但不在运行
837
+ clearInterval(checkInterval);
838
+ refreshBtn.disabled = false;
839
+ }
840
+ } catch (error) {
841
+ console.error('检查刷新状态失败:', error);
842
+ }
843
+ }, 5000); // 每5秒检查一次
844
+
845
+ // 设置超时检查,12分钟后如果还没完成就停止检查
846
+ setTimeout(() => {
847
+ if (checkInterval) {
848
+ clearInterval(checkInterval);
849
+ refreshBtn.disabled = false;
850
+ statusContainer.style.display = 'none';
851
+ }
852
+ }, 720000);
853
+ } catch (error) {
854
+ console.error('刷新Cookie失败:', error);
855
+ statusText.textContent = '刷新请求发送失败';
856
+ progressBar.value = 0;
857
+ showMessage('refreshCookieMessage', `刷新请求发送失败: ${error.message}`, 'error');
858
+ refreshBtn.disabled = false;
859
+ }
860
+ });
861
+
862
+ // 检查登录状态
863
+ function checkAuth() {
864
+ const token = localStorage.getItem('adminToken');
865
+ if (!token) {
866
+ window.location.href = '/login.html';
867
+ return;
868
+ }
869
+
870
+ // 验证token
871
+ fetch('/v1/admin/verify', {
872
+ headers: {
873
+ 'Authorization': `Bearer ${token}`
874
+ }
875
+ })
876
+ .then(response => response.json())
877
+ .then(data => {
878
+ if (!data.success) {
879
+ localStorage.removeItem('adminToken');
880
+ window.location.href = '/login.html';
881
+ } else {
882
+ // 显示管理员用户名
883
+ document.getElementById('adminUsername').textContent = `管理员:${data.username}`;
884
+ }
885
+ })
886
+ .catch(error => {
887
+ console.error('验证失败:', error);
888
+ localStorage.removeItem('adminToken');
889
+ window.location.href = '/login.html';
890
+ });
891
+ }
892
+
893
+ // 退出登录
894
+ document.getElementById('logoutBtn').addEventListener('click', () => {
895
+ localStorage.removeItem('adminToken');
896
+ window.location.href = '/login.html';
897
+ });
898
+
899
+ // 添加token到所有API请求
900
+ function addAuthHeader(headers = {}) {
901
+ const token = localStorage.getItem('adminToken');
902
+ return {
903
+ ...headers,
904
+ 'Authorization': `Bearer ${token}`
905
+ };
906
+ }
907
+
908
+ // 修改所有fetch请求,添加token
909
+ const originalFetch = window.fetch;
910
+ window.fetch = function(url, options = {}) {
911
+ // 只对管理页面的API请求添加token
912
+ if (url.includes('/v1/api-keys') ||
913
+ url.includes('/v1/invalid-cookies') ||
914
+ url.includes('/v1/refresh-cookies')) {
915
+ options.headers = addAuthHeader(options.headers);
916
+ }
917
+ return originalFetch(url, options);
918
+ };
919
+ </script>
920
+ </body>
921
+ </html>
src/public/login.html ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cursor To OpenAI - 管理员登录</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Arial', sans-serif;
10
+ line-height: 1.6;
11
+ margin: 0;
12
+ padding: 20px;
13
+ color: #333;
14
+ background-color: #f5f5f5;
15
+ display: flex;
16
+ justify-content: center;
17
+ align-items: center;
18
+ min-height: 100vh;
19
+ }
20
+ .container {
21
+ background: #fff;
22
+ padding: 30px;
23
+ border-radius: 8px;
24
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
25
+ width: 100%;
26
+ max-width: 400px;
27
+ }
28
+ h1 {
29
+ color: #2c3e50;
30
+ text-align: center;
31
+ margin-bottom: 30px;
32
+ }
33
+ .form-group {
34
+ margin-bottom: 20px;
35
+ }
36
+ label {
37
+ display: block;
38
+ margin-bottom: 5px;
39
+ font-weight: bold;
40
+ }
41
+ input {
42
+ width: 100%;
43
+ padding: 10px;
44
+ border: 1px solid #ddd;
45
+ border-radius: 4px;
46
+ font-size: 16px;
47
+ box-sizing: border-box;
48
+ }
49
+ button {
50
+ background: #3498db;
51
+ color: white;
52
+ border: none;
53
+ padding: 12px 20px;
54
+ border-radius: 4px;
55
+ cursor: pointer;
56
+ font-size: 16px;
57
+ width: 100%;
58
+ margin-top: 10px;
59
+ }
60
+ button:hover {
61
+ background: #2980b9;
62
+ }
63
+ .message {
64
+ margin-top: 20px;
65
+ padding: 10px;
66
+ border-radius: 4px;
67
+ text-align: center;
68
+ }
69
+ .error {
70
+ background-color: #f8d7da;
71
+ color: #721c24;
72
+ }
73
+ .info {
74
+ background-color: #d4edda;
75
+ color: #155724;
76
+ }
77
+ .toggle-form {
78
+ text-align: center;
79
+ margin-top: 20px;
80
+ color: #3498db;
81
+ cursor: pointer;
82
+ }
83
+ #registerForm {
84
+ display: none;
85
+ }
86
+ </style>
87
+ </head>
88
+ <body>
89
+ <div class="container">
90
+ <h1>管理员登录</h1>
91
+
92
+ <!-- 登录表单 -->
93
+ <form id="loginForm">
94
+ <div class="form-group">
95
+ <label for="loginUsername">用户名</label>
96
+ <input type="text" id="loginUsername" required>
97
+ </div>
98
+ <div class="form-group">
99
+ <label for="loginPassword">密码</label>
100
+ <input type="password" id="loginPassword" required>
101
+ </div>
102
+ <button type="submit">登录</button>
103
+ <div id="loginMessage" class="message"></div>
104
+ </form>
105
+
106
+ <!-- 注册表单 -->
107
+ <form id="registerForm">
108
+ <div class="form-group">
109
+ <label for="registerUsername">用户名</label>
110
+ <input type="text" id="registerUsername" required>
111
+ </div>
112
+ <div class="form-group">
113
+ <label for="registerPassword">密码</label>
114
+ <input type="password" id="registerPassword" required>
115
+ </div>
116
+ <div class="form-group">
117
+ <label for="confirmPassword">确认密码</label>
118
+ <input type="password" id="confirmPassword" required>
119
+ </div>
120
+ <button type="submit">注册</button>
121
+ <div id="registerMessage" class="message"></div>
122
+ </form>
123
+
124
+ <div class="toggle-form" id="toggleForm">
125
+ 还没有账号?点击注册
126
+ </div>
127
+ </div>
128
+
129
+ <script>
130
+ // 获取元素
131
+ const loginForm = document.getElementById('loginForm');
132
+ const registerForm = document.getElementById('registerForm');
133
+ const toggleForm = document.getElementById('toggleForm');
134
+ const loginMessage = document.getElementById('loginMessage');
135
+ const registerMessage = document.getElementById('registerMessage');
136
+
137
+ // 切换表单显示
138
+ let isLoginForm = true;
139
+ toggleForm.addEventListener('click', () => {
140
+ isLoginForm = !isLoginForm;
141
+ loginForm.style.display = isLoginForm ? 'block' : 'none';
142
+ registerForm.style.display = isLoginForm ? 'none' : 'block';
143
+ toggleForm.textContent = isLoginForm ? '还没有账号?点击注册' : '已有账号?点击登录';
144
+ loginMessage.textContent = '';
145
+ registerMessage.textContent = '';
146
+ });
147
+
148
+ // 检查是否已有管理员账号
149
+ async function checkAdminExists() {
150
+ try {
151
+ const response = await fetch('/v1/admin/check');
152
+ const data = await response.json();
153
+
154
+ if (data.exists) {
155
+ // 如果已有管理员,显示登录表单
156
+ loginForm.style.display = 'block';
157
+ registerForm.style.display = 'none';
158
+ toggleForm.style.display = 'none';
159
+ } else {
160
+ // 如果没有管理员,显示注册表单
161
+ loginForm.style.display = 'none';
162
+ registerForm.style.display = 'block';
163
+ toggleForm.style.display = 'none';
164
+ registerMessage.innerHTML = '<div class="info">首次使用,请注册管理员账号</div>';
165
+ }
166
+ } catch (error) {
167
+ console.error('检查管理员账号失败:', error);
168
+ }
169
+ }
170
+
171
+ // 登录处理
172
+ loginForm.addEventListener('submit', async (e) => {
173
+ e.preventDefault();
174
+
175
+ const username = document.getElementById('loginUsername').value;
176
+ const password = document.getElementById('loginPassword').value;
177
+
178
+ try {
179
+ const response = await fetch('/v1/admin/login', {
180
+ method: 'POST',
181
+ headers: {
182
+ 'Content-Type': 'application/json',
183
+ },
184
+ body: JSON.stringify({ username, password }),
185
+ });
186
+
187
+ const data = await response.json();
188
+
189
+ if (data.success) {
190
+ // 登录成功,保存token并跳转
191
+ localStorage.setItem('adminToken', data.token);
192
+ window.location.href = '/index.html';
193
+ } else {
194
+ loginMessage.innerHTML = `<div class="error">${data.message}</div>`;
195
+ }
196
+ } catch (error) {
197
+ console.error('登录失败:', error);
198
+ loginMessage.innerHTML = '<div class="error">登录失败,请稍后重试</div>';
199
+ }
200
+ });
201
+
202
+ // 注册处理
203
+ registerForm.addEventListener('submit', async (e) => {
204
+ e.preventDefault();
205
+
206
+ const username = document.getElementById('registerUsername').value;
207
+ const password = document.getElementById('registerPassword').value;
208
+ const confirmPassword = document.getElementById('confirmPassword').value;
209
+
210
+ if (password !== confirmPassword) {
211
+ registerMessage.innerHTML = '<div class="error">两次输入的密码不一致</div>';
212
+ return;
213
+ }
214
+
215
+ try {
216
+ const response = await fetch('/v1/admin/register', {
217
+ method: 'POST',
218
+ headers: {
219
+ 'Content-Type': 'application/json',
220
+ },
221
+ body: JSON.stringify({ username, password }),
222
+ });
223
+
224
+ const data = await response.json();
225
+
226
+ if (data.success) {
227
+ // 注册成功,保存token并跳转
228
+ localStorage.setItem('adminToken', data.token);
229
+ window.location.href = '/index.html';
230
+ } else {
231
+ registerMessage.innerHTML = `<div class="error">${data.message}</div>`;
232
+ }
233
+ } catch (error) {
234
+ console.error('注册失败:', error);
235
+ registerMessage.innerHTML = '<div class="error">注册失败,请稍后重试</div>';
236
+ }
237
+ });
238
+
239
+ // 页面加载时检查管理员账号
240
+ document.addEventListener('DOMContentLoaded', checkAdminExists);
241
+ </script>
242
+ </body>
243
+ </html>
src/routes/index.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const v1Routes = require('./v1');
4
+
5
+ // OpenAI v1 API routes
6
+ router.use('/v1', v1Routes);
7
+
8
+ module.exports = router;
src/routes/v1.js ADDED
@@ -0,0 +1,931 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+
4
+ const $root = require('../proto/message.js');
5
+ const { v4: uuidv4, v5: uuidv5 } = require('uuid');
6
+ const { generateCursorBody, chunkToUtf8String, generateHashed64Hex, generateCursorChecksum } = require('../utils/utils.js');
7
+ const keyManager = require('../utils/keyManager.js');
8
+ const { spawn } = require('child_process');
9
+ const path = require('path');
10
+ const admin = require('../models/admin');
11
+
12
+ // 存储刷新状态的变量
13
+ let refreshStatus = {
14
+ isRunning: false,
15
+ status: 'idle', // idle, running, completed, failed
16
+ message: '',
17
+ startTime: null,
18
+ endTime: null,
19
+ error: null
20
+ };
21
+
22
+ // 检查是否已有管理员账号
23
+ router.get('/admin/check', (req, res) => {
24
+ try {
25
+ return res.json({
26
+ success: true,
27
+ exists: admin.hasAdmin()
28
+ });
29
+ } catch (error) {
30
+ console.error('检查管理员账号失败:', error);
31
+ return res.status(500).json({
32
+ success: false,
33
+ message: error.message
34
+ });
35
+ }
36
+ });
37
+
38
+ // 注册管理员
39
+ router.post('/admin/register', (req, res) => {
40
+ try {
41
+ const { username, password } = req.body;
42
+
43
+ if (!username || !password) {
44
+ return res.status(400).json({
45
+ success: false,
46
+ message: '用户名和密码不能为空'
47
+ });
48
+ }
49
+
50
+ const token = admin.register(username, password);
51
+
52
+ return res.json({
53
+ success: true,
54
+ message: '注册成功',
55
+ token
56
+ });
57
+ } catch (error) {
58
+ console.error('注册管理员失败:', error);
59
+ return res.status(400).json({
60
+ success: false,
61
+ message: error.message
62
+ });
63
+ }
64
+ });
65
+
66
+ // 管理员登录
67
+ router.post('/admin/login', (req, res) => {
68
+ try {
69
+ const { username, password } = req.body;
70
+
71
+ if (!username || !password) {
72
+ return res.status(400).json({
73
+ success: false,
74
+ message: '用户名和密码不能为空'
75
+ });
76
+ }
77
+
78
+ const token = admin.login(username, password);
79
+
80
+ return res.json({
81
+ success: true,
82
+ message: '登录成功',
83
+ token
84
+ });
85
+ } catch (error) {
86
+ console.error('登录失败:', error);
87
+ return res.status(400).json({
88
+ success: false,
89
+ message: error.message
90
+ });
91
+ }
92
+ });
93
+
94
+ // 验证token
95
+ router.get('/admin/verify', (req, res) => {
96
+ try {
97
+ const authHeader = req.headers.authorization;
98
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
99
+ return res.status(401).json({
100
+ success: false,
101
+ message: '未提供认证token'
102
+ });
103
+ }
104
+
105
+ const token = authHeader.split(' ')[1];
106
+ const result = admin.verifyToken(token);
107
+
108
+ return res.json(result);
109
+ } catch (error) {
110
+ console.error('验证token失败:', error);
111
+ return res.status(401).json({
112
+ success: false,
113
+ message: error.message
114
+ });
115
+ }
116
+ });
117
+
118
+ // 添加API key管理路由
119
+ router.post("/api-keys", async (req, res) => {
120
+ try {
121
+ const { apiKey, cookieValues } = req.body;
122
+
123
+ if (!apiKey || !cookieValues) {
124
+ return res.status(400).json({
125
+ error: 'API key and cookie values are required',
126
+ });
127
+ }
128
+
129
+ keyManager.addOrUpdateApiKey(apiKey, cookieValues);
130
+
131
+ return res.json({
132
+ success: true,
133
+ message: 'API key added or updated successfully',
134
+ });
135
+ } catch (error) {
136
+ console.error(error);
137
+ return res.status(500).json({
138
+ error: 'Internal server error',
139
+ });
140
+ }
141
+ });
142
+
143
+ // 获取所有API Keys
144
+ router.get("/api-keys", async (req, res) => {
145
+ try {
146
+ console.log('收到获取API Keys请求');
147
+ const apiKeys = keyManager.getAllApiKeys();
148
+ console.log('获取到的API Keys:', apiKeys);
149
+
150
+ const result = {
151
+ success: true,
152
+ apiKeys: apiKeys.map(apiKey => ({
153
+ key: apiKey,
154
+ cookieCount: keyManager.getAllCookiesForApiKey(apiKey).length,
155
+ })),
156
+ };
157
+ console.log('返回结果:', result);
158
+
159
+ return res.json(result);
160
+ } catch (error) {
161
+ console.error('获取API Keys失败:', error);
162
+ return res.status(500).json({
163
+ error: 'Internal server error',
164
+ message: error.message
165
+ });
166
+ }
167
+ });
168
+
169
+ // 删除API key
170
+ router.delete("/api-keys/:apiKey", async (req, res) => {
171
+ try {
172
+ const { apiKey } = req.params;
173
+
174
+ keyManager.removeApiKey(apiKey);
175
+
176
+ return res.json({
177
+ success: true,
178
+ message: 'API key removed successfully',
179
+ });
180
+ } catch (error) {
181
+ console.error(error);
182
+ return res.status(500).json({
183
+ error: 'Internal server error',
184
+ });
185
+ }
186
+ });
187
+
188
+ // 获取特定API Key的Cookie值
189
+ router.get("/api-keys/:apiKey/cookies", async (req, res) => {
190
+ try {
191
+ const { apiKey } = req.params;
192
+ console.log(`收到获取API Key ${apiKey}的Cookie值请求`);
193
+
194
+ const cookies = keyManager.getAllCookiesForApiKey(apiKey);
195
+ console.log(`API Key ${apiKey}的Cookie值:`, cookies);
196
+
197
+ return res.json({
198
+ success: true,
199
+ cookies: cookies
200
+ });
201
+ } catch (error) {
202
+ console.error(`获取API Key ${req.params.apiKey}的Cookie值失败:`, error);
203
+ return res.status(500).json({
204
+ error: 'Internal server error',
205
+ message: error.message
206
+ });
207
+ }
208
+ });
209
+
210
+ // 获取所有无效的cookie
211
+ router.get("/invalid-cookies", async (req, res) => {
212
+ try {
213
+ const invalidCookies = keyManager.getInvalidCookies();
214
+
215
+ return res.json({
216
+ success: true,
217
+ invalidCookies: Array.from(invalidCookies)
218
+ });
219
+ } catch (error) {
220
+ console.error('获取无效cookie失败:', error);
221
+ return res.status(500).json({
222
+ error: 'Internal server error',
223
+ message: error.message
224
+ });
225
+ }
226
+ });
227
+
228
+ // 清除特定的无效cookie
229
+ router.delete("/invalid-cookies/:cookie", async (req, res) => {
230
+ try {
231
+ const { cookie } = req.params;
232
+ const success = keyManager.clearInvalidCookie(cookie);
233
+
234
+ return res.json({
235
+ success: success,
236
+ message: success ? '无效cookie已清除' : '未找到指定的无效cookie'
237
+ });
238
+ } catch (error) {
239
+ console.error('清除无效cookie失败:', error);
240
+ return res.status(500).json({
241
+ error: 'Internal server error',
242
+ message: error.message
243
+ });
244
+ }
245
+ });
246
+
247
+ // 清除所有无效cookie
248
+ router.delete("/invalid-cookies", async (req, res) => {
249
+ try {
250
+ keyManager.clearAllInvalidCookies();
251
+
252
+ return res.json({
253
+ success: true,
254
+ message: '所有无效cookie已清除'
255
+ });
256
+ } catch (error) {
257
+ console.error('清除所有无效cookie失败:', error);
258
+ return res.status(500).json({
259
+ error: 'Internal server error',
260
+ message: error.message
261
+ });
262
+ }
263
+ });
264
+
265
+ router.get("/models", async (req, res) => {
266
+ try{
267
+ let bearerToken = req.headers.authorization?.replace('Bearer ', '');
268
+
269
+ // 使用keyManager获取实际的cookie
270
+ let authToken = keyManager.getCookieForApiKey(bearerToken);
271
+
272
+ if (authToken && authToken.includes('%3A%3A')) {
273
+ authToken = authToken.split('%3A%3A')[1];
274
+ }
275
+ else if (authToken && authToken.includes('::')) {
276
+ authToken = authToken.split('::')[1];
277
+ }
278
+
279
+ const checksum = req.headers['x-cursor-checksum']
280
+ ?? process.env['x-cursor-checksum']
281
+ ?? generateCursorChecksum(authToken.trim());
282
+ const cursorClientVersion = "0.45.11"
283
+
284
+ const availableModelsResponse = await fetch("https://api2.cursor.sh/aiserver.v1.AiService/AvailableModels", {
285
+ method: 'POST',
286
+ headers: {
287
+ 'accept-encoding': 'gzip',
288
+ 'authorization': `Bearer ${authToken}`,
289
+ 'connect-protocol-version': '1',
290
+ 'content-type': 'application/proto',
291
+ 'user-agent': 'connect-es/1.6.1',
292
+ 'x-cursor-checksum': checksum,
293
+ 'x-cursor-client-version': cursorClientVersion,
294
+ 'x-cursor-timezone': 'Asia/Shanghai',
295
+ 'x-ghost-mode': 'true',
296
+ 'Host': 'api2.cursor.sh',
297
+ },
298
+ })
299
+ const data = await availableModelsResponse.arrayBuffer();
300
+ const buffer = Buffer.from(data);
301
+ try{
302
+ const models = $root.AvailableModelsResponse.decode(buffer).models;
303
+
304
+ return res.json({
305
+ object: "list",
306
+ data: models.map(model => ({
307
+ id: model.name,
308
+ created: Date.now(),
309
+ object: 'model',
310
+ owned_by: 'cursor'
311
+ }))
312
+ })
313
+ } catch (error) {
314
+ const text = buffer.toString('utf-8');
315
+ throw new Error(text);
316
+ }
317
+ }
318
+ catch (error) {
319
+ console.error(error);
320
+ return res.status(500).json({
321
+ error: 'Internal server error',
322
+ });
323
+ }
324
+ })
325
+
326
+ router.post('/chat/completions', async (req, res) => {
327
+ // o1开头的模型,不支持流式输出
328
+ if (req.body.model.startsWith('o1-') && req.body.stream) {
329
+ return res.status(400).json({
330
+ error: 'Model not supported stream',
331
+ });
332
+ }
333
+
334
+ try {
335
+ const { model, messages, stream = false } = req.body;
336
+ let bearerToken = req.headers.authorization?.replace('Bearer ', '');
337
+
338
+ // 使用keyManager获取实际的cookie
339
+ let authToken = keyManager.getCookieForApiKey(bearerToken);
340
+ // 保存原始cookie,用于后续可能的错误处理
341
+ const originalAuthToken = authToken;
342
+ //console.log('原始cookie:', originalAuthToken);
343
+
344
+ if (authToken && authToken.includes('%3A%3A')) {
345
+ authToken = authToken.split('%3A%3A')[1];
346
+ }
347
+ else if (authToken && authToken.includes('::')) {
348
+ authToken = authToken.split('::')[1];
349
+ }
350
+
351
+ if (!messages || !Array.isArray(messages) || messages.length === 0 || !authToken) {
352
+ return res.status(400).json({
353
+ error: 'Invalid request. Messages should be a non-empty array and authorization is required',
354
+ });
355
+ }
356
+
357
+ const checksum = req.headers['x-cursor-checksum']
358
+ ?? process.env['x-cursor-checksum']
359
+ ?? generateCursorChecksum(authToken.trim());
360
+
361
+ const sessionid = uuidv5(authToken, uuidv5.DNS);
362
+ const clientKey = generateHashed64Hex(authToken)
363
+ const cursorClientVersion = "0.45.11"
364
+
365
+ // Request the AvailableModels before StreamChat.
366
+ const availableModelsResponse = await fetch("https://api2.cursor.sh/aiserver.v1.AiService/AvailableModels", {
367
+ method: 'POST',
368
+ headers: {
369
+ 'accept-encoding': 'gzip',
370
+ 'authorization': `Bearer ${authToken}`,
371
+ 'connect-protocol-version': '1',
372
+ 'content-type': 'application/proto',
373
+ 'user-agent': 'connect-es/1.6.1',
374
+ 'x-amzn-trace-id': `Root=${uuidv4()}`,
375
+ 'x-client-key': clientKey,
376
+ 'x-cursor-checksum': checksum,
377
+ 'x-cursor-client-version': cursorClientVersion,
378
+ 'x-cursor-timezone': 'Asia/Shanghai',
379
+ 'x-ghost-mode': 'true',
380
+ "x-request-id": uuidv4(),
381
+ "x-session-id": sessionid,
382
+ 'Host': 'api2.cursor.sh',
383
+ },
384
+ })
385
+
386
+ const cursorBody = generateCursorBody(messages, model);
387
+ const response = await fetch('https://api2.cursor.sh/aiserver.v1.AiService/StreamChat', {
388
+ method: 'POST',
389
+ headers: {
390
+ 'authorization': `Bearer ${authToken}`,
391
+ 'connect-accept-encoding': 'gzip',
392
+ 'connect-content-encoding': 'gzip',
393
+ 'connect-protocol-version': '1',
394
+ 'content-type': 'application/connect+proto',
395
+ 'user-agent': 'connect-es/1.6.1',
396
+ 'x-amzn-trace-id': `Root=${uuidv4()}`,
397
+ 'x-client-key': clientKey,
398
+ 'x-cursor-checksum': checksum,
399
+ 'x-cursor-client-version': cursorClientVersion,
400
+ 'x-cursor-timezone': 'Asia/Shanghai',
401
+ 'x-ghost-mode': 'true',
402
+ 'x-request-id': uuidv4(),
403
+ 'x-session-id': sessionid,
404
+ 'Host': 'api2.cursor.sh',
405
+ },
406
+ body: cursorBody,
407
+ timeout: {
408
+ connect: 5000,
409
+ read: 30000
410
+ }
411
+ });
412
+
413
+ // 处理响应
414
+ if (stream) {
415
+ res.setHeader('Content-Type', 'text/event-stream');
416
+ res.setHeader('Cache-Control', 'no-cache');
417
+ res.setHeader('Connection', 'keep-alive');
418
+
419
+ const responseId = `chatcmpl-${uuidv4()}`;
420
+
421
+
422
+ try {
423
+ let responseEnded = false; // 添加标志,标记响应是否已结束
424
+
425
+ for await (const chunk of response.body) {
426
+ // 如果响应已结束,不再处理后续数据
427
+ if (responseEnded) {
428
+ continue;
429
+ }
430
+
431
+ let text = chunkToUtf8String(chunk);
432
+ // 输出完整的text内容和类型,便于调试
433
+ //console.log("收到的响应:", typeof text, text && typeof text === 'object' ? JSON.stringify(text) : text);
434
+
435
+
436
+ // 检查是否返回了错误对象
437
+ if (text && typeof text === 'object' && text.error) {
438
+ // 检查是否包含特定的无效cookie错误信息
439
+ const errorStr = typeof text.error === 'string' ? text.error : JSON.stringify(text.error);
440
+
441
+ // 处理错误并获取结果
442
+ const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken);
443
+
444
+ // 如果是需要移除的cookie,从API Key中移除
445
+ if (errorResult.shouldRemoveCookie) {
446
+ const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken);
447
+ console.log(`Cookie移除${removed ? '成功' : '失败'}`);
448
+
449
+ // 如果成功移除,在错误消息中添加明确提示
450
+ if (removed) {
451
+ errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\n\n${errorResult.message}`;
452
+ }
453
+ }
454
+
455
+ // 返回错误信息给客户端,作为assistant消息
456
+ res.write(
457
+ `data: ${JSON.stringify({
458
+ id: responseId,
459
+ object: 'chat.completion.chunk',
460
+ created: Math.floor(Date.now() / 1000),
461
+ model: req.body.model,
462
+ choices: [
463
+ {
464
+ index: 0,
465
+ delta: {
466
+ content: errorResult.message,
467
+ },
468
+ },
469
+ ],
470
+ })}\n\n`
471
+ );
472
+
473
+ res.write('data: [DONE]\n\n');
474
+ responseEnded = true; // 标记响应已结束
475
+ break; // 跳出循环,不再处理后续数据
476
+ }
477
+
478
+ if (text && text.length > 0) {
479
+ res.write(
480
+ `data: ${JSON.stringify({
481
+ id: responseId,
482
+ object: 'chat.completion.chunk',
483
+ created: Math.floor(Date.now() / 1000),
484
+ model: req.body.model,
485
+ choices: [
486
+ {
487
+ index: 0,
488
+ delta: {
489
+ content: text,
490
+ },
491
+ },
492
+ ],
493
+ })}\n\n`
494
+ );
495
+ }
496
+ }
497
+
498
+ // 只有在响应尚未结束的情况下,才发送结束标记
499
+ if (!responseEnded) {
500
+ res.write('data: [DONE]\n\n');
501
+ res.end();
502
+ }
503
+ } catch (streamError) {
504
+ console.error('Stream error:', streamError);
505
+ // 确保在发送错误信息前检查响应是否已结束
506
+ if (!res.writableEnded) {
507
+ if (streamError.name === 'TimeoutError') {
508
+ // 将超时错误作为assistant消息发送
509
+ const errorMessage = `⚠️ 请求超时 ⚠️\n\n错误:服务器响应超时,请稍后重试。`;
510
+ res.write(
511
+ `data: ${JSON.stringify({
512
+ id: responseId,
513
+ object: 'chat.completion.chunk',
514
+ created: Math.floor(Date.now() / 1000),
515
+ model: req.body.model,
516
+ choices: [
517
+ {
518
+ index: 0,
519
+ delta: {
520
+ content: errorMessage,
521
+ },
522
+ },
523
+ ],
524
+ })}\n\n`
525
+ );
526
+ } else {
527
+ // 将处理错误作为assistant消息发送
528
+ const errorMessage = `⚠️ 处理错误 ⚠️\n\n错误:流处理出错,请稍后重试。\n\n${streamError.message || ''}`;
529
+ res.write(
530
+ `data: ${JSON.stringify({
531
+ id: responseId,
532
+ object: 'chat.completion.chunk',
533
+ created: Math.floor(Date.now() / 1000),
534
+ model: req.body.model,
535
+ choices: [
536
+ {
537
+ index: 0,
538
+ delta: {
539
+ content: errorMessage,
540
+ },
541
+ },
542
+ ],
543
+ })}\n\n`
544
+ );
545
+ }
546
+ res.write('data: [DONE]\n\n');
547
+ res.end();
548
+ }
549
+ }
550
+ } else {
551
+ try {
552
+ let text = '';
553
+ let responseEnded = false; // 添加标志,标记响应是否已结束
554
+
555
+ for await (const chunk of response.body) {
556
+ // 如果响应已结束,不再处理后续数据
557
+ if (responseEnded) {
558
+ continue;
559
+ }
560
+
561
+ const chunkText = chunkToUtf8String(chunk);
562
+ // 输出完整的chunkText内容和类型,便于调试
563
+ //console.log("收到的非流式响应:", typeof chunkText, chunkText && typeof chunkText === 'object' ? JSON.stringify(chunkText) : chunkText);
564
+
565
+ // 检查是否返回了错误对象
566
+ if (chunkText && typeof chunkText === 'object' && chunkText.error) {
567
+ //console.error('检测到错误响应:', chunkText.error);
568
+
569
+ // 检查是否包含特定的无效cookie错误信息
570
+ const errorStr = typeof chunkText.error === 'string' ? chunkText.error : JSON.stringify(chunkText.error);
571
+
572
+ // 处理错误并获取结果
573
+ const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken);
574
+
575
+ // 如果是需要移除的cookie,从API Key中移除
576
+ if (errorResult.shouldRemoveCookie) {
577
+ const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken);
578
+ console.log(`Cookie移除${removed ? '成功' : '失败'}`);
579
+
580
+ // 如果成功移除,在错误消息中添加明确提示
581
+ if (removed) {
582
+ errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\n\n${errorResult.message}`;
583
+ }
584
+ }
585
+
586
+ // 无效cookie错误,格式化为assistant消息
587
+ res.json({
588
+ id: `chatcmpl-${uuidv4()}`,
589
+ object: 'chat.completion',
590
+ created: Math.floor(Date.now() / 1000),
591
+ model,
592
+ choices: [
593
+ {
594
+ index: 0,
595
+ message: {
596
+ role: 'assistant',
597
+ content: errorResult.message,
598
+ },
599
+ finish_reason: 'stop',
600
+ },
601
+ ],
602
+ usage: {
603
+ prompt_tokens: 0,
604
+ completion_tokens: 0,
605
+ total_tokens: 0,
606
+ },
607
+ });
608
+
609
+ responseEnded = true; // 标记响应已结束
610
+ break; // 跳出循环,不再处理后续数据
611
+ }
612
+
613
+ // 正常文本,添加到结果中
614
+ if (chunkText && typeof chunkText === 'string') {
615
+ text += chunkText;
616
+ }
617
+ }
618
+
619
+ // 只有在响应尚未结束的情况下,才处理和返回结果
620
+ if (!responseEnded) {
621
+ // 对解析后的字符串进行进一步处理
622
+ text = text.replace(/^.*<\|END_USER\|>/s, '');
623
+ text = text.replace(/^\n[a-zA-Z]?/, '').trim();
624
+ // console.log(text)
625
+
626
+ res.json({
627
+ id: `chatcmpl-${uuidv4()}`,
628
+ object: 'chat.completion',
629
+ created: Math.floor(Date.now() / 1000),
630
+ model,
631
+ choices: [
632
+ {
633
+ index: 0,
634
+ message: {
635
+ role: 'assistant',
636
+ content: text,
637
+ },
638
+ finish_reason: 'stop',
639
+ },
640
+ ],
641
+ usage: {
642
+ prompt_tokens: 0,
643
+ completion_tokens: 0,
644
+ total_tokens: 0,
645
+ },
646
+ });
647
+ }
648
+ } catch (error) {
649
+ console.error('Non-stream error:', error);
650
+ // 确保在发送错误信息前检查响应是否已结束
651
+ if (!res.headersSent) {
652
+ if (error.name === 'TimeoutError') {
653
+ // 使用统一的错误格式
654
+ const errorMessage = `⚠️ 请求超时 ⚠️\n\n错误:服务器响应超时,请稍后重试。`;
655
+ return res.json({
656
+ id: `chatcmpl-${uuidv4()}`,
657
+ object: 'chat.completion',
658
+ created: Math.floor(Date.now() / 1000),
659
+ model: req.body.model || 'unknown',
660
+ choices: [
661
+ {
662
+ index: 0,
663
+ message: {
664
+ role: 'assistant',
665
+ content: errorMessage,
666
+ },
667
+ finish_reason: 'stop',
668
+ },
669
+ ],
670
+ usage: {
671
+ prompt_tokens: 0,
672
+ completion_tokens: 0,
673
+ total_tokens: 0,
674
+ },
675
+ });
676
+ }
677
+ throw error;
678
+ }
679
+ }
680
+ }
681
+ } catch (error) {
682
+ console.error('Error:', error);
683
+ if (!res.headersSent) {
684
+ const errorText = error.name === 'TimeoutError' ? '请求超时' : '服务器内部错误';
685
+
686
+ if (req.body.stream) {
687
+ // 流式响应格式的错误
688
+ const responseId = `chatcmpl-${uuidv4()}`;
689
+ // 添加清晰的错误提示
690
+ const errorMessage = `⚠️ 请求失败 ⚠️\n\n错误:${errorText},请稍后重试。\n\n${error.message || ''}`;
691
+ res.write(
692
+ `data: ${JSON.stringify({
693
+ id: responseId,
694
+ object: 'chat.completion.chunk',
695
+ created: Math.floor(Date.now() / 1000),
696
+ model: req.body.model || 'unknown',
697
+ choices: [
698
+ {
699
+ index: 0,
700
+ delta: {
701
+ content: errorMessage,
702
+ },
703
+ },
704
+ ],
705
+ })}\n\n`
706
+ );
707
+ res.write('data: [DONE]\n\n');
708
+ res.end();
709
+ } else {
710
+ // 非流式响应格式的错误
711
+ // 添加清晰的错误提示
712
+ const errorMessage = `⚠️ 请求失败 ⚠️\n\n错误:${errorText},请稍后重试。\n\n${error.message || ''}`;
713
+ res.json({
714
+ id: `chatcmpl-${uuidv4()}`,
715
+ object: 'chat.completion',
716
+ created: Math.floor(Date.now() / 1000),
717
+ model: req.body.model || 'unknown',
718
+ choices: [
719
+ {
720
+ index: 0,
721
+ message: {
722
+ role: 'assistant',
723
+ content: errorMessage,
724
+ },
725
+ finish_reason: 'stop',
726
+ },
727
+ ],
728
+ usage: {
729
+ prompt_tokens: 0,
730
+ completion_tokens: 0,
731
+ total_tokens: 0,
732
+ },
733
+ });
734
+ }
735
+ }
736
+ }
737
+ });
738
+
739
+ // 触发Cookie刷新
740
+ router.post("/refresh-cookies", async (req, res) => {
741
+ try {
742
+ // 如果已经有刷新进程在运行,则返回错误
743
+ if (refreshStatus.isRunning) {
744
+ return res.status(409).json({
745
+ success: false,
746
+ message: '已有刷新进程在运行,请等待完成后再试'
747
+ });
748
+ }
749
+
750
+ // 获取请求参数
751
+ const apiKey = req.query.apiKey || '';
752
+
753
+ // 重置刷新状态
754
+ refreshStatus = {
755
+ isRunning: true,
756
+ status: 'running',
757
+ message: '正在启动刷新进程...',
758
+ startTime: new Date(),
759
+ endTime: null,
760
+ error: null
761
+ };
762
+
763
+ console.log(`收到刷新Cookie请求,API Key: ${apiKey || '所有'}`);
764
+
765
+ // 构建命令行参数
766
+ const args = [];
767
+ if (apiKey) {
768
+ args.push(apiKey);
769
+ }
770
+
771
+ // 获取auto-refresh-cookies.js的绝对路径
772
+ const scriptPath = path.resolve(__dirname, '../../auto-refresh-cookies.js');
773
+
774
+ // 启动子进程执行刷新脚本
775
+ const refreshProcess = spawn('node', [scriptPath, ...args], {
776
+ stdio: ['ignore', 'pipe', 'pipe']
777
+ });
778
+
779
+ // 收集输出
780
+ let output = '';
781
+
782
+ refreshProcess.stdout.on('data', (data) => {
783
+ const text = data.toString();
784
+ output += text;
785
+ console.log(`刷新进程输出: ${text}`);
786
+
787
+ // 更新状态消息
788
+ if (text.includes('开始自动刷新')) {
789
+ refreshStatus.message = '正在刷新Cookie...';
790
+ } else if (text.includes('刷新结果:')) {
791
+ refreshStatus.message = text.trim();
792
+ }
793
+ });
794
+
795
+ refreshProcess.stderr.on('data', (data) => {
796
+ const text = data.toString();
797
+ output += text;
798
+ console.error(`刷新进程错误: ${text}`);
799
+
800
+ // 更新错误信息
801
+ refreshStatus.error = text.trim();
802
+ refreshStatus.message = `发生错误: ${text.trim()}`;
803
+ });
804
+
805
+ refreshProcess.on('close', (code) => {
806
+ console.log(`刷新进程退出,代码: ${code}`);
807
+
808
+ refreshStatus.isRunning = false;
809
+ refreshStatus.endTime = new Date();
810
+
811
+ if (code === 0) {
812
+ refreshStatus.status = 'completed';
813
+
814
+ // 提取成功信息
815
+ const successMatch = output.match(/成功刷新 (\d+) 个/);
816
+ if (successMatch) {
817
+ refreshStatus.message = `成功刷新 ${successMatch[1]} 个API Key的Cookie`;
818
+ } else {
819
+ refreshStatus.message = '刷新完成';
820
+ }
821
+
822
+ // 子进程执行完成后,重新初始化API Keys来加载新的Cookie
823
+ try {
824
+ const keyManager = require('../utils/keyManager');
825
+ console.log('子进程刷新Cookie完成,重新初始化主进程中的API Keys...');
826
+ keyManager.initializeApiKeys();
827
+ console.log('主进程API Keys重新加载完成');
828
+ } catch (initError) {
829
+ console.error('重新初始化API Keys失败:', initError);
830
+ }
831
+ } else {
832
+ refreshStatus.status = 'failed';
833
+ refreshStatus.message = refreshStatus.error || '刷新失败,请查看服务器日志';
834
+ }
835
+ });
836
+
837
+ // 立即返回响应,不等待刷新完成
838
+ return res.json({
839
+ success: true,
840
+ message: '刷新请求已接受,正在后台处理'
841
+ });
842
+ } catch (error) {
843
+ console.error('触发刷新Cookie失败:', error);
844
+
845
+ // 更新刷新状态
846
+ refreshStatus.isRunning = false;
847
+ refreshStatus.status = 'failed';
848
+ refreshStatus.endTime = new Date();
849
+ refreshStatus.error = error.message;
850
+ refreshStatus.message = `触发刷新失败: ${error.message}`;
851
+
852
+ return res.status(500).json({
853
+ success: false,
854
+ message: `触发刷新失败: ${error.message}`
855
+ });
856
+ }
857
+ });
858
+
859
+ // 查询Cookie刷新状态
860
+ router.get("/refresh-status", (req, res) => {
861
+ try {
862
+ // 返回当前刷新状态
863
+ return res.json({
864
+ success: true,
865
+ data: {
866
+ ...refreshStatus,
867
+ isRunning: refreshStatus.isRunning || false,
868
+ status: refreshStatus.status || 'unknown',
869
+ message: refreshStatus.message || '未触发刷新',
870
+ startTime: refreshStatus.startTime || null,
871
+ endTime: refreshStatus.endTime || null
872
+ }
873
+ });
874
+ } catch (error) {
875
+ console.error('获取刷新状态失败:', error);
876
+ return res.status(500).json({
877
+ success: false,
878
+ message: `获取刷新状态失败: ${error.message}`
879
+ });
880
+ }
881
+ });
882
+
883
+ // 在文件末尾添加错误处理函数
884
+ function handleCursorError(errorStr, bearerToken, originalAuthToken) {
885
+ let message = '';
886
+ let shouldRemoveCookie = false;
887
+
888
+ if (errorStr.includes('Not logged in')) {
889
+ // 更明确的错误日志
890
+ if (originalAuthToken === bearerToken) {
891
+ console.error(`检测到API Key "${bearerToken}" 中没有可用Cookie,正在尝试以向后兼容模式使用API Key本身`);
892
+ message = `错误:API Key "${bearerToken}" 中没有可用的Cookie。请添加有效的Cookie到此API Key,或使用其他有效的API Key。\n\n详细信息:${errorStr}`;
893
+ } else {
894
+ console.error('检测到无效cookie:', originalAuthToken);
895
+ message = `错误:Cookie无效或已过期,请更新Cookie。\n\n详细信息:${errorStr}`;
896
+ }
897
+ shouldRemoveCookie = true;
898
+ } else if (errorStr.includes('You\'ve reached your trial request limit')) {
899
+ console.error('检测到额度用尽cookie:', originalAuthToken);
900
+ message = `错误:Cookie使用额度已用完,请更换Cookie或等待刷新。\n\n详细信息:${errorStr}`;
901
+ shouldRemoveCookie = true;
902
+ } else if (errorStr.includes('User is unauthorized')) {
903
+ console.error('检测到未授权cookie:', originalAuthToken);
904
+ message = `错误:Cookie已被封禁或失效,请更换Cookie。\n\n详细信息:${errorStr}`;
905
+ shouldRemoveCookie = true;
906
+ } else if (errorStr.includes('suspicious activity checks')) {
907
+ console.error('检测到IP黑名单:', originalAuthToken);
908
+ message = `错误:IP可能被列入黑名单,请尝试更换网络环境或使用代理。\n\n详细信息:${errorStr}`;
909
+ shouldRemoveCookie = false;
910
+ } else if (errorStr.includes('Too many computers')) {
911
+ console.error('检测到账户暂时被封禁:', originalAuthToken);
912
+ message = `错误:账户因在多台设备登录而暂时被封禁,请稍后再试或更换账户。\n\n详细信息:${errorStr}`;
913
+ shouldRemoveCookie = true;
914
+ } else if (errorStr.includes('Login expired') || errorStr.includes('login expired')) {
915
+ console.error('检测到登录过期cookie:', originalAuthToken);
916
+ message = `错误:Cookie登录已过期,请更新Cookie。\n\n详细信息:${errorStr}`;
917
+ shouldRemoveCookie = true;
918
+ } else {
919
+ // 非Cookie相关错误
920
+ console.error('检测到其他错误:', errorStr);
921
+ message = `错误:请求失败。\n\n详细信息:${errorStr}`;
922
+ shouldRemoveCookie = false;
923
+ }
924
+
925
+ return {
926
+ message,
927
+ shouldRemoveCookie
928
+ };
929
+ }
930
+
931
+ module.exports = router;
src/utils/cookieRefresher.js ADDED
@@ -0,0 +1,750 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const csv = require('csv-parser');
4
+ const axios = require('axios');
5
+ const AdmZip = require('adm-zip');
6
+ const { Octokit } = require('@octokit/rest');
7
+ const keyManager = require('./keyManager');
8
+ const config = require('../config/config');
9
+ const { extractCookiesFromCsv } = require('./extractCookieFromCsv');
10
+
11
+ // GitHub 仓库信息从环境变量中获取
12
+ const GITHUB_OWNER = process.env.GITHUB_OWNER || 'liuw1535';
13
+ const GITHUB_REPO = process.env.GITHUB_REPO || 'Cursor-Register';
14
+ const GITHUB_TOKEN = process.env.GITHUB_TOKEN; // 需要在环境变量中设置
15
+ const GITHUB_WORKFLOW_ID = process.env.GITHUB_WORKFLOW_ID || 'register.yml';
16
+ const TRIGGER_WORKFLOW = process.env.TRIGGER_WORKFLOW === 'true';
17
+
18
+ // 下载目录
19
+ const DOWNLOAD_DIR = path.join(__dirname, '../../downloads');
20
+ const EXTRACT_DIR = path.join(__dirname, '../../extracted');
21
+
22
+ // 确保目录存在
23
+ function ensureDirectoryExists(dir) {
24
+ if (!fs.existsSync(dir)) {
25
+ try {
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ console.log(`创建目录成功: ${dir}`);
28
+ } catch (err) {
29
+ console.error(`创建目录失败: ${dir}`, err);
30
+ throw err;
31
+ }
32
+ }
33
+ }
34
+
35
+ // 触发 GitHub Actions 工作流
36
+ async function triggerWorkflow() {
37
+ try {
38
+ if (!GITHUB_TOKEN) {
39
+ console.error('未设置 GITHUB_TOKEN,无法触发工作流');
40
+ return null;
41
+ }
42
+
43
+ console.log(`正在触发 GitHub Actions 工作流: ${GITHUB_WORKFLOW_ID}...`);
44
+ const octokit = new Octokit({
45
+ auth: GITHUB_TOKEN
46
+ });
47
+
48
+ // 从环境变量获取工作流参数
49
+ const number = process.env.REGISTER_NUMBER || '2';
50
+ const maxWorkers = process.env.REGISTER_MAX_WORKERS || '1';
51
+ const emailServer = process.env.REGISTER_EMAIL_SERVER || 'TempEmail';
52
+ const ingestToOneapi = process.env.REGISTER_INGEST_TO_ONEAPI === 'true';
53
+ const uploadArtifact = process.env.REGISTER_UPLOAD_ARTIFACT !== 'false'; // 默认为true
54
+ const useConfigFile = process.env.REGISTER_USE_CONFIG_FILE !== 'false'; // 默认为true
55
+ const emailConfigs = process.env.REGISTER_EMAIL_CONFIGS || '[]';
56
+
57
+ console.log(`工作流参数: number=${number}, maxWorkers=${maxWorkers}, emailServer=${emailServer}, ingestToOneapi=${ingestToOneapi}, uploadArtifact=${uploadArtifact}, useConfigFile=${useConfigFile}`);
58
+
59
+ // 获取触发前的最新工作流ID,用于后续识别新触发的工作流
60
+ const { data: beforeWorkflowRuns } = await octokit.actions.listWorkflowRuns({
61
+ owner: GITHUB_OWNER,
62
+ repo: GITHUB_REPO,
63
+ workflow_id: GITHUB_WORKFLOW_ID,
64
+ per_page: 1
65
+ });
66
+
67
+ const latestWorkflowIdBefore = beforeWorkflowRuns.workflow_runs && beforeWorkflowRuns.workflow_runs.length > 0
68
+ ? beforeWorkflowRuns.workflow_runs[0].id
69
+ : 0;
70
+
71
+ console.log(`触发前最新工作流ID: ${latestWorkflowIdBefore}`);
72
+
73
+ // 触发工作流
74
+ const response = await octokit.actions.createWorkflowDispatch({
75
+ owner: GITHUB_OWNER,
76
+ repo: GITHUB_REPO,
77
+ workflow_id: GITHUB_WORKFLOW_ID,
78
+ ref: 'main', // 默认使用 main 分支,可以根据需要修改
79
+ inputs: {
80
+ number: number,
81
+ max_workers: maxWorkers,
82
+ email_server: emailServer,
83
+ ingest_to_oneapi: ingestToOneapi.toString(),
84
+ upload_artifact: uploadArtifact.toString(),
85
+ use_config_file: useConfigFile.toString(),
86
+ email_configs: emailConfigs
87
+ }
88
+ });
89
+
90
+ console.log('工作流触发成功,等待工作流开始运行...');
91
+
92
+ // 等待新工作流出现并获取其ID
93
+ let newWorkflowRunId = null;
94
+ let findAttempts = 0;
95
+ const maxFindAttempts = 30; // 最多等待30次,每次5秒
96
+
97
+ while (findAttempts < maxFindAttempts && !newWorkflowRunId) {
98
+ findAttempts++;
99
+ console.log(`查找新触发的工作流,尝试 ${findAttempts}/${maxFindAttempts}...`);
100
+
101
+ try {
102
+ const { data: afterWorkflowRuns } = await octokit.actions.listWorkflowRuns({
103
+ owner: GITHUB_OWNER,
104
+ repo: GITHUB_REPO,
105
+ workflow_id: GITHUB_WORKFLOW_ID,
106
+ per_page: 5
107
+ });
108
+
109
+ if (afterWorkflowRuns.workflow_runs && afterWorkflowRuns.workflow_runs.length > 0) {
110
+ // 查找ID大于之前最新工作流ID的工作流(即新触发的工作流)
111
+ const newWorkflow = afterWorkflowRuns.workflow_runs.find(run => run.id > latestWorkflowIdBefore);
112
+ if (newWorkflow) {
113
+ newWorkflowRunId = newWorkflow.id;
114
+ console.log(`找到新触发的工作流,ID: ${newWorkflowRunId}, 状态: ${newWorkflow.status}`);
115
+ }
116
+ }
117
+ } catch (error) {
118
+ console.error(`查找工作流时出错 (尝试 ${findAttempts}/${maxFindAttempts}): ${error.message}`);
119
+ // 出错时继续尝试,不中断循环
120
+ }
121
+
122
+ if (!newWorkflowRunId) {
123
+ // 等待5秒后再次检查
124
+ await new Promise(resolve => setTimeout(resolve, 5000));
125
+ }
126
+ }
127
+
128
+ if (!newWorkflowRunId) {
129
+ console.log('未能找到新触发的工作流,可能触发失败');
130
+ return null;
131
+ }
132
+
133
+ // 等待工作流完成
134
+ let attempts = 0;
135
+ const maxAttempts = 120; // 最多等待120次,每次30秒,总共60分钟
136
+ let consecutiveErrors = 0;
137
+ const maxConsecutiveErrors = 5; // 最多允许连续5次错误
138
+
139
+ while (attempts < maxAttempts) {
140
+ attempts++;
141
+ console.log(`等待工作流完成,尝试 ${attempts}/${maxAttempts}...`);
142
+
143
+ try {
144
+ // 获取工作流状态
145
+ const { data: workflowRun } = await octokit.actions.getWorkflowRun({
146
+ owner: GITHUB_OWNER,
147
+ repo: GITHUB_REPO,
148
+ run_id: newWorkflowRunId
149
+ });
150
+
151
+ // 重置连续错误计数
152
+ consecutiveErrors = 0;
153
+
154
+ console.log(`工作流状态: ${workflowRun.status}, 结果: ${workflowRun.conclusion || '进行中'}`);
155
+
156
+ // 检查工作流是否完成
157
+ if (workflowRun.status === 'completed') {
158
+ if (workflowRun.conclusion === 'success') {
159
+ console.log(`工作流运行成功,ID: ${newWorkflowRunId}`);
160
+ return workflowRun;
161
+ } else {
162
+ console.log(`工作流运行失败,结果: ${workflowRun.conclusion}`);
163
+ return null;
164
+ }
165
+ }
166
+ } catch (error) {
167
+ consecutiveErrors++;
168
+ console.error(`获取工作流状态时出错 (尝试 ${attempts}/${maxAttempts}, 连续错误 ${consecutiveErrors}/${maxConsecutiveErrors}): ${error.message}`);
169
+
170
+ // 如果连续错误次数超过阈值,则放弃
171
+ if (consecutiveErrors >= maxConsecutiveErrors) {
172
+ console.error(`连续错误次数超过阈值 (${maxConsecutiveErrors}),放弃等待`);
173
+ throw new Error(`连续 ${maxConsecutiveErrors} 次获取工作流状态失败: ${error.message}`);
174
+ }
175
+
176
+ // 错误后等待时间稍微延长
177
+ await new Promise(resolve => setTimeout(resolve, 10000));
178
+ // 继续循环,不中断
179
+ continue;
180
+ }
181
+
182
+ // 等待30秒后再次检查
183
+ await new Promise(resolve => setTimeout(resolve, 30000));
184
+ }
185
+
186
+ console.log('等待工作流完成超时');
187
+ return null;
188
+ } catch (error) {
189
+ console.error('触发工作流失败:', error);
190
+ throw error; // 重新抛出错误,让调用者处理
191
+ }
192
+ }
193
+
194
+ // 从 GitHub Actions 获取最新的 Artifact
195
+ async function getLatestArtifact() {
196
+ try {
197
+ console.log('正在连接 GitHub API...');
198
+ const octokit = new Octokit({
199
+ auth: GITHUB_TOKEN
200
+ });
201
+
202
+ // 如果配置了自动触发工作流,则先触发工作流
203
+ let workflowRun = null;
204
+ if (TRIGGER_WORKFLOW) {
205
+ console.log('配置了自动触发工作流,正在触发...');
206
+ try {
207
+ workflowRun = await triggerWorkflow();
208
+ } catch (error) {
209
+ console.error('触发工作流过程中出现错误:', error.message);
210
+ console.log('尝试继续使用已找到的工作流ID...');
211
+
212
+ // 尝试获取最新的工作流,看是否有正在运行的工作流
213
+ const { data: runningWorkflows } = await octokit.actions.listWorkflowRuns({
214
+ owner: GITHUB_OWNER,
215
+ repo: GITHUB_REPO,
216
+ workflow_id: GITHUB_WORKFLOW_ID,
217
+ status: 'in_progress',
218
+ per_page: 5
219
+ });
220
+
221
+ if (runningWorkflows.workflow_runs && runningWorkflows.workflow_runs.length > 0) {
222
+ // 找到正在运行的工作流
223
+ const runningWorkflow = runningWorkflows.workflow_runs[0];
224
+ console.log(`找到正在运行的工作流,ID: ${runningWorkflow.id}, 状态: ${runningWorkflow.status}`);
225
+
226
+ // 等待工作流完成
227
+ let attempts = 0;
228
+ const maxAttempts = 120; // 最多等待120次,每次30秒,总共60分钟
229
+ let consecutiveErrors = 0;
230
+ const maxConsecutiveErrors = 5; // 最多允许连续5次错误
231
+
232
+ while (attempts < maxAttempts) {
233
+ attempts++;
234
+ console.log(`等待工作流完成,尝试 ${attempts}/${maxAttempts}...`);
235
+
236
+ try {
237
+ // 获取工作流状态
238
+ const { data: currentWorkflow } = await octokit.actions.getWorkflowRun({
239
+ owner: GITHUB_OWNER,
240
+ repo: GITHUB_REPO,
241
+ run_id: runningWorkflow.id
242
+ });
243
+
244
+ // 重置连续错误计数
245
+ consecutiveErrors = 0;
246
+
247
+ console.log(`工作流状态: ${currentWorkflow.status}, 结果: ${currentWorkflow.conclusion || '进行中'}`);
248
+
249
+ // 检查工作流是否完成
250
+ if (currentWorkflow.status === 'completed') {
251
+ if (currentWorkflow.conclusion === 'success') {
252
+ console.log(`工作流运行成功,ID: ${currentWorkflow.id}`);
253
+ workflowRun = currentWorkflow;
254
+ break;
255
+ } else {
256
+ console.log(`工作流运行失败,结果: ${currentWorkflow.conclusion}`);
257
+ break;
258
+ }
259
+ }
260
+ } catch (err) {
261
+ consecutiveErrors++;
262
+ console.error(`获取工作流状态时出错 (尝试 ${attempts}/${maxAttempts}, 连续错误 ${consecutiveErrors}/${maxConsecutiveErrors}): ${err.message}`);
263
+
264
+ // 如果连续错误次数超过阈值,则放弃
265
+ if (consecutiveErrors >= maxConsecutiveErrors) {
266
+ console.error(`连续错误次数超过阈值 (${maxConsecutiveErrors}),放弃等待`);
267
+ break;
268
+ }
269
+
270
+ // 错误后等待时间稍微延长
271
+ await new Promise(resolve => setTimeout(resolve, 10000));
272
+ // 继续循环,不中断
273
+ continue;
274
+ }
275
+
276
+ // 等待30秒后再次检查
277
+ await new Promise(resolve => setTimeout(resolve, 30000));
278
+ }
279
+ }
280
+ }
281
+
282
+ if (!workflowRun) {
283
+ console.log('触发工作流失败或等待超时,尝试获取最新的工作流运行');
284
+ }
285
+ }
286
+
287
+ // 如果没有触发工作流或触发失败,则获取最新的工作流运行
288
+ if (!workflowRun) {
289
+ console.log('获取最新的工作流运行...');
290
+ const { data: workflowRuns } = await octokit.actions.listWorkflowRunsForRepo({
291
+ owner: GITHUB_OWNER,
292
+ repo: GITHUB_REPO,
293
+ status: 'success',
294
+ per_page: 5
295
+ });
296
+
297
+ if (!workflowRuns.workflow_runs || workflowRuns.workflow_runs.length === 0) {
298
+ console.log('没有找到成功的工作流运行');
299
+ return null;
300
+ }
301
+
302
+ // 获取最新成功运行的 Artifacts
303
+ workflowRun = workflowRuns.workflow_runs[0];
304
+ }
305
+
306
+ console.log(`找到最新的工作流运行: ${workflowRun.id}`);
307
+
308
+ // 等待一段时间,确保Artifact已经上传完成
309
+ console.log('等待Artifact上传完成...');
310
+ await new Promise(resolve => setTimeout(resolve, 10000));
311
+
312
+ // 获取工作流的Artifacts
313
+ let artifacts = null;
314
+ let artifactAttempts = 0;
315
+ const maxArtifactAttempts = 10; // 最多尝试10次,每次10秒
316
+
317
+ while (artifactAttempts < maxArtifactAttempts && (!artifacts || !artifacts.artifacts || artifacts.artifacts.length === 0)) {
318
+ artifactAttempts++;
319
+ console.log(`尝试获取Artifacts,尝试 ${artifactAttempts}/${maxArtifactAttempts}...`);
320
+
321
+ try {
322
+ const response = await octokit.actions.listWorkflowRunArtifacts({
323
+ owner: GITHUB_OWNER,
324
+ repo: GITHUB_REPO,
325
+ run_id: workflowRun.id
326
+ });
327
+
328
+ artifacts = response.data;
329
+ } catch (error) {
330
+ console.error(`获取Artifacts时出错 (尝试 ${artifactAttempts}/${maxArtifactAttempts}): ${error.message}`);
331
+ // 出错时继续尝试,不中断循环
332
+ }
333
+
334
+ if (!artifacts || !artifacts.artifacts || artifacts.artifacts.length === 0) {
335
+ console.log('暂时没有找到Artifacts,等待10秒后重试...');
336
+ await new Promise(resolve => setTimeout(resolve, 10000));
337
+ }
338
+ }
339
+
340
+ if (!artifacts || !artifacts.artifacts || artifacts.artifacts.length === 0) {
341
+ console.log('没有找到Artifacts,可能工作流没有生成Artifact');
342
+ return null;
343
+ }
344
+
345
+ console.log(`找到 ${artifacts.artifacts.length} 个Artifacts`);
346
+
347
+ // 查找 Account info Artifact
348
+ const accountInfoArtifact = artifacts.artifacts.find(artifact =>
349
+ artifact.name.toLowerCase().includes('account info'));
350
+
351
+ if (!accountInfoArtifact) {
352
+ console.log('没有找到 Account info Artifact');
353
+ return null;
354
+ }
355
+
356
+ console.log(`找到 Account info Artifact: ${accountInfoArtifact.id}`);
357
+ return accountInfoArtifact;
358
+ } catch (error) {
359
+ console.error('获取 Artifact 失败:', error);
360
+ return null;
361
+ }
362
+ }
363
+
364
+ // 下载 Artifact
365
+ async function downloadArtifact(artifact) {
366
+ let downloadAttempts = 0;
367
+ const maxDownloadAttempts = 5; // 最多尝试5次下载
368
+
369
+ while (downloadAttempts < maxDownloadAttempts) {
370
+ downloadAttempts++;
371
+ try {
372
+ console.log(`开始下载 Artifact: ${artifact.id}... (尝试 ${downloadAttempts}/${maxDownloadAttempts})`);
373
+ ensureDirectoryExists(DOWNLOAD_DIR);
374
+
375
+ const octokit = new Octokit({
376
+ auth: GITHUB_TOKEN
377
+ });
378
+
379
+ // 获取下载 URL
380
+ const { url } = await octokit.actions.downloadArtifact({
381
+ owner: GITHUB_OWNER,
382
+ repo: GITHUB_REPO,
383
+ artifact_id: artifact.id,
384
+ archive_format: 'zip'
385
+ });
386
+
387
+ // 下载 zip 文件
388
+ const zipFilePath = path.join(DOWNLOAD_DIR, `${artifact.id}.zip`);
389
+ const response = await axios({
390
+ method: 'get',
391
+ url: url,
392
+ responseType: 'arraybuffer',
393
+ timeout: 60000 // 设置60秒超时
394
+ });
395
+
396
+ fs.writeFileSync(zipFilePath, response.data);
397
+ console.log(`Artifact 下载完成: ${zipFilePath}`);
398
+ return zipFilePath;
399
+ } catch (error) {
400
+ console.error(`下载 Artifact 失败 (尝试 ${downloadAttempts}/${maxDownloadAttempts}): ${error.message}`);
401
+
402
+ if (downloadAttempts >= maxDownloadAttempts) {
403
+ console.error('达到最大尝试次数,放弃下载');
404
+ return null;
405
+ }
406
+
407
+ // 等待一段时间后重试
408
+ const retryDelay = 10000; // 10秒
409
+ console.log(`等待 ${retryDelay/1000} 秒后重试...`);
410
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
411
+ }
412
+ }
413
+
414
+ return null;
415
+ }
416
+
417
+ // 解压 Artifact
418
+ async function extractArtifact(zipFilePath) {
419
+ let extractAttempts = 0;
420
+ const maxExtractAttempts = 3; // 最多尝试3次解压
421
+
422
+ while (extractAttempts < maxExtractAttempts) {
423
+ extractAttempts++;
424
+ try {
425
+ console.log(`开始解压 Artifact: ${zipFilePath}... (尝试 ${extractAttempts}/${maxExtractAttempts})`);
426
+ ensureDirectoryExists(EXTRACT_DIR);
427
+
428
+ const zip = new AdmZip(zipFilePath);
429
+ zip.extractAllTo(EXTRACT_DIR, true);
430
+ console.log(`Artifact 解压完成: ${EXTRACT_DIR}`);
431
+
432
+ // 查找 token CSV 文件
433
+ const files = fs.readdirSync(EXTRACT_DIR);
434
+ const tokenFile = files.find(file => file.startsWith('token_') && file.endsWith('.csv'));
435
+
436
+ if (!tokenFile) {
437
+ console.log('没有找到 token CSV 文件');
438
+
439
+ if (extractAttempts >= maxExtractAttempts) {
440
+ return null;
441
+ }
442
+
443
+ // 等待一段时间后重试
444
+ const retryDelay = 5000; // 5秒
445
+ console.log(`等待 ${retryDelay/1000} 秒后重试...`);
446
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
447
+ continue;
448
+ }
449
+
450
+ console.log(`找到 token CSV 文件: ${tokenFile}`);
451
+ return path.join(EXTRACT_DIR, tokenFile);
452
+ } catch (error) {
453
+ console.error(`解压 Artifact 失败 (尝试 ${extractAttempts}/${maxExtractAttempts}): ${error.message}`);
454
+
455
+ if (extractAttempts >= maxExtractAttempts) {
456
+ console.error('达到最大尝试次数,放弃解压');
457
+ return null;
458
+ }
459
+
460
+ // 等待一段时间后重试
461
+ const retryDelay = 5000; // 5秒
462
+ console.log(`等待 ${retryDelay/1000} 秒后重试...`);
463
+ await new Promise(resolve => setTimeout(resolve, retryDelay));
464
+ }
465
+ }
466
+
467
+ return null;
468
+ }
469
+
470
+ /**
471
+ * 从CSV文件中提取cookies
472
+ * @param {string} csvFilePath - CSV文件路径
473
+ * @returns {Promise<string[]>} - 提取到的cookie数组
474
+ */
475
+ async function extractCookiesFromCsvFile(csvFilePath) {
476
+ const maxExtractAttempts = 3;
477
+ let attempt = 1;
478
+
479
+ while (attempt <= maxExtractAttempts) {
480
+ console.log(`尝试从CSV文件提取cookies (尝试 ${attempt}/${maxExtractAttempts})...`);
481
+
482
+ try {
483
+ // 使用新的提取模块
484
+ const cookies = await extractCookiesFromCsv(csvFilePath);
485
+
486
+ if (cookies && cookies.length > 0) {
487
+ console.log(`成功从CSV文件提取到 ${cookies.length} 个cookies`);
488
+ return cookies;
489
+ } else {
490
+ console.warn(`未能从CSV文件提取到cookies (尝试 ${attempt}/${maxExtractAttempts})`);
491
+ }
492
+ } catch (error) {
493
+ console.error(`从CSV文件提取cookies时出错 (尝试 ${attempt}/${maxExtractAttempts}):`, error);
494
+ }
495
+
496
+ attempt++;
497
+ if (attempt <= maxExtractAttempts) {
498
+ console.log(`等待5秒后重试...`);
499
+ await new Promise(resolve => setTimeout(resolve, 5000));
500
+ }
501
+ }
502
+
503
+ console.error(`在 ${maxExtractAttempts} 次尝试后未能从CSV文件提取到cookies`);
504
+ return [];
505
+ }
506
+
507
+ // 将新的有效cookie添加到系统中
508
+ function addNewCookiesToSystem(apiKey, newCookies) {
509
+ try {
510
+ console.log(`准备添加 ${newCookies.length} 个新cookie到系统中`);
511
+
512
+ // 获取当前的cookies
513
+ const currentCookies = keyManager.getAllCookiesForApiKey(apiKey) || [];
514
+ console.log(`当前API密钥 ${apiKey} 有 ${currentCookies.length} 个cookies`);
515
+
516
+ // 获取无效的cookies
517
+ const invalidCookies = keyManager.getInvalidCookies() || [];
518
+ console.log(`系统中有 ${invalidCookies.length || 0} 个无效cookies`);
519
+
520
+ // 过滤出新的有效cookie
521
+ let newValidCookies = [];
522
+
523
+ // 检查invalidCookies的类型并相应处理
524
+ if (invalidCookies instanceof Set) {
525
+ newValidCookies = newCookies.filter(cookie =>
526
+ !currentCookies.includes(cookie) && !invalidCookies.has(cookie)
527
+ );
528
+ } else if (Array.isArray(invalidCookies)) {
529
+ newValidCookies = newCookies.filter(cookie =>
530
+ !currentCookies.includes(cookie) && !invalidCookies.includes(cookie)
531
+ );
532
+ } else if (invalidCookies && typeof invalidCookies === 'object') {
533
+ // 如果是普通对象,检查cookie是否作为键存在
534
+ newValidCookies = newCookies.filter(cookie =>
535
+ !currentCookies.includes(cookie) && !(cookie in invalidCookies)
536
+ );
537
+ } else {
538
+ // 如果invalidCookies不是预期的类型,只过滤当前cookies
539
+ newValidCookies = newCookies.filter(cookie => !currentCookies.includes(cookie));
540
+ }
541
+
542
+ console.log(`过滤后有 ${newValidCookies.length} 个新的有效cookies`);
543
+
544
+ // 验证cookie是否完整
545
+ const validatedCookies = newValidCookies.filter(cookie => {
546
+ // 检查cookie是否包含JWT的三个部分
547
+ if (cookie.includes('%3A%3A')) {
548
+ const parts = cookie.split('%3A%3A');
549
+ if (parts.length === 2) {
550
+ const jwt = parts[1];
551
+ // 检查JWT是否包含点(表示JWT的三个部分)
552
+ if (!jwt.includes('.') || jwt.split('.').length !== 3) {
553
+ console.warn(`跳过不完整的cookie: ${cookie}`);
554
+ return false;
555
+ }
556
+ }
557
+ }
558
+ return true;
559
+ });
560
+
561
+ console.log(`验证完整性后有 ${validatedCookies.length} 个有效cookies`);
562
+
563
+ if (validatedCookies.length > 0) {
564
+ // 添加新的有效cookie到系统
565
+ keyManager.addOrUpdateApiKey(apiKey, [...currentCookies, ...validatedCookies]);
566
+ console.log(`成功添加 ${validatedCookies.length} 个新cookie到API密钥 ${apiKey}`);
567
+ return validatedCookies.length; // 返回添加的cookie数量
568
+ } else {
569
+ console.log(`没有新的有效cookie需要添加到API密钥 ${apiKey}`);
570
+ return 0; // 没有添加cookie,返回0
571
+ }
572
+ } catch (error) {
573
+ console.error('添加新cookie到系统时出错:', error);
574
+ return 0; // 出错时返回0
575
+ }
576
+ }
577
+
578
+ // 清理临时文件
579
+ function cleanupTempFiles() {
580
+ try {
581
+ console.log('开始清理临时文件...');
582
+
583
+ // 清理下载目录
584
+ if (fs.existsSync(DOWNLOAD_DIR)) {
585
+ fs.readdirSync(DOWNLOAD_DIR).forEach(file => {
586
+ fs.unlinkSync(path.join(DOWNLOAD_DIR, file));
587
+ });
588
+ }
589
+
590
+ // 清理解压目录
591
+ if (fs.existsSync(EXTRACT_DIR)) {
592
+ fs.readdirSync(EXTRACT_DIR).forEach(file => {
593
+ fs.unlinkSync(path.join(EXTRACT_DIR, file));
594
+ });
595
+ }
596
+
597
+ console.log('临时文件清理完成');
598
+ } catch (error) {
599
+ console.error('清理临时文件失败:', error);
600
+ }
601
+ }
602
+
603
+ // 检查 API Key 是否需要补充 Cookie
604
+ function checkApiKeyNeedRefresh(apiKey, minCookieCount = config.refresh.minCookieCount) {
605
+ const cookies = keyManager.getAllCookiesForApiKey(apiKey);
606
+ return cookies.length < minCookieCount;
607
+ }
608
+
609
+ // 将现有cookie全部设为无效并从API Key中移除
610
+ function markExistingCookiesAsInvalid(apiKey) {
611
+ try {
612
+ // 获取当前API Key的所有cookie
613
+ const currentCookies = keyManager.getAllCookiesForApiKey(apiKey) || [];
614
+ console.log(`正在将API Key ${apiKey} 的 ${currentCookies.length} 个现有cookie标记为无效...`);
615
+
616
+ // 如果没有cookie,直接返回
617
+ if (currentCookies.length === 0) {
618
+ console.log(`API Key ${apiKey} 没有现有cookie,无需标记为无效`);
619
+ return 0;
620
+ }
621
+
622
+ // 获取无效cookie列表
623
+ const invalidCookies = keyManager.getInvalidCookies();
624
+ let markedCount = 0;
625
+
626
+ // 遍历cookie并添加到无效列表
627
+ for (const cookie of currentCookies) {
628
+ // 将cookie添加到无效集合中
629
+ if (invalidCookies instanceof Set) {
630
+ invalidCookies.add(cookie);
631
+ }
632
+ markedCount++;
633
+ }
634
+
635
+ // 保存无效cookie到文件
636
+ keyManager.saveInvalidCookiesToFile();
637
+
638
+ // 清空当前API Key的cookie列表
639
+ keyManager.addOrUpdateApiKey(apiKey, []);
640
+
641
+ // 保存更新后的API Keys
642
+ keyManager.saveApiKeysToFile();
643
+
644
+ console.log(`已将API Key ${apiKey} 的 ${markedCount} 个cookie标记为无效并从API Key中移除`);
645
+ return markedCount;
646
+ } catch (error) {
647
+ console.error(`标记现有cookie为无效时出错:`, error);
648
+ return 0;
649
+ }
650
+ }
651
+
652
+ // 主函数:自动刷新 Cookie
653
+ async function autoRefreshCookies(apiKey, minCookieCount = config.refresh.minCookieCount) {
654
+ console.log(`开始自动刷新 Cookie,目标 API Key: ${apiKey},最小 Cookie 数量: ${minCookieCount}`);
655
+
656
+ try {
657
+ // 检查是否需要刷新
658
+ if (!checkApiKeyNeedRefresh(apiKey, minCookieCount)) {
659
+ console.log(`API Key ${apiKey} 的 Cookie 数量足够,不需要刷新`);
660
+ return {
661
+ success: true,
662
+ message: '当前 Cookie 数量足够,不需要刷新',
663
+ refreshed: 0
664
+ };
665
+ }
666
+
667
+ // 获取最新的 Artifact
668
+ const artifact = await getLatestArtifact();
669
+ if (!artifact) {
670
+ return {
671
+ success: false,
672
+ message: '获取 Artifact 失败',
673
+ refreshed: 0
674
+ };
675
+ }
676
+
677
+ // 下载 Artifact
678
+ const zipFilePath = await downloadArtifact(artifact);
679
+ if (!zipFilePath) {
680
+ return {
681
+ success: false,
682
+ message: '下载 Artifact 失败',
683
+ refreshed: 0
684
+ };
685
+ }
686
+
687
+ // 解压 Artifact
688
+ const csvFilePath = await extractArtifact(zipFilePath);
689
+ if (!csvFilePath) {
690
+ return {
691
+ success: false,
692
+ message: '解压 Artifact 失败',
693
+ refreshed: 0
694
+ };
695
+ }
696
+
697
+ // 提取 Cookie
698
+ const cookies = await extractCookiesFromCsvFile(csvFilePath);
699
+ if (cookies.length === 0) {
700
+ return {
701
+ success: false,
702
+ message: '没有找到有效的 Cookie',
703
+ refreshed: 0
704
+ };
705
+ }
706
+
707
+ // 根据配置决定是否将现有cookie标记为无效
708
+ const refreshMode = process.env.COOKIE_REFRESH_MODE || 'append';
709
+
710
+ if (refreshMode === 'replace') {
711
+ // 将现有cookie标记为无效并从API Key中移除
712
+ console.log('使用替换模式: 将现有cookie标记为无效');
713
+ markExistingCookiesAsInvalid(apiKey);
714
+ } else {
715
+ console.log('使用追加模式: 保留现有cookie,只添加新cookie');
716
+ }
717
+
718
+ // 添加新的 Cookie 到系统
719
+ const addedCount = addNewCookiesToSystem(apiKey, cookies);
720
+
721
+ // 清理临时文件
722
+ cleanupTempFiles();
723
+
724
+ return {
725
+ success: true,
726
+ message: `成功添加 ${addedCount} 个新 Cookie`,
727
+ refreshed: addedCount
728
+ };
729
+ } catch (error) {
730
+ console.error('自动刷新 Cookie 失败:', error);
731
+ return {
732
+ success: false,
733
+ message: `刷新失败: ${error.message}`,
734
+ refreshed: 0
735
+ };
736
+ }
737
+ }
738
+
739
+ module.exports = {
740
+ autoRefreshCookies,
741
+ checkApiKeyNeedRefresh,
742
+ getLatestArtifact,
743
+ downloadArtifact,
744
+ extractArtifact,
745
+ extractCookiesFromCsvFile,
746
+ addNewCookiesToSystem,
747
+ cleanupTempFiles,
748
+ triggerWorkflow,
749
+ markExistingCookiesAsInvalid
750
+ };
src/utils/envChecker.js ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * 检查 .env 文件是否存在
6
+ * @returns {boolean} 文件是否存在
7
+ */
8
+ function checkEnvFileExists() {
9
+ const envPath = path.resolve(process.cwd(), '.env');
10
+ return fs.existsSync(envPath);
11
+ }
12
+
13
+ /**
14
+ * 检查必要的环境变量是否已设置
15
+ * @returns {Object} 检查结果,包含是否通过和缺失的变量列表
16
+ */
17
+ function checkRequiredEnvVars() {
18
+ // 定义必要的环境变量列表
19
+ const requiredVars = [
20
+ 'API_KEYS', // API Keys 配置
21
+ ];
22
+
23
+ // 如果启用了自动刷新,则需要检查相关配置
24
+ if (process.env.ENABLE_AUTO_REFRESH === 'true') {
25
+ requiredVars.push(
26
+ 'GITHUB_TOKEN',
27
+ 'GITHUB_OWNER',
28
+ 'GITHUB_REPO',
29
+ 'GITHUB_WORKFLOW_ID',
30
+ 'TRIGGER_WORKFLOW'
31
+ );
32
+ }
33
+
34
+ // 检查每个必要的环境变量
35
+ const missingVars = requiredVars.filter(varName => !process.env[varName]);
36
+
37
+ return {
38
+ passed: missingVars.length === 0,
39
+ missingVars
40
+ };
41
+ }
42
+
43
+ /**
44
+ * 执行环境检查,如果不符合要求则退出程序
45
+ */
46
+ function enforceEnvCheck() {
47
+ console.log('正在检查环境配置...');
48
+
49
+ // 检查 .env 文件是否存在
50
+ const envFileExists = checkEnvFileExists();
51
+ if (!envFileExists) {
52
+ console.error('\n错误: 未找到 .env 文件!');
53
+ console.error('请根据 .env.example 创建 .env 文件并配置必要的环境变量。');
54
+ console.error('执行以下命令复制示例文件: cp .env.example .env,或执行npm run setup\n');
55
+ process.exit(1); // 退出程序,状态码 1 表示错误
56
+ }
57
+
58
+ // 检查必要的环境变量
59
+ const { passed, missingVars } = checkRequiredEnvVars();
60
+ if (!passed) {
61
+ console.error('\n错误: 以下必要的环境变量未在 .env 文件中设置:');
62
+ missingVars.forEach(varName => {
63
+ console.error(` - ${varName}`);
64
+ });
65
+ console.error('\n请在 .env 文件中配置这些变量后重新启动程序。\n');
66
+ process.exit(1); // 退出程序,状态码 1 表示错误
67
+ }
68
+
69
+ console.log('环境检查通过,继续启动程序...');
70
+ }
71
+
72
+ module.exports = {
73
+ checkEnvFileExists,
74
+ checkRequiredEnvVars,
75
+ enforceEnvCheck
76
+ };
src/utils/extractCookieFromCsv.js ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const csv = require('csv-parser');
4
+
5
+ /**
6
+ * 从CSV文件中提取完整的cookie
7
+ * @param {string} csvFilePath - CSV文件路径
8
+ * @returns {Promise<string[]>} - 提取到的cookie数组
9
+ */
10
+ async function extractCookiesFromCsv(csvFilePath) {
11
+ return new Promise((resolve, reject) => {
12
+ try {
13
+ // 检查文件是否存在
14
+ if (!fs.existsSync(csvFilePath)) {
15
+ console.error(`CSV文件不存在: ${csvFilePath}`);
16
+ return resolve([]);
17
+ }
18
+
19
+ // 读取文件内容
20
+ const fileContent = fs.readFileSync(csvFilePath, 'utf8');
21
+ console.log(`文件内容前200个字符: ${fileContent.substring(0, 200)}`);
22
+
23
+ // 检查文件是否为空
24
+ if (!fileContent || fileContent.trim() === '') {
25
+ console.error('CSV文件为空');
26
+ return resolve([]);
27
+ }
28
+
29
+ // 检查文件内容是否包含关键字
30
+ const hasTokenKeyword = fileContent.includes('token');
31
+ const hasUserPrefix = fileContent.includes('user_');
32
+ console.log(`文件包含"token"关键字: ${hasTokenKeyword}`);
33
+ console.log(`文件包含"user_"前缀: ${hasUserPrefix}`);
34
+
35
+ // 如果文件包含user_前缀,尝试直接从内容中提取cookie
36
+ if (hasUserPrefix) {
37
+ const cookies = extractCookiesFromText(fileContent);
38
+ if (cookies.length > 0) {
39
+ console.log(`直接从文件内容中提取到 ${cookies.length} 个Cookie`);
40
+ return resolve(cookies);
41
+ }
42
+ }
43
+
44
+ // 使用csv-parser解析CSV文件
45
+ const cookies = [];
46
+ const possibleTokenFields = ['token', 'cookie', 'value', 'Token', 'Cookie', 'Value'];
47
+
48
+ fs.createReadStream(csvFilePath)
49
+ .pipe(csv())
50
+ .on('data', (row) => {
51
+ // 检查所有可能的字段名
52
+ for (const field of possibleTokenFields) {
53
+ if (row[field] && row[field].includes('user_')) {
54
+ cookies.push(row[field]);
55
+ break;
56
+ }
57
+ }
58
+
59
+ // 如果没有找到预定义的字段,遍历所有字段
60
+ if (cookies.length === 0) {
61
+ for (const field in row) {
62
+ if (row[field] && typeof row[field] === 'string' && row[field].includes('user_')) {
63
+ cookies.push(row[field]);
64
+ break;
65
+ }
66
+ }
67
+ }
68
+ })
69
+ .on('end', () => {
70
+ console.log(`从CSV解析中提取到 ${cookies.length} 个Cookie`);
71
+
72
+ // 如果通过CSV解析没有找到cookie,尝试按行读取
73
+ if (cookies.length === 0) {
74
+ console.log('尝试按行读取文件...');
75
+ const lines = fileContent.split('\n');
76
+ for (const line of lines) {
77
+ if (line.includes('user_')) {
78
+ const extractedCookies = extractCookiesFromText(line);
79
+ extractedCookies.forEach(cookie => {
80
+ if (!cookies.includes(cookie)) {
81
+ cookies.push(cookie);
82
+ }
83
+ });
84
+ }
85
+ }
86
+ console.log(`按行读取后提取到 ${cookies.length} 个Cookie`);
87
+ }
88
+
89
+ // 如果仍然没有找到cookie,尝试从整个文件内容中提取
90
+ if (cookies.length === 0) {
91
+ console.log('尝试从整个文件内容中提取Cookie...');
92
+ const extractedCookies = extractCookiesFromText(fileContent);
93
+ extractedCookies.forEach(cookie => {
94
+ if (!cookies.includes(cookie)) {
95
+ cookies.push(cookie);
96
+ }
97
+ });
98
+ console.log(`从整个文件内容中提取到 ${cookies.length} 个Cookie`);
99
+ }
100
+
101
+ // 验证提取的cookie是否完整
102
+ const validatedCookies = validateCookies(cookies);
103
+
104
+ resolve(validatedCookies);
105
+ })
106
+ .on('error', (error) => {
107
+ console.error('解析CSV文件时出错:', error);
108
+ // 如果解析出错,尝试从整个文件内容中提取
109
+ const extractedCookies = extractCookiesFromText(fileContent);
110
+ console.log(`解析出错后从文件内容中提取到 ${extractedCookies.length} 个Cookie`);
111
+ resolve(validateCookies(extractedCookies));
112
+ });
113
+ } catch (error) {
114
+ console.error('提取Cookie时出错:', error);
115
+ reject(error);
116
+ }
117
+ });
118
+ }
119
+
120
+ /**
121
+ * 从文本中提取cookie
122
+ * @param {string} text - 要提取cookie的文本
123
+ * @returns {string[]} - 提取到的cookie数组
124
+ */
125
+ function extractCookiesFromText(text) {
126
+ const cookies = [];
127
+
128
+ // 使用正则表达式匹配user_开头的cookie
129
+ const regex = /user_[a-zA-Z0-9%]+%3A%3A[a-zA-Z0-9%\.\_\-]+/g;
130
+ const matches = text.match(regex);
131
+
132
+ if (matches) {
133
+ matches.forEach(match => {
134
+ if (!cookies.includes(match)) {
135
+ cookies.push(match);
136
+ }
137
+ });
138
+ }
139
+
140
+ return cookies;
141
+ }
142
+
143
+ /**
144
+ * 验证cookie是否完整
145
+ * @param {string[]} cookies - 要验证的cookie数组
146
+ * @returns {string[]} - 验证后的cookie数组
147
+ */
148
+ function validateCookies(cookies) {
149
+ return cookies.map(cookie => {
150
+ // 检查cookie是否包含JWT的三个部分(header.payload.signature)
151
+ if (cookie.includes('%3A%3A')) {
152
+ const parts = cookie.split('%3A%3A');
153
+ if (parts.length === 2) {
154
+ const jwt = parts[1];
155
+ // 检查JWT是否包含两个点(表示三个部分)
156
+ if (jwt.includes('.') && jwt.split('.').length === 3) {
157
+ return cookie; // cookie完整
158
+ } else {
159
+ console.warn(`检测到不完整的JWT: ${cookie}`);
160
+ // 尝试修复不完整的JWT(如果可能)
161
+ return cookie;
162
+ }
163
+ }
164
+ }
165
+ return cookie;
166
+ });
167
+ }
168
+
169
+ module.exports = {
170
+ extractCookiesFromCsv
171
+ };
src/utils/keyManager.js ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const config = require('../config/config');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ // 定义无效cookie的存储文件路径
6
+ const INVALID_COOKIES_FILE = path.join(__dirname, '../../data/invalid_cookies.json');
7
+ // 定义API Keys的存储文件路径
8
+ const API_KEYS_FILE = path.join(__dirname, '../../data/api_keys.json');
9
+
10
+ // 确保data目录存在
11
+ function ensureDataDirExists() {
12
+ const dataDir = path.join(__dirname, '../../data');
13
+ if (!fs.existsSync(dataDir)) {
14
+ try {
15
+ fs.mkdirSync(dataDir, { recursive: true });
16
+ console.log('创建data目录成功');
17
+ } catch (err) {
18
+ console.error('创建data目录失败:', err);
19
+ }
20
+ }
21
+ }
22
+
23
+ // 存储API key与Cursor cookie的映射关系
24
+ let apiKeyMap = new Map();
25
+
26
+ // 存储每个API key对应的cookie轮询索引
27
+ let rotationIndexes = new Map();
28
+
29
+ // 存储被标记为无效的cookie
30
+ let invalidCookies = new Set();
31
+
32
+ // 从文件加载无效cookie
33
+ function loadInvalidCookiesFromFile() {
34
+ ensureDataDirExists();
35
+
36
+ try {
37
+ if (fs.existsSync(INVALID_COOKIES_FILE)) {
38
+ const data = fs.readFileSync(INVALID_COOKIES_FILE, 'utf8');
39
+ const cookiesArray = JSON.parse(data);
40
+
41
+ // 清空当前集合并添加从文件加载的cookie
42
+ invalidCookies.clear();
43
+ cookiesArray.forEach(cookie => invalidCookies.add(cookie));
44
+
45
+ console.log(`从文件加载了 ${cookiesArray.length} 个无效cookie`);
46
+ } else {
47
+ saveInvalidCookiesToFile(); // 如果文件不存在,创建新文件
48
+ }
49
+ } catch (err) {
50
+ console.error('加载无效cookie文件失败:', err);
51
+ saveInvalidCookiesToFile(); // 如果加载失败,尝试创建新文件
52
+ }
53
+ }
54
+
55
+ // 将无效cookie保存到文件
56
+ function saveInvalidCookiesToFile() {
57
+ ensureDataDirExists();
58
+
59
+ try {
60
+ const cookiesArray = Array.from(invalidCookies);
61
+ fs.writeFileSync(INVALID_COOKIES_FILE, JSON.stringify(cookiesArray, null, 2), 'utf8');
62
+ console.log(`已将 ${cookiesArray.length} 个无效cookie保存到文件`);
63
+ } catch (err) {
64
+ console.error('保存无效cookie文件失败:', err);
65
+ }
66
+ }
67
+
68
+ // 从文件加载API Keys
69
+ function loadApiKeysFromFile() {
70
+ ensureDataDirExists();
71
+
72
+ try {
73
+ if (fs.existsSync(API_KEYS_FILE)) {
74
+ const data = fs.readFileSync(API_KEYS_FILE, 'utf8');
75
+ const apiKeysObj = JSON.parse(data);
76
+
77
+ // 清空现有映射
78
+ apiKeyMap.clear();
79
+ rotationIndexes.clear();
80
+
81
+ // 统计总cookie数量
82
+ let totalCookies = 0;
83
+
84
+ // 添加从文件加载的API Keys
85
+ for (const [apiKey, cookies] of Object.entries(apiKeysObj)) {
86
+ if (Array.isArray(cookies)) {
87
+ apiKeyMap.set(apiKey, cookies);
88
+ rotationIndexes.set(apiKey, 0);
89
+ totalCookies += cookies.length;
90
+ } else {
91
+ console.error(`API Key ${apiKey} 的cookies不是数组,跳过`);
92
+ }
93
+ }
94
+
95
+ const apiKeyCount = Object.keys(apiKeysObj).length;
96
+ console.log(`从文件加载了 ${apiKeyCount} 个API Key,共 ${totalCookies} 个Cookie`);
97
+ return apiKeyCount > 0;
98
+ } else {
99
+ console.log('API Keys文件不存在,将使用配置中的API Keys');
100
+ return false;
101
+ }
102
+ } catch (err) {
103
+ console.error('加载API Keys文件失败:', err);
104
+ return false;
105
+ }
106
+ }
107
+
108
+ // 将API Keys保存到文件
109
+ function saveApiKeysToFile() {
110
+ ensureDataDirExists();
111
+
112
+ try {
113
+ // 将Map转换为普通对象
114
+ const apiKeysObj = {};
115
+ for (const [apiKey, cookies] of apiKeyMap.entries()) {
116
+ apiKeysObj[apiKey] = cookies;
117
+ }
118
+
119
+ // 使用JSON.stringify时避免特殊字符处理问题
120
+ const jsonString = JSON.stringify(apiKeysObj, null, 2);
121
+ fs.writeFileSync(API_KEYS_FILE, jsonString, 'utf8');
122
+ console.log(`已将 ${Object.keys(apiKeysObj).length} 个API Key保存到文件`);
123
+
124
+ // 简化验证过程
125
+ try {
126
+ const savedContent = fs.readFileSync(API_KEYS_FILE, 'utf8');
127
+ JSON.parse(savedContent); // 只验证JSON格式是否正确
128
+ console.log('验证通过: 所有cookie都被完整保存');
129
+ } catch (verifyErr) {
130
+ console.error('验证保存内容时出错:', verifyErr);
131
+ }
132
+ } catch (err) {
133
+ console.error('保存API Keys文件失败:', err);
134
+ }
135
+ }
136
+
137
+ // API Keys初始化函数
138
+ function initializeApiKeys() {
139
+ // 首先从文件加载现有的API Keys
140
+ const loadedFromFile = loadApiKeysFromFile();
141
+
142
+ // 检查环境变量中是否有API Keys配置
143
+ const configApiKeys = config.apiKeys;
144
+ const hasEnvApiKeys = Object.keys(configApiKeys).length > 0;
145
+
146
+ if (hasEnvApiKeys) {
147
+ console.log('从环境变量检测到API Keys配置,将合并到现有配置...');
148
+
149
+ // 记录合并前的Cookie数量
150
+ let beforeMergeCookies = 0;
151
+ for (const cookies of apiKeyMap.values()) {
152
+ beforeMergeCookies += cookies.length;
153
+ }
154
+
155
+ // 合并环境变量中的API Keys到现有映射
156
+ for (const [apiKey, cookieValue] of Object.entries(configApiKeys)) {
157
+ // 获取现有的cookies(如果有)
158
+ const existingCookies = apiKeyMap.get(apiKey) || [];
159
+
160
+ // 准备要添加的新cookies
161
+ let newCookies = [];
162
+ if (typeof cookieValue === 'string') {
163
+ newCookies = [cookieValue];
164
+ } else if (Array.isArray(cookieValue)) {
165
+ newCookies = cookieValue;
166
+ }
167
+
168
+ // 合并cookies,确保不重复
169
+ const mergedCookies = [...existingCookies];
170
+ for (const cookie of newCookies) {
171
+ if (!mergedCookies.includes(cookie)) {
172
+ mergedCookies.push(cookie);
173
+ }
174
+ }
175
+
176
+ // 更新映射
177
+ apiKeyMap.set(apiKey, mergedCookies);
178
+
179
+ // 确保轮询索引存在
180
+ if (!rotationIndexes.has(apiKey)) {
181
+ rotationIndexes.set(apiKey, 0);
182
+ }
183
+ }
184
+
185
+ // 记录合并后的Cookie数量
186
+ let afterMergeCookies = 0;
187
+ for (const cookies of apiKeyMap.values()) {
188
+ afterMergeCookies += cookies.length;
189
+ }
190
+
191
+ console.log(`合并前共有 ${beforeMergeCookies} 个Cookie,合并后共有 ${afterMergeCookies} 个Cookie`);
192
+
193
+ // 保存合并后的结果到文件
194
+ saveApiKeysToFile();
195
+ } else if (!loadedFromFile) {
196
+ console.log('警告: 未能从文件加载API Keys,且环境变量中也没有配置API Keys');
197
+ }
198
+
199
+ // 统计API Keys和Cookies数量
200
+ let totalCookies = 0;
201
+ for (const cookies of apiKeyMap.values()) {
202
+ totalCookies += cookies.length;
203
+ }
204
+
205
+ console.log(`API Keys初始化完成,共有 ${apiKeyMap.size} 个API Key,${totalCookies} 个Cookie`);
206
+
207
+ // 加载无效cookie
208
+ loadInvalidCookiesFromFile();
209
+
210
+ // 从API Key中移除已知的无效cookie
211
+ console.log('开始从API Keys中移除无效cookie...');
212
+ removeInvalidCookiesFromApiKeys();
213
+ }
214
+
215
+ // 从所有API Key中移除已知的无效cookie
216
+ function removeInvalidCookiesFromApiKeys() {
217
+ let totalRemoved = 0;
218
+
219
+ for (const [apiKey, cookies] of apiKeyMap.entries()) {
220
+ const initialLength = cookies.length;
221
+
222
+ // 过滤掉无效的cookie
223
+ const filteredCookies = cookies.filter(cookie => !invalidCookies.has(cookie));
224
+
225
+ // 如果有cookie被移除,更新API Key的cookie列表
226
+ if (filteredCookies.length < initialLength) {
227
+ const removedCount = initialLength - filteredCookies.length;
228
+ totalRemoved += removedCount;
229
+
230
+ apiKeyMap.set(apiKey, filteredCookies);
231
+ rotationIndexes.set(apiKey, 0);
232
+
233
+ console.log(`从API Key ${apiKey} 中移除了 ${removedCount} 个无效cookie,剩余 ${filteredCookies.length} 个`);
234
+ }
235
+ }
236
+
237
+ console.log(`总共从API Keys中移除了 ${totalRemoved} 个无效cookie`);
238
+
239
+ // 如果有cookie被移除,保存更新后的API Keys
240
+ if (totalRemoved > 0) {
241
+ saveApiKeysToFile();
242
+ }
243
+ }
244
+
245
+ // 添加或更新API key映射
246
+ function addOrUpdateApiKey(apiKey, cookieValues) {
247
+ if (!Array.isArray(cookieValues)) {
248
+ cookieValues = [cookieValues];
249
+ }
250
+
251
+ // 过滤掉已知的无效cookie
252
+ const validCookies = cookieValues.filter(cookie => !invalidCookies.has(cookie));
253
+
254
+ if (validCookies.length < cookieValues.length) {
255
+ console.log(`API Key ${apiKey} 中有 ${cookieValues.length - validCookies.length} 个无效cookie被过滤`);
256
+ }
257
+
258
+ apiKeyMap.set(apiKey, validCookies);
259
+ rotationIndexes.set(apiKey, 0);
260
+
261
+ // 保存更新后的API Keys
262
+ saveApiKeysToFile();
263
+ }
264
+
265
+ // 删除API key映射
266
+ function removeApiKey(apiKey) {
267
+ apiKeyMap.delete(apiKey);
268
+ rotationIndexes.delete(apiKey);
269
+
270
+ // 保存更新后的API Keys
271
+ saveApiKeysToFile();
272
+ }
273
+
274
+ // 获取API key对应的cookie值(根据轮询策略)
275
+ function getCookieForApiKey(apiKey, strategy = config.defaultRotationStrategy) {
276
+ // 如果API key不存在,也许是cookie本身,直接返回API key本身(向后兼容)
277
+ if (!apiKeyMap.has(apiKey)) {
278
+ return apiKey;
279
+ }
280
+ const cookies = apiKeyMap.get(apiKey);
281
+
282
+ if (!cookies || cookies.length === 0) {
283
+ return apiKey;
284
+ }
285
+
286
+ if (cookies.length === 1) {
287
+ return cookies[0];
288
+ }
289
+
290
+ // 根据策略选择cookie
291
+ if (strategy === 'random') {
292
+ // 随机策略
293
+ const randomIndex = Math.floor(Math.random() * cookies.length);
294
+ return cookies[randomIndex];
295
+ } else {
296
+ // 轮询策略(round-robin)
297
+ let currentIndex = rotationIndexes.get(apiKey) || 0;
298
+ const cookie = cookies[currentIndex];
299
+
300
+ // 更新索引
301
+ currentIndex = (currentIndex + 1) % cookies.length;
302
+ rotationIndexes.set(apiKey, currentIndex);
303
+
304
+ return cookie;
305
+ }
306
+ }
307
+
308
+ // 获取所有API key
309
+ function getAllApiKeys() {
310
+ return Array.from(apiKeyMap.keys());
311
+ }
312
+
313
+ // 获取API key对应的所有cookie
314
+ function getAllCookiesForApiKey(apiKey) {
315
+ return apiKeyMap.get(apiKey) || [];
316
+ }
317
+
318
+ // 从API key的cookie列表中移除特定cookie
319
+ function removeCookieFromApiKey(apiKey, cookieToRemove) {
320
+ if (!apiKeyMap.has(apiKey)) {
321
+ console.log(`API Key ${apiKey} 不存在,无法移除cookie`);
322
+ return false;
323
+ }
324
+
325
+ const cookies = apiKeyMap.get(apiKey);
326
+ const initialLength = cookies.length;
327
+
328
+ // 检查是否尝试移除与API Key相同的值(可能是向后兼容模式)
329
+ if (cookieToRemove === apiKey && initialLength === 0) {
330
+ console.log(`API Key ${apiKey} 中没有任何cookie,系统正在尝试以向后兼容模式使用API Key本身`);
331
+ return false;
332
+ }
333
+
334
+ // 过滤掉要移除的cookie
335
+ const filteredCookies = cookies.filter(cookie => cookie !== cookieToRemove);
336
+
337
+ // 如果长度没变,说明没有找到要移除的cookie
338
+ if (filteredCookies.length === initialLength) {
339
+ console.log(`未找到要移除的cookie: ${cookieToRemove}`);
340
+ return false;
341
+ }
342
+
343
+ // 更新cookie列表
344
+ apiKeyMap.set(apiKey, filteredCookies);
345
+
346
+ // 重置轮询索引
347
+ rotationIndexes.set(apiKey, 0);
348
+
349
+ // 将移除的cookie添加到无效cookie集合中
350
+ invalidCookies.add(cookieToRemove);
351
+
352
+ // 保存无效cookie到文件
353
+ saveInvalidCookiesToFile();
354
+
355
+ // 保存更新后的API Keys
356
+ saveApiKeysToFile();
357
+
358
+ console.log(`已从API Key ${apiKey} 中移除cookie: ${cookieToRemove}`);
359
+ console.log(`剩余cookie数量: ${filteredCookies.length}`);
360
+
361
+ return true;
362
+ }
363
+
364
+ // 获取所有被标记为无效的cookie
365
+ function getInvalidCookies() {
366
+ return invalidCookies;
367
+ }
368
+
369
+ // 清除特定的无效cookie记录
370
+ function clearInvalidCookie(cookie) {
371
+ const result = invalidCookies.delete(cookie);
372
+
373
+ if (result) {
374
+ // 保存更新后的无效cookie到文件
375
+ saveInvalidCookiesToFile();
376
+ }
377
+
378
+ return result;
379
+ }
380
+
381
+ // 清除所有无效cookie记录
382
+ function clearAllInvalidCookies() {
383
+ invalidCookies.clear();
384
+
385
+ // 保存更新后的无效cookie到文件
386
+ saveInvalidCookiesToFile();
387
+
388
+ return true;
389
+ }
390
+
391
+ module.exports = {
392
+ addOrUpdateApiKey,
393
+ removeApiKey,
394
+ getCookieForApiKey,
395
+ getAllApiKeys,
396
+ getAllCookiesForApiKey,
397
+ initializeApiKeys,
398
+ removeCookieFromApiKey,
399
+ getInvalidCookies,
400
+ clearInvalidCookie,
401
+ clearAllInvalidCookies,
402
+ loadInvalidCookiesFromFile,
403
+ saveInvalidCookiesToFile,
404
+ loadApiKeysFromFile,
405
+ saveApiKeysToFile
406
+ };
src/utils/utils.js ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const os = require('os');
2
+ const zlib = require('zlib');
3
+ const crypto = require('crypto');
4
+ const { v4: uuidv4 } = require('uuid');
5
+ const $root = require('../proto/message.js');
6
+
7
+ const regex = /<\|BEGIN_SYSTEM\|>.*?<\|END_SYSTEM\|>.*?<\|BEGIN_USER\|>.*?<\|END_USER\|>/s;
8
+
9
+ function generateCursorBody(messages, modelName) {
10
+
11
+ const systemMessages = messages
12
+ .filter((msg) => msg.role === 'system');
13
+ const instruction = systemMessages.map((msg) => msg.content).join('\n')
14
+
15
+ const nonSystemMessages = messages
16
+ .filter((msg) => msg.role !== 'system');
17
+ const formattedMessages = nonSystemMessages.map((msg) => ({
18
+ ...msg,
19
+ role: msg.role === 'user' ? 1 : 2,
20
+ messageId: uuidv4()
21
+ }));
22
+
23
+ const chatBody = {
24
+ userMessages: formattedMessages,
25
+ instructions: {
26
+ instruction: "Alway respond in 中文.\n" + instruction
27
+ },
28
+ model: {
29
+ name: modelName,
30
+ empty: '',
31
+ },
32
+ unknown13: 1,
33
+ conversationId: uuidv4(),
34
+ unknown16: 1,
35
+ unknown29: 1,
36
+ unknown31: 0,
37
+ };
38
+
39
+ const errMsg = $root.ChatMessage.verify(chatBody);
40
+ if (errMsg) throw Error(errMsg);
41
+ const chatMessageInstance = $root.ChatMessage.create(chatBody);
42
+ let buffer = $root.ChatMessage.encode(chatMessageInstance).finish();
43
+ let magicNumber = 0x00
44
+ if (formattedMessages.length >= 5){
45
+ buffer = zlib.gzipSync(buffer)
46
+ magicNumber = 0x01
47
+ }
48
+
49
+ const finalBody = Buffer.concat([
50
+ Buffer.from([magicNumber]),
51
+ Buffer.from(buffer.length.toString(16).padStart(8, '0'), 'hex'),
52
+ buffer
53
+ ])
54
+
55
+ return finalBody
56
+ }
57
+
58
+ function chunkToUtf8String(chunk) {
59
+ const results = []
60
+ const errorResults = { hasError: false, errorMessage: '' }
61
+
62
+ try {
63
+ if (!chunk || chunk.length === 0) {
64
+ console.error('收到空的chunk数据');
65
+ return '';
66
+ }
67
+
68
+ const buffer = Buffer.from(chunk, 'hex');
69
+
70
+ for(let i = 0; i < buffer.length; i++){
71
+ try {
72
+ if (i + 5 >= buffer.length) {
73
+ console.error('数据不足以读取魔数和长度');
74
+ break;
75
+ }
76
+
77
+ const magicNumber = parseInt(buffer.subarray(i, i + 1).toString('hex'), 16)
78
+ const dataLength = parseInt(buffer.subarray(i + 1, i + 5).toString('hex'), 16)
79
+
80
+ if (dataLength <= 0 || dataLength > 1000000) {
81
+ console.error(`数据长度异常: ${dataLength}`);
82
+ break;
83
+ }
84
+
85
+ if (i + 5 + dataLength > buffer.length) {
86
+ console.error('数据不足以读取内容');
87
+ break;
88
+ }
89
+
90
+ const data = buffer.subarray(i + 5, i + 5 + dataLength)
91
+
92
+ // 处理不同类型的消息
93
+ const processMessage = async (data, isGzip = false) => {
94
+ try {
95
+ let processedData = data;
96
+ if (isGzip) {
97
+ processedData = zlib.gunzipSync(data);
98
+ }
99
+
100
+ if (magicNumber === 0 || magicNumber === 1) {
101
+ // 处理 Proto 消息
102
+ const resMessage = $root.ResMessage.decode(processedData);
103
+ if (resMessage.content !== undefined) {
104
+ results.push(resMessage.content);
105
+ }
106
+ } else if (magicNumber === 2 || magicNumber === 3) {
107
+ // 处理 JSON 消息
108
+ const utf8 = processedData.toString('utf-8');
109
+ const message = JSON.parse(utf8);
110
+
111
+ if (message && message.error) {
112
+ console.error(`检测到JSON错误对象(${isGzip ? 'Gzip ' : ''}):`, utf8);
113
+ errorResults.hasError = true;
114
+ errorResults.errorMessage = utf8;
115
+ }
116
+ } else {
117
+ console.log('未知的魔数:', magicNumber);
118
+ }
119
+ } catch (error) {
120
+ console.error(`处理${isGzip ? 'Gzip ' : ''}消息错误:`, error);
121
+ }
122
+ };
123
+
124
+ // 根据魔数处理不同类型的消息
125
+ if (magicNumber === 0 || magicNumber === 2) {
126
+ processMessage(data, false);
127
+ } else if (magicNumber === 1 || magicNumber === 3) {
128
+ processMessage(data, true);
129
+ }
130
+
131
+ i += 5 + dataLength - 1;
132
+ } catch (chunkParseError) {
133
+ console.error('解析单个chunk部分错误:', chunkParseError);
134
+ i += 1;
135
+ }
136
+ }
137
+ } catch (err) {
138
+ console.error('解析chunk整体错误:', err);
139
+ }
140
+
141
+ if (errorResults.hasError) {
142
+ return { error: errorResults.errorMessage };
143
+ }
144
+
145
+ return results.join('');
146
+ }
147
+
148
+ function generateUUIDHash(input, salt = '') {
149
+ const hash = crypto.createHash('sha256').update(input + salt).digest('hex');
150
+ const hash128 = hash.substring(0, 32);
151
+ const uuid = `${hash128.substring(0, 8)}-${hash128.substring(8, 12)}-${hash128.substring(12, 16)}-${hash128.substring(16, 20)}-${hash128.substring(20, 32)}`;
152
+
153
+ return uuid;
154
+ }
155
+
156
+ function generateHashed64Hex(input, salt = '') {
157
+ const hash = crypto.createHash('sha256');
158
+ hash.update(input + salt);
159
+ return hash.digest('hex');
160
+ }
161
+
162
+ function obfuscateBytes(byteArray) {
163
+ let t = 165;
164
+ for (let r = 0; r < byteArray.length; r++) {
165
+ byteArray[r] = (byteArray[r] ^ t) + (r % 256);
166
+ t = byteArray[r];
167
+ }
168
+ return byteArray;
169
+ }
170
+
171
+ function generateCursorChecksum(token) {
172
+ // 生成machineId和macMachineId
173
+ const machineId = generateHashed64Hex(token, 'machineId');
174
+ const macMachineId = generateHashed64Hex(token, 'macMachineId');
175
+
176
+ // 获取时间戳并转换为字节数组
177
+ const timestamp = Math.floor(Date.now() / 1e6);
178
+ const byteArray = new Uint8Array([
179
+ (timestamp >> 40) & 255,
180
+ (timestamp >> 32) & 255,
181
+ (timestamp >> 24) & 255,
182
+ (timestamp >> 16) & 255,
183
+ (timestamp >> 8) & 255,
184
+ 255 & timestamp,
185
+ ]);
186
+
187
+ // 混淆字节数组并进行base64编码
188
+ const obfuscatedBytes = obfuscateBytes(byteArray);
189
+ const encodedChecksum = Buffer.from(obfuscatedBytes).toString('base64');
190
+
191
+ // 组合最终的checksum
192
+ return `${encodedChecksum}${machineId}/${macMachineId}`;
193
+ }
194
+
195
+ module.exports = {
196
+ generateCursorBody,
197
+ chunkToUtf8String,
198
+ generateHashed64Hex,
199
+ generateCursorChecksum
200
+ };