github-actions[bot] commited on
Commit
b51f54a
·
1 Parent(s): ad76d93

Update from GitHub Actions

Browse files
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ API_KEYS={}
.env.example ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 服务端口
2
+ PORT=3010
3
+
4
+ # 日志格式 (tiny, combined, common, dev, short)
5
+ MORGAN_FORMAT=tiny
6
+
7
+ # API Key与Cookie映射关系 (JSON格式)
8
+ # 格式: {"自定义API Key": "Cookie值"} 或 {"自定义API Key": ["Cookie值1", "Cookie值2"]}
9
+ API_KEYS={"sk-cursor-123":"user_xxxxxxx","sk-cursor-456":["user_yyyyyyy","user_zzzzzzz"]}
10
+
11
+ # 轮询策略 (random 或 round-robin)
12
+ ROTATION_STRATEGY=round-robin
13
+
14
+ # Cursor校验和 (可选)
15
+ # x-cursor-checksum=xxxxxxxx
16
+
17
+ # 自动刷新Cookie设置
18
+ # 是否启用自动刷新Cookie (true 或 false)
19
+ ENABLE_AUTO_REFRESH=true
20
+
21
+ # 自动刷新Cookie的定时规则 (Cron表达式)
22
+ # 默认每6小时执行一次
23
+ REFRESH_CRON=0 */6 * * *
24
+
25
+ # 每个API Key最小Cookie数量
26
+ # 当Cookie数量低于此值时,会自动尝试刷新
27
+ MIN_COOKIE_COUNT=1000
28
+
29
+ # Cookie刷新模式
30
+ # replace: 每次刷新都将现有cookie全部标记为无效并替换成新cookie(默认)
31
+ # append: 保留现有cookie,仅追加新cookie
32
+ COOKIE_REFRESH_MODE=replace
33
+
34
+ # GitHub 仓库信息
35
+ GITHUB_OWNER=your_name
36
+ GITHUB_REPO=Cursor-Register-fix
37
+
38
+ # GitHub Token (用于从GitHub Actions下载Artifact)
39
+ # 需要有repo权限
40
+ GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
41
+
42
+ # GitHub Actions 工作流ID
43
+ # 用于触发工作流程
44
+ GITHUB_WORKFLOW_ID=cursor_register.yml
45
+
46
+ # 是否自动触发工作流
47
+ # 设置为true时,会自动触发工作流而不是仅获取最新结果
48
+ TRIGGER_WORKFLOW=true
49
+
50
+ # 工作流参数设置 目前只支持gmail,outlook过于复杂,暂时不支持
51
+ # 注册账号数量
52
+ REGISTER_NUMBER=1
53
+ # 最大并发工作线程数
54
+ REGISTER_MAX_WORKERS=1
55
+ # 邮箱服务器类型 (TempEmail 或 IMAP)
56
+ REGISTER_EMAIL_SERVER=IMAP
57
+ # 是否将账号令牌注入到OneAPI (true 或 false)
58
+ REGISTER_INGEST_TO_ONEAPI=false
59
+ # 是否上传账号信息到Artifact (true 或 false)
60
+ REGISTER_UPLOAD_ARTIFACT=true
61
+ # 是否从config.yaml读取邮箱配置 (true 或 false)
62
+ REGISTER_USE_CONFIG_FILE=false
63
+ # 邮箱配置JSON字符串(仅在REGISTER_USE_CONFIG_FILE=false时有效)
64
+ # 格式例如[{"email":"[email protected]","imap_server":"imap.gmail.com","imap_port":993,"username":"[email protected]","password":"your_app_password"}]
65
+ REGISTER_EMAIL_CONFIGS=[]
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
37
+ *.webp filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-alpine
2
+
3
+ # 创建应用目录并设置工作目录
4
+ WORKDIR /app
5
+
6
+ # 复制依赖文件
7
+ COPY package.json package-lock.json ./
8
+
9
+ # 安装依赖
10
+ RUN npm install
11
+
12
+ # 复制应用代码
13
+ COPY . /app
14
+
15
+ # 创建数据目录并设置权限
16
+ RUN mkdir -p /app/data && \
17
+ chown -R node:node /app
18
+
19
+ # 使用非root用户运行应用
20
+ USER node
21
+
22
+ # 暴露端口
23
+ EXPOSE 7860
24
+
25
+ # 启动应用
26
+ CMD ["npm", "run", "start"]
LICENSE ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2024 liuw1535
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
SETUP.md ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cursor-To-OpenAI 一键配置指南
2
+
3
+ 本文档将指导你使用一键配置工具来设置 Cursor-To-OpenAI 环境。
4
+
5
+ ## 准备工作
6
+
7
+ 在开始配置前,请确保你已经:
8
+
9
+ 1. Fork了 [Cursor-Register-fix](https://github.com/liuw1535/Cursor-Register-fix) 仓库到你的GitHub账号
10
+ 2. 创建了一个GitHub个人访问令牌(Personal Access Token),且具有 `repo` 权限
11
+ 3. 拥有至少一个Gmail账号,并启用了两步验证
12
+ 4. 为Gmail账号创建了应用密码(Application Password)
13
+
14
+ ## 配置步骤
15
+
16
+ ### 1. 安装依赖
17
+
18
+ ```bash
19
+ npm install
20
+ ```
21
+
22
+ ### 2. 运行配置脚本
23
+
24
+ ```bash
25
+ npm run setup
26
+ ```
27
+
28
+ 或者直接运行:
29
+
30
+ ```bash
31
+ node setup.js
32
+ ```
33
+
34
+ ### 3. 按照提示输入信息
35
+
36
+ 脚本会引导你输入以下信息:
37
+
38
+ - GitHub用户名:你的GitHub账号用户名
39
+ - GitHub Token:你的个人访问令牌
40
+ - API Key:自定义的API Key,用于访问服务
41
+ - Gmail账号:用于自动注册Cursor账号的Gmail地址
42
+ - Gmail应用密码:对应Gmail账号的应用密码(不是邮箱密码)
43
+
44
+ ### 4. 创建应用密码的步骤
45
+
46
+ 如果你还没有创建Gmail应用密码,请按照以下步骤操作:
47
+
48
+ 1. 访问 [Google账号安全设置](https://myaccount.google.com/security)
49
+ 2. 在"登录Google"部分,点击"两步验证"
50
+ (如果未启用两步验证,需要先启用)
51
+ 3. 在页面底部找到"应用密码",点击进入
52
+ 4. 在"选择应用"下拉菜单中选择"其他(自定义名称)"
53
+ 5. 输入一个名称,例如"Cursor注册"
54
+ 6. 点击"生成"
55
+ 7. 复制生成的16位应用密码(格式如:xxxx xxxx xxxx xxxx)
56
+
57
+ ### 5. 管理邮箱配置
58
+
59
+ 系统提供了一个专门的邮箱配置管理工具,可以随时添加、修改或删除邮箱:
60
+
61
+ ```bash
62
+ npm run manage-emails
63
+ ```
64
+
65
+ 使用此工具可以:
66
+ - 查看所有已配置的邮箱
67
+ - 添加新的Gmail账号
68
+ - 修改现有Gmail账号的配置
69
+ - 删除不再使用的Gmail账号
70
+
71
+ ## 配置完成后
72
+
73
+ 配置完成后,你可以:
74
+
75
+ 1. 启动服务:
76
+
77
+ ```bash
78
+ npm start
79
+ ```
80
+
81
+ 2. 手动触发Cookie刷新:
82
+
83
+ ```bash
84
+ npm run refresh-cookies:force
85
+ ```
86
+
87
+ ## 配置文件说明
88
+
89
+ 脚本会生成`.env`文件,其中包含以下主要配置:
90
+
91
+ - `API_KEYS`:API Key到Cookie的映射关系
92
+ - `GITHUB_OWNER`:你的GitHub用户名
93
+ - `GITHUB_TOKEN`:你的GitHub个人访问令牌
94
+ - `REGISTER_EMAIL_CONFIGS`:Gmail账号配置,用于自动注册
95
+
96
+ ## 注意事项
97
+
98
+ 1. GitHub Token需要具有repo权限,用于访问你fork的仓库
99
+ 2. Gmail应用密码不同于你的Gmail登录密码,是专门为第三方应用生成的
100
+ 3. MIN_COOKIE_COUNT设置为1000,确保系统会尝试刷新Cookie
101
+ 4. 配置完成后,你可以通过Web界面查看和管理Cookie状态
102
+ 5. 始终确保至少有一个有效的Gmail账号配置,否则自动刷新功能将无法正常工作
add-api-key.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const fetch = require('node-fetch');
2
+
3
+ async function addApiKey() {
4
+ try {
5
+ console.log('添加API Key...');
6
+ const response = await fetch('http://localhost:3010/v1/api-keys', {
7
+ method: 'POST',
8
+ headers: {
9
+ 'Content-Type': 'application/json',
10
+ },
11
+ body: JSON.stringify({
12
+ apiKey: 'test-key',
13
+ cookieValues: ['test-cookie'],
14
+ }),
15
+ });
16
+
17
+ console.log('响应状态:', response.status);
18
+
19
+ if (!response.ok) {
20
+ throw new Error(`HTTP错误: ${response.status} ${response.statusText}`);
21
+ }
22
+
23
+ const data = await response.json();
24
+ console.log('响应数据:', data);
25
+
26
+ // 测试获取API Keys
27
+ console.log('\n测试获取API Keys...');
28
+ const getResponse = await fetch('http://localhost:3010/v1/api-keys');
29
+
30
+ console.log('响应状态:', getResponse.status);
31
+
32
+ if (!getResponse.ok) {
33
+ throw new Error(`HTTP错误: ${getResponse.status} ${getResponse.statusText}`);
34
+ }
35
+
36
+ const getData = await getResponse.json();
37
+ console.log('获取到的数据:', getData);
38
+ } catch (error) {
39
+ console.error('操作失败:', error);
40
+ }
41
+ }
42
+
43
+ addApiKey();
auto-refresh-cookies.js ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 加载环境变量
2
+ require('dotenv').config();
3
+
4
+ // 环境检查
5
+ const envChecker = require('./src/utils/envChecker');
6
+ console.log('启动前检查环境配置...');
7
+ envChecker.enforceEnvCheck();
8
+
9
+ // 已适配GitHub Actions工作流新参数 (use_config_file, email_configs)
10
+ console.log('环境检查通过,已适配最新GitHub Actions工作流参数');
11
+
12
+ const cookieRefresher = require('./src/utils/cookieRefresher');
13
+ const keyManager = require('./src/utils/keyManager');
14
+ const config = require('./src/config/config');
15
+
16
+ // 解析命令行参数
17
+ const args = process.argv.slice(2);
18
+ const targetApiKey = args.length > 0 ? args[0] : null;
19
+ const forceRefresh = args.includes('--force') || args.includes('-f');
20
+
21
+ // 最小 Cookie 数量
22
+ const MIN_COOKIE_COUNT = config.refresh.minCookieCount;
23
+
24
+ // 获取Cookie刷新模式
25
+ const COOKIE_REFRESH_MODE = process.env.COOKIE_REFRESH_MODE || 'append';
26
+
27
+ // 主函数
28
+ async function main() {
29
+ console.log('===== 自动刷新 Cookie 开始 =====');
30
+ console.log(`最小 Cookie 数量: ${MIN_COOKIE_COUNT}`);
31
+ console.log(`Cookie 刷新模式: ${COOKIE_REFRESH_MODE} (${COOKIE_REFRESH_MODE === 'replace' ? '替换现有cookie' : '追加新cookie'})`);
32
+
33
+ if (targetApiKey) {
34
+ console.log(`指定刷新 API Key: ${targetApiKey}`);
35
+ }
36
+
37
+ if (forceRefresh) {
38
+ console.log('强制刷新模式: 忽略 Cookie 数量检查');
39
+ }
40
+
41
+ try {
42
+ // 获取所有 API Key
43
+ const apiKeys = keyManager.getAllApiKeys();
44
+
45
+ if (apiKeys.length === 0) {
46
+ console.log('警告: 系统中没有找到任何 API Key');
47
+
48
+ // 检查环境变量中是否有 API Keys
49
+ const envApiKeys = Object.keys(config.apiKeys);
50
+ if (envApiKeys.length > 0) {
51
+ console.log(`检测到环境变量中有 ${envApiKeys.length} 个 API Key,但尚未加载到系统中`);
52
+ console.log('正在重新初始化 API Keys...');
53
+
54
+ // 重新初始化 API Keys
55
+ keyManager.initializeApiKeys();
56
+
57
+ // 重新获取 API Keys
58
+ const refreshedApiKeys = keyManager.getAllApiKeys();
59
+ if (refreshedApiKeys.length > 0) {
60
+ console.log(`成功加载 ${refreshedApiKeys.length} 个 API Key,继续刷新流程`);
61
+ // 继续执行后续刷新逻辑
62
+ } else {
63
+ console.log('初始化后仍未找到 API Key,请检查配置');
64
+ console.log('===== 自动刷新 Cookie 结束 =====');
65
+ return;
66
+ }
67
+ } else {
68
+ console.log('环境变量中也没有配置 API Key,请先添加 API Key');
69
+ console.log('===== 自动刷新 Cookie 结束 =====');
70
+ return;
71
+ }
72
+ }
73
+
74
+ // 重新获取最新的 API Keys(可能已经通过上面的初始化更新了)
75
+ const updatedApiKeys = keyManager.getAllApiKeys();
76
+ console.log(`系统中共有 ${updatedApiKeys.length} 个 API Key`);
77
+
78
+ // 如果指定了特定的 API Key,检查它是否存在
79
+ if (targetApiKey && !updatedApiKeys.includes(targetApiKey)) {
80
+ console.error(`错误: 指定的 API Key "${targetApiKey}" 不存在`);
81
+ console.log('===== 自动刷新 Cookie 异常结束 =====');
82
+ return;
83
+ }
84
+
85
+ // 过滤需要处理的 API Keys
86
+ const keysToProcess = targetApiKey ? [targetApiKey] : updatedApiKeys;
87
+
88
+ // 按 Cookie 数量排序,优先处理 Cookie 数量少的 API Key
89
+ const sortedKeys = keysToProcess.sort((a, b) => {
90
+ const aCount = keyManager.getAllCookiesForApiKey(a).length;
91
+ const bCount = keyManager.getAllCookiesForApiKey(b).length;
92
+ return aCount - bCount; // 升序排列,Cookie 数量少的排在前面
93
+ });
94
+
95
+ // 检查每个 API Key 是否需要刷新
96
+ let refreshedCount = 0;
97
+ let needRefreshCount = 0;
98
+
99
+ for (const apiKey of sortedKeys) {
100
+ const cookies = keyManager.getAllCookiesForApiKey(apiKey);
101
+ console.log(`API Key: ${apiKey}, Cookie 数量: ${cookies.length}`);
102
+
103
+ // 判断是否需要刷新:强制刷新模式或 Cookie 数量低于阈值
104
+ if (forceRefresh || cookies.length < MIN_COOKIE_COUNT) {
105
+ needRefreshCount++;
106
+ if (forceRefresh) {
107
+ console.log(`强制刷新 API Key: ${apiKey}`);
108
+ } else {
109
+ console.log(`API Key ${apiKey} 的 Cookie 数量不足,需要刷新`);
110
+ }
111
+
112
+ // 执行刷新
113
+ console.log(`开始自动刷新 Cookie,目标 API Key: ${apiKey},最小 Cookie 数量: ${MIN_COOKIE_COUNT},刷新模式: ${COOKIE_REFRESH_MODE}`);
114
+ const result = await cookieRefresher.autoRefreshCookies(apiKey, MIN_COOKIE_COUNT);
115
+
116
+ if (result.success) {
117
+ refreshedCount++;
118
+ console.log(`刷新结果: ${result.message}`);
119
+
120
+ // 根据刷新模式输出额外的信息
121
+ if (COOKIE_REFRESH_MODE === 'replace') {
122
+ console.log(`使用替换模式: 现有cookie已全部标记为无效,系统现在只使用新cookie`);
123
+ } else {
124
+ console.log(`使用追加模式: 现有cookie已保留,新cookie已添加到系统`);
125
+ }
126
+ } else {
127
+ console.error(`刷新失败: ${result.message}`);
128
+ }
129
+ } else {
130
+ console.log(`API Key ${apiKey} 的 Cookie 数量足够,不需要刷新`);
131
+ }
132
+ }
133
+
134
+ console.log('===== 自动刷新 Cookie 完成 =====');
135
+ console.log(`共有 ${needRefreshCount} 个 API Key 需要刷新,成功刷新 ${refreshedCount} 个`);
136
+ } catch (error) {
137
+ console.error('自动刷新 Cookie 失败:', error);
138
+ console.log('===== 自动刷新 Cookie 异常结束 =====');
139
+ }
140
+ }
141
+
142
+ // 执行主函数
143
+ main().catch(console.error);
cursor-to-openai-helper.sh ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Colors for better UI
4
+ RED='\033[0;31m'
5
+ GREEN='\033[0;32m'
6
+ YELLOW='\033[0;33m'
7
+ BLUE='\033[0;34m'
8
+ NC='\033[0m' # No Color
9
+
10
+ # Create backups directory if it doesn't exist
11
+ mkdir -p backups
12
+
13
+ # Function to display header
14
+ show_header() {
15
+ clear
16
+ echo -e "${BLUE}=======================================${NC}"
17
+ echo -e "${GREEN} Cursor-To-OpenAI 简易脚本 ${NC}"
18
+ echo -e "${BLUE}=======================================${NC}"
19
+ echo
20
+ }
21
+
22
+ # Function to check if Docker is installed
23
+ check_docker() {
24
+ if ! command -v docker &> /dev/null; then
25
+ echo -e "${RED}未安装Docker。请先安装Docker。${NC}"
26
+ exit 1
27
+ fi
28
+ }
29
+
30
+ # Function to check if Node.js is installed
31
+ check_nodejs() {
32
+ if ! command -v node &> /dev/null; then
33
+ echo -e "${RED}未安装Node.js。请先安装Node.js。${NC}"
34
+ exit 1
35
+ fi
36
+
37
+ if ! command -v npm &> /dev/null; then
38
+ echo -e "${RED}未安装npm。请先安装npm。${NC}"
39
+ exit 1
40
+ fi
41
+ }
42
+
43
+ # Function to backup configuration before update
44
+ backup_configs() {
45
+ echo -e "${YELLOW}正在备份配置文件...${NC}"
46
+ backup_dir="backups/update_backup_$(date +"%Y%m%d_%H%M%S")"
47
+ mkdir -p "$backup_dir"
48
+
49
+ # Backup important configurations
50
+ if [ -d data ]; then
51
+ cp -r data "$backup_dir/"
52
+ fi
53
+
54
+ if [ -f .env ]; then
55
+ cp .env "$backup_dir/"
56
+ fi
57
+
58
+ echo -e "${GREEN}配置文件已备份到 ${backup_dir}${NC}"
59
+ }
60
+
61
+ # Function to restore configuration after update
62
+ restore_configs() {
63
+ if [ -z "$1" ]; then
64
+ echo -e "${RED}未指定备份目录,无法恢复配置。${NC}"
65
+ return 1
66
+ fi
67
+
68
+ backup_dir="$1"
69
+ echo -e "${YELLOW}正在恢复配置文件...${NC}"
70
+
71
+ if [ -d "$backup_dir/data" ]; then
72
+ cp -r "$backup_dir/data/"* data/ 2>/dev/null
73
+ fi
74
+
75
+ if [ -f "$backup_dir/.env" ]; then
76
+ cp "$backup_dir/.env" ./ 2>/dev/null
77
+ fi
78
+
79
+ echo -e "${GREEN}配置文件已恢复${NC}"
80
+ }
81
+
82
+ # Function for installation tasks
83
+ installation_menu() {
84
+ show_header
85
+ echo -e "${YELLOW}===== 安装菜单 =====${NC}"
86
+ echo -e "1) 克隆仓库"
87
+ echo -e "2) 安装依赖"
88
+ echo -e "3) 创建配置文件"
89
+ echo -e "4) 设置管理员账户"
90
+ echo -e "5) 运行安装向导"
91
+ echo -e "6) 构建并启动Docker容器"
92
+ echo -e "7) 使用npm启动"
93
+ echo -e "8) 返回主菜单"
94
+ echo
95
+ echo -n "请输入选择 [1-8]: "
96
+ read -r choice
97
+
98
+ case $choice in
99
+ 1)
100
+ show_header
101
+ echo -e "${YELLOW}正在克隆仓库...${NC}"
102
+ read -p "请输入您的GitHub用户名: " username
103
+ git clone "https://github.com/${username}/cursor-to-openai.git"
104
+ if [ $? -eq 0 ]; then
105
+ echo -e "${GREEN}仓库克隆成功!${NC}"
106
+ cd cursor-to-openai
107
+ else
108
+ echo -e "${RED}克隆仓库失败!${NC}"
109
+ fi
110
+ read -p "按回车键继续..."
111
+ installation_menu
112
+ ;;
113
+ 2)
114
+ show_header
115
+ echo -e "${YELLOW}正在安装依赖...${NC}"
116
+ npm install
117
+ if [ $? -eq 0 ]; then
118
+ echo -e "${GREEN}依赖安装成功!${NC}"
119
+ else
120
+ echo -e "${RED}依赖安装失败!${NC}"
121
+ fi
122
+ read -p "按回车键继续..."
123
+ installation_menu
124
+ ;;
125
+ 3)
126
+ show_header
127
+ echo -e "${YELLOW}正在创建配置文件...${NC}"
128
+
129
+ if [ ! -f .env ]; then
130
+ cp .env.example .env
131
+ echo -e "${GREEN}.env文件已创建。${NC}"
132
+ else
133
+ echo -e "${YELLOW}.env文件已存在。${NC}"
134
+ fi
135
+
136
+ mkdir -p data
137
+
138
+ if [ ! -f data/admin.json ]; then
139
+ cp data/admin.example.json data/admin.json
140
+ echo -e "${GREEN}admin.json文件已创建。${NC}"
141
+ else
142
+ echo -e "${YELLOW}admin.json文件已存在。${NC}"
143
+ fi
144
+
145
+ echo -e "${YELLOW}您应该编辑.env文件来配置您的环境。${NC}"
146
+ read -p "是否现在编辑.env文件?(y/n): " edit_env
147
+ if [[ $edit_env == "y" || $edit_env == "Y" ]]; then
148
+ if command -v nano &> /dev/null; then
149
+ nano .env
150
+ elif command -v vim &> /dev/null; then
151
+ vim .env
152
+ else
153
+ echo -e "${RED}未找到编辑器。请稍后手动编辑.env文件。${NC}"
154
+ fi
155
+ fi
156
+
157
+ read -p "按回车键继续..."
158
+ installation_menu
159
+ ;;
160
+ 4)
161
+ show_header
162
+ echo -e "${YELLOW}正在设置管理员账户...${NC}"
163
+ node scripts/create-admin.js
164
+ read -p "按回车键继续..."
165
+ installation_menu
166
+ ;;
167
+ 5)
168
+ show_header
169
+ echo -e "${YELLOW}正在运行安装向导...${NC}"
170
+ node setup.js
171
+ read -p "按回车键继续..."
172
+ installation_menu
173
+ ;;
174
+ 6)
175
+ show_header
176
+ echo -e "${YELLOW}正在构建并启动Docker容器...${NC}"
177
+ check_docker
178
+ docker compose up -d --build
179
+ if [ $? -eq 0 ]; then
180
+ echo -e "${GREEN}Docker容器启动成功!${NC}"
181
+ echo -e "${GREEN}访问管理界面: http://localhost:3010${NC}"
182
+ else
183
+ echo -e "${RED}启动Docker容器失败!${NC}"
184
+ fi
185
+ read -p "按回车键继续..."
186
+ installation_menu
187
+ ;;
188
+ 7)
189
+ show_header
190
+ echo -e "${YELLOW}正在使用npm启动...${NC}"
191
+ check_nodejs
192
+ npm start &
193
+ if [ $? -eq 0 ]; then
194
+ echo -e "${GREEN}服务启动成功!${NC}"
195
+ echo -e "${GREEN}访问管理界面: http://localhost:3010${NC}"
196
+ else
197
+ echo -e "${RED}启动服务失败!${NC}"
198
+ fi
199
+ read -p "按回车键继续..."
200
+ installation_menu
201
+ ;;
202
+ 8)
203
+ main_menu
204
+ ;;
205
+ *)
206
+ echo -e "${RED}无效选项。请重试。${NC}"
207
+ read -p "按回车键继续..."
208
+ installation_menu
209
+ ;;
210
+ esac
211
+ }
212
+
213
+ # Function for maintenance tasks
214
+ maintenance_menu() {
215
+ show_header
216
+ echo -e "${YELLOW}===== 维护菜单 =====${NC}"
217
+ echo -e "1) 查看服务状态"
218
+ echo -e "2) 刷新Cookie"
219
+ echo -e "3) 强制刷新Cookie"
220
+ echo -e "4) 管理邮箱"
221
+ echo -e "5) 管理无效Cookie"
222
+ echo -e "6) 查看日志"
223
+ echo -e "7) 重启服务"
224
+ echo -e "8) 停止服务"
225
+ echo -e "9) 更新项目代码"
226
+ echo -e "10) 备份项目数据"
227
+ echo -e "11) 持续刷新Cookie直到成功"
228
+ echo -e "12) 返回主菜单"
229
+ echo
230
+ echo -n "请输入选择 [1-12]: "
231
+ read -r choice
232
+
233
+ case $choice in
234
+ 1)
235
+ show_header
236
+ echo -e "${YELLOW}服务状态:${NC}"
237
+ if docker ps | grep -q cursor-to-openai; then
238
+ echo -e "${GREEN}Docker容器正在运行。${NC}"
239
+ docker ps | grep cursor-to-openai
240
+ else
241
+ echo -e "${RED}Docker容器未运行。${NC}"
242
+ fi
243
+
244
+ pids=$(pgrep -f "node.*start")
245
+ if [ -n "$pids" ]; then
246
+ echo -e "${GREEN}Node.js服务正在运行,PID: $pids${NC}"
247
+ else
248
+ echo -e "${RED}Node.js服务未运行。${NC}"
249
+ fi
250
+
251
+ read -p "按回车键继续..."
252
+ maintenance_menu
253
+ ;;
254
+ 2)
255
+ show_header
256
+ echo -e "${YELLOW}正在刷新Cookie...${NC}"
257
+ npm run refresh-cookies
258
+ read -p "按回车键继续..."
259
+ maintenance_menu
260
+ ;;
261
+ 3)
262
+ show_header
263
+ echo -e "${YELLOW}正在强制刷新Cookie...${NC}"
264
+ npm run refresh-cookies -- --force
265
+ read -p "按回车键继续..."
266
+ maintenance_menu
267
+ ;;
268
+ 4)
269
+ show_header
270
+ echo -e "${YELLOW}正在管理邮箱...${NC}"
271
+ npm run manage-emails
272
+ read -p "按回车键继续..."
273
+ maintenance_menu
274
+ ;;
275
+ 5)
276
+ show_header
277
+ echo -e "${YELLOW}正在管理无效Cookie...${NC}"
278
+ node manage-invalid-cookies.js
279
+ read -p "按回车键继续..."
280
+ maintenance_menu
281
+ ;;
282
+ 6)
283
+ show_header
284
+ echo -e "${YELLOW}正在查看日志...${NC}"
285
+ if docker ps | grep -q cursor-to-openai; then
286
+ docker compose logs -f
287
+ else
288
+ echo -e "${RED}Docker容器未运行。${NC}"
289
+ echo -e "${YELLOW}正在检查npm日志...${NC}"
290
+ # Try to find logs in npm-debug.log or similar
291
+ if [ -f npm-debug.log ]; then
292
+ cat npm-debug.log
293
+ else
294
+ echo -e "${RED}未找到日志文件。${NC}"
295
+ fi
296
+ fi
297
+ read -p "按回车键继续..."
298
+ maintenance_menu
299
+ ;;
300
+ 7)
301
+ show_header
302
+ echo -e "${YELLOW}正在重启服务...${NC}"
303
+ if docker ps | grep -q cursor-to-openai; then
304
+ docker compose restart
305
+ echo -e "${GREEN}Docker容器已重启。${NC}"
306
+ else
307
+ pids=$(pgrep -f "node.*start")
308
+ if [ -n "$pids" ]; then
309
+ kill $pids
310
+ sleep 2
311
+ npm start &
312
+ echo -e "${GREEN}Node.js服务已重启。${NC}"
313
+ else
314
+ echo -e "${RED}未检测到运行中的服务。${NC}"
315
+ echo -e "${YELLOW}是否要启动服务?(y/n): ${NC}"
316
+ read -r start_service
317
+ if [[ $start_service == "y" || $start_service == "Y" ]]; then
318
+ npm start &
319
+ echo -e "${GREEN}服务已启动。${NC}"
320
+ fi
321
+ fi
322
+ fi
323
+ read -p "按回车键继续..."
324
+ maintenance_menu
325
+ ;;
326
+ 8)
327
+ show_header
328
+ echo -e "${YELLOW}正在停止服务...${NC}"
329
+ if docker ps | grep -q cursor-to-openai; then
330
+ docker compose down
331
+ echo -e "${GREEN}Docker容器已停止。${NC}"
332
+ else
333
+ pids=$(pgrep -f "node.*start")
334
+ if [ -n "$pids" ]; then
335
+ kill $pids
336
+ echo -e "${GREEN}Node.js服务已停止。${NC}"
337
+ else
338
+ echo -e "${RED}未检测到运行中的服务。${NC}"
339
+ fi
340
+ fi
341
+ read -p "按回车键继续..."
342
+ maintenance_menu
343
+ ;;
344
+ 9)
345
+ show_header
346
+ echo -e "${YELLOW}正在更新项目代码...${NC}"
347
+
348
+ # 备份配置文件
349
+ backup_configs
350
+ backup_dir=$(ls -td backups/update_backup_* | head -1)
351
+
352
+ # 检查是否存在未提交的更改
353
+ if [ -n "$(git status --porcelain)" ]; then
354
+ echo -e "${YELLOW}检测到未提交的更改。更新前请处理这些更改。${NC}"
355
+ echo -e "1) 查看更改"
356
+ echo -e "2) 备份并放弃更改"
357
+ echo -e "3) 取消更新"
358
+ echo -n "请选择操作 [1-3]: "
359
+ read -r update_choice
360
+
361
+ case $update_choice in
362
+ 1)
363
+ git status
364
+ echo -e "${YELLOW}是否继续更新?(y/n): ${NC}"
365
+ read -r continue_update
366
+ if [[ $continue_update != "y" && $continue_update != "Y" ]]; then
367
+ echo -e "${YELLOW}更新已取消。${NC}"
368
+ read -p "按回车键继续..."
369
+ maintenance_menu
370
+ return
371
+ fi
372
+ ;;
373
+ 2)
374
+ echo -e "${YELLOW}备份更改...${NC}"
375
+ git diff > "$backup_dir/local_changes.patch"
376
+ git checkout -- .
377
+ echo -e "${GREEN}更改已备份到 $backup_dir/local_changes.patch${NC}"
378
+ ;;
379
+ 3)
380
+ echo -e "${YELLOW}更新已取消。${NC}"
381
+ read -p "按回车键继续..."
382
+ maintenance_menu
383
+ return
384
+ ;;
385
+ *)
386
+ echo -e "${RED}无效选项。更新已取消。${NC}"
387
+ read -p "按回车键继续..."
388
+ maintenance_menu
389
+ return
390
+ ;;
391
+ esac
392
+ fi
393
+
394
+ # 更新代码
395
+ git pull
396
+ update_status=$?
397
+
398
+ # 恢复配置文件
399
+ restore_configs "$backup_dir"
400
+
401
+ if [ $update_status -eq 0 ]; then
402
+ echo -e "${GREEN}项目代码更新成功!${NC}"
403
+ echo -e "${YELLOW}是否需要重新安装依赖?(y/n): ${NC}"
404
+ read -r reinstall
405
+ if [[ $reinstall == "y" || $reinstall == "Y" ]]; then
406
+ npm install
407
+ if [ $? -eq 0 ]; then
408
+ echo -e "${GREEN}依赖安装成功!${NC}"
409
+ else
410
+ echo -e "${RED}依赖安装失败!${NC}"
411
+ fi
412
+ fi
413
+ else
414
+ echo -e "${RED}项目代码更新失败!${NC}"
415
+ fi
416
+
417
+ read -p "按回车键继续..."
418
+ maintenance_menu
419
+ ;;
420
+ 10)
421
+ show_header
422
+ echo -e "${YELLOW}正在备份项目数据...${NC}"
423
+
424
+ # 创建备份目录
425
+ backup_dir="backups/backup_$(date +"%Y%m%d_%H%M%S")"
426
+ mkdir -p "$backup_dir"
427
+
428
+ # 备份关键文件
429
+ cp -r data "$backup_dir/" 2>/dev/null
430
+ if [ -f .env ]; then
431
+ cp .env "$backup_dir/"
432
+ fi
433
+
434
+ # 压缩备份
435
+ tar -czf "${backup_dir}.tar.gz" "$backup_dir"
436
+ rm -rf "$backup_dir"
437
+
438
+ echo -e "${GREEN}备份已创建: ${backup_dir}.tar.gz${NC}"
439
+ read -p "按回车键继续..."
440
+ maintenance_menu
441
+ ;;
442
+ 11)
443
+ show_header
444
+ echo -e "${YELLOW}持续刷新Cookie直到成功...${NC}"
445
+ read -p "请输入最大尝试时间(分钟, 默认60): " max_time
446
+ max_time=${max_time:-60}
447
+ max_seconds=$((max_time * 60))
448
+
449
+ echo -e "${YELLOW}将持续尝试刷新Cookie,最长 ${max_time} 分钟...${NC}"
450
+
451
+ start_time=$(date +%s)
452
+ success=false
453
+ attempt=0
454
+
455
+ while ! $success && [ $(($(date +%s) - start_time)) -lt $max_seconds ]; do
456
+ attempt=$((attempt + 1))
457
+ elapsed=$(($(date +%s) - start_time))
458
+ remaining=$((max_seconds - elapsed))
459
+ remaining_min=$((remaining / 60))
460
+ remaining_sec=$((remaining % 60))
461
+
462
+ echo -e "${YELLOW}尝试 #${attempt}...(剩余时间: ${remaining_min}分${remaining_sec}秒)${NC}"
463
+
464
+ # 运行刷新命令并检查输出
465
+ output=$(npm run refresh-cookies -- --force 2>&1)
466
+ echo "$output"
467
+
468
+ if echo "$output" | grep -q "成功添加新的Cookie" || echo "$output" | grep -q "Successfully added new cookies"; then
469
+ success=true
470
+ echo -e "${GREEN}成功添加新Cookie!${NC}"
471
+ else
472
+ wait_time=$((RANDOM % 61 + 30)) # 30-90秒随机间隔
473
+ echo -e "${YELLOW}等待 ${wait_time} 秒后重试...${NC}"
474
+ sleep $wait_time
475
+ fi
476
+ done
477
+
478
+ if $success; then
479
+ echo -e "${GREEN}成功刷新Cookie!${NC}"
480
+ else
481
+ echo -e "${RED}达到最大尝试时间,未能成功刷新Cookie。${NC}"
482
+ fi
483
+
484
+ read -p "按回车键继续..."
485
+ maintenance_menu
486
+ ;;
487
+ 12)
488
+ main_menu
489
+ ;;
490
+ *)
491
+ echo -e "${RED}无效选项。请重试。${NC}"
492
+ read -p "按回车键继续..."
493
+ maintenance_menu
494
+ ;;
495
+ esac
496
+ }
497
+
498
+ # Main menu function
499
+ main_menu() {
500
+ show_header
501
+ echo -e "${YELLOW}===== 主菜单 =====${NC}"
502
+ echo -e "1) 启动服务 (npm)"
503
+ echo -e "2) 安装配置"
504
+ echo -e "3) 系统维护"
505
+ echo -e "4) 退出"
506
+ echo
507
+ echo -n "请输入选择 [1-4]: "
508
+ read -r choice
509
+
510
+ case $choice in
511
+ 1)
512
+ show_header
513
+ echo -e "${YELLOW}正在使用npm启动服务...${NC}"
514
+ check_nodejs
515
+
516
+ # 检查Node.js服务是否已在运行
517
+ pids=$(pgrep -f "node.*start")
518
+ if [ -n "$pids" ]; then
519
+ echo -e "${YELLOW}服务已在运行,PID: $pids${NC}"
520
+ echo -e "${YELLOW}是否要重启服务?(y/n): ${NC}"
521
+ read -r restart
522
+ if [[ $restart == "y" || $restart == "Y" ]]; then
523
+ kill $pids
524
+ sleep 2
525
+ npm start &
526
+ echo -e "${GREEN}服务已重启${NC}"
527
+ fi
528
+ else
529
+ npm start &
530
+ echo -e "${GREEN}服务已启动${NC}"
531
+ fi
532
+
533
+ echo -e "${GREEN}访问管理界面: http://localhost:3010${NC}"
534
+ read -p "按回车键继续..."
535
+ main_menu
536
+ ;;
537
+ 2)
538
+ installation_menu
539
+ ;;
540
+ 3)
541
+ maintenance_menu
542
+ ;;
543
+ 4)
544
+ show_header
545
+ echo -e "${GREEN}感谢使用Cursor-To-OpenAI简易脚本!${NC}"
546
+ exit 0
547
+ ;;
548
+ *)
549
+ echo -e "${RED}无效选项。请重试。${NC}"
550
+ read -p "按回车键继续..."
551
+ main_menu
552
+ ;;
553
+ esac
554
+ }
555
+
556
+ # Start the script
557
+ main_menu
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
+ ]
docker-compose.yaml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ app:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ image: cursor-to-openai
9
+ volumes:
10
+ - ./data:/app/data
11
+ ports:
12
+ - "3010:3010"
13
+ env_file:
14
+ - .env
manage-emails.js ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const dotenv = require('dotenv');
7
+
8
+ // 创建交互式命令行界面
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout
12
+ });
13
+
14
+ // 加载环境变量
15
+ const ENV_FILE_PATH = path.join(process.cwd(), '.env');
16
+ let envContent = '';
17
+ let emailConfigs = [];
18
+
19
+ // 应用密码说明
20
+ function printAppPasswordInstructions() {
21
+ console.log('\n===== 如何创建谷歌应用密码 =====');
22
+ console.log('1. 访问 https://myaccount.google.com/security');
23
+ console.log('2. 在"登录Google"部分,点击"两步验证"');
24
+ console.log(' (如果未启用两步验证,需要先启用)');
25
+ console.log('3. 在页面底部找到"应用密码",点击进入');
26
+ console.log('4. 在"选择应用"下拉菜单中选择"其他(自定义名称)"');
27
+ console.log('5. 输入一个名称,例如"Cursor注册"');
28
+ console.log('6. 点击"生成"');
29
+ console.log('7. 复制生成的16位应用密码(格式如:xxxx xxxx xxxx xxxx)');
30
+ console.log('注意: 应用密码只会显示一次,请务必保存好\n');
31
+ }
32
+
33
+ // 加载当前环境变量和邮箱配置
34
+ function loadEnvironment() {
35
+ try {
36
+ if (!fs.existsSync(ENV_FILE_PATH)) {
37
+ console.error('❌ .env文件不存在,请先运行setup.js进行初始化配置');
38
+ process.exit(1);
39
+ }
40
+
41
+ // 读取原始.env文件内容
42
+ envContent = fs.readFileSync(ENV_FILE_PATH, 'utf8');
43
+
44
+ // 解析环境变量
45
+ dotenv.config();
46
+
47
+ // 尝试解析当前的邮箱配置
48
+ try {
49
+ const configStr = process.env.REGISTER_EMAIL_CONFIGS;
50
+ if (configStr) {
51
+ emailConfigs = JSON.parse(configStr);
52
+ if (!Array.isArray(emailConfigs)) {
53
+ emailConfigs = [];
54
+ }
55
+ }
56
+ } catch (parseErr) {
57
+ console.warn('⚠️ 解析当前邮箱配置出错,将使用空配置');
58
+ emailConfigs = [];
59
+ }
60
+
61
+ return true;
62
+ } catch (error) {
63
+ console.error(`❌ 加载环境变量失败: ${error.message}`);
64
+ return false;
65
+ }
66
+ }
67
+
68
+ // 保存更新后的邮箱配置到.env文件
69
+ function saveEmailConfigs() {
70
+ try {
71
+ // 将邮箱配置格式化为JSON字符串
72
+ const configStr = JSON.stringify(emailConfigs);
73
+
74
+ // 替换.env文件中的配置
75
+ let newEnvContent = '';
76
+
77
+ if (envContent.includes('REGISTER_EMAIL_CONFIGS=')) {
78
+ // 使用正则表达式替换REGISTER_EMAIL_CONFIGS行
79
+ newEnvContent = envContent.replace(
80
+ /REGISTER_EMAIL_CONFIGS=.*/,
81
+ `REGISTER_EMAIL_CONFIGS=${configStr}`
82
+ );
83
+ } else {
84
+ // 如果不存在该配置行,添加到文件末尾
85
+ newEnvContent = `${envContent}\nREGISTER_EMAIL_CONFIGS=${configStr}`;
86
+ }
87
+
88
+ // 同时确保USE_CONFIG_FILE设置为false
89
+ if (newEnvContent.includes('REGISTER_USE_CONFIG_FILE=')) {
90
+ newEnvContent = newEnvContent.replace(
91
+ /REGISTER_USE_CONFIG_FILE=.*/,
92
+ 'REGISTER_USE_CONFIG_FILE=false'
93
+ );
94
+ } else {
95
+ newEnvContent = `${newEnvContent}\nREGISTER_USE_CONFIG_FILE=false`;
96
+ }
97
+
98
+ // 确保EMAIL_SERVER设置为IMAP
99
+ if (newEnvContent.includes('REGISTER_EMAIL_SERVER=')) {
100
+ newEnvContent = newEnvContent.replace(
101
+ /REGISTER_EMAIL_SERVER=.*/,
102
+ 'REGISTER_EMAIL_SERVER=IMAP'
103
+ );
104
+ } else {
105
+ newEnvContent = `${newEnvContent}\nREGISTER_EMAIL_SERVER=IMAP`;
106
+ }
107
+
108
+ // 写入更新后的内容
109
+ fs.writeFileSync(ENV_FILE_PATH, newEnvContent, 'utf8');
110
+
111
+ console.log('✅ 邮箱配置已成功保存到.env文件');
112
+ return true;
113
+ } catch (error) {
114
+ console.error(`❌ 保存邮箱配置失败: ${error.message}`);
115
+ return false;
116
+ }
117
+ }
118
+
119
+ // 显示所有已配置的邮箱
120
+ function displayEmails() {
121
+ console.log('\n===== 当前已配置的邮箱 =====');
122
+
123
+ if (emailConfigs.length === 0) {
124
+ console.log('暂无已配置的邮箱');
125
+ return;
126
+ }
127
+
128
+ emailConfigs.forEach((config, index) => {
129
+ console.log(`[${index + 1}] ${config.email}`);
130
+ console.log(` IMAP服务器: ${config.imap_server}`);
131
+ console.log(` IMAP端口: ${config.imap_port}`);
132
+ console.log(` 用户名: ${config.username}`);
133
+ console.log(` 应用密码: ${config.password}`);
134
+ console.log('');
135
+ });
136
+ }
137
+
138
+ // 添加新邮箱
139
+ function addEmail() {
140
+ console.log('\n===== 添加新邮箱 =====');
141
+ printAppPasswordInstructions();
142
+
143
+ rl.question('请输入Gmail地址: ', (email) => {
144
+ rl.question('请输入Gmail的应用密码 (不是邮箱密码): ', (password) => {
145
+ // 创建新配置
146
+ const newConfig = {
147
+ email: email,
148
+ imap_server: 'imap.gmail.com',
149
+ imap_port: 993,
150
+ username: email,
151
+ password: password
152
+ };
153
+
154
+ // 添加到配置列表
155
+ emailConfigs.push(newConfig);
156
+
157
+ console.log(`\n✅ 已添加邮箱: ${email}`);
158
+
159
+ // 保存到.env文件
160
+ if (saveEmailConfigs()) {
161
+ showMainMenu();
162
+ }
163
+ });
164
+ });
165
+ }
166
+
167
+ // 修改邮箱
168
+ function modifyEmail() {
169
+ if (emailConfigs.length === 0) {
170
+ console.log('\n❌ 当前没有可修改的邮箱。请先添加邮箱。');
171
+ showMainMenu();
172
+ return;
173
+ }
174
+
175
+ console.log('\n===== 修改邮箱 =====');
176
+ displayEmails();
177
+
178
+ rl.question('请输入要修改的邮箱序号 (1-' + emailConfigs.length + '): ', (indexStr) => {
179
+ const index = parseInt(indexStr) - 1;
180
+
181
+ if (isNaN(index) || index < 0 || index >= emailConfigs.length) {
182
+ console.log('\n❌ 无效的序号。请重新选择。');
183
+ modifyEmail();
184
+ return;
185
+ }
186
+
187
+ const currentConfig = emailConfigs[index];
188
+
189
+ console.log(`\n正在修改邮箱: ${currentConfig.email}`);
190
+
191
+ rl.question(`新的Gmail地址 (当前: ${currentConfig.email},直接回车保持不变): `, (email) => {
192
+ const newEmail = email.trim() === '' ? currentConfig.email : email;
193
+
194
+ rl.question('新的应用密码 (直接回车保持不变): ', (password) => {
195
+ const newPassword = password.trim() === '' ? currentConfig.password : password;
196
+
197
+ // 更新配置
198
+ emailConfigs[index] = {
199
+ email: newEmail,
200
+ imap_server: 'imap.gmail.com',
201
+ imap_port: 993,
202
+ username: newEmail,
203
+ password: newPassword
204
+ };
205
+
206
+ console.log(`\n✅ 已修改邮箱配置: ${newEmail}`);
207
+
208
+ // 保存到.env文件
209
+ if (saveEmailConfigs()) {
210
+ showMainMenu();
211
+ }
212
+ });
213
+ });
214
+ });
215
+ }
216
+
217
+ // 删除邮箱
218
+ function deleteEmail() {
219
+ if (emailConfigs.length === 0) {
220
+ console.log('\n❌ 当前没有可删除的邮箱。');
221
+ showMainMenu();
222
+ return;
223
+ }
224
+
225
+ console.log('\n===== 删除邮箱 =====');
226
+ displayEmails();
227
+
228
+ rl.question('请输入要删除的邮箱序号 (1-' + emailConfigs.length + '): ', (indexStr) => {
229
+ const index = parseInt(indexStr) - 1;
230
+
231
+ if (isNaN(index) || index < 0 || index >= emailConfigs.length) {
232
+ console.log('\n❌ 无效的序号。请重新选择。');
233
+ deleteEmail();
234
+ return;
235
+ }
236
+
237
+ const emailToDelete = emailConfigs[index].email;
238
+
239
+ rl.question(`确认删除邮箱 "${emailToDelete}"? (y/n): `, (answer) => {
240
+ if (answer.toLowerCase() === 'y') {
241
+ // 删除邮箱
242
+ emailConfigs.splice(index, 1);
243
+
244
+ console.log(`\n✅ 已删除邮箱: ${emailToDelete}`);
245
+
246
+ // 保存到.env文件
247
+ if (saveEmailConfigs()) {
248
+ showMainMenu();
249
+ }
250
+ } else {
251
+ console.log('\n操作已取消');
252
+ showMainMenu();
253
+ }
254
+ });
255
+ });
256
+ }
257
+
258
+ // 显示主菜单
259
+ function showMainMenu() {
260
+ console.log('\n===== 邮箱配置管理 =====');
261
+ console.log('1. 查看所有邮箱');
262
+ console.log('2. 添加新邮箱');
263
+ console.log('3. 修改邮箱');
264
+ console.log('4. 删除邮箱');
265
+ console.log('0. 退出');
266
+
267
+ rl.question('请选择操作 (0-4): ', (choice) => {
268
+ switch (choice) {
269
+ case '1':
270
+ displayEmails();
271
+ showMainMenu();
272
+ break;
273
+ case '2':
274
+ addEmail();
275
+ break;
276
+ case '3':
277
+ modifyEmail();
278
+ break;
279
+ case '4':
280
+ deleteEmail();
281
+ break;
282
+ case '0':
283
+ console.log('\n✅ 配置完成,退出程序');
284
+ rl.close();
285
+ break;
286
+ default:
287
+ console.log('\n❌ 无效的选择,请重新输入');
288
+ showMainMenu();
289
+ break;
290
+ }
291
+ });
292
+ }
293
+
294
+ // 主函数
295
+ async function main() {
296
+ console.log('===== Cursor-To-OpenAI 邮箱配置管理 =====');
297
+
298
+ // 加载当前配置
299
+ if (loadEnvironment()) {
300
+ // 显示主菜单
301
+ showMainMenu();
302
+ } else {
303
+ console.error('程序退出');
304
+ rl.close();
305
+ }
306
+ }
307
+
308
+ // 运行主函数
309
+ main();
manage-invalid-cookies.js ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // 加载环境变量
2
+ require('dotenv').config();
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const readline = require('readline');
7
+ const keyManager = require('./src/utils/keyManager');
8
+
9
+ // 创建命令行交互界面
10
+ const rl = readline.createInterface({
11
+ input: process.stdin,
12
+ output: process.stdout
13
+ });
14
+
15
+ // 显示菜单
16
+ function showMenu() {
17
+ console.log('\n===== 无效Cookie管理工具 =====');
18
+ console.log('1. 查看所有无效Cookie');
19
+ console.log('2. 添加无效Cookie');
20
+ console.log('3. 删除特定无效Cookie');
21
+ console.log('4. 清空所有无效Cookie');
22
+ console.log('5. 从API Keys中移除所有无效Cookie');
23
+ console.log('6. 退出');
24
+ console.log('============================');
25
+
26
+ rl.question('请选择操作 (1-6): ', (answer) => {
27
+ switch(answer) {
28
+ case '1':
29
+ listInvalidCookies();
30
+ break;
31
+ case '2':
32
+ addInvalidCookie();
33
+ break;
34
+ case '3':
35
+ removeInvalidCookie();
36
+ break;
37
+ case '4':
38
+ clearAllInvalidCookies();
39
+ break;
40
+ case '5':
41
+ removeInvalidCookiesFromApiKeys();
42
+ break;
43
+ case '6':
44
+ console.log('退出程序');
45
+ rl.close();
46
+ break;
47
+ default:
48
+ console.log('无效的选择,请重新输入');
49
+ showMenu();
50
+ break;
51
+ }
52
+ });
53
+ }
54
+
55
+ // 查看所有无效Cookie
56
+ function listInvalidCookies() {
57
+ const invalidCookies = Array.from(keyManager.getInvalidCookies());
58
+
59
+ console.log('\n===== 所有无效Cookie =====');
60
+ if (invalidCookies.length === 0) {
61
+ console.log('没有无效Cookie');
62
+ } else {
63
+ invalidCookies.forEach((cookie, index) => {
64
+ console.log(`${index + 1}. ${cookie}`);
65
+ });
66
+ }
67
+
68
+ showMenu();
69
+ }
70
+
71
+ // 添加无效Cookie
72
+ function addInvalidCookie() {
73
+ rl.question('\n请输入要添加的无效Cookie: ', (cookie) => {
74
+ if (!cookie.trim()) {
75
+ console.log('Cookie不能为空');
76
+ showMenu();
77
+ return;
78
+ }
79
+
80
+ // 将cookie添加到无效集合
81
+ const invalidCookies = new Set(keyManager.getInvalidCookies());
82
+ invalidCookies.add(cookie.trim());
83
+
84
+ // 保存到文件
85
+ const INVALID_COOKIES_FILE = path.join(__dirname, 'data/invalid_cookies.json');
86
+ try {
87
+ // 确保目录存在
88
+ const dataDir = path.join(__dirname, 'data');
89
+ if (!fs.existsSync(dataDir)) {
90
+ fs.mkdirSync(dataDir, { recursive: true });
91
+ }
92
+
93
+ fs.writeFileSync(INVALID_COOKIES_FILE, JSON.stringify(Array.from(invalidCookies), null, 2), 'utf8');
94
+ console.log('无效Cookie添加成功');
95
+
96
+ // 重新加载无效cookie
97
+ keyManager.loadInvalidCookiesFromFile();
98
+ } catch (err) {
99
+ console.error('保存无效Cookie失败:', err);
100
+ }
101
+
102
+ showMenu();
103
+ });
104
+ }
105
+
106
+ // 删除特定无效Cookie
107
+ function removeInvalidCookie() {
108
+ const invalidCookies = Array.from(keyManager.getInvalidCookies());
109
+
110
+ if (invalidCookies.length === 0) {
111
+ console.log('\n没有无效Cookie可删除');
112
+ showMenu();
113
+ return;
114
+ }
115
+
116
+ console.log('\n===== 所有无效Cookie =====');
117
+ invalidCookies.forEach((cookie, index) => {
118
+ console.log(`${index + 1}. ${cookie}`);
119
+ });
120
+
121
+ rl.question('\n请输入要删除的Cookie编号 (1-' + invalidCookies.length + '): ', (answer) => {
122
+ const index = parseInt(answer) - 1;
123
+
124
+ if (isNaN(index) || index < 0 || index >= invalidCookies.length) {
125
+ console.log('无效的编号');
126
+ showMenu();
127
+ return;
128
+ }
129
+
130
+ const cookieToRemove = invalidCookies[index];
131
+ const result = keyManager.clearInvalidCookie(cookieToRemove);
132
+
133
+ if (result) {
134
+ console.log(`成功删除无效Cookie: ${cookieToRemove}`);
135
+ } else {
136
+ console.log('删除失败');
137
+ }
138
+
139
+ showMenu();
140
+ });
141
+ }
142
+
143
+ // 清空所有无效Cookie
144
+ function clearAllInvalidCookies() {
145
+ rl.question('\n确定要清空所有无效Cookie吗? (y/n): ', (answer) => {
146
+ if (answer.toLowerCase() === 'y') {
147
+ keyManager.clearAllInvalidCookies();
148
+ console.log('所有无效Cookie已清空');
149
+ } else {
150
+ console.log('操作已取消');
151
+ }
152
+
153
+ showMenu();
154
+ });
155
+ }
156
+
157
+ // 从API Keys中移除所有无效Cookie
158
+ function removeInvalidCookiesFromApiKeys() {
159
+ // 重新初始化API Keys,这会自动移除无效cookie
160
+ keyManager.initializeApiKeys();
161
+ console.log('已从API Keys中移除所有无效Cookie');
162
+
163
+ showMenu();
164
+ }
165
+
166
+ // 启动程序
167
+ console.log('正在加载无效Cookie...');
168
+ keyManager.loadInvalidCookiesFromFile();
169
+ showMenu();
package-lock.json ADDED
@@ -0,0 +1,2291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cursor-to-openai",
3
+ "version": "1.3.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "cursor-to-openai",
9
+ "version": "1.3.0",
10
+ "license": "MIT",
11
+ "dependencies": {
12
+ "@octokit/rest": "^20.0.2",
13
+ "adm-zip": "^0.5.16",
14
+ "axios": "^1.6.7",
15
+ "csv-parser": "^3.0.0",
16
+ "dotenv": "^16.4.7",
17
+ "express": "4.21.2",
18
+ "jsonwebtoken": "^9.0.2",
19
+ "morgan": "^1.10.0",
20
+ "node-cron": "^3.0.3",
21
+ "node-fetch": "^2.7.0",
22
+ "protobufjs": "^7.4.0",
23
+ "undici": "^6.21.2",
24
+ "uuid": "11.0.5"
25
+ },
26
+ "devDependencies": {
27
+ "protobufjs-cli": "^1.1.3"
28
+ }
29
+ },
30
+ "node_modules/@babel/helper-string-parser": {
31
+ "version": "7.25.9",
32
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
33
+ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
34
+ "dev": true,
35
+ "license": "MIT",
36
+ "engines": {
37
+ "node": ">=6.9.0"
38
+ }
39
+ },
40
+ "node_modules/@babel/helper-validator-identifier": {
41
+ "version": "7.25.9",
42
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
43
+ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
44
+ "dev": true,
45
+ "license": "MIT",
46
+ "engines": {
47
+ "node": ">=6.9.0"
48
+ }
49
+ },
50
+ "node_modules/@babel/parser": {
51
+ "version": "7.26.7",
52
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz",
53
+ "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==",
54
+ "dev": true,
55
+ "license": "MIT",
56
+ "dependencies": {
57
+ "@babel/types": "^7.26.7"
58
+ },
59
+ "bin": {
60
+ "parser": "bin/babel-parser.js"
61
+ },
62
+ "engines": {
63
+ "node": ">=6.0.0"
64
+ }
65
+ },
66
+ "node_modules/@babel/types": {
67
+ "version": "7.26.7",
68
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz",
69
+ "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==",
70
+ "dev": true,
71
+ "license": "MIT",
72
+ "dependencies": {
73
+ "@babel/helper-string-parser": "^7.25.9",
74
+ "@babel/helper-validator-identifier": "^7.25.9"
75
+ },
76
+ "engines": {
77
+ "node": ">=6.9.0"
78
+ }
79
+ },
80
+ "node_modules/@jsdoc/salty": {
81
+ "version": "0.2.9",
82
+ "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz",
83
+ "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==",
84
+ "dev": true,
85
+ "license": "Apache-2.0",
86
+ "dependencies": {
87
+ "lodash": "^4.17.21"
88
+ },
89
+ "engines": {
90
+ "node": ">=v12.0.0"
91
+ }
92
+ },
93
+ "node_modules/@octokit/auth-token": {
94
+ "version": "4.0.0",
95
+ "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
96
+ "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
97
+ "license": "MIT",
98
+ "engines": {
99
+ "node": ">= 18"
100
+ }
101
+ },
102
+ "node_modules/@octokit/core": {
103
+ "version": "5.2.0",
104
+ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz",
105
+ "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==",
106
+ "license": "MIT",
107
+ "dependencies": {
108
+ "@octokit/auth-token": "^4.0.0",
109
+ "@octokit/graphql": "^7.1.0",
110
+ "@octokit/request": "^8.3.1",
111
+ "@octokit/request-error": "^5.1.0",
112
+ "@octokit/types": "^13.0.0",
113
+ "before-after-hook": "^2.2.0",
114
+ "universal-user-agent": "^6.0.0"
115
+ },
116
+ "engines": {
117
+ "node": ">= 18"
118
+ }
119
+ },
120
+ "node_modules/@octokit/endpoint": {
121
+ "version": "9.0.6",
122
+ "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz",
123
+ "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==",
124
+ "license": "MIT",
125
+ "dependencies": {
126
+ "@octokit/types": "^13.1.0",
127
+ "universal-user-agent": "^6.0.0"
128
+ },
129
+ "engines": {
130
+ "node": ">= 18"
131
+ }
132
+ },
133
+ "node_modules/@octokit/graphql": {
134
+ "version": "7.1.1",
135
+ "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz",
136
+ "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==",
137
+ "license": "MIT",
138
+ "dependencies": {
139
+ "@octokit/request": "^8.4.1",
140
+ "@octokit/types": "^13.0.0",
141
+ "universal-user-agent": "^6.0.0"
142
+ },
143
+ "engines": {
144
+ "node": ">= 18"
145
+ }
146
+ },
147
+ "node_modules/@octokit/openapi-types": {
148
+ "version": "23.0.1",
149
+ "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz",
150
+ "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==",
151
+ "license": "MIT"
152
+ },
153
+ "node_modules/@octokit/plugin-paginate-rest": {
154
+ "version": "11.4.4-cjs.2",
155
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz",
156
+ "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==",
157
+ "license": "MIT",
158
+ "dependencies": {
159
+ "@octokit/types": "^13.7.0"
160
+ },
161
+ "engines": {
162
+ "node": ">= 18"
163
+ },
164
+ "peerDependencies": {
165
+ "@octokit/core": "5"
166
+ }
167
+ },
168
+ "node_modules/@octokit/plugin-request-log": {
169
+ "version": "4.0.1",
170
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz",
171
+ "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==",
172
+ "license": "MIT",
173
+ "engines": {
174
+ "node": ">= 18"
175
+ },
176
+ "peerDependencies": {
177
+ "@octokit/core": "5"
178
+ }
179
+ },
180
+ "node_modules/@octokit/plugin-rest-endpoint-methods": {
181
+ "version": "13.3.2-cjs.1",
182
+ "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz",
183
+ "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==",
184
+ "license": "MIT",
185
+ "dependencies": {
186
+ "@octokit/types": "^13.8.0"
187
+ },
188
+ "engines": {
189
+ "node": ">= 18"
190
+ },
191
+ "peerDependencies": {
192
+ "@octokit/core": "^5"
193
+ }
194
+ },
195
+ "node_modules/@octokit/request": {
196
+ "version": "8.4.1",
197
+ "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz",
198
+ "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==",
199
+ "license": "MIT",
200
+ "dependencies": {
201
+ "@octokit/endpoint": "^9.0.6",
202
+ "@octokit/request-error": "^5.1.1",
203
+ "@octokit/types": "^13.1.0",
204
+ "universal-user-agent": "^6.0.0"
205
+ },
206
+ "engines": {
207
+ "node": ">= 18"
208
+ }
209
+ },
210
+ "node_modules/@octokit/request-error": {
211
+ "version": "5.1.1",
212
+ "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz",
213
+ "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==",
214
+ "license": "MIT",
215
+ "dependencies": {
216
+ "@octokit/types": "^13.1.0",
217
+ "deprecation": "^2.0.0",
218
+ "once": "^1.4.0"
219
+ },
220
+ "engines": {
221
+ "node": ">= 18"
222
+ }
223
+ },
224
+ "node_modules/@octokit/rest": {
225
+ "version": "20.1.2",
226
+ "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz",
227
+ "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==",
228
+ "license": "MIT",
229
+ "dependencies": {
230
+ "@octokit/core": "^5.0.2",
231
+ "@octokit/plugin-paginate-rest": "11.4.4-cjs.2",
232
+ "@octokit/plugin-request-log": "^4.0.0",
233
+ "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1"
234
+ },
235
+ "engines": {
236
+ "node": ">= 18"
237
+ }
238
+ },
239
+ "node_modules/@octokit/types": {
240
+ "version": "13.8.0",
241
+ "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz",
242
+ "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==",
243
+ "license": "MIT",
244
+ "dependencies": {
245
+ "@octokit/openapi-types": "^23.0.1"
246
+ }
247
+ },
248
+ "node_modules/@protobufjs/aspromise": {
249
+ "version": "1.1.2",
250
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
251
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
252
+ "license": "BSD-3-Clause"
253
+ },
254
+ "node_modules/@protobufjs/base64": {
255
+ "version": "1.1.2",
256
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
257
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
258
+ "license": "BSD-3-Clause"
259
+ },
260
+ "node_modules/@protobufjs/codegen": {
261
+ "version": "2.0.4",
262
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
263
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
264
+ "license": "BSD-3-Clause"
265
+ },
266
+ "node_modules/@protobufjs/eventemitter": {
267
+ "version": "1.1.0",
268
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
269
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
270
+ "license": "BSD-3-Clause"
271
+ },
272
+ "node_modules/@protobufjs/fetch": {
273
+ "version": "1.1.0",
274
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
275
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
276
+ "license": "BSD-3-Clause",
277
+ "dependencies": {
278
+ "@protobufjs/aspromise": "^1.1.1",
279
+ "@protobufjs/inquire": "^1.1.0"
280
+ }
281
+ },
282
+ "node_modules/@protobufjs/float": {
283
+ "version": "1.0.2",
284
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
285
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
286
+ "license": "BSD-3-Clause"
287
+ },
288
+ "node_modules/@protobufjs/inquire": {
289
+ "version": "1.1.0",
290
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
291
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
292
+ "license": "BSD-3-Clause"
293
+ },
294
+ "node_modules/@protobufjs/path": {
295
+ "version": "1.1.2",
296
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
297
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
298
+ "license": "BSD-3-Clause"
299
+ },
300
+ "node_modules/@protobufjs/pool": {
301
+ "version": "1.1.0",
302
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
303
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
304
+ "license": "BSD-3-Clause"
305
+ },
306
+ "node_modules/@protobufjs/utf8": {
307
+ "version": "1.1.0",
308
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
309
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
310
+ "license": "BSD-3-Clause"
311
+ },
312
+ "node_modules/@types/linkify-it": {
313
+ "version": "5.0.0",
314
+ "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
315
+ "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
316
+ "dev": true,
317
+ "license": "MIT"
318
+ },
319
+ "node_modules/@types/markdown-it": {
320
+ "version": "14.1.2",
321
+ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
322
+ "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
323
+ "dev": true,
324
+ "license": "MIT",
325
+ "dependencies": {
326
+ "@types/linkify-it": "^5",
327
+ "@types/mdurl": "^2"
328
+ }
329
+ },
330
+ "node_modules/@types/mdurl": {
331
+ "version": "2.0.0",
332
+ "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
333
+ "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
334
+ "dev": true,
335
+ "license": "MIT"
336
+ },
337
+ "node_modules/@types/node": {
338
+ "version": "22.10.5",
339
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
340
+ "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
341
+ "license": "MIT",
342
+ "dependencies": {
343
+ "undici-types": "~6.20.0"
344
+ }
345
+ },
346
+ "node_modules/accepts": {
347
+ "version": "1.3.8",
348
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
349
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
350
+ "license": "MIT",
351
+ "dependencies": {
352
+ "mime-types": "~2.1.34",
353
+ "negotiator": "0.6.3"
354
+ },
355
+ "engines": {
356
+ "node": ">= 0.6"
357
+ }
358
+ },
359
+ "node_modules/acorn": {
360
+ "version": "8.14.0",
361
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
362
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
363
+ "dev": true,
364
+ "license": "MIT",
365
+ "bin": {
366
+ "acorn": "bin/acorn"
367
+ },
368
+ "engines": {
369
+ "node": ">=0.4.0"
370
+ }
371
+ },
372
+ "node_modules/acorn-jsx": {
373
+ "version": "5.3.2",
374
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
375
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
376
+ "dev": true,
377
+ "license": "MIT",
378
+ "peerDependencies": {
379
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
380
+ }
381
+ },
382
+ "node_modules/adm-zip": {
383
+ "version": "0.5.16",
384
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz",
385
+ "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==",
386
+ "license": "MIT",
387
+ "engines": {
388
+ "node": ">=12.0"
389
+ }
390
+ },
391
+ "node_modules/ansi-styles": {
392
+ "version": "4.3.0",
393
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
394
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
395
+ "dev": true,
396
+ "license": "MIT",
397
+ "dependencies": {
398
+ "color-convert": "^2.0.1"
399
+ },
400
+ "engines": {
401
+ "node": ">=8"
402
+ },
403
+ "funding": {
404
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
405
+ }
406
+ },
407
+ "node_modules/argparse": {
408
+ "version": "2.0.1",
409
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
410
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
411
+ "dev": true,
412
+ "license": "Python-2.0"
413
+ },
414
+ "node_modules/array-flatten": {
415
+ "version": "1.1.1",
416
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
417
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
418
+ "license": "MIT"
419
+ },
420
+ "node_modules/asynckit": {
421
+ "version": "0.4.0",
422
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
423
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
424
+ "license": "MIT"
425
+ },
426
+ "node_modules/axios": {
427
+ "version": "1.8.3",
428
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
429
+ "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
430
+ "license": "MIT",
431
+ "dependencies": {
432
+ "follow-redirects": "^1.15.6",
433
+ "form-data": "^4.0.0",
434
+ "proxy-from-env": "^1.1.0"
435
+ }
436
+ },
437
+ "node_modules/balanced-match": {
438
+ "version": "1.0.2",
439
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
440
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
441
+ "dev": true,
442
+ "license": "MIT"
443
+ },
444
+ "node_modules/basic-auth": {
445
+ "version": "2.0.1",
446
+ "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
447
+ "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
448
+ "license": "MIT",
449
+ "dependencies": {
450
+ "safe-buffer": "5.1.2"
451
+ },
452
+ "engines": {
453
+ "node": ">= 0.8"
454
+ }
455
+ },
456
+ "node_modules/basic-auth/node_modules/safe-buffer": {
457
+ "version": "5.1.2",
458
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
459
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
460
+ "license": "MIT"
461
+ },
462
+ "node_modules/before-after-hook": {
463
+ "version": "2.2.3",
464
+ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
465
+ "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
466
+ "license": "Apache-2.0"
467
+ },
468
+ "node_modules/bluebird": {
469
+ "version": "3.7.2",
470
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
471
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==",
472
+ "dev": true,
473
+ "license": "MIT"
474
+ },
475
+ "node_modules/body-parser": {
476
+ "version": "1.20.3",
477
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
478
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
479
+ "license": "MIT",
480
+ "dependencies": {
481
+ "bytes": "3.1.2",
482
+ "content-type": "~1.0.5",
483
+ "debug": "2.6.9",
484
+ "depd": "2.0.0",
485
+ "destroy": "1.2.0",
486
+ "http-errors": "2.0.0",
487
+ "iconv-lite": "0.4.24",
488
+ "on-finished": "2.4.1",
489
+ "qs": "6.13.0",
490
+ "raw-body": "2.5.2",
491
+ "type-is": "~1.6.18",
492
+ "unpipe": "1.0.0"
493
+ },
494
+ "engines": {
495
+ "node": ">= 0.8",
496
+ "npm": "1.2.8000 || >= 1.4.16"
497
+ }
498
+ },
499
+ "node_modules/brace-expansion": {
500
+ "version": "2.0.1",
501
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
502
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
503
+ "dev": true,
504
+ "license": "MIT",
505
+ "dependencies": {
506
+ "balanced-match": "^1.0.0"
507
+ }
508
+ },
509
+ "node_modules/buffer-equal-constant-time": {
510
+ "version": "1.0.1",
511
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
512
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
513
+ "license": "BSD-3-Clause"
514
+ },
515
+ "node_modules/bytes": {
516
+ "version": "3.1.2",
517
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
518
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
519
+ "license": "MIT",
520
+ "engines": {
521
+ "node": ">= 0.8"
522
+ }
523
+ },
524
+ "node_modules/call-bind-apply-helpers": {
525
+ "version": "1.0.1",
526
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz",
527
+ "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==",
528
+ "license": "MIT",
529
+ "dependencies": {
530
+ "es-errors": "^1.3.0",
531
+ "function-bind": "^1.1.2"
532
+ },
533
+ "engines": {
534
+ "node": ">= 0.4"
535
+ }
536
+ },
537
+ "node_modules/call-bound": {
538
+ "version": "1.0.3",
539
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz",
540
+ "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==",
541
+ "license": "MIT",
542
+ "dependencies": {
543
+ "call-bind-apply-helpers": "^1.0.1",
544
+ "get-intrinsic": "^1.2.6"
545
+ },
546
+ "engines": {
547
+ "node": ">= 0.4"
548
+ },
549
+ "funding": {
550
+ "url": "https://github.com/sponsors/ljharb"
551
+ }
552
+ },
553
+ "node_modules/catharsis": {
554
+ "version": "0.9.0",
555
+ "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz",
556
+ "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==",
557
+ "dev": true,
558
+ "license": "MIT",
559
+ "dependencies": {
560
+ "lodash": "^4.17.15"
561
+ },
562
+ "engines": {
563
+ "node": ">= 10"
564
+ }
565
+ },
566
+ "node_modules/chalk": {
567
+ "version": "4.1.2",
568
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
569
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
570
+ "dev": true,
571
+ "license": "MIT",
572
+ "dependencies": {
573
+ "ansi-styles": "^4.1.0",
574
+ "supports-color": "^7.1.0"
575
+ },
576
+ "engines": {
577
+ "node": ">=10"
578
+ },
579
+ "funding": {
580
+ "url": "https://github.com/chalk/chalk?sponsor=1"
581
+ }
582
+ },
583
+ "node_modules/color-convert": {
584
+ "version": "2.0.1",
585
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
586
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
587
+ "dev": true,
588
+ "license": "MIT",
589
+ "dependencies": {
590
+ "color-name": "~1.1.4"
591
+ },
592
+ "engines": {
593
+ "node": ">=7.0.0"
594
+ }
595
+ },
596
+ "node_modules/color-name": {
597
+ "version": "1.1.4",
598
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
599
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
600
+ "dev": true,
601
+ "license": "MIT"
602
+ },
603
+ "node_modules/combined-stream": {
604
+ "version": "1.0.8",
605
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
606
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
607
+ "license": "MIT",
608
+ "dependencies": {
609
+ "delayed-stream": "~1.0.0"
610
+ },
611
+ "engines": {
612
+ "node": ">= 0.8"
613
+ }
614
+ },
615
+ "node_modules/content-disposition": {
616
+ "version": "0.5.4",
617
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
618
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
619
+ "license": "MIT",
620
+ "dependencies": {
621
+ "safe-buffer": "5.2.1"
622
+ },
623
+ "engines": {
624
+ "node": ">= 0.6"
625
+ }
626
+ },
627
+ "node_modules/content-type": {
628
+ "version": "1.0.5",
629
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
630
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
631
+ "license": "MIT",
632
+ "engines": {
633
+ "node": ">= 0.6"
634
+ }
635
+ },
636
+ "node_modules/cookie": {
637
+ "version": "0.7.1",
638
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
639
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
640
+ "license": "MIT",
641
+ "engines": {
642
+ "node": ">= 0.6"
643
+ }
644
+ },
645
+ "node_modules/cookie-signature": {
646
+ "version": "1.0.6",
647
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
648
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
649
+ "license": "MIT"
650
+ },
651
+ "node_modules/csv-parser": {
652
+ "version": "3.2.0",
653
+ "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz",
654
+ "integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==",
655
+ "license": "MIT",
656
+ "bin": {
657
+ "csv-parser": "bin/csv-parser"
658
+ },
659
+ "engines": {
660
+ "node": ">= 10"
661
+ }
662
+ },
663
+ "node_modules/debug": {
664
+ "version": "2.6.9",
665
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
666
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
667
+ "license": "MIT",
668
+ "dependencies": {
669
+ "ms": "2.0.0"
670
+ }
671
+ },
672
+ "node_modules/deep-is": {
673
+ "version": "0.1.4",
674
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
675
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
676
+ "dev": true,
677
+ "license": "MIT"
678
+ },
679
+ "node_modules/delayed-stream": {
680
+ "version": "1.0.0",
681
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
682
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
683
+ "license": "MIT",
684
+ "engines": {
685
+ "node": ">=0.4.0"
686
+ }
687
+ },
688
+ "node_modules/depd": {
689
+ "version": "2.0.0",
690
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
691
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
692
+ "license": "MIT",
693
+ "engines": {
694
+ "node": ">= 0.8"
695
+ }
696
+ },
697
+ "node_modules/deprecation": {
698
+ "version": "2.3.1",
699
+ "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
700
+ "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
701
+ "license": "ISC"
702
+ },
703
+ "node_modules/destroy": {
704
+ "version": "1.2.0",
705
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
706
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
707
+ "license": "MIT",
708
+ "engines": {
709
+ "node": ">= 0.8",
710
+ "npm": "1.2.8000 || >= 1.4.16"
711
+ }
712
+ },
713
+ "node_modules/dotenv": {
714
+ "version": "16.4.7",
715
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
716
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
717
+ "license": "BSD-2-Clause",
718
+ "engines": {
719
+ "node": ">=12"
720
+ },
721
+ "funding": {
722
+ "url": "https://dotenvx.com"
723
+ }
724
+ },
725
+ "node_modules/dunder-proto": {
726
+ "version": "1.0.1",
727
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
728
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
729
+ "license": "MIT",
730
+ "dependencies": {
731
+ "call-bind-apply-helpers": "^1.0.1",
732
+ "es-errors": "^1.3.0",
733
+ "gopd": "^1.2.0"
734
+ },
735
+ "engines": {
736
+ "node": ">= 0.4"
737
+ }
738
+ },
739
+ "node_modules/ecdsa-sig-formatter": {
740
+ "version": "1.0.11",
741
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
742
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
743
+ "license": "Apache-2.0",
744
+ "dependencies": {
745
+ "safe-buffer": "^5.0.1"
746
+ }
747
+ },
748
+ "node_modules/ee-first": {
749
+ "version": "1.1.1",
750
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
751
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
752
+ "license": "MIT"
753
+ },
754
+ "node_modules/encodeurl": {
755
+ "version": "2.0.0",
756
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
757
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
758
+ "license": "MIT",
759
+ "engines": {
760
+ "node": ">= 0.8"
761
+ }
762
+ },
763
+ "node_modules/entities": {
764
+ "version": "4.5.0",
765
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
766
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
767
+ "dev": true,
768
+ "license": "BSD-2-Clause",
769
+ "engines": {
770
+ "node": ">=0.12"
771
+ },
772
+ "funding": {
773
+ "url": "https://github.com/fb55/entities?sponsor=1"
774
+ }
775
+ },
776
+ "node_modules/es-define-property": {
777
+ "version": "1.0.1",
778
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
779
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
780
+ "license": "MIT",
781
+ "engines": {
782
+ "node": ">= 0.4"
783
+ }
784
+ },
785
+ "node_modules/es-errors": {
786
+ "version": "1.3.0",
787
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
788
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
789
+ "license": "MIT",
790
+ "engines": {
791
+ "node": ">= 0.4"
792
+ }
793
+ },
794
+ "node_modules/es-object-atoms": {
795
+ "version": "1.0.0",
796
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz",
797
+ "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==",
798
+ "license": "MIT",
799
+ "dependencies": {
800
+ "es-errors": "^1.3.0"
801
+ },
802
+ "engines": {
803
+ "node": ">= 0.4"
804
+ }
805
+ },
806
+ "node_modules/es-set-tostringtag": {
807
+ "version": "2.1.0",
808
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
809
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
810
+ "license": "MIT",
811
+ "dependencies": {
812
+ "es-errors": "^1.3.0",
813
+ "get-intrinsic": "^1.2.6",
814
+ "has-tostringtag": "^1.0.2",
815
+ "hasown": "^2.0.2"
816
+ },
817
+ "engines": {
818
+ "node": ">= 0.4"
819
+ }
820
+ },
821
+ "node_modules/escape-html": {
822
+ "version": "1.0.3",
823
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
824
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
825
+ "license": "MIT"
826
+ },
827
+ "node_modules/escape-string-regexp": {
828
+ "version": "2.0.0",
829
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
830
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
831
+ "dev": true,
832
+ "license": "MIT",
833
+ "engines": {
834
+ "node": ">=8"
835
+ }
836
+ },
837
+ "node_modules/escodegen": {
838
+ "version": "1.14.3",
839
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
840
+ "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
841
+ "dev": true,
842
+ "license": "BSD-2-Clause",
843
+ "dependencies": {
844
+ "esprima": "^4.0.1",
845
+ "estraverse": "^4.2.0",
846
+ "esutils": "^2.0.2",
847
+ "optionator": "^0.8.1"
848
+ },
849
+ "bin": {
850
+ "escodegen": "bin/escodegen.js",
851
+ "esgenerate": "bin/esgenerate.js"
852
+ },
853
+ "engines": {
854
+ "node": ">=4.0"
855
+ },
856
+ "optionalDependencies": {
857
+ "source-map": "~0.6.1"
858
+ }
859
+ },
860
+ "node_modules/escodegen/node_modules/estraverse": {
861
+ "version": "4.3.0",
862
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
863
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
864
+ "dev": true,
865
+ "license": "BSD-2-Clause",
866
+ "engines": {
867
+ "node": ">=4.0"
868
+ }
869
+ },
870
+ "node_modules/eslint-visitor-keys": {
871
+ "version": "3.4.3",
872
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
873
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
874
+ "dev": true,
875
+ "license": "Apache-2.0",
876
+ "engines": {
877
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
878
+ },
879
+ "funding": {
880
+ "url": "https://opencollective.com/eslint"
881
+ }
882
+ },
883
+ "node_modules/espree": {
884
+ "version": "9.6.1",
885
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
886
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
887
+ "dev": true,
888
+ "license": "BSD-2-Clause",
889
+ "dependencies": {
890
+ "acorn": "^8.9.0",
891
+ "acorn-jsx": "^5.3.2",
892
+ "eslint-visitor-keys": "^3.4.1"
893
+ },
894
+ "engines": {
895
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
896
+ },
897
+ "funding": {
898
+ "url": "https://opencollective.com/eslint"
899
+ }
900
+ },
901
+ "node_modules/esprima": {
902
+ "version": "4.0.1",
903
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
904
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
905
+ "dev": true,
906
+ "license": "BSD-2-Clause",
907
+ "bin": {
908
+ "esparse": "bin/esparse.js",
909
+ "esvalidate": "bin/esvalidate.js"
910
+ },
911
+ "engines": {
912
+ "node": ">=4"
913
+ }
914
+ },
915
+ "node_modules/estraverse": {
916
+ "version": "5.3.0",
917
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
918
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
919
+ "dev": true,
920
+ "license": "BSD-2-Clause",
921
+ "engines": {
922
+ "node": ">=4.0"
923
+ }
924
+ },
925
+ "node_modules/esutils": {
926
+ "version": "2.0.3",
927
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
928
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
929
+ "dev": true,
930
+ "license": "BSD-2-Clause",
931
+ "engines": {
932
+ "node": ">=0.10.0"
933
+ }
934
+ },
935
+ "node_modules/etag": {
936
+ "version": "1.8.1",
937
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
938
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
939
+ "license": "MIT",
940
+ "engines": {
941
+ "node": ">= 0.6"
942
+ }
943
+ },
944
+ "node_modules/express": {
945
+ "version": "4.21.2",
946
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
947
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
948
+ "license": "MIT",
949
+ "dependencies": {
950
+ "accepts": "~1.3.8",
951
+ "array-flatten": "1.1.1",
952
+ "body-parser": "1.20.3",
953
+ "content-disposition": "0.5.4",
954
+ "content-type": "~1.0.4",
955
+ "cookie": "0.7.1",
956
+ "cookie-signature": "1.0.6",
957
+ "debug": "2.6.9",
958
+ "depd": "2.0.0",
959
+ "encodeurl": "~2.0.0",
960
+ "escape-html": "~1.0.3",
961
+ "etag": "~1.8.1",
962
+ "finalhandler": "1.3.1",
963
+ "fresh": "0.5.2",
964
+ "http-errors": "2.0.0",
965
+ "merge-descriptors": "1.0.3",
966
+ "methods": "~1.1.2",
967
+ "on-finished": "2.4.1",
968
+ "parseurl": "~1.3.3",
969
+ "path-to-regexp": "0.1.12",
970
+ "proxy-addr": "~2.0.7",
971
+ "qs": "6.13.0",
972
+ "range-parser": "~1.2.1",
973
+ "safe-buffer": "5.2.1",
974
+ "send": "0.19.0",
975
+ "serve-static": "1.16.2",
976
+ "setprototypeof": "1.2.0",
977
+ "statuses": "2.0.1",
978
+ "type-is": "~1.6.18",
979
+ "utils-merge": "1.0.1",
980
+ "vary": "~1.1.2"
981
+ },
982
+ "engines": {
983
+ "node": ">= 0.10.0"
984
+ },
985
+ "funding": {
986
+ "type": "opencollective",
987
+ "url": "https://opencollective.com/express"
988
+ }
989
+ },
990
+ "node_modules/fast-levenshtein": {
991
+ "version": "2.0.6",
992
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
993
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
994
+ "dev": true,
995
+ "license": "MIT"
996
+ },
997
+ "node_modules/finalhandler": {
998
+ "version": "1.3.1",
999
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
1000
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
1001
+ "license": "MIT",
1002
+ "dependencies": {
1003
+ "debug": "2.6.9",
1004
+ "encodeurl": "~2.0.0",
1005
+ "escape-html": "~1.0.3",
1006
+ "on-finished": "2.4.1",
1007
+ "parseurl": "~1.3.3",
1008
+ "statuses": "2.0.1",
1009
+ "unpipe": "~1.0.0"
1010
+ },
1011
+ "engines": {
1012
+ "node": ">= 0.8"
1013
+ }
1014
+ },
1015
+ "node_modules/follow-redirects": {
1016
+ "version": "1.15.9",
1017
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
1018
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
1019
+ "funding": [
1020
+ {
1021
+ "type": "individual",
1022
+ "url": "https://github.com/sponsors/RubenVerborgh"
1023
+ }
1024
+ ],
1025
+ "license": "MIT",
1026
+ "engines": {
1027
+ "node": ">=4.0"
1028
+ },
1029
+ "peerDependenciesMeta": {
1030
+ "debug": {
1031
+ "optional": true
1032
+ }
1033
+ }
1034
+ },
1035
+ "node_modules/form-data": {
1036
+ "version": "4.0.2",
1037
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
1038
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
1039
+ "license": "MIT",
1040
+ "dependencies": {
1041
+ "asynckit": "^0.4.0",
1042
+ "combined-stream": "^1.0.8",
1043
+ "es-set-tostringtag": "^2.1.0",
1044
+ "mime-types": "^2.1.12"
1045
+ },
1046
+ "engines": {
1047
+ "node": ">= 6"
1048
+ }
1049
+ },
1050
+ "node_modules/forwarded": {
1051
+ "version": "0.2.0",
1052
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
1053
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
1054
+ "license": "MIT",
1055
+ "engines": {
1056
+ "node": ">= 0.6"
1057
+ }
1058
+ },
1059
+ "node_modules/fresh": {
1060
+ "version": "0.5.2",
1061
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
1062
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
1063
+ "license": "MIT",
1064
+ "engines": {
1065
+ "node": ">= 0.6"
1066
+ }
1067
+ },
1068
+ "node_modules/fs.realpath": {
1069
+ "version": "1.0.0",
1070
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
1071
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
1072
+ "dev": true,
1073
+ "license": "ISC"
1074
+ },
1075
+ "node_modules/function-bind": {
1076
+ "version": "1.1.2",
1077
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1078
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1079
+ "license": "MIT",
1080
+ "funding": {
1081
+ "url": "https://github.com/sponsors/ljharb"
1082
+ }
1083
+ },
1084
+ "node_modules/get-intrinsic": {
1085
+ "version": "1.2.7",
1086
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
1087
+ "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
1088
+ "license": "MIT",
1089
+ "dependencies": {
1090
+ "call-bind-apply-helpers": "^1.0.1",
1091
+ "es-define-property": "^1.0.1",
1092
+ "es-errors": "^1.3.0",
1093
+ "es-object-atoms": "^1.0.0",
1094
+ "function-bind": "^1.1.2",
1095
+ "get-proto": "^1.0.0",
1096
+ "gopd": "^1.2.0",
1097
+ "has-symbols": "^1.1.0",
1098
+ "hasown": "^2.0.2",
1099
+ "math-intrinsics": "^1.1.0"
1100
+ },
1101
+ "engines": {
1102
+ "node": ">= 0.4"
1103
+ },
1104
+ "funding": {
1105
+ "url": "https://github.com/sponsors/ljharb"
1106
+ }
1107
+ },
1108
+ "node_modules/get-proto": {
1109
+ "version": "1.0.1",
1110
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1111
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1112
+ "license": "MIT",
1113
+ "dependencies": {
1114
+ "dunder-proto": "^1.0.1",
1115
+ "es-object-atoms": "^1.0.0"
1116
+ },
1117
+ "engines": {
1118
+ "node": ">= 0.4"
1119
+ }
1120
+ },
1121
+ "node_modules/glob": {
1122
+ "version": "8.1.0",
1123
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
1124
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
1125
+ "deprecated": "Glob versions prior to v9 are no longer supported",
1126
+ "dev": true,
1127
+ "license": "ISC",
1128
+ "dependencies": {
1129
+ "fs.realpath": "^1.0.0",
1130
+ "inflight": "^1.0.4",
1131
+ "inherits": "2",
1132
+ "minimatch": "^5.0.1",
1133
+ "once": "^1.3.0"
1134
+ },
1135
+ "engines": {
1136
+ "node": ">=12"
1137
+ },
1138
+ "funding": {
1139
+ "url": "https://github.com/sponsors/isaacs"
1140
+ }
1141
+ },
1142
+ "node_modules/gopd": {
1143
+ "version": "1.2.0",
1144
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1145
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1146
+ "license": "MIT",
1147
+ "engines": {
1148
+ "node": ">= 0.4"
1149
+ },
1150
+ "funding": {
1151
+ "url": "https://github.com/sponsors/ljharb"
1152
+ }
1153
+ },
1154
+ "node_modules/graceful-fs": {
1155
+ "version": "4.2.11",
1156
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
1157
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
1158
+ "dev": true,
1159
+ "license": "ISC"
1160
+ },
1161
+ "node_modules/has-flag": {
1162
+ "version": "4.0.0",
1163
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
1164
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
1165
+ "dev": true,
1166
+ "license": "MIT",
1167
+ "engines": {
1168
+ "node": ">=8"
1169
+ }
1170
+ },
1171
+ "node_modules/has-symbols": {
1172
+ "version": "1.1.0",
1173
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1174
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1175
+ "license": "MIT",
1176
+ "engines": {
1177
+ "node": ">= 0.4"
1178
+ },
1179
+ "funding": {
1180
+ "url": "https://github.com/sponsors/ljharb"
1181
+ }
1182
+ },
1183
+ "node_modules/has-tostringtag": {
1184
+ "version": "1.0.2",
1185
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
1186
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
1187
+ "license": "MIT",
1188
+ "dependencies": {
1189
+ "has-symbols": "^1.0.3"
1190
+ },
1191
+ "engines": {
1192
+ "node": ">= 0.4"
1193
+ },
1194
+ "funding": {
1195
+ "url": "https://github.com/sponsors/ljharb"
1196
+ }
1197
+ },
1198
+ "node_modules/hasown": {
1199
+ "version": "2.0.2",
1200
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1201
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1202
+ "license": "MIT",
1203
+ "dependencies": {
1204
+ "function-bind": "^1.1.2"
1205
+ },
1206
+ "engines": {
1207
+ "node": ">= 0.4"
1208
+ }
1209
+ },
1210
+ "node_modules/http-errors": {
1211
+ "version": "2.0.0",
1212
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
1213
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
1214
+ "license": "MIT",
1215
+ "dependencies": {
1216
+ "depd": "2.0.0",
1217
+ "inherits": "2.0.4",
1218
+ "setprototypeof": "1.2.0",
1219
+ "statuses": "2.0.1",
1220
+ "toidentifier": "1.0.1"
1221
+ },
1222
+ "engines": {
1223
+ "node": ">= 0.8"
1224
+ }
1225
+ },
1226
+ "node_modules/iconv-lite": {
1227
+ "version": "0.4.24",
1228
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
1229
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
1230
+ "license": "MIT",
1231
+ "dependencies": {
1232
+ "safer-buffer": ">= 2.1.2 < 3"
1233
+ },
1234
+ "engines": {
1235
+ "node": ">=0.10.0"
1236
+ }
1237
+ },
1238
+ "node_modules/inflight": {
1239
+ "version": "1.0.6",
1240
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1241
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
1242
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
1243
+ "dev": true,
1244
+ "license": "ISC",
1245
+ "dependencies": {
1246
+ "once": "^1.3.0",
1247
+ "wrappy": "1"
1248
+ }
1249
+ },
1250
+ "node_modules/inherits": {
1251
+ "version": "2.0.4",
1252
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1253
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1254
+ "license": "ISC"
1255
+ },
1256
+ "node_modules/ipaddr.js": {
1257
+ "version": "1.9.1",
1258
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
1259
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
1260
+ "license": "MIT",
1261
+ "engines": {
1262
+ "node": ">= 0.10"
1263
+ }
1264
+ },
1265
+ "node_modules/js2xmlparser": {
1266
+ "version": "4.0.2",
1267
+ "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz",
1268
+ "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==",
1269
+ "dev": true,
1270
+ "license": "Apache-2.0",
1271
+ "dependencies": {
1272
+ "xmlcreate": "^2.0.4"
1273
+ }
1274
+ },
1275
+ "node_modules/jsdoc": {
1276
+ "version": "4.0.4",
1277
+ "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz",
1278
+ "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==",
1279
+ "dev": true,
1280
+ "license": "Apache-2.0",
1281
+ "dependencies": {
1282
+ "@babel/parser": "^7.20.15",
1283
+ "@jsdoc/salty": "^0.2.1",
1284
+ "@types/markdown-it": "^14.1.1",
1285
+ "bluebird": "^3.7.2",
1286
+ "catharsis": "^0.9.0",
1287
+ "escape-string-regexp": "^2.0.0",
1288
+ "js2xmlparser": "^4.0.2",
1289
+ "klaw": "^3.0.0",
1290
+ "markdown-it": "^14.1.0",
1291
+ "markdown-it-anchor": "^8.6.7",
1292
+ "marked": "^4.0.10",
1293
+ "mkdirp": "^1.0.4",
1294
+ "requizzle": "^0.2.3",
1295
+ "strip-json-comments": "^3.1.0",
1296
+ "underscore": "~1.13.2"
1297
+ },
1298
+ "bin": {
1299
+ "jsdoc": "jsdoc.js"
1300
+ },
1301
+ "engines": {
1302
+ "node": ">=12.0.0"
1303
+ }
1304
+ },
1305
+ "node_modules/jsonwebtoken": {
1306
+ "version": "9.0.2",
1307
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
1308
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
1309
+ "license": "MIT",
1310
+ "dependencies": {
1311
+ "jws": "^3.2.2",
1312
+ "lodash.includes": "^4.3.0",
1313
+ "lodash.isboolean": "^3.0.3",
1314
+ "lodash.isinteger": "^4.0.4",
1315
+ "lodash.isnumber": "^3.0.3",
1316
+ "lodash.isplainobject": "^4.0.6",
1317
+ "lodash.isstring": "^4.0.1",
1318
+ "lodash.once": "^4.0.0",
1319
+ "ms": "^2.1.1",
1320
+ "semver": "^7.5.4"
1321
+ },
1322
+ "engines": {
1323
+ "node": ">=12",
1324
+ "npm": ">=6"
1325
+ }
1326
+ },
1327
+ "node_modules/jsonwebtoken/node_modules/ms": {
1328
+ "version": "2.1.3",
1329
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1330
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1331
+ "license": "MIT"
1332
+ },
1333
+ "node_modules/jwa": {
1334
+ "version": "1.4.1",
1335
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
1336
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
1337
+ "license": "MIT",
1338
+ "dependencies": {
1339
+ "buffer-equal-constant-time": "1.0.1",
1340
+ "ecdsa-sig-formatter": "1.0.11",
1341
+ "safe-buffer": "^5.0.1"
1342
+ }
1343
+ },
1344
+ "node_modules/jws": {
1345
+ "version": "3.2.2",
1346
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
1347
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
1348
+ "license": "MIT",
1349
+ "dependencies": {
1350
+ "jwa": "^1.4.1",
1351
+ "safe-buffer": "^5.0.1"
1352
+ }
1353
+ },
1354
+ "node_modules/klaw": {
1355
+ "version": "3.0.0",
1356
+ "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz",
1357
+ "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==",
1358
+ "dev": true,
1359
+ "license": "MIT",
1360
+ "dependencies": {
1361
+ "graceful-fs": "^4.1.9"
1362
+ }
1363
+ },
1364
+ "node_modules/levn": {
1365
+ "version": "0.3.0",
1366
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
1367
+ "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==",
1368
+ "dev": true,
1369
+ "license": "MIT",
1370
+ "dependencies": {
1371
+ "prelude-ls": "~1.1.2",
1372
+ "type-check": "~0.3.2"
1373
+ },
1374
+ "engines": {
1375
+ "node": ">= 0.8.0"
1376
+ }
1377
+ },
1378
+ "node_modules/linkify-it": {
1379
+ "version": "5.0.0",
1380
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
1381
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
1382
+ "dev": true,
1383
+ "license": "MIT",
1384
+ "dependencies": {
1385
+ "uc.micro": "^2.0.0"
1386
+ }
1387
+ },
1388
+ "node_modules/lodash": {
1389
+ "version": "4.17.21",
1390
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
1391
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
1392
+ "dev": true,
1393
+ "license": "MIT"
1394
+ },
1395
+ "node_modules/lodash.includes": {
1396
+ "version": "4.3.0",
1397
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
1398
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
1399
+ "license": "MIT"
1400
+ },
1401
+ "node_modules/lodash.isboolean": {
1402
+ "version": "3.0.3",
1403
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
1404
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
1405
+ "license": "MIT"
1406
+ },
1407
+ "node_modules/lodash.isinteger": {
1408
+ "version": "4.0.4",
1409
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
1410
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
1411
+ "license": "MIT"
1412
+ },
1413
+ "node_modules/lodash.isnumber": {
1414
+ "version": "3.0.3",
1415
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
1416
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
1417
+ "license": "MIT"
1418
+ },
1419
+ "node_modules/lodash.isplainobject": {
1420
+ "version": "4.0.6",
1421
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
1422
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
1423
+ "license": "MIT"
1424
+ },
1425
+ "node_modules/lodash.isstring": {
1426
+ "version": "4.0.1",
1427
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
1428
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
1429
+ "license": "MIT"
1430
+ },
1431
+ "node_modules/lodash.once": {
1432
+ "version": "4.1.1",
1433
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
1434
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
1435
+ "license": "MIT"
1436
+ },
1437
+ "node_modules/long": {
1438
+ "version": "5.2.4",
1439
+ "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz",
1440
+ "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==",
1441
+ "license": "Apache-2.0"
1442
+ },
1443
+ "node_modules/markdown-it": {
1444
+ "version": "14.1.0",
1445
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
1446
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
1447
+ "dev": true,
1448
+ "license": "MIT",
1449
+ "dependencies": {
1450
+ "argparse": "^2.0.1",
1451
+ "entities": "^4.4.0",
1452
+ "linkify-it": "^5.0.0",
1453
+ "mdurl": "^2.0.0",
1454
+ "punycode.js": "^2.3.1",
1455
+ "uc.micro": "^2.1.0"
1456
+ },
1457
+ "bin": {
1458
+ "markdown-it": "bin/markdown-it.mjs"
1459
+ }
1460
+ },
1461
+ "node_modules/markdown-it-anchor": {
1462
+ "version": "8.6.7",
1463
+ "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz",
1464
+ "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==",
1465
+ "dev": true,
1466
+ "license": "Unlicense",
1467
+ "peerDependencies": {
1468
+ "@types/markdown-it": "*",
1469
+ "markdown-it": "*"
1470
+ }
1471
+ },
1472
+ "node_modules/marked": {
1473
+ "version": "4.3.0",
1474
+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
1475
+ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
1476
+ "dev": true,
1477
+ "license": "MIT",
1478
+ "bin": {
1479
+ "marked": "bin/marked.js"
1480
+ },
1481
+ "engines": {
1482
+ "node": ">= 12"
1483
+ }
1484
+ },
1485
+ "node_modules/math-intrinsics": {
1486
+ "version": "1.1.0",
1487
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1488
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1489
+ "license": "MIT",
1490
+ "engines": {
1491
+ "node": ">= 0.4"
1492
+ }
1493
+ },
1494
+ "node_modules/mdurl": {
1495
+ "version": "2.0.0",
1496
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
1497
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
1498
+ "dev": true,
1499
+ "license": "MIT"
1500
+ },
1501
+ "node_modules/media-typer": {
1502
+ "version": "0.3.0",
1503
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
1504
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
1505
+ "license": "MIT",
1506
+ "engines": {
1507
+ "node": ">= 0.6"
1508
+ }
1509
+ },
1510
+ "node_modules/merge-descriptors": {
1511
+ "version": "1.0.3",
1512
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
1513
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
1514
+ "license": "MIT",
1515
+ "funding": {
1516
+ "url": "https://github.com/sponsors/sindresorhus"
1517
+ }
1518
+ },
1519
+ "node_modules/methods": {
1520
+ "version": "1.1.2",
1521
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1522
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
1523
+ "license": "MIT",
1524
+ "engines": {
1525
+ "node": ">= 0.6"
1526
+ }
1527
+ },
1528
+ "node_modules/mime": {
1529
+ "version": "1.6.0",
1530
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1531
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1532
+ "license": "MIT",
1533
+ "bin": {
1534
+ "mime": "cli.js"
1535
+ },
1536
+ "engines": {
1537
+ "node": ">=4"
1538
+ }
1539
+ },
1540
+ "node_modules/mime-db": {
1541
+ "version": "1.52.0",
1542
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1543
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1544
+ "license": "MIT",
1545
+ "engines": {
1546
+ "node": ">= 0.6"
1547
+ }
1548
+ },
1549
+ "node_modules/mime-types": {
1550
+ "version": "2.1.35",
1551
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1552
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1553
+ "license": "MIT",
1554
+ "dependencies": {
1555
+ "mime-db": "1.52.0"
1556
+ },
1557
+ "engines": {
1558
+ "node": ">= 0.6"
1559
+ }
1560
+ },
1561
+ "node_modules/minimatch": {
1562
+ "version": "5.1.6",
1563
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
1564
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
1565
+ "dev": true,
1566
+ "license": "ISC",
1567
+ "dependencies": {
1568
+ "brace-expansion": "^2.0.1"
1569
+ },
1570
+ "engines": {
1571
+ "node": ">=10"
1572
+ }
1573
+ },
1574
+ "node_modules/minimist": {
1575
+ "version": "1.2.8",
1576
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
1577
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
1578
+ "dev": true,
1579
+ "license": "MIT",
1580
+ "funding": {
1581
+ "url": "https://github.com/sponsors/ljharb"
1582
+ }
1583
+ },
1584
+ "node_modules/mkdirp": {
1585
+ "version": "1.0.4",
1586
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
1587
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
1588
+ "dev": true,
1589
+ "license": "MIT",
1590
+ "bin": {
1591
+ "mkdirp": "bin/cmd.js"
1592
+ },
1593
+ "engines": {
1594
+ "node": ">=10"
1595
+ }
1596
+ },
1597
+ "node_modules/morgan": {
1598
+ "version": "1.10.0",
1599
+ "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
1600
+ "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==",
1601
+ "license": "MIT",
1602
+ "dependencies": {
1603
+ "basic-auth": "~2.0.1",
1604
+ "debug": "2.6.9",
1605
+ "depd": "~2.0.0",
1606
+ "on-finished": "~2.3.0",
1607
+ "on-headers": "~1.0.2"
1608
+ },
1609
+ "engines": {
1610
+ "node": ">= 0.8.0"
1611
+ }
1612
+ },
1613
+ "node_modules/morgan/node_modules/on-finished": {
1614
+ "version": "2.3.0",
1615
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
1616
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
1617
+ "license": "MIT",
1618
+ "dependencies": {
1619
+ "ee-first": "1.1.1"
1620
+ },
1621
+ "engines": {
1622
+ "node": ">= 0.8"
1623
+ }
1624
+ },
1625
+ "node_modules/ms": {
1626
+ "version": "2.0.0",
1627
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1628
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
1629
+ "license": "MIT"
1630
+ },
1631
+ "node_modules/negotiator": {
1632
+ "version": "0.6.3",
1633
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1634
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1635
+ "license": "MIT",
1636
+ "engines": {
1637
+ "node": ">= 0.6"
1638
+ }
1639
+ },
1640
+ "node_modules/node-cron": {
1641
+ "version": "3.0.3",
1642
+ "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
1643
+ "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
1644
+ "license": "ISC",
1645
+ "dependencies": {
1646
+ "uuid": "8.3.2"
1647
+ },
1648
+ "engines": {
1649
+ "node": ">=6.0.0"
1650
+ }
1651
+ },
1652
+ "node_modules/node-cron/node_modules/uuid": {
1653
+ "version": "8.3.2",
1654
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
1655
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
1656
+ "license": "MIT",
1657
+ "bin": {
1658
+ "uuid": "dist/bin/uuid"
1659
+ }
1660
+ },
1661
+ "node_modules/node-fetch": {
1662
+ "version": "2.7.0",
1663
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
1664
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
1665
+ "license": "MIT",
1666
+ "dependencies": {
1667
+ "whatwg-url": "^5.0.0"
1668
+ },
1669
+ "engines": {
1670
+ "node": "4.x || >=6.0.0"
1671
+ },
1672
+ "peerDependencies": {
1673
+ "encoding": "^0.1.0"
1674
+ },
1675
+ "peerDependenciesMeta": {
1676
+ "encoding": {
1677
+ "optional": true
1678
+ }
1679
+ }
1680
+ },
1681
+ "node_modules/object-inspect": {
1682
+ "version": "1.13.3",
1683
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
1684
+ "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
1685
+ "license": "MIT",
1686
+ "engines": {
1687
+ "node": ">= 0.4"
1688
+ },
1689
+ "funding": {
1690
+ "url": "https://github.com/sponsors/ljharb"
1691
+ }
1692
+ },
1693
+ "node_modules/on-finished": {
1694
+ "version": "2.4.1",
1695
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1696
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1697
+ "license": "MIT",
1698
+ "dependencies": {
1699
+ "ee-first": "1.1.1"
1700
+ },
1701
+ "engines": {
1702
+ "node": ">= 0.8"
1703
+ }
1704
+ },
1705
+ "node_modules/on-headers": {
1706
+ "version": "1.0.2",
1707
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
1708
+ "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
1709
+ "license": "MIT",
1710
+ "engines": {
1711
+ "node": ">= 0.8"
1712
+ }
1713
+ },
1714
+ "node_modules/once": {
1715
+ "version": "1.4.0",
1716
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1717
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1718
+ "license": "ISC",
1719
+ "dependencies": {
1720
+ "wrappy": "1"
1721
+ }
1722
+ },
1723
+ "node_modules/optionator": {
1724
+ "version": "0.8.3",
1725
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
1726
+ "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
1727
+ "dev": true,
1728
+ "license": "MIT",
1729
+ "dependencies": {
1730
+ "deep-is": "~0.1.3",
1731
+ "fast-levenshtein": "~2.0.6",
1732
+ "levn": "~0.3.0",
1733
+ "prelude-ls": "~1.1.2",
1734
+ "type-check": "~0.3.2",
1735
+ "word-wrap": "~1.2.3"
1736
+ },
1737
+ "engines": {
1738
+ "node": ">= 0.8.0"
1739
+ }
1740
+ },
1741
+ "node_modules/parseurl": {
1742
+ "version": "1.3.3",
1743
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1744
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1745
+ "license": "MIT",
1746
+ "engines": {
1747
+ "node": ">= 0.8"
1748
+ }
1749
+ },
1750
+ "node_modules/path-to-regexp": {
1751
+ "version": "0.1.12",
1752
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
1753
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
1754
+ "license": "MIT"
1755
+ },
1756
+ "node_modules/prelude-ls": {
1757
+ "version": "1.1.2",
1758
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
1759
+ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==",
1760
+ "dev": true,
1761
+ "engines": {
1762
+ "node": ">= 0.8.0"
1763
+ }
1764
+ },
1765
+ "node_modules/protobufjs": {
1766
+ "version": "7.4.0",
1767
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
1768
+ "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
1769
+ "hasInstallScript": true,
1770
+ "license": "BSD-3-Clause",
1771
+ "dependencies": {
1772
+ "@protobufjs/aspromise": "^1.1.2",
1773
+ "@protobufjs/base64": "^1.1.2",
1774
+ "@protobufjs/codegen": "^2.0.4",
1775
+ "@protobufjs/eventemitter": "^1.1.0",
1776
+ "@protobufjs/fetch": "^1.1.0",
1777
+ "@protobufjs/float": "^1.0.2",
1778
+ "@protobufjs/inquire": "^1.1.0",
1779
+ "@protobufjs/path": "^1.1.2",
1780
+ "@protobufjs/pool": "^1.1.0",
1781
+ "@protobufjs/utf8": "^1.1.0",
1782
+ "@types/node": ">=13.7.0",
1783
+ "long": "^5.0.0"
1784
+ },
1785
+ "engines": {
1786
+ "node": ">=12.0.0"
1787
+ }
1788
+ },
1789
+ "node_modules/protobufjs-cli": {
1790
+ "version": "1.1.3",
1791
+ "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.3.tgz",
1792
+ "integrity": "sha512-MqD10lqF+FMsOayFiNOdOGNlXc4iKDCf0ZQPkPR+gizYh9gqUeGTWulABUCdI+N67w5RfJ6xhgX4J8pa8qmMXQ==",
1793
+ "dev": true,
1794
+ "license": "BSD-3-Clause",
1795
+ "dependencies": {
1796
+ "chalk": "^4.0.0",
1797
+ "escodegen": "^1.13.0",
1798
+ "espree": "^9.0.0",
1799
+ "estraverse": "^5.1.0",
1800
+ "glob": "^8.0.0",
1801
+ "jsdoc": "^4.0.0",
1802
+ "minimist": "^1.2.0",
1803
+ "semver": "^7.1.2",
1804
+ "tmp": "^0.2.1",
1805
+ "uglify-js": "^3.7.7"
1806
+ },
1807
+ "bin": {
1808
+ "pbjs": "bin/pbjs",
1809
+ "pbts": "bin/pbts"
1810
+ },
1811
+ "engines": {
1812
+ "node": ">=12.0.0"
1813
+ },
1814
+ "peerDependencies": {
1815
+ "protobufjs": "^7.0.0"
1816
+ }
1817
+ },
1818
+ "node_modules/proxy-addr": {
1819
+ "version": "2.0.7",
1820
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1821
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1822
+ "license": "MIT",
1823
+ "dependencies": {
1824
+ "forwarded": "0.2.0",
1825
+ "ipaddr.js": "1.9.1"
1826
+ },
1827
+ "engines": {
1828
+ "node": ">= 0.10"
1829
+ }
1830
+ },
1831
+ "node_modules/proxy-from-env": {
1832
+ "version": "1.1.0",
1833
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1834
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
1835
+ "license": "MIT"
1836
+ },
1837
+ "node_modules/punycode.js": {
1838
+ "version": "2.3.1",
1839
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
1840
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
1841
+ "dev": true,
1842
+ "license": "MIT",
1843
+ "engines": {
1844
+ "node": ">=6"
1845
+ }
1846
+ },
1847
+ "node_modules/qs": {
1848
+ "version": "6.13.0",
1849
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
1850
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
1851
+ "license": "BSD-3-Clause",
1852
+ "dependencies": {
1853
+ "side-channel": "^1.0.6"
1854
+ },
1855
+ "engines": {
1856
+ "node": ">=0.6"
1857
+ },
1858
+ "funding": {
1859
+ "url": "https://github.com/sponsors/ljharb"
1860
+ }
1861
+ },
1862
+ "node_modules/range-parser": {
1863
+ "version": "1.2.1",
1864
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1865
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1866
+ "license": "MIT",
1867
+ "engines": {
1868
+ "node": ">= 0.6"
1869
+ }
1870
+ },
1871
+ "node_modules/raw-body": {
1872
+ "version": "2.5.2",
1873
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
1874
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
1875
+ "license": "MIT",
1876
+ "dependencies": {
1877
+ "bytes": "3.1.2",
1878
+ "http-errors": "2.0.0",
1879
+ "iconv-lite": "0.4.24",
1880
+ "unpipe": "1.0.0"
1881
+ },
1882
+ "engines": {
1883
+ "node": ">= 0.8"
1884
+ }
1885
+ },
1886
+ "node_modules/requizzle": {
1887
+ "version": "0.2.4",
1888
+ "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz",
1889
+ "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==",
1890
+ "dev": true,
1891
+ "license": "MIT",
1892
+ "dependencies": {
1893
+ "lodash": "^4.17.21"
1894
+ }
1895
+ },
1896
+ "node_modules/safe-buffer": {
1897
+ "version": "5.2.1",
1898
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1899
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1900
+ "funding": [
1901
+ {
1902
+ "type": "github",
1903
+ "url": "https://github.com/sponsors/feross"
1904
+ },
1905
+ {
1906
+ "type": "patreon",
1907
+ "url": "https://www.patreon.com/feross"
1908
+ },
1909
+ {
1910
+ "type": "consulting",
1911
+ "url": "https://feross.org/support"
1912
+ }
1913
+ ],
1914
+ "license": "MIT"
1915
+ },
1916
+ "node_modules/safer-buffer": {
1917
+ "version": "2.1.2",
1918
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1919
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1920
+ "license": "MIT"
1921
+ },
1922
+ "node_modules/semver": {
1923
+ "version": "7.7.0",
1924
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz",
1925
+ "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==",
1926
+ "license": "ISC",
1927
+ "bin": {
1928
+ "semver": "bin/semver.js"
1929
+ },
1930
+ "engines": {
1931
+ "node": ">=10"
1932
+ }
1933
+ },
1934
+ "node_modules/send": {
1935
+ "version": "0.19.0",
1936
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
1937
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
1938
+ "license": "MIT",
1939
+ "dependencies": {
1940
+ "debug": "2.6.9",
1941
+ "depd": "2.0.0",
1942
+ "destroy": "1.2.0",
1943
+ "encodeurl": "~1.0.2",
1944
+ "escape-html": "~1.0.3",
1945
+ "etag": "~1.8.1",
1946
+ "fresh": "0.5.2",
1947
+ "http-errors": "2.0.0",
1948
+ "mime": "1.6.0",
1949
+ "ms": "2.1.3",
1950
+ "on-finished": "2.4.1",
1951
+ "range-parser": "~1.2.1",
1952
+ "statuses": "2.0.1"
1953
+ },
1954
+ "engines": {
1955
+ "node": ">= 0.8.0"
1956
+ }
1957
+ },
1958
+ "node_modules/send/node_modules/encodeurl": {
1959
+ "version": "1.0.2",
1960
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1961
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
1962
+ "license": "MIT",
1963
+ "engines": {
1964
+ "node": ">= 0.8"
1965
+ }
1966
+ },
1967
+ "node_modules/send/node_modules/ms": {
1968
+ "version": "2.1.3",
1969
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1970
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1971
+ "license": "MIT"
1972
+ },
1973
+ "node_modules/serve-static": {
1974
+ "version": "1.16.2",
1975
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
1976
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
1977
+ "license": "MIT",
1978
+ "dependencies": {
1979
+ "encodeurl": "~2.0.0",
1980
+ "escape-html": "~1.0.3",
1981
+ "parseurl": "~1.3.3",
1982
+ "send": "0.19.0"
1983
+ },
1984
+ "engines": {
1985
+ "node": ">= 0.8.0"
1986
+ }
1987
+ },
1988
+ "node_modules/setprototypeof": {
1989
+ "version": "1.2.0",
1990
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1991
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1992
+ "license": "ISC"
1993
+ },
1994
+ "node_modules/side-channel": {
1995
+ "version": "1.1.0",
1996
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1997
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1998
+ "license": "MIT",
1999
+ "dependencies": {
2000
+ "es-errors": "^1.3.0",
2001
+ "object-inspect": "^1.13.3",
2002
+ "side-channel-list": "^1.0.0",
2003
+ "side-channel-map": "^1.0.1",
2004
+ "side-channel-weakmap": "^1.0.2"
2005
+ },
2006
+ "engines": {
2007
+ "node": ">= 0.4"
2008
+ },
2009
+ "funding": {
2010
+ "url": "https://github.com/sponsors/ljharb"
2011
+ }
2012
+ },
2013
+ "node_modules/side-channel-list": {
2014
+ "version": "1.0.0",
2015
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
2016
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
2017
+ "license": "MIT",
2018
+ "dependencies": {
2019
+ "es-errors": "^1.3.0",
2020
+ "object-inspect": "^1.13.3"
2021
+ },
2022
+ "engines": {
2023
+ "node": ">= 0.4"
2024
+ },
2025
+ "funding": {
2026
+ "url": "https://github.com/sponsors/ljharb"
2027
+ }
2028
+ },
2029
+ "node_modules/side-channel-map": {
2030
+ "version": "1.0.1",
2031
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
2032
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
2033
+ "license": "MIT",
2034
+ "dependencies": {
2035
+ "call-bound": "^1.0.2",
2036
+ "es-errors": "^1.3.0",
2037
+ "get-intrinsic": "^1.2.5",
2038
+ "object-inspect": "^1.13.3"
2039
+ },
2040
+ "engines": {
2041
+ "node": ">= 0.4"
2042
+ },
2043
+ "funding": {
2044
+ "url": "https://github.com/sponsors/ljharb"
2045
+ }
2046
+ },
2047
+ "node_modules/side-channel-weakmap": {
2048
+ "version": "1.0.2",
2049
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
2050
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
2051
+ "license": "MIT",
2052
+ "dependencies": {
2053
+ "call-bound": "^1.0.2",
2054
+ "es-errors": "^1.3.0",
2055
+ "get-intrinsic": "^1.2.5",
2056
+ "object-inspect": "^1.13.3",
2057
+ "side-channel-map": "^1.0.1"
2058
+ },
2059
+ "engines": {
2060
+ "node": ">= 0.4"
2061
+ },
2062
+ "funding": {
2063
+ "url": "https://github.com/sponsors/ljharb"
2064
+ }
2065
+ },
2066
+ "node_modules/source-map": {
2067
+ "version": "0.6.1",
2068
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
2069
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
2070
+ "dev": true,
2071
+ "license": "BSD-3-Clause",
2072
+ "optional": true,
2073
+ "engines": {
2074
+ "node": ">=0.10.0"
2075
+ }
2076
+ },
2077
+ "node_modules/statuses": {
2078
+ "version": "2.0.1",
2079
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
2080
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
2081
+ "license": "MIT",
2082
+ "engines": {
2083
+ "node": ">= 0.8"
2084
+ }
2085
+ },
2086
+ "node_modules/strip-json-comments": {
2087
+ "version": "3.1.1",
2088
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
2089
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
2090
+ "dev": true,
2091
+ "license": "MIT",
2092
+ "engines": {
2093
+ "node": ">=8"
2094
+ },
2095
+ "funding": {
2096
+ "url": "https://github.com/sponsors/sindresorhus"
2097
+ }
2098
+ },
2099
+ "node_modules/supports-color": {
2100
+ "version": "7.2.0",
2101
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
2102
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
2103
+ "dev": true,
2104
+ "license": "MIT",
2105
+ "dependencies": {
2106
+ "has-flag": "^4.0.0"
2107
+ },
2108
+ "engines": {
2109
+ "node": ">=8"
2110
+ }
2111
+ },
2112
+ "node_modules/tmp": {
2113
+ "version": "0.2.3",
2114
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
2115
+ "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
2116
+ "dev": true,
2117
+ "license": "MIT",
2118
+ "engines": {
2119
+ "node": ">=14.14"
2120
+ }
2121
+ },
2122
+ "node_modules/toidentifier": {
2123
+ "version": "1.0.1",
2124
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
2125
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
2126
+ "license": "MIT",
2127
+ "engines": {
2128
+ "node": ">=0.6"
2129
+ }
2130
+ },
2131
+ "node_modules/tr46": {
2132
+ "version": "0.0.3",
2133
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
2134
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
2135
+ "license": "MIT"
2136
+ },
2137
+ "node_modules/type-check": {
2138
+ "version": "0.3.2",
2139
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
2140
+ "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==",
2141
+ "dev": true,
2142
+ "license": "MIT",
2143
+ "dependencies": {
2144
+ "prelude-ls": "~1.1.2"
2145
+ },
2146
+ "engines": {
2147
+ "node": ">= 0.8.0"
2148
+ }
2149
+ },
2150
+ "node_modules/type-is": {
2151
+ "version": "1.6.18",
2152
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
2153
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
2154
+ "license": "MIT",
2155
+ "dependencies": {
2156
+ "media-typer": "0.3.0",
2157
+ "mime-types": "~2.1.24"
2158
+ },
2159
+ "engines": {
2160
+ "node": ">= 0.6"
2161
+ }
2162
+ },
2163
+ "node_modules/uc.micro": {
2164
+ "version": "2.1.0",
2165
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
2166
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
2167
+ "dev": true,
2168
+ "license": "MIT"
2169
+ },
2170
+ "node_modules/uglify-js": {
2171
+ "version": "3.19.3",
2172
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
2173
+ "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
2174
+ "dev": true,
2175
+ "license": "BSD-2-Clause",
2176
+ "bin": {
2177
+ "uglifyjs": "bin/uglifyjs"
2178
+ },
2179
+ "engines": {
2180
+ "node": ">=0.8.0"
2181
+ }
2182
+ },
2183
+ "node_modules/underscore": {
2184
+ "version": "1.13.7",
2185
+ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
2186
+ "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==",
2187
+ "dev": true,
2188
+ "license": "MIT"
2189
+ },
2190
+ "node_modules/undici": {
2191
+ "version": "6.21.2",
2192
+ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz",
2193
+ "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==",
2194
+ "license": "MIT",
2195
+ "engines": {
2196
+ "node": ">=18.17"
2197
+ }
2198
+ },
2199
+ "node_modules/undici-types": {
2200
+ "version": "6.20.0",
2201
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
2202
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
2203
+ "license": "MIT"
2204
+ },
2205
+ "node_modules/universal-user-agent": {
2206
+ "version": "6.0.1",
2207
+ "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
2208
+ "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
2209
+ "license": "ISC"
2210
+ },
2211
+ "node_modules/unpipe": {
2212
+ "version": "1.0.0",
2213
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
2214
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
2215
+ "license": "MIT",
2216
+ "engines": {
2217
+ "node": ">= 0.8"
2218
+ }
2219
+ },
2220
+ "node_modules/utils-merge": {
2221
+ "version": "1.0.1",
2222
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
2223
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
2224
+ "license": "MIT",
2225
+ "engines": {
2226
+ "node": ">= 0.4.0"
2227
+ }
2228
+ },
2229
+ "node_modules/uuid": {
2230
+ "version": "11.0.5",
2231
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz",
2232
+ "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==",
2233
+ "funding": [
2234
+ "https://github.com/sponsors/broofa",
2235
+ "https://github.com/sponsors/ctavan"
2236
+ ],
2237
+ "license": "MIT",
2238
+ "bin": {
2239
+ "uuid": "dist/esm/bin/uuid"
2240
+ }
2241
+ },
2242
+ "node_modules/vary": {
2243
+ "version": "1.1.2",
2244
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
2245
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
2246
+ "license": "MIT",
2247
+ "engines": {
2248
+ "node": ">= 0.8"
2249
+ }
2250
+ },
2251
+ "node_modules/webidl-conversions": {
2252
+ "version": "3.0.1",
2253
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
2254
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
2255
+ "license": "BSD-2-Clause"
2256
+ },
2257
+ "node_modules/whatwg-url": {
2258
+ "version": "5.0.0",
2259
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
2260
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
2261
+ "license": "MIT",
2262
+ "dependencies": {
2263
+ "tr46": "~0.0.3",
2264
+ "webidl-conversions": "^3.0.0"
2265
+ }
2266
+ },
2267
+ "node_modules/word-wrap": {
2268
+ "version": "1.2.5",
2269
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
2270
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
2271
+ "dev": true,
2272
+ "license": "MIT",
2273
+ "engines": {
2274
+ "node": ">=0.10.0"
2275
+ }
2276
+ },
2277
+ "node_modules/wrappy": {
2278
+ "version": "1.0.2",
2279
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
2280
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
2281
+ "license": "ISC"
2282
+ },
2283
+ "node_modules/xmlcreate": {
2284
+ "version": "2.0.4",
2285
+ "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz",
2286
+ "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==",
2287
+ "dev": true,
2288
+ "license": "Apache-2.0"
2289
+ }
2290
+ }
2291
+ }
package.json ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "cursor-to-openai",
3
+ "version": "1.3.0",
4
+ "description": "Cursor to OpenAPI",
5
+ "author": "JiuZ-Chn",
6
+ "private": false,
7
+ "main": "index.js",
8
+ "url": "https://github.com/JiuZ-Chn/Cursor-To-OpenAI",
9
+ "license": "MIT",
10
+ "dependencies": {
11
+ "@octokit/rest": "^20.0.2",
12
+ "adm-zip": "^0.5.16",
13
+ "axios": "^1.6.7",
14
+ "csv-parser": "^3.0.0",
15
+ "dotenv": "^16.4.7",
16
+ "express": "4.21.2",
17
+ "jsonwebtoken": "^9.0.2",
18
+ "morgan": "^1.10.0",
19
+ "node-cron": "^3.0.3",
20
+ "node-fetch": "^2.7.0",
21
+ "protobufjs": "^7.4.0",
22
+ "undici": "^6.21.2",
23
+ "uuid": "11.0.5"
24
+ },
25
+ "scripts": {
26
+ "proto": "npx pbjs -t static-module -w commonjs -o src/proto/message.js src/proto/message.proto",
27
+ "start": "node src/app.js",
28
+ "setup": "node setup.js",
29
+ "manage-emails": "node manage-emails.js",
30
+ "refresh-cookies": "node auto-refresh-cookies.js",
31
+ "refresh-cookies:force": "node auto-refresh-cookies.js --force",
32
+ "refresh-cookies:api": "node auto-refresh-cookies.js",
33
+ "manage-cookies": "node manage-invalid-cookies.js"
34
+ },
35
+ "devDependencies": {
36
+ "protobufjs-cli": "^1.1.3"
37
+ }
38
+ }
project.sh ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/data/data/com.termux/files/usr/bin/bash
2
+
3
+ # 项目管理脚本(数字选择版)
4
+
5
+ echo "请选择操作:"
6
+ echo "1. 更新 cookie"
7
+ echo "2. 启动项目"
8
+ echo "3. 管理邮箱"
9
+ echo "4. 初始化配置"
10
+ echo "5. 更新项目代码"
11
+ echo "6. 备份项目"
12
+ echo "7. 退出"
13
+
14
+ read -p "输入数字 (1-7): " choice
15
+
16
+ case $choice in
17
+ 1)
18
+ echo "正在更新 cookie..."
19
+ npm run refresh-cookies
20
+ ;;
21
+ 2)
22
+ echo "正在启动项目..."
23
+ npm start
24
+ ;;
25
+ 3)
26
+ echo "正在管理邮箱..."
27
+ npm run manage-emails
28
+ ;;
29
+ 4)
30
+ echo "正在初始化配置文件..."
31
+ npm run setup
32
+ ;;
33
+ 5)
34
+ echo "正在更新项目代码..."
35
+ git pull
36
+ ;;
37
+ 6)
38
+ echo "正在备份项目..."
39
+ DATE=$(date +%Y%m%d_%H%M%S)
40
+ tar -czf "backup_$DATE.tar.gz" .
41
+ echo "备份完成: backup_$DATE.tar.gz"
42
+ ;;
43
+ 7)
44
+ echo "退出"
45
+ exit 0
46
+ ;;
47
+ *)
48
+ echo "错误:请输入 1-7 之间的数字"
49
+ exit 1
50
+ ;;
51
+ esac
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();
setup.js ADDED
@@ -0,0 +1,361 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const dotenv = require('dotenv');
7
+
8
+ // 创建交互式命令行界面
9
+ const rl = readline.createInterface({
10
+ input: process.stdin,
11
+ output: process.stdout
12
+ });
13
+
14
+ // 配置模板
15
+ const ENV_TEMPLATE = `# 服务端口
16
+ PORT=3010
17
+
18
+ # 日志格式 (tiny, combined, common, dev, short)
19
+ MORGAN_FORMAT=tiny
20
+
21
+ # API Key与Cookie映射关系 (JSON格式)
22
+ # 格式: {"自定义API Key": "Cookie值"} 或 {"自定义API Key": ["Cookie值1", "Cookie值2"]}
23
+ API_KEYS={API_KEYS_PLACEHOLDER}
24
+
25
+ # 轮询策略 (random 或 round-robin)
26
+ ROTATION_STRATEGY=round-robin
27
+
28
+ # Cursor校验和 (可选)
29
+ # x-cursor-checksum=xxxxxxxx
30
+
31
+ # 自动刷新Cookie设置
32
+ # 是否启用自动刷新Cookie (true 或 false)
33
+ ENABLE_AUTO_REFRESH=true
34
+
35
+ # 自动刷新Cookie的定时规则 (Cron表达式)
36
+ # 默认每6小时执行一次
37
+ REFRESH_CRON=0 */6 * * *
38
+
39
+ # 每个API Key最小Cookie数量
40
+ # 当Cookie数量低于此值时,会自动尝试刷新
41
+ MIN_COOKIE_COUNT=1000
42
+
43
+ # Cookie刷新模式
44
+ # replace: 每次刷新都将现有cookie全部标记为无效并替换成新cookie(默认)
45
+ # append: 保留现有cookie,仅追加新cookie
46
+ COOKIE_REFRESH_MODE=replace
47
+
48
+ # GitHub 仓库信息
49
+ GITHUB_OWNER={GITHUB_OWNER_PLACEHOLDER}
50
+ GITHUB_REPO=Cursor-Register-fix
51
+
52
+ # GitHub Token (用于从GitHub Actions下载Artifact)
53
+ # 需要有repo权限
54
+ GITHUB_TOKEN={GITHUB_TOKEN_PLACEHOLDER}
55
+
56
+ # GitHub Actions 工作流ID
57
+ # 用于触发工作流程
58
+ GITHUB_WORKFLOW_ID=cursor_register.yml
59
+
60
+ # 是否自动触发工作流
61
+ # 设置为true时,会自动触发工作流而不是仅获取最新结果
62
+ TRIGGER_WORKFLOW=true
63
+
64
+ # 工作流参数设置 目前只支持gmail,outlook过于复杂,暂时不支持
65
+ # 注册账号数量
66
+ REGISTER_NUMBER=1
67
+ # 最大并发工作线程数
68
+ REGISTER_MAX_WORKERS=1
69
+ # 邮箱服务器类型 (TempEmail 或 IMAP)
70
+ REGISTER_EMAIL_SERVER=IMAP
71
+ # 是否将账号令牌注入到OneAPI (true 或 false)
72
+ REGISTER_INGEST_TO_ONEAPI=false
73
+ # 是否上传账号信息到Artifact (true 或 false)
74
+ REGISTER_UPLOAD_ARTIFACT=true
75
+ # 是否从config.yaml读取邮箱配置 (true 或 false)
76
+ REGISTER_USE_CONFIG_FILE=false
77
+ # 邮箱配置JSON字符串(仅在REGISTER_USE_CONFIG_FILE=false时有效)
78
+ # 格式例如[{"email":"[email protected]","imap_server":"imap.gmail.com","imap_port":993,"username":"[email protected]","password":"your_app_password"}]
79
+ REGISTER_EMAIL_CONFIGS={EMAIL_CONFIGS_PLACEHOLDER}
80
+ `;
81
+
82
+ // 提示信息
83
+ console.log('===== Cursor-To-OpenAI 环境配置助手 =====');
84
+ console.log('这个脚本将帮助你配置必要的环境变量\n');
85
+
86
+ // 应用密码说明
87
+ function printAppPasswordInstructions() {
88
+ console.log('\n===== 如何创建谷歌应用密码 =====');
89
+ console.log('1. 访问 https://myaccount.google.com/security');
90
+ console.log('2. 在"登录Google"部分,点击"两步验证"');
91
+ console.log(' (如果未启用两步验证,需要先启用)');
92
+ console.log('3. 在页面底部找到"应用密码",点击进入');
93
+ console.log('4. 在"选择应用"下拉菜单中选择"其他(自定义名称)"');
94
+ console.log('5. 输入一个名称,例如"Cursor注册"');
95
+ console.log('6. 点击"生成"');
96
+ console.log('7. 复制生成的16位应用密码(格式如:xxxx xxxx xxxx xxxx)');
97
+ console.log('8. 在下面的提示中输入这个密码');
98
+ console.log('注意: 应用密码只会显示一次,请务必保存好\n');
99
+ }
100
+
101
+ // 从现有.env文件加载配置
102
+ function loadExistingConfig() {
103
+ const envPath = path.join(process.cwd(), '.env');
104
+ let existingConfig = {
105
+ apiKeys: {},
106
+ githubOwner: '',
107
+ githubToken: '',
108
+ emailConfigs: [],
109
+ cookieRefreshMode: 'append'
110
+ };
111
+
112
+ if (fs.existsSync(envPath)) {
113
+ console.log('发现现有的.env配置文件,将加载现有设置作为默认值');
114
+ console.log('提示: 直接按回车将保留现有设置不变\n');
115
+
116
+ try {
117
+ // 加载.env文件
118
+ const envConfig = dotenv.parse(fs.readFileSync(envPath));
119
+
120
+ // 提取API Keys
121
+ if (envConfig.API_KEYS) {
122
+ try {
123
+ existingConfig.apiKeys = JSON.parse(envConfig.API_KEYS);
124
+ } catch (e) {
125
+ console.log('无法解析现有的API Keys配置,将使用默认设置');
126
+ }
127
+ }
128
+
129
+ // 提取GitHub Owner
130
+ if (envConfig.GITHUB_OWNER) {
131
+ existingConfig.githubOwner = envConfig.GITHUB_OWNER;
132
+ }
133
+
134
+ // 提取GitHub Token
135
+ if (envConfig.GITHUB_TOKEN) {
136
+ existingConfig.githubToken = envConfig.GITHUB_TOKEN;
137
+ }
138
+
139
+ // 提取Email配置
140
+ if (envConfig.REGISTER_EMAIL_CONFIGS) {
141
+ try {
142
+ existingConfig.emailConfigs = JSON.parse(envConfig.REGISTER_EMAIL_CONFIGS);
143
+ } catch (e) {
144
+ console.log('无法解析现有的Email配置,将使用默认设置');
145
+ }
146
+ }
147
+
148
+ // 提取Cookie刷新模式
149
+ if (envConfig.COOKIE_REFRESH_MODE) {
150
+ existingConfig.cookieRefreshMode = envConfig.COOKIE_REFRESH_MODE;
151
+ }
152
+
153
+ console.log('成功加载现有配置');
154
+ } catch (error) {
155
+ console.error('加载现有配置时出错:', error.message);
156
+ console.log('将使用默认设置');
157
+ }
158
+ } else {
159
+ console.log('未找到现有的.env配置文件,将创建新的配置文件');
160
+ }
161
+
162
+ return existingConfig;
163
+ }
164
+
165
+ // 提示用户输入,带有默认值
166
+ function promptWithDefault(question, defaultValue) {
167
+ return new Promise((resolve) => {
168
+ const defaultText = defaultValue ? ` [${defaultValue}]` : '';
169
+ rl.question(`${question}${defaultText}: `, (answer) => {
170
+ // 如果用户只按了回车,使用默认值
171
+ resolve(answer.trim() || defaultValue || '');
172
+ });
173
+ });
174
+ }
175
+
176
+ // 收集配置信息
177
+ async function collectConfig() {
178
+ // 加载现有配置
179
+ const existingConfig = loadExistingConfig();
180
+
181
+ const config = {
182
+ apiKeys: {},
183
+ githubOwner: '',
184
+ githubToken: '',
185
+ emailConfigs: [],
186
+ cookieRefreshMode: 'replace'
187
+ };
188
+
189
+ // 获取GitHub用户名
190
+ config.githubOwner = await promptWithDefault('请输入你的GitHub用户名', existingConfig.githubOwner);
191
+
192
+ // 获取GitHub Token
193
+ config.githubToken = await promptWithDefault('请输入你的GitHub Token (具有repo权限)', existingConfig.githubToken);
194
+
195
+ // 处理API Keys
196
+ const existingApiKeys = Object.keys(existingConfig.apiKeys);
197
+ if (existingApiKeys.length > 0) {
198
+ console.log('\n现有的API Keys:');
199
+ existingApiKeys.forEach(key => console.log(`- ${key}`));
200
+
201
+ const keepExistingApiKeys = await promptWithDefault('是否保留现有的API Keys? (y/n)', 'y');
202
+ if (keepExistingApiKeys.toLowerCase() === 'y') {
203
+ config.apiKeys = { ...existingConfig.apiKeys };
204
+ }
205
+ }
206
+
207
+ // 询问是否添加新的API Key
208
+ const addNewApiKey = await promptWithDefault('是否添加新的API Key? (y/n)', existingApiKeys.length === 0 ? 'y' : 'n');
209
+ if (addNewApiKey.toLowerCase() === 'y') {
210
+ const apiKey = await promptWithDefault('请输入自定义的API Key (不含sk-前缀,将自动添加)', '');
211
+ if (apiKey) {
212
+ const fullApiKey = apiKey.startsWith('sk-') ? apiKey : `sk-${apiKey}`;
213
+ config.apiKeys[fullApiKey] = [];
214
+ }
215
+ }
216
+
217
+ // 询问Cookie刷新模式
218
+ const refreshModePrompt = `选择Cookie刷新模式 [append/replace]`;
219
+ const defaultRefreshMode = existingConfig.cookieRefreshMode || 'replace';
220
+ config.cookieRefreshMode = await promptWithDefault(refreshModePrompt, defaultRefreshMode);
221
+
222
+ // 解释所选的刷新模式
223
+ if (config.cookieRefreshMode.toLowerCase() === 'replace') {
224
+ config.cookieRefreshMode = 'replace';
225
+ console.log('已选择替换模式: 每次刷新都将现有cookie全部标记为无效并替换成新cookie');
226
+ } else {
227
+ config.cookieRefreshMode = 'append';
228
+ console.log('已选择追加模式: 保留现有cookie,仅追加新cookie');
229
+ }
230
+
231
+ // 处理Email配置
232
+ if (existingConfig.emailConfigs.length > 0) {
233
+ console.log('\n现有的Gmail账号:');
234
+ existingConfig.emailConfigs.forEach((emailConfig, index) => {
235
+ console.log(`- ${index + 1}: ${emailConfig.email}`);
236
+ });
237
+
238
+ const keepExistingEmails = await promptWithDefault('是否保留现有的Gmail账号? (y/n)', 'y');
239
+ if (keepExistingEmails.toLowerCase() === 'y') {
240
+ config.emailConfigs = [...existingConfig.emailConfigs];
241
+ }
242
+ }
243
+
244
+ // 询问是否添加新的Gmail账号
245
+ const addNewGmail = await promptWithDefault('是否添加新的Gmail账号? (y/n)', existingConfig.emailConfigs.length === 0 ? 'y' : 'n');
246
+ if (addNewGmail.toLowerCase() === 'y') {
247
+ printAppPasswordInstructions();
248
+ await askForGmailAccount(config);
249
+ } else if (config.emailConfigs.length === 0 && existingConfig.emailConfigs.length === 0) {
250
+ console.log('\n⚠️ 警告: 未添加Gmail账号,自动刷新功能可能无法正常工作');
251
+ console.log('你可以稍后在.env文件中手动配置REGISTER_EMAIL_CONFIGS\n');
252
+ }
253
+
254
+ return config;
255
+ }
256
+
257
+ // 询问Gmail账号
258
+ async function askForGmailAccount(config) {
259
+ const addGmail = await promptWithDefault('\n是否添加Gmail账号用于注册? (y/n)', 'y');
260
+
261
+ if (addGmail.toLowerCase() === 'y') {
262
+ const email = await promptWithDefault('请输入Gmail地址', '');
263
+ const password = await promptWithDefault('请输入Gmail的应用密码 (不是邮箱密码)', '');
264
+
265
+ if (email && password) {
266
+ // 添加Email配置
267
+ config.emailConfigs.push({
268
+ email: email,
269
+ imap_server: "imap.gmail.com",
270
+ imap_port: 993,
271
+ username: email,
272
+ password: password
273
+ });
274
+ }
275
+
276
+ const continueAnswer = await promptWithDefault('是否继续添加另一个Gmail账号? (y/n)', 'n');
277
+ if (continueAnswer.toLowerCase() === 'y') {
278
+ await askForGmailAccount(config);
279
+ }
280
+ }
281
+
282
+ return config;
283
+ }
284
+
285
+ // 生成配置文件
286
+ function generateEnvFile(config) {
287
+ try {
288
+ // 准备API Keys
289
+ const apiKeysJson = JSON.stringify(config.apiKeys);
290
+
291
+ // 准备邮箱配置
292
+ const emailConfigsJson = JSON.stringify(config.emailConfigs);
293
+
294
+ // 替换模板中的占位符
295
+ let envContent = ENV_TEMPLATE
296
+ .replace('{API_KEYS_PLACEHOLDER}', apiKeysJson)
297
+ .replace('{GITHUB_OWNER_PLACEHOLDER}', config.githubOwner)
298
+ .replace('{GITHUB_TOKEN_PLACEHOLDER}', config.githubToken)
299
+ .replace('{EMAIL_CONFIGS_PLACEHOLDER}', emailConfigsJson);
300
+
301
+ // 更新Cookie刷新模式
302
+ envContent = envContent.replace('COOKIE_REFRESH_MODE=append', `COOKIE_REFRESH_MODE=${config.cookieRefreshMode}`);
303
+
304
+ // 写入.env文件
305
+ const envPath = path.join(process.cwd(), '.env');
306
+
307
+ // 检查是否存在备份文件
308
+ const backupPath = path.join(process.cwd(), '.env.backup');
309
+ if (fs.existsSync(envPath)) {
310
+ // 创建备份
311
+ fs.copyFileSync(envPath, backupPath);
312
+ console.log(`\n✅ 已创建原配置文件备份: ${backupPath}`);
313
+ }
314
+
315
+ fs.writeFileSync(envPath, envContent, 'utf8');
316
+ console.log(`\n✅ 配置文件已生成: ${envPath}`);
317
+
318
+ // 检查data目录
319
+ const dataDir = path.join(process.cwd(), 'data');
320
+ if (!fs.existsSync(dataDir)) {
321
+ fs.mkdirSync(dataDir, { recursive: true });
322
+ console.log(`✅ 创建数据目录: ${dataDir}`);
323
+ }
324
+
325
+ return true;
326
+ } catch (error) {
327
+ console.error('\n❌ 生成配置文件时出错:', error.message);
328
+ return false;
329
+ }
330
+ }
331
+
332
+ // 主函数
333
+ async function main() {
334
+ try {
335
+ const config = await collectConfig();
336
+
337
+ if (generateEnvFile(config)) {
338
+ console.log('\n===== 配置完成 =====');
339
+ console.log('你可以使用以下命令启动服务:');
340
+ console.log(' npm start');
341
+ console.log('\n如需手动获取cookie执行:');
342
+ console.log(' npm run refresh-cookies');
343
+
344
+ // 根据配置的刷新模式提供提示
345
+ console.log(`\n当前Cookie刷新模式为: ${config.cookieRefreshMode}`);
346
+ if (config.cookieRefreshMode === 'replace') {
347
+ console.log('每次刷新都会将现有cookie全部标记为无效并替换成新cookie');
348
+ } else {
349
+ console.log('刷新时会保留现有cookie,仅追加新cookie');
350
+ }
351
+ console.log('你可以在.env文件中修改COOKIE_REFRESH_MODE设置');
352
+ }
353
+ } catch (error) {
354
+ console.error('\n❌ 配置过程中出错:', error.message);
355
+ } finally {
356
+ rl.close();
357
+ }
358
+ }
359
+
360
+ // 运行主函数
361
+ 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,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ // 添加代理配置
25
+ proxy: {
26
+ enabled: process.env.PROXY_ENABLED === 'true' || false,
27
+ url: process.env.PROXY_URL || 'http://127.0.0.1:7890',
28
+ },
29
+
30
+ // GitHub相关配置
31
+ github: {
32
+ token: process.env.GITHUB_TOKEN,
33
+ owner: process.env.GITHUB_OWNER,
34
+ repo: process.env.GITHUB_REPO,
35
+ workflowId: process.env.GITHUB_WORKFLOW_ID,
36
+ triggerWorkflow: process.env.TRIGGER_WORKFLOW === 'true'
37
+ },
38
+
39
+ // 工作流参数
40
+ workflowParams: {
41
+ number: parseInt(process.env.REGISTER_NUMBER || '2', 10),
42
+ maxWorkers: parseInt(process.env.REGISTER_MAX_WORKERS || '1', 10),
43
+ emailServer: process.env.REGISTER_EMAIL_SERVER || 'TempEmail',
44
+ ingestToOneapi: process.env.REGISTER_INGEST_TO_ONEAPI === 'true',
45
+ uploadArtifact: process.env.REGISTER_UPLOAD_ARTIFACT === 'true',
46
+ useConfigFile: process.env.REGISTER_USE_CONFIG_FILE !== 'false',
47
+ emailConfigs: process.env.REGISTER_EMAIL_CONFIGS || '[]'
48
+ },
49
+
50
+ // 刷新配置
51
+ refresh: {
52
+ cron: process.env.REFRESH_CRON || '0 */6 * * *',
53
+ minCookieCount: parseInt(process.env.MIN_COOKIE_COUNT || '2', 10),
54
+ enabled: process.env.ENABLE_AUTO_REFRESH === 'true'
55
+ }
56
+ };
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
The diff for this file is too large to render. See raw diff
 
src/proto/message.proto ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ syntax = "proto3";
2
+
3
+ message AvailableModelsResponse {
4
+ message AvailableModel {
5
+ string name = 1;
6
+ bool defaultOn = 2;
7
+ optional bool isLongContextOnly = 3;
8
+ optional bool isChatOnly = 4;
9
+ }
10
+ repeated AvailableModel models = 2;
11
+ repeated string modelNames = 1;
12
+ }
13
+
14
+ message MessageSummary {
15
+ string content = 1;
16
+ string summaryId1 = 2;
17
+ string summaryId2 = 3; // uuid, equal to summaryId1
18
+ string previousSummaryId = 4;
19
+ }
20
+
21
+ message MessageThinking {
22
+ string content = 1;
23
+ }
24
+ message StreamUnifiedChatWithToolsRequest {
25
+ message Request {
26
+ message Message {
27
+ message Image {
28
+ message Metadata {
29
+ int32 width = 1;
30
+ int32 height = 2;
31
+ }
32
+ bytes data = 1;
33
+ Metadata metadata = 2;
34
+ }
35
+ string content = 1;
36
+ int32 role = 2;
37
+ Image image = 10;
38
+ string messageId = 13;
39
+ string unknown29 = 29; // 1, only for user message
40
+ string summaryId = 32;
41
+ MessageSummary summary = 39;
42
+ MessageThinking thinking = 45;
43
+ int32 chatModeEnum = 47; // 1 for ask, 2 for agent, 3 for edit
44
+ }
45
+ message Instruction {
46
+ string instruction = 1;
47
+ }
48
+ message Model {
49
+ string name = 1;
50
+ bytes empty = 4;
51
+ }
52
+ message CursorSetting {
53
+ message Unknown6 {
54
+ bytes unknown1 = 1;
55
+ bytes unknown2 = 2;
56
+ }
57
+ string name = 1;
58
+ bytes unknown3 = 3;
59
+ Unknown6 unknown6 = 6;
60
+ int32 unknown8 = 8;
61
+ int32 unknown9 = 9;
62
+ }
63
+ message Metadata {
64
+ string os = 1; // win32
65
+ string arch = 2; // x64
66
+ string version = 3; // 10.0.22631
67
+ string path = 4; // C:\Program Files\PowerShell\7\pwsh.exe
68
+ string timestamp = 5; // 2025-03-03T13:10:08.590Z
69
+ }
70
+ message MessageId {
71
+ string messageId = 1;
72
+ string summaryId = 2;
73
+ int32 role = 3;
74
+ }
75
+
76
+ repeated Message messages = 1;
77
+ int32 unknown2 = 2; // 1
78
+ Instruction instruction = 3;
79
+ int32 unknown4 = 4; // 1
80
+ Model model = 5;
81
+ repeated string wikiTool = 7; // one url one item
82
+ string webTool = 8; // "full search"
83
+ int32 unknown13 = 13;
84
+ CursorSetting cursorSetting = 15;
85
+ int32 unknown19 = 19; // 1
86
+ int32 unknown22 = 22; // 1
87
+ string conversationId = 23; // uuid
88
+ Metadata metadata = 26;
89
+ int32 unknown27 = 27; // 1
90
+ string unknown29 = 29;
91
+ repeated MessageId messageIds = 30;
92
+ int32 largeContext = 35; // 1
93
+ int32 unknown38 = 38; // 0
94
+ int32 chatModeEnum = 46; // 1 for ask, 2 for agent, 3 for edit
95
+ string unknown47 = 47;
96
+ int32 unknown48 = 48; // 0
97
+ int32 unknown49 = 49; // 0
98
+ int32 unknown51 = 51; // 0
99
+ int32 unknown53 = 53; // 0
100
+ string chatMode = 54;
101
+ }
102
+
103
+ Request request = 1;
104
+ }
105
+
106
+ message StreamUnifiedChatWithToolsResponse {
107
+ message Message {
108
+ message WebTool {
109
+ message WebPage {
110
+ string url = 1;
111
+ string title = 2;
112
+ string content = 3;
113
+ }
114
+ repeated WebPage webPage = 1;
115
+ }
116
+ message Unknown12 {
117
+ message Content {
118
+ string content = 1;
119
+ }
120
+ Content content = 1;
121
+ }
122
+ string content = 1;
123
+ WebTool webtool = 11;
124
+ Unknown12 unknown12 = 12;
125
+ string unknown22 = 22; // uuid
126
+ string unknown23 = 23;
127
+ string unknown27 = 27; // uuid
128
+ MessageThinking thinking = 25;
129
+ }
130
+
131
+ Message message = 2;
132
+ MessageSummary summary = 3;
133
+ }
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,944 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const { fetch, ProxyAgent, Agent } = require('undici');
4
+
5
+ const $root = require('../proto/message.js');
6
+ const { v4: uuidv4, v5: uuidv5 } = require('uuid');
7
+ const { generateCursorBody, chunkToUtf8String, generateHashed64Hex, generateCursorChecksum } = require('../utils/utils.js');
8
+ const keyManager = require('../utils/keyManager.js');
9
+ const { spawn } = require('child_process');
10
+ const path = require('path');
11
+ const admin = require('../models/admin');
12
+ const config = require('../config/config');
13
+
14
+ // 存储刷新状态的变量
15
+ let refreshStatus = {
16
+ isRunning: false,
17
+ status: 'idle', // idle, running, completed, failed
18
+ message: '',
19
+ startTime: null,
20
+ endTime: null,
21
+ error: null
22
+ };
23
+
24
+ // 检查是否已有管理员账号
25
+ router.get('/admin/check', (req, res) => {
26
+ try {
27
+ return res.json({
28
+ success: true,
29
+ exists: admin.hasAdmin()
30
+ });
31
+ } catch (error) {
32
+ console.error('检查管理员账号失败:', error);
33
+ return res.status(500).json({
34
+ success: false,
35
+ message: error.message
36
+ });
37
+ }
38
+ });
39
+
40
+ // 注册管理员
41
+ router.post('/admin/register', (req, res) => {
42
+ try {
43
+ const { username, password } = req.body;
44
+
45
+ if (!username || !password) {
46
+ return res.status(400).json({
47
+ success: false,
48
+ message: '用户名和密码不能为空'
49
+ });
50
+ }
51
+
52
+ const token = admin.register(username, password);
53
+
54
+ return res.json({
55
+ success: true,
56
+ message: '注册成功',
57
+ token
58
+ });
59
+ } catch (error) {
60
+ console.error('注册管理员失败:', error);
61
+ return res.status(400).json({
62
+ success: false,
63
+ message: error.message
64
+ });
65
+ }
66
+ });
67
+
68
+ // 管理员登录
69
+ router.post('/admin/login', (req, res) => {
70
+ try {
71
+ const { username, password } = req.body;
72
+
73
+ if (!username || !password) {
74
+ return res.status(400).json({
75
+ success: false,
76
+ message: '用户名和密码不能为空'
77
+ });
78
+ }
79
+
80
+ const token = admin.login(username, password);
81
+
82
+ return res.json({
83
+ success: true,
84
+ message: '登录成功',
85
+ token
86
+ });
87
+ } catch (error) {
88
+ console.error('登录失败:', error);
89
+ return res.status(400).json({
90
+ success: false,
91
+ message: error.message
92
+ });
93
+ }
94
+ });
95
+
96
+ // 验证token
97
+ router.get('/admin/verify', (req, res) => {
98
+ try {
99
+ const authHeader = req.headers.authorization;
100
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
101
+ return res.status(401).json({
102
+ success: false,
103
+ message: '未提供认证token'
104
+ });
105
+ }
106
+
107
+ const token = authHeader.split(' ')[1];
108
+ const result = admin.verifyToken(token);
109
+
110
+ return res.json(result);
111
+ } catch (error) {
112
+ console.error('验证token失败:', error);
113
+ return res.status(401).json({
114
+ success: false,
115
+ message: error.message
116
+ });
117
+ }
118
+ });
119
+
120
+ // 添加API key管理路由
121
+ router.post("/api-keys", async (req, res) => {
122
+ try {
123
+ const { apiKey, cookieValues } = req.body;
124
+
125
+ if (!apiKey || !cookieValues) {
126
+ return res.status(400).json({
127
+ error: 'API key and cookie values are required',
128
+ });
129
+ }
130
+
131
+ keyManager.addOrUpdateApiKey(apiKey, cookieValues);
132
+
133
+ return res.json({
134
+ success: true,
135
+ message: 'API key added or updated successfully',
136
+ });
137
+ } catch (error) {
138
+ console.error(error);
139
+ return res.status(500).json({
140
+ error: 'Internal server error',
141
+ });
142
+ }
143
+ });
144
+
145
+ // 获取所有API Keys
146
+ router.get("/api-keys", async (req, res) => {
147
+ try {
148
+ console.log('收到获取API Keys请求');
149
+ const apiKeys = keyManager.getAllApiKeys();
150
+ console.log('获取到的API Keys:', apiKeys);
151
+
152
+ const result = {
153
+ success: true,
154
+ apiKeys: apiKeys.map(apiKey => ({
155
+ key: apiKey,
156
+ cookieCount: keyManager.getAllCookiesForApiKey(apiKey).length,
157
+ })),
158
+ };
159
+ console.log('返回结果:', result);
160
+
161
+ return res.json(result);
162
+ } catch (error) {
163
+ console.error('获取API Keys失败:', error);
164
+ return res.status(500).json({
165
+ error: 'Internal server error',
166
+ message: error.message
167
+ });
168
+ }
169
+ });
170
+
171
+ // 删除API key
172
+ router.delete("/api-keys/:apiKey", async (req, res) => {
173
+ try {
174
+ const { apiKey } = req.params;
175
+
176
+ keyManager.removeApiKey(apiKey);
177
+
178
+ return res.json({
179
+ success: true,
180
+ message: 'API key removed successfully',
181
+ });
182
+ } catch (error) {
183
+ console.error(error);
184
+ return res.status(500).json({
185
+ error: 'Internal server error',
186
+ });
187
+ }
188
+ });
189
+
190
+ // 获取特定API Key的Cookie值
191
+ router.get("/api-keys/:apiKey/cookies", async (req, res) => {
192
+ try {
193
+ const { apiKey } = req.params;
194
+ console.log(`收到获取API Key ${apiKey}的Cookie值请求`);
195
+
196
+ const cookies = keyManager.getAllCookiesForApiKey(apiKey);
197
+ console.log(`API Key ${apiKey}的Cookie值:`, cookies);
198
+
199
+ return res.json({
200
+ success: true,
201
+ cookies: cookies
202
+ });
203
+ } catch (error) {
204
+ console.error(`获取API Key ${req.params.apiKey}的Cookie值失败:`, error);
205
+ return res.status(500).json({
206
+ error: 'Internal server error',
207
+ message: error.message
208
+ });
209
+ }
210
+ });
211
+
212
+ // 获取所有无效的cookie
213
+ router.get("/invalid-cookies", async (req, res) => {
214
+ try {
215
+ const invalidCookies = keyManager.getInvalidCookies();
216
+
217
+ return res.json({
218
+ success: true,
219
+ invalidCookies: Array.from(invalidCookies)
220
+ });
221
+ } catch (error) {
222
+ console.error('获取无效cookie失败:', error);
223
+ return res.status(500).json({
224
+ error: 'Internal server error',
225
+ message: error.message
226
+ });
227
+ }
228
+ });
229
+
230
+ // 清除特定的无效cookie
231
+ router.delete("/invalid-cookies/:cookie", async (req, res) => {
232
+ try {
233
+ const { cookie } = req.params;
234
+ const success = keyManager.clearInvalidCookie(cookie);
235
+
236
+ return res.json({
237
+ success: success,
238
+ message: success ? '无效cookie已清除' : '未找到指定的无效cookie'
239
+ });
240
+ } catch (error) {
241
+ console.error('清除无效cookie失败:', error);
242
+ return res.status(500).json({
243
+ error: 'Internal server error',
244
+ message: error.message
245
+ });
246
+ }
247
+ });
248
+
249
+ // 清除所有无效cookie
250
+ router.delete("/invalid-cookies", async (req, res) => {
251
+ try {
252
+ keyManager.clearAllInvalidCookies();
253
+
254
+ return res.json({
255
+ success: true,
256
+ message: '所有无效cookie已清除'
257
+ });
258
+ } catch (error) {
259
+ console.error('清除所有无效cookie失败:', error);
260
+ return res.status(500).json({
261
+ error: 'Internal server error',
262
+ message: error.message
263
+ });
264
+ }
265
+ });
266
+
267
+ router.get("/models", async (req, res) => {
268
+ try{
269
+ let bearerToken = req.headers.authorization?.replace('Bearer ', '');
270
+
271
+ // 使用keyManager获取实际的cookie
272
+ let authToken = keyManager.getCookieForApiKey(bearerToken);
273
+
274
+ if (authToken && authToken.includes('%3A%3A')) {
275
+ authToken = authToken.split('%3A%3A')[1];
276
+ }
277
+ else if (authToken && authToken.includes('::')) {
278
+ authToken = authToken.split('::')[1];
279
+ }
280
+
281
+ const checksum = req.headers['x-cursor-checksum']
282
+ ?? process.env['x-cursor-checksum']
283
+ ?? generateCursorChecksum(authToken.trim());
284
+ //const cursorClientVersion = "0.45.11"
285
+ const cursorClientVersion = "0.48.7";
286
+
287
+ const availableModelsResponse = await fetch("https://api2.cursor.sh/aiserver.v1.AiService/AvailableModels", {
288
+ method: 'POST',
289
+ headers: {
290
+ 'accept-encoding': 'gzip',
291
+ 'authorization': `Bearer ${authToken}`,
292
+ 'connect-protocol-version': '1',
293
+ 'content-type': 'application/proto',
294
+ 'user-agent': 'connect-es/1.6.1',
295
+ 'x-cursor-checksum': checksum,
296
+ 'x-cursor-client-version': cursorClientVersion,
297
+ 'x-cursor-config-version': uuidv4(),
298
+ 'x-cursor-timezone': 'Asia/Shanghai',
299
+ 'x-ghost-mode': 'true',
300
+ 'Host': 'api2.cursor.sh',
301
+ },
302
+ })
303
+ const data = await availableModelsResponse.arrayBuffer();
304
+ const buffer = Buffer.from(data);
305
+ try{
306
+ const models = $root.AvailableModelsResponse.decode(buffer).models;
307
+
308
+ return res.json({
309
+ object: "list",
310
+ data: models.map(model => ({
311
+ id: model.name,
312
+ created: Date.now(),
313
+ object: 'model',
314
+ owned_by: 'cursor'
315
+ }))
316
+ })
317
+ } catch (error) {
318
+ const text = buffer.toString('utf-8');
319
+ throw new Error(text);
320
+ }
321
+ }
322
+ catch (error) {
323
+ console.error(error);
324
+ return res.status(500).json({
325
+ error: 'Internal server error',
326
+ });
327
+ }
328
+ })
329
+
330
+ router.post('/chat/completions', async (req, res) => {
331
+ // o1开头的模型,不支持流式输出
332
+ if (req.body.model.startsWith('o1-') && req.body.stream) {
333
+ return res.status(400).json({
334
+ error: 'Model not supported stream',
335
+ });
336
+ }
337
+
338
+ try {
339
+ const { model, messages, stream = false } = req.body;
340
+ let bearerToken = req.headers.authorization?.replace('Bearer ', '');
341
+
342
+ // 使用keyManager获取实际的cookie
343
+ let authToken = keyManager.getCookieForApiKey(bearerToken);
344
+ // 保存原始cookie,用于后续可能的错误处理
345
+ const originalAuthToken = authToken;
346
+ //console.log('原始cookie:', originalAuthToken);
347
+
348
+ if (authToken && authToken.includes('%3A%3A')) {
349
+ authToken = authToken.split('%3A%3A')[1];
350
+ }
351
+ else if (authToken && authToken.includes('::')) {
352
+ authToken = authToken.split('::')[1];
353
+ }
354
+
355
+ if (!messages || !Array.isArray(messages) || messages.length === 0 || !authToken) {
356
+ return res.status(400).json({
357
+ error: 'Invalid request. Messages should be a non-empty array and authorization is required',
358
+ });
359
+ }
360
+
361
+ const checksum = req.headers['x-cursor-checksum']
362
+ ?? process.env['x-cursor-checksum']
363
+ ?? generateCursorChecksum(authToken.trim());
364
+
365
+ const sessionid = uuidv5(authToken, uuidv5.DNS);
366
+ const clientKey = generateHashed64Hex(authToken)
367
+ //const cursorClientVersion = "0.45.11"
368
+ const cursorClientVersion = "0.48.7";
369
+ // Request the AvailableModels before StreamChat.
370
+ const availableModelsResponse = await fetch("https://api2.cursor.sh/aiserver.v1.AiService/AvailableModels", {
371
+ method: 'POST',
372
+ headers: {
373
+ 'accept-encoding': 'gzip',
374
+ 'authorization': `Bearer ${authToken}`,
375
+ 'connect-protocol-version': '1',
376
+ 'content-type': 'application/proto',
377
+ 'user-agent': 'connect-es/1.6.1',
378
+ 'x-amzn-trace-id': `Root=${uuidv4()}`,
379
+ 'x-client-key': clientKey,
380
+ 'x-cursor-checksum': checksum,
381
+ 'x-cursor-client-version': cursorClientVersion,
382
+ 'x-cursor-timezone': 'Asia/Shanghai',
383
+ 'x-ghost-mode': 'true',
384
+ "x-request-id": uuidv4(),
385
+ "x-session-id": sessionid,
386
+ 'Host': 'api2.cursor.sh',
387
+ },
388
+ })
389
+
390
+ const cursorBody = generateCursorBody(messages, model);
391
+
392
+ // 添加代理支持
393
+ const dispatcher = config.proxy && config.proxy.enabled
394
+ ? new ProxyAgent(config.proxy.url, { allowH2: true })
395
+ : new Agent({ allowH2: true });
396
+
397
+ // 更新接口地址为 StreamUnifiedChatWithTools
398
+ const response = await fetch('https://api2.cursor.sh/aiserver.v1.ChatService/StreamUnifiedChatWithTools', {
399
+ method: 'POST',
400
+ headers: {
401
+ 'authorization': `Bearer ${authToken}`,
402
+ 'connect-accept-encoding': 'gzip',
403
+ 'connect-content-encoding': 'gzip',
404
+ 'connect-protocol-version': '1',
405
+ 'content-type': 'application/connect+proto',
406
+ 'user-agent': 'connect-es/1.6.1',
407
+ 'x-amzn-trace-id': `Root=${uuidv4()}`,
408
+ 'x-client-key': clientKey,
409
+ 'x-cursor-checksum': checksum,
410
+ 'x-cursor-client-version': cursorClientVersion,
411
+ 'x-cursor-config-version': uuidv4(),
412
+ 'x-cursor-timezone': 'Asia/Shanghai',
413
+ 'x-ghost-mode': 'true',
414
+ 'x-request-id': uuidv4(),
415
+ 'x-session-id': sessionid,
416
+ 'Host': 'api2.cursor.sh',
417
+ },
418
+ body: cursorBody,
419
+ dispatcher: dispatcher,
420
+ timeout: {
421
+ connect: 5000,
422
+ read: 30000
423
+ }
424
+ });
425
+
426
+ // 处理响应
427
+ if (stream) {
428
+ res.setHeader('Content-Type', 'text/event-stream');
429
+ res.setHeader('Cache-Control', 'no-cache');
430
+ res.setHeader('Connection', 'keep-alive');
431
+
432
+ const responseId = `chatcmpl-${uuidv4()}`;
433
+
434
+
435
+ try {
436
+ let responseEnded = false; // 添加标志,标记响应是否已结束
437
+
438
+ for await (const chunk of response.body) {
439
+ // 如果响应已结束,不再处理后续数据
440
+ if (responseEnded) {
441
+ continue;
442
+ }
443
+
444
+ let text = chunkToUtf8String(chunk);
445
+ // 输出完整的text内容和类型,便于调试
446
+ //console.log("收到的响应:", typeof text, text && typeof text === 'object' ? JSON.stringify(text) : text);
447
+
448
+
449
+ // 检查是否返回了错误对象
450
+ if (text && typeof text === 'object' && text.error) {
451
+ // 检查是否包含特定的无效cookie错误信息
452
+ const errorStr = typeof text.error === 'string' ? text.error : JSON.stringify(text.error);
453
+
454
+ // 处理错误并获取结果
455
+ const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken);
456
+
457
+ // 如果是需要移除的cookie,从API Key中移除
458
+ if (errorResult.shouldRemoveCookie) {
459
+ const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken);
460
+ console.log(`Cookie移除${removed ? '成功' : '失败'}`);
461
+
462
+ // 如果成功移除,在错误消息中添加明确提示
463
+ if (removed) {
464
+ errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\n\n${errorResult.message}`;
465
+ }
466
+ }
467
+
468
+ // 返回错误信息给客户端,作为assistant消息
469
+ res.write(
470
+ `data: ${JSON.stringify({
471
+ id: responseId,
472
+ object: 'chat.completion.chunk',
473
+ created: Math.floor(Date.now() / 1000),
474
+ model: req.body.model,
475
+ choices: [
476
+ {
477
+ index: 0,
478
+ delta: {
479
+ content: errorResult.message,
480
+ },
481
+ },
482
+ ],
483
+ })}\n\n`
484
+ );
485
+
486
+ res.write('data: [DONE]\n\n');
487
+ responseEnded = true; // 标记响应已结束
488
+ break; // 跳出循环,不再处理后续数据
489
+ }
490
+
491
+ if (text && text.length > 0) {
492
+ res.write(
493
+ `data: ${JSON.stringify({
494
+ id: responseId,
495
+ object: 'chat.completion.chunk',
496
+ created: Math.floor(Date.now() / 1000),
497
+ model: req.body.model,
498
+ choices: [
499
+ {
500
+ index: 0,
501
+ delta: {
502
+ content: text,
503
+ },
504
+ },
505
+ ],
506
+ })}\n\n`
507
+ );
508
+ }
509
+ }
510
+
511
+ // 只有在响应尚未结束的情况下,才发送结束标记
512
+ if (!responseEnded) {
513
+ res.write('data: [DONE]\n\n');
514
+ res.end();
515
+ }
516
+ } catch (streamError) {
517
+ console.error('Stream error:', streamError);
518
+ // 确保在发送错误信息前检查响应是否已结束
519
+ if (!res.writableEnded) {
520
+ if (streamError.name === 'TimeoutError') {
521
+ // 将超时错误作为assistant消息发送
522
+ const errorMessage = `⚠️ 请求超时 ⚠️\n\n错误:服务器响应超时,请稍后重试。`;
523
+ res.write(
524
+ `data: ${JSON.stringify({
525
+ id: responseId,
526
+ object: 'chat.completion.chunk',
527
+ created: Math.floor(Date.now() / 1000),
528
+ model: req.body.model,
529
+ choices: [
530
+ {
531
+ index: 0,
532
+ delta: {
533
+ content: errorMessage,
534
+ },
535
+ },
536
+ ],
537
+ })}\n\n`
538
+ );
539
+ } else {
540
+ // 将处理错误作为assistant消息发送
541
+ const errorMessage = `⚠️ 处理错误 ⚠️\n\n错误:流处理出错,请稍后重试。\n\n${streamError.message || ''}`;
542
+ res.write(
543
+ `data: ${JSON.stringify({
544
+ id: responseId,
545
+ object: 'chat.completion.chunk',
546
+ created: Math.floor(Date.now() / 1000),
547
+ model: req.body.model,
548
+ choices: [
549
+ {
550
+ index: 0,
551
+ delta: {
552
+ content: errorMessage,
553
+ },
554
+ },
555
+ ],
556
+ })}\n\n`
557
+ );
558
+ }
559
+ res.write('data: [DONE]\n\n');
560
+ res.end();
561
+ }
562
+ }
563
+ } else {
564
+ try {
565
+ let text = '';
566
+ let responseEnded = false; // 添加标志,标记响应是否已结束
567
+
568
+ for await (const chunk of response.body) {
569
+ // 如果响应已结束,不再处理后续数据
570
+ if (responseEnded) {
571
+ continue;
572
+ }
573
+
574
+ const chunkText = chunkToUtf8String(chunk);
575
+ // 输出完整的chunkText内容和类型,便于调试
576
+ //console.log("收到的非流式响应:", typeof chunkText, chunkText && typeof chunkText === 'object' ? JSON.stringify(chunkText) : chunkText);
577
+
578
+ // 检查是否返回了错误对象
579
+ if (chunkText && typeof chunkText === 'object' && chunkText.error) {
580
+ //console.error('检测到错误响应:', chunkText.error);
581
+
582
+ // 检查是否包含特定的无效cookie错误信息
583
+ const errorStr = typeof chunkText.error === 'string' ? chunkText.error : JSON.stringify(chunkText.error);
584
+
585
+ // 处理错误并获取结果
586
+ const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken);
587
+
588
+ // 如果是需要移除的cookie,从API Key中移除
589
+ if (errorResult.shouldRemoveCookie) {
590
+ const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken);
591
+ console.log(`Cookie移除${removed ? '成功' : '失败'}`);
592
+
593
+ // 如果成功移除,在错误消息中添加明确提示
594
+ if (removed) {
595
+ errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\n\n${errorResult.message}`;
596
+ }
597
+ }
598
+
599
+ // 无效cookie错误,格式化为assistant消息
600
+ res.json({
601
+ id: `chatcmpl-${uuidv4()}`,
602
+ object: 'chat.completion',
603
+ created: Math.floor(Date.now() / 1000),
604
+ model,
605
+ choices: [
606
+ {
607
+ index: 0,
608
+ message: {
609
+ role: 'assistant',
610
+ content: errorResult.message,
611
+ },
612
+ finish_reason: 'stop',
613
+ },
614
+ ],
615
+ usage: {
616
+ prompt_tokens: 0,
617
+ completion_tokens: 0,
618
+ total_tokens: 0,
619
+ },
620
+ });
621
+
622
+ responseEnded = true; // 标记响应已结束
623
+ break; // 跳出循环,不再处理后续数据
624
+ }
625
+
626
+ // 正常文本,添加到结果中
627
+ if (chunkText && typeof chunkText === 'string') {
628
+ text += chunkText;
629
+ }
630
+ }
631
+
632
+ // 只有在响应尚未结束的情况下,才处理和返回结果
633
+ if (!responseEnded) {
634
+ // 对解析后的字符串进行进一步处理
635
+ text = text.replace(/^.*<\|END_USER\|>/s, '');
636
+ text = text.replace(/^\n[a-zA-Z]?/, '').trim();
637
+ // console.log(text)
638
+
639
+ res.json({
640
+ id: `chatcmpl-${uuidv4()}`,
641
+ object: 'chat.completion',
642
+ created: Math.floor(Date.now() / 1000),
643
+ model,
644
+ choices: [
645
+ {
646
+ index: 0,
647
+ message: {
648
+ role: 'assistant',
649
+ content: text,
650
+ },
651
+ finish_reason: 'stop',
652
+ },
653
+ ],
654
+ usage: {
655
+ prompt_tokens: 0,
656
+ completion_tokens: 0,
657
+ total_tokens: 0,
658
+ },
659
+ });
660
+ }
661
+ } catch (error) {
662
+ console.error('Non-stream error:', error);
663
+ // 确保在发送错误信息前检查响应是否已结束
664
+ if (!res.headersSent) {
665
+ if (error.name === 'TimeoutError') {
666
+ // 使用统一的错误格式
667
+ const errorMessage = `⚠️ 请求超时 ⚠️\n\n错误:服务器响应超时,请稍后重试。`;
668
+ return res.json({
669
+ id: `chatcmpl-${uuidv4()}`,
670
+ object: 'chat.completion',
671
+ created: Math.floor(Date.now() / 1000),
672
+ model: req.body.model || 'unknown',
673
+ choices: [
674
+ {
675
+ index: 0,
676
+ message: {
677
+ role: 'assistant',
678
+ content: errorMessage,
679
+ },
680
+ finish_reason: 'stop',
681
+ },
682
+ ],
683
+ usage: {
684
+ prompt_tokens: 0,
685
+ completion_tokens: 0,
686
+ total_tokens: 0,
687
+ },
688
+ });
689
+ }
690
+ throw error;
691
+ }
692
+ }
693
+ }
694
+ } catch (error) {
695
+ console.error('Error:', error);
696
+ if (!res.headersSent) {
697
+ const errorText = error.name === 'TimeoutError' ? '请求超时' : '服务器内部错误';
698
+
699
+ if (req.body.stream) {
700
+ // 流式响应格式的错误
701
+ const responseId = `chatcmpl-${uuidv4()}`;
702
+ // 添加清晰的错误提示
703
+ const errorMessage = `⚠️ 请求失败 ⚠️\n\n错误:${errorText},请稍后重试。\n\n${error.message || ''}`;
704
+ res.write(
705
+ `data: ${JSON.stringify({
706
+ id: responseId,
707
+ object: 'chat.completion.chunk',
708
+ created: Math.floor(Date.now() / 1000),
709
+ model: req.body.model || 'unknown',
710
+ choices: [
711
+ {
712
+ index: 0,
713
+ delta: {
714
+ content: errorMessage,
715
+ },
716
+ },
717
+ ],
718
+ })}\n\n`
719
+ );
720
+ res.write('data: [DONE]\n\n');
721
+ res.end();
722
+ } else {
723
+ // 非流式响应格式的错误
724
+ // 添加清晰的错误提示
725
+ const errorMessage = `⚠️ 请求失败 ⚠️\n\n错误:${errorText},请稍后重试。\n\n${error.message || ''}`;
726
+ res.json({
727
+ id: `chatcmpl-${uuidv4()}`,
728
+ object: 'chat.completion',
729
+ created: Math.floor(Date.now() / 1000),
730
+ model: req.body.model || 'unknown',
731
+ choices: [
732
+ {
733
+ index: 0,
734
+ message: {
735
+ role: 'assistant',
736
+ content: errorMessage,
737
+ },
738
+ finish_reason: 'stop',
739
+ },
740
+ ],
741
+ usage: {
742
+ prompt_tokens: 0,
743
+ completion_tokens: 0,
744
+ total_tokens: 0,
745
+ },
746
+ });
747
+ }
748
+ }
749
+ }
750
+ });
751
+
752
+ // 触发Cookie刷新
753
+ router.post("/refresh-cookies", async (req, res) => {
754
+ try {
755
+ // 如果已经有刷新进程在运行,则返回错误
756
+ if (refreshStatus.isRunning) {
757
+ return res.status(409).json({
758
+ success: false,
759
+ message: '已有刷新进程在运行,请等待完成后再试'
760
+ });
761
+ }
762
+
763
+ // 获取请求参数
764
+ const apiKey = req.query.apiKey || '';
765
+
766
+ // 重置刷新状态
767
+ refreshStatus = {
768
+ isRunning: true,
769
+ status: 'running',
770
+ message: '正在启动刷新进程...',
771
+ startTime: new Date(),
772
+ endTime: null,
773
+ error: null
774
+ };
775
+
776
+ console.log(`收到刷新Cookie请求,API Key: ${apiKey || '所有'}`);
777
+
778
+ // 构建命令行参数
779
+ const args = [];
780
+ if (apiKey) {
781
+ args.push(apiKey);
782
+ }
783
+
784
+ // 获取auto-refresh-cookies.js的绝对路径
785
+ const scriptPath = path.resolve(__dirname, '../../auto-refresh-cookies.js');
786
+
787
+ // 启动子进程执行刷新脚本
788
+ const refreshProcess = spawn('node', [scriptPath, ...args], {
789
+ stdio: ['ignore', 'pipe', 'pipe']
790
+ });
791
+
792
+ // 收集输出
793
+ let output = '';
794
+
795
+ refreshProcess.stdout.on('data', (data) => {
796
+ const text = data.toString();
797
+ output += text;
798
+ console.log(`刷新进程输出: ${text}`);
799
+
800
+ // 更新状态消息
801
+ if (text.includes('开始自动刷新')) {
802
+ refreshStatus.message = '正在刷新Cookie...';
803
+ } else if (text.includes('刷新结果:')) {
804
+ refreshStatus.message = text.trim();
805
+ }
806
+ });
807
+
808
+ refreshProcess.stderr.on('data', (data) => {
809
+ const text = data.toString();
810
+ output += text;
811
+ console.error(`刷新进程错误: ${text}`);
812
+
813
+ // 更新错误信息
814
+ refreshStatus.error = text.trim();
815
+ refreshStatus.message = `发生错误: ${text.trim()}`;
816
+ });
817
+
818
+ refreshProcess.on('close', (code) => {
819
+ console.log(`刷新进程退出,代码: ${code}`);
820
+
821
+ refreshStatus.isRunning = false;
822
+ refreshStatus.endTime = new Date();
823
+
824
+ if (code === 0) {
825
+ refreshStatus.status = 'completed';
826
+
827
+ // 提取成功信息
828
+ const successMatch = output.match(/成功刷新 (\d+) 个/);
829
+ if (successMatch) {
830
+ refreshStatus.message = `成功刷新 ${successMatch[1]} 个API Key的Cookie`;
831
+ } else {
832
+ refreshStatus.message = '刷新完成';
833
+ }
834
+
835
+ // 子进程执行完成后,重新初始化API Keys来加载新的Cookie
836
+ try {
837
+ const keyManager = require('../utils/keyManager');
838
+ console.log('子进程刷新Cookie完成,重新初始化主进程中的API Keys...');
839
+ keyManager.initializeApiKeys();
840
+ console.log('主进程API Keys重新加载完成');
841
+ } catch (initError) {
842
+ console.error('重新初始化API Keys失败:', initError);
843
+ }
844
+ } else {
845
+ refreshStatus.status = 'failed';
846
+ refreshStatus.message = refreshStatus.error || '刷新失败,请查看服务器日志';
847
+ }
848
+ });
849
+
850
+ // 立即返回响应,不等待刷新完成
851
+ return res.json({
852
+ success: true,
853
+ message: '刷新请求已接受,正在后台处理'
854
+ });
855
+ } catch (error) {
856
+ console.error('触发刷新Cookie失败:', error);
857
+
858
+ // 更新刷新状态
859
+ refreshStatus.isRunning = false;
860
+ refreshStatus.status = 'failed';
861
+ refreshStatus.endTime = new Date();
862
+ refreshStatus.error = error.message;
863
+ refreshStatus.message = `触发刷新失败: ${error.message}`;
864
+
865
+ return res.status(500).json({
866
+ success: false,
867
+ message: `触发刷新失败: ${error.message}`
868
+ });
869
+ }
870
+ });
871
+
872
+ // 查询Cookie刷新状态
873
+ router.get("/refresh-status", (req, res) => {
874
+ try {
875
+ // 返回当前刷新状态
876
+ return res.json({
877
+ success: true,
878
+ data: {
879
+ ...refreshStatus,
880
+ isRunning: refreshStatus.isRunning || false,
881
+ status: refreshStatus.status || 'unknown',
882
+ message: refreshStatus.message || '未触发刷新',
883
+ startTime: refreshStatus.startTime || null,
884
+ endTime: refreshStatus.endTime || null
885
+ }
886
+ });
887
+ } catch (error) {
888
+ console.error('获取刷新状态失败:', error);
889
+ return res.status(500).json({
890
+ success: false,
891
+ message: `获取刷新状态失败: ${error.message}`
892
+ });
893
+ }
894
+ });
895
+
896
+ // 在文件末尾添加错误处理函数
897
+ function handleCursorError(errorStr, bearerToken, originalAuthToken) {
898
+ let message = '';
899
+ let shouldRemoveCookie = false;
900
+
901
+ if (errorStr.includes('Not logged in')) {
902
+ // 更明确的错误日志
903
+ if (originalAuthToken === bearerToken) {
904
+ console.error(`检测到API Key "${bearerToken}" 中没有可用Cookie,正在尝试以向后兼容模式使用API Key本身`);
905
+ message = `错误:API Key "${bearerToken}" 中没有可用的Cookie。请添加有效的Cookie到此API Key,或使用其他有效的API Key。\n\n详细信息:${errorStr}`;
906
+ } else {
907
+ console.error('检测到无效cookie:', originalAuthToken);
908
+ message = `错误:Cookie无效或已过期,请更新Cookie。\n\n详细信息:${errorStr}`;
909
+ }
910
+ shouldRemoveCookie = true;
911
+ } else if (errorStr.includes('You\'ve reached your trial request limit')) {
912
+ console.error('检测到额度用尽cookie:', originalAuthToken);
913
+ message = `错误:Cookie使用额度已用完,请更换Cookie或等待刷新。\n\n详细信息:${errorStr}`;
914
+ shouldRemoveCookie = true;
915
+ } else if (errorStr.includes('User is unauthorized')) {
916
+ console.error('检测到未授权cookie:', originalAuthToken);
917
+ message = `错误:Cookie已被封禁或失效,请更换Cookie。\n\n详细信息:${errorStr}`;
918
+ shouldRemoveCookie = true;
919
+ } else if (errorStr.includes('suspicious activity checks')) {
920
+ console.error('检测到IP黑名单:', originalAuthToken);
921
+ message = `错误:IP可能被列入黑名单,请尝试更换网络环境或使用代理。\n\n详细信息:${errorStr}`;
922
+ shouldRemoveCookie = false;
923
+ } else if (errorStr.includes('Too many computers')) {
924
+ console.error('检测到账户暂时被封禁:', originalAuthToken);
925
+ message = `错误:账户因在多台设备登录而暂时被封禁,请稍后再试或更换账户。\n\n详细信息:${errorStr}`;
926
+ shouldRemoveCookie = true;
927
+ } else if (errorStr.includes('Login expired') || errorStr.includes('login expired')) {
928
+ console.error('检测到登录过期cookie:', originalAuthToken);
929
+ message = `错误:Cookie登录已过期,请更新Cookie。\n\n详细信息:${errorStr}`;
930
+ shouldRemoveCookie = true;
931
+ } else {
932
+ // 非Cookie相关错误
933
+ console.error('检测到其他错误:', errorStr);
934
+ message = `错误:请求失败。\n\n详细信息:${errorStr}`;
935
+ shouldRemoveCookie = false;
936
+ }
937
+
938
+ return {
939
+ message,
940
+ shouldRemoveCookie
941
+ };
942
+ }
943
+
944
+ module.exports = router;
src/utils/cookieRefresher.js ADDED
@@ -0,0 +1,817 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ if (!fs.existsSync(csvFilePath)) {
485
+ console.error(`CSV文件不存在: ${csvFilePath}`);
486
+ return [];
487
+ }
488
+
489
+ // 读取文件内容并处理可能的换行符
490
+ let fileContent = fs.readFileSync(csvFilePath, 'utf8');
491
+ fileContent = fileContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
492
+
493
+ // 首先尝试直接从文件内容中提取所有可能的cookie
494
+ const cookies = [];
495
+
496
+ // 1. 检查是否有JWT格式的token (新格式)
497
+ const jwtRegex = /ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
498
+ const jwtMatches = fileContent.match(jwtRegex);
499
+
500
+ if (jwtMatches && jwtMatches.length > 0) {
501
+ console.log(`直接从文件内容中提取到 ${jwtMatches.length} 个JWT token格式的Cookie`);
502
+ jwtMatches.forEach(match => {
503
+ if (!cookies.includes(match)) {
504
+ cookies.push(match);
505
+ }
506
+ });
507
+ }
508
+
509
+ // 2. 检查是否有旧格式的cookie
510
+ if (fileContent.includes('user_')) {
511
+ console.log('文件包含旧格式cookie标识"user_"');
512
+
513
+ // 使用旧的提取函数尝试提取
514
+ try {
515
+ const oldFormatCookies = await extractCookiesFromCsv(csvFilePath);
516
+ if (oldFormatCookies && oldFormatCookies.length > 0) {
517
+ console.log(`通过提取模块获取到 ${oldFormatCookies.length} 个cookie`);
518
+ oldFormatCookies.forEach(cookie => {
519
+ if (!cookies.includes(cookie)) {
520
+ cookies.push(cookie);
521
+ }
522
+ });
523
+ }
524
+ } catch (e) {
525
+ console.warn('通过提取模块获取cookie失败:', e.message);
526
+ }
527
+ }
528
+
529
+ // 3. 如果找到了cookie,返回结果
530
+ if (cookies.length > 0) {
531
+ const newFormatCount = cookies.filter(c => c.startsWith('ey')).length;
532
+ const oldFormatCount = cookies.filter(c => c.includes('%3A%3A')).length;
533
+
534
+ console.log(`总共找到 ${cookies.length} 个cookie`);
535
+ console.log(`新格式cookie(ey开头): ${newFormatCount}个`);
536
+ console.log(`旧格式cookie(包含%3A%3A): ${oldFormatCount}个`);
537
+ console.log(`其他格式cookie: ${cookies.length - newFormatCount - oldFormatCount}个`);
538
+
539
+ return cookies;
540
+ }
541
+
542
+ console.warn(`未能从文件中提取到任何cookie (尝试 ${attempt}/${maxExtractAttempts})`);
543
+ } catch (error) {
544
+ console.error(`从CSV文件提取cookies时出错 (尝试 ${attempt}/${maxExtractAttempts}):`, error);
545
+ }
546
+
547
+ attempt++;
548
+ if (attempt <= maxExtractAttempts) {
549
+ console.log(`等待5秒后重试...`);
550
+ await new Promise(resolve => setTimeout(resolve, 5000));
551
+ }
552
+ }
553
+
554
+ console.error(`在 ${maxExtractAttempts} 次尝试后未能从CSV文件提取到cookies`);
555
+ return [];
556
+ }
557
+
558
+ // 将新的有效cookie添加到系统中
559
+ function addNewCookiesToSystem(apiKey, newCookies) {
560
+ try {
561
+ console.log(`准备添加 ${newCookies.length} 个新cookie到系统中`);
562
+
563
+ // 获取当前的cookies
564
+ const currentCookies = keyManager.getAllCookiesForApiKey(apiKey) || [];
565
+ console.log(`当前API密钥 ${apiKey} 有 ${currentCookies.length} 个cookies`);
566
+
567
+ // 获取无效的cookies
568
+ const invalidCookies = keyManager.getInvalidCookies() || [];
569
+ console.log(`系统中有 ${invalidCookies.length || 0} 个无效cookies`);
570
+
571
+ // 过滤出新的有效cookie
572
+ let newValidCookies = [];
573
+
574
+ // 检查invalidCookies的类型并相应处理
575
+ if (invalidCookies instanceof Set) {
576
+ newValidCookies = newCookies.filter(cookie =>
577
+ !currentCookies.includes(cookie) && !invalidCookies.has(cookie)
578
+ );
579
+ } else if (Array.isArray(invalidCookies)) {
580
+ newValidCookies = newCookies.filter(cookie =>
581
+ !currentCookies.includes(cookie) && !invalidCookies.includes(cookie)
582
+ );
583
+ } else if (invalidCookies && typeof invalidCookies === 'object') {
584
+ // 如果是普通对象,检查cookie是否作为键存在
585
+ newValidCookies = newCookies.filter(cookie =>
586
+ !currentCookies.includes(cookie) && !(cookie in invalidCookies)
587
+ );
588
+ } else {
589
+ // 如果invalidCookies不是预期的类型,只过滤当前cookies
590
+ newValidCookies = newCookies.filter(cookie => !currentCookies.includes(cookie));
591
+ }
592
+
593
+ console.log(`过滤后有 ${newValidCookies.length} 个新的有效cookies`);
594
+
595
+ // 验证cookie是否完整
596
+ const validatedCookies = newValidCookies.filter(cookie => {
597
+ // 检查是否是新格式的JWT token (ey开头)
598
+ if (cookie.startsWith('ey') && cookie.includes('.')) {
599
+ const parts = cookie.split('.');
600
+ // 检查JWT是否包含三个部分
601
+ if (parts.length !== 3) {
602
+ console.warn(`跳过不完整的JWT cookie (新格式): ${cookie}`);
603
+ return false;
604
+ }
605
+ return true;
606
+ }
607
+ // 检查旧格式cookie是否包含JWT的三个部分
608
+ else if (cookie.includes('%3A%3A')) {
609
+ const parts = cookie.split('%3A%3A');
610
+ if (parts.length === 2) {
611
+ const jwt = parts[1];
612
+ // 检查JWT是否包含点(表示JWT的三个部分)
613
+ if (!jwt.includes('.') || jwt.split('.').length !== 3) {
614
+ console.warn(`跳过不完整的cookie (旧格式): ${cookie}`);
615
+ return false;
616
+ }
617
+ }
618
+ }
619
+ return true;
620
+ });
621
+
622
+ console.log(`验证完整性后有 ${validatedCookies.length} 个有效cookies`);
623
+
624
+ if (validatedCookies.length > 0) {
625
+ // 添加新的有效cookie到系统
626
+ keyManager.addOrUpdateApiKey(apiKey, [...currentCookies, ...validatedCookies]);
627
+ console.log(`成功添加 ${validatedCookies.length} 个新cookie到API密钥 ${apiKey}`);
628
+ return validatedCookies.length; // 返回添加的cookie数量
629
+ } else {
630
+ console.log(`没有新的有效cookie需要添加到API密钥 ${apiKey}`);
631
+ return 0; // 没有添加cookie,返回0
632
+ }
633
+ } catch (error) {
634
+ console.error('添加新cookie到系统时出错:', error);
635
+ return 0; // 出错时返回0
636
+ }
637
+ }
638
+
639
+ // 清理临时文件
640
+ function cleanupTempFiles() {
641
+ try {
642
+ console.log('开始清理临时文件...');
643
+
644
+ // 清理下载目录
645
+ if (fs.existsSync(DOWNLOAD_DIR)) {
646
+ fs.readdirSync(DOWNLOAD_DIR).forEach(file => {
647
+ fs.unlinkSync(path.join(DOWNLOAD_DIR, file));
648
+ });
649
+ }
650
+
651
+ // 清理解压目录
652
+ if (fs.existsSync(EXTRACT_DIR)) {
653
+ fs.readdirSync(EXTRACT_DIR).forEach(file => {
654
+ fs.unlinkSync(path.join(EXTRACT_DIR, file));
655
+ });
656
+ }
657
+
658
+ console.log('临时文件清理完成');
659
+ } catch (error) {
660
+ console.error('清理临时文件失败:', error);
661
+ }
662
+ }
663
+
664
+ // 检查 API Key 是否需要补充 Cookie
665
+ function checkApiKeyNeedRefresh(apiKey, minCookieCount = config.refresh.minCookieCount) {
666
+ const cookies = keyManager.getAllCookiesForApiKey(apiKey);
667
+ return cookies.length < minCookieCount;
668
+ }
669
+
670
+ // 将��有cookie全部设为无效并从API Key中移除
671
+ function markExistingCookiesAsInvalid(apiKey) {
672
+ try {
673
+ // 获取当前API Key的所有cookie
674
+ const currentCookies = keyManager.getAllCookiesForApiKey(apiKey) || [];
675
+ console.log(`正在将API Key ${apiKey} 的 ${currentCookies.length} 个现有cookie标记为无效...`);
676
+
677
+ // 如果没有cookie,直接返回
678
+ if (currentCookies.length === 0) {
679
+ console.log(`API Key ${apiKey} 没有现有cookie,无需标记为无效`);
680
+ return 0;
681
+ }
682
+
683
+ // 获取无效cookie列表
684
+ const invalidCookies = keyManager.getInvalidCookies();
685
+ let markedCount = 0;
686
+
687
+ // 遍历cookie并添加到无效列表
688
+ for (const cookie of currentCookies) {
689
+ // 将cookie添加到无效集合中
690
+ if (invalidCookies instanceof Set) {
691
+ invalidCookies.add(cookie);
692
+ }
693
+ markedCount++;
694
+ }
695
+
696
+ // 保存无效cookie到文件
697
+ keyManager.saveInvalidCookiesToFile();
698
+
699
+ // 清空当前API Key的cookie列表
700
+ keyManager.addOrUpdateApiKey(apiKey, []);
701
+
702
+ // 保存更新后的API Keys
703
+ keyManager.saveApiKeysToFile();
704
+
705
+ console.log(`已将API Key ${apiKey} 的 ${markedCount} 个cookie标记为无效并从API Key中移除`);
706
+ return markedCount;
707
+ } catch (error) {
708
+ console.error(`标记现有cookie为无效时出错:`, error);
709
+ return 0;
710
+ }
711
+ }
712
+
713
+ // 主函数:自动刷新 Cookie
714
+ async function autoRefreshCookies(apiKey, minCookieCount = config.refresh.minCookieCount) {
715
+ console.log(`开始自动刷新 Cookie,目标 API Key: ${apiKey},最小 Cookie 数量: ${minCookieCount}`);
716
+
717
+ try {
718
+ // 检查是否需要刷新
719
+ if (!checkApiKeyNeedRefresh(apiKey, minCookieCount)) {
720
+ console.log(`API Key ${apiKey} 的 Cookie 数量足够,不需要刷新`);
721
+ return {
722
+ success: true,
723
+ message: '当前 Cookie 数量足够,不需要刷新',
724
+ refreshed: 0
725
+ };
726
+ }
727
+
728
+ // 获取最新的 Artifact
729
+ const artifact = await getLatestArtifact();
730
+ if (!artifact) {
731
+ return {
732
+ success: false,
733
+ message: '获取 Artifact 失败',
734
+ refreshed: 0
735
+ };
736
+ }
737
+
738
+ // 下载 Artifact
739
+ const zipFilePath = await downloadArtifact(artifact);
740
+ if (!zipFilePath) {
741
+ return {
742
+ success: false,
743
+ message: '下载 Artifact 失败',
744
+ refreshed: 0
745
+ };
746
+ }
747
+
748
+ // 解压 Artifact
749
+ const csvFilePath = await extractArtifact(zipFilePath);
750
+ if (!csvFilePath) {
751
+ return {
752
+ success: false,
753
+ message: '解压 Artifact 失败',
754
+ refreshed: 0
755
+ };
756
+ }
757
+
758
+ // 提取 Cookie
759
+ const cookies = await extractCookiesFromCsvFile(csvFilePath);
760
+ if (cookies.length === 0) {
761
+ return {
762
+ success: false,
763
+ message: '没有找到有效的 Cookie',
764
+ refreshed: 0
765
+ };
766
+ }
767
+
768
+ // 分析提取到的cookie格式
769
+ const newFormatCookies = cookies.filter(cookie => cookie.startsWith('ey'));
770
+ const oldFormatCookies = cookies.filter(cookie => cookie.includes('%3A%3A'));
771
+ console.log(`提取到 ${newFormatCookies.length} 个新格式cookie(ey开头)`);
772
+ console.log(`提取到 ${oldFormatCookies.length} 个旧格式cookie(包含%3A%3A)`);
773
+
774
+ // 根据配置决定是否将现有cookie标记为无效
775
+ const refreshMode = process.env.COOKIE_REFRESH_MODE || 'append';
776
+
777
+ if (refreshMode === 'replace') {
778
+ // 将现有cookie标记为无效并从API Key中移除
779
+ console.log('使用替换模式: 将现有cookie标记为无效');
780
+ markExistingCookiesAsInvalid(apiKey);
781
+ } else {
782
+ console.log('使用追加模式: 保留现有cookie,只添加新cookie');
783
+ }
784
+
785
+ // 添加新的 Cookie 到系统
786
+ const addedCount = addNewCookiesToSystem(apiKey, cookies);
787
+
788
+ // 清理临时文件
789
+ cleanupTempFiles();
790
+
791
+ return {
792
+ success: true,
793
+ message: `成功添加 ${addedCount} 个新 Cookie (新格式: ${newFormatCookies.length}, 旧格式: ${oldFormatCookies.length})`,
794
+ refreshed: addedCount
795
+ };
796
+ } catch (error) {
797
+ console.error('自动刷新 Cookie 失败:', error);
798
+ return {
799
+ success: false,
800
+ message: `刷新失败: ${error.message}`,
801
+ refreshed: 0
802
+ };
803
+ }
804
+ }
805
+
806
+ module.exports = {
807
+ autoRefreshCookies,
808
+ checkApiKeyNeedRefresh,
809
+ getLatestArtifact,
810
+ downloadArtifact,
811
+ extractArtifact,
812
+ extractCookiesFromCsvFile,
813
+ addNewCookiesToSystem,
814
+ cleanupTempFiles,
815
+ triggerWorkflow,
816
+ markExistingCookiesAsInvalid
817
+ };
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,269 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ // 首先尝试直接从文件内容中提取所有可能的cookie
30
+ const cookies = [];
31
+
32
+ // 检查是否有JWT格式的token (新格式)
33
+ const jwtRegex = /ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
34
+ const jwtMatches = fileContent.match(jwtRegex);
35
+
36
+ if (jwtMatches && jwtMatches.length > 0) {
37
+ console.log(`直接从文件内容中提取到 ${jwtMatches.length} 个JWT token格式的Cookie`);
38
+ jwtMatches.forEach(match => {
39
+ if (!cookies.includes(match)) {
40
+ cookies.push(match);
41
+ }
42
+ });
43
+ }
44
+
45
+ // 检查文件内容是否包含关键字
46
+ const hasTokenKeyword = fileContent.includes('token');
47
+ const hasUserPrefix = fileContent.includes('user_');
48
+ console.log(`文件包含"token"关键字: ${hasTokenKeyword}`);
49
+ console.log(`文件包含"user_"前缀: ${hasUserPrefix}`);
50
+
51
+ // 如果文件包含user_前缀,尝试提取旧格式cookie
52
+ if (hasUserPrefix) {
53
+ const oldFormatCookies = extractCookiesFromText(fileContent);
54
+ if (oldFormatCookies.length > 0) {
55
+ console.log(`从文件内容中提取到 ${oldFormatCookies.length} 个旧格式Cookie`);
56
+ oldFormatCookies.forEach(cookie => {
57
+ if (!cookies.includes(cookie)) {
58
+ cookies.push(cookie);
59
+ }
60
+ });
61
+ }
62
+ }
63
+
64
+ // 如果已经找到cookie,返回结果
65
+ if (cookies.length > 0) {
66
+ console.log(`总共提取到 ${cookies.length} 个Cookie`);
67
+ return resolve(validateCookies(cookies));
68
+ }
69
+
70
+ // 使用csv-parser解析CSV文件
71
+ const possibleTokenFields = ['token', 'cookie', 'value', 'Token', 'Cookie', 'Value', 'jwt', 'JWT'];
72
+
73
+ fs.createReadStream(csvFilePath)
74
+ .pipe(csv())
75
+ .on('data', (row) => {
76
+ // 检查所有可能的字段名
77
+ for (const field of possibleTokenFields) {
78
+ if (row[field]) {
79
+ // 检查是否是JWT格式
80
+ if (row[field].startsWith('ey') && row[field].includes('.')) {
81
+ if (!cookies.includes(row[field])) {
82
+ cookies.push(row[field]);
83
+ }
84
+ break;
85
+ }
86
+ // 检查是否是旧格式
87
+ else if (row[field].includes('user_')) {
88
+ if (!cookies.includes(row[field])) {
89
+ cookies.push(row[field]);
90
+ }
91
+ break;
92
+ }
93
+ }
94
+ }
95
+
96
+ // 如果没有找到预定义的字段,遍历所有字段
97
+ if (cookies.length === 0) {
98
+ for (const field in row) {
99
+ if (row[field] && typeof row[field] === 'string') {
100
+ // 检查是否是JWT格式
101
+ if (row[field].startsWith('ey') && row[field].includes('.')) {
102
+ if (!cookies.includes(row[field])) {
103
+ cookies.push(row[field]);
104
+ }
105
+ break;
106
+ }
107
+ // 检查是否是旧格式
108
+ else if (row[field].includes('user_')) {
109
+ if (!cookies.includes(row[field])) {
110
+ cookies.push(row[field]);
111
+ }
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ })
118
+ .on('end', () => {
119
+ console.log(`从CSV解析中提取到 ${cookies.length} 个Cookie`);
120
+
121
+ // 如果通过CSV解析没有找到cookie,尝试按行读取
122
+ if (cookies.length === 0) {
123
+ console.log('尝试按行读取文件...');
124
+ const lines = fileContent.split('\n');
125
+ for (const line of lines) {
126
+ // 检查是否有JWT格式token
127
+ if (line.includes('ey')) {
128
+ const jwtMatches = line.match(jwtRegex);
129
+ if (jwtMatches) {
130
+ jwtMatches.forEach(match => {
131
+ if (!cookies.includes(match)) {
132
+ cookies.push(match);
133
+ }
134
+ });
135
+ }
136
+ }
137
+
138
+ // 检查是否有旧格式cookie
139
+ if (line.includes('user_')) {
140
+ const extractedCookies = extractCookiesFromText(line);
141
+ extractedCookies.forEach(cookie => {
142
+ if (!cookies.includes(cookie)) {
143
+ cookies.push(cookie);
144
+ }
145
+ });
146
+ }
147
+ }
148
+ console.log(`按行读取后提取到 ${cookies.length} 个Cookie`);
149
+ }
150
+
151
+ // 验证提取的cookie是否完整
152
+ const validatedCookies = validateCookies(cookies);
153
+
154
+ resolve(validatedCookies);
155
+ })
156
+ .on('error', (error) => {
157
+ console.error('解析CSV文件时出错:', error);
158
+
159
+ // 如果已经提取到cookie,直接返回
160
+ if (cookies.length > 0) {
161
+ console.log(`解析出错但已提取到 ${cookies.length} 个Cookie,进行验证后返回`);
162
+ resolve(validateCookies(cookies));
163
+ } else {
164
+ // 否则尝试其他方法提取
165
+ console.log('尝试其他方法提取Cookie...');
166
+
167
+ // 尝试提取JWT格式token
168
+ const jwtMatches = fileContent.match(jwtRegex);
169
+ if (jwtMatches) {
170
+ jwtMatches.forEach(match => {
171
+ if (!cookies.includes(match)) {
172
+ cookies.push(match);
173
+ }
174
+ });
175
+ }
176
+
177
+ // 尝试提取旧格式cookie
178
+ const oldFormatCookies = extractCookiesFromText(fileContent);
179
+ oldFormatCookies.forEach(cookie => {
180
+ if (!cookies.includes(cookie)) {
181
+ cookies.push(cookie);
182
+ }
183
+ });
184
+
185
+ console.log(`通过其他方法提取到 ${cookies.length} 个Cookie`);
186
+ resolve(validateCookies(cookies));
187
+ }
188
+ });
189
+ } catch (error) {
190
+ console.error('提取Cookie时出错:', error);
191
+ reject(error);
192
+ }
193
+ });
194
+ }
195
+
196
+ /**
197
+ * 从文本中提取cookie
198
+ * @param {string} text - 要提取cookie的文本
199
+ * @returns {string[]} - 提取到的cookie数组
200
+ */
201
+ function extractCookiesFromText(text) {
202
+ const cookies = [];
203
+
204
+ // 使用正则表达式匹配user_开头的cookie(旧格式)
205
+ const oldFormatRegex = /user_[a-zA-Z0-9%]+%3A%3A[a-zA-Z0-9%\.\_\-]+/g;
206
+ const oldFormatMatches = text.match(oldFormatRegex);
207
+
208
+ if (oldFormatMatches) {
209
+ oldFormatMatches.forEach(match => {
210
+ if (!cookies.includes(match)) {
211
+ cookies.push(match);
212
+ }
213
+ });
214
+ }
215
+
216
+ // 使用正则表达式匹配以ey开头的JWT格式cookie(新格式)
217
+ const jwtRegex = /ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
218
+ const jwtMatches = text.match(jwtRegex);
219
+
220
+ if (jwtMatches) {
221
+ jwtMatches.forEach(match => {
222
+ if (!cookies.includes(match)) {
223
+ cookies.push(match);
224
+ }
225
+ });
226
+ }
227
+
228
+ return cookies;
229
+ }
230
+
231
+ /**
232
+ * 验证cookie是否完整
233
+ * @param {string[]} cookies - 要验证的cookie数组
234
+ * @returns {string[]} - 验证后的cookie数组
235
+ */
236
+ function validateCookies(cookies) {
237
+ return cookies.filter(cookie => {
238
+ // 检查是否是新格式的JWT token (ey开头)
239
+ if (cookie.startsWith('ey') && cookie.includes('.')) {
240
+ const parts = cookie.split('.');
241
+ // 检查JWT是否包含三个部分
242
+ if (parts.length === 3) {
243
+ return true; // cookie有效
244
+ } else {
245
+ console.warn(`检测到不完整的JWT(新格式): ${cookie}`);
246
+ return false;
247
+ }
248
+ }
249
+ // 检查旧格式cookie是否完整
250
+ else if (cookie.includes('%3A%3A')) {
251
+ const parts = cookie.split('%3A%3A');
252
+ if (parts.length === 2) {
253
+ const jwt = parts[1];
254
+ // 检查JWT是否包含两个点(表示三个部分)
255
+ if (jwt.includes('.') && jwt.split('.').length === 3) {
256
+ return true; // cookie完整
257
+ } else {
258
+ console.warn(`检测到不完整的JWT(旧格式): ${cookie}`);
259
+ return false;
260
+ }
261
+ }
262
+ }
263
+ return true; // 对于无法识别的格式,默认保留
264
+ });
265
+ }
266
+
267
+ module.exports = {
268
+ extractCookiesFromCsv
269
+ };
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,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ function generateCursorBody(messages, modelName) {
8
+
9
+ const instruction = messages
10
+ .filter(msg => msg.role === 'system')
11
+ .map(msg => msg.content)
12
+ .join('\n')
13
+
14
+ const formattedMessages = messages
15
+ .filter(msg => msg.role !== 'system')
16
+ .map(msg => ({
17
+ content: msg.content,
18
+ role: msg.role === 'user' ? 1 : 2,
19
+ messageId: uuidv4(),
20
+ ...(msg.role === 'user' ? { chatModeEnum: 1 } : {})
21
+ //...(msg.role !== 'user' ? { summaryId: uuidv4() } : {})
22
+ }));
23
+
24
+ const messageIds = formattedMessages.map(msg => {
25
+ const { role, messageId, summaryId } = msg;
26
+ return summaryId ? { role, messageId, summaryId } : { role, messageId };
27
+ });
28
+
29
+ const body = {
30
+ request:{
31
+ messages: formattedMessages,
32
+ unknown2: 1,
33
+ instruction: {
34
+ instruction: instruction
35
+ },
36
+ unknown4: 1,
37
+ model: {
38
+ name: modelName,
39
+ empty: '',
40
+ },
41
+ webTool: "",
42
+ unknown13: 1,
43
+ cursorSetting: {
44
+ name: "cursor\\aisettings",
45
+ unknown3: "",
46
+ unknown6: {
47
+ unknwon1: "",
48
+ unknown2: ""
49
+ },
50
+ unknown8: 1,
51
+ unknown9: 1
52
+ },
53
+ unknown19: 1,
54
+ //unknown22: 1,
55
+ conversationId: uuidv4(),
56
+ metadata: {
57
+ os: "win32",
58
+ arch: "x64",
59
+ version: "10.0.22631",
60
+ path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
61
+ timestamp: new Date().toISOString(),
62
+ },
63
+ unknown27: 0,
64
+ //unknown29: "",
65
+ messageIds: messageIds,
66
+ largeContext: 0,
67
+ unknown38: 0,
68
+ chatModeEnum: 1,
69
+ unknown47: "",
70
+ unknown48: 0,
71
+ unknown49: 0,
72
+ unknown51: 0,
73
+ unknown53: 1,
74
+ chatMode: "Ask"
75
+ }
76
+ };
77
+
78
+ const errMsg = $root.StreamUnifiedChatWithToolsRequest.verify(body);
79
+ if (errMsg) throw Error(errMsg);
80
+ const instance = $root.StreamUnifiedChatWithToolsRequest.create(body);
81
+ let buffer = $root.StreamUnifiedChatWithToolsRequest.encode(instance).finish();
82
+ let magicNumber = 0x00
83
+ if (formattedMessages.length >= 3){
84
+ buffer = zlib.gzipSync(buffer)
85
+ magicNumber = 0x01
86
+ }
87
+
88
+ const finalBody = Buffer.concat([
89
+ Buffer.from([magicNumber]),
90
+ Buffer.from(buffer.length.toString(16).padStart(8, '0'), 'hex'),
91
+ buffer
92
+ ])
93
+
94
+ return finalBody
95
+ }
96
+
97
+ function chunkToUtf8String(chunk) {
98
+ const results = []
99
+ const errorResults = { hasError: false, errorMessage: '' }
100
+ const buffer = Buffer.from(chunk, 'hex');
101
+ //console.log("Chunk buffer:", buffer.toString('hex'))
102
+
103
+ try {
104
+ for(let i = 0; i < buffer.length; i++){
105
+ const magicNumber = parseInt(buffer.subarray(i, i + 1).toString('hex'), 16)
106
+ const dataLength = parseInt(buffer.subarray(i + 1, i + 5).toString('hex'), 16)
107
+ const data = buffer.subarray(i + 5, i + 5 + dataLength)
108
+ //console.log("Parsed buffer:", magicNumber, dataLength, data.toString('hex'))
109
+
110
+ if (magicNumber == 0 || magicNumber == 1) {
111
+ const gunzipData = magicNumber == 0 ? data : zlib.gunzipSync(data)
112
+ const response = $root.StreamUnifiedChatWithToolsResponse.decode(gunzipData);
113
+ const thinking = response?.message?.thinking?.content
114
+ if (thinking !== undefined){
115
+ results.push(thinking);
116
+ //console.log(thinking);
117
+ }
118
+
119
+ const content = response?.message?.content
120
+ if (content !== undefined){
121
+ results.push(content)
122
+ //console.log(content)
123
+ }
124
+
125
+ }
126
+ else if (magicNumber == 2 || magicNumber == 3) {
127
+ // Json message
128
+ const gunzipData = magicNumber == 2 ? data : zlib.gunzipSync(data)
129
+ const utf8 = gunzipData.toString('utf-8')
130
+ const message = JSON.parse(utf8)
131
+
132
+ if (message != null && (typeof message !== 'object' ||
133
+ (Array.isArray(message) ? message.length > 0 : Object.keys(message).length > 0))){
134
+ //results.push(utf8)
135
+ console.error(utf8)
136
+
137
+ // 检查是否为错误消息
138
+ if (message && message.error) {
139
+ errorResults.hasError = true;
140
+ errorResults.errorMessage = utf8;
141
+ }
142
+ }
143
+ }
144
+ else {
145
+ //console.log('Unknown magic number when parsing chunk response: ' + magicNumber)
146
+ }
147
+
148
+ i += 5 + dataLength - 1
149
+ }
150
+ } catch (err) {
151
+ console.log('Error parsing chunk response:', err)
152
+ }
153
+
154
+ // 如果存在错误,返回错误对象
155
+ if (errorResults.hasError) {
156
+ return { error: errorResults.errorMessage };
157
+ }
158
+
159
+ return results.join('')
160
+ }
161
+
162
+ function generateHashed64Hex(input, salt = '') {
163
+ const hash = crypto.createHash('sha256');
164
+ hash.update(input + salt);
165
+ return hash.digest('hex');
166
+ }
167
+
168
+ function obfuscateBytes(byteArray) {
169
+ let t = 165;
170
+ for (let r = 0; r < byteArray.length; r++) {
171
+ byteArray[r] = (byteArray[r] ^ t) + (r % 256);
172
+ t = byteArray[r];
173
+ }
174
+ return byteArray;
175
+ }
176
+
177
+ function generateCursorChecksum(token) {
178
+ const machineId = generateHashed64Hex(token, 'machineId');
179
+ const macMachineId = generateHashed64Hex(token, 'macMachineId');
180
+
181
+ const timestamp = Math.floor(Date.now() / 1e6);
182
+ const byteArray = new Uint8Array([
183
+ (timestamp >> 40) & 255,
184
+ (timestamp >> 32) & 255,
185
+ (timestamp >> 24) & 255,
186
+ (timestamp >> 16) & 255,
187
+ (timestamp >> 8) & 255,
188
+ 255 & timestamp,
189
+ ]);
190
+
191
+ const obfuscatedBytes = obfuscateBytes(byteArray);
192
+ const encodedChecksum = Buffer.from(obfuscatedBytes).toString('base64');
193
+
194
+ return `${encodedChecksum}${machineId}/${macMachineId}`;
195
+ }
196
+
197
+ module.exports = {
198
+ generateCursorBody,
199
+ chunkToUtf8String,
200
+ generateHashed64Hex,
201
+ generateCursorChecksum
202
+ };