diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..815075c7dda30e6f3fc169af02bb5cf3d4ad1184 --- /dev/null +++ b/.env @@ -0,0 +1,29 @@ +# 服务端口 +PORT=3010 + +# 日志格式 (tiny, combined, common, dev, short) +MORGAN_FORMAT=tiny + +# API Key与Cookie映射关系 (JSON格式) +# 格式: {"自定义API Key": "Cookie值"} 或 {"自定义API Key": ["Cookie值1", "Cookie值2"]} +API_KEYS={"sk-123":[]} + +# 轮询策略 (random 或 round-robin 或 default) +ROTATION_STRATEGY=default + +# 是否使用TLS代理 (true 或 false) +USE_TLS_PROXY=true + +# 是否使用辅助代理服务器 (true 或 false) +USE_OTHERS_PROXY=true + +# 代理服务器平台 +# 可选值: auto, windows_x64, linux_x64, android_arm64 +# auto: 自动检测平台 +# windows_x64: Windows 64位 +# linux_x64: Linux 64位 +# android_arm64: 安卓ARM 64位 +PROXY_PLATFORM=auto + +# 是否使用其它接口 (true 或 false) +USE_OTHERS=true diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..ccca748da295378df6e355b29199c56896f773b4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +src/proxy/cursor_proxy_server_linux_amd64 filter=lfs diff=lfs merge=lfs -text +src/proxy/others/cursor_proxy_server_linux_amd64 filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2ad906bbf9cad3c575ebcdcdbce329090ca295c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +data/admin.json +data/api_keys.json +data/invalid_cookies.json +.env.* + +# 测试脚本 +test-*.js +src/utils/test-*.js + +*.bak diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..167ed78f265110643e47ef5b9444427344c2b0f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM node:lts-alpine + +WORKDIR /app + +COPY package.json package-lock.json ./ + +RUN npm install + +COPY . /app + +EXPOSE 3010 + +CMD ["npm", "run", "start"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..b1209281d996c14b136a2d0939c895ee9fc01129 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 liuw1535 + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..2db0dac3a0f189a3980471c6c7957b99f4ca0967 --- /dev/null +++ b/README.en.md @@ -0,0 +1,209 @@ +# Cursor-To-OpenAI-Nexus + +[中文](README.md) | English + +Forward Cursor API requests to OpenAI, with support for multiple API Keys rotation. + +## Features + +- 🔑 **Multiple Keys Rotation**: Configure multiple API Keys rotation to improve availability +- 🚀 **Easy Configuration**: One-click configuration script for quick setup +- 📊 **Status Monitoring**: Monitor API Key usage status +- 🔧 **Easy Maintenance**: Convenient maintenance scripts to simplify daily operations + +## 🚀 Basic Installation +### Clone Repository +``` +git clone https://github.com/liuw1535/cursor-to-openai-nexus.git +``` +### Enter Project Directory +``` +cd cursor-to-openai-nexus +``` +### Install Dependencies +``` +npm install +``` + +## ⚙️ Configure Project +``` +npm run setup +``` +- Just fill in the custom key and whether to enable TLS proxy server +- Other options can be skipped by pressing Enter or filled in randomly +- 🛡️ If you frequently encounter account blocking issues, it's recommended to enable TLS server +- If you're not satisfied with the configuration, you can re-run this command to modify it + +## 🏃 Start Service +``` +npm start +``` + +## 🔍 Usage +1. Access the management interface: `http://127.0.0.1:3010` +2. Use the blue button at the bottom of the page to get cookies +3. Configure in the Tavern page: + - API address: `http://127.0.0.1:3010/v1` + - Key: `sk-text` (if "text" was entered during configuration) + +## 📧 Account Registration Recommendations +- Recommended to use domain email (subdomain email is better) +- Search for "cloudfare domain email" for configuration tutorials +- ⚠️ Register no more than 2 accounts at a time to avoid being blocked + +## 🛠️ Common Commands +``` +npm start # Start project +npm run setup # Modify configuration +``` + +## Environment Configuration + +Configure the following key parameters in the `.env` file: + +- `API_KEYS`: Mapping relationship between API Key and Cookie (JSON format) +- `USE_TLS_PROXY`: (true) Enable TLS server, which can avoid request blocking issues +- `PROXY_PLATFORM`: The platform corresponding to the TLS server when enabled, default is auto detection + +The system will automatically merge API Keys from `.env` and `data/api_keys.json` at startup to ensure data consistency. + +## Deployment Method + +### Using Docker Compose + +```bash +# Create configuration files +cp .env.example .env +mkdir -p data +cp data/admin.example.json data/admin.json + +# Create admin account +node scripts/create-admin.js + +# Start service +docker compose up -d --build + +# View logs +docker compose logs -f + +# Stop service +docker compose down +``` + +## API Usage Example + +### Python Example + +```python +from openai import OpenAI + +# Use custom API Key +client = OpenAI(api_key="your_custom_api_key", + base_url="http://localhost:3010/v1") + +# Or use Cookie directly +# client = OpenAI(api_key="user_...", +# base_url="http://localhost:3010/v1") + +response = client.chat.completions.create( + model="claude-3-7-sonnet", + messages=[ + {"role": "user", "content": "Hello."}, + ], + stream=False +) + +print(response.choices) +``` + +## Notes + +- Please keep your WorkosCursorSessionToken secure +- This project is for learning and research purposes only, please comply with Cursor's terms of use + +## Acknowledgements + +- This project is based on [cursor-api](https://github.com/zhx47/cursor-api) (by zhx47) +- Integrated content from [cursor-api](https://github.com/lvguanjun/cursor-api) (by lvguanjun) + +# Logging System + +The project integrates a unified logging system, which can be configured through the following methods: + +## Log Level Configuration + +1. Set environment variables in the `.env` file + ``` + LOG_LEVEL=INFO + LOG_FORMAT=colored + LOG_TO_FILE=true + LOG_MAX_SIZE=10 + LOG_MAX_FILES=10 + ``` +2. Specify environment variables in the startup command, for example: `LOG_LEVEL=DEBUG npm start` + +Supported log levels include: +- ERROR: Only display error messages +- WARN: Display warning and error messages +- INFO: Display general information, warnings, and error messages (default) +- DEBUG: Display debug information, general information, warnings, and error messages +- TRACE: Display all log information + +## Log Format + +The log format is: `[LEVEL] timestamp log content`, with different levels displayed in different colors for easy differentiation: +- ERROR: Red +- WARN: Yellow +- INFO: Green +- DEBUG: Blue +- TRACE: Cyan +- HTTP: Cyan (dedicated to HTTP request logs) + +## HTTP Request Logs + +The project uses the Morgan middleware to record HTTP requests, integrated into the unified logging system: + +1. Set HTTP log format in the `.env` file: + ``` + # Options: tiny, combined, common, dev, short + MORGAN_FORMAT=tiny + ``` + +2. HTTP logs will be displayed with the `[HTTP]` prefix, highlighted in cyan for easy identification + +3. Morgan format options explanation: + - `tiny`: The most concise format, including only method, URL, status code, response time + - `combined`: Standard Apache combined log format, including IP, time, request, status code, response size, referrer, user-agent + - `common`: Standard Apache common log format, similar to combined but without referrer and user-agent + - `dev`: Developer-friendly colored format, including method, URL, status code (with color), response time + - `short`: Shorter format, including method, URL, status code, response time, response size + +## File Logs + +The project supports outputting logs to both console and files, which can be enabled with the following configuration: + +1. Set in the `.env` file: `LOG_TO_FILE=true` +2. Optional configuration: + - `LOG_MAX_SIZE`: Maximum size of log file, in MB, default 10MB + - `LOG_MAX_FILES`: Number of historical log files to keep, default 10 + +Log files are stored in the `logs` folder in the project root directory: +- Current log file: `app.log` +- Historical log file: `app-2023-05-05T12-45-30-000Z.log` + +File logs will automatically rotate, creating a new log file when the log file size exceeds the set value and keeping the most recent N files. + +## Usage in Code + +Different log levels can be used as needed in the code: + +```javascript +const logger = require('./utils/logger'); + +logger.error('This is an error message'); +logger.warn('This is a warning message'); +logger.info('This is a general information message'); +logger.debug('This is a debug message'); +logger.trace('This is a trace message'); +logger.http('This is an HTTP request log'); +``` diff --git a/README.md b/README.md index faac0ca12b7023ae1ef4439d138688335b9c4149..543577e468112715c5732551d54ae5872ccd0bc1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,228 @@ ---- -title: Cursor -emoji: 🏢 -colorFrom: purple -colorTo: yellow -sdk: docker -pinned: false -license: mit ---- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +--- +title: Cursor-To-OpenAI-Nexus +emoji: 🚀 +colorFrom: blue +colorTo: green +sdk: docker +app_port: 3010 +--- +# Cursor-To-OpenAI-Nexus + +[English](README.en.md) | 中文 + +将Cursor的API请求转发到OpenAI,支持多个API Key轮询。 + +## 项目特点 + +- 🔑 **多Key轮询**: 配置多个API Key轮询,提高可用性 +- 🚀 **简易配置**: 一键配置脚本,快速搭建环境 +- 📊 **状态监控**: 查看API Key使用情况 +- 🔧 **易于维护**: 便捷的维护脚本,简化日常操作 + +## 🚀 基础安装 +### 克隆项目 +``` +git clone https://github.com/liuw1535/cursor-to-openai-nexus.git +``` +### 进入项目 +``` +cd cursor-to-openai-nexus +``` +### 安装依赖 +``` +npm install +``` + +## ⚙️ 配置项目 +``` +npm run setup +``` +- 只需填写自定义密钥和是否启用TLS伪造代理服务器 +- 其他选项可直接回车跳过或随意填写 +- 🛡️ 如频繁遇到封号问题,建议启用TLS服务器 +- 配置不满意可重新执行此命令修改 + +## 🏃 启动服务 +``` +npm start +``` + +## 🔍 使用方法 +1. 访问管理界面: `http://127.0.0.1:3010` +2. 使用页面底部蓝色按钮获取cookie +3. 在酒馆页面配置: + - API地址: `http://127.0.0.1:3010/v1` + - 密钥: `sk-text` (如果配置时输入的是"text") + +## 📧 账号注册建议 +- 推荐使用域名邮箱(子域名邮箱更佳) +- 搜索"cloudfare 域名邮箱"获取配置教程 +- ⚠️ 每次注册账号不超过2个,避免被封 + +## 🛠️ 常用命令 +``` +npm start # 启动项目 +npm run setup # 修改配置 +``` + +## 环境配置 + +在`.env`文件中配置以下关键参数: + +- `API_KEYS`: API Key与Cookie的映射关系(JSON格式) +- `USE_TLS_PROXY`: (true)启用tls服务器,可以避免阻止请求(block)的问题 +- `PROXY_PLATFORM`: 启用tls服务器时对应的平台,默认auto自动检测 + +系统启动时会自动合并`.env`中的API Keys和`data/api_keys.json`中的API Keys,确保数据一致性。 + +## 部署方式 + +### 使用Docker Compose + +```bash +# 创建配置文件 +cp .env.example .env +mkdir -p data +cp data/admin.example.json data/admin.json + +# 创建管理员账户 +node scripts/create-admin.js + +# 启动服务 +docker compose up -d --build + +# 查看日志 +docker compose logs -f + +# 停止服务 +docker compose down +``` + +### 部署到 Hugging Face Spaces + +本项目包含 `Dockerfile`,可以直接部署到 Hugging Face Spaces。 + +1. 在Hugging Face上创建一个新的Space,并选择 **Docker** 作为SDK。 +2. 将此仓库克隆到您的Space中。 +3. 在Space的 **Settings -> Repository secrets** 中设置您的环境变量(如 `API_KEYS` 等)。这些secrets会作为环境变量注入到容器中,作用等同于 `.env` 文件。 +4. Hugging Face会自动构建并运行应用。 + +**注意**: 部署后,请将文档中所有的 `http://127.0.0.1:3010` 替换为您的Space URL,例如 `https://your-space-name.hf.space`。 + +## API使用示例 + +### Python示例 + +```python +from openai import OpenAI + +# 使用自定义API Key +client = OpenAI(api_key="your_custom_api_key", + base_url="http://localhost:3010/v1") + +# 或直接使用Cookie +# client = OpenAI(api_key="user_...", +# base_url="http://localhost:3010/v1") + +response = client.chat.completions.create( + model="claude-3-7-sonnet", + messages=[ + {"role": "user", "content": "Hello."}, + ], + stream=False +) + +print(response.choices) +``` + +## 注意事项 + +- 请妥善保管你的WorkosCursorSessionToken +- 本项目仅用于学习和研究目的,请遵守Cursor的使用条款 + +## 致谢 + +- 本项目基于[cursor-api](https://github.com/zhx47/cursor-api)(by zhx47) +- 整合了[cursor-api](https://github.com/lvguanjun/cursor-api)(by lvguanjun)的提交内容 + +# 日志系统 + +项目集成了统一的日志系统,可以通过以下方式配置: + +## 日志级别配置 + +1. 在 `.env` 文件中设置环境变量 + ``` + LOG_LEVEL=INFO + LOG_FORMAT=colored + LOG_TO_FILE=true + LOG_MAX_SIZE=10 + LOG_MAX_FILES=10 + ``` +2. 在启动命令中指定环境变量,例如:`LOG_LEVEL=DEBUG npm start` + +支持的日志级别有: +- ERROR:只显示错误信息 +- WARN:显示警告和错误信息 +- INFO:显示一般信息、警告和错误信息(默认) +- DEBUG:显示调试信息、一般信息、警告和错误信息 +- TRACE:显示所有日志信息 + +## 日志格式 + +日志格式为:`[级别] 时间戳 日志内容`,不同级别使用不同颜色显示,方便区分: +- ERROR:红色 +- WARN:黄色 +- INFO:绿色 +- DEBUG:蓝色 +- TRACE:青色 +- HTTP:青色(专用于HTTP请求日志) + +## HTTP请求日志 + +项目使用 Morgan 中间件记录 HTTP 请求,并集成到统一日志系统中: + +1. 在 `.env` 文件中设置 HTTP 日志格式: + ``` + # 选项: tiny, combined, common, dev, short + MORGAN_FORMAT=tiny + ``` + +2. HTTP 日志会以 `[HTTP]` 前缀显示,使用青色高亮,便于识别 + +3. Morgan 格式选项说明: + - `tiny`: 最简洁的格式,仅包含方法、URL、状态码、响应时间 + - `combined`: 标准的 Apache 组合日志格式,包含 IP、时间、请求、状态码、响应大小、referrer、user-agent + - `common`: 标准的 Apache 通用日志格式,类似 combined 但不包含 referrer 和 user-agent + - `dev`: 开发友好的彩色格式,包含方法、URL、状态码(带颜色)、响应时间 + - `short`: 更短的格式,包含方法、URL、状态码、响应时间、响应大小 + +## 文件日志 + +项目支持将日志同时输出到控制台和文件,可以通过以下配置启用: + +1. 在 `.env` 文件中设置:`LOG_TO_FILE=true` +2. 可选配置: + - `LOG_MAX_SIZE`: 日志文件最大大小,单位MB,默认10MB + - `LOG_MAX_FILES`: 保留的历史日志文件数量,默认10个 + +日志文件存储在项目根目录的 `logs` 文件夹下: +- 当前日志文件: `app.log` +- 历史日志文件: `app-2023-05-05T12-45-30-000Z.log` + +文件日志会自动轮转,当日志文件大小超过设定值时,会创建新的日志文件并保留最近的N个文件。 + +## 代码中使用 + +在代码中可以按需使用不同级别的日志: + +```javascript +const logger = require('./utils/logger'); + +logger.error('这是错误信息'); +logger.warn('这是警告信息'); +logger.info('这是一般信息'); +logger.debug('这是调试信息'); +logger.trace('这是跟踪信息'); +logger.http('这是HTTP请求日志'); +``` diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000000000000000000000000000000000000..d57e993054ca7e252337690ce8d74e905171293e --- /dev/null +++ b/SETUP.md @@ -0,0 +1,102 @@ +# Cursor-To-OpenAI 一键配置指南 + +本文档将指导你使用一键配置工具来设置 Cursor-To-OpenAI 环境。 + +## 准备工作 + +在开始配置前,请确保你已经: + +1. Fork了 [Cursor-Register-fix](https://github.com/liuw1535/Cursor-Register-fix) 仓库到你的GitHub账号 +2. 创建了一个GitHub个人访问令牌(Personal Access Token),且具有 `repo` 权限 +3. 拥有至少一个Gmail账号,并启用了两步验证 +4. 为Gmail账号创建了应用密码(Application Password) + +## 配置步骤 + +### 1. 安装依赖 + +```bash +npm install +``` + +### 2. 运行配置脚本 + +```bash +npm run setup +``` + +或者直接运行: + +```bash +node setup.js +``` + +### 3. 按照提示输入信息 + +脚本会引导你输入以下信息: + +- GitHub用户名:你的GitHub账号用户名 +- GitHub Token:你的个人访问令牌 +- API Key:自定义的API Key,用于访问服务 +- Gmail账号:用于自动注册Cursor账号的Gmail地址 +- Gmail应用密码:对应Gmail账号的应用密码(不是邮箱密码) + +### 4. 创建应用密码的步骤 + +如果你还没有创建Gmail应用密码,请按照以下步骤操作: + +1. 访问 [Google账号安全设置](https://myaccount.google.com/security) +2. 在"登录Google"部分,点击"两步验证" + (如果未启用两步验证,需要先启用) +3. 在页面底部找到"应用密码",点击进入 +4. 在"选择应用"下拉菜单中选择"其他(自定义名称)" +5. 输入一个名称,例如"Cursor注册" +6. 点击"生成" +7. 复制生成的16位应用密码(格式如:xxxx xxxx xxxx xxxx) + +### 5. 管理邮箱配置 + +系统提供了一个专门的邮箱配置管理工具,可以随时添加、修改或删除邮箱: + +```bash +npm run manage-emails +``` + +使用此工具可以: +- 查看所有已配置的邮箱 +- 添加新的Gmail账号 +- 修改现有Gmail账号的配置 +- 删除不再使用的Gmail账号 + +## 配置完成后 + +配置完成后,你可以: + +1. 启动服务: + +```bash +npm start +``` + +2. 手动触发Cookie刷新: + +```bash +npm run refresh-cookies:force +``` + +## 配置文件说明 + +脚本会生成`.env`文件,其中包含以下主要配置: + +- `API_KEYS`:API Key到Cookie的映射关系 +- `GITHUB_OWNER`:你的GitHub用户名 +- `GITHUB_TOKEN`:你的GitHub个人访问令牌 +- `REGISTER_EMAIL_CONFIGS`:Gmail账号配置,用于自动注册 + +## 注意事项 + +1. GitHub Token需要具有repo权限,用于访问你fork的仓库 +2. Gmail应用密码不同于你的Gmail登录密码,是专门为第三方应用生成的 +3. MIN_COOKIE_COUNT设置为1000,确保系统会尝试刷新Cookie +4. 配置完成后,你可以通过Web界面查看和管理Cookie状态 +5. 始终确保至少有一个有效的Gmail账号配置,否则自动刷新功能将无法正常工作 \ No newline at end of file diff --git a/add-api-key.js b/add-api-key.js new file mode 100644 index 0000000000000000000000000000000000000000..fe3bdc269658c62eb47b88cf4dd26a978ee2227f --- /dev/null +++ b/add-api-key.js @@ -0,0 +1,43 @@ +const fetch = require('node-fetch'); + +async function addApiKey() { + try { + console.log('添加API Key...'); + const response = await fetch('http://localhost:3010/v1/api-keys', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + apiKey: 'test-key', + cookieValues: ['test-cookie'], + }), + }); + + console.log('响应状态:', response.status); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + console.log('响应数据:', data); + + // 测试获取API Keys + console.log('\n测试获取API Keys...'); + const getResponse = await fetch('http://localhost:3010/v1/api-keys'); + + console.log('响应状态:', getResponse.status); + + if (!getResponse.ok) { + throw new Error(`HTTP错误: ${getResponse.status} ${getResponse.statusText}`); + } + + const getData = await getResponse.json(); + console.log('获取到的数据:', getData); + } catch (error) { + console.error('操作失败:', error); + } +} + +addApiKey(); \ No newline at end of file diff --git a/auto-refresh-cookies.js b/auto-refresh-cookies.js new file mode 100644 index 0000000000000000000000000000000000000000..2173fa6445be5e3b9b84e1281056f6ed7b276e05 --- /dev/null +++ b/auto-refresh-cookies.js @@ -0,0 +1,149 @@ +// 加载环境变量 +require('dotenv').config(); + +const fs = require('fs'); +const path = require('path'); +const axios = require('axios'); +const { spawn } = require('child_process'); +const keyManager = require('./src/utils/keyManager'); +const logger = require('./src/utils/logger'); + +// 环境检查 +const envChecker = require('./src/utils/envChecker'); +logger.info('启动前检查环境配置...'); +envChecker.enforceEnvCheck(); + +// 已适配GitHub Actions工作流新参数 (use_config_file, email_configs) +logger.info('环境检查通过,已适配最新GitHub Actions工作流参数'); + +const cookieRefresher = require('./src/utils/cookieRefresher'); +const config = require('./src/config/config'); + +// 解析命令行参数 +const args = process.argv.slice(2); +const targetApiKey = args.length > 0 ? args[0] : null; +const forceRefresh = args.includes('--force') || args.includes('-f'); + +// 最小 Cookie 数量 +const MIN_COOKIE_COUNT = process.env.MIN_COOKIE_COUNT || 3; + +// 获取Cookie刷新模式 +const COOKIE_REFRESH_MODE = process.env.COOKIE_REFRESH_MODE || 'append'; + +// 主函数 +async function main() { + logger.info('===== 自动刷新 Cookie 开始 ====='); + logger.info(`最小 Cookie 数量: ${MIN_COOKIE_COUNT}`); + logger.info(`Cookie 刷新模式: ${COOKIE_REFRESH_MODE} (${COOKIE_REFRESH_MODE === 'replace' ? '替换现有cookie' : '追加新cookie'})`); + + if (targetApiKey) { + logger.info(`指定刷新 API Key: ${targetApiKey}`); + } + + if (forceRefresh) { + logger.info('强制刷新模式: 忽略 Cookie 数量检查'); + } + + try { + // 获取所有 API Key + const apiKeys = keyManager.getAllApiKeys(); + + if (apiKeys.length === 0) { + logger.warn('警告: 系统中没有找到任何 API Key'); + + // 检查环境变量中是否有 API Keys + const envApiKeys = Object.keys(config.apiKeys); + if (envApiKeys.length > 0) { + logger.info(`检测到环境变量中有 ${envApiKeys.length} 个 API Key,但尚未加载到系统中`); + logger.info('正在重新初始化 API Keys...'); + + // 重新初始化 API Keys + keyManager.initializeApiKeys(); + + // 重新获取 API Keys + const refreshedApiKeys = keyManager.getAllApiKeys(); + if (refreshedApiKeys.length > 0) { + logger.info(`成功加载 ${refreshedApiKeys.length} 个 API Key,继续刷新流程`); + // 继续执行后续刷新逻辑 + } else { + logger.warn('初始化后仍未找到 API Key,请检查配置'); + logger.info('===== 自动刷新 Cookie 结束 ====='); + return; + } + } else { + logger.warn('环境变量中也没有配置 API Key,请先添加 API Key'); + logger.info('===== 自动刷新 Cookie 结束 ====='); + return; + } + } + + // 重新获取最新的 API Keys(可能已经通过上面的初始化更新了) + const updatedApiKeys = keyManager.getAllApiKeys(); + logger.info(`系统中共有 ${updatedApiKeys.length} 个 API Key`); + + // 如果指定了特定的 API Key,检查它是否存在 + if (targetApiKey && !updatedApiKeys.includes(targetApiKey)) { + logger.error(`错误: 指定的 API Key "${targetApiKey}" 不存在`); + logger.info('===== 自动刷新 Cookie 异常结束 ====='); + return; + } + + // 过滤需要处理的 API Keys + const keysToProcess = targetApiKey ? [targetApiKey] : updatedApiKeys; + + // 按 Cookie 数量排序,优先处理 Cookie 数量少的 API Key + const sortedKeys = keysToProcess.sort((a, b) => { + const aCount = keyManager.getAllCookiesForApiKey(a).length; + const bCount = keyManager.getAllCookiesForApiKey(b).length; + return aCount - bCount; // 升序排列,Cookie 数量少的排在前面 + }); + + // 检查每个 API Key 是否需要刷新 + let refreshedCount = 0; + let needRefreshCount = 0; + + for (const apiKey of sortedKeys) { + const cookies = keyManager.getAllCookiesForApiKey(apiKey); + logger.info(`API Key: ${apiKey}, Cookie 数量: ${cookies.length}`); + + // 判断是否需要刷新:强制刷新模式或 Cookie 数量低于阈值 + if (forceRefresh || cookies.length < MIN_COOKIE_COUNT) { + needRefreshCount++; + if (forceRefresh) { + logger.info(`强制刷新 API Key: ${apiKey}`); + } else { + logger.info(`API Key ${apiKey} 的 Cookie 数量不足,需要刷新`); + } + + // 执行刷新 + logger.info(`开始自动刷新 Cookie,目标 API Key: ${apiKey},最小 Cookie 数量: ${MIN_COOKIE_COUNT},刷新模式: ${COOKIE_REFRESH_MODE}`); + const result = await cookieRefresher.autoRefreshCookies(apiKey, MIN_COOKIE_COUNT); + + if (result.success) { + refreshedCount++; + logger.info(`刷新结果: ${result.message}`); + + // 根据刷新模式输出额外的信息 + if (COOKIE_REFRESH_MODE === 'replace') { + logger.info(`使用替换模式: 现有cookie已全部标记为无效,系统现在只使用新cookie`); + } else { + logger.info(`使用追加模式: 现有cookie已保留,新cookie已添加到系统`); + } + } else { + logger.error(`刷新失败: ${result.message}`); + } + } else { + logger.info(`API Key ${apiKey} 的 Cookie 数量足够,不需要刷新`); + } + } + + logger.info('===== 自动刷新 Cookie 完成 ====='); + logger.info(`共有 ${needRefreshCount} 个 API Key 需要刷新,成功刷新 ${refreshedCount} 个`); + } catch (error) { + logger.error('自动刷新 Cookie 失败:', error); + logger.info('===== 自动刷新 Cookie 异常结束 ====='); + } +} + +// 执行主函数 +main().catch(err => logger.error(err)); \ No newline at end of file diff --git a/cursor-to-openai-helper.sh b/cursor-to-openai-helper.sh new file mode 100644 index 0000000000000000000000000000000000000000..e4a8fe978ea7882f11c103032b30e8ad2f23ce87 --- /dev/null +++ b/cursor-to-openai-helper.sh @@ -0,0 +1,557 @@ +#!/bin/bash + +# Colors for better UI +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Create backups directory if it doesn't exist +mkdir -p backups + +# Function to display header +show_header() { + clear + echo -e "${BLUE}=======================================${NC}" + echo -e "${GREEN} Cursor-To-OpenAI 简易脚本 ${NC}" + echo -e "${BLUE}=======================================${NC}" + echo +} + +# Function to check if Docker is installed +check_docker() { + if ! command -v docker &> /dev/null; then + echo -e "${RED}未安装Docker。请先安装Docker。${NC}" + exit 1 + fi +} + +# Function to check if Node.js is installed +check_nodejs() { + if ! command -v node &> /dev/null; then + echo -e "${RED}未安装Node.js。请先安装Node.js。${NC}" + exit 1 + fi + + if ! command -v npm &> /dev/null; then + echo -e "${RED}未安装npm。请先安装npm。${NC}" + exit 1 + fi +} + +# Function to backup configuration before update +backup_configs() { + echo -e "${YELLOW}正在备份配置文件...${NC}" + backup_dir="backups/update_backup_$(date +"%Y%m%d_%H%M%S")" + mkdir -p "$backup_dir" + + # Backup important configurations + if [ -d data ]; then + cp -r data "$backup_dir/" + fi + + if [ -f .env ]; then + cp .env "$backup_dir/" + fi + + echo -e "${GREEN}配置文件已备份到 ${backup_dir}${NC}" +} + +# Function to restore configuration after update +restore_configs() { + if [ -z "$1" ]; then + echo -e "${RED}未指定备份目录,无法恢复配置。${NC}" + return 1 + fi + + backup_dir="$1" + echo -e "${YELLOW}正在恢复配置文件...${NC}" + + if [ -d "$backup_dir/data" ]; then + cp -r "$backup_dir/data/"* data/ 2>/dev/null + fi + + if [ -f "$backup_dir/.env" ]; then + cp "$backup_dir/.env" ./ 2>/dev/null + fi + + echo -e "${GREEN}配置文件已恢复${NC}" +} + +# Function for installation tasks +installation_menu() { + show_header + echo -e "${YELLOW}===== 安装菜单 =====${NC}" + echo -e "1) 克隆仓库" + echo -e "2) 安装依赖" + echo -e "3) 创建配置文件" + echo -e "4) 设置管理员账户" + echo -e "5) 运行安装向导" + echo -e "6) 构建并启动Docker容器" + echo -e "7) 使用npm启动" + echo -e "8) 返回主菜单" + echo + echo -n "请输入选择 [1-8]: " + read -r choice + + case $choice in + 1) + show_header + echo -e "${YELLOW}正在克隆仓库...${NC}" + read -p "请输入您的GitHub用户名: " username + git clone "https://github.com/${username}/cursor-to-openai.git" + if [ $? -eq 0 ]; then + echo -e "${GREEN}仓库克隆成功!${NC}" + cd cursor-to-openai + else + echo -e "${RED}克隆仓库失败!${NC}" + fi + read -p "按回车键继续..." + installation_menu + ;; + 2) + show_header + echo -e "${YELLOW}正在安装依赖...${NC}" + npm install + if [ $? -eq 0 ]; then + echo -e "${GREEN}依赖安装成功!${NC}" + else + echo -e "${RED}依赖安装失败!${NC}" + fi + read -p "按回车键继续..." + installation_menu + ;; + 3) + show_header + echo -e "${YELLOW}正在创建配置文件...${NC}" + + if [ ! -f .env ]; then + cp .env.example .env + echo -e "${GREEN}.env文件已创建。${NC}" + else + echo -e "${YELLOW}.env文件已存在。${NC}" + fi + + mkdir -p data + + if [ ! -f data/admin.json ]; then + cp data/admin.example.json data/admin.json + echo -e "${GREEN}admin.json文件已创建。${NC}" + else + echo -e "${YELLOW}admin.json文件已存在。${NC}" + fi + + echo -e "${YELLOW}您应该编辑.env文件来配置您的环境。${NC}" + read -p "是否现在编辑.env文件?(y/n): " edit_env + if [[ $edit_env == "y" || $edit_env == "Y" ]]; then + if command -v nano &> /dev/null; then + nano .env + elif command -v vim &> /dev/null; then + vim .env + else + echo -e "${RED}未找到编辑器。请稍后手动编辑.env文件。${NC}" + fi + fi + + read -p "按回车键继续..." + installation_menu + ;; + 4) + show_header + echo -e "${YELLOW}正在设置管理员账户...${NC}" + node scripts/create-admin.js + read -p "按回车键继续..." + installation_menu + ;; + 5) + show_header + echo -e "${YELLOW}正在运行安装向导...${NC}" + node setup.js + read -p "按回车键继续..." + installation_menu + ;; + 6) + show_header + echo -e "${YELLOW}正在构建并启动Docker容器...${NC}" + check_docker + docker compose up -d --build + if [ $? -eq 0 ]; then + echo -e "${GREEN}Docker容器启动成功!${NC}" + echo -e "${GREEN}访问管理界面: http://localhost:3010${NC}" + else + echo -e "${RED}启动Docker容器失败!${NC}" + fi + read -p "按回车键继续..." + installation_menu + ;; + 7) + show_header + echo -e "${YELLOW}正在使用npm启动...${NC}" + check_nodejs + npm start & + if [ $? -eq 0 ]; then + echo -e "${GREEN}服务启动成功!${NC}" + echo -e "${GREEN}访问管理界面: http://localhost:3010${NC}" + else + echo -e "${RED}启动服务失败!${NC}" + fi + read -p "按回车键继续..." + installation_menu + ;; + 8) + main_menu + ;; + *) + echo -e "${RED}无效选项。请重试。${NC}" + read -p "按回车键继续..." + installation_menu + ;; + esac +} + +# Function for maintenance tasks +maintenance_menu() { + show_header + echo -e "${YELLOW}===== 维护菜单 =====${NC}" + echo -e "1) 查看服务状态" + echo -e "2) 刷新Cookie" + echo -e "3) 强制刷新Cookie" + echo -e "4) 管理邮箱" + echo -e "5) 管理无效Cookie" + echo -e "6) 查看日志" + echo -e "7) 重启服务" + echo -e "8) 停止服务" + echo -e "9) 更新项目代码" + echo -e "10) 备份项目数据" + echo -e "11) 持续刷新Cookie直到成功" + echo -e "12) 返回主菜单" + echo + echo -n "请输入选择 [1-12]: " + read -r choice + + case $choice in + 1) + show_header + echo -e "${YELLOW}服务状态:${NC}" + if docker ps | grep -q cursor-to-openai; then + echo -e "${GREEN}Docker容器正在运行。${NC}" + docker ps | grep cursor-to-openai + else + echo -e "${RED}Docker容器未运行。${NC}" + fi + + pids=$(pgrep -f "node.*start") + if [ -n "$pids" ]; then + echo -e "${GREEN}Node.js服务正在运行,PID: $pids${NC}" + else + echo -e "${RED}Node.js服务未运行。${NC}" + fi + + read -p "按回车键继续..." + maintenance_menu + ;; + 2) + show_header + echo -e "${YELLOW}正在刷新Cookie...${NC}" + npm run refresh-cookies + read -p "按回车键继续..." + maintenance_menu + ;; + 3) + show_header + echo -e "${YELLOW}正在强制刷新Cookie...${NC}" + npm run refresh-cookies -- --force + read -p "按回车键继续..." + maintenance_menu + ;; + 4) + show_header + echo -e "${YELLOW}正在管理邮箱...${NC}" + npm run manage-emails + read -p "按回车键继续..." + maintenance_menu + ;; + 5) + show_header + echo -e "${YELLOW}正在管理无效Cookie...${NC}" + node manage-invalid-cookies.js + read -p "按回车键继续..." + maintenance_menu + ;; + 6) + show_header + echo -e "${YELLOW}正在查看日志...${NC}" + if docker ps | grep -q cursor-to-openai; then + docker compose logs -f + else + echo -e "${RED}Docker容器未运行。${NC}" + echo -e "${YELLOW}正在检查npm日志...${NC}" + # Try to find logs in npm-debug.log or similar + if [ -f npm-debug.log ]; then + cat npm-debug.log + else + echo -e "${RED}未找到日志文件。${NC}" + fi + fi + read -p "按回车键继续..." + maintenance_menu + ;; + 7) + show_header + echo -e "${YELLOW}正在重启服务...${NC}" + if docker ps | grep -q cursor-to-openai; then + docker compose restart + echo -e "${GREEN}Docker容器已重启。${NC}" + else + pids=$(pgrep -f "node.*start") + if [ -n "$pids" ]; then + kill $pids + sleep 2 + npm start & + echo -e "${GREEN}Node.js服务已重启。${NC}" + else + echo -e "${RED}未检测到运行中的服务。${NC}" + echo -e "${YELLOW}是否要启动服务?(y/n): ${NC}" + read -r start_service + if [[ $start_service == "y" || $start_service == "Y" ]]; then + npm start & + echo -e "${GREEN}服务已启动。${NC}" + fi + fi + fi + read -p "按回车键继续..." + maintenance_menu + ;; + 8) + show_header + echo -e "${YELLOW}正在停止服务...${NC}" + if docker ps | grep -q cursor-to-openai; then + docker compose down + echo -e "${GREEN}Docker容器已停止。${NC}" + else + pids=$(pgrep -f "node.*start") + if [ -n "$pids" ]; then + kill $pids + echo -e "${GREEN}Node.js服务已停止。${NC}" + else + echo -e "${RED}未检测到运行中的服务。${NC}" + fi + fi + read -p "按回车键继续..." + maintenance_menu + ;; + 9) + show_header + echo -e "${YELLOW}正在更新项目代码...${NC}" + + # 备份配置文件 + backup_configs + backup_dir=$(ls -td backups/update_backup_* | head -1) + + # 检查是否存在未提交的更改 + if [ -n "$(git status --porcelain)" ]; then + echo -e "${YELLOW}检测到未提交的更改。更新前请处理这些更改。${NC}" + echo -e "1) 查看更改" + echo -e "2) 备份并放弃更改" + echo -e "3) 取消更新" + echo -n "请选择操作 [1-3]: " + read -r update_choice + + case $update_choice in + 1) + git status + echo -e "${YELLOW}是否继续更新?(y/n): ${NC}" + read -r continue_update + if [[ $continue_update != "y" && $continue_update != "Y" ]]; then + echo -e "${YELLOW}更新已取消。${NC}" + read -p "按回车键继续..." + maintenance_menu + return + fi + ;; + 2) + echo -e "${YELLOW}备份更改...${NC}" + git diff > "$backup_dir/local_changes.patch" + git checkout -- . + echo -e "${GREEN}更改已备份到 $backup_dir/local_changes.patch${NC}" + ;; + 3) + echo -e "${YELLOW}更新已取消。${NC}" + read -p "按回车键继续..." + maintenance_menu + return + ;; + *) + echo -e "${RED}无效选项。更新已取消。${NC}" + read -p "按回车键继续..." + maintenance_menu + return + ;; + esac + fi + + # 更新代码 + git pull + update_status=$? + + # 恢复配置文件 + restore_configs "$backup_dir" + + if [ $update_status -eq 0 ]; then + echo -e "${GREEN}项目代码更新成功!${NC}" + echo -e "${YELLOW}是否需要重新安装依赖?(y/n): ${NC}" + read -r reinstall + if [[ $reinstall == "y" || $reinstall == "Y" ]]; then + npm install + if [ $? -eq 0 ]; then + echo -e "${GREEN}依赖安装成功!${NC}" + else + echo -e "${RED}依赖安装失败!${NC}" + fi + fi + else + echo -e "${RED}项目代码更新失败!${NC}" + fi + + read -p "按回车键继续..." + maintenance_menu + ;; + 10) + show_header + echo -e "${YELLOW}正在备份项目数据...${NC}" + + # 创建备份目录 + backup_dir="backups/backup_$(date +"%Y%m%d_%H%M%S")" + mkdir -p "$backup_dir" + + # 备份关键文件 + cp -r data "$backup_dir/" 2>/dev/null + if [ -f .env ]; then + cp .env "$backup_dir/" + fi + + # 压缩备份 + tar -czf "${backup_dir}.tar.gz" "$backup_dir" + rm -rf "$backup_dir" + + echo -e "${GREEN}备份已创建: ${backup_dir}.tar.gz${NC}" + read -p "按回车键继续..." + maintenance_menu + ;; + 11) + show_header + echo -e "${YELLOW}持续刷新Cookie直到成功...${NC}" + read -p "请输入最大尝试时间(分钟, 默认60): " max_time + max_time=${max_time:-60} + max_seconds=$((max_time * 60)) + + echo -e "${YELLOW}将持续尝试刷新Cookie,最长 ${max_time} 分钟...${NC}" + + start_time=$(date +%s) + success=false + attempt=0 + + while ! $success && [ $(($(date +%s) - start_time)) -lt $max_seconds ]; do + attempt=$((attempt + 1)) + elapsed=$(($(date +%s) - start_time)) + remaining=$((max_seconds - elapsed)) + remaining_min=$((remaining / 60)) + remaining_sec=$((remaining % 60)) + + echo -e "${YELLOW}尝试 #${attempt}...(剩余时间: ${remaining_min}分${remaining_sec}秒)${NC}" + + # 运行刷新命令并检查输出 + output=$(npm run refresh-cookies -- --force 2>&1) + echo "$output" + + if echo "$output" | grep -q "成功添加新的Cookie" || echo "$output" | grep -q "Successfully added new cookies"; then + success=true + echo -e "${GREEN}成功添加新Cookie!${NC}" + else + wait_time=$((RANDOM % 61 + 30)) # 30-90秒随机间隔 + echo -e "${YELLOW}等待 ${wait_time} 秒后重试...${NC}" + sleep $wait_time + fi + done + + if $success; then + echo -e "${GREEN}成功刷新Cookie!${NC}" + else + echo -e "${RED}达到最大尝试时间,未能成功刷新Cookie。${NC}" + fi + + read -p "按回车键继续..." + maintenance_menu + ;; + 12) + main_menu + ;; + *) + echo -e "${RED}无效选项。请重试。${NC}" + read -p "按回车键继续..." + maintenance_menu + ;; + esac +} + +# Main menu function +main_menu() { + show_header + echo -e "${YELLOW}===== 主菜单 =====${NC}" + echo -e "1) 启动服务 (npm)" + echo -e "2) 安装配置" + echo -e "3) 系统维护" + echo -e "4) 退出" + echo + echo -n "请输入选择 [1-4]: " + read -r choice + + case $choice in + 1) + show_header + echo -e "${YELLOW}正在使用npm启动服务...${NC}" + check_nodejs + + # 检查Node.js服务是否已在运行 + pids=$(pgrep -f "node.*start") + if [ -n "$pids" ]; then + echo -e "${YELLOW}服务已在运行,PID: $pids${NC}" + echo -e "${YELLOW}是否要重启服务?(y/n): ${NC}" + read -r restart + if [[ $restart == "y" || $restart == "Y" ]]; then + kill $pids + sleep 2 + npm start & + echo -e "${GREEN}服务已重启${NC}" + fi + else + npm start & + echo -e "${GREEN}服务已启动${NC}" + fi + + echo -e "${GREEN}访问管理界面: http://localhost:3010${NC}" + read -p "按回车键继续..." + main_menu + ;; + 2) + installation_menu + ;; + 3) + maintenance_menu + ;; + 4) + show_header + echo -e "${GREEN}感谢使用Cursor-To-OpenAI简易脚本!${NC}" + exit 0 + ;; + *) + echo -e "${RED}无效选项。请重试。${NC}" + read -p "按回车键继续..." + main_menu + ;; + esac +} + +# Start the script +main_menu diff --git a/data/admin.example.json b/data/admin.example.json new file mode 100644 index 0000000000000000000000000000000000000000..d61b186801d0cddfa5787ad560f99190b7225170 --- /dev/null +++ b/data/admin.example.json @@ -0,0 +1,7 @@ +{ + "admin": { + "username": "your_admin_username", + "salt": "your_generated_salt", + "hash": "your_password_hash" + } +} \ No newline at end of file diff --git a/data/api_keys.example.json b/data/api_keys.example.json new file mode 100644 index 0000000000000000000000000000000000000000..196b309668044261bea07bada341899186d22b00 --- /dev/null +++ b/data/api_keys.example.json @@ -0,0 +1,5 @@ +{ + "sk-text@example": [ + "user_XXXXXXXXXXXX%3A%3AeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" + ] +} \ No newline at end of file diff --git a/data/invalid_cookies.example.json b/data/invalid_cookies.example.json new file mode 100644 index 0000000000000000000000000000000000000000..f5b3cfaf727febd91d6aad0d2833e286104e57c5 --- /dev/null +++ b/data/invalid_cookies.example.json @@ -0,0 +1,3 @@ +[ + "user_XXXXXXXXXXXX%3A%3AeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" +] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b33034900dbebf02c107b670046813edd777375b --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,14 @@ +version: '3.8' + +services: + app: + build: + context: . + dockerfile: Dockerfile + image: cursor-to-openai + volumes: + - ./data:/app/data + ports: + - "3010:3010" + env_file: + - .env \ No newline at end of file diff --git a/manage-emails.js b/manage-emails.js new file mode 100644 index 0000000000000000000000000000000000000000..da728800bf3d2289199061e29936605cafe32082 --- /dev/null +++ b/manage-emails.js @@ -0,0 +1,309 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const dotenv = require('dotenv'); + +// 创建交互式命令行界面 +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// 加载环境变量 +const ENV_FILE_PATH = path.join(process.cwd(), '.env'); +let envContent = ''; +let emailConfigs = []; + +// 应用密码说明 +function printAppPasswordInstructions() { + console.log('\n===== 如何创建谷歌应用密码 ====='); + console.log('1. 访问 https://myaccount.google.com/security'); + console.log('2. 在"登录Google"部分,点击"两步验证"'); + console.log(' (如果未启用两步验证,需要先启用)'); + console.log('3. 在页面底部找到"应用密码",点击进入'); + console.log('4. 在"选择应用"下拉菜单中选择"其他(自定义名称)"'); + console.log('5. 输入一个名称,例如"Cursor注册"'); + console.log('6. 点击"生成"'); + console.log('7. 复制生成的16位应用密码(格式如:xxxx xxxx xxxx xxxx)'); + console.log('注意: 应用密码只会显示一次,请务必保存好\n'); +} + +// 加载当前环境变量和邮箱配置 +function loadEnvironment() { + try { + if (!fs.existsSync(ENV_FILE_PATH)) { + console.error('❌ .env文件不存在,请先运行setup.js进行初始化配置'); + process.exit(1); + } + + // 读取原始.env文件内容 + envContent = fs.readFileSync(ENV_FILE_PATH, 'utf8'); + + // 解析环境变量 + dotenv.config(); + + // 尝试解析当前的邮箱配置 + try { + const configStr = process.env.REGISTER_EMAIL_CONFIGS; + if (configStr) { + emailConfigs = JSON.parse(configStr); + if (!Array.isArray(emailConfigs)) { + emailConfigs = []; + } + } + } catch (parseErr) { + console.warn('⚠️ 解析当前邮箱配置出错,将使用空配置'); + emailConfigs = []; + } + + return true; + } catch (error) { + console.error(`❌ 加载环境变量失败: ${error.message}`); + return false; + } +} + +// 保存更新后的邮箱配置到.env文件 +function saveEmailConfigs() { + try { + // 将邮箱配置格式化为JSON字符串 + const configStr = JSON.stringify(emailConfigs); + + // 替换.env文件中的配置 + let newEnvContent = ''; + + if (envContent.includes('REGISTER_EMAIL_CONFIGS=')) { + // 使用正则表达式替换REGISTER_EMAIL_CONFIGS行 + newEnvContent = envContent.replace( + /REGISTER_EMAIL_CONFIGS=.*/, + `REGISTER_EMAIL_CONFIGS=${configStr}` + ); + } else { + // 如果不存在该配置行,添加到文件末尾 + newEnvContent = `${envContent}\nREGISTER_EMAIL_CONFIGS=${configStr}`; + } + + // 同时确保USE_CONFIG_FILE设置为false + if (newEnvContent.includes('REGISTER_USE_CONFIG_FILE=')) { + newEnvContent = newEnvContent.replace( + /REGISTER_USE_CONFIG_FILE=.*/, + 'REGISTER_USE_CONFIG_FILE=false' + ); + } else { + newEnvContent = `${newEnvContent}\nREGISTER_USE_CONFIG_FILE=false`; + } + + // 确保EMAIL_SERVER设置为IMAP + if (newEnvContent.includes('REGISTER_EMAIL_SERVER=')) { + newEnvContent = newEnvContent.replace( + /REGISTER_EMAIL_SERVER=.*/, + 'REGISTER_EMAIL_SERVER=IMAP' + ); + } else { + newEnvContent = `${newEnvContent}\nREGISTER_EMAIL_SERVER=IMAP`; + } + + // 写入更新后的内容 + fs.writeFileSync(ENV_FILE_PATH, newEnvContent, 'utf8'); + + console.log('✅ 邮箱配置已成功保存到.env文件'); + return true; + } catch (error) { + console.error(`❌ 保存邮箱配置失败: ${error.message}`); + return false; + } +} + +// 显示所有已配置的邮箱 +function displayEmails() { + console.log('\n===== 当前已配置的邮箱 ====='); + + if (emailConfigs.length === 0) { + console.log('暂无已配置的邮箱'); + return; + } + + emailConfigs.forEach((config, index) => { + console.log(`[${index + 1}] ${config.email}`); + console.log(` IMAP服务器: ${config.imap_server}`); + console.log(` IMAP端口: ${config.imap_port}`); + console.log(` 用户名: ${config.username}`); + console.log(` 应用密码: ${config.password}`); + console.log(''); + }); +} + +// 添加新邮箱 +function addEmail() { + console.log('\n===== 添加新邮箱 ====='); + printAppPasswordInstructions(); + + rl.question('请输入Gmail地址: ', (email) => { + rl.question('请输入Gmail的应用密码 (不是邮箱密码): ', (password) => { + // 创建新配置 + const newConfig = { + email: email, + imap_server: 'imap.gmail.com', + imap_port: 993, + username: email, + password: password + }; + + // 添加到配置列表 + emailConfigs.push(newConfig); + + console.log(`\n✅ 已添加邮箱: ${email}`); + + // 保存到.env文件 + if (saveEmailConfigs()) { + showMainMenu(); + } + }); + }); +} + +// 修改邮箱 +function modifyEmail() { + if (emailConfigs.length === 0) { + console.log('\n❌ 当前没有可修改的邮箱。请先添加邮箱。'); + showMainMenu(); + return; + } + + console.log('\n===== 修改邮箱 ====='); + displayEmails(); + + rl.question('请输入要修改的邮箱序号 (1-' + emailConfigs.length + '): ', (indexStr) => { + const index = parseInt(indexStr) - 1; + + if (isNaN(index) || index < 0 || index >= emailConfigs.length) { + console.log('\n❌ 无效的序号。请重新选择。'); + modifyEmail(); + return; + } + + const currentConfig = emailConfigs[index]; + + console.log(`\n正在修改邮箱: ${currentConfig.email}`); + + rl.question(`新的Gmail地址 (当前: ${currentConfig.email},直接回车保持不变): `, (email) => { + const newEmail = email.trim() === '' ? currentConfig.email : email; + + rl.question('新的应用密码 (直接回车保持不变): ', (password) => { + const newPassword = password.trim() === '' ? currentConfig.password : password; + + // 更新配置 + emailConfigs[index] = { + email: newEmail, + imap_server: 'imap.gmail.com', + imap_port: 993, + username: newEmail, + password: newPassword + }; + + console.log(`\n✅ 已修改邮箱配置: ${newEmail}`); + + // 保存到.env文件 + if (saveEmailConfigs()) { + showMainMenu(); + } + }); + }); + }); +} + +// 删除邮箱 +function deleteEmail() { + if (emailConfigs.length === 0) { + console.log('\n❌ 当前没有可删除的邮箱。'); + showMainMenu(); + return; + } + + console.log('\n===== 删除邮箱 ====='); + displayEmails(); + + rl.question('请输入要删除的邮箱序号 (1-' + emailConfigs.length + '): ', (indexStr) => { + const index = parseInt(indexStr) - 1; + + if (isNaN(index) || index < 0 || index >= emailConfigs.length) { + console.log('\n❌ 无效的序号。请重新选择。'); + deleteEmail(); + return; + } + + const emailToDelete = emailConfigs[index].email; + + rl.question(`确认删除邮箱 "${emailToDelete}"? (y/n): `, (answer) => { + if (answer.toLowerCase() === 'y') { + // 删除邮箱 + emailConfigs.splice(index, 1); + + console.log(`\n✅ 已删除邮箱: ${emailToDelete}`); + + // 保存到.env文件 + if (saveEmailConfigs()) { + showMainMenu(); + } + } else { + console.log('\n操作已取消'); + showMainMenu(); + } + }); + }); +} + +// 显示主菜单 +function showMainMenu() { + console.log('\n===== 邮箱配置管理 ====='); + console.log('1. 查看所有邮箱'); + console.log('2. 添加新邮箱'); + console.log('3. 修改邮箱'); + console.log('4. 删除邮箱'); + console.log('0. 退出'); + + rl.question('请选择操作 (0-4): ', (choice) => { + switch (choice) { + case '1': + displayEmails(); + showMainMenu(); + break; + case '2': + addEmail(); + break; + case '3': + modifyEmail(); + break; + case '4': + deleteEmail(); + break; + case '0': + console.log('\n✅ 配置完成,退出程序'); + rl.close(); + break; + default: + console.log('\n❌ 无效的选择,请重新输入'); + showMainMenu(); + break; + } + }); +} + +// 主函数 +async function main() { + console.log('===== Cursor-To-OpenAI 邮箱配置管理 ====='); + + // 加载当前配置 + if (loadEnvironment()) { + // 显示主菜单 + showMainMenu(); + } else { + console.error('程序退出'); + rl.close(); + } +} + +// 运行主函数 +main(); \ No newline at end of file diff --git a/manage-invalid-cookies.js b/manage-invalid-cookies.js new file mode 100644 index 0000000000000000000000000000000000000000..466b98b08f1617d96053cdd911192cc8b407f371 --- /dev/null +++ b/manage-invalid-cookies.js @@ -0,0 +1,173 @@ +// 加载环境变量 +require('dotenv').config(); + +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const keyManager = require('./src/utils/keyManager'); +const logger = require('./src/utils/logger'); + +// 创建命令行交互界面 +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// 初始化API Keys +keyManager.initializeApiKeys(); + +// 显示菜单 +function showMenu() { + logger.info('\n===== 无效Cookie管理工具 ====='); + logger.info('1. 查看所有无效Cookie'); + logger.info('2. 添加无效Cookie'); + logger.info('3. 删除特定无效Cookie'); + logger.info('4. 清空所有无效Cookie'); + logger.info('5. 从API Keys中移除所有无效Cookie'); + logger.info('6. 退出'); + logger.info('============================'); + + rl.question('请选择操作 (1-6): ', (answer) => { + switch(answer) { + case '1': + listInvalidCookies(); + break; + case '2': + addInvalidCookie(); + break; + case '3': + removeInvalidCookie(); + break; + case '4': + clearAllInvalidCookies(); + break; + case '5': + removeInvalidCookiesFromApiKeys(); + break; + case '6': + logger.info('退出程序'); + rl.close(); + break; + default: + logger.warn('无效的选择,请重新输入'); + showMenu(); + break; + } + }); +} + +// 查看所有无效Cookie +function listInvalidCookies() { + const invalidCookies = Array.from(keyManager.getInvalidCookies()); + + logger.info('\n===== 所有无效Cookie ====='); + if (invalidCookies.length === 0) { + logger.info('没有无效Cookie'); + } else { + invalidCookies.forEach((cookie, index) => { + logger.info(`${index + 1}. ${cookie}`); + }); + } + + showMenu(); +} + +// 添加无效Cookie +function addInvalidCookie() { + rl.question('\n请输入要添加的无效Cookie: ', (cookie) => { + if (!cookie.trim()) { + logger.warn('Cookie不能为空'); + showMenu(); + return; + } + + // 将cookie添加到无效集合 + const invalidCookies = new Set(keyManager.getInvalidCookies()); + invalidCookies.add(cookie.trim()); + + // 保存到文件 + const INVALID_COOKIES_FILE = path.join(__dirname, 'data/invalid_cookies.json'); + try { + // 确保目录存在 + const dataDir = path.join(__dirname, 'data'); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + + fs.writeFileSync(INVALID_COOKIES_FILE, JSON.stringify(Array.from(invalidCookies), null, 2), 'utf8'); + logger.info('无效Cookie添加成功'); + + // 重新加载无效cookie + keyManager.loadInvalidCookiesFromFile(); + } catch (err) { + logger.error('保存无效Cookie失败:', err); + } + + showMenu(); + }); +} + +// 删除特定无效Cookie +function removeInvalidCookie() { + const invalidCookies = Array.from(keyManager.getInvalidCookies()); + + if (invalidCookies.length === 0) { + logger.warn('\n没有无效Cookie可删除'); + showMenu(); + return; + } + + logger.info('\n===== 所有无效Cookie ====='); + invalidCookies.forEach((cookie, index) => { + logger.info(`${index + 1}. ${cookie}`); + }); + + rl.question('\n请输入要删除的Cookie编号 (1-' + invalidCookies.length + '): ', (answer) => { + const index = parseInt(answer) - 1; + + if (isNaN(index) || index < 0 || index >= invalidCookies.length) { + logger.warn('无效的编号'); + showMenu(); + return; + } + + const cookieToRemove = invalidCookies[index]; + const result = keyManager.clearInvalidCookie(cookieToRemove); + + if (result) { + logger.info(`成功删除无效Cookie: ${cookieToRemove}`); + } else { + logger.warn('删除失败'); + } + + showMenu(); + }); +} + +// 清空所有无效Cookie +function clearAllInvalidCookies() { + rl.question('\n确定要清空所有无效Cookie吗? (y/n): ', (answer) => { + if (answer.toLowerCase() === 'y') { + keyManager.clearAllInvalidCookies(); + logger.info('所有无效Cookie已清空'); + } else { + logger.info('操作已取消'); + } + + showMenu(); + }); +} + +// 从API Keys中移除所有无效Cookie +function removeInvalidCookiesFromApiKeys() { + // 重新初始化API Keys,这会自动移除无效cookie + keyManager.initializeApiKeys(); + logger.info('已从API Keys中移除所有无效Cookie'); + + showMenu(); +} + +// 启动程序 +logger.info('正在加载无效Cookie...'); +keyManager.loadInvalidCookiesFromFile(); +showMenu(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..b94ed4e66a0fcdf6b3e7cbdc13e9f71b3f66f094 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2457 @@ +{ + "name": "cursor-to-openai", + "version": "1.3.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cursor-to-openai", + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "@octokit/rest": "^20.0.2", + "adm-zip": "^0.5.16", + "axios": "^1.6.7", + "cookie-parser": "^1.4.7", + "csv-parser": "^3.0.0", + "dotenv": "^16.4.7", + "express": "4.21.2", + "jsonwebtoken": "^9.0.2", + "logger": "^0.0.1", + "morgan": "^1.10.0", + "node-cron": "^3.0.3", + "node-fetch": "^2.7.0", + "open": "^10.1.0", + "protobufjs": "^7.4.0", + "undici": "^6.21.2", + "uuid": "11.0.5" + }, + "devDependencies": { + "protobufjs-cli": "^1.1.3" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", + "integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/core": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.0.tgz", + "integrity": "sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^4.0.0", + "@octokit/graphql": "^7.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/endpoint": { + "version": "9.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.6.tgz", + "integrity": "sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/graphql": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.1.1.tgz", + "integrity": "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^8.4.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "11.4.4-cjs.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.4-cjs.2.tgz", + "integrity": "sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.7.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.1.tgz", + "integrity": "sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "5" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "13.3.2-cjs.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-13.3.2-cjs.1.tgz", + "integrity": "sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.8.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^5" + } + }, + "node_modules/@octokit/request": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.4.1.tgz", + "integrity": "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^9.0.6", + "@octokit/request-error": "^5.1.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/request-error": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.1.1.tgz", + "integrity": "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^13.1.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/rest": { + "version": "20.1.2", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.1.2.tgz", + "integrity": "sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==", + "license": "MIT", + "dependencies": { + "@octokit/core": "^5.0.2", + "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", + "@octokit/plugin-request-log": "^4.0.0", + "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/types": { + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", + "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^23.0.1" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", + "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "license": "Apache-2.0" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/csv-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.2.0.tgz", + "integrity": "sha512-fgKbp+AJbn1h2dcAHKIdKNSSjfp43BZZykXsCjzALjKy80VXQNHPFJ6T9Afwdzoj24aMkq8GwDS7KGcDPpejrA==", + "license": "MIT", + "bin": { + "csv-parser": "bin/csv-parser" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "license": "ISC" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logger": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/logger/-/logger-0.0.1.tgz", + "integrity": "sha512-UD45f4iZrsj6dQKt5QBN7K+R0hmFwGS8G+Pv8WtHjrnhrMQftIclma8b86mNtg1LKB6HDIOW/ZtjnXELBhr89w==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/long": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", + "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", + "license": "Apache-2.0" + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.3.tgz", + "integrity": "sha512-MqD10lqF+FMsOayFiNOdOGNlXc4iKDCf0ZQPkPR+gizYh9gqUeGTWulABUCdI+N67w5RfJ6xhgX4J8pa8qmMXQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", + "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.21.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", + "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "license": "ISC" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.5.tgz", + "integrity": "sha512-508e6IcKLrhxKdBbcA2b4KQZlLVp2+J5UwQ6F7Drckkc5N9ZJwFa4TgWtsww9UG8fGHbm6gbV19TdM5pQ4GaIA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..8c843f9234cfcbc4568252ce5097ab2cb70f2ef9 --- /dev/null +++ b/package.json @@ -0,0 +1,41 @@ +{ + "name": "cursor-to-openai", + "version": "1.3.0", + "description": "Cursor to OpenAPI", + "author": "JiuZ-Chn", + "private": false, + "main": "index.js", + "url": "https://github.com/JiuZ-Chn/Cursor-To-OpenAI", + "license": "MIT", + "dependencies": { + "@octokit/rest": "^20.0.2", + "adm-zip": "^0.5.16", + "axios": "^1.6.7", + "cookie-parser": "^1.4.7", + "csv-parser": "^3.0.0", + "dotenv": "^16.4.7", + "express": "4.21.2", + "jsonwebtoken": "^9.0.2", + "logger": "^0.0.1", + "morgan": "^1.10.0", + "node-cron": "^3.0.3", + "node-fetch": "^2.7.0", + "open": "^10.1.0", + "protobufjs": "^7.4.0", + "undici": "^6.21.2", + "uuid": "11.0.5" + }, + "scripts": { + "proto": "npx pbjs -t static-module -w commonjs -o src/proto/message.js src/proto/message.proto", + "start": "node src/app.js", + "setup": "node setup.js", + "manage-emails": "node manage-emails.js", + "refresh-cookies": "node auto-refresh-cookies.js", + "refresh-cookies:force": "node auto-refresh-cookies.js --force", + "refresh-cookies:api": "node auto-refresh-cookies.js", + "manage-cookies": "node manage-invalid-cookies.js" + }, + "devDependencies": { + "protobufjs-cli": "^1.1.3" + } +} diff --git a/project.sh b/project.sh new file mode 100644 index 0000000000000000000000000000000000000000..f500ba034df6488fd26a1c24f7661c6d4b841e63 --- /dev/null +++ b/project.sh @@ -0,0 +1,51 @@ +#!/data/data/com.termux/files/usr/bin/bash + +# 项目管理脚本(数字选择版) + +echo "请选择操作:" +echo "1. 更新 cookie" +echo "2. 启动项目" +echo "3. 管理邮箱" +echo "4. 初始化配置" +echo "5. 更新项目代码" +echo "6. 备份项目" +echo "7. 退出" + +read -p "输入数字 (1-7): " choice + +case $choice in + 1) + echo "正在更新 cookie..." + npm run refresh-cookies + ;; + 2) + echo "正在启动项目..." + npm start + ;; + 3) + echo "正在管理邮箱..." + npm run manage-emails + ;; + 4) + echo "正在初始化配置文件..." + npm run setup + ;; + 5) + echo "正在更新项目代码..." + git pull + ;; + 6) + echo "正在备份项目..." + DATE=$(date +%Y%m%d_%H%M%S) + tar -czf "backup_$DATE.tar.gz" . + echo "备份完成: backup_$DATE.tar.gz" + ;; + 7) + echo "退出" + exit 0 + ;; + *) + echo "错误:请输入 1-7 之间的数字" + exit 1 + ;; +esac \ No newline at end of file diff --git a/scripts/create-admin.js b/scripts/create-admin.js new file mode 100644 index 0000000000000000000000000000000000000000..cb99aa2f991dc0bd6f20e195b11db3bb589a5209 --- /dev/null +++ b/scripts/create-admin.js @@ -0,0 +1,71 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const readline = require('readline'); + +const ADMIN_FILE = path.join(__dirname, '../data/admin.json'); + +// 创建readline接口 +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// 生成盐值 +function generateSalt() { + return crypto.randomBytes(16).toString('hex'); +} + +// 使用盐值哈希密码 +function hashPassword(password, salt) { + return crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex'); +} + +// 提示用户输入 +function question(query) { + return new Promise((resolve) => { + rl.question(query, resolve); + }); +} + +async function main() { + try { + console.log('创建管理员账户\n'); + + // 获取用户输入 + const username = await question('请输入管理员用户名: '); + const password = await question('请输入管理员密码: '); + + // 生成盐值和密码哈希 + const salt = generateSalt(); + const hash = hashPassword(password, salt); + + // 创建管理员数据 + const adminData = { + admin: { + username, + salt, + hash + } + }; + + // 确保data目录存在 + const dataDir = path.dirname(ADMIN_FILE); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + + // 写入文件 + fs.writeFileSync(ADMIN_FILE, JSON.stringify(adminData, null, 2)); + + console.log('\n管理员账户创建成功!'); + console.log('请妥善保管账户信息,不要将admin.json文件提交到版本控制系统。'); + + } catch (error) { + console.error('创建管理员账户失败:', error); + } finally { + rl.close(); + } +} + +main(); \ No newline at end of file diff --git a/setup.js b/setup.js new file mode 100644 index 0000000000000000000000000000000000000000..3629c6155405b011c705fbb292617a68b9adb7f4 --- /dev/null +++ b/setup.js @@ -0,0 +1,296 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const readline = require('readline'); +const dotenv = require('dotenv'); + +// 创建交互式命令行界面 +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}); + +// 配置模板 +const ENV_TEMPLATE = `# 服务端口 +PORT=3010 + +# 日志格式 (tiny, combined, common, dev, short) +MORGAN_FORMAT=tiny + +# API Key与Cookie映射关系 (JSON格式) +# 格式: {"自定义API Key": "Cookie值"} 或 {"自定义API Key": ["Cookie值1", "Cookie值2"]} +API_KEYS={API_KEYS_PLACEHOLDER} + +# 轮询策略 (random 或 round-robin 或 default) +ROTATION_STRATEGY=default + +# 是否使用TLS代理 (true 或 false) +USE_TLS_PROXY={USE_TLS_PROXY_PLACEHOLDER} + +# 是否使用辅助代理服务器 (true 或 false) +USE_OTHERS_PROXY={USE_OTHERS_PROXY_PLACEHOLDER} + +# 代理服务器平台 +# 可选值: auto, windows_x64, linux_x64, android_arm64 +# auto: 自动检测平台 +# windows_x64: Windows 64位 +# linux_x64: Linux 64位 +# android_arm64: 安卓ARM 64位 +PROXY_PLATFORM={PROXY_PLATFORM_PLACEHOLDER} + +# 是否使用其它接口 (true 或 false) +USE_OTHERS={USE_OTHERS_PLACEHOLDER} +`; + +// 提示信息 +console.log('===== Cursor-To-OpenAI 环境配置助手 ====='); +console.log('这个脚本将帮助你配置必要的环境变量\n'); + +// 从现有.env文件加载配置 +function loadExistingConfig() { + const envPath = path.join(process.cwd(), '.env'); + let existingConfig = { + apiKeys: {}, + useTlsProxy: true, + useOthersProxy: true, + proxyPlatform: 'auto', + useOthers: true, + rotationStrategy: 'default' + }; + + if (fs.existsSync(envPath)) { + console.log('发现现有的.env配置文件,将加载现有设置作为默认值'); + console.log('提示: 直接按回车将保留现有设置不变\n'); + + try { + // 加载.env文件 + const envConfig = dotenv.parse(fs.readFileSync(envPath)); + + // 提取API Keys + if (envConfig.API_KEYS) { + try { + existingConfig.apiKeys = JSON.parse(envConfig.API_KEYS); + } catch (e) { + console.log('无法解析现有的API Keys配置,将使用默认设置'); + } + } + + // 提取TLS代理配置 + if (envConfig.USE_TLS_PROXY !== undefined) { + existingConfig.useTlsProxy = envConfig.USE_TLS_PROXY === 'true'; + } + + // 提取辅助代理服务器配置 + if (envConfig.USE_OTHERS_PROXY !== undefined) { + existingConfig.useOthersProxy = envConfig.USE_OTHERS_PROXY === 'true'; + } + + // 提取代理服务器平台 + if (envConfig.PROXY_PLATFORM) { + existingConfig.proxyPlatform = envConfig.PROXY_PLATFORM; + } + + // 提取是否使用其它接口 + if (envConfig.USE_OTHERS !== undefined) { + existingConfig.useOthers = envConfig.USE_OTHERS === 'true'; + } + + // 提取轮询策略 + if (envConfig.ROTATION_STRATEGY) { + existingConfig.rotationStrategy = envConfig.ROTATION_STRATEGY; + } + + console.log('成功加载现有配置'); + } catch (error) { + console.error('加载现有配置时出错:', error.message); + console.log('将使用默认设置'); + } + } else { + console.log('未找到现有的.env配置文件,将创建新的配置文件'); + } + + return existingConfig; +} + +// 提示用户输入,带有默认值 +function promptWithDefault(question, defaultValue) { + return new Promise((resolve) => { + const defaultText = defaultValue ? ` [${defaultValue}]` : ''; + rl.question(`${question}${defaultText}: `, (answer) => { + // 如果用户只按了回车,使用默认值 + resolve(answer.trim() || defaultValue || ''); + }); + }); +} + +// 收集配置信息 +async function collectConfig() { + // 加载现有配置 + const existingConfig = loadExistingConfig(); + + const config = { + apiKeys: {}, + useTlsProxy: existingConfig.useTlsProxy, + useOthersProxy: existingConfig.useOthersProxy, + proxyPlatform: existingConfig.proxyPlatform, + useOthers: existingConfig.useOthers, + rotationStrategy: existingConfig.rotationStrategy + }; + + // 询问是否使用TLS代理 + const useTlsProxyPrompt = `是否使用TLS代理服务器? (y/n)`; + const defaultUseTlsProxy = existingConfig.useTlsProxy ? 'y' : 'n'; + const useTlsProxyAnswer = await promptWithDefault(useTlsProxyPrompt, defaultUseTlsProxy); + config.useTlsProxy = useTlsProxyAnswer.toLowerCase() === 'y'; + + if (config.useTlsProxy) { + // 询问是否使用辅助代理服务器 + const useOthersProxyPrompt = `是否使用辅助代理服务器(port 10654)? (y/n)`; + const defaultUseOthersProxy = existingConfig.useOthersProxy ? 'y' : 'n'; + const useOthersProxyAnswer = await promptWithDefault(useOthersProxyPrompt, defaultUseOthersProxy); + config.useOthersProxy = useOthersProxyAnswer.toLowerCase() === 'y'; + + // 询问代理服务器平台 + console.log('\n代理服务器平台选项:'); + console.log('- auto: 自动检测当前系统平台'); + console.log('- windows_x64: Windows 64位'); + console.log('- linux_x64: Linux 64位'); + console.log('- android_arm64: 安卓ARM 64位'); + + const proxyPlatformPrompt = `选择代理服务器平台`; + const defaultProxyPlatform = existingConfig.proxyPlatform || 'auto'; + config.proxyPlatform = await promptWithDefault(proxyPlatformPrompt, defaultProxyPlatform); + } + + // 询问是否使用其它接口 + const useOthersPrompt = `是否使用其它接口? (y/n)`; + const defaultUseOthers = existingConfig.useOthers ? 'y' : 'n'; + const useOthersAnswer = await promptWithDefault(useOthersPrompt, defaultUseOthers); + config.useOthers = useOthersAnswer.toLowerCase() === 'y'; + + // 询问轮询策略 + console.log('\n轮询策略选项:'); + console.log('- default: 默认策略'); + console.log('- random: 随机策略'); + console.log('- round-robin: 轮询策略'); + + const rotationStrategyPrompt = `选择轮询策略`; + const defaultRotationStrategy = existingConfig.rotationStrategy || 'default'; + config.rotationStrategy = await promptWithDefault(rotationStrategyPrompt, defaultRotationStrategy); + + // 处理API Keys + const existingApiKeys = Object.keys(existingConfig.apiKeys); + if (existingApiKeys.length > 0) { + console.log('\n现有的API Keys:'); + existingApiKeys.forEach(key => console.log(`- ${key}`)); + + const keepExistingApiKeys = await promptWithDefault('是否保留现有的API Keys? (y/n)', 'y'); + if (keepExistingApiKeys.toLowerCase() === 'y') { + config.apiKeys = { ...existingConfig.apiKeys }; + } + } + + // 询问是否添加新的API Key + const addNewApiKey = await promptWithDefault('是否添加新的API Key? (y/n)', existingApiKeys.length === 0 ? 'y' : 'n'); + if (addNewApiKey.toLowerCase() === 'y') { + const apiKey = await promptWithDefault('请输入自定义的API Key (不含sk-前缀,将自动添加)', ''); + if (apiKey) { + const fullApiKey = apiKey.startsWith('sk-') ? apiKey : `sk-${apiKey}`; + config.apiKeys[fullApiKey] = []; + } else { + // 如果用户直接回车跳过,默认添加 sk-text + config.apiKeys['sk-text'] = []; + console.log('已默认添加API Key: sk-text'); + } + } else if (Object.keys(config.apiKeys).length === 0) { + // 如果没有任何API Key,默认添加 sk-text + config.apiKeys['sk-text'] = []; + console.log('已默认添加API Key: sk-text'); + } + + return config; +} + +// 生成配置文件 +function generateEnvFile(config) { + try { + // 准备API Keys + const apiKeysJson = JSON.stringify(config.apiKeys); + + // 替换模板中的占位符 + let envContent = ENV_TEMPLATE + .replace('{API_KEYS_PLACEHOLDER}', apiKeysJson) + .replace('{USE_TLS_PROXY_PLACEHOLDER}', config.useTlsProxy) + .replace('{USE_OTHERS_PROXY_PLACEHOLDER}', config.useOthersProxy) + .replace('{PROXY_PLATFORM_PLACEHOLDER}', config.proxyPlatform) + .replace('{USE_OTHERS_PLACEHOLDER}', config.useOthers); + + // 更新轮询策略 + envContent = envContent.replace('ROTATION_STRATEGY=default', `ROTATION_STRATEGY=${config.rotationStrategy}`); + + // 写入.env文件 + const envPath = path.join(process.cwd(), '.env'); + + // 检查是否存在备份文件 + const backupPath = path.join(process.cwd(), '.env.backup'); + if (fs.existsSync(envPath)) { + // 创建备份 + fs.copyFileSync(envPath, backupPath); + console.log(`\n✅ 已创建原配置文件备份: ${backupPath}`); + } + + fs.writeFileSync(envPath, envContent, 'utf8'); + console.log(`\n✅ 配置文件已生成: ${envPath}`); + + // 检查data目录 + const dataDir = path.join(process.cwd(), 'data'); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + console.log(`✅ 创建数据目录: ${dataDir}`); + } + + return true; + } catch (error) { + console.error('\n❌ 生成配置文件时出错:', error.message); + return false; + } +} + +// 主函数 +async function main() { + try { + const config = await collectConfig(); + + if (generateEnvFile(config)) { + console.log('\n===== 配置完成 ====='); + console.log('你可以使用以下命令启动服务:'); + console.log(' npm start'); + + // 显示TLS代理配置信息 + console.log(`\n当前TLS代理配置:`); + console.log(`- 是否启用TLS代理: ${config.useTlsProxy ? '是' : '否'}`); + if (config.useTlsProxy) { + console.log(`- 是否启用辅助代理服务器: ${config.useOthersProxy ? '是' : '否'}`); + console.log(`- 代理服务器平台: ${config.proxyPlatform}`); + } + + // 显示是否使用其它接口配置信息 + console.log(`\n当前是否使用其它接口: ${config.useOthers ? '是' : '否'}`); + + // 显示轮询策略 + console.log(`\n当前轮询策略: ${config.rotationStrategy}`); + + // 显示API Keys + console.log('\n当前配置的API Keys:'); + Object.keys(config.apiKeys).forEach(key => console.log(`- ${key}`)); + } + } catch (error) { + console.error('\n❌ 配置过程中出错:', error.message); + } finally { + rl.close(); + } +} + +// 运行主函数 +main(); \ No newline at end of file diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000000000000000000000000000000000000..374dccbeff3b48c816cc8241707b278ec425fac0 --- /dev/null +++ b/src/app.js @@ -0,0 +1,202 @@ +// 加载环境变量 +require('dotenv').config(); + +// 在logger加载前添加临时日志函数 +function tempLog(level, message) { + const timestamp = new Date().toISOString(); + if (level === 'ERROR') { + console.error(`[ERROR] ${timestamp} ${message}`); + } else if (level === 'WARN') { + console.warn(`[WARN] ${timestamp} ${message}`); + } else { + console.log(`[INFO] ${timestamp} ${message}`); + } +} + +// 环境检查 +tempLog('INFO', '启动前检查环境配置...'); +const envChecker = require('./utils/envChecker'); +// 先执行简单检查,避免循环依赖 +envChecker.enforceEnvCheck(); + +const express = require('express'); +const morgan = require('morgan'); +const path = require('path'); +const cron = require('node-cron'); +const app = express(); +const cookieParser = require('cookie-parser'); +const { spawn } = require('child_process'); + +// 先加载配置,再加载logger +const config = require('./config/config'); +const logger = require('./utils/logger'); +const routes = require('./routes'); +const keyManager = require('./utils/keyManager'); +const cookieRefresher = require('./utils/cookieRefresher'); +const authMiddleware = require('./middleware/auth'); +const proxyLauncher = require('./utils/proxyLauncher'); + +// 初始化代理服务器 +if (process.env.USE_TLS_PROXY === 'true') { + logger.info('正在启动TLS代理服务器...'); + proxyLauncher.startProxyServer(); +} else { + logger.info('TLS代理服务器未启用,跳过启动代理'); +} + +// 加载路由 +const v1Router = require('./routes/v1'); + +// 初始化API Keys +logger.info('初始化API Keys...'); +keyManager.initializeApiKeys(); + +// 输出最终的API Keys配置 +logger.debug('最终API Keys配置:', JSON.stringify(keyManager.getAllApiKeys().reduce((obj, key) => { + obj[key] = keyManager.getAllCookiesForApiKey(key); + return obj; +}, {}), null, 2)); + +// 输出每个API key的Cookie数量信息 +const apiKeys = keyManager.getAllApiKeys(); +const keySummary = apiKeys.map(key => { + const cookies = keyManager.getAllCookiesForApiKey(key); + return `${key}: ${cookies.length}个Cookie`; +}).join(', '); + +logger.info(`当前已加载 ${apiKeys.length} 个API Key,详情: ${keySummary}`); + +// 添加CORS支持 +app.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); + res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + + if (req.method === 'OPTIONS') { + return res.status(200).end(); + } + + next(); +}); + +app.use(express.json({ limit: '50mb' })); +app.use(express.urlencoded({ extended: true, limit: '50mb' })); +app.use(cookieParser()); + +// 自定义Morgan格式,将日志输出到我们的日志系统 +morgan.token('remote-addr', (req) => { + return req.headers['x-forwarded-for'] || req.socket.remoteAddress; +}); + +// 创建一个将 Morgan 日志写入我们的日志系统的流 +const morganLoggerStream = { + write: (message) => { + // 移除行尾的换行符 + const trimmedMessage = message.trim(); + if (trimmedMessage) { + logger.http(trimmedMessage); + } + } +}; + +// 使用自定义格式的 Morgan 中间件 +app.use(morgan(process.env.MORGAN_FORMAT || 'combined', { + stream: morganLoggerStream, + // 跳过健康检查等路由的日志 + skip: (req, res) => { + return req.path === '/health' || req.path === '/favicon.ico'; + } +})); + +// 添加静态文件支持 +app.use(express.static(path.join(__dirname, 'public'))); + +// 添加根路由,重定向到登录页面 +app.get('/', (req, res) => { + res.redirect('/login.html'); +}); + +// 添加认证中间件 +app.use(authMiddleware); + +// API路由 +app.use('/v1', v1Router); + +app.use("/", routes) + +// 设置自动定时刷新Cookie任务 +if (config.refresh.enabled) { + logger.info(`已启用自动刷新 Cookie,定时任务将在每 ${config.refresh.interval} 运行`); + cron.schedule(config.refresh.interval, () => { + logger.info('开始定时自动刷新 Cookie...'); + const scriptPath = path.resolve(__dirname, '../auto-refresh-cookies.js'); + + const child = spawn('node', [scriptPath], { + stdio: ['ignore', 'pipe', 'pipe'] + }); + + child.stdout.on('data', (data) => { + logger.info(`刷新进程输出: ${data.toString().trim()}`); + }); + + child.stderr.on('data', (data) => { + logger.error(`刷新进程错误: ${data.toString().trim()}`); + }); + + child.on('close', (code) => { + if (code === 0) { + logger.info('自动刷新 Cookie 定时任务完成'); + } else { + logger.error(`自动刷新 Cookie 定时任务异常退出,代码: ${code}`); + } + }); + }); +} else { + logger.info('未启用自动刷新 Cookie,如需启用请设置环境变量 ENABLE_AUTO_REFRESH=true'); +} + +// 错误处理中间件 +app.use((err, req, res, next) => { + logger.error('服务器错误:', err); + res.status(500).json({ + error: 'Internal server error', + message: err.message + }); +}); + +// 处理404请求 +app.use((req, res) => { + logger.warn(`未找到路由: ${req.method} ${req.url}`); + res.status(404).json({ + error: 'Not found', + message: '请求的资源不存在' + }); +}); + +app.listen(config.port, () => { + logger.info(`服务器已启动,监听端口: ${config.port}`); + logger.info(`打开管理界面: http://localhost:${config.port}`); +}); + +// 处理进程退出事件,清理资源 +process.on('SIGINT', () => { + logger.info('接收到SIGINT信号,正在优雅关闭服务...'); + // 停止代理服务器 + if (process.env.USE_TLS_PROXY === 'true') { + logger.info('正在停止TLS代理服务器...'); + proxyLauncher.stopProxyServer(); + } + process.exit(0); +}); + +process.on('SIGTERM', () => { + logger.info('接收到SIGTERM信号,正在优雅关闭服务...'); + // 停止代理服务器 + if (process.env.USE_TLS_PROXY === 'true') { + logger.info('正在停止TLS代理服务器...'); + proxyLauncher.stopProxyServer(); + } + process.exit(0); +}); + +module.exports = app; diff --git a/src/config/CURSOR_CONFIG.md b/src/config/CURSOR_CONFIG.md new file mode 100644 index 0000000000000000000000000000000000000000..67376276d2cec0b71ba016f80584f1786605146a --- /dev/null +++ b/src/config/CURSOR_CONFIG.md @@ -0,0 +1,238 @@ +# Cursor AI 配置指南 + +本文档详细说明了所有可用的Cursor配置选项,以及它们的用途和影响。 + +## 🚀 快速开始 + +1. 复制配置模板文件: +```bash +cp cursor-config.env.example cursor-config.env +``` + +2. 根据需要修改配置文件 +3. 重启服务以应用配置: +```bash +npm start +``` + +## 📋 配置选项详解 + +### 🎯 核心模式设置 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_MAX_MODE_ENABLED` | `true` | **MAX模式开关** - 启用增强AI能力,提供更深入的分析和建议 | +| `CURSOR_AGENT_MODE` | `true` | **Agent模式开关** - 启用智能代理功能,可以执行复杂的多步骤任务 | +| `CURSOR_UNIFIED_MODE` | `1` | **统一模式** - 整合多种功能模式,提供一致的用户体验 | +| `CURSOR_CHAT_MODE_ENUM` | `2` | **聊天模式枚举** - 控制对话交互的行为模式
• `0`: 简单问答
• `1`: 对话模式
• `2`: 协作模式 | +| `CURSOR_CHAT_MODE` | `collaborative` | **聊天模式字符串** - 自定义聊天模式名称 | +| `CURSOR_PREPROCESSING_FLAG` | `false` | **预处理模式** - 是否在主要处理前进行环境初始化 | +| `CURSOR_STREAM_MODE` | `1` | **流式输出模式** - 控制响应是否实时流式输出
• `0`: 关闭
• `1`: 启用 | +| `CURSOR_THINKING_LEVEL` | `3` | **思考级别** - AI的思考深度等级 (0-5,数值越高思考越深入) | + +### 🤖 模型配置选项 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_MODEL_NAME` | `claude-4-sonnet-thinking` | **默认模型名称** - 指定使用的AI模型 | +| `CURSOR_STREAMING_ENABLED` | `true` | **启用流式响应** - 是否支持实时响应输出 | +| `CURSOR_MAX_TOKENS` | `4096` | **最大Token数量** - 单次对话的最大token限制 (影响响应长度) | +| `CURSOR_TEMPERATURE` | `0.7` | **创造性温度参数** - 控制AI响应的创造性
• `0.0`: 最保守,结果一致
• `1.0`: 平衡
• `2.0`: 最有创意 | +| `CURSOR_THINKING_MODE` | `true` | **思考模式开关** - 是否显示AI的思考过程 | +| `CURSOR_THINKING_DEPTH` | `5` | **思考深度级别** - 思考过程的详细程度 (1-10) | + +### 🛠️ Agent能力配置 + +#### 代码相关功能 +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_CODE_UNDERSTANDING` | `true` | **代码理解能力** - 分析和理解现有代码的能力 | +| `CURSOR_CODE_GENERATION` | `true` | **代码生成能力** - 自动生成新代码的能力 | +| `CURSOR_CODE_REFACTORING` | `true` | **代码重构能力** - 优化和重构现有代码的能力 | +| `CURSOR_DEBUGGING_ASSISTANCE` | `true` | **调试辅助功能** - 帮助查找和修复代码错误 | +| `CURSOR_TEST_GENERATION` | `true` | **测试代码生成** - 自动生成单元测试和集成测试 | +| `CURSOR_ERROR_ANALYSIS` | `true` | **错误分析能力** - 深度分析错误原因和解决方案 | + +#### 文件操作能力 +| 配置项 | 默认值 | 说明 | 安全级别 | +|--------|--------|------|----------| +| `CURSOR_FILE_READING` | `true` | **文件读取权限** - 允许AI读取项目文件内容 | 🟢 低风险 | +| `CURSOR_FILE_WRITING` | `false` | **文件写入权限** - 允许AI创建和修改文件 | 🟡 谨慎开启 | +| `CURSOR_FILE_SEARCH` | `true` | **文件搜索功能** - 在项目中搜索特定文件和内容 | 🟢 低风险 | +| `CURSOR_FILESYSTEM_ACCESS` | `false` | **文件系统访问** - 更广泛的文件系统操作权限 | 🔴 谨慎开启 | + +#### 项目分析能力 +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_PROJECT_ANALYSIS` | `true` | **项目结构分析** - 分析项目的整体架构和组织结构 | +| `CURSOR_DEPENDENCY_ANALYSIS` | `true` | **依赖关系分析** - 分析项目的依赖关系和模块间的交互 | +| `CURSOR_ARCHITECTURE_ANALYSIS` | `true` | **架构分析** - 深度分析软件架构和设计模式 | +| `CURSOR_PERFORMANCE_OPTIMIZATION` | `true` | **性能优化建议** - 提供代码和架构的性能优化建议 | +| `CURSOR_SECURITY_ANALYSIS` | `true` | **安全分析功能** - 检查代码中的安全漏洞和风险 | + +### 📝 文档和注释功能 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_DOCUMENTATION_GENERATION` | `true` | **文档自动生成** - 根据代码自动生成技术文档 | +| `CURSOR_COMMENT_GENERATION` | `true` | **代码注释生成** - 为代码添加详细的注释说明 | + +### 🧠 思考配置选项 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_DEPTH_LEVEL` | `5` | **思考深度级别** - 问题分析的深度 (1-10) | +| `CURSOR_SHOW_THINKING` | `true` | **显示思考过程** - 是否在响应中显示AI的思考步骤 | +| `CURSOR_STEP_BY_STEP` | `true` | **逐步分析模式** - 将复杂问题分解为步骤处理 | +| `CURSOR_REASONING_CHAINS` | `true` | **推理链展示** - 显示逻辑推理的完整链条 | +| `CURSOR_SELF_VERIFICATION` | `true` | **自我验证机制** - AI对自己的答案进行验证和检查 | +| `CURSOR_ALTERNATIVE_APPROACHES` | `true` | **提供替代方案** - 为问题提供多种解决方案 | + +### 📚 上下文配置选项 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_MAX_CONTEXT_TOKENS` | `8192` | **最大上下文Token数** - 对话中保持的最大上下文长度 | +| `CURSOR_MAX_FILES` | `50` | **最大处理文件数** - 单次可以分析的最大文件数量 | +| `CURSOR_MAX_FILE_SIZE` | `1048576` | **单个文件大小限制** - 每个文件的最大大小(字节,默认1MB) | +| `CURSOR_CONVERSATION_HISTORY` | `10` | **对话历史长度** - 保持的对话轮数 | +| `CURSOR_INCLUDE_EDIT_HISTORY` | `true` | **包含编辑历史** - 是否在上下文中包含文件编辑历史 | +| `CURSOR_INCLUDE_PROJECT_STRUCTURE` | `true` | **包含项目结构信息** - 是否在上下文中包含完整项目结构 | +| `CURSOR_INCLUDE_DEPENDENCIES` | `true` | **包含依赖关系信息** - 是否在上下文中包含依赖关系图 | +| `CURSOR_LARGE_CONTEXT` | `1` | **启用大上下文处理** - 处理超长文本和复杂项目 | + +### 🔧 外部工具配置 + +| 配置项 | 默认值 | 说明 | 安全级别 | +|--------|--------|------|----------| +| `CURSOR_WEB_SEARCH_ENABLED` | `false` | **Web搜索功能** - 允许AI进行网络搜索获取最新信息 | 🟡 中等风险 | +| `CURSOR_CODE_EXECUTION_ENABLED` | `false` | **代码执行权限** - 允许AI执行代码进行测试 | 🔴 高风险功能 | +| `CURSOR_EXTERNAL_API_CALLS` | `false` | **外部API调用** - 允许调用第三方API服务 | 🟡 中等风险 | +| `CURSOR_DATABASE_ACCESS` | `false` | **数据库访问权限** - 允许访问数据库 | 🔴 高风险功能 | +| `CURSOR_WIKI_TOOL` | `[]` | **Wiki工具集成** - 集成Wiki知识库查询 | 🟢 低风险 | +| `CURSOR_WEB_TOOL` | `0` | **Web工具集成** - Web相关工具的启用状态 | 🟢 低风险 | + +### ⚙️ 高级控制选项 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_ENABLE_MAX_FEATURES` | `1` | **启用所有最大功能** - 开启所有高级功能 (可能影响性能) | +| `CURSOR_STREAM_CONTROL_FLAG` | `1` | **流式输出控制** - 精细控制流式输出行为 | +| `CURSOR_TOKEN_START_FLAG` | `1` | **Token开始标志** - 控制Token处理的开始时机 | +| `CURSOR_TOKEN_CONTROL_FLAG` | `1` | **Token控制标志** - 高级Token管理选项 | +| `CURSOR_SESSION_TRACKING_FLAG` | `1` | **会话跟踪** - 跟踪用户会话状态和历史 | + +### 🎛️ 请求控制标志 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_CONTROL_FLAG` | `true` | **主控制标志** - 全局功能开关 | +| `CURSOR_INSTRUCTION_FLAG` | `1` | **指令处理标志** - 如何处理用户指令 | +| `CURSOR_MODEL_FLAG` | `1` | **模型选择标志** - 动态模型选择控制 | +| `CURSOR_REQUEST_FLAG` | `1` | **请求类型标志** - 请求处理方式控制 | +| `CURSOR_FEEDBACK_FLAG` | `1` | **反馈收集标志** - 是否收集用户反馈数据 | +| `CURSOR_DESIRED_MAX_TOKENS` | `2048` | **期望最大Token数** - 响应的期望最大长度 | +| `CURSOR_CONTENT_FORMAT` | `markdown` | **内容格式** - 响应内容的格式类型 | + +### 🌐 系统环境配置 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `CURSOR_TIMEZONE` | `Asia/Shanghai` | **客户端时区设置** | +| `CURSOR_CLIENT_VERSION` | `0.50.5` | **客户端版本号** | +| `CURSOR_CONFIG_VERSION` | `v1.0.0` | **配置版本号** - 配置文件的版本标识 | +| `CURSOR_PROJECT_TYPE` | `javascript` | **项目类型标识** - 帮助AI理解项目性质 | + +## 🎯 预设配置模式 + +### ASK模式 (简单问答) +```env +CURSOR_MAX_MODE_ENABLED=false +CURSOR_AGENT_MODE=false +CURSOR_THINKING_MODE=false +CURSOR_CODE_GENERATION=true +CURSOR_FILE_READING=true +CURSOR_FILE_WRITING=false +CURSOR_SHOW_THINKING=false +``` + +### AGENT模式 (智能代理) +```env +CURSOR_MAX_MODE_ENABLED=true +CURSOR_AGENT_MODE=true +CURSOR_THINKING_MODE=true +CURSOR_THINKING_DEPTH=5 +CURSOR_PROJECT_ANALYSIS=true +CURSOR_FILE_WRITING=true +CURSOR_CODE_REFACTORING=true +CURSOR_SHOW_THINKING=true +``` + +### MAX模式 (全功能) +```env +CURSOR_MAX_MODE_ENABLED=true +CURSOR_AGENT_MODE=true +CURSOR_ENABLE_MAX_FEATURES=1 +CURSOR_THINKING_DEPTH=10 +CURSOR_LARGE_CONTEXT=1 +CURSOR_SHOW_THINKING=true +# 启用所有Agent能力... +``` + +## ⚠️ 安全建议 + +### 🔴 高风险功能 (谨慎开启) +- `CURSOR_CODE_EXECUTION_ENABLED`: 允许执行代码 +- `CURSOR_DATABASE_ACCESS`: 数据库访问权限 +- `CURSOR_FILESYSTEM_ACCESS`: 文件系统全面访问 + +### 🟡 中等风险功能 (建议监控) +- `CURSOR_FILE_WRITING`: 文件写入权限 +- `CURSOR_WEB_SEARCH_ENABLED`: 网络搜索功能 +- `CURSOR_EXTERNAL_API_CALLS`: 外部API调用 + +### 🟢 低风险功能 (安全开启) +- `CURSOR_FILE_READING`: 文件读取 +- `CURSOR_CODE_UNDERSTANDING`: 代码理解 +- `CURSOR_DOCUMENTATION_GENERATION`: 文档生成 + +## 🔄 配置热重载 + +修改配置文件后,可以通过以下方式重新加载: + +1. **重启服务** (推荐): +```bash +npm start +``` + +2. **API重载** (如果支持): +```bash +curl -X POST http://localhost:3010/v1/admin/reload-config +``` + +## 🐛 常见问题 + +### Q: 配置修改后没有生效? +A: 确保: +1. 配置文件名为 `cursor-config.env` +2. 重启了服务 +3. 环境变量格式正确(boolean用true/false,数字不加引号) + +### Q: MAX模式无法启用? +A: 检查以下配置: +1. `CURSOR_MAX_MODE_ENABLED=true` +2. `CURSOR_AGENT_MODE=true` +3. 模型名称包含"max"关键词 + +### Q: 思考过程不显示? +A: 确保: +1. `CURSOR_THINKING_MODE=true` +2. `CURSOR_SHOW_THINKING=true` +3. `CURSOR_THINKING_DEPTH > 0` + +## 📚 相关文档 + +- [项目README](README.md) +- [安装指南](SETUP.md) +- [API文档](API.md) +- [故障排除](TROUBLESHOOTING.md) \ No newline at end of file diff --git a/src/config/config.js b/src/config/config.js new file mode 100644 index 0000000000000000000000000000000000000000..008b913123b948bbfa90af4c59a750f5ecb3af55 --- /dev/null +++ b/src/config/config.js @@ -0,0 +1,98 @@ +// 读取并解析API_KEYS环境变量 +// 避免循环依赖,不要在此处引用logger + +// 添加自己的简单日志函数,防止循环依赖 +function log(level, message) { + // 只在控制台输出,不写入文件 + const timestamp = new Date().toISOString(); + if (level === 'ERROR') { + console.error(`[ERROR] ${timestamp} ${message}`); + } else if (level === 'WARN') { + console.warn(`[WARN] ${timestamp} ${message}`); + } else { + console.log(`[INFO] ${timestamp} ${message}`); + } +} + +// 解析API Keys配置 +let apiKeysConfig = {}; +try { + if (process.env.API_KEYS) { + // 解析API Keys字符串为对象 + apiKeysConfig = JSON.parse(process.env.API_KEYS); + log('INFO', '正在从环境变量加载API Keys...'); + log('INFO', `成功解析API Keys,包含 ${Object.keys(apiKeysConfig).length} 个键`); + } +} catch (error) { + log('ERROR', '解析API_KEYS环境变量失败:' + error.message); + log('ERROR', '请确保API_KEYS是有效的JSON格式'); +} + +// 导出配置 +module.exports = { + port: process.env.PORT || 3000, + + // 日志配置 + log: { + level: process.env.LOG_LEVEL || 'INFO', // ERROR, WARN, INFO, DEBUG, TRACE + format: process.env.LOG_FORMAT || 'colored', // colored, json, text + toFile: process.env.LOG_TO_FILE === 'true' || false, + maxSize: parseInt(process.env.LOG_MAX_SIZE || '10', 10) * 1024 * 1024, // 默认10MB + maxFiles: parseInt(process.env.LOG_MAX_FILES || '10', 10) // 保留最近10个日志文件 + }, + + // 合并API Keys设置 + apiKeys: { + ...apiKeysConfig, + ...Object.fromEntries( + Object.entries(process.env) + .filter(([key]) => key.startsWith('API_KEY_')) + .map(([key, value]) => { + const apiKey = key.replace('API_KEY_', 'sk-'); + try { + // 尝试解析JSON字符串,支持数组格式的cookie + const parsed = JSON.parse(value); + return [apiKey, parsed]; + } catch (e) { + // 如果不是JSON,直接作为字符串处理 + return [apiKey, value]; + } + }) + ) + }, + + defaultRotationStrategy: process.env.ROTATION_STRATEGY || 'round-robin', + + // 添加代理配置 + proxy: { + enabled: process.env.PROXY_ENABLED === 'true' || false, + url: process.env.PROXY_URL || 'http://127.0.0.1:7890', + }, + + // GitHub相关配置 + github: { + token: process.env.GITHUB_TOKEN, + owner: process.env.GITHUB_OWNER, + repo: process.env.GITHUB_REPO, + workflowId: process.env.GITHUB_WORKFLOW_ID, + triggerWorkflow: process.env.TRIGGER_WORKFLOW === 'true' + }, + + // 工作流参数 + workflowParams: { + number: parseInt(process.env.REGISTER_NUMBER || '2', 10), + maxWorkers: parseInt(process.env.REGISTER_MAX_WORKERS || '1', 10), + emailServer: process.env.REGISTER_EMAIL_SERVER || 'TempEmail', + ingestToOneapi: process.env.REGISTER_INGEST_TO_ONEAPI === 'true', + uploadArtifact: process.env.REGISTER_UPLOAD_ARTIFACT === 'true', + useConfigFile: process.env.REGISTER_USE_CONFIG_FILE !== 'false', + emailConfigs: process.env.REGISTER_EMAIL_CONFIGS || '[]' + }, + + // 刷新配置 + refresh: { + cron: process.env.REFRESH_CRON || '0 */6 * * *', + minCookieCount: parseInt(process.env.MIN_COOKIE_COUNT || '2', 10), + enabled: process.env.ENABLE_AUTO_REFRESH === 'true' + } +}; diff --git a/src/config/cursor-config.env.example b/src/config/cursor-config.env.example new file mode 100644 index 0000000000000000000000000000000000000000..39ece4e11791d8237d3d9da6a5ff46fe06dbf91b --- /dev/null +++ b/src/config/cursor-config.env.example @@ -0,0 +1,253 @@ +# ================================================================= +# Cursor AI 配置环境变量 +# 复制此文件为 cursor-config.env 并根据需要修改配置 +# ================================================================= + +# ================================================================= +# 🎯 核心模式设置 +# ================================================================= + +# MAX模式开关 - 启用增强AI能力,提供更深入的分析和建议 +CURSOR_MAX_MODE_ENABLED=true + +# Agent模式开关 - 启用智能代理功能,可以执行复杂的多步骤任务 +CURSOR_AGENT_MODE=true + +# 统一模式 - 整合多种功能模式,提供一致的用户体验 +CURSOR_UNIFIED_MODE=1 + +# 聊天模式枚举值 - 控制对话交互的行为模式 (0=简单问答, 1=对话模式, 2=协作模式) +CURSOR_CHAT_MODE_ENUM=2 + +# 聊天模式字符串 - 自定义聊天模式名称 +CURSOR_CHAT_MODE=collaborative + +# 预处理模式开关 - 是否在主要处理前进行环境初始化 +CURSOR_PREPROCESSING_FLAG=false + +# 流式输出模式 - 控制响应是否实时流式输出 (0=关闭, 1=启用) +CURSOR_STREAM_MODE=1 + +# 思考级别 - AI的思考深度等级 (0-5, 数值越高思考越深入) +CURSOR_THINKING_LEVEL=3 + +# ================================================================= +# 🤖 模型配置选项 +# ================================================================= + +# 默认模型名称 - 指定使用的AI模型 +CURSOR_MODEL_NAME=claude-4-sonnet-thinking + +# 启用流式响应 - 是否支持实时响应输出 +CURSOR_STREAMING_ENABLED=true + +# 最大Token数量 - 单次对话的最大token限制 (影响响应长度) +CURSOR_MAX_TOKENS=4096 + +# 创造性温度参数 - 控制AI响应的创造性 (0.0-2.0, 越高越有创意) +CURSOR_TEMPERATURE=0.7 + +# 思考模式开关 - 是否显示AI的思考过程 +CURSOR_THINKING_MODE=true + +# 思考深度级别 - 思考过程的详细程度 (1-10) +CURSOR_THINKING_DEPTH=5 + +# ================================================================= +# 🛠️ Agent能力配置 - 代码相关功能 +# ================================================================= + +# 代码理解能力 - 分析和理解现有代码的能力 +CURSOR_CODE_UNDERSTANDING=true + +# 代码生成能力 - 自动生成新代码的能力 +CURSOR_CODE_GENERATION=true + +# 代码重构能力 - 优化和重构现有代码的能力 +CURSOR_CODE_REFACTORING=true + +# 调试辅助功能 - 帮助查找和修复代码错误 +CURSOR_DEBUGGING_ASSISTANCE=true + +# 测试代码生成 - 自动生成单元测试和集成测试 +CURSOR_TEST_GENERATION=true + +# 错误分析能力 - 深度分析错误原因和解决方案 +CURSOR_ERROR_ANALYSIS=true + +# ================================================================= +# 📁 文件操作能力配置 +# ================================================================= + +# 文件读取权限 - 允许AI读取项目文件内容 +CURSOR_FILE_READING=true + +# 文件写入权限 - 允许AI创建和修改文件 (谨慎开启) +CURSOR_FILE_WRITING=false + +# 文件搜索功能 - 在项目中搜索特定文件和内容 +CURSOR_FILE_SEARCH=true + +# 文件系统访问 - 更广泛的文件系统操作权限 (谨慎开启) +CURSOR_FILESYSTEM_ACCESS=false + +# ================================================================= +# 📊 项目分析能力配置 +# ================================================================= + +# 项目结构分析 - 分析项目的整体架构和组织结构 +CURSOR_PROJECT_ANALYSIS=true + +# 依赖关系分析 - 分析项目的依赖关系和模块间的交互 +CURSOR_DEPENDENCY_ANALYSIS=true + +# 架构分析 - 深度分析软件架构和设计模式 +CURSOR_ARCHITECTURE_ANALYSIS=true + +# 性能优化建议 - 提供代码和架构的性能优化建议 +CURSOR_PERFORMANCE_OPTIMIZATION=true + +# 安全分析功能 - 检查代码中的安全漏洞和风险 +CURSOR_SECURITY_ANALYSIS=true + +# ================================================================= +# 📝 文档和注释功能 +# ================================================================= + +# 文档自动生成 - 根据代码自动生成技术文档 +CURSOR_DOCUMENTATION_GENERATION=true + +# 代码注释生成 - 为代码添加详细的注释说明 +CURSOR_COMMENT_GENERATION=true + +# ================================================================= +# 🧠 思考配置选项 +# ================================================================= + +# 思考深度级别 - 问题分析的深度 (1-10) +CURSOR_DEPTH_LEVEL=5 + +# 显示思考过程 - 是否在响应中显示AI的思考步骤 +CURSOR_SHOW_THINKING=true + +# 逐步分析模式 - 将复杂问题分解为步骤处理 +CURSOR_STEP_BY_STEP=true + +# 推理链展示 - 显示逻辑推理的完整链条 +CURSOR_REASONING_CHAINS=true + +# 自我验证机制 - AI对自己的答案进行验证和检查 +CURSOR_SELF_VERIFICATION=true + +# 提供替代方案 - 为问题提供多种解决方案 +CURSOR_ALTERNATIVE_APPROACHES=true + +# ================================================================= +# 📚 上下文配置选项 +# ================================================================= + +# 最大上下文Token数 - 对话中保持的最大上下文长度 +CURSOR_MAX_CONTEXT_TOKENS=8192 + +# 最大处理文件数 - 单次可以分析的最大文件数量 +CURSOR_MAX_FILES=50 + +# 单个文件大小限制 - 每个文件的最大大小(字节) +CURSOR_MAX_FILE_SIZE=1048576 + +# 对话历史长度 - 保持的对话轮数 +CURSOR_CONVERSATION_HISTORY=10 + +# 包含编辑历史 - 是否在上下文中包含文件编辑历史 +CURSOR_INCLUDE_EDIT_HISTORY=true + +# 包含项目结构信息 - 是否在上下文中包含完整项目结构 +CURSOR_INCLUDE_PROJECT_STRUCTURE=true + +# 包含依赖关系信息 - 是否在上下文中包含依赖关系图 +CURSOR_INCLUDE_DEPENDENCIES=true + +# 启用大上下文处理 - 处理超长文本和复杂项目 +CURSOR_LARGE_CONTEXT=1 + +# ================================================================= +# 🔧 外部工具配置 +# ================================================================= + +# Web搜索功能 - 允许AI进行网络搜索获取最新信息 +CURSOR_WEB_SEARCH_ENABLED=false + +# 代码执行权限 - 允许AI执行代码进行测试 (高风险功能) +CURSOR_CODE_EXECUTION_ENABLED=false + +# 外部API调用 - 允许调用第三方API服务 +CURSOR_EXTERNAL_API_CALLS=false + +# 数据库访问权限 - 允许访问数据库 (高风险功能) +CURSOR_DATABASE_ACCESS=false + +# Wiki工具集成 - 集成Wiki知识库查询 +CURSOR_WIKI_TOOL=[] + +# Web工具集成 - Web相关工具的启用状态 +CURSOR_WEB_TOOL=0 + +# ================================================================= +# ⚙️ 高级控制选项 +# ================================================================= + +# 启用所有最大功能 - 开启所有高级功能 (可能影响性能) +CURSOR_ENABLE_MAX_FEATURES=1 + +# 流式输出控制 - 精细控制流式输出行为 +CURSOR_STREAM_CONTROL_FLAG=1 + +# Token开始标志 - 控制Token处理的开始时机 +CURSOR_TOKEN_START_FLAG=1 + +# Token控制标志 - 高级Token管理选项 +CURSOR_TOKEN_CONTROL_FLAG=1 + +# 会话跟踪 - 跟踪用户会话状态和历史 +CURSOR_SESSION_TRACKING_FLAG=1 + +# ================================================================= +# 🎛️ 请求控制标志 +# ================================================================= + +# 主控制标志 - 全局功能开关 +CURSOR_CONTROL_FLAG=true + +# 指令处理标志 - 如何处理用户指令 +CURSOR_INSTRUCTION_FLAG=1 + +# 模型选择标志 - 动态模型选择控制 +CURSOR_MODEL_FLAG=1 + +# 请求类型标志 - 请求处理方式控制 +CURSOR_REQUEST_FLAG=1 + +# 反馈收集标志 - 是否收集用户反馈数据 +CURSOR_FEEDBACK_FLAG=1 + +# 期望最大Token数 - 响应的期望最大长度 +CURSOR_DESIRED_MAX_TOKENS=2048 + +# 内容格式 - 响应内容的格式类型 +CURSOR_CONTENT_FORMAT=markdown + +# ================================================================= +# 🌐 系统环境配置 +# ================================================================= + +# 客户端时区设置 +CURSOR_TIMEZONE=Asia/Shanghai + +# 客户端版本号 +CURSOR_CLIENT_VERSION=0.50.5 + +# 配置版本号 - 配置文件的版本标识 +CURSOR_CONFIG_VERSION=v1.0.0 + +# 项目类型标识 - 帮助AI理解项目性质 +CURSOR_PROJECT_TYPE=javascript \ No newline at end of file diff --git a/src/config/cursorConfig.js b/src/config/cursorConfig.js new file mode 100644 index 0000000000000000000000000000000000000000..5a55d42d0aa654189769e3303da1e24bb34b3fa7 --- /dev/null +++ b/src/config/cursorConfig.js @@ -0,0 +1,257 @@ +const path = require('path'); +const fs = require('fs'); + +// 尝试加载自定义的cursor配置文件 +const customConfigPath = path.join(process.cwd(), 'cursor-config.env'); +if (fs.existsSync(customConfigPath)) { + require('dotenv').config({ path: customConfigPath }); +} + +// 辅助函数:解析布尔值 +function parseBoolean(value, defaultValue = false) { + if (typeof value === 'boolean') return value; + if (typeof value === 'string') { + return value.toLowerCase() === 'true' || value === '1'; + } + return defaultValue; +} + +// 辅助函数:解析整数 +function parseInteger(value, defaultValue = 0) { + const parsed = parseInt(value, 10); + return isNaN(parsed) ? defaultValue : parsed; +} + +// 辅助函数:解析浮点数 +function parseFloat(value, defaultValue = 0.0) { + const parsed = global.parseFloat(value); + return isNaN(parsed) ? defaultValue : parsed; +} + +// 辅助函数:解析数组 +function parseArray(value, defaultValue = []) { + if (Array.isArray(value)) return value; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch (e) { + return value.split(',').map(item => item.trim()).filter(item => item); + } + } + return defaultValue; +} + +// Cursor配置对象 +const cursorConfig = { + // 🎯 核心模式设置 + core: { + maxModeEnabled: parseBoolean(process.env.CURSOR_MAX_MODE_ENABLED, true), + agentMode: parseBoolean(process.env.CURSOR_AGENT_MODE, true), + unifiedMode: parseInteger(process.env.CURSOR_UNIFIED_MODE, 1), + chatModeEnum: parseInteger(process.env.CURSOR_CHAT_MODE_ENUM, 2), + chatMode: process.env.CURSOR_CHAT_MODE || 'collaborative', + preprocessingFlag: parseBoolean(process.env.CURSOR_PREPROCESSING_FLAG, false), + streamMode: parseInteger(process.env.CURSOR_STREAM_MODE, 1), + thinkingLevel: parseInteger(process.env.CURSOR_THINKING_LEVEL, 3) + }, + + // 🤖 模型配置选项 + model: { + modelName: process.env.CURSOR_MODEL_NAME || 'claude-4-sonnet-thinking', + streamingEnabled: parseBoolean(process.env.CURSOR_STREAMING_ENABLED, true), + maxTokens: parseInteger(process.env.CURSOR_MAX_TOKENS, 4096), + temperature: parseFloat(process.env.CURSOR_TEMPERATURE, 0.7), + thinkingMode: parseBoolean(process.env.CURSOR_THINKING_MODE, true), + thinkingDepth: parseInteger(process.env.CURSOR_THINKING_DEPTH, 5) + }, + + // 🛠️ Agent能力配置 + agentCapabilities: { + codeUnderstanding: parseBoolean(process.env.CURSOR_CODE_UNDERSTANDING, true), + codeGeneration: parseBoolean(process.env.CURSOR_CODE_GENERATION, true), + codeRefactoring: parseBoolean(process.env.CURSOR_CODE_REFACTORING, true), + debuggingAssistance: parseBoolean(process.env.CURSOR_DEBUGGING_ASSISTANCE, true), + testGeneration: parseBoolean(process.env.CURSOR_TEST_GENERATION, true), + errorAnalysis: parseBoolean(process.env.CURSOR_ERROR_ANALYSIS, true), + fileReading: parseBoolean(process.env.CURSOR_FILE_READING, true), + fileWriting: parseBoolean(process.env.CURSOR_FILE_WRITING, false), + fileSearch: parseBoolean(process.env.CURSOR_FILE_SEARCH, true), + filesystemAccess: parseBoolean(process.env.CURSOR_FILESYSTEM_ACCESS, false), + projectAnalysis: parseBoolean(process.env.CURSOR_PROJECT_ANALYSIS, true), + dependencyAnalysis: parseBoolean(process.env.CURSOR_DEPENDENCY_ANALYSIS, true), + architectureAnalysis: parseBoolean(process.env.CURSOR_ARCHITECTURE_ANALYSIS, true), + performanceOptimization: parseBoolean(process.env.CURSOR_PERFORMANCE_OPTIMIZATION, true), + securityAnalysis: parseBoolean(process.env.CURSOR_SECURITY_ANALYSIS, true), + documentationGeneration: parseBoolean(process.env.CURSOR_DOCUMENTATION_GENERATION, true), + commentGeneration: parseBoolean(process.env.CURSOR_COMMENT_GENERATION, true) + }, + + // 🧠 思考配置选项 + thinking: { + depthLevel: parseInteger(process.env.CURSOR_DEPTH_LEVEL, 5), + showThinking: parseBoolean(process.env.CURSOR_SHOW_THINKING, true), + stepByStep: parseBoolean(process.env.CURSOR_STEP_BY_STEP, true), + reasoningChains: parseBoolean(process.env.CURSOR_REASONING_CHAINS, true), + selfVerification: parseBoolean(process.env.CURSOR_SELF_VERIFICATION, true), + alternativeApproaches: parseBoolean(process.env.CURSOR_ALTERNATIVE_APPROACHES, true) + }, + + // 📚 上下文配置选项 + context: { + maxContextTokens: parseInteger(process.env.CURSOR_MAX_CONTEXT_TOKENS, 8192), + maxFiles: parseInteger(process.env.CURSOR_MAX_FILES, 50), + maxFileSize: parseInteger(process.env.CURSOR_MAX_FILE_SIZE, 1048576), + conversationHistory: parseInteger(process.env.CURSOR_CONVERSATION_HISTORY, 10), + includeEditHistory: parseBoolean(process.env.CURSOR_INCLUDE_EDIT_HISTORY, true), + includeProjectStructure: parseBoolean(process.env.CURSOR_INCLUDE_PROJECT_STRUCTURE, true), + includeDependencies: parseBoolean(process.env.CURSOR_INCLUDE_DEPENDENCIES, true), + largeContext: parseInteger(process.env.CURSOR_LARGE_CONTEXT, 1) + }, + + // 🔧 外部工具配置 + tools: { + webSearchEnabled: parseBoolean(process.env.CURSOR_WEB_SEARCH_ENABLED, false), + codeExecutionEnabled: parseBoolean(process.env.CURSOR_CODE_EXECUTION_ENABLED, false), + externalApiCalls: parseBoolean(process.env.CURSOR_EXTERNAL_API_CALLS, false), + databaseAccess: parseBoolean(process.env.CURSOR_DATABASE_ACCESS, false), + wikiTool: parseArray(process.env.CURSOR_WIKI_TOOL, []), + webTool: parseInteger(process.env.CURSOR_WEB_TOOL, 0) + }, + + // ⚙️ 高级控制选项 + advanced: { + enableMaxFeatures: parseInteger(process.env.CURSOR_ENABLE_MAX_FEATURES, 1), + streamControlFlag: parseInteger(process.env.CURSOR_STREAM_CONTROL_FLAG, 1), + tokenStartFlag: parseInteger(process.env.CURSOR_TOKEN_START_FLAG, 1), + tokenControlFlag: parseInteger(process.env.CURSOR_TOKEN_CONTROL_FLAG, 1), + sessionTrackingFlag: parseInteger(process.env.CURSOR_SESSION_TRACKING_FLAG, 1) + }, + + // 🎛️ 请求控制标志 + control: { + controlFlag: parseBoolean(process.env.CURSOR_CONTROL_FLAG, true), + instructionFlag: parseInteger(process.env.CURSOR_INSTRUCTION_FLAG, 1), + modelFlag: parseInteger(process.env.CURSOR_MODEL_FLAG, 1), + requestFlag: parseInteger(process.env.CURSOR_REQUEST_FLAG, 1), + feedbackFlag: parseInteger(process.env.CURSOR_FEEDBACK_FLAG, 1), + desiredMaxTokens: parseInteger(process.env.CURSOR_DESIRED_MAX_TOKENS, 2048), + contentFormat: process.env.CURSOR_CONTENT_FORMAT || 'markdown' + }, + + // 🌐 系统环境配置 + system: { + timezone: process.env.CURSOR_TIMEZONE || 'Asia/Shanghai', + clientVersion: process.env.CURSOR_CLIENT_VERSION || '0.50.5', + configVersion: process.env.CURSOR_CONFIG_VERSION || 'v1.0.0', + projectType: process.env.CURSOR_PROJECT_TYPE || 'javascript' + } +}; + +// 导出配置和辅助函数 +module.exports = { + cursorConfig, + + // 生成能力数组(用于protobuf) + getCapabilities() { + const caps = []; + const abilities = cursorConfig.agentCapabilities; + + if (abilities.codeUnderstanding) caps.push(1); + if (abilities.codeGeneration) caps.push(3); + if (abilities.codeRefactoring) caps.push(5); + if (abilities.fileReading) caps.push(6); + if (abilities.fileWriting) caps.push(7); + if (abilities.fileSearch) caps.push(8); + if (abilities.projectAnalysis) caps.push(9); + if (abilities.debuggingAssistance) caps.push(11); + if (abilities.testGeneration) caps.push(12); + if (abilities.errorAnalysis) caps.push(14); + if (abilities.documentationGeneration) caps.push(15); + if (abilities.performanceOptimization) caps.push(17); + if (abilities.securityAnalysis) caps.push(18); + if (abilities.dependencyAnalysis) caps.push(20); + if (abilities.architectureAnalysis) caps.push(19); + if (abilities.commentGeneration) caps.push(21); + + // 添加基础能力 + caps.push(22, 23, 24); + + return caps; + }, + + // 生成模型配置对象 + getModelConfiguration(modelName, stream) { + return { + model_name: modelName || cursorConfig.model.modelName, + api_endpoint: 'https://api.openai.com/v1', + streaming_enabled: stream !== undefined ? stream : cursorConfig.model.streamingEnabled, + max_tokens: cursorConfig.model.maxTokens, + temperature: cursorConfig.model.temperature, + thinking_mode: cursorConfig.model.thinkingMode, + thinking_depth: cursorConfig.model.thinkingDepth, + agent_capabilities: cursorConfig.core.agentMode + }; + }, + + // 生成系统环境信息 + getSystemEnvironment() { + return { + platform: 'win32', + architecture: 'x64', + os_version: '10.0.26100', + executable_path: 'cursor.exe', + timestamp: new Date().toISOString(), + timezone: cursorConfig.system.timezone, + client_version: cursorConfig.system.clientVersion, + config_version: cursorConfig.system.configVersion + }; + }, + + // 生成MAX模式设置 + getMaxModeSettings() { + return { + enabled: cursorConfig.core.maxModeEnabled, + agent_caps: cursorConfig.agentCapabilities, + thinking_config: cursorConfig.thinking, + context_config: cursorConfig.context, + tool_config: cursorConfig.tools + }; + }, + + // 检查是否启用了某个功能 + isFeatureEnabled(feature) { + const parts = feature.split('.'); + let current = cursorConfig; + + for (const part of parts) { + if (current[part] === undefined) return false; + current = current[part]; + } + + return parseBoolean(current, false); + }, + + // 获取配置值 + getValue(path, defaultValue) { + const parts = path.split('.'); + let current = cursorConfig; + + for (const part of parts) { + if (current[part] === undefined) return defaultValue; + current = current[part]; + } + + return current; + }, + + // 重新加载配置 + reload() { + if (fs.existsSync(customConfigPath)) { + delete require.cache[require.resolve('dotenv')]; + require('dotenv').config({ path: customConfigPath, override: true }); + } + + // 重新计算配置值... + // 这里可以重新执行配置初始化逻辑 + } +}; \ No newline at end of file diff --git a/src/middleware/auth.js b/src/middleware/auth.js new file mode 100644 index 0000000000000000000000000000000000000000..2c0d33c23bd0fc2a18ae15b8faa512b5ab0fa901 --- /dev/null +++ b/src/middleware/auth.js @@ -0,0 +1,51 @@ +const admin = require('../models/admin'); + +// 验证管理员权限的中间件 +function authMiddleware(req, res, next) { + // 跳过登录相关的路由 + if (req.path.startsWith('/v1/admin/')) { + return next(); + } + + // 对静态HTML页面的处理 + if (req.path === '/logs.html') { + // 日志页面的访问不在中间件中做验证,而是在前端页面中进行验证 + return next(); + } + + // 修改为:只对管理相关的API进行认证 + if (req.path.startsWith('/v1/api-keys') || + req.path.startsWith('/v1/invalid-cookies') || + req.path.startsWith('/v1/refresh-cookies') || + req.path.startsWith('/v1/logs')) { + // 获取Authorization头 + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + success: false, + message: '未提供认证token' + }); + } + + // 提取token + const token = authHeader.split(' ')[1]; + + // 验证token + const result = admin.verifyToken(token); + if (!result.success) { + return res.status(401).json({ + success: false, + message: '无效的token' + }); + } + + // 将用户信息添加到请求对象 + req.admin = { + username: result.username + }; + } + + next(); +} + +module.exports = authMiddleware; \ No newline at end of file diff --git a/src/models/admin.js b/src/models/admin.js new file mode 100644 index 0000000000000000000000000000000000000000..d3a2bceef41a927a64772aa29e6b5a120e110605 --- /dev/null +++ b/src/models/admin.js @@ -0,0 +1,114 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); +const jwt = require('jsonwebtoken'); + +// 管理员数据文件路径 +const ADMIN_FILE = path.join(__dirname, '../../data/admin.json'); +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; + +// 确保data目录存在 +const dataDir = path.dirname(ADMIN_FILE); +if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); +} + +// 确保admin.json文件存在 +if (!fs.existsSync(ADMIN_FILE)) { + fs.writeFileSync(ADMIN_FILE, JSON.stringify({ admin: null }), 'utf8'); +} + +class Admin { + constructor() { + this.loadAdmin(); + } + + // 加载管理员数据 + loadAdmin() { + try { + const data = fs.readFileSync(ADMIN_FILE, 'utf8'); + this.admin = JSON.parse(data).admin; + } catch (error) { + console.error('加载管理员数据失败:', error); + this.admin = null; + } + } + + // 保存管理员数据 + saveAdmin() { + try { + fs.writeFileSync(ADMIN_FILE, JSON.stringify({ admin: this.admin }), 'utf8'); + } catch (error) { + console.error('保存管理员数据失败:', error); + throw error; + } + } + + // 检查是否已有管理员 + hasAdmin() { + return !!this.admin; + } + + // 注册管理员 + register(username, password) { + if (this.hasAdmin()) { + throw new Error('已存在管理员账号'); + } + + // 生成盐值 + const salt = crypto.randomBytes(16).toString('hex'); + // 使用盐值加密密码 + const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex'); + + this.admin = { + username, + salt, + hash + }; + + this.saveAdmin(); + return this.generateToken(username); + } + + // 验证密码 + verifyPassword(password, salt, hash) { + const testHash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex'); + return testHash === hash; + } + + // 登录验证 + login(username, password) { + if (!this.admin || username !== this.admin.username) { + throw new Error('用户名或密码错误'); + } + + if (!this.verifyPassword(password, this.admin.salt, this.admin.hash)) { + throw new Error('用户名或密码错误'); + } + + return this.generateToken(username); + } + + // 生成JWT token + generateToken(username) { + return jwt.sign({ username }, JWT_SECRET, { expiresIn: '24h' }); + } + + // 验证JWT token + verifyToken(token) { + try { + const decoded = jwt.verify(token, JWT_SECRET); + return { + success: true, + username: decoded.username + }; + } catch (error) { + return { + success: false, + error: 'Invalid token' + }; + } + } +} + +module.exports = new Admin(); \ No newline at end of file diff --git a/src/proto/message.js b/src/proto/message.js new file mode 100644 index 0000000000000000000000000000000000000000..2d909966ef78d21859de20425768553b30cfffaf --- /dev/null +++ b/src/proto/message.js @@ -0,0 +1,5983 @@ +/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/ +"use strict"; + +var $protobuf = require("protobufjs/minimal"); + +// Common aliases +var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util; + +// Exported root namespace +var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {}); + +$root.AvailableModelsResponse = (function() { + + /** + * Properties of an AvailableModelsResponse. + * @exports IAvailableModelsResponse + * @interface IAvailableModelsResponse + * @property {Array.|null} [models] AvailableModelsResponse models + * @property {Array.|null} [modelNames] AvailableModelsResponse modelNames + */ + + /** + * Constructs a new AvailableModelsResponse. + * @exports AvailableModelsResponse + * @classdesc Represents an AvailableModelsResponse. + * @implements IAvailableModelsResponse + * @constructor + * @param {IAvailableModelsResponse=} [properties] Properties to set + */ + function AvailableModelsResponse(properties) { + this.models = []; + this.modelNames = []; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * AvailableModelsResponse models. + * @member {Array.} models + * @memberof AvailableModelsResponse + * @instance + */ + AvailableModelsResponse.prototype.models = $util.emptyArray; + + /** + * AvailableModelsResponse modelNames. + * @member {Array.} modelNames + * @memberof AvailableModelsResponse + * @instance + */ + AvailableModelsResponse.prototype.modelNames = $util.emptyArray; + + /** + * Creates a new AvailableModelsResponse instance using the specified properties. + * @function create + * @memberof AvailableModelsResponse + * @static + * @param {IAvailableModelsResponse=} [properties] Properties to set + * @returns {AvailableModelsResponse} AvailableModelsResponse instance + */ + AvailableModelsResponse.create = function create(properties) { + return new AvailableModelsResponse(properties); + }; + + /** + * Encodes the specified AvailableModelsResponse message. Does not implicitly {@link AvailableModelsResponse.verify|verify} messages. + * @function encode + * @memberof AvailableModelsResponse + * @static + * @param {IAvailableModelsResponse} message AvailableModelsResponse message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + AvailableModelsResponse.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.modelNames != null && message.modelNames.length) + for (var i = 0; i < message.modelNames.length; ++i) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.modelNames[i]); + if (message.models != null && message.models.length) + for (var i = 0; i < message.models.length; ++i) + $root.AvailableModelsResponse.AvailableModel.encode(message.models[i], writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified AvailableModelsResponse message, length delimited. Does not implicitly {@link AvailableModelsResponse.verify|verify} messages. + * @function encodeDelimited + * @memberof AvailableModelsResponse + * @static + * @param {IAvailableModelsResponse} message AvailableModelsResponse message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + AvailableModelsResponse.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an AvailableModelsResponse message from the specified reader or buffer. + * @function decode + * @memberof AvailableModelsResponse + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {AvailableModelsResponse} AvailableModelsResponse + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + AvailableModelsResponse.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.AvailableModelsResponse(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 2: { + if (!(message.models && message.models.length)) + message.models = []; + message.models.push($root.AvailableModelsResponse.AvailableModel.decode(reader, reader.uint32())); + break; + } + case 1: { + if (!(message.modelNames && message.modelNames.length)) + message.modelNames = []; + message.modelNames.push(reader.string()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an AvailableModelsResponse message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof AvailableModelsResponse + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {AvailableModelsResponse} AvailableModelsResponse + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + AvailableModelsResponse.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an AvailableModelsResponse message. + * @function verify + * @memberof AvailableModelsResponse + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + AvailableModelsResponse.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.models != null && message.hasOwnProperty("models")) { + if (!Array.isArray(message.models)) + return "models: array expected"; + for (var i = 0; i < message.models.length; ++i) { + var error = $root.AvailableModelsResponse.AvailableModel.verify(message.models[i]); + if (error) + return "models." + error; + } + } + if (message.modelNames != null && message.hasOwnProperty("modelNames")) { + if (!Array.isArray(message.modelNames)) + return "modelNames: array expected"; + for (var i = 0; i < message.modelNames.length; ++i) + if (!$util.isString(message.modelNames[i])) + return "modelNames: string[] expected"; + } + return null; + }; + + /** + * Creates an AvailableModelsResponse message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof AvailableModelsResponse + * @static + * @param {Object.} object Plain object + * @returns {AvailableModelsResponse} AvailableModelsResponse + */ + AvailableModelsResponse.fromObject = function fromObject(object) { + if (object instanceof $root.AvailableModelsResponse) + return object; + var message = new $root.AvailableModelsResponse(); + if (object.models) { + if (!Array.isArray(object.models)) + throw TypeError(".AvailableModelsResponse.models: array expected"); + message.models = []; + for (var i = 0; i < object.models.length; ++i) { + if (typeof object.models[i] !== "object") + throw TypeError(".AvailableModelsResponse.models: object expected"); + message.models[i] = $root.AvailableModelsResponse.AvailableModel.fromObject(object.models[i]); + } + } + if (object.modelNames) { + if (!Array.isArray(object.modelNames)) + throw TypeError(".AvailableModelsResponse.modelNames: array expected"); + message.modelNames = []; + for (var i = 0; i < object.modelNames.length; ++i) + message.modelNames[i] = String(object.modelNames[i]); + } + return message; + }; + + /** + * Creates a plain object from an AvailableModelsResponse message. Also converts values to other types if specified. + * @function toObject + * @memberof AvailableModelsResponse + * @static + * @param {AvailableModelsResponse} message AvailableModelsResponse + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + AvailableModelsResponse.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.modelNames = []; + object.models = []; + } + if (message.modelNames && message.modelNames.length) { + object.modelNames = []; + for (var j = 0; j < message.modelNames.length; ++j) + object.modelNames[j] = message.modelNames[j]; + } + if (message.models && message.models.length) { + object.models = []; + for (var j = 0; j < message.models.length; ++j) + object.models[j] = $root.AvailableModelsResponse.AvailableModel.toObject(message.models[j], options); + } + return object; + }; + + /** + * Converts this AvailableModelsResponse to JSON. + * @function toJSON + * @memberof AvailableModelsResponse + * @instance + * @returns {Object.} JSON object + */ + AvailableModelsResponse.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for AvailableModelsResponse + * @function getTypeUrl + * @memberof AvailableModelsResponse + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + AvailableModelsResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/AvailableModelsResponse"; + }; + + AvailableModelsResponse.AvailableModel = (function() { + + /** + * Properties of an AvailableModel. + * @memberof AvailableModelsResponse + * @interface IAvailableModel + * @property {string|null} [name] AvailableModel name + * @property {boolean|null} [defaultOn] AvailableModel defaultOn + * @property {boolean|null} [isLongContextOnly] AvailableModel isLongContextOnly + * @property {boolean|null} [isChatOnly] AvailableModel isChatOnly + */ + + /** + * Constructs a new AvailableModel. + * @memberof AvailableModelsResponse + * @classdesc Represents an AvailableModel. + * @implements IAvailableModel + * @constructor + * @param {AvailableModelsResponse.IAvailableModel=} [properties] Properties to set + */ + function AvailableModel(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * AvailableModel name. + * @member {string} name + * @memberof AvailableModelsResponse.AvailableModel + * @instance + */ + AvailableModel.prototype.name = ""; + + /** + * AvailableModel defaultOn. + * @member {boolean} defaultOn + * @memberof AvailableModelsResponse.AvailableModel + * @instance + */ + AvailableModel.prototype.defaultOn = false; + + /** + * AvailableModel isLongContextOnly. + * @member {boolean|null|undefined} isLongContextOnly + * @memberof AvailableModelsResponse.AvailableModel + * @instance + */ + AvailableModel.prototype.isLongContextOnly = null; + + /** + * AvailableModel isChatOnly. + * @member {boolean|null|undefined} isChatOnly + * @memberof AvailableModelsResponse.AvailableModel + * @instance + */ + AvailableModel.prototype.isChatOnly = null; + + // OneOf field names bound to virtual getters and setters + var $oneOfFields; + + // Virtual OneOf for proto3 optional field + Object.defineProperty(AvailableModel.prototype, "_isLongContextOnly", { + get: $util.oneOfGetter($oneOfFields = ["isLongContextOnly"]), + set: $util.oneOfSetter($oneOfFields) + }); + + // Virtual OneOf for proto3 optional field + Object.defineProperty(AvailableModel.prototype, "_isChatOnly", { + get: $util.oneOfGetter($oneOfFields = ["isChatOnly"]), + set: $util.oneOfSetter($oneOfFields) + }); + + /** + * Creates a new AvailableModel instance using the specified properties. + * @function create + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {AvailableModelsResponse.IAvailableModel=} [properties] Properties to set + * @returns {AvailableModelsResponse.AvailableModel} AvailableModel instance + */ + AvailableModel.create = function create(properties) { + return new AvailableModel(properties); + }; + + /** + * Encodes the specified AvailableModel message. Does not implicitly {@link AvailableModelsResponse.AvailableModel.verify|verify} messages. + * @function encode + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {AvailableModelsResponse.IAvailableModel} message AvailableModel message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + AvailableModel.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.name != null && Object.hasOwnProperty.call(message, "name")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); + if (message.defaultOn != null && Object.hasOwnProperty.call(message, "defaultOn")) + writer.uint32(/* id 2, wireType 0 =*/16).bool(message.defaultOn); + if (message.isLongContextOnly != null && Object.hasOwnProperty.call(message, "isLongContextOnly")) + writer.uint32(/* id 3, wireType 0 =*/24).bool(message.isLongContextOnly); + if (message.isChatOnly != null && Object.hasOwnProperty.call(message, "isChatOnly")) + writer.uint32(/* id 4, wireType 0 =*/32).bool(message.isChatOnly); + return writer; + }; + + /** + * Encodes the specified AvailableModel message, length delimited. Does not implicitly {@link AvailableModelsResponse.AvailableModel.verify|verify} messages. + * @function encodeDelimited + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {AvailableModelsResponse.IAvailableModel} message AvailableModel message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + AvailableModel.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an AvailableModel message from the specified reader or buffer. + * @function decode + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {AvailableModelsResponse.AvailableModel} AvailableModel + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + AvailableModel.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.AvailableModelsResponse.AvailableModel(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.name = reader.string(); + break; + } + case 2: { + message.defaultOn = reader.bool(); + break; + } + case 3: { + message.isLongContextOnly = reader.bool(); + break; + } + case 4: { + message.isChatOnly = reader.bool(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an AvailableModel message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {AvailableModelsResponse.AvailableModel} AvailableModel + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + AvailableModel.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an AvailableModel message. + * @function verify + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + AvailableModel.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + var properties = {}; + if (message.name != null && message.hasOwnProperty("name")) + if (!$util.isString(message.name)) + return "name: string expected"; + if (message.defaultOn != null && message.hasOwnProperty("defaultOn")) + if (typeof message.defaultOn !== "boolean") + return "defaultOn: boolean expected"; + if (message.isLongContextOnly != null && message.hasOwnProperty("isLongContextOnly")) { + properties._isLongContextOnly = 1; + if (typeof message.isLongContextOnly !== "boolean") + return "isLongContextOnly: boolean expected"; + } + if (message.isChatOnly != null && message.hasOwnProperty("isChatOnly")) { + properties._isChatOnly = 1; + if (typeof message.isChatOnly !== "boolean") + return "isChatOnly: boolean expected"; + } + return null; + }; + + /** + * Creates an AvailableModel message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {Object.} object Plain object + * @returns {AvailableModelsResponse.AvailableModel} AvailableModel + */ + AvailableModel.fromObject = function fromObject(object) { + if (object instanceof $root.AvailableModelsResponse.AvailableModel) + return object; + var message = new $root.AvailableModelsResponse.AvailableModel(); + if (object.name != null) + message.name = String(object.name); + if (object.defaultOn != null) + message.defaultOn = Boolean(object.defaultOn); + if (object.isLongContextOnly != null) + message.isLongContextOnly = Boolean(object.isLongContextOnly); + if (object.isChatOnly != null) + message.isChatOnly = Boolean(object.isChatOnly); + return message; + }; + + /** + * Creates a plain object from an AvailableModel message. Also converts values to other types if specified. + * @function toObject + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {AvailableModelsResponse.AvailableModel} message AvailableModel + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + AvailableModel.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.name = ""; + object.defaultOn = false; + } + if (message.name != null && message.hasOwnProperty("name")) + object.name = message.name; + if (message.defaultOn != null && message.hasOwnProperty("defaultOn")) + object.defaultOn = message.defaultOn; + if (message.isLongContextOnly != null && message.hasOwnProperty("isLongContextOnly")) { + object.isLongContextOnly = message.isLongContextOnly; + if (options.oneofs) + object._isLongContextOnly = "isLongContextOnly"; + } + if (message.isChatOnly != null && message.hasOwnProperty("isChatOnly")) { + object.isChatOnly = message.isChatOnly; + if (options.oneofs) + object._isChatOnly = "isChatOnly"; + } + return object; + }; + + /** + * Converts this AvailableModel to JSON. + * @function toJSON + * @memberof AvailableModelsResponse.AvailableModel + * @instance + * @returns {Object.} JSON object + */ + AvailableModel.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for AvailableModel + * @function getTypeUrl + * @memberof AvailableModelsResponse.AvailableModel + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + AvailableModel.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/AvailableModelsResponse.AvailableModel"; + }; + + return AvailableModel; + })(); + + return AvailableModelsResponse; +})(); + +$root.MessageSummary = (function() { + + /** + * Properties of a MessageSummary. + * @exports IMessageSummary + * @interface IMessageSummary + * @property {string|null} [content] MessageSummary content + * @property {string|null} [summaryId1] MessageSummary summaryId1 + * @property {string|null} [summaryId2] MessageSummary summaryId2 + * @property {string|null} [previousSummaryId] MessageSummary previousSummaryId + */ + + /** + * Constructs a new MessageSummary. + * @exports MessageSummary + * @classdesc Represents a MessageSummary. + * @implements IMessageSummary + * @constructor + * @param {IMessageSummary=} [properties] Properties to set + */ + function MessageSummary(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * MessageSummary content. + * @member {string} content + * @memberof MessageSummary + * @instance + */ + MessageSummary.prototype.content = ""; + + /** + * MessageSummary summaryId1. + * @member {string} summaryId1 + * @memberof MessageSummary + * @instance + */ + MessageSummary.prototype.summaryId1 = ""; + + /** + * MessageSummary summaryId2. + * @member {string} summaryId2 + * @memberof MessageSummary + * @instance + */ + MessageSummary.prototype.summaryId2 = ""; + + /** + * MessageSummary previousSummaryId. + * @member {string} previousSummaryId + * @memberof MessageSummary + * @instance + */ + MessageSummary.prototype.previousSummaryId = ""; + + /** + * Creates a new MessageSummary instance using the specified properties. + * @function create + * @memberof MessageSummary + * @static + * @param {IMessageSummary=} [properties] Properties to set + * @returns {MessageSummary} MessageSummary instance + */ + MessageSummary.create = function create(properties) { + return new MessageSummary(properties); + }; + + /** + * Encodes the specified MessageSummary message. Does not implicitly {@link MessageSummary.verify|verify} messages. + * @function encode + * @memberof MessageSummary + * @static + * @param {IMessageSummary} message MessageSummary message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MessageSummary.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.content); + if (message.summaryId1 != null && Object.hasOwnProperty.call(message, "summaryId1")) + writer.uint32(/* id 2, wireType 2 =*/18).string(message.summaryId1); + if (message.summaryId2 != null && Object.hasOwnProperty.call(message, "summaryId2")) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.summaryId2); + if (message.previousSummaryId != null && Object.hasOwnProperty.call(message, "previousSummaryId")) + writer.uint32(/* id 4, wireType 2 =*/34).string(message.previousSummaryId); + return writer; + }; + + /** + * Encodes the specified MessageSummary message, length delimited. Does not implicitly {@link MessageSummary.verify|verify} messages. + * @function encodeDelimited + * @memberof MessageSummary + * @static + * @param {IMessageSummary} message MessageSummary message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MessageSummary.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MessageSummary message from the specified reader or buffer. + * @function decode + * @memberof MessageSummary + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {MessageSummary} MessageSummary + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MessageSummary.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.MessageSummary(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.content = reader.string(); + break; + } + case 2: { + message.summaryId1 = reader.string(); + break; + } + case 3: { + message.summaryId2 = reader.string(); + break; + } + case 4: { + message.previousSummaryId = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MessageSummary message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof MessageSummary + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {MessageSummary} MessageSummary + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MessageSummary.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MessageSummary message. + * @function verify + * @memberof MessageSummary + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MessageSummary.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.content != null && message.hasOwnProperty("content")) + if (!$util.isString(message.content)) + return "content: string expected"; + if (message.summaryId1 != null && message.hasOwnProperty("summaryId1")) + if (!$util.isString(message.summaryId1)) + return "summaryId1: string expected"; + if (message.summaryId2 != null && message.hasOwnProperty("summaryId2")) + if (!$util.isString(message.summaryId2)) + return "summaryId2: string expected"; + if (message.previousSummaryId != null && message.hasOwnProperty("previousSummaryId")) + if (!$util.isString(message.previousSummaryId)) + return "previousSummaryId: string expected"; + return null; + }; + + /** + * Creates a MessageSummary message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof MessageSummary + * @static + * @param {Object.} object Plain object + * @returns {MessageSummary} MessageSummary + */ + MessageSummary.fromObject = function fromObject(object) { + if (object instanceof $root.MessageSummary) + return object; + var message = new $root.MessageSummary(); + if (object.content != null) + message.content = String(object.content); + if (object.summaryId1 != null) + message.summaryId1 = String(object.summaryId1); + if (object.summaryId2 != null) + message.summaryId2 = String(object.summaryId2); + if (object.previousSummaryId != null) + message.previousSummaryId = String(object.previousSummaryId); + return message; + }; + + /** + * Creates a plain object from a MessageSummary message. Also converts values to other types if specified. + * @function toObject + * @memberof MessageSummary + * @static + * @param {MessageSummary} message MessageSummary + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MessageSummary.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.content = ""; + object.summaryId1 = ""; + object.summaryId2 = ""; + object.previousSummaryId = ""; + } + if (message.content != null && message.hasOwnProperty("content")) + object.content = message.content; + if (message.summaryId1 != null && message.hasOwnProperty("summaryId1")) + object.summaryId1 = message.summaryId1; + if (message.summaryId2 != null && message.hasOwnProperty("summaryId2")) + object.summaryId2 = message.summaryId2; + if (message.previousSummaryId != null && message.hasOwnProperty("previousSummaryId")) + object.previousSummaryId = message.previousSummaryId; + return object; + }; + + /** + * Converts this MessageSummary to JSON. + * @function toJSON + * @memberof MessageSummary + * @instance + * @returns {Object.} JSON object + */ + MessageSummary.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for MessageSummary + * @function getTypeUrl + * @memberof MessageSummary + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + MessageSummary.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/MessageSummary"; + }; + + return MessageSummary; +})(); + +$root.MessageThinking = (function() { + + /** + * Properties of a MessageThinking. + * @exports IMessageThinking + * @interface IMessageThinking + * @property {string|null} [content] MessageThinking content + */ + + /** + * Constructs a new MessageThinking. + * @exports MessageThinking + * @classdesc Represents a MessageThinking. + * @implements IMessageThinking + * @constructor + * @param {IMessageThinking=} [properties] Properties to set + */ + function MessageThinking(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * MessageThinking content. + * @member {string} content + * @memberof MessageThinking + * @instance + */ + MessageThinking.prototype.content = ""; + + /** + * Creates a new MessageThinking instance using the specified properties. + * @function create + * @memberof MessageThinking + * @static + * @param {IMessageThinking=} [properties] Properties to set + * @returns {MessageThinking} MessageThinking instance + */ + MessageThinking.create = function create(properties) { + return new MessageThinking(properties); + }; + + /** + * Encodes the specified MessageThinking message. Does not implicitly {@link MessageThinking.verify|verify} messages. + * @function encode + * @memberof MessageThinking + * @static + * @param {IMessageThinking} message MessageThinking message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MessageThinking.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.content); + return writer; + }; + + /** + * Encodes the specified MessageThinking message, length delimited. Does not implicitly {@link MessageThinking.verify|verify} messages. + * @function encodeDelimited + * @memberof MessageThinking + * @static + * @param {IMessageThinking} message MessageThinking message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MessageThinking.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MessageThinking message from the specified reader or buffer. + * @function decode + * @memberof MessageThinking + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {MessageThinking} MessageThinking + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MessageThinking.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.MessageThinking(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.content = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MessageThinking message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof MessageThinking + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {MessageThinking} MessageThinking + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MessageThinking.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MessageThinking message. + * @function verify + * @memberof MessageThinking + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MessageThinking.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.content != null && message.hasOwnProperty("content")) + if (!$util.isString(message.content)) + return "content: string expected"; + return null; + }; + + /** + * Creates a MessageThinking message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof MessageThinking + * @static + * @param {Object.} object Plain object + * @returns {MessageThinking} MessageThinking + */ + MessageThinking.fromObject = function fromObject(object) { + if (object instanceof $root.MessageThinking) + return object; + var message = new $root.MessageThinking(); + if (object.content != null) + message.content = String(object.content); + return message; + }; + + /** + * Creates a plain object from a MessageThinking message. Also converts values to other types if specified. + * @function toObject + * @memberof MessageThinking + * @static + * @param {MessageThinking} message MessageThinking + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MessageThinking.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.content = ""; + if (message.content != null && message.hasOwnProperty("content")) + object.content = message.content; + return object; + }; + + /** + * Converts this MessageThinking to JSON. + * @function toJSON + * @memberof MessageThinking + * @instance + * @returns {Object.} JSON object + */ + MessageThinking.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for MessageThinking + * @function getTypeUrl + * @memberof MessageThinking + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + MessageThinking.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/MessageThinking"; + }; + + return MessageThinking; +})(); + +$root.StreamUnifiedChatWithToolsRequest = (function() { + + /** + * Properties of a StreamUnifiedChatWithToolsRequest. + * @exports IStreamUnifiedChatWithToolsRequest + * @interface IStreamUnifiedChatWithToolsRequest + * @property {StreamUnifiedChatWithToolsRequest.IRequest|null} [request] StreamUnifiedChatWithToolsRequest request + */ + + /** + * Constructs a new StreamUnifiedChatWithToolsRequest. + * @exports StreamUnifiedChatWithToolsRequest + * @classdesc Represents a StreamUnifiedChatWithToolsRequest. + * @implements IStreamUnifiedChatWithToolsRequest + * @constructor + * @param {IStreamUnifiedChatWithToolsRequest=} [properties] Properties to set + */ + function StreamUnifiedChatWithToolsRequest(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * StreamUnifiedChatWithToolsRequest request. + * @member {StreamUnifiedChatWithToolsRequest.IRequest|null|undefined} request + * @memberof StreamUnifiedChatWithToolsRequest + * @instance + */ + StreamUnifiedChatWithToolsRequest.prototype.request = null; + + /** + * Creates a new StreamUnifiedChatWithToolsRequest instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {IStreamUnifiedChatWithToolsRequest=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest} StreamUnifiedChatWithToolsRequest instance + */ + StreamUnifiedChatWithToolsRequest.create = function create(properties) { + return new StreamUnifiedChatWithToolsRequest(properties); + }; + + /** + * Encodes the specified StreamUnifiedChatWithToolsRequest message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {IStreamUnifiedChatWithToolsRequest} message StreamUnifiedChatWithToolsRequest message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + StreamUnifiedChatWithToolsRequest.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.request != null && Object.hasOwnProperty.call(message, "request")) + $root.StreamUnifiedChatWithToolsRequest.Request.encode(message.request, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified StreamUnifiedChatWithToolsRequest message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {IStreamUnifiedChatWithToolsRequest} message StreamUnifiedChatWithToolsRequest message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + StreamUnifiedChatWithToolsRequest.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a StreamUnifiedChatWithToolsRequest message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest} StreamUnifiedChatWithToolsRequest + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + StreamUnifiedChatWithToolsRequest.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.request = $root.StreamUnifiedChatWithToolsRequest.Request.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a StreamUnifiedChatWithToolsRequest message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest} StreamUnifiedChatWithToolsRequest + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + StreamUnifiedChatWithToolsRequest.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a StreamUnifiedChatWithToolsRequest message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + StreamUnifiedChatWithToolsRequest.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.request != null && message.hasOwnProperty("request")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.verify(message.request); + if (error) + return "request." + error; + } + return null; + }; + + /** + * Creates a StreamUnifiedChatWithToolsRequest message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest} StreamUnifiedChatWithToolsRequest + */ + StreamUnifiedChatWithToolsRequest.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest(); + if (object.request != null) { + if (typeof object.request !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.request: object expected"); + message.request = $root.StreamUnifiedChatWithToolsRequest.Request.fromObject(object.request); + } + return message; + }; + + /** + * Creates a plain object from a StreamUnifiedChatWithToolsRequest message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {StreamUnifiedChatWithToolsRequest} message StreamUnifiedChatWithToolsRequest + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + StreamUnifiedChatWithToolsRequest.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.request = null; + if (message.request != null && message.hasOwnProperty("request")) + object.request = $root.StreamUnifiedChatWithToolsRequest.Request.toObject(message.request, options); + return object; + }; + + /** + * Converts this StreamUnifiedChatWithToolsRequest to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest + * @instance + * @returns {Object.} JSON object + */ + StreamUnifiedChatWithToolsRequest.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for StreamUnifiedChatWithToolsRequest + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + StreamUnifiedChatWithToolsRequest.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest"; + }; + + StreamUnifiedChatWithToolsRequest.Request = (function() { + + /** + * Properties of a Request. + * @memberof StreamUnifiedChatWithToolsRequest + * @interface IRequest + * @property {Array.|null} [messages] Request messages + * @property {number|null} [unknown2] Request unknown2 + * @property {StreamUnifiedChatWithToolsRequest.Request.IInstruction|null} [instruction] Request instruction + * @property {number|null} [unknown4] Request unknown4 + * @property {StreamUnifiedChatWithToolsRequest.Request.IModel|null} [model] Request model + * @property {Array.|null} [wikiTool] Request wikiTool + * @property {string|null} [webTool] Request webTool + * @property {number|null} [unknown13] Request unknown13 + * @property {StreamUnifiedChatWithToolsRequest.Request.ICursorSetting|null} [cursorSetting] Request cursorSetting + * @property {number|null} [unknown19] Request unknown19 + * @property {number|null} [unknown22] Request unknown22 + * @property {string|null} [conversationId] Request conversationId + * @property {StreamUnifiedChatWithToolsRequest.Request.IMetadata|null} [metadata] Request metadata + * @property {number|null} [unknown27] Request unknown27 + * @property {string|null} [unknown29] Request unknown29 + * @property {Array.|null} [messageIds] Request messageIds + * @property {number|null} [largeContext] Request largeContext + * @property {number|null} [unknown38] Request unknown38 + * @property {number|null} [chatModeEnum] Request chatModeEnum + * @property {string|null} [unknown47] Request unknown47 + * @property {number|null} [unknown48] Request unknown48 + * @property {number|null} [unknown49] Request unknown49 + * @property {number|null} [unknown51] Request unknown51 + * @property {number|null} [unknown53] Request unknown53 + * @property {string|null} [chatMode] Request chatMode + */ + + /** + * Constructs a new Request. + * @memberof StreamUnifiedChatWithToolsRequest + * @classdesc Represents a Request. + * @implements IRequest + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.IRequest=} [properties] Properties to set + */ + function Request(properties) { + this.messages = []; + this.wikiTool = []; + this.messageIds = []; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Request messages. + * @member {Array.} messages + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.messages = $util.emptyArray; + + /** + * Request unknown2. + * @member {number} unknown2 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown2 = 0; + + /** + * Request instruction. + * @member {StreamUnifiedChatWithToolsRequest.Request.IInstruction|null|undefined} instruction + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.instruction = null; + + /** + * Request unknown4. + * @member {number} unknown4 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown4 = 0; + + /** + * Request model. + * @member {StreamUnifiedChatWithToolsRequest.Request.IModel|null|undefined} model + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.model = null; + + /** + * Request wikiTool. + * @member {Array.} wikiTool + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.wikiTool = $util.emptyArray; + + /** + * Request webTool. + * @member {string} webTool + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.webTool = ""; + + /** + * Request unknown13. + * @member {number} unknown13 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown13 = 0; + + /** + * Request cursorSetting. + * @member {StreamUnifiedChatWithToolsRequest.Request.ICursorSetting|null|undefined} cursorSetting + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.cursorSetting = null; + + /** + * Request unknown19. + * @member {number} unknown19 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown19 = 0; + + /** + * Request unknown22. + * @member {number} unknown22 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown22 = 0; + + /** + * Request conversationId. + * @member {string} conversationId + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.conversationId = ""; + + /** + * Request metadata. + * @member {StreamUnifiedChatWithToolsRequest.Request.IMetadata|null|undefined} metadata + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.metadata = null; + + /** + * Request unknown27. + * @member {number} unknown27 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown27 = 0; + + /** + * Request unknown29. + * @member {string} unknown29 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown29 = ""; + + /** + * Request messageIds. + * @member {Array.} messageIds + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.messageIds = $util.emptyArray; + + /** + * Request largeContext. + * @member {number} largeContext + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.largeContext = 0; + + /** + * Request unknown38. + * @member {number} unknown38 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown38 = 0; + + /** + * Request chatModeEnum. + * @member {number} chatModeEnum + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.chatModeEnum = 0; + + /** + * Request unknown47. + * @member {string} unknown47 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown47 = ""; + + /** + * Request unknown48. + * @member {number} unknown48 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown48 = 0; + + /** + * Request unknown49. + * @member {number} unknown49 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown49 = 0; + + /** + * Request unknown51. + * @member {number} unknown51 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown51 = 0; + + /** + * Request unknown53. + * @member {number} unknown53 + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.unknown53 = 0; + + /** + * Request chatMode. + * @member {string} chatMode + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + */ + Request.prototype.chatMode = ""; + + /** + * Creates a new Request instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {StreamUnifiedChatWithToolsRequest.IRequest=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request} Request instance + */ + Request.create = function create(properties) { + return new Request(properties); + }; + + /** + * Encodes the specified Request message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {StreamUnifiedChatWithToolsRequest.IRequest} message Request message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Request.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.messages != null && message.messages.length) + for (var i = 0; i < message.messages.length; ++i) + $root.StreamUnifiedChatWithToolsRequest.Request.Message.encode(message.messages[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + if (message.unknown2 != null && Object.hasOwnProperty.call(message, "unknown2")) + writer.uint32(/* id 2, wireType 0 =*/16).int32(message.unknown2); + if (message.instruction != null && Object.hasOwnProperty.call(message, "instruction")) + $root.StreamUnifiedChatWithToolsRequest.Request.Instruction.encode(message.instruction, writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); + if (message.unknown4 != null && Object.hasOwnProperty.call(message, "unknown4")) + writer.uint32(/* id 4, wireType 0 =*/32).int32(message.unknown4); + if (message.model != null && Object.hasOwnProperty.call(message, "model")) + $root.StreamUnifiedChatWithToolsRequest.Request.Model.encode(message.model, writer.uint32(/* id 5, wireType 2 =*/42).fork()).ldelim(); + if (message.wikiTool != null && message.wikiTool.length) + for (var i = 0; i < message.wikiTool.length; ++i) + writer.uint32(/* id 7, wireType 2 =*/58).string(message.wikiTool[i]); + if (message.webTool != null && Object.hasOwnProperty.call(message, "webTool")) + writer.uint32(/* id 8, wireType 2 =*/66).string(message.webTool); + if (message.unknown13 != null && Object.hasOwnProperty.call(message, "unknown13")) + writer.uint32(/* id 13, wireType 0 =*/104).int32(message.unknown13); + if (message.cursorSetting != null && Object.hasOwnProperty.call(message, "cursorSetting")) + $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.encode(message.cursorSetting, writer.uint32(/* id 15, wireType 2 =*/122).fork()).ldelim(); + if (message.unknown19 != null && Object.hasOwnProperty.call(message, "unknown19")) + writer.uint32(/* id 19, wireType 0 =*/152).int32(message.unknown19); + if (message.unknown22 != null && Object.hasOwnProperty.call(message, "unknown22")) + writer.uint32(/* id 22, wireType 0 =*/176).int32(message.unknown22); + if (message.conversationId != null && Object.hasOwnProperty.call(message, "conversationId")) + writer.uint32(/* id 23, wireType 2 =*/186).string(message.conversationId); + if (message.metadata != null && Object.hasOwnProperty.call(message, "metadata")) + $root.StreamUnifiedChatWithToolsRequest.Request.Metadata.encode(message.metadata, writer.uint32(/* id 26, wireType 2 =*/210).fork()).ldelim(); + if (message.unknown27 != null && Object.hasOwnProperty.call(message, "unknown27")) + writer.uint32(/* id 27, wireType 0 =*/216).int32(message.unknown27); + if (message.unknown29 != null && Object.hasOwnProperty.call(message, "unknown29")) + writer.uint32(/* id 29, wireType 2 =*/234).string(message.unknown29); + if (message.messageIds != null && message.messageIds.length) + for (var i = 0; i < message.messageIds.length; ++i) + $root.StreamUnifiedChatWithToolsRequest.Request.MessageId.encode(message.messageIds[i], writer.uint32(/* id 30, wireType 2 =*/242).fork()).ldelim(); + if (message.largeContext != null && Object.hasOwnProperty.call(message, "largeContext")) + writer.uint32(/* id 35, wireType 0 =*/280).int32(message.largeContext); + if (message.unknown38 != null && Object.hasOwnProperty.call(message, "unknown38")) + writer.uint32(/* id 38, wireType 0 =*/304).int32(message.unknown38); + if (message.chatModeEnum != null && Object.hasOwnProperty.call(message, "chatModeEnum")) + writer.uint32(/* id 46, wireType 0 =*/368).int32(message.chatModeEnum); + if (message.unknown47 != null && Object.hasOwnProperty.call(message, "unknown47")) + writer.uint32(/* id 47, wireType 2 =*/378).string(message.unknown47); + if (message.unknown48 != null && Object.hasOwnProperty.call(message, "unknown48")) + writer.uint32(/* id 48, wireType 0 =*/384).int32(message.unknown48); + if (message.unknown49 != null && Object.hasOwnProperty.call(message, "unknown49")) + writer.uint32(/* id 49, wireType 0 =*/392).int32(message.unknown49); + if (message.unknown51 != null && Object.hasOwnProperty.call(message, "unknown51")) + writer.uint32(/* id 51, wireType 0 =*/408).int32(message.unknown51); + if (message.unknown53 != null && Object.hasOwnProperty.call(message, "unknown53")) + writer.uint32(/* id 53, wireType 0 =*/424).int32(message.unknown53); + if (message.chatMode != null && Object.hasOwnProperty.call(message, "chatMode")) + writer.uint32(/* id 54, wireType 2 =*/434).string(message.chatMode); + return writer; + }; + + /** + * Encodes the specified Request message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {StreamUnifiedChatWithToolsRequest.IRequest} message Request message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Request.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Request message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request} Request + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Request.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (!(message.messages && message.messages.length)) + message.messages = []; + message.messages.push($root.StreamUnifiedChatWithToolsRequest.Request.Message.decode(reader, reader.uint32())); + break; + } + case 2: { + message.unknown2 = reader.int32(); + break; + } + case 3: { + message.instruction = $root.StreamUnifiedChatWithToolsRequest.Request.Instruction.decode(reader, reader.uint32()); + break; + } + case 4: { + message.unknown4 = reader.int32(); + break; + } + case 5: { + message.model = $root.StreamUnifiedChatWithToolsRequest.Request.Model.decode(reader, reader.uint32()); + break; + } + case 7: { + if (!(message.wikiTool && message.wikiTool.length)) + message.wikiTool = []; + message.wikiTool.push(reader.string()); + break; + } + case 8: { + message.webTool = reader.string(); + break; + } + case 13: { + message.unknown13 = reader.int32(); + break; + } + case 15: { + message.cursorSetting = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.decode(reader, reader.uint32()); + break; + } + case 19: { + message.unknown19 = reader.int32(); + break; + } + case 22: { + message.unknown22 = reader.int32(); + break; + } + case 23: { + message.conversationId = reader.string(); + break; + } + case 26: { + message.metadata = $root.StreamUnifiedChatWithToolsRequest.Request.Metadata.decode(reader, reader.uint32()); + break; + } + case 27: { + message.unknown27 = reader.int32(); + break; + } + case 29: { + message.unknown29 = reader.string(); + break; + } + case 30: { + if (!(message.messageIds && message.messageIds.length)) + message.messageIds = []; + message.messageIds.push($root.StreamUnifiedChatWithToolsRequest.Request.MessageId.decode(reader, reader.uint32())); + break; + } + case 35: { + message.largeContext = reader.int32(); + break; + } + case 38: { + message.unknown38 = reader.int32(); + break; + } + case 46: { + message.chatModeEnum = reader.int32(); + break; + } + case 47: { + message.unknown47 = reader.string(); + break; + } + case 48: { + message.unknown48 = reader.int32(); + break; + } + case 49: { + message.unknown49 = reader.int32(); + break; + } + case 51: { + message.unknown51 = reader.int32(); + break; + } + case 53: { + message.unknown53 = reader.int32(); + break; + } + case 54: { + message.chatMode = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Request message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request} Request + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Request.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Request message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Request.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.messages != null && message.hasOwnProperty("messages")) { + if (!Array.isArray(message.messages)) + return "messages: array expected"; + for (var i = 0; i < message.messages.length; ++i) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.Message.verify(message.messages[i]); + if (error) + return "messages." + error; + } + } + if (message.unknown2 != null && message.hasOwnProperty("unknown2")) + if (!$util.isInteger(message.unknown2)) + return "unknown2: integer expected"; + if (message.instruction != null && message.hasOwnProperty("instruction")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.Instruction.verify(message.instruction); + if (error) + return "instruction." + error; + } + if (message.unknown4 != null && message.hasOwnProperty("unknown4")) + if (!$util.isInteger(message.unknown4)) + return "unknown4: integer expected"; + if (message.model != null && message.hasOwnProperty("model")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.Model.verify(message.model); + if (error) + return "model." + error; + } + if (message.wikiTool != null && message.hasOwnProperty("wikiTool")) { + if (!Array.isArray(message.wikiTool)) + return "wikiTool: array expected"; + for (var i = 0; i < message.wikiTool.length; ++i) + if (!$util.isString(message.wikiTool[i])) + return "wikiTool: string[] expected"; + } + if (message.webTool != null && message.hasOwnProperty("webTool")) + if (!$util.isString(message.webTool)) + return "webTool: string expected"; + if (message.unknown13 != null && message.hasOwnProperty("unknown13")) + if (!$util.isInteger(message.unknown13)) + return "unknown13: integer expected"; + if (message.cursorSetting != null && message.hasOwnProperty("cursorSetting")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.verify(message.cursorSetting); + if (error) + return "cursorSetting." + error; + } + if (message.unknown19 != null && message.hasOwnProperty("unknown19")) + if (!$util.isInteger(message.unknown19)) + return "unknown19: integer expected"; + if (message.unknown22 != null && message.hasOwnProperty("unknown22")) + if (!$util.isInteger(message.unknown22)) + return "unknown22: integer expected"; + if (message.conversationId != null && message.hasOwnProperty("conversationId")) + if (!$util.isString(message.conversationId)) + return "conversationId: string expected"; + if (message.metadata != null && message.hasOwnProperty("metadata")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.Metadata.verify(message.metadata); + if (error) + return "metadata." + error; + } + if (message.unknown27 != null && message.hasOwnProperty("unknown27")) + if (!$util.isInteger(message.unknown27)) + return "unknown27: integer expected"; + if (message.unknown29 != null && message.hasOwnProperty("unknown29")) + if (!$util.isString(message.unknown29)) + return "unknown29: string expected"; + if (message.messageIds != null && message.hasOwnProperty("messageIds")) { + if (!Array.isArray(message.messageIds)) + return "messageIds: array expected"; + for (var i = 0; i < message.messageIds.length; ++i) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.MessageId.verify(message.messageIds[i]); + if (error) + return "messageIds." + error; + } + } + if (message.largeContext != null && message.hasOwnProperty("largeContext")) + if (!$util.isInteger(message.largeContext)) + return "largeContext: integer expected"; + if (message.unknown38 != null && message.hasOwnProperty("unknown38")) + if (!$util.isInteger(message.unknown38)) + return "unknown38: integer expected"; + if (message.chatModeEnum != null && message.hasOwnProperty("chatModeEnum")) + if (!$util.isInteger(message.chatModeEnum)) + return "chatModeEnum: integer expected"; + if (message.unknown47 != null && message.hasOwnProperty("unknown47")) + if (!$util.isString(message.unknown47)) + return "unknown47: string expected"; + if (message.unknown48 != null && message.hasOwnProperty("unknown48")) + if (!$util.isInteger(message.unknown48)) + return "unknown48: integer expected"; + if (message.unknown49 != null && message.hasOwnProperty("unknown49")) + if (!$util.isInteger(message.unknown49)) + return "unknown49: integer expected"; + if (message.unknown51 != null && message.hasOwnProperty("unknown51")) + if (!$util.isInteger(message.unknown51)) + return "unknown51: integer expected"; + if (message.unknown53 != null && message.hasOwnProperty("unknown53")) + if (!$util.isInteger(message.unknown53)) + return "unknown53: integer expected"; + if (message.chatMode != null && message.hasOwnProperty("chatMode")) + if (!$util.isString(message.chatMode)) + return "chatMode: string expected"; + return null; + }; + + /** + * Creates a Request message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request} Request + */ + Request.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request(); + if (object.messages) { + if (!Array.isArray(object.messages)) + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.messages: array expected"); + message.messages = []; + for (var i = 0; i < object.messages.length; ++i) { + if (typeof object.messages[i] !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.messages: object expected"); + message.messages[i] = $root.StreamUnifiedChatWithToolsRequest.Request.Message.fromObject(object.messages[i]); + } + } + if (object.unknown2 != null) + message.unknown2 = object.unknown2 | 0; + if (object.instruction != null) { + if (typeof object.instruction !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.instruction: object expected"); + message.instruction = $root.StreamUnifiedChatWithToolsRequest.Request.Instruction.fromObject(object.instruction); + } + if (object.unknown4 != null) + message.unknown4 = object.unknown4 | 0; + if (object.model != null) { + if (typeof object.model !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.model: object expected"); + message.model = $root.StreamUnifiedChatWithToolsRequest.Request.Model.fromObject(object.model); + } + if (object.wikiTool) { + if (!Array.isArray(object.wikiTool)) + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.wikiTool: array expected"); + message.wikiTool = []; + for (var i = 0; i < object.wikiTool.length; ++i) + message.wikiTool[i] = String(object.wikiTool[i]); + } + if (object.webTool != null) + message.webTool = String(object.webTool); + if (object.unknown13 != null) + message.unknown13 = object.unknown13 | 0; + if (object.cursorSetting != null) { + if (typeof object.cursorSetting !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.cursorSetting: object expected"); + message.cursorSetting = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.fromObject(object.cursorSetting); + } + if (object.unknown19 != null) + message.unknown19 = object.unknown19 | 0; + if (object.unknown22 != null) + message.unknown22 = object.unknown22 | 0; + if (object.conversationId != null) + message.conversationId = String(object.conversationId); + if (object.metadata != null) { + if (typeof object.metadata !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.metadata: object expected"); + message.metadata = $root.StreamUnifiedChatWithToolsRequest.Request.Metadata.fromObject(object.metadata); + } + if (object.unknown27 != null) + message.unknown27 = object.unknown27 | 0; + if (object.unknown29 != null) + message.unknown29 = String(object.unknown29); + if (object.messageIds) { + if (!Array.isArray(object.messageIds)) + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.messageIds: array expected"); + message.messageIds = []; + for (var i = 0; i < object.messageIds.length; ++i) { + if (typeof object.messageIds[i] !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.messageIds: object expected"); + message.messageIds[i] = $root.StreamUnifiedChatWithToolsRequest.Request.MessageId.fromObject(object.messageIds[i]); + } + } + if (object.largeContext != null) + message.largeContext = object.largeContext | 0; + if (object.unknown38 != null) + message.unknown38 = object.unknown38 | 0; + if (object.chatModeEnum != null) + message.chatModeEnum = object.chatModeEnum | 0; + if (object.unknown47 != null) + message.unknown47 = String(object.unknown47); + if (object.unknown48 != null) + message.unknown48 = object.unknown48 | 0; + if (object.unknown49 != null) + message.unknown49 = object.unknown49 | 0; + if (object.unknown51 != null) + message.unknown51 = object.unknown51 | 0; + if (object.unknown53 != null) + message.unknown53 = object.unknown53 | 0; + if (object.chatMode != null) + message.chatMode = String(object.chatMode); + return message; + }; + + /** + * Creates a plain object from a Request message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request} message Request + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Request.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) { + object.messages = []; + object.wikiTool = []; + object.messageIds = []; + } + if (options.defaults) { + object.unknown2 = 0; + object.instruction = null; + object.unknown4 = 0; + object.model = null; + object.webTool = ""; + object.unknown13 = 0; + object.cursorSetting = null; + object.unknown19 = 0; + object.unknown22 = 0; + object.conversationId = ""; + object.metadata = null; + object.unknown27 = 0; + object.unknown29 = ""; + object.largeContext = 0; + object.unknown38 = 0; + object.chatModeEnum = 0; + object.unknown47 = ""; + object.unknown48 = 0; + object.unknown49 = 0; + object.unknown51 = 0; + object.unknown53 = 0; + object.chatMode = ""; + } + if (message.messages && message.messages.length) { + object.messages = []; + for (var j = 0; j < message.messages.length; ++j) + object.messages[j] = $root.StreamUnifiedChatWithToolsRequest.Request.Message.toObject(message.messages[j], options); + } + if (message.unknown2 != null && message.hasOwnProperty("unknown2")) + object.unknown2 = message.unknown2; + if (message.instruction != null && message.hasOwnProperty("instruction")) + object.instruction = $root.StreamUnifiedChatWithToolsRequest.Request.Instruction.toObject(message.instruction, options); + if (message.unknown4 != null && message.hasOwnProperty("unknown4")) + object.unknown4 = message.unknown4; + if (message.model != null && message.hasOwnProperty("model")) + object.model = $root.StreamUnifiedChatWithToolsRequest.Request.Model.toObject(message.model, options); + if (message.wikiTool && message.wikiTool.length) { + object.wikiTool = []; + for (var j = 0; j < message.wikiTool.length; ++j) + object.wikiTool[j] = message.wikiTool[j]; + } + if (message.webTool != null && message.hasOwnProperty("webTool")) + object.webTool = message.webTool; + if (message.unknown13 != null && message.hasOwnProperty("unknown13")) + object.unknown13 = message.unknown13; + if (message.cursorSetting != null && message.hasOwnProperty("cursorSetting")) + object.cursorSetting = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.toObject(message.cursorSetting, options); + if (message.unknown19 != null && message.hasOwnProperty("unknown19")) + object.unknown19 = message.unknown19; + if (message.unknown22 != null && message.hasOwnProperty("unknown22")) + object.unknown22 = message.unknown22; + if (message.conversationId != null && message.hasOwnProperty("conversationId")) + object.conversationId = message.conversationId; + if (message.metadata != null && message.hasOwnProperty("metadata")) + object.metadata = $root.StreamUnifiedChatWithToolsRequest.Request.Metadata.toObject(message.metadata, options); + if (message.unknown27 != null && message.hasOwnProperty("unknown27")) + object.unknown27 = message.unknown27; + if (message.unknown29 != null && message.hasOwnProperty("unknown29")) + object.unknown29 = message.unknown29; + if (message.messageIds && message.messageIds.length) { + object.messageIds = []; + for (var j = 0; j < message.messageIds.length; ++j) + object.messageIds[j] = $root.StreamUnifiedChatWithToolsRequest.Request.MessageId.toObject(message.messageIds[j], options); + } + if (message.largeContext != null && message.hasOwnProperty("largeContext")) + object.largeContext = message.largeContext; + if (message.unknown38 != null && message.hasOwnProperty("unknown38")) + object.unknown38 = message.unknown38; + if (message.chatModeEnum != null && message.hasOwnProperty("chatModeEnum")) + object.chatModeEnum = message.chatModeEnum; + if (message.unknown47 != null && message.hasOwnProperty("unknown47")) + object.unknown47 = message.unknown47; + if (message.unknown48 != null && message.hasOwnProperty("unknown48")) + object.unknown48 = message.unknown48; + if (message.unknown49 != null && message.hasOwnProperty("unknown49")) + object.unknown49 = message.unknown49; + if (message.unknown51 != null && message.hasOwnProperty("unknown51")) + object.unknown51 = message.unknown51; + if (message.unknown53 != null && message.hasOwnProperty("unknown53")) + object.unknown53 = message.unknown53; + if (message.chatMode != null && message.hasOwnProperty("chatMode")) + object.chatMode = message.chatMode; + return object; + }; + + /** + * Converts this Request to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @instance + * @returns {Object.} JSON object + */ + Request.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Request + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Request.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request"; + }; + + Request.Message = (function() { + + /** + * Properties of a Message. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @interface IMessage + * @property {string|null} [content] Message content + * @property {number|null} [role] Message role + * @property {StreamUnifiedChatWithToolsRequest.Request.Message.IImage|null} [image] Message image + * @property {string|null} [messageId] Message messageId + * @property {string|null} [unknown29] Message unknown29 + * @property {string|null} [summaryId] Message summaryId + * @property {IMessageSummary|null} [summary] Message summary + * @property {IMessageThinking|null} [thinking] Message thinking + * @property {number|null} [chatModeEnum] Message chatModeEnum + */ + + /** + * Constructs a new Message. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @classdesc Represents a Message. + * @implements IMessage + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessage=} [properties] Properties to set + */ + function Message(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Message content. + * @member {string} content + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.content = ""; + + /** + * Message role. + * @member {number} role + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.role = 0; + + /** + * Message image. + * @member {StreamUnifiedChatWithToolsRequest.Request.Message.IImage|null|undefined} image + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.image = null; + + /** + * Message messageId. + * @member {string} messageId + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.messageId = ""; + + /** + * Message unknown29. + * @member {string} unknown29 + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.unknown29 = ""; + + /** + * Message summaryId. + * @member {string} summaryId + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.summaryId = ""; + + /** + * Message summary. + * @member {IMessageSummary|null|undefined} summary + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.summary = null; + + /** + * Message thinking. + * @member {IMessageThinking|null|undefined} thinking + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.thinking = null; + + /** + * Message chatModeEnum. + * @member {number} chatModeEnum + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + */ + Message.prototype.chatModeEnum = 0; + + /** + * Creates a new Message instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessage=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message} Message instance + */ + Message.create = function create(properties) { + return new Message(properties); + }; + + /** + * Encodes the specified Message message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Message.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessage} message Message message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Message.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.content); + if (message.role != null && Object.hasOwnProperty.call(message, "role")) + writer.uint32(/* id 2, wireType 0 =*/16).int32(message.role); + if (message.image != null && Object.hasOwnProperty.call(message, "image")) + $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.encode(message.image, writer.uint32(/* id 10, wireType 2 =*/82).fork()).ldelim(); + if (message.messageId != null && Object.hasOwnProperty.call(message, "messageId")) + writer.uint32(/* id 13, wireType 2 =*/106).string(message.messageId); + if (message.unknown29 != null && Object.hasOwnProperty.call(message, "unknown29")) + writer.uint32(/* id 29, wireType 2 =*/234).string(message.unknown29); + if (message.summaryId != null && Object.hasOwnProperty.call(message, "summaryId")) + writer.uint32(/* id 32, wireType 2 =*/258).string(message.summaryId); + if (message.summary != null && Object.hasOwnProperty.call(message, "summary")) + $root.MessageSummary.encode(message.summary, writer.uint32(/* id 39, wireType 2 =*/314).fork()).ldelim(); + if (message.thinking != null && Object.hasOwnProperty.call(message, "thinking")) + $root.MessageThinking.encode(message.thinking, writer.uint32(/* id 45, wireType 2 =*/362).fork()).ldelim(); + if (message.chatModeEnum != null && Object.hasOwnProperty.call(message, "chatModeEnum")) + writer.uint32(/* id 47, wireType 0 =*/376).int32(message.chatModeEnum); + return writer; + }; + + /** + * Encodes the specified Message message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Message.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessage} message Message message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Message.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Message message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message} Message + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Message.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.Message(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.content = reader.string(); + break; + } + case 2: { + message.role = reader.int32(); + break; + } + case 10: { + message.image = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.decode(reader, reader.uint32()); + break; + } + case 13: { + message.messageId = reader.string(); + break; + } + case 29: { + message.unknown29 = reader.string(); + break; + } + case 32: { + message.summaryId = reader.string(); + break; + } + case 39: { + message.summary = $root.MessageSummary.decode(reader, reader.uint32()); + break; + } + case 45: { + message.thinking = $root.MessageThinking.decode(reader, reader.uint32()); + break; + } + case 47: { + message.chatModeEnum = reader.int32(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Message message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message} Message + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Message.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Message message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Message.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.content != null && message.hasOwnProperty("content")) + if (!$util.isString(message.content)) + return "content: string expected"; + if (message.role != null && message.hasOwnProperty("role")) + if (!$util.isInteger(message.role)) + return "role: integer expected"; + if (message.image != null && message.hasOwnProperty("image")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.verify(message.image); + if (error) + return "image." + error; + } + if (message.messageId != null && message.hasOwnProperty("messageId")) + if (!$util.isString(message.messageId)) + return "messageId: string expected"; + if (message.unknown29 != null && message.hasOwnProperty("unknown29")) + if (!$util.isString(message.unknown29)) + return "unknown29: string expected"; + if (message.summaryId != null && message.hasOwnProperty("summaryId")) + if (!$util.isString(message.summaryId)) + return "summaryId: string expected"; + if (message.summary != null && message.hasOwnProperty("summary")) { + var error = $root.MessageSummary.verify(message.summary); + if (error) + return "summary." + error; + } + if (message.thinking != null && message.hasOwnProperty("thinking")) { + var error = $root.MessageThinking.verify(message.thinking); + if (error) + return "thinking." + error; + } + if (message.chatModeEnum != null && message.hasOwnProperty("chatModeEnum")) + if (!$util.isInteger(message.chatModeEnum)) + return "chatModeEnum: integer expected"; + return null; + }; + + /** + * Creates a Message message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message} Message + */ + Message.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.Message) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.Message(); + if (object.content != null) + message.content = String(object.content); + if (object.role != null) + message.role = object.role | 0; + if (object.image != null) { + if (typeof object.image !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.Message.image: object expected"); + message.image = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.fromObject(object.image); + } + if (object.messageId != null) + message.messageId = String(object.messageId); + if (object.unknown29 != null) + message.unknown29 = String(object.unknown29); + if (object.summaryId != null) + message.summaryId = String(object.summaryId); + if (object.summary != null) { + if (typeof object.summary !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.Message.summary: object expected"); + message.summary = $root.MessageSummary.fromObject(object.summary); + } + if (object.thinking != null) { + if (typeof object.thinking !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.Message.thinking: object expected"); + message.thinking = $root.MessageThinking.fromObject(object.thinking); + } + if (object.chatModeEnum != null) + message.chatModeEnum = object.chatModeEnum | 0; + return message; + }; + + /** + * Creates a plain object from a Message message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message} message Message + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Message.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.content = ""; + object.role = 0; + object.image = null; + object.messageId = ""; + object.unknown29 = ""; + object.summaryId = ""; + object.summary = null; + object.thinking = null; + object.chatModeEnum = 0; + } + if (message.content != null && message.hasOwnProperty("content")) + object.content = message.content; + if (message.role != null && message.hasOwnProperty("role")) + object.role = message.role; + if (message.image != null && message.hasOwnProperty("image")) + object.image = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.toObject(message.image, options); + if (message.messageId != null && message.hasOwnProperty("messageId")) + object.messageId = message.messageId; + if (message.unknown29 != null && message.hasOwnProperty("unknown29")) + object.unknown29 = message.unknown29; + if (message.summaryId != null && message.hasOwnProperty("summaryId")) + object.summaryId = message.summaryId; + if (message.summary != null && message.hasOwnProperty("summary")) + object.summary = $root.MessageSummary.toObject(message.summary, options); + if (message.thinking != null && message.hasOwnProperty("thinking")) + object.thinking = $root.MessageThinking.toObject(message.thinking, options); + if (message.chatModeEnum != null && message.hasOwnProperty("chatModeEnum")) + object.chatModeEnum = message.chatModeEnum; + return object; + }; + + /** + * Converts this Message to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @instance + * @returns {Object.} JSON object + */ + Message.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Message + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Message.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.Message"; + }; + + Message.Image = (function() { + + /** + * Properties of an Image. + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @interface IImage + * @property {Uint8Array|null} [data] Image data + * @property {StreamUnifiedChatWithToolsRequest.Request.Message.Image.IMetadata|null} [metadata] Image metadata + */ + + /** + * Constructs a new Image. + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message + * @classdesc Represents an Image. + * @implements IImage + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.IImage=} [properties] Properties to set + */ + function Image(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Image data. + * @member {Uint8Array} data + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @instance + */ + Image.prototype.data = $util.newBuffer([]); + + /** + * Image metadata. + * @member {StreamUnifiedChatWithToolsRequest.Request.Message.Image.IMetadata|null|undefined} metadata + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @instance + */ + Image.prototype.metadata = null; + + /** + * Creates a new Image instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.IImage=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image} Image instance + */ + Image.create = function create(properties) { + return new Image(properties); + }; + + /** + * Encodes the specified Image message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Message.Image.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.IImage} message Image message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Image.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.data != null && Object.hasOwnProperty.call(message, "data")) + writer.uint32(/* id 1, wireType 2 =*/10).bytes(message.data); + if (message.metadata != null && Object.hasOwnProperty.call(message, "metadata")) + $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.encode(message.metadata, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified Image message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Message.Image.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.IImage} message Image message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Image.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an Image message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image} Image + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Image.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.data = reader.bytes(); + break; + } + case 2: { + message.metadata = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an Image message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image} Image + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Image.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an Image message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Image.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.data != null && message.hasOwnProperty("data")) + if (!(message.data && typeof message.data.length === "number" || $util.isString(message.data))) + return "data: buffer expected"; + if (message.metadata != null && message.hasOwnProperty("metadata")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.verify(message.metadata); + if (error) + return "metadata." + error; + } + return null; + }; + + /** + * Creates an Image message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image} Image + */ + Image.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image(); + if (object.data != null) + if (typeof object.data === "string") + $util.base64.decode(object.data, message.data = $util.newBuffer($util.base64.length(object.data)), 0); + else if (object.data.length >= 0) + message.data = object.data; + if (object.metadata != null) { + if (typeof object.metadata !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.Message.Image.metadata: object expected"); + message.metadata = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.fromObject(object.metadata); + } + return message; + }; + + /** + * Creates a plain object from an Image message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.Image} message Image + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Image.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + if (options.bytes === String) + object.data = ""; + else { + object.data = []; + if (options.bytes !== Array) + object.data = $util.newBuffer(object.data); + } + object.metadata = null; + } + if (message.data != null && message.hasOwnProperty("data")) + object.data = options.bytes === String ? $util.base64.encode(message.data, 0, message.data.length) : options.bytes === Array ? Array.prototype.slice.call(message.data) : message.data; + if (message.metadata != null && message.hasOwnProperty("metadata")) + object.metadata = $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.toObject(message.metadata, options); + return object; + }; + + /** + * Converts this Image to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @instance + * @returns {Object.} JSON object + */ + Image.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Image + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Image.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.Message.Image"; + }; + + Image.Metadata = (function() { + + /** + * Properties of a Metadata. + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @interface IMetadata + * @property {number|null} [width] Metadata width + * @property {number|null} [height] Metadata height + */ + + /** + * Constructs a new Metadata. + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image + * @classdesc Represents a Metadata. + * @implements IMetadata + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.Image.IMetadata=} [properties] Properties to set + */ + function Metadata(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Metadata width. + * @member {number} width + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @instance + */ + Metadata.prototype.width = 0; + + /** + * Metadata height. + * @member {number} height + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @instance + */ + Metadata.prototype.height = 0; + + /** + * Creates a new Metadata instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.Image.IMetadata=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata} Metadata instance + */ + Metadata.create = function create(properties) { + return new Metadata(properties); + }; + + /** + * Encodes the specified Metadata message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.Image.IMetadata} message Metadata message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Metadata.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.width != null && Object.hasOwnProperty.call(message, "width")) + writer.uint32(/* id 1, wireType 0 =*/8).int32(message.width); + if (message.height != null && Object.hasOwnProperty.call(message, "height")) + writer.uint32(/* id 2, wireType 0 =*/16).int32(message.height); + return writer; + }; + + /** + * Encodes the specified Metadata message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.Image.IMetadata} message Metadata message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Metadata.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Metadata message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata} Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Metadata.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.width = reader.int32(); + break; + } + case 2: { + message.height = reader.int32(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Metadata message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata} Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Metadata.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Metadata message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Metadata.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.width != null && message.hasOwnProperty("width")) + if (!$util.isInteger(message.width)) + return "width: integer expected"; + if (message.height != null && message.hasOwnProperty("height")) + if (!$util.isInteger(message.height)) + return "height: integer expected"; + return null; + }; + + /** + * Creates a Metadata message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata} Metadata + */ + Metadata.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata(); + if (object.width != null) + message.width = object.width | 0; + if (object.height != null) + message.height = object.height | 0; + return message; + }; + + /** + * Creates a plain object from a Metadata message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata} message Metadata + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Metadata.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.width = 0; + object.height = 0; + } + if (message.width != null && message.hasOwnProperty("width")) + object.width = message.width; + if (message.height != null && message.hasOwnProperty("height")) + object.height = message.height; + return object; + }; + + /** + * Converts this Metadata to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @instance + * @returns {Object.} JSON object + */ + Metadata.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Metadata + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Metadata.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.Message.Image.Metadata"; + }; + + return Metadata; + })(); + + return Image; + })(); + + return Message; + })(); + + Request.Instruction = (function() { + + /** + * Properties of an Instruction. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @interface IInstruction + * @property {string|null} [instruction] Instruction instruction + */ + + /** + * Constructs a new Instruction. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @classdesc Represents an Instruction. + * @implements IInstruction + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.IInstruction=} [properties] Properties to set + */ + function Instruction(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Instruction instruction. + * @member {string} instruction + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @instance + */ + Instruction.prototype.instruction = ""; + + /** + * Creates a new Instruction instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IInstruction=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.Instruction} Instruction instance + */ + Instruction.create = function create(properties) { + return new Instruction(properties); + }; + + /** + * Encodes the specified Instruction message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Instruction.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IInstruction} message Instruction message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Instruction.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.instruction != null && Object.hasOwnProperty.call(message, "instruction")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.instruction); + return writer; + }; + + /** + * Encodes the specified Instruction message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Instruction.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IInstruction} message Instruction message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Instruction.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an Instruction message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.Instruction} Instruction + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Instruction.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.Instruction(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.instruction = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an Instruction message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.Instruction} Instruction + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Instruction.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an Instruction message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Instruction.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.instruction != null && message.hasOwnProperty("instruction")) + if (!$util.isString(message.instruction)) + return "instruction: string expected"; + return null; + }; + + /** + * Creates an Instruction message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.Instruction} Instruction + */ + Instruction.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.Instruction) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.Instruction(); + if (object.instruction != null) + message.instruction = String(object.instruction); + return message; + }; + + /** + * Creates a plain object from an Instruction message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Instruction} message Instruction + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Instruction.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.instruction = ""; + if (message.instruction != null && message.hasOwnProperty("instruction")) + object.instruction = message.instruction; + return object; + }; + + /** + * Converts this Instruction to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @instance + * @returns {Object.} JSON object + */ + Instruction.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Instruction + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.Instruction + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Instruction.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.Instruction"; + }; + + return Instruction; + })(); + + Request.Model = (function() { + + /** + * Properties of a Model. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @interface IModel + * @property {string|null} [name] Model name + * @property {Uint8Array|null} [empty] Model empty + */ + + /** + * Constructs a new Model. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @classdesc Represents a Model. + * @implements IModel + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.IModel=} [properties] Properties to set + */ + function Model(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Model name. + * @member {string} name + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @instance + */ + Model.prototype.name = ""; + + /** + * Model empty. + * @member {Uint8Array} empty + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @instance + */ + Model.prototype.empty = $util.newBuffer([]); + + /** + * Creates a new Model instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IModel=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.Model} Model instance + */ + Model.create = function create(properties) { + return new Model(properties); + }; + + /** + * Encodes the specified Model message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Model.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IModel} message Model message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Model.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.name != null && Object.hasOwnProperty.call(message, "name")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); + if (message.empty != null && Object.hasOwnProperty.call(message, "empty")) + writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.empty); + return writer; + }; + + /** + * Encodes the specified Model message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Model.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IModel} message Model message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Model.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Model message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.Model} Model + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Model.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.Model(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.name = reader.string(); + break; + } + case 4: { + message.empty = reader.bytes(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Model message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.Model} Model + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Model.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Model message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Model.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.name != null && message.hasOwnProperty("name")) + if (!$util.isString(message.name)) + return "name: string expected"; + if (message.empty != null && message.hasOwnProperty("empty")) + if (!(message.empty && typeof message.empty.length === "number" || $util.isString(message.empty))) + return "empty: buffer expected"; + return null; + }; + + /** + * Creates a Model message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.Model} Model + */ + Model.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.Model) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.Model(); + if (object.name != null) + message.name = String(object.name); + if (object.empty != null) + if (typeof object.empty === "string") + $util.base64.decode(object.empty, message.empty = $util.newBuffer($util.base64.length(object.empty)), 0); + else if (object.empty.length >= 0) + message.empty = object.empty; + return message; + }; + + /** + * Creates a plain object from a Model message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Model} message Model + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Model.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.name = ""; + if (options.bytes === String) + object.empty = ""; + else { + object.empty = []; + if (options.bytes !== Array) + object.empty = $util.newBuffer(object.empty); + } + } + if (message.name != null && message.hasOwnProperty("name")) + object.name = message.name; + if (message.empty != null && message.hasOwnProperty("empty")) + object.empty = options.bytes === String ? $util.base64.encode(message.empty, 0, message.empty.length) : options.bytes === Array ? Array.prototype.slice.call(message.empty) : message.empty; + return object; + }; + + /** + * Converts this Model to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @instance + * @returns {Object.} JSON object + */ + Model.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Model + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.Model + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Model.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.Model"; + }; + + return Model; + })(); + + Request.CursorSetting = (function() { + + /** + * Properties of a CursorSetting. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @interface ICursorSetting + * @property {string|null} [name] CursorSetting name + * @property {Uint8Array|null} [unknown3] CursorSetting unknown3 + * @property {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.IUnknown6|null} [unknown6] CursorSetting unknown6 + * @property {number|null} [unknown8] CursorSetting unknown8 + * @property {number|null} [unknown9] CursorSetting unknown9 + */ + + /** + * Constructs a new CursorSetting. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @classdesc Represents a CursorSetting. + * @implements ICursorSetting + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.ICursorSetting=} [properties] Properties to set + */ + function CursorSetting(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * CursorSetting name. + * @member {string} name + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @instance + */ + CursorSetting.prototype.name = ""; + + /** + * CursorSetting unknown3. + * @member {Uint8Array} unknown3 + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @instance + */ + CursorSetting.prototype.unknown3 = $util.newBuffer([]); + + /** + * CursorSetting unknown6. + * @member {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.IUnknown6|null|undefined} unknown6 + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @instance + */ + CursorSetting.prototype.unknown6 = null; + + /** + * CursorSetting unknown8. + * @member {number} unknown8 + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @instance + */ + CursorSetting.prototype.unknown8 = 0; + + /** + * CursorSetting unknown9. + * @member {number} unknown9 + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @instance + */ + CursorSetting.prototype.unknown9 = 0; + + /** + * Creates a new CursorSetting instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.ICursorSetting=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting} CursorSetting instance + */ + CursorSetting.create = function create(properties) { + return new CursorSetting(properties); + }; + + /** + * Encodes the specified CursorSetting message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.CursorSetting.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.ICursorSetting} message CursorSetting message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + CursorSetting.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.name != null && Object.hasOwnProperty.call(message, "name")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.name); + if (message.unknown3 != null && Object.hasOwnProperty.call(message, "unknown3")) + writer.uint32(/* id 3, wireType 2 =*/26).bytes(message.unknown3); + if (message.unknown6 != null && Object.hasOwnProperty.call(message, "unknown6")) + $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.encode(message.unknown6, writer.uint32(/* id 6, wireType 2 =*/50).fork()).ldelim(); + if (message.unknown8 != null && Object.hasOwnProperty.call(message, "unknown8")) + writer.uint32(/* id 8, wireType 0 =*/64).int32(message.unknown8); + if (message.unknown9 != null && Object.hasOwnProperty.call(message, "unknown9")) + writer.uint32(/* id 9, wireType 0 =*/72).int32(message.unknown9); + return writer; + }; + + /** + * Encodes the specified CursorSetting message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.CursorSetting.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.ICursorSetting} message CursorSetting message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + CursorSetting.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a CursorSetting message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting} CursorSetting + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + CursorSetting.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.name = reader.string(); + break; + } + case 3: { + message.unknown3 = reader.bytes(); + break; + } + case 6: { + message.unknown6 = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.decode(reader, reader.uint32()); + break; + } + case 8: { + message.unknown8 = reader.int32(); + break; + } + case 9: { + message.unknown9 = reader.int32(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a CursorSetting message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting} CursorSetting + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + CursorSetting.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a CursorSetting message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + CursorSetting.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.name != null && message.hasOwnProperty("name")) + if (!$util.isString(message.name)) + return "name: string expected"; + if (message.unknown3 != null && message.hasOwnProperty("unknown3")) + if (!(message.unknown3 && typeof message.unknown3.length === "number" || $util.isString(message.unknown3))) + return "unknown3: buffer expected"; + if (message.unknown6 != null && message.hasOwnProperty("unknown6")) { + var error = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.verify(message.unknown6); + if (error) + return "unknown6." + error; + } + if (message.unknown8 != null && message.hasOwnProperty("unknown8")) + if (!$util.isInteger(message.unknown8)) + return "unknown8: integer expected"; + if (message.unknown9 != null && message.hasOwnProperty("unknown9")) + if (!$util.isInteger(message.unknown9)) + return "unknown9: integer expected"; + return null; + }; + + /** + * Creates a CursorSetting message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting} CursorSetting + */ + CursorSetting.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting(); + if (object.name != null) + message.name = String(object.name); + if (object.unknown3 != null) + if (typeof object.unknown3 === "string") + $util.base64.decode(object.unknown3, message.unknown3 = $util.newBuffer($util.base64.length(object.unknown3)), 0); + else if (object.unknown3.length >= 0) + message.unknown3 = object.unknown3; + if (object.unknown6 != null) { + if (typeof object.unknown6 !== "object") + throw TypeError(".StreamUnifiedChatWithToolsRequest.Request.CursorSetting.unknown6: object expected"); + message.unknown6 = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.fromObject(object.unknown6); + } + if (object.unknown8 != null) + message.unknown8 = object.unknown8 | 0; + if (object.unknown9 != null) + message.unknown9 = object.unknown9 | 0; + return message; + }; + + /** + * Creates a plain object from a CursorSetting message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.CursorSetting} message CursorSetting + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + CursorSetting.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.name = ""; + if (options.bytes === String) + object.unknown3 = ""; + else { + object.unknown3 = []; + if (options.bytes !== Array) + object.unknown3 = $util.newBuffer(object.unknown3); + } + object.unknown6 = null; + object.unknown8 = 0; + object.unknown9 = 0; + } + if (message.name != null && message.hasOwnProperty("name")) + object.name = message.name; + if (message.unknown3 != null && message.hasOwnProperty("unknown3")) + object.unknown3 = options.bytes === String ? $util.base64.encode(message.unknown3, 0, message.unknown3.length) : options.bytes === Array ? Array.prototype.slice.call(message.unknown3) : message.unknown3; + if (message.unknown6 != null && message.hasOwnProperty("unknown6")) + object.unknown6 = $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.toObject(message.unknown6, options); + if (message.unknown8 != null && message.hasOwnProperty("unknown8")) + object.unknown8 = message.unknown8; + if (message.unknown9 != null && message.hasOwnProperty("unknown9")) + object.unknown9 = message.unknown9; + return object; + }; + + /** + * Converts this CursorSetting to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @instance + * @returns {Object.} JSON object + */ + CursorSetting.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for CursorSetting + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + CursorSetting.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.CursorSetting"; + }; + + CursorSetting.Unknown6 = (function() { + + /** + * Properties of an Unknown6. + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @interface IUnknown6 + * @property {Uint8Array|null} [unknown1] Unknown6 unknown1 + * @property {Uint8Array|null} [unknown2] Unknown6 unknown2 + */ + + /** + * Constructs a new Unknown6. + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting + * @classdesc Represents an Unknown6. + * @implements IUnknown6 + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.IUnknown6=} [properties] Properties to set + */ + function Unknown6(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Unknown6 unknown1. + * @member {Uint8Array} unknown1 + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @instance + */ + Unknown6.prototype.unknown1 = $util.newBuffer([]); + + /** + * Unknown6 unknown2. + * @member {Uint8Array} unknown2 + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @instance + */ + Unknown6.prototype.unknown2 = $util.newBuffer([]); + + /** + * Creates a new Unknown6 instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.IUnknown6=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6} Unknown6 instance + */ + Unknown6.create = function create(properties) { + return new Unknown6(properties); + }; + + /** + * Encodes the specified Unknown6 message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.IUnknown6} message Unknown6 message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Unknown6.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.unknown1 != null && Object.hasOwnProperty.call(message, "unknown1")) + writer.uint32(/* id 1, wireType 2 =*/10).bytes(message.unknown1); + if (message.unknown2 != null && Object.hasOwnProperty.call(message, "unknown2")) + writer.uint32(/* id 2, wireType 2 =*/18).bytes(message.unknown2); + return writer; + }; + + /** + * Encodes the specified Unknown6 message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.IUnknown6} message Unknown6 message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Unknown6.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an Unknown6 message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6} Unknown6 + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Unknown6.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.unknown1 = reader.bytes(); + break; + } + case 2: { + message.unknown2 = reader.bytes(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an Unknown6 message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6} Unknown6 + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Unknown6.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an Unknown6 message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Unknown6.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.unknown1 != null && message.hasOwnProperty("unknown1")) + if (!(message.unknown1 && typeof message.unknown1.length === "number" || $util.isString(message.unknown1))) + return "unknown1: buffer expected"; + if (message.unknown2 != null && message.hasOwnProperty("unknown2")) + if (!(message.unknown2 && typeof message.unknown2.length === "number" || $util.isString(message.unknown2))) + return "unknown2: buffer expected"; + return null; + }; + + /** + * Creates an Unknown6 message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6} Unknown6 + */ + Unknown6.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6(); + if (object.unknown1 != null) + if (typeof object.unknown1 === "string") + $util.base64.decode(object.unknown1, message.unknown1 = $util.newBuffer($util.base64.length(object.unknown1)), 0); + else if (object.unknown1.length >= 0) + message.unknown1 = object.unknown1; + if (object.unknown2 != null) + if (typeof object.unknown2 === "string") + $util.base64.decode(object.unknown2, message.unknown2 = $util.newBuffer($util.base64.length(object.unknown2)), 0); + else if (object.unknown2.length >= 0) + message.unknown2 = object.unknown2; + return message; + }; + + /** + * Creates a plain object from an Unknown6 message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6} message Unknown6 + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Unknown6.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + if (options.bytes === String) + object.unknown1 = ""; + else { + object.unknown1 = []; + if (options.bytes !== Array) + object.unknown1 = $util.newBuffer(object.unknown1); + } + if (options.bytes === String) + object.unknown2 = ""; + else { + object.unknown2 = []; + if (options.bytes !== Array) + object.unknown2 = $util.newBuffer(object.unknown2); + } + } + if (message.unknown1 != null && message.hasOwnProperty("unknown1")) + object.unknown1 = options.bytes === String ? $util.base64.encode(message.unknown1, 0, message.unknown1.length) : options.bytes === Array ? Array.prototype.slice.call(message.unknown1) : message.unknown1; + if (message.unknown2 != null && message.hasOwnProperty("unknown2")) + object.unknown2 = options.bytes === String ? $util.base64.encode(message.unknown2, 0, message.unknown2.length) : options.bytes === Array ? Array.prototype.slice.call(message.unknown2) : message.unknown2; + return object; + }; + + /** + * Converts this Unknown6 to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @instance + * @returns {Object.} JSON object + */ + Unknown6.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Unknown6 + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6 + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Unknown6.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.CursorSetting.Unknown6"; + }; + + return Unknown6; + })(); + + return CursorSetting; + })(); + + Request.Metadata = (function() { + + /** + * Properties of a Metadata. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @interface IMetadata + * @property {string|null} [os] Metadata os + * @property {string|null} [arch] Metadata arch + * @property {string|null} [version] Metadata version + * @property {string|null} [path] Metadata path + * @property {string|null} [timestamp] Metadata timestamp + */ + + /** + * Constructs a new Metadata. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @classdesc Represents a Metadata. + * @implements IMetadata + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.IMetadata=} [properties] Properties to set + */ + function Metadata(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Metadata os. + * @member {string} os + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @instance + */ + Metadata.prototype.os = ""; + + /** + * Metadata arch. + * @member {string} arch + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @instance + */ + Metadata.prototype.arch = ""; + + /** + * Metadata version. + * @member {string} version + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @instance + */ + Metadata.prototype.version = ""; + + /** + * Metadata path. + * @member {string} path + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @instance + */ + Metadata.prototype.path = ""; + + /** + * Metadata timestamp. + * @member {string} timestamp + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @instance + */ + Metadata.prototype.timestamp = ""; + + /** + * Creates a new Metadata instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMetadata=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.Metadata} Metadata instance + */ + Metadata.create = function create(properties) { + return new Metadata(properties); + }; + + /** + * Encodes the specified Metadata message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Metadata.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMetadata} message Metadata message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Metadata.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.os != null && Object.hasOwnProperty.call(message, "os")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.os); + if (message.arch != null && Object.hasOwnProperty.call(message, "arch")) + writer.uint32(/* id 2, wireType 2 =*/18).string(message.arch); + if (message.version != null && Object.hasOwnProperty.call(message, "version")) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.version); + if (message.path != null && Object.hasOwnProperty.call(message, "path")) + writer.uint32(/* id 4, wireType 2 =*/34).string(message.path); + if (message.timestamp != null && Object.hasOwnProperty.call(message, "timestamp")) + writer.uint32(/* id 5, wireType 2 =*/42).string(message.timestamp); + return writer; + }; + + /** + * Encodes the specified Metadata message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.Metadata.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMetadata} message Metadata message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Metadata.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Metadata message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.Metadata} Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Metadata.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.Metadata(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.os = reader.string(); + break; + } + case 2: { + message.arch = reader.string(); + break; + } + case 3: { + message.version = reader.string(); + break; + } + case 4: { + message.path = reader.string(); + break; + } + case 5: { + message.timestamp = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Metadata message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.Metadata} Metadata + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Metadata.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Metadata message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Metadata.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.os != null && message.hasOwnProperty("os")) + if (!$util.isString(message.os)) + return "os: string expected"; + if (message.arch != null && message.hasOwnProperty("arch")) + if (!$util.isString(message.arch)) + return "arch: string expected"; + if (message.version != null && message.hasOwnProperty("version")) + if (!$util.isString(message.version)) + return "version: string expected"; + if (message.path != null && message.hasOwnProperty("path")) + if (!$util.isString(message.path)) + return "path: string expected"; + if (message.timestamp != null && message.hasOwnProperty("timestamp")) + if (!$util.isString(message.timestamp)) + return "timestamp: string expected"; + return null; + }; + + /** + * Creates a Metadata message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.Metadata} Metadata + */ + Metadata.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.Metadata) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.Metadata(); + if (object.os != null) + message.os = String(object.os); + if (object.arch != null) + message.arch = String(object.arch); + if (object.version != null) + message.version = String(object.version); + if (object.path != null) + message.path = String(object.path); + if (object.timestamp != null) + message.timestamp = String(object.timestamp); + return message; + }; + + /** + * Creates a plain object from a Metadata message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.Metadata} message Metadata + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Metadata.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.os = ""; + object.arch = ""; + object.version = ""; + object.path = ""; + object.timestamp = ""; + } + if (message.os != null && message.hasOwnProperty("os")) + object.os = message.os; + if (message.arch != null && message.hasOwnProperty("arch")) + object.arch = message.arch; + if (message.version != null && message.hasOwnProperty("version")) + object.version = message.version; + if (message.path != null && message.hasOwnProperty("path")) + object.path = message.path; + if (message.timestamp != null && message.hasOwnProperty("timestamp")) + object.timestamp = message.timestamp; + return object; + }; + + /** + * Converts this Metadata to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @instance + * @returns {Object.} JSON object + */ + Metadata.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Metadata + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.Metadata + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Metadata.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.Metadata"; + }; + + return Metadata; + })(); + + Request.MessageId = (function() { + + /** + * Properties of a MessageId. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @interface IMessageId + * @property {string|null} [messageId] MessageId messageId + * @property {string|null} [summaryId] MessageId summaryId + * @property {number|null} [role] MessageId role + */ + + /** + * Constructs a new MessageId. + * @memberof StreamUnifiedChatWithToolsRequest.Request + * @classdesc Represents a MessageId. + * @implements IMessageId + * @constructor + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessageId=} [properties] Properties to set + */ + function MessageId(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * MessageId messageId. + * @member {string} messageId + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @instance + */ + MessageId.prototype.messageId = ""; + + /** + * MessageId summaryId. + * @member {string} summaryId + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @instance + */ + MessageId.prototype.summaryId = ""; + + /** + * MessageId role. + * @member {number} role + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @instance + */ + MessageId.prototype.role = 0; + + /** + * Creates a new MessageId instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessageId=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsRequest.Request.MessageId} MessageId instance + */ + MessageId.create = function create(properties) { + return new MessageId(properties); + }; + + /** + * Encodes the specified MessageId message. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.MessageId.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessageId} message MessageId message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MessageId.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.messageId != null && Object.hasOwnProperty.call(message, "messageId")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.messageId); + if (message.summaryId != null && Object.hasOwnProperty.call(message, "summaryId")) + writer.uint32(/* id 2, wireType 2 =*/18).string(message.summaryId); + if (message.role != null && Object.hasOwnProperty.call(message, "role")) + writer.uint32(/* id 3, wireType 0 =*/24).int32(message.role); + return writer; + }; + + /** + * Encodes the specified MessageId message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsRequest.Request.MessageId.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.IMessageId} message MessageId message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + MessageId.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a MessageId message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsRequest.Request.MessageId} MessageId + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MessageId.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsRequest.Request.MessageId(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.messageId = reader.string(); + break; + } + case 2: { + message.summaryId = reader.string(); + break; + } + case 3: { + message.role = reader.int32(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a MessageId message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsRequest.Request.MessageId} MessageId + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + MessageId.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a MessageId message. + * @function verify + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + MessageId.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.messageId != null && message.hasOwnProperty("messageId")) + if (!$util.isString(message.messageId)) + return "messageId: string expected"; + if (message.summaryId != null && message.hasOwnProperty("summaryId")) + if (!$util.isString(message.summaryId)) + return "summaryId: string expected"; + if (message.role != null && message.hasOwnProperty("role")) + if (!$util.isInteger(message.role)) + return "role: integer expected"; + return null; + }; + + /** + * Creates a MessageId message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsRequest.Request.MessageId} MessageId + */ + MessageId.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsRequest.Request.MessageId) + return object; + var message = new $root.StreamUnifiedChatWithToolsRequest.Request.MessageId(); + if (object.messageId != null) + message.messageId = String(object.messageId); + if (object.summaryId != null) + message.summaryId = String(object.summaryId); + if (object.role != null) + message.role = object.role | 0; + return message; + }; + + /** + * Creates a plain object from a MessageId message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {StreamUnifiedChatWithToolsRequest.Request.MessageId} message MessageId + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + MessageId.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.messageId = ""; + object.summaryId = ""; + object.role = 0; + } + if (message.messageId != null && message.hasOwnProperty("messageId")) + object.messageId = message.messageId; + if (message.summaryId != null && message.hasOwnProperty("summaryId")) + object.summaryId = message.summaryId; + if (message.role != null && message.hasOwnProperty("role")) + object.role = message.role; + return object; + }; + + /** + * Converts this MessageId to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @instance + * @returns {Object.} JSON object + */ + MessageId.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for MessageId + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsRequest.Request.MessageId + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + MessageId.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsRequest.Request.MessageId"; + }; + + return MessageId; + })(); + + return Request; + })(); + + return StreamUnifiedChatWithToolsRequest; +})(); + +$root.StreamUnifiedChatWithToolsResponse = (function() { + + /** + * Properties of a StreamUnifiedChatWithToolsResponse. + * @exports IStreamUnifiedChatWithToolsResponse + * @interface IStreamUnifiedChatWithToolsResponse + * @property {StreamUnifiedChatWithToolsResponse.IMessage|null} [message] StreamUnifiedChatWithToolsResponse message + * @property {IMessageSummary|null} [summary] StreamUnifiedChatWithToolsResponse summary + */ + + /** + * Constructs a new StreamUnifiedChatWithToolsResponse. + * @exports StreamUnifiedChatWithToolsResponse + * @classdesc Represents a StreamUnifiedChatWithToolsResponse. + * @implements IStreamUnifiedChatWithToolsResponse + * @constructor + * @param {IStreamUnifiedChatWithToolsResponse=} [properties] Properties to set + */ + function StreamUnifiedChatWithToolsResponse(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * StreamUnifiedChatWithToolsResponse message. + * @member {StreamUnifiedChatWithToolsResponse.IMessage|null|undefined} message + * @memberof StreamUnifiedChatWithToolsResponse + * @instance + */ + StreamUnifiedChatWithToolsResponse.prototype.message = null; + + /** + * StreamUnifiedChatWithToolsResponse summary. + * @member {IMessageSummary|null|undefined} summary + * @memberof StreamUnifiedChatWithToolsResponse + * @instance + */ + StreamUnifiedChatWithToolsResponse.prototype.summary = null; + + /** + * Creates a new StreamUnifiedChatWithToolsResponse instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {IStreamUnifiedChatWithToolsResponse=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsResponse} StreamUnifiedChatWithToolsResponse instance + */ + StreamUnifiedChatWithToolsResponse.create = function create(properties) { + return new StreamUnifiedChatWithToolsResponse(properties); + }; + + /** + * Encodes the specified StreamUnifiedChatWithToolsResponse message. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {IStreamUnifiedChatWithToolsResponse} message StreamUnifiedChatWithToolsResponse message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + StreamUnifiedChatWithToolsResponse.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.message != null && Object.hasOwnProperty.call(message, "message")) + $root.StreamUnifiedChatWithToolsResponse.Message.encode(message.message, writer.uint32(/* id 2, wireType 2 =*/18).fork()).ldelim(); + if (message.summary != null && Object.hasOwnProperty.call(message, "summary")) + $root.MessageSummary.encode(message.summary, writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified StreamUnifiedChatWithToolsResponse message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {IStreamUnifiedChatWithToolsResponse} message StreamUnifiedChatWithToolsResponse message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + StreamUnifiedChatWithToolsResponse.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a StreamUnifiedChatWithToolsResponse message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsResponse} StreamUnifiedChatWithToolsResponse + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + StreamUnifiedChatWithToolsResponse.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsResponse(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 2: { + message.message = $root.StreamUnifiedChatWithToolsResponse.Message.decode(reader, reader.uint32()); + break; + } + case 3: { + message.summary = $root.MessageSummary.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a StreamUnifiedChatWithToolsResponse message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsResponse} StreamUnifiedChatWithToolsResponse + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + StreamUnifiedChatWithToolsResponse.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a StreamUnifiedChatWithToolsResponse message. + * @function verify + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + StreamUnifiedChatWithToolsResponse.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.message != null && message.hasOwnProperty("message")) { + var error = $root.StreamUnifiedChatWithToolsResponse.Message.verify(message.message); + if (error) + return "message." + error; + } + if (message.summary != null && message.hasOwnProperty("summary")) { + var error = $root.MessageSummary.verify(message.summary); + if (error) + return "summary." + error; + } + return null; + }; + + /** + * Creates a StreamUnifiedChatWithToolsResponse message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsResponse} StreamUnifiedChatWithToolsResponse + */ + StreamUnifiedChatWithToolsResponse.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsResponse) + return object; + var message = new $root.StreamUnifiedChatWithToolsResponse(); + if (object.message != null) { + if (typeof object.message !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.message: object expected"); + message.message = $root.StreamUnifiedChatWithToolsResponse.Message.fromObject(object.message); + } + if (object.summary != null) { + if (typeof object.summary !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.summary: object expected"); + message.summary = $root.MessageSummary.fromObject(object.summary); + } + return message; + }; + + /** + * Creates a plain object from a StreamUnifiedChatWithToolsResponse message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {StreamUnifiedChatWithToolsResponse} message StreamUnifiedChatWithToolsResponse + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + StreamUnifiedChatWithToolsResponse.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.message = null; + object.summary = null; + } + if (message.message != null && message.hasOwnProperty("message")) + object.message = $root.StreamUnifiedChatWithToolsResponse.Message.toObject(message.message, options); + if (message.summary != null && message.hasOwnProperty("summary")) + object.summary = $root.MessageSummary.toObject(message.summary, options); + return object; + }; + + /** + * Converts this StreamUnifiedChatWithToolsResponse to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsResponse + * @instance + * @returns {Object.} JSON object + */ + StreamUnifiedChatWithToolsResponse.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for StreamUnifiedChatWithToolsResponse + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsResponse + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + StreamUnifiedChatWithToolsResponse.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsResponse"; + }; + + StreamUnifiedChatWithToolsResponse.Message = (function() { + + /** + * Properties of a Message. + * @memberof StreamUnifiedChatWithToolsResponse + * @interface IMessage + * @property {string|null} [content] Message content + * @property {StreamUnifiedChatWithToolsResponse.Message.IWebTool|null} [webtool] Message webtool + * @property {StreamUnifiedChatWithToolsResponse.Message.IUnknown12|null} [unknown12] Message unknown12 + * @property {string|null} [unknown22] Message unknown22 + * @property {string|null} [unknown23] Message unknown23 + * @property {string|null} [unknown27] Message unknown27 + * @property {IMessageThinking|null} [thinking] Message thinking + */ + + /** + * Constructs a new Message. + * @memberof StreamUnifiedChatWithToolsResponse + * @classdesc Represents a Message. + * @implements IMessage + * @constructor + * @param {StreamUnifiedChatWithToolsResponse.IMessage=} [properties] Properties to set + */ + function Message(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Message content. + * @member {string} content + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.content = ""; + + /** + * Message webtool. + * @member {StreamUnifiedChatWithToolsResponse.Message.IWebTool|null|undefined} webtool + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.webtool = null; + + /** + * Message unknown12. + * @member {StreamUnifiedChatWithToolsResponse.Message.IUnknown12|null|undefined} unknown12 + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.unknown12 = null; + + /** + * Message unknown22. + * @member {string} unknown22 + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.unknown22 = ""; + + /** + * Message unknown23. + * @member {string} unknown23 + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.unknown23 = ""; + + /** + * Message unknown27. + * @member {string} unknown27 + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.unknown27 = ""; + + /** + * Message thinking. + * @member {IMessageThinking|null|undefined} thinking + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + */ + Message.prototype.thinking = null; + + /** + * Creates a new Message instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {StreamUnifiedChatWithToolsResponse.IMessage=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsResponse.Message} Message instance + */ + Message.create = function create(properties) { + return new Message(properties); + }; + + /** + * Encodes the specified Message message. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {StreamUnifiedChatWithToolsResponse.IMessage} message Message message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Message.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.content); + if (message.webtool != null && Object.hasOwnProperty.call(message, "webtool")) + $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.encode(message.webtool, writer.uint32(/* id 11, wireType 2 =*/90).fork()).ldelim(); + if (message.unknown12 != null && Object.hasOwnProperty.call(message, "unknown12")) + $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.encode(message.unknown12, writer.uint32(/* id 12, wireType 2 =*/98).fork()).ldelim(); + if (message.unknown22 != null && Object.hasOwnProperty.call(message, "unknown22")) + writer.uint32(/* id 22, wireType 2 =*/178).string(message.unknown22); + if (message.unknown23 != null && Object.hasOwnProperty.call(message, "unknown23")) + writer.uint32(/* id 23, wireType 2 =*/186).string(message.unknown23); + if (message.thinking != null && Object.hasOwnProperty.call(message, "thinking")) + $root.MessageThinking.encode(message.thinking, writer.uint32(/* id 25, wireType 2 =*/202).fork()).ldelim(); + if (message.unknown27 != null && Object.hasOwnProperty.call(message, "unknown27")) + writer.uint32(/* id 27, wireType 2 =*/218).string(message.unknown27); + return writer; + }; + + /** + * Encodes the specified Message message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {StreamUnifiedChatWithToolsResponse.IMessage} message Message message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Message.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Message message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsResponse.Message} Message + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Message.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsResponse.Message(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.content = reader.string(); + break; + } + case 11: { + message.webtool = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.decode(reader, reader.uint32()); + break; + } + case 12: { + message.unknown12 = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.decode(reader, reader.uint32()); + break; + } + case 22: { + message.unknown22 = reader.string(); + break; + } + case 23: { + message.unknown23 = reader.string(); + break; + } + case 27: { + message.unknown27 = reader.string(); + break; + } + case 25: { + message.thinking = $root.MessageThinking.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Message message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsResponse.Message} Message + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Message.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Message message. + * @function verify + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Message.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.content != null && message.hasOwnProperty("content")) + if (!$util.isString(message.content)) + return "content: string expected"; + if (message.webtool != null && message.hasOwnProperty("webtool")) { + var error = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.verify(message.webtool); + if (error) + return "webtool." + error; + } + if (message.unknown12 != null && message.hasOwnProperty("unknown12")) { + var error = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.verify(message.unknown12); + if (error) + return "unknown12." + error; + } + if (message.unknown22 != null && message.hasOwnProperty("unknown22")) + if (!$util.isString(message.unknown22)) + return "unknown22: string expected"; + if (message.unknown23 != null && message.hasOwnProperty("unknown23")) + if (!$util.isString(message.unknown23)) + return "unknown23: string expected"; + if (message.unknown27 != null && message.hasOwnProperty("unknown27")) + if (!$util.isString(message.unknown27)) + return "unknown27: string expected"; + if (message.thinking != null && message.hasOwnProperty("thinking")) { + var error = $root.MessageThinking.verify(message.thinking); + if (error) + return "thinking." + error; + } + return null; + }; + + /** + * Creates a Message message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsResponse.Message} Message + */ + Message.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsResponse.Message) + return object; + var message = new $root.StreamUnifiedChatWithToolsResponse.Message(); + if (object.content != null) + message.content = String(object.content); + if (object.webtool != null) { + if (typeof object.webtool !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.Message.webtool: object expected"); + message.webtool = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.fromObject(object.webtool); + } + if (object.unknown12 != null) { + if (typeof object.unknown12 !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.Message.unknown12: object expected"); + message.unknown12 = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.fromObject(object.unknown12); + } + if (object.unknown22 != null) + message.unknown22 = String(object.unknown22); + if (object.unknown23 != null) + message.unknown23 = String(object.unknown23); + if (object.unknown27 != null) + message.unknown27 = String(object.unknown27); + if (object.thinking != null) { + if (typeof object.thinking !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.Message.thinking: object expected"); + message.thinking = $root.MessageThinking.fromObject(object.thinking); + } + return message; + }; + + /** + * Creates a plain object from a Message message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message} message Message + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Message.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.content = ""; + object.webtool = null; + object.unknown12 = null; + object.unknown22 = ""; + object.unknown23 = ""; + object.thinking = null; + object.unknown27 = ""; + } + if (message.content != null && message.hasOwnProperty("content")) + object.content = message.content; + if (message.webtool != null && message.hasOwnProperty("webtool")) + object.webtool = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.toObject(message.webtool, options); + if (message.unknown12 != null && message.hasOwnProperty("unknown12")) + object.unknown12 = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.toObject(message.unknown12, options); + if (message.unknown22 != null && message.hasOwnProperty("unknown22")) + object.unknown22 = message.unknown22; + if (message.unknown23 != null && message.hasOwnProperty("unknown23")) + object.unknown23 = message.unknown23; + if (message.thinking != null && message.hasOwnProperty("thinking")) + object.thinking = $root.MessageThinking.toObject(message.thinking, options); + if (message.unknown27 != null && message.hasOwnProperty("unknown27")) + object.unknown27 = message.unknown27; + return object; + }; + + /** + * Converts this Message to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @instance + * @returns {Object.} JSON object + */ + Message.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Message + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Message.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsResponse.Message"; + }; + + Message.WebTool = (function() { + + /** + * Properties of a WebTool. + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @interface IWebTool + * @property {Array.|null} [webPage] WebTool webPage + */ + + /** + * Constructs a new WebTool. + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @classdesc Represents a WebTool. + * @implements IWebTool + * @constructor + * @param {StreamUnifiedChatWithToolsResponse.Message.IWebTool=} [properties] Properties to set + */ + function WebTool(properties) { + this.webPage = []; + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * WebTool webPage. + * @member {Array.} webPage + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @instance + */ + WebTool.prototype.webPage = $util.emptyArray; + + /** + * Creates a new WebTool instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.IWebTool=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool} WebTool instance + */ + WebTool.create = function create(properties) { + return new WebTool(properties); + }; + + /** + * Encodes the specified WebTool message. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.WebTool.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.IWebTool} message WebTool message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + WebTool.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.webPage != null && message.webPage.length) + for (var i = 0; i < message.webPage.length; ++i) + $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.encode(message.webPage[i], writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified WebTool message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.WebTool.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.IWebTool} message WebTool message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + WebTool.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a WebTool message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool} WebTool + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + WebTool.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsResponse.Message.WebTool(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + if (!(message.webPage && message.webPage.length)) + message.webPage = []; + message.webPage.push($root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.decode(reader, reader.uint32())); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a WebTool message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool} WebTool + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + WebTool.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a WebTool message. + * @function verify + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + WebTool.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.webPage != null && message.hasOwnProperty("webPage")) { + if (!Array.isArray(message.webPage)) + return "webPage: array expected"; + for (var i = 0; i < message.webPage.length; ++i) { + var error = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.verify(message.webPage[i]); + if (error) + return "webPage." + error; + } + } + return null; + }; + + /** + * Creates a WebTool message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool} WebTool + */ + WebTool.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsResponse.Message.WebTool) + return object; + var message = new $root.StreamUnifiedChatWithToolsResponse.Message.WebTool(); + if (object.webPage) { + if (!Array.isArray(object.webPage)) + throw TypeError(".StreamUnifiedChatWithToolsResponse.Message.WebTool.webPage: array expected"); + message.webPage = []; + for (var i = 0; i < object.webPage.length; ++i) { + if (typeof object.webPage[i] !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.Message.WebTool.webPage: object expected"); + message.webPage[i] = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.fromObject(object.webPage[i]); + } + } + return message; + }; + + /** + * Creates a plain object from a WebTool message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.WebTool} message WebTool + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + WebTool.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.arrays || options.defaults) + object.webPage = []; + if (message.webPage && message.webPage.length) { + object.webPage = []; + for (var j = 0; j < message.webPage.length; ++j) + object.webPage[j] = $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.toObject(message.webPage[j], options); + } + return object; + }; + + /** + * Converts this WebTool to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @instance + * @returns {Object.} JSON object + */ + WebTool.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for WebTool + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + WebTool.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsResponse.Message.WebTool"; + }; + + WebTool.WebPage = (function() { + + /** + * Properties of a WebPage. + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @interface IWebPage + * @property {string|null} [url] WebPage url + * @property {string|null} [title] WebPage title + * @property {string|null} [content] WebPage content + */ + + /** + * Constructs a new WebPage. + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool + * @classdesc Represents a WebPage. + * @implements IWebPage + * @constructor + * @param {StreamUnifiedChatWithToolsResponse.Message.WebTool.IWebPage=} [properties] Properties to set + */ + function WebPage(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * WebPage url. + * @member {string} url + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @instance + */ + WebPage.prototype.url = ""; + + /** + * WebPage title. + * @member {string} title + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @instance + */ + WebPage.prototype.title = ""; + + /** + * WebPage content. + * @member {string} content + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @instance + */ + WebPage.prototype.content = ""; + + /** + * Creates a new WebPage instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.WebTool.IWebPage=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage} WebPage instance + */ + WebPage.create = function create(properties) { + return new WebPage(properties); + }; + + /** + * Encodes the specified WebPage message. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.WebTool.IWebPage} message WebPage message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + WebPage.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.url != null && Object.hasOwnProperty.call(message, "url")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.url); + if (message.title != null && Object.hasOwnProperty.call(message, "title")) + writer.uint32(/* id 2, wireType 2 =*/18).string(message.title); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + writer.uint32(/* id 3, wireType 2 =*/26).string(message.content); + return writer; + }; + + /** + * Encodes the specified WebPage message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.WebTool.IWebPage} message WebPage message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + WebPage.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a WebPage message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage} WebPage + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + WebPage.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.url = reader.string(); + break; + } + case 2: { + message.title = reader.string(); + break; + } + case 3: { + message.content = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a WebPage message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage} WebPage + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + WebPage.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a WebPage message. + * @function verify + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + WebPage.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.url != null && message.hasOwnProperty("url")) + if (!$util.isString(message.url)) + return "url: string expected"; + if (message.title != null && message.hasOwnProperty("title")) + if (!$util.isString(message.title)) + return "title: string expected"; + if (message.content != null && message.hasOwnProperty("content")) + if (!$util.isString(message.content)) + return "content: string expected"; + return null; + }; + + /** + * Creates a WebPage message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage} WebPage + */ + WebPage.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage) + return object; + var message = new $root.StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage(); + if (object.url != null) + message.url = String(object.url); + if (object.title != null) + message.title = String(object.title); + if (object.content != null) + message.content = String(object.content); + return message; + }; + + /** + * Creates a plain object from a WebPage message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage} message WebPage + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + WebPage.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) { + object.url = ""; + object.title = ""; + object.content = ""; + } + if (message.url != null && message.hasOwnProperty("url")) + object.url = message.url; + if (message.title != null && message.hasOwnProperty("title")) + object.title = message.title; + if (message.content != null && message.hasOwnProperty("content")) + object.content = message.content; + return object; + }; + + /** + * Converts this WebPage to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @instance + * @returns {Object.} JSON object + */ + WebPage.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for WebPage + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + WebPage.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsResponse.Message.WebTool.WebPage"; + }; + + return WebPage; + })(); + + return WebTool; + })(); + + Message.Unknown12 = (function() { + + /** + * Properties of an Unknown12. + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @interface IUnknown12 + * @property {StreamUnifiedChatWithToolsResponse.Message.Unknown12.IContent|null} [content] Unknown12 content + */ + + /** + * Constructs a new Unknown12. + * @memberof StreamUnifiedChatWithToolsResponse.Message + * @classdesc Represents an Unknown12. + * @implements IUnknown12 + * @constructor + * @param {StreamUnifiedChatWithToolsResponse.Message.IUnknown12=} [properties] Properties to set + */ + function Unknown12(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Unknown12 content. + * @member {StreamUnifiedChatWithToolsResponse.Message.Unknown12.IContent|null|undefined} content + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @instance + */ + Unknown12.prototype.content = null; + + /** + * Creates a new Unknown12 instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.IUnknown12=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12} Unknown12 instance + */ + Unknown12.create = function create(properties) { + return new Unknown12(properties); + }; + + /** + * Encodes the specified Unknown12 message. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.Unknown12.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.IUnknown12} message Unknown12 message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Unknown12.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.encode(message.content, writer.uint32(/* id 1, wireType 2 =*/10).fork()).ldelim(); + return writer; + }; + + /** + * Encodes the specified Unknown12 message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.Unknown12.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.IUnknown12} message Unknown12 message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Unknown12.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes an Unknown12 message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12} Unknown12 + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Unknown12.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.content = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.decode(reader, reader.uint32()); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes an Unknown12 message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12} Unknown12 + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Unknown12.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies an Unknown12 message. + * @function verify + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Unknown12.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.content != null && message.hasOwnProperty("content")) { + var error = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.verify(message.content); + if (error) + return "content." + error; + } + return null; + }; + + /** + * Creates an Unknown12 message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12} Unknown12 + */ + Unknown12.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12) + return object; + var message = new $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12(); + if (object.content != null) { + if (typeof object.content !== "object") + throw TypeError(".StreamUnifiedChatWithToolsResponse.Message.Unknown12.content: object expected"); + message.content = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.fromObject(object.content); + } + return message; + }; + + /** + * Creates a plain object from an Unknown12 message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.Unknown12} message Unknown12 + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Unknown12.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.content = null; + if (message.content != null && message.hasOwnProperty("content")) + object.content = $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.toObject(message.content, options); + return object; + }; + + /** + * Converts this Unknown12 to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @instance + * @returns {Object.} JSON object + */ + Unknown12.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Unknown12 + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Unknown12.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsResponse.Message.Unknown12"; + }; + + Unknown12.Content = (function() { + + /** + * Properties of a Content. + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @interface IContent + * @property {string|null} [content] Content content + */ + + /** + * Constructs a new Content. + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12 + * @classdesc Represents a Content. + * @implements IContent + * @constructor + * @param {StreamUnifiedChatWithToolsResponse.Message.Unknown12.IContent=} [properties] Properties to set + */ + function Content(properties) { + if (properties) + for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) + if (properties[keys[i]] != null) + this[keys[i]] = properties[keys[i]]; + } + + /** + * Content content. + * @member {string} content + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @instance + */ + Content.prototype.content = ""; + + /** + * Creates a new Content instance using the specified properties. + * @function create + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.Unknown12.IContent=} [properties] Properties to set + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content} Content instance + */ + Content.create = function create(properties) { + return new Content(properties); + }; + + /** + * Encodes the specified Content message. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.verify|verify} messages. + * @function encode + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.Unknown12.IContent} message Content message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Content.encode = function encode(message, writer) { + if (!writer) + writer = $Writer.create(); + if (message.content != null && Object.hasOwnProperty.call(message, "content")) + writer.uint32(/* id 1, wireType 2 =*/10).string(message.content); + return writer; + }; + + /** + * Encodes the specified Content message, length delimited. Does not implicitly {@link StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content.verify|verify} messages. + * @function encodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.Unknown12.IContent} message Content message or plain object to encode + * @param {$protobuf.Writer} [writer] Writer to encode to + * @returns {$protobuf.Writer} Writer + */ + Content.encodeDelimited = function encodeDelimited(message, writer) { + return this.encode(message, writer).ldelim(); + }; + + /** + * Decodes a Content message from the specified reader or buffer. + * @function decode + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @param {number} [length] Message length if known beforehand + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content} Content + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Content.decode = function decode(reader, length) { + if (!(reader instanceof $Reader)) + reader = $Reader.create(reader); + var end = length === undefined ? reader.len : reader.pos + length, message = new $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content(); + while (reader.pos < end) { + var tag = reader.uint32(); + switch (tag >>> 3) { + case 1: { + message.content = reader.string(); + break; + } + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }; + + /** + * Decodes a Content message from the specified reader or buffer, length delimited. + * @function decodeDelimited + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {$protobuf.Reader|Uint8Array} reader Reader or buffer to decode from + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content} Content + * @throws {Error} If the payload is not a reader or valid buffer + * @throws {$protobuf.util.ProtocolError} If required fields are missing + */ + Content.decodeDelimited = function decodeDelimited(reader) { + if (!(reader instanceof $Reader)) + reader = new $Reader(reader); + return this.decode(reader, reader.uint32()); + }; + + /** + * Verifies a Content message. + * @function verify + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {Object.} message Plain object to verify + * @returns {string|null} `null` if valid, otherwise the reason why it is not + */ + Content.verify = function verify(message) { + if (typeof message !== "object" || message === null) + return "object expected"; + if (message.content != null && message.hasOwnProperty("content")) + if (!$util.isString(message.content)) + return "content: string expected"; + return null; + }; + + /** + * Creates a Content message from a plain object. Also converts values to their respective internal types. + * @function fromObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {Object.} object Plain object + * @returns {StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content} Content + */ + Content.fromObject = function fromObject(object) { + if (object instanceof $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content) + return object; + var message = new $root.StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content(); + if (object.content != null) + message.content = String(object.content); + return message; + }; + + /** + * Creates a plain object from a Content message. Also converts values to other types if specified. + * @function toObject + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content} message Content + * @param {$protobuf.IConversionOptions} [options] Conversion options + * @returns {Object.} Plain object + */ + Content.toObject = function toObject(message, options) { + if (!options) + options = {}; + var object = {}; + if (options.defaults) + object.content = ""; + if (message.content != null && message.hasOwnProperty("content")) + object.content = message.content; + return object; + }; + + /** + * Converts this Content to JSON. + * @function toJSON + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @instance + * @returns {Object.} JSON object + */ + Content.prototype.toJSON = function toJSON() { + return this.constructor.toObject(this, $protobuf.util.toJSONOptions); + }; + + /** + * Gets the default type url for Content + * @function getTypeUrl + * @memberof StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content + * @static + * @param {string} [typeUrlPrefix] your custom typeUrlPrefix(default "type.googleapis.com") + * @returns {string} The default type url + */ + Content.getTypeUrl = function getTypeUrl(typeUrlPrefix) { + if (typeUrlPrefix === undefined) { + typeUrlPrefix = "type.googleapis.com"; + } + return typeUrlPrefix + "/StreamUnifiedChatWithToolsResponse.Message.Unknown12.Content"; + }; + + return Content; + })(); + + return Unknown12; + })(); + + return Message; + })(); + + return StreamUnifiedChatWithToolsResponse; +})(); + +module.exports = $root; diff --git a/src/proto/message.proto b/src/proto/message.proto new file mode 100644 index 0000000000000000000000000000000000000000..c5da418e65929b285b90739965ecf928c4b9e2cb --- /dev/null +++ b/src/proto/message.proto @@ -0,0 +1,133 @@ +syntax = "proto3"; + +message AvailableModelsResponse { + message AvailableModel { + string name = 1; + bool defaultOn = 2; + optional bool isLongContextOnly = 3; + optional bool isChatOnly = 4; + } + repeated AvailableModel models = 2; + repeated string modelNames = 1; +} + +message MessageSummary { + string content = 1; + string summaryId1 = 2; + string summaryId2 = 3; // uuid, equal to summaryId1 + string previousSummaryId = 4; +} + +message MessageThinking { + string content = 1; +} +message StreamUnifiedChatWithToolsRequest { + message Request { + message Message { + message Image { + message Metadata { + int32 width = 1; + int32 height = 2; + } + bytes data = 1; + Metadata metadata = 2; + } + string content = 1; + int32 role = 2; + Image image = 10; + string messageId = 13; + string unknown29 = 29; // 1, only for user message + string summaryId = 32; + MessageSummary summary = 39; + MessageThinking thinking = 45; + int32 chatModeEnum = 47; // 1 for ask, 2 for agent, 3 for edit + } + message Instruction { + string instruction = 1; + } + message Model { + string name = 1; + bytes empty = 4; + } + message CursorSetting { + message Unknown6 { + bytes unknown1 = 1; + bytes unknown2 = 2; + } + string name = 1; + bytes unknown3 = 3; + Unknown6 unknown6 = 6; + int32 unknown8 = 8; + int32 unknown9 = 9; + } + message Metadata { + string os = 1; // win32 + string arch = 2; // x64 + string version = 3; // 10.0.22631 + string path = 4; // C:\Program Files\PowerShell\7\pwsh.exe + string timestamp = 5; // 2025-03-03T13:10:08.590Z + } + message MessageId { + string messageId = 1; + string summaryId = 2; + int32 role = 3; + } + + repeated Message messages = 1; + int32 unknown2 = 2; // 1 + Instruction instruction = 3; + int32 unknown4 = 4; // 1 + Model model = 5; + repeated string wikiTool = 7; // one url one item + string webTool = 8; // "full search" + int32 unknown13 = 13; + CursorSetting cursorSetting = 15; + int32 unknown19 = 19; // 1 + int32 unknown22 = 22; // 1 + string conversationId = 23; // uuid + Metadata metadata = 26; + int32 unknown27 = 27; // 1 + string unknown29 = 29; + repeated MessageId messageIds = 30; + int32 largeContext = 35; // 1 + int32 unknown38 = 38; // 0 + int32 chatModeEnum = 46; // 1 for ask, 2 for agent, 3 for edit + string unknown47 = 47; + int32 unknown48 = 48; // 0 + int32 unknown49 = 49; // 0 + int32 unknown51 = 51; // 0 + int32 unknown53 = 53; // 0 + string chatMode = 54; + } + + Request request = 1; +} + +message StreamUnifiedChatWithToolsResponse { + message Message { + message WebTool { + message WebPage { + string url = 1; + string title = 2; + string content = 3; + } + repeated WebPage webPage = 1; + } + message Unknown12 { + message Content { + string content = 1; + } + Content content = 1; + } + string content = 1; + WebTool webtool = 11; + Unknown12 unknown12 = 12; + string unknown22 = 22; // uuid + string unknown23 = 23; + string unknown27 = 27; // uuid + MessageThinking thinking = 25; + } + + Message message = 2; + MessageSummary summary = 3; +} diff --git a/src/proxy/cursor_proxy_server_linux_amd64 b/src/proxy/cursor_proxy_server_linux_amd64 new file mode 100644 index 0000000000000000000000000000000000000000..e39ebbdacd50019bc9d3ef2f9aa6a7cf8e17ff42 --- /dev/null +++ b/src/proxy/cursor_proxy_server_linux_amd64 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:985fa643f2a5b793102a4e97b058f14fc1e58cfd280c0bcc70e8a8455e932a4e +size 12790492 diff --git a/src/proxy/others/cursor_proxy_server_linux_amd64 b/src/proxy/others/cursor_proxy_server_linux_amd64 new file mode 100644 index 0000000000000000000000000000000000000000..8baf7cb9ea49303215509a2c4552b74a2cb1ca46 --- /dev/null +++ b/src/proxy/others/cursor_proxy_server_linux_amd64 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6fe0a95e158d753f3f179b88690d5e76cdb4a5b6408ca024bc544d2a84c6a919 +size 12780345 diff --git a/src/public/index.html b/src/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..65d5472e22ac5171e42d600f07873512b74fa27b --- /dev/null +++ b/src/public/index.html @@ -0,0 +1,326 @@ + + + + + + Cursor To OpenAI - API Key 管理 + + + + + + + + +
+
+
+ +

Cursor To OpenAI

+
+

管理自定义 API Key 与 Cursor Cookie 映射关系的高效工具

+
+
+ + +
+
+ + + + +
+
+ +
+ +
+
+ +

添加/更新 API Key

+
+
+
+
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+ +
+ +
+
+ +
+
+ +

现有 API Key

+
+
+
+ + + + + + + + + + + +
API KEYCOOKIE 数量操作
+
+
+ +
+
+ +

使用说明

+
+
    +
  1. 添加自定义 API Key 和对应的 Cursor Cookie 值。
  2. +
  3. API Key 可以不添加 Cookie,但不会有任何功能,可以后续再添加 Cookie。
  4. +
  5. 使用自定义 API Key 作为 OpenAI API 的认证凭证。
  6. +
  7. 系统将自动在多个 Cookie 之间进行轮询。 +
      +
    • /v1/models - 模型列表
    • +
    • /v1/chat/completions - 聊天补全
    • +
    +
  8. +
+
+ +
+
+ +

无效Cookie管理

+
+
+
+ + 以下是系统自动检测到的无效Cookie列表。这些Cookie在请求过程中被发现无效,已被自动从API Key中移除。 +
+
+
+ + 正在加载... +
+
+
+
+ + +
+
不建议使用
+ +
+ +

Cookie自动刷新 (不建议使用)

+
+ +
+
+ + 警告:此功能已标记为不建议使用,可能会导致账号风险。除非必要,请避免使用此功能。 +
+
+ + 系统支持自动刷新Cookie,确保API Key始终有足够的可用Cookie。您可以在此手动触发刷新操作。 +
+
+
+
+ + +
+ +
+ +
+
+ + +
+
+ +

获取Cursor Cookie

+
+
+
+ + 通过此功能可以快速获取新的Cursor Cookie并添加到系统中。点击按钮生成链接,完成登录后自动添加Cookie。系统将等待最多5分钟获取Cookie。 +
+
+
+
+ + +
+ +
+ +
+
+
+ + + + + + + + +
+
+
+
+
+ +
+
+ + + + \ No newline at end of file diff --git a/src/public/login.html b/src/public/login.html new file mode 100644 index 0000000000000000000000000000000000000000..39516c3b5577d8aedc3abea641d04198fc0f84ad --- /dev/null +++ b/src/public/login.html @@ -0,0 +1,282 @@ + + + + + + Cursor To OpenAI - 管理员登录 + + + + + + + + +
+
+
+ +

Cursor To OpenAI

+
+

管理员登录系统 - 安全访问后台管理界面

+
+ + +
+
+ +

管理员登录

+
+
+
+
+ + +
+
+ + +
+ +
+
+ 还没有账号?点击注册 +
+
+ + + + +
+
+ +

系统信息

+
+
+ + Cursor To OpenAI 是一个管理自定义 API Key 与 Cursor Cookie 映射关系的高效工具。登录后可以进行API Key的管理和配置。 +
+
+
+ + + + \ No newline at end of file diff --git a/src/public/logs.html b/src/public/logs.html new file mode 100644 index 0000000000000000000000000000000000000000..fa38d29744dc5004b613c1dd10f2c494879d7adc --- /dev/null +++ b/src/public/logs.html @@ -0,0 +1,714 @@ + + + + + + Cursor To OpenAI - 日志查看 + + + + +
+
+

Cursor To OpenAI - 日志查看

+

在此页面上,您可以查看和筛选系统日志。

+
+
+ + +
+
+ + +
+
+
+ +
+

日志筛选

+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ +
+
+ +
+ + 已开启 +
+
+
+
+ +
+

日志列表

+
+
+ + + + + + + + + + + +
时间级别内容
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/src/public/scripts.js b/src/public/scripts.js new file mode 100644 index 0000000000000000000000000000000000000000..cd2d4e17cfd22f5839c47ae1ac59303f0e1a7c38 --- /dev/null +++ b/src/public/scripts.js @@ -0,0 +1,1342 @@ +// 模态框相关功能 +document.addEventListener('DOMContentLoaded', function() { + // 获取所有模态框和关闭按钮 + const modals = document.querySelectorAll('.modal'); + const closeBtns = document.querySelectorAll('.close'); + + // 关闭所有模态框的函数 + function closeAllModals() { + modals.forEach(modal => { + modal.style.display = 'none'; + }); + document.body.classList.remove('modal-open'); + } + + // 为每个关闭按钮添加事件 + closeBtns.forEach(btn => { + btn.onclick = closeAllModals; + }); + + // 点击模态框外部关闭 + window.onclick = function(event) { + modals.forEach(modal => { + if (event.target == modal) { + closeAllModals(); + } + }); + } + + // 页面加载时获取 API Key 列表和无效Cookie列表 + checkAuth(); + loadApiKeys(); + renderInvalidCookies(); + populateRefreshApiKeySelect(); + populateCookieApiKeySelect(); + + // 初始化添加Cookie的标签容器 + renderAddCookieTags([]); + + // 绑定事件监听器 + bindEventListeners(); + + // 处理日志按钮点击事件 + document.getElementById('logsBtn')?.addEventListener('click', function() { + window.location.href = '/logs.html'; + }); +}); + +// 绑定各种事件监听器 +function bindEventListeners() { + // 表单提交 + document.getElementById('addKeyForm').addEventListener('submit', handleAddKeyForm); + document.getElementById('editCookieForm').addEventListener('submit', handleEditCookieForm); + document.getElementById('invalidCookieForm').addEventListener('submit', handleInvalidCookieForm); + + // 按钮点击 + // 注意:testApiBtn可能在页面上出现两次,需要检查元素是否存在 + const testApiButtons = document.querySelectorAll('#testApiBtn'); + testApiButtons.forEach(btn => { + if(btn) btn.addEventListener('click', testApiConnection); + }); + + const clearCacheButtons = document.querySelectorAll('#clearCacheBtn'); + clearCacheButtons.forEach(btn => { + if(btn) btn.addEventListener('click', clearCacheAndRefresh); + }); + + // 其他按钮 + if(document.getElementById('addNewCookieBtn')) document.getElementById('addNewCookieBtn').addEventListener('click', handleAddNewCookie); + if(document.getElementById('addCookieBtn')) document.getElementById('addCookieBtn').addEventListener('click', handleAddCookie); + if(document.getElementById('addInvalidCookieBtn')) document.getElementById('addInvalidCookieBtn').addEventListener('click', handleAddInvalidCookie); + if(document.getElementById('closeInvalidCookieModal')) document.getElementById('closeInvalidCookieModal').addEventListener('click', closeInvalidCookieModal); + + // 修复刷新Cookie和生成链接按钮的事件绑定 + const refreshCookieBtn = document.getElementById('refreshCookieBtn'); + if(refreshCookieBtn) { + console.log('为refreshCookieBtn绑定事件'); + refreshCookieBtn.addEventListener('click', handleRefreshCookie); + } + + const generateLinkBtn = document.getElementById('generateLinkBtn'); + if(generateLinkBtn) { + console.log('为generateLinkBtn绑定事件'); + generateLinkBtn.addEventListener('click', handleGenerateLink); + } + + if(document.getElementById('logoutBtn')) document.getElementById('logoutBtn').addEventListener('click', handleLogout); +} + +// API Key 管理相关函数 +// 加载现有 API Key +async function loadApiKeys() { + try { + console.log('开始加载API Keys...'); + const response = await fetch('/v1/api-keys', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + console.log('API响应状态:', response.status); + const data = await response.json(); + console.log('获取到的数据:', data); + + const keyList = document.getElementById('keyList'); + keyList.innerHTML = ''; + + if (data.success && data.apiKeys.length > 0) { + data.apiKeys.forEach(key => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${key.key} + ${key.cookieCount} + + + + + `; + keyList.appendChild(row); + }); + } else { + keyList.innerHTML = '暂无 API Key'; + } + } catch (error) { + console.error('加载 API Key 失败:', error); + document.getElementById('keyListMessage').innerHTML = ` +
加载 API Key 失败: ${error.message}
+ `; + } +} + +// 处理添加/更新 API Key 表单提交 +async function handleAddKeyForm(e) { + e.preventDefault(); + + const apiKey = document.getElementById('apiKey').value.trim(); + const cookieValuesText = document.getElementById('cookieValues').value.trim(); + + if (!apiKey) { + document.getElementById('addKeyMessage').innerHTML = ` +
API Key 不能为空
+ `; + return; + } + + // 将逗号分隔的 Cookie 值转换为数组 + const cookieValues = cookieValuesText ? + cookieValuesText.split(',').map(cookie => cookie.trim()).filter(cookie => cookie) : + []; + + try { + const response = await fetch('/v1/api-keys', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + apiKey, + cookieValues, + }), + }); + + const data = await response.json(); + + if (data.success) { + document.getElementById('addKeyMessage').innerHTML = ` +
API Key 添加/更新成功
+ `; + // 等待3秒后再刷新页面 + setTimeout(() => { + window.location.reload(); + }, 3000); + } else { + document.getElementById('addKeyMessage').innerHTML = ` +
API Key 添加/更新失败: ${data.error}
+ `; + } + } catch (error) { + console.error('添加/更新 API Key 失败:', error); + document.getElementById('addKeyMessage').innerHTML = ` +
添加/更新 API Key 失败: ${error.message}
+ `; + } +} + +// 删除 API Key +async function deleteApiKey(apiKey) { + if (!confirm(`确定要删除 API Key "${apiKey}" 吗?`)) { + return; + } + + try { + const response = await fetch(`/v1/api-keys/${encodeURIComponent(apiKey)}`, { + method: 'DELETE', + }); + + const data = await response.json(); + + if (data.success) { + document.getElementById('keyListMessage').innerHTML = ` +
API Key 删除成功
+ `; + loadApiKeys(); + } else { + document.getElementById('keyListMessage').innerHTML = ` +
API Key 删除失败: ${data.error}
+ `; + } + } catch (error) { + console.error('删除 API Key 失败:', error); + document.getElementById('keyListMessage').innerHTML = ` +
删除 API Key 失败: ${error.message}
+ `; + } +} + +// 获取API Key的Cookie值 +async function getCookiesForApiKey(apiKey) { + try { + const response = await fetch(`/v1/api-keys/${encodeURIComponent(apiKey)}/cookies`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.cookies; + } catch (error) { + console.error(`获取 ${apiKey} 的Cookie值失败:`, error); + throw error; + } +} + +// 修改 API Key +async function editApiKey(apiKey) { + try { + document.getElementById('editModalMessage').innerHTML = ''; + document.getElementById('editApiKey').value = apiKey; + + // 获取当前Cookie值 + const cookies = await getCookiesForApiKey(apiKey); + + // 更新隐藏的textarea + document.getElementById('editCookieValues').value = cookies.join(','); + + // 更新Cookie标签容器 + renderCookieTags(cookies); + + // 清空新Cookie输入框 + document.getElementById('newCookie').value = ''; + + // 显示模态框 + const modal = document.getElementById('editModal'); + modal.style.display = 'block'; + document.body.classList.add('modal-open'); + + } catch (error) { + console.error('打开修改模态框失败:', error); + document.getElementById('editModalMessage').innerHTML = ` +
无法加载Cookie数据: ${error.message}
+ `; + const modal = document.getElementById('editModal'); + modal.style.display = 'block'; // 即使出错也显示模态框,以便显示错误信息 + document.body.classList.add('modal-open'); + } +} + +// 获取API Keys的辅助函数 +async function getApiKeys() { + const response = await fetch('/v1/api-keys', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.success ? data.apiKeys : []; +} + +// 复制文本到剪贴板的通用函数 +async function copyTextToClipboard(text) { + try { + await navigator.clipboard.writeText(text); + return true; + } catch (err) { + console.error('复制到剪贴板失败:', err); + + // 如果navigator.clipboard不可用,使用备用方法 + try { + const textArea = document.createElement('textarea'); + textArea.value = text; + + // 将元素设置为看不见 + textArea.style.position = 'fixed'; + textArea.style.top = '0'; + textArea.style.left = '0'; + textArea.style.width = '2em'; + textArea.style.height = '2em'; + textArea.style.padding = '0'; + textArea.style.border = 'none'; + textArea.style.outline = 'none'; + textArea.style.boxShadow = 'none'; + textArea.style.background = 'transparent'; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + const successful = document.execCommand('copy'); + document.body.removeChild(textArea); + return successful; + } catch (fallbackErr) { + console.error('备用复制方法失败:', fallbackErr); + return false; + } + } +} + +// 显示复制成功弹窗提示 +function showCopyToast(success) { + const toast = document.createElement('div'); + toast.style.position = 'fixed'; + toast.style.bottom = '20px'; + toast.style.left = '50%'; + toast.style.transform = 'translateX(-50%)'; + toast.style.padding = '8px 16px'; + toast.style.borderRadius = '4px'; + toast.style.zIndex = '9999'; + toast.style.fontSize = '14px'; + + if (success) { + toast.style.backgroundColor = '#28a745'; + toast.style.color = 'white'; + toast.textContent = '复制成功!'; + } else { + toast.style.backgroundColor = '#dc3545'; + toast.style.color = 'white'; + toast.textContent = '复制失败,请手动复制'; + } + + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.opacity = '0'; + toast.style.transition = 'opacity 0.5s'; + setTimeout(() => { + document.body.removeChild(toast); + }, 500); + }, 2000); +} + +// 处理复制Cookie按钮点击 +async function handleCopyCookie(cookie) { + const success = await copyTextToClipboard(cookie); + showCopyToast(success); +} + +// Cookie管理相关函数 +// 渲染Cookie标签 +function renderCookieTags(cookies) { + const container = document.getElementById('cookieTagsContainer'); + container.innerHTML = ''; + + if (cookies.length === 0) { + container.innerHTML = '
暂无Cookie,请添加
'; + return; + } + + cookies.forEach((cookie, index) => { + // 创建标签 + const tag = document.createElement('span'); + tag.className = 'cookie-tag'; + + // 对短文本添加特殊类 + if (cookie.length < 5) { + tag.classList.add('short-cookie'); + } + + // 截断Cookie显示 + const displayText = cookie.length > 20 ? + cookie.substring(0, 8) + '...' + cookie.substring(cookie.length - 8) : + cookie; + + tag.title = cookie; // 完整Cookie作为工具提示 + + // 增加对移动端友好的结构,添加复制按钮 + tag.innerHTML = ` + ${displayText} + + `; + container.appendChild(tag); + }); + + // 添加删除按钮的事件监听 + document.querySelectorAll('.delete-cookie').forEach(btn => { + btn.addEventListener('click', function() { + const index = parseInt(this.getAttribute('data-index')); + deleteCookieTag(index); + }); + }); + + // 添加复制按钮的事件监听 + document.querySelectorAll('.copy-btn').forEach(btn => { + btn.addEventListener('click', function() { + const cookie = this.getAttribute('data-cookie'); + handleCopyCookie(cookie); + }); + }); +} + +// 删除Cookie标签 +function deleteCookieTag(index) { + // 从隐藏的textarea中获取当前的cookies + const cookieValuesElem = document.getElementById('editCookieValues'); + let cookies = cookieValuesElem.value.split(',').map(c => c.trim()).filter(c => c); + + // 删除指定索引的cookie + cookies.splice(index, 1); + + // 更新隐藏的textarea + cookieValuesElem.value = cookies.join(','); + + // 重新渲染标签 + renderCookieTags(cookies); +} + +// 处理添加新Cookie +function handleAddCookie() { + const newCookieInput = document.getElementById('newCookie'); + const newCookie = newCookieInput.value.trim(); + + if (!newCookie) { + return; + } + + // 获取当前的cookies + const cookieValuesElem = document.getElementById('editCookieValues'); + let cookies = cookieValuesElem.value ? + cookieValuesElem.value.split(',').map(c => c.trim()).filter(c => c) : + []; + + // 添加新cookie + cookies.push(newCookie); + + // 更新隐藏的textarea + cookieValuesElem.value = cookies.join(','); + + // 重新渲染标签 + renderCookieTags(cookies); + + // 清空输入框 + newCookieInput.value = ''; +} + +// 处理编辑表单提交 +async function handleEditCookieForm(e) { + e.preventDefault(); + + const apiKey = document.getElementById('editApiKey').value.trim(); + const cookieValuesText = document.getElementById('editCookieValues').value.trim(); + + if (!apiKey) { + document.getElementById('editModalMessage').innerHTML = ` +
API Key不能为空
+ `; + return; + } + + // 将逗号分隔的 Cookie 值转换为数组 + const cookieValues = cookieValuesText ? + cookieValuesText.split(',').map(cookie => cookie.trim()).filter(cookie => cookie) : + []; + + try { + const response = await fetch('/v1/api-keys', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + apiKey, + cookieValues, + }), + }); + + const data = await response.json(); + + if (data.success) { + document.getElementById('editModalMessage').innerHTML = ` +
Cookie 修改成功
+ `; + setTimeout(() => { + document.getElementById('editModal').style.display = 'none'; + loadApiKeys(); + }, 1500); + } else { + document.getElementById('editModalMessage').innerHTML = ` +
Cookie 修改失败: ${data.error}
+ `; + } + } catch (error) { + console.error('修改 Cookie 失败:', error); + document.getElementById('editModalMessage').innerHTML = ` +
修改 Cookie 失败: ${error.message}
+ `; + } +} + +// 渲染添加API Key表单中的Cookie标签 +function renderAddCookieTags(cookies) { + const container = document.getElementById('addCookieTagsContainer'); + container.innerHTML = ''; + + if (cookies.length === 0) { + container.innerHTML = '
暂无Cookie,请添加
'; + return; + } + + cookies.forEach((cookie, index) => { + const tag = document.createElement('span'); + tag.className = 'cookie-tag'; + + // 对短文本添加特殊类 + if (cookie.length < 5) { + tag.classList.add('short-cookie'); + } + + const displayText = cookie.length > 20 ? + cookie.substring(0, 8) + '...' + cookie.substring(cookie.length - 8) : + cookie; + + tag.title = cookie; + + // 增加对移动端友好的结构,添加复制按钮 + tag.innerHTML = ` + ${displayText} + + `; + container.appendChild(tag); + }); + + document.querySelectorAll('.delete-add-cookie').forEach(btn => { + btn.addEventListener('click', function() { + const index = parseInt(this.getAttribute('data-index')); + deleteAddCookieTag(index); + }); + }); + + // 添加复制按钮的事件监听 + document.querySelectorAll('.copy-btn').forEach(btn => { + btn.addEventListener('click', function() { + const cookie = this.getAttribute('data-cookie'); + handleCopyCookie(cookie); + }); + }); +} + +// 删除添加表单中的Cookie标签 +function deleteAddCookieTag(index) { + const cookieValuesElem = document.getElementById('cookieValues'); + let cookies = cookieValuesElem.value ? + cookieValuesElem.value.split(',').map(c => c.trim()).filter(c => c) : + []; + + cookies.splice(index, 1); + cookieValuesElem.value = cookies.join(','); + renderAddCookieTags(cookies); +} + +// 处理添加新Cookie标签到添加表单 +function handleAddNewCookie() { + const newCookieInput = document.getElementById('addNewCookie'); + const newCookie = newCookieInput.value.trim(); + + if (!newCookie) { + return; + } + + const cookieValuesElem = document.getElementById('cookieValues'); + let cookies = cookieValuesElem.value ? + cookieValuesElem.value.split(',').map(c => c.trim()).filter(c => c) : + []; + + cookies.push(newCookie); + cookieValuesElem.value = cookies.join(','); + renderAddCookieTags(cookies); + newCookieInput.value = ''; +} + +// 无效Cookie管理相关函数 +// 获取无效Cookie列表 +async function getInvalidCookies() { + try { + const response = await fetch('/v1/invalid-cookies', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.invalidCookies; + } catch (error) { + console.error('获取无效Cookie失败:', error); + throw error; + } +} + +// 清除特定无效Cookie +async function clearInvalidCookie(cookie) { + try { + const response = await fetch(`/v1/invalid-cookies/${encodeURIComponent(cookie)}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.success; + } catch (error) { + console.error(`清除无效Cookie失败:`, error); + throw error; + } +} + +// 清除所有无效Cookie +async function clearAllInvalidCookies() { + try { + const response = await fetch('/v1/invalid-cookies', { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + return data.success; + } catch (error) { + console.error('清除所有无效Cookie失败:', error); + throw error; + } +} + +// 渲染无效Cookie列表 +async function renderInvalidCookies() { + const container = document.getElementById('invalidCookiesContainer'); + + try { + const invalidCookies = await getInvalidCookies(); + + if (invalidCookies.length === 0) { + container.innerHTML = '
没有检测到无效Cookie
'; + return; + } + + let html = '
'; + + // 展示为一行,类似于API Key列表 + html += ` + + + + + + `; + + html += '
无效Cookie数量操作
无效Cookie${invalidCookies.length} + + +
'; + container.innerHTML = html; + + // 添加按钮事件监听 + document.getElementById('editInvalidCookiesBtn').addEventListener('click', openInvalidCookieModal); + document.getElementById('clearAllInvalidCookiesInTable').addEventListener('click', handleClearAllInvalidCookies); + + } catch (error) { + container.innerHTML = `
加载失败: ${error.message}
`; + } +} + +// 处理清除所有无效Cookie按钮事件 +async function handleClearAllInvalidCookies() { + try { + await clearAllInvalidCookies(); + showMessage('invalidCookiesContainer', '所有无效Cookie已清除', 'info'); + renderInvalidCookies(); // 重新渲染列表 + } catch (error) { + showMessage('invalidCookiesContainer', `清除失败: ${error.message}`, 'error'); + } +} + +// API 测试相关函数 +// 测试API连接 +async function testApiConnection() { + const resultDiv = document.getElementById('testApiResult'); + resultDiv.innerHTML = '
正在测试API连接...
'; + + try { + const response = await fetch('/v1/api-keys', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + resultDiv.innerHTML = `
API响应状态: ${response.status}
`; + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + resultDiv.innerHTML += `
获取到的数据: ${JSON.stringify(data)}
`; + } catch (error) { + console.error('测试API失败:', error); + resultDiv.innerHTML = `
测试API失败: ${error.message}
`; + } +} + +// 清除缓存并刷新 +function clearCacheAndRefresh() { + // 清除缓存 + if ('caches' in window) { + caches.keys().then(function(names) { + for (let name of names) { + caches.delete(name); + } + }); + } + + // 强制刷新页面(绕过缓存) + window.location.reload(true); +} + +// 显示消息的通用函数 +function showMessage(containerId, message, type) { + const container = document.getElementById(containerId); + container.innerHTML = `
${message}
`; +} + +// Cookie刷新相关函数 +// 填充刷新API Key的下拉选择框 +async function populateRefreshApiKeySelect() { + try { + const apiKeys = await getApiKeys(); + const select = document.getElementById('refreshApiKey'); + + // 清空现有选项(保留"所有API Key"选项) + while (select.options.length > 1) { + select.remove(1); + } + + // 添加API Key选项 + apiKeys.forEach(key => { + const option = document.createElement('option'); + option.value = key.key; + option.textContent = `${key.key} (${key.cookieCount} 个Cookie)`; + select.appendChild(option); + }); + } catch (error) { + console.error('加载API Key选项失败:', error); + } +} + +// 处理刷新Cookie按钮事件 +async function handleRefreshCookie() { + console.log('刷新Cookie按钮被点击'); + const refreshBtn = document.getElementById('refreshCookieBtn'); + const apiKey = document.getElementById('refreshApiKey').value; + const statusContainer = document.getElementById('refreshStatusContainer'); + const statusText = document.getElementById('refreshStatus'); + const progressBar = document.getElementById('refreshProgress'); + + // 显示调试信息 + showMessage('refreshCookieMessage', '正在准备发送请求...', 'info'); + + // 禁用按钮,显示状态容器 + refreshBtn.disabled = true; + statusContainer.style.display = 'block'; + statusText.textContent = '正在发送刷新请求...'; + progressBar.value = 10; + + try { + // 构建请求URL + let url = '/v1/refresh-cookies'; + if (apiKey) { + url += `?apiKey=${encodeURIComponent(apiKey)}`; + } + + // 发送刷新请求 + statusText.textContent = '正在发送刷新请求...'; + progressBar.value = 20; + + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + } + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + // 显示长时间等待提示 + statusText.textContent = '刷新请求已发送,请耐心等待2-12分钟...'; + progressBar.value = 50; + showMessage('refreshCookieMessage', '刷新请求已发送,由于需要访问Cursor官网获取新Cookie,整个过程可能需要2-12分钟,请耐心等待。您可以关闭此页面,稍后再来查看结果。', 'info'); + + // 启动定时检查刷新状态 + let checkInterval = setInterval(async () => { + try { + const statusResponse = await fetch('/v1/refresh-status', { + method: 'GET', + headers: { + 'Cache-Control': 'no-cache' + } + }); + + if (!statusResponse.ok) { + throw new Error(`HTTP错误: ${statusResponse.status} ${statusResponse.statusText}`); + } + + const statusData = await statusResponse.json(); + const refreshData = statusData.data; + + // 更新状态信息 + statusText.textContent = refreshData.message || '正在刷新...'; + + // 根据状态更新进度条和UI + if (refreshData.status === 'completed') { + // 刷新完成 + progressBar.value = 100; + statusText.textContent = `刷新完成: ${refreshData.message}`; + clearInterval(checkInterval); + + // 重新加载API Key列表 + await loadApiKeys(); + await populateRefreshApiKeySelect(); + + // 显示成功消息 + showMessage('refreshCookieMessage', `刷新完成: ${refreshData.message}`, 'success'); + + // 启用按钮 + refreshBtn.disabled = false; + + // 3秒后隐藏状态容器 + setTimeout(() => { + statusContainer.style.display = 'none'; + }, 3000); + } else if (refreshData.status === 'failed') { + // 刷新失败 + progressBar.value = 0; + statusText.textContent = `刷新失败: ${refreshData.message}`; + clearInterval(checkInterval); + + // 显示错误消息 + showMessage('refreshCookieMessage', `刷新失败: ${refreshData.message}`, 'error'); + + // 启用按钮 + refreshBtn.disabled = false; + } else if (refreshData.status === 'running') { + // 正在刷新 + progressBar.value = 75; + } else if (!refreshData.isRunning) { + // 未知状态但不在运行 + clearInterval(checkInterval); + refreshBtn.disabled = false; + } + } catch (error) { + console.error('检查刷新状态失败:', error); + } + }, 5000); // 每5秒检查一次 + + // 设置超时检查,12分钟后如果还没完成就停止检查 + setTimeout(() => { + if (checkInterval) { + clearInterval(checkInterval); + refreshBtn.disabled = false; + statusContainer.style.display = 'none'; + } + }, 720000); + } catch (error) { + console.error('刷新Cookie失败:', error); + statusText.textContent = '刷新请求发送失败'; + progressBar.value = 0; + showMessage('refreshCookieMessage', `刷新请求发送失败: ${error.message}`, 'error'); + refreshBtn.disabled = false; + } +} + +// 获取Cookie相关函数 +// 为Cookie获取功能填充API Key下拉框 +function populateCookieApiKeySelect() { + populateRefreshApiKeySelect().then(() => { + // 复制refreshApiKey的选项到targetApiKey + const sourceSelect = document.getElementById('refreshApiKey'); + const targetSelect = document.getElementById('targetApiKey'); + + // 保留第一个选项("所有API Key") + while (targetSelect.options.length > 1) { + targetSelect.remove(1); + } + + // 复制选项 + for (let i = 1; i < sourceSelect.options.length; i++) { + const option = document.createElement('option'); + option.value = sourceSelect.options[i].value; + option.textContent = sourceSelect.options[i].textContent; + targetSelect.appendChild(option); + } + }); +} + +// 处理生成登录链接 +async function handleGenerateLink() { + console.log('生成登录链接按钮被点击'); + const messageContainer = document.getElementById('getCookieMessage'); + const linkContainer = document.getElementById('loginLinkContainer'); + const loginLink = document.getElementById('loginLink'); + const pollStatusText = document.getElementById('pollStatusText'); + const pollProgress = document.getElementById('pollProgress'); + const targetApiKey = document.getElementById('targetApiKey').value; + + try { + // 显示加载状态 + messageContainer.innerHTML = '
正在生成登录链接...
'; + + // 请求生成登录链接 + const response = await fetch('/v1/generate-cookie-link', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'no-cache' + }, + body: JSON.stringify({ apiKey: targetApiKey }) + }); + + if (!response.ok) { + throw new Error(`HTTP错误: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + + if (!data.success) { + throw new Error(data.message || '生成链接失败'); + } + + // 显示链接 + loginLink.href = data.url; + loginLink.textContent = data.url; + linkContainer.style.display = 'block'; + + // 更新状态 + pollStatusText.textContent = '等待用户登录...'; + pollProgress.value = 10; + + // 开始轮询获取cookie状态 + messageContainer.innerHTML = '
链接已生成,请点击链接登录Cursor账号并授权
'; + + // 开始轮询cookie状态 + pollForCookieStatus(data.uuid); + + } catch (error) { + console.error('生成登录链接失败:', error); + messageContainer.innerHTML = `
生成链接失败: ${error.message}
`; + } +} + +// 轮询Cookie获取状态 +function pollForCookieStatus(uuid) { + const messageContainer = document.getElementById('getCookieMessage'); + const pollStatusText = document.getElementById('pollStatusText'); + const pollProgress = document.getElementById('pollProgress'); + const maxAttempts = 300; // 最多尝试300次,相当于5分钟(原来是120次,2分钟) + let attempt = 0; + + // 更新状态显示 + pollStatusText.textContent = '等待用户登录...'; + + const interval = setInterval(function() { + attempt++; + + try { + // 更新进度条(保持在10%-90%之间,表示等待中) + pollProgress.value = 10 + Math.min(80, attempt / 3.75); // 调整进度条速度以适应5分钟 + + // 请求检查状态 + fetch(`/v1/check-cookie-status?uuid=${encodeURIComponent(uuid)}`, { + method: 'GET', + headers: { + 'Cache-Control': 'no-cache' + } + }).then(function(response) { + if (!response.ok) { + pollStatusText.textContent = `请求失败: ${response.status}`; + return; + } + + return response.json(); + }).then(function(data) { + if (data.success) { + // Cookie获取成功 + clearInterval(interval); + pollProgress.value = 100; + pollStatusText.textContent = '获取Cookie成功!'; + messageContainer.innerHTML = `
成功获取并添加Cookie!${data.message || ''}
`; + + // 刷新API Keys列表 + loadApiKeys(); + populateCookieApiKeySelect(); + + } else if (data.status === 'waiting') { + // 继续等待 + pollStatusText.textContent = '等待用户登录...'; + } else if (data.status === 'failed') { + // 获取失败 + clearInterval(interval); + pollStatusText.textContent = '获取失败'; + pollProgress.value = 0; + messageContainer.innerHTML = `
获取Cookie失败: ${data.message || '未知错误'}
`; + } + }).catch(function(error) { + console.error('轮询Cookie状态失败:', error); + pollStatusText.textContent = `轮询出错: ${error.message}`; + }); + + } catch (error) { + console.error('轮询Cookie状态出错:', error); + pollStatusText.textContent = `轮询出错: ${error.message}`; + } + + // 达到最大尝试次数后停止 + if (attempt >= maxAttempts) { + clearInterval(interval); + pollStatusText.textContent = '超时,请重试'; + pollProgress.value = 0; + messageContainer.innerHTML = '
获取Cookie超时,请重新尝试
'; + } + + }, 1000); // 每秒轮询一次 +} + +// 授权相关函数 +// 检查登录状态 +function checkAuth() { + const token = localStorage.getItem('adminToken'); + if (!token) { + window.location.href = '/login.html'; + return; + } + + // 验证token + fetch('/v1/admin/verify', { + headers: { + 'Authorization': `Bearer ${token}` + } + }) + .then(response => response.json()) + .then(data => { + if (!data.success) { + localStorage.removeItem('adminToken'); + window.location.href = '/login.html'; + } else { + // 更新为新的用户名显示方式 + const usernameElem = document.getElementById('usernameText'); + if (usernameElem) { + usernameElem.textContent = data.username; + } else { + // 兼容旧版模板,可能没有usernameText元素 + const adminElem = document.getElementById('adminUsername'); + if (adminElem) { + adminElem.textContent = `管理员:${data.username}`; + } + } + } + }) + .catch(error => { + console.error('验证失败:', error); + localStorage.removeItem('adminToken'); + window.location.href = '/login.html'; + }); +} + +// 处理退出登录 +function handleLogout() { + localStorage.removeItem('adminToken'); + window.location.href = '/login.html'; +} + +// 添加token到所有API请求 +function addAuthHeader(headers = {}) { + const token = localStorage.getItem('adminToken'); + return { + ...headers, + 'Authorization': `Bearer ${token}` + }; +} + +// 修改所有fetch请求,添加token +(function() { + const originalFetch = window.fetch; + window.fetch = function(url, options = {}) { + // 只对管理页面的API请求添加token + if (url.includes('/v1/api-keys') || + url.includes('/v1/invalid-cookies') || + url.includes('/v1/refresh-cookies') || + url.includes('/v1/generate-cookie-link') || + url.includes('/v1/check-cookie-status') || + url.includes('/v1/logs')) { + options.headers = addAuthHeader(options.headers); + } + return originalFetch(url, options); + }; +})(); + +// 无效Cookie模态窗口相关函数 +// 打开无效Cookie模态窗口 +async function openInvalidCookieModal() { + try { + document.getElementById('invalidCookieModalMessage').innerHTML = ''; + const invalidCookies = await getInvalidCookies(); + renderInvalidCookieTags(invalidCookies); + document.getElementById('invalidCookiesValues').value = invalidCookies.join(','); + document.getElementById('newInvalidCookie').value = ''; + const modal = document.getElementById('invalidCookieModal'); + modal.style.display = 'block'; + document.body.classList.add('modal-open'); + } catch (error) { + console.error('打开无效Cookie模态框失败:', error); + showMessage('invalidCookiesContainer', `加载无效Cookie失败: ${error.message}`, 'error'); + } +} + +// 关闭无效Cookie模态框 +function closeInvalidCookieModal() { + const modal = document.getElementById('invalidCookieModal'); + modal.style.display = 'none'; + document.body.classList.remove('modal-open'); +} + +// 渲染无效Cookie标签 +function renderInvalidCookieTags(cookies) { + const container = document.getElementById('invalidCookieTagsContainer'); + container.innerHTML = ''; + + if (cookies.length === 0) { + container.innerHTML = '
暂无无效Cookie
'; + return; + } + + cookies.forEach((cookie, index) => { + // 创建标签 + const tag = document.createElement('span'); + tag.className = 'cookie-tag'; + + // 对短文本添加特殊类 + if (cookie.length < 5) { + tag.classList.add('short-cookie'); + } + + // 截断Cookie显示 + const displayText = cookie.length > 20 ? + cookie.substring(0, 8) + '...' + cookie.substring(cookie.length - 8) : + cookie; + + tag.title = cookie; // 完整Cookie作为工具提示 + + // 修改样式,使用与API Key相同的删除按钮样式 + tag.innerHTML = ` + ${displayText} + + `; + container.appendChild(tag); + }); + + // 添加删除按钮的事件监听 + document.querySelectorAll('#invalidCookieTagsContainer .delete-cookie').forEach(btn => { + btn.addEventListener('click', function() { + const index = parseInt(this.getAttribute('data-index')); + deleteInvalidCookieTag(index); + }); + }); + + // 添加复制按钮的事件监听 + document.querySelectorAll('#invalidCookieTagsContainer .copy-btn').forEach(btn => { + btn.addEventListener('click', function() { + const cookie = this.getAttribute('data-cookie'); + handleCopyCookie(cookie); + }); + }); +} + +// 删除无效Cookie标签 +function deleteInvalidCookieTag(index) { + // 从隐藏的textarea中获取当前的cookies + const cookieValuesElem = document.getElementById('invalidCookiesValues'); + let cookies = cookieValuesElem.value.split(',').map(c => c.trim()).filter(c => c); + + // 删除指定索引的cookie + cookies.splice(index, 1); + + // 更新隐藏的textarea + cookieValuesElem.value = cookies.join(','); + + // 重新渲染标签 + renderInvalidCookieTags(cookies); +} + +// 处理添加新无效Cookie +function handleAddInvalidCookie() { + const newCookieInput = document.getElementById('newInvalidCookie'); + const newCookie = newCookieInput.value.trim(); + + if (!newCookie) { + return; + } + + // 获取当前的cookies + const cookieValuesElem = document.getElementById('invalidCookiesValues'); + let cookies = cookieValuesElem.value ? + cookieValuesElem.value.split(',').map(c => c.trim()).filter(c => c) : + []; + + // 添加新cookie + cookies.push(newCookie); + + // 更新隐藏的textarea + cookieValuesElem.value = cookies.join(','); + + // 重新渲染标签 + renderInvalidCookieTags(cookies); + + // 清空输入框 + newCookieInput.value = ''; +} + +// 处理无效Cookie编辑表单提交 +async function handleInvalidCookieForm(e) { + e.preventDefault(); + + const cookieValuesText = document.getElementById('invalidCookiesValues').value.trim(); + + // 将逗号分隔的 Cookie 值转换为数组 + const invalidCookies = cookieValuesText ? + cookieValuesText.split(',').map(cookie => cookie.trim()).filter(cookie => cookie) : + []; + + try { + // 先清除所有无效Cookie + await clearAllInvalidCookies(); + + // 如果有新的无效Cookie,逐个添加 + if (invalidCookies.length > 0) { + // 假设API提供了批量添加无效Cookie的接口 + const response = await fetch('/v1/invalid-cookies', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + invalidCookies, + }), + }); + + const data = await response.json(); + + if (data.success) { + document.getElementById('invalidCookieModalMessage').innerHTML = ` +
无效Cookie修改成功
+ `; + setTimeout(() => { + closeInvalidCookieModal(); + renderInvalidCookies(); // 重新渲染列表 + }, 1500); + } else { + document.getElementById('invalidCookieModalMessage').innerHTML = ` +
无效Cookie修改失败: ${data.error}
+ `; + } + } else { + // 如果清空了所有无效Cookie + document.getElementById('invalidCookieModalMessage').innerHTML = ` +
已清空所有无效Cookie
+ `; + setTimeout(() => { + closeInvalidCookieModal(); + renderInvalidCookies(); // 重新渲染列表 + }, 1500); + } + } catch (error) { + console.error('修改无效Cookie失败:', error); + document.getElementById('invalidCookieModalMessage').innerHTML = ` +
修改无效Cookie失败: ${error.message}
+ `; + } +} \ No newline at end of file diff --git a/src/public/styles.css b/src/public/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..41f4ad111b12c9ed502f9f512d8db480b4e66141 --- /dev/null +++ b/src/public/styles.css @@ -0,0 +1,1270 @@ +/* iOS风格现代化样式 */ +:root { + --ios-background: #f2f2f7; + --ios-card-background: #ffffff; + --ios-blue: #007aff; + --ios-green: #34c759; + --ios-red: #ff3b30; + --ios-yellow: #ffcc00; + --ios-gray: #8e8e93; + --ios-light-gray: #d1d1d6; + --ios-text-primary: #000000; + --ios-text-secondary: #6c6c70; + --ios-border-radius: 10px; + --ios-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + --ios-padding: 16px; +} + +/* 夜间模式变量 */ +[data-theme="dark"] { + --ios-background: #000000; + --ios-card-background: #1c1c1e; + --ios-blue: #0a84ff; + --ios-green: #30d158; + --ios-red: #ff453a; + --ios-yellow: #ffd60a; + --ios-gray: #8e8e93; + --ios-light-gray: #38383a; + --ios-text-primary: #ffffff; + --ios-text-secondary: #adadb5; + --ios-shadow: 0 2px 8px rgba(0, 0, 0, 0.2); +} + +/* 防止初始加载时主题切换闪烁 */ +.no-transition * { + transition: none !important; +} + +/* 主题切换时的平滑过渡效果 */ +.theme-transition { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.6s cubic-bezier(0.25, 1, 0.5, 1); +} + +.theme-transition * { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.6s cubic-bezier(0.25, 1, 0.5, 1), + transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), + opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} + +/* 主题切换按钮样式 */ +.theme-switch { + position: fixed; + bottom: 20px; + right: 20px; + background: var(--ios-card-background); + border-radius: 50%; + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: var(--ios-shadow); + z-index: 1000; + border: 1px solid rgba(142, 142, 147, 0.1); + transition: all 0.3s cubic-bezier(0.25, 1, 0.5, 1); + overflow: hidden; +} + +/* 主题切换按钮动画 */ +.theme-switch-animate { + transform: rotate(180deg) scale(1.1); + box-shadow: 0 0 20px rgba(var(--ios-blue-rgb, 0, 122, 255), 0.5); +} + +/* 主题图标动画 */ +.theme-icon { + width: 24px; + height: 24px; + color: var(--ios-text-primary); + transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1), + opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} + +.theme-icon.dark-mode { + transform: rotate(360deg) scale(1.1); +} + +.theme-switch:hover { + transform: scale(1.05); + box-shadow: 0 0 15px rgba(var(--ios-blue-rgb), 0.3); +} + +.theme-switch:active { + transform: scale(0.95); +} + +/* 提取颜色的RGB值用于动画和透明度 */ +:root { + --ios-blue-rgb: 0, 122, 255; + --ios-green-rgb: 52, 199, 89; + --ios-red-rgb: 255, 59, 48; + --ios-yellow-rgb: 255, 204, 0; +} + +[data-theme="dark"] { + --ios-blue-rgb: 10, 132, 255; + --ios-green-rgb: 48, 209, 88; + --ios-red-rgb: 255, 69, 58; + --ios-yellow-rgb: 255, 214, 10; +} + +/* 适配夜间模式的样式调整 */ +[data-theme="dark"] .header-card { + background: linear-gradient(-45deg, #30d158, #0a84ff, #5e5ce6, #64d2ff); + background-size: 300% 300%; + animation: gradientAnimation 12s ease infinite; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + border: none; +} + +[data-theme="dark"] .header-card::after { + background: linear-gradient(120deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 60%); +} + +[data-theme="dark"] .header-card, +[data-theme="dark"] .header-card h1, +[data-theme="dark"] .header-card p { + color: white; +} + +[data-theme="dark"] .cookie-tag { + background-color: rgba(10, 132, 255, 0.15); + border: 1px solid rgba(10, 132, 255, 0.2); +} + +[data-theme="dark"] .cookie-tag:hover { + background-color: rgba(10, 132, 255, 0.25); +} + +[data-theme="dark"] .delete-cookie, +[data-theme="dark"] .delete-add-cookie { + background-color: rgba(255, 69, 58, 0.2); +} + +[data-theme="dark"] .delete-cookie:hover, +[data-theme="dark"] .delete-add-cookie:hover { + background-color: rgba(255, 69, 58, 0.3); +} + +[data-theme="dark"] .copy-btn { + background: rgba(142, 142, 147, 0.2); +} + +[data-theme="dark"] .copy-btn:hover { + background-color: rgba(10, 132, 255, 0.2); +} + +[data-theme="dark"] .sticky-actions { + background: rgba(28, 28, 30, 0.8); +} + +[data-theme="dark"] .modal-content { + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); +} + +[data-theme="dark"] input, +[data-theme="dark"] textarea, +[data-theme="dark"] select { + background-color: rgba(142, 142, 147, 0.12); +} + +[data-theme="dark"] input:focus, +[data-theme="dark"] textarea:focus, +[data-theme="dark"] select:focus { + background-color: rgba(142, 142, 147, 0.18); + box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.25); +} + +/* 主题切换动画 */ +body { + transition: background-color 0.3s ease; +} + +.card, input, textarea, select, button, th, td, .modal-content, .cookie-tag, .sticky-actions { + transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease; +} + +* { + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +html { + scroll-behavior: smooth; + height: -webkit-fill-available; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display', 'Helvetica Neue', Arial, sans-serif; + line-height: 1.5; + margin: 0; + padding: 16px; + color: var(--ios-text-primary); + max-width: 960px; + margin: 0 auto; + background-color: var(--ios-background); + font-size: 16px; + font-weight: 400; +} + +h1, h2 { + color: var(--ios-text-primary); + margin-top: 0; + font-weight: 600; + letter-spacing: -0.5px; +} + +h1 { + font-size: 28px; + margin-bottom: 8px; +} + +h2 { + font-size: 22px; + margin-bottom: 16px; +} + +.container { + display: flex; + flex-direction: column; + gap: 16px; +} + +.card { + background: var(--ios-card-background); + border-radius: var(--ios-border-radius); + box-shadow: var(--ios-shadow); + padding: var(--ios-padding); + transition: transform 0.2s, box-shadow 0.2s; + border: 1px solid rgba(0, 0, 0, 0.04); + overflow: hidden; +} + +.card:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06); +} + +.form-group { + margin-bottom: 20px; +} + +label { + display: block; + margin-bottom: 8px; + font-weight: 500; + color: var(--ios-text-primary); + font-size: 15px; +} + +input, textarea, select { + width: 100%; + padding: 12px 14px; + border: 1px solid var(--ios-light-gray); + border-radius: 8px; + font-size: 16px; + transition: all 0.2s; + box-sizing: border-box; + background-color: rgba(142, 142, 147, 0.06); + color: var(--ios-text-primary); + appearance: none; + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif; +} + +input:focus, textarea:focus, select:focus { + border-color: var(--ios-blue); + outline: none; + box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15); + background-color: var(--ios-card-background); +} + +textarea { + min-height: 100px; + font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Helvetica Neue', Arial, sans-serif; + line-height: 1.5; +} + +button { + background: var(--ios-blue); + color: white; + border: none; + padding: 12px 18px; + border-radius: 8px; + cursor: pointer; + font-size: 16px; + transition: all 0.2s; + font-weight: 500; + -webkit-tap-highlight-color: transparent; + text-align: center; + display: inline-flex; + align-items: center; + justify-content: center; +} + +button:hover { + background: #0062cc; + transform: translateY(-1px); +} + +button:active { + transform: translateY(1px); + opacity: 0.9; +} + +table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin-bottom: 16px; + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--ios-light-gray); +} + +th, td { + padding: 14px 16px; + text-align: left; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +th { + background-color: rgba(142, 142, 147, 0.06); + font-weight: 600; + color: var(--ios-text-primary); + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +tr:last-child td { + border-bottom: none; +} + +tr:hover { + background-color: rgba(0, 122, 255, 0.03); +} + +.action-btn { + background: var(--ios-red); + margin-right: 8px; + font-size: 14px; + padding: 8px 12px; +} + +.action-btn:hover { + background: #e02e24; +} + +.edit-btn { + background: var(--ios-blue); + margin-right: 8px; + font-size: 14px; + padding: 8px 12px; +} + +.edit-btn:hover { + background: #0062cc; +} + +.info { + background-color: rgba(52, 199, 89, 0.1); + color: var(--ios-text-primary); + padding: 14px; + border-radius: 8px; + margin-bottom: 16px; + border-left: 3px solid var(--ios-green); + font-size: 15px; +} + +.error { + background-color: rgba(255, 59, 48, 0.1); + color: var(--ios-text-primary); + padding: 14px; + border-radius: 8px; + margin-bottom: 16px; + border-left: 3px solid var(--ios-red); + font-size: 15px; +} + +.modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); +} + +.modal-content { + background-color: var(--ios-card-background); + margin: 10% auto; + padding: 24px; + border: none; + width: 85%; + max-width: 500px; + border-radius: 14px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + animation: modalFadeIn 0.3s; +} + +@keyframes modalFadeIn { + from { opacity: 0; transform: translateY(-20px); } + to { opacity: 1; transform: translateY(0); } +} + +.close { + color: var(--ios-gray); + float: right; + font-size: 24px; + font-weight: 300; + cursor: pointer; + margin-top: -5px; + transition: all 0.2s; +} + +.close:hover, +.close:focus { + color: var(--ios-text-primary); + text-decoration: none; +} + +.cookie-text { + word-break: break-all; + font-size: 14px; + font-family: monospace; +} + +.cookie-tag { + display: inline-flex; + align-items: center; + justify-content: space-between; + background-color: rgba(0, 122, 255, 0.08); + padding: 8px 12px; + border-radius: 8px; + margin: 0 0 8px 0; + width: 100%; + position: relative; + transition: all 0.2s; + border: 1px solid rgba(0, 122, 255, 0.12); +} + +.cookie-tag:hover { + background-color: rgba(0, 122, 255, 0.12); +} + +.delete-cookie, .delete-add-cookie { + background-color: rgba(255, 59, 48, 0.1); + color: var(--ios-red); + border: none; + width: 22px; + height: 22px; + border-radius: 50%; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 12px; + margin-left: 8px; + padding: 0; + line-height: 1; + transition: all 0.2s; + -webkit-tap-highlight-color: transparent; +} + +.delete-cookie:hover, .delete-add-cookie:hover { + background-color: rgba(255, 59, 48, 0.2); + transform: scale(1.05); +} + +.delete-cookie:active, .delete-add-cookie:active { + transform: scale(0.95); + opacity: 0.9; +} + +.cookie-text-content { + max-width: calc(100% - 70px); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--ios-text-primary); +} + +.cookies-container { + display: flex; + flex-wrap: wrap; + margin-bottom: 15px; + padding: 5px 0; +} + +.add-cookie-btn { + padding: 10px 14px; + background-color: var(--ios-blue); + transition: all 0.2s ease; +} + +.add-cookie-btn:hover { + background-color: #0062cc; +} + +progress { + width: 100%; + height: 8px; + border-radius: 4px; + overflow: hidden; +} + +progress::-webkit-progress-bar { + background-color: rgba(142, 142, 147, 0.2); + border-radius: 4px; +} + +progress::-webkit-progress-value { + background-color: var(--ios-blue); + border-radius: 4px; + transition: width 0.3s ease; +} + +/* 梦幻动态渐变背景动画 */ +@keyframes gradientAnimation { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +.header-card { + background: linear-gradient(-45deg, #4CD964, #5ac8fa, #34C759, #5ac8fa); + background-size: 300% 300%; + animation: gradientAnimation 12s ease infinite; + color: var(--ios-text-primary); + position: relative; + overflow: hidden; + padding: 20px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); + border: none; +} + +.header-card::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(120deg, rgba(255,255,255,0.4) 0%, rgba(255,255,255,0) 60%); + pointer-events: none; +} + +.header-card h1, .header-card p { + color: var(--ios-text-primary); +} + +.sticky-actions { + position: sticky; + bottom: 20px; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + padding: 15px; + border-radius: 12px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); + margin-top: 20px; + z-index: 100; + display: flex; + justify-content: flex-end; + gap: 10px; +} + +.cookie-tag.short-cookie { + max-width: 180px; +} + +.copy-btn { + background: rgba(142, 142, 147, 0.1); + color: var(--ios-blue); + border: none; + width: 28px; + height: 28px; + border-radius: 50%; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 14px; + margin-left: 6px; + padding: 0; + transition: all 0.2s; + -webkit-tap-highlight-color: transparent; +} + +.copy-btn:hover { + background-color: rgba(0, 122, 255, 0.1); + transform: scale(1.05); +} + +.copy-btn:active { + transform: scale(0.95); + opacity: 0.9; +} + +.delete-cookie, .delete-add-cookie { + background-color: rgba(255, 59, 48, 0.1); + color: var(--ios-red); + border: none; + width: 22px; + height: 22px; + border-radius: 50%; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 12px; + margin-left: 8px; + padding: 0; + line-height: 1; + transition: all 0.2s; + -webkit-tap-highlight-color: transparent; +} + +.delete-cookie:hover, .delete-add-cookie:hover { + background-color: rgba(255, 59, 48, 0.2); + transform: scale(1.05); +} + +.delete-cookie:active, .delete-add-cookie:active { + transform: scale(0.95); + opacity: 0.9; +} + +.cookie-buttons { + display: flex; + align-items: center; + flex-shrink: 0; + margin-left: auto; +} + +/* 适配移动端的样式 */ +@media screen and (max-width: 768px) { + body { + padding: 12px; + font-size: 15px; + } + + h1 { + font-size: 24px; + } + + h2 { + font-size: 20px; + } + + .card { + padding: 16px; + } + + input, textarea, select { + padding: 10px 12px; + font-size: 16px; + } + + button { + padding: 12px 16px; + width: 100%; + margin-bottom: 8px; + } + + /* 优化添加按钮在移动端的宽度 */ + .add-cookie-btn { + width: auto; + min-width: 42px; + max-width: 42px; + padding: 10px; + margin-bottom: 0; + flex: 0 0 auto; + } + + .add-cookie-btn i { + margin: 0; + } + + /* 让输入框占据更多宽度 */ + #addNewCookie, + #newCookie, + #newInvalidCookie { + flex: 1; + } + + /* 优化表单布局 */ + div[style*="display: flex; gap: 10px;"] { + gap: 8px !important; + } + + /* 优化Cookie标签在移动端的显示 */ + .cookie-tag { + font-size: 13px; + padding: 6px 10px; + margin: 0 0 8px 0; + width: 100%; + } + + /* 让Cookie内容区域尽可能宽 */ + .cookie-text-content { + max-width: calc(100% - 68px); + } + + /* 优化复制与删除按钮 */ + .cookie-buttons { + display: flex; + align-items: center; + gap: 4px; + flex-shrink: 0; + } + + .copy-btn, + .delete-cookie, + .delete-add-cookie { + width: 28px; + height: 28px; + margin-left: 4px; + background-color: rgba(142, 142, 147, 0.15); + transition: all 0.15s ease; + } + + .copy-btn:active, + .delete-cookie:active, + .delete-add-cookie:active { + transform: scale(0.92); + opacity: 0.8; + } + + .cookie-tag.short-cookie { + max-width: calc(100% - 16px); + } + + th, td { + padding: 12px; + font-size: 14px; + } + + .modal-content { + width: 92%; + padding: 20px; + margin: 15% auto 5%; + } + + /* 在小屏幕上重新排列表格 */ + table, thead, tbody, th, td, tr { + display: block; + } + + thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + + tr { + border: 1px solid rgba(0, 0, 0, 0.05); + margin-bottom: 10px; + border-radius: 8px; + overflow: hidden; + } + + td { + border: none; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + position: relative; + padding-left: 50%; + white-space: normal; + text-align: left; + } + + td:before { + position: absolute; + top: 12px; + left: 12px; + width: 45%; + padding-right: 10px; + white-space: nowrap; + text-align: left; + font-weight: 600; + content: attr(data-title); + color: var(--ios-text-secondary); + font-size: 13px; + } + + /* 设置每个单元格的标题 */ + #keyTable td:nth-of-type(1):before { content: "API Key"; } + #keyTable td:nth-of-type(2):before { content: "Cookie 数量"; } + #keyTable td:nth-of-type(3):before { content: "操作"; } + + .action-btn, .edit-btn { + margin-right: 8px; + font-size: 14px; + padding: 8px 12px; + width: auto; + display: inline-flex; + } + + .form-input-group { + display: flex; + flex-direction: column; + gap: 8px; + } + + .form-input-group input { + width: 100%; + } + + .form-input-group button { + width: 100%; + } + + .header-card div[style*="display: flex; justify-content: space-between;"] { + flex-direction: column; + gap: 10px; + } + + .header-card div[style*="display: flex; justify-content: space-between;"] div { + width: 100%; + display: flex; + flex-wrap: wrap; + gap: 8px; + } + + .header-card div[style*="display: flex; justify-content: space-between;"] button { + flex: 1; + min-width: 0; + } + + #adminUsername { + display: block; + width: 100%; + margin-bottom: 8px; + text-align: center; + } +} + +/* 为header-card内的按钮添加特殊样式 */ +.header-card button { + background-color: rgba(255, 255, 255, 0.2); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + color: var(--ios-text-primary); + border: 1px solid rgba(255, 255, 255, 0.3); + font-weight: 500; + transition: all 0.3s ease; +} + +.header-card button:hover { + background-color: rgba(255, 255, 255, 0.25); + transform: translateY(-2px); +} + +.header-card button.danger { + background-color: #ff3b30; + color: white; + border: none; +} + +.header-card button.danger:hover { + background-color: #ff2d20; +} + +[data-theme="dark"] .header-card button { + background-color: rgba(255, 255, 255, 0.15); + color: white; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +[data-theme="dark"] .header-card button:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +/* 用户信息样式 */ +.header-card .user-info { + display: flex; + align-items: center; + margin: 15px 0; +} + +.header-card .user-info i { + margin-right: 8px; + opacity: 0.9; +} + +/* 退出按钮特殊样式 */ +.header-card .logout-btn { + background-color: rgba(255, 59, 48, 0.9); + color: white; + border: none; + width: 100%; + padding: 12px; + margin-top: 10px; + font-weight: 500; + transition: all 0.3s ease; +} + +.header-card .logout-btn:hover { + background-color: rgba(255, 59, 48, 1); + transform: translateY(-2px); +} + +[data-theme="dark"] .header-card .logout-btn { + background-color: rgba(255, 69, 58, 0.8); +} + +[data-theme="dark"] .header-card .logout-btn:hover { + background-color: rgba(255, 69, 58, 0.9); +} + +/* 按钮组样式 */ +.header-card .button-group { + display: flex; + gap: 10px; + margin-bottom: 15px; +} + +.header-card .button-group button { + flex: 1; +} + +/* 用户名颜色随主题切换 */ +#adminUsername { + color: var(--ios-text-primary) !important; +} + +/* 优化主题切换动画 - 添加在文件末尾 */ +/* 提取颜色的RGB值用于动画和透明度 */ +:root { + --ios-blue-rgb: 0, 122, 255; + --ios-green-rgb: 52, 199, 89; + --ios-red-rgb: 255, 59, 48; + --ios-yellow-rgb: 255, 204, 0; +} + +[data-theme="dark"] { + --ios-blue-rgb: 10, 132, 255; + --ios-green-rgb: 48, 209, 88; + --ios-red-rgb: 255, 69, 58; + --ios-yellow-rgb: 255, 214, 10; +} + +/* 防止初始加载时主题切换闪烁 */ +.no-transition * { + transition: none !important; +} + +/* 主题切换时的平滑过渡效果 */ +.theme-transition { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.6s cubic-bezier(0.25, 1, 0.5, 1); +} + +.theme-transition * { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.6s cubic-bezier(0.25, 1, 0.5, 1), + transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), + opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} + +/* 主题切换按钮动画 */ +.theme-switch-animate { + transform: rotate(180deg) scale(1.1); + box-shadow: 0 0 20px rgba(var(--ios-blue-rgb), 0.5); +} + +/* 主题图标动画 */ +.theme-icon { + width: 24px; + height: 24px; + color: var(--ios-text-primary); + transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1), + opacity 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} + +.theme-icon.dark-mode { + transform: rotate(360deg) scale(1.1); +} + +.theme-switch:hover { + transform: scale(1.05); + box-shadow: 0 0 15px rgba(var(--ios-blue-rgb), 0.3); +} + +.theme-switch:active { + transform: scale(0.95); +} + +/* 优化特定元素的过渡效果 */ +.card { + transition: transform 0.3s cubic-bezier(0.25, 1, 0.5, 1), + background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1); +} + +button { + transition: background-color 0.3s cubic-bezier(0.25, 1, 0.5, 1), + transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.3s cubic-bezier(0.25, 1, 0.5, 1), + opacity 0.2s cubic-bezier(0.25, 1, 0.5, 1); +} + +input, textarea, select { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} + +.cookie-tag, .delete-cookie, .delete-add-cookie, .copy-btn { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + transform 0.2s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1); +} + +/* 添加主题切换时的特殊动画效果 */ +.theme-transition .card { + animation: card-theme-shift 0.8s cubic-bezier(0.25, 1, 0.5, 1); +} + +@keyframes card-theme-shift { + 0% { transform: translateY(0); } + 50% { transform: translateY(-5px); } + 100% { transform: translateY(0); } +} + +/* 确保元素在滚动和动画期间的流畅渲染 */ +.card, .theme-switch, .modal-content, input, button { + backface-visibility: hidden; + -webkit-backface-visibility: hidden; + transform-style: preserve-3d; + -webkit-transform-style: preserve-3d; + will-change: transform, opacity; +} + +/* 调整主题切换动画效果 */ +.theme-transition body { + animation: bg-fade 0.8s cubic-bezier(0.25, 1, 0.5, 1); +} + +@keyframes bg-fade { + 0% { opacity: 0.98; } + 50% { opacity: 0.95; } + 100% { opacity: 1; } +} + +/* 侧边导航栏样式 */ +.side-nav-trigger { + position: fixed; + right: 20px; + top: 50%; + transform: translateY(-50%); + z-index: 1000; + width: 12px; + height: 12px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); + border-radius: 50%; + background-color: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + box-shadow: 0 0 5px rgba(0, 122, 255, 0.2); +} + +.trigger-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: rgba(88, 166, 255, 0.8); + box-shadow: 0 0 4px rgba(88, 166, 255, 0.5); + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); +} + +.side-nav-trigger:hover { + background-color: rgba(255, 255, 255, 0.2); +} + +.side-nav-trigger:hover .trigger-dot { + background-color: var(--ios-blue); + box-shadow: 0 0 8px rgba(0, 122, 255, 0.7); +} + +.side-nav-menu { + position: fixed; + right: 40px; + top: 50%; + transform: translateY(-50%); + z-index: 999; + background-color: rgba(255, 255, 255, 0.08); + backdrop-filter: blur(25px); + -webkit-backdrop-filter: blur(25px); + border-radius: 16px; + padding: 15px 10px; + width: 200px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.15); + opacity: 0; + visibility: hidden; + transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); +} + +.side-nav-trigger:hover + .side-nav-menu, +.side-nav-menu:hover { + opacity: 1; + visibility: visible; +} + +.side-nav-content { + display: flex; + flex-direction: column; + gap: 3px; +} + +.nav-item { + display: flex; + align-items: center; + padding: 8px 10px; + border-radius: 10px; + cursor: pointer; + transition: all 0.2s ease; +} + +.nav-item:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +.nav-item.active { + background-color: rgba(0, 122, 255, 0.1); +} + +.nav-item-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background-color: rgba(158, 158, 158, 0.8); + margin-right: 10px; + transition: all 0.2s ease; +} + +.nav-item.active .nav-item-dot { + background-color: var(--ios-blue); + box-shadow: 0 0 5px rgba(0, 122, 255, 0.5); +} + +/* 非活动条目使用浅灰色文字 */ +.nav-item-title { + color: var(--ios-gray); + font-size: 14px; + font-weight: 400; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + letter-spacing: -0.2px; + transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1); +} + +/* 当前激活条目使用主要文字颜色并加粗 */ +.nav-item.active .nav-item-title { + color: var(--ios-blue); + font-weight: 600; +} + +/* 鼠标悬停时字体放大加粗 */ +.nav-item:hover .nav-item-title { + transform: scale(1.05); + font-weight: 500; + color: var(--ios-text-primary); +} + +@media (max-width: 768px) { + .side-nav-trigger { + width: 14px; + height: 14px; + } + + .trigger-dot { + width: 7px; + height: 7px; + } + + .side-nav-menu { + right: 40px; + width: 180px; + } + + /* 移动端点击显示目录 */ + .side-nav-trigger.touch-active + .side-nav-menu, + .side-nav-menu.touch-active { + opacity: 1; + visibility: visible; + } +} + +/* 暗色模式自适应 */ +@media (prefers-color-scheme: dark) { + .side-nav-trigger { + background-color: rgba(30, 30, 30, 0.3); + } + + .side-nav-trigger:hover { + background-color: rgba(30, 30, 30, 0.5); + } + + .side-nav-menu { + background-color: rgba(30, 30, 30, 0.08); + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(255, 255, 255, 0.08); + } + + .nav-item:hover { + background-color: rgba(255, 255, 255, 0.08); + } + + .nav-item.active { + background-color: rgba(10, 132, 255, 0.15); + } +} + +[data-theme="dark"] .nav-item-title { + color: var(--ios-gray); +} + +[data-theme="dark"] .nav-item.active .nav-item-title { + color: var(--ios-blue); +} + +[data-theme="dark"] .nav-item:hover .nav-item-title { + color: rgba(255, 255, 255, 0.9); +} + +input, textarea, select { + transition: background-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + border-color 0.6s cubic-bezier(0.25, 1, 0.5, 1), + box-shadow 0.3s cubic-bezier(0.25, 1, 0.5, 1); +} \ No newline at end of file diff --git a/src/public/theme.js b/src/public/theme.js new file mode 100644 index 0000000000000000000000000000000000000000..f7053f456ce5ee50253555009afa037bf1b4c6b2 --- /dev/null +++ b/src/public/theme.js @@ -0,0 +1,290 @@ +// 主题管理系统 + +// 在HTML解析前应用主题,防止初始闪烁 +(function() { + // 尝试从localStorage读取用户主题偏好 + const savedTheme = localStorage.getItem('userThemePreference'); + + // 如果有保存的主题偏好,立即应用 + if (savedTheme) { + document.documentElement.setAttribute('data-theme', savedTheme); + } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + // 检查系统主题 + document.documentElement.setAttribute('data-theme', 'dark'); + } else { + // 检查当前时间 + const currentHour = new Date().getHours(); + if (currentHour >= 19 || currentHour < 7) { + document.documentElement.setAttribute('data-theme', 'dark'); + } + } + + // 添加类以防止过渡效果在页面加载时触发 + document.documentElement.classList.add('no-transition'); +})(); + +document.addEventListener('DOMContentLoaded', () => { + // 创建主题切换按钮 + createThemeToggle(); + + // 初始化主题 + initTheme(); + + // 监听系统主题变化 + listenForSystemThemeChanges(); + + // 移除阻止过渡效果的类 + setTimeout(() => { + document.documentElement.classList.remove('no-transition'); + }, 100); +}); + +// 创建主题切换按钮 +function createThemeToggle() { + const themeSwitch = document.createElement('div'); + themeSwitch.className = 'theme-switch'; + themeSwitch.setAttribute('title', '切换亮/暗主题'); + themeSwitch.innerHTML = ` + + + + + + + + + + + + `; + + // 更新为当前主题的图标 + const currentTheme = document.documentElement.getAttribute('data-theme'); + updateThemeIcon(currentTheme); + + // 添加点击事件监听 + themeSwitch.addEventListener('click', toggleTheme); + + // 添加到页面 + document.body.appendChild(themeSwitch); +} + +// 初始化主题 +function initTheme() { + // 首先检查用户的主题偏好 + const savedTheme = localStorage.getItem('userThemePreference'); + if (savedTheme) { + applyTheme(savedTheme); + return; + } + + // 如果没有用户偏好,检查系统主题 + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + applyTheme('dark'); + return; + } + + // 检查当前时间 + const currentHour = new Date().getHours(); + if (currentHour >= 19 || currentHour < 7) { + applyTheme('dark'); + return; + } + + // 如果没有特殊情况,使用亮色主题 + applyTheme('light'); +} + +// 应用主题 +function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + updateThemeIcon(theme); + localStorage.setItem('userThemePreference', theme); +} + +// 更新主题图标 +function updateThemeIcon(theme) { + const themeIcon = document.querySelector('.theme-icon'); + + if (!themeIcon) return; + + if (theme === 'dark') { + // 使用CSS类切换动画而不是直接修改innerHTML + themeIcon.classList.add('dark-mode'); + themeIcon.innerHTML = ` + + `; + } else { + themeIcon.classList.remove('dark-mode'); + themeIcon.innerHTML = ` + + + + + + + + + + `; + } +} + +// 切换主题 +function toggleTheme() { + const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; + const newTheme = currentTheme === 'light' ? 'dark' : 'light'; + + // 添加过渡类,启用平滑动画 + document.documentElement.classList.add('theme-transition'); + + // 应用新主题 + applyTheme(newTheme); + + // 切换动画效果 + const themeSwitch = document.querySelector('.theme-switch'); + if (themeSwitch) { + themeSwitch.classList.add('theme-switch-animate'); + setTimeout(() => { + themeSwitch.classList.remove('theme-switch-animate'); + }, 700); + } +} + +// 监听系统主题变化 +function listenForSystemThemeChanges() { + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { + // 只有当用户没有手动设置主题时才跟随系统 + if (!localStorage.getItem('userThemePreference')) { + applyTheme(e.matches ? 'dark' : 'light'); + } + }); + } +} + +// 基于时间自动切换主题的功能 +function scheduleThemeChange() { + // 只有当用户没有手动设置主题时才自动切换 + if (!localStorage.getItem('userThemePreference')) { + const currentHour = new Date().getHours(); + if (currentHour >= 19 || currentHour < 7) { + applyTheme('dark'); + } else { + applyTheme('light'); + } + } + + // 每小时检查一次 + setTimeout(scheduleThemeChange, 3600000); +} + +// 启动基于时间的主题切换 +scheduleThemeChange(); + +// 侧边导航功能 +document.addEventListener('DOMContentLoaded', function() { + initSideNavigation(); +}); + +// 初始化侧边导航 +function initSideNavigation() { + // 获取所有卡片 + const cards = document.querySelectorAll('.card'); + const navContent = document.querySelector('.side-nav-content'); + const trigger = document.querySelector('.side-nav-trigger'); + const menu = document.querySelector('.side-nav-menu'); + + if (!cards.length || !navContent) return; + + // 为每个卡片创建导航项 + cards.forEach((card, index) => { + // 尝试获取卡片标题 + let title = ''; + const h2 = card.querySelector('h2'); + const h1 = card.querySelector('h1'); + + if (h2) { + title = h2.textContent.trim(); + } else if (h1) { + title = h1.textContent.trim(); + } else { + title = `部分 ${index + 1}`; + } + + // 创建导航项 + const navItem = document.createElement('div'); + navItem.className = 'nav-item'; + navItem.setAttribute('data-target', index); + + // 创建导航点 + const dot = document.createElement('div'); + dot.className = 'nav-item-dot'; + + // 创建标题 + const titleSpan = document.createElement('div'); + titleSpan.className = 'nav-item-title'; + titleSpan.textContent = title; + + navItem.appendChild(dot); + navItem.appendChild(titleSpan); + navContent.appendChild(navItem); + + // 点击事件:滚动到对应卡片 + navItem.addEventListener('click', (e) => { + e.preventDefault(); + card.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }); + }); + + // 使用 Intersection Observer 检测当前可见的卡片 + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const index = Array.from(cards).indexOf(entry.target); + updateActiveNavItem(index); + } + }); + }, { threshold: 0.3 }); + + // 观察所有卡片 + cards.forEach(card => { + observer.observe(card); + }); + + // 更新活动导航项 + function updateActiveNavItem(index) { + const navItems = document.querySelectorAll('.nav-item'); + navItems.forEach(item => { + item.classList.remove('active'); + }); + + const activeItem = document.querySelector(`.nav-item[data-target="${index}"]`); + if (activeItem) { + activeItem.classList.add('active'); + + // 确保活动项在可视区域内 + if (activeItem.offsetTop < navContent.scrollTop || + activeItem.offsetTop > navContent.scrollTop + navContent.clientHeight) { + activeItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + } + } + + // 移动端触摸事件处理 + if (trigger && menu) { + trigger.addEventListener('touchstart', function(e) { + e.preventDefault(); + this.classList.toggle('touch-active'); + menu.classList.toggle('touch-active'); + }); + + // 点击其他区域关闭移动端目录 + document.addEventListener('touchstart', function(e) { + if (!e.target.closest('.side-nav-trigger') && !e.target.closest('.side-nav-menu')) { + trigger.classList.remove('touch-active'); + menu.classList.remove('touch-active'); + } + }); + } +} \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100644 index 0000000000000000000000000000000000000000..f536308381c9e304e36e7fab1fe0f2b11eab674c --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = express.Router(); +const v1Routes = require('./v1'); + +// OpenAI v1 API routes +router.use('/v1', v1Routes); + +module.exports = router; diff --git a/src/routes/v1.js b/src/routes/v1.js new file mode 100644 index 0000000000000000000000000000000000000000..55f7f1894e914d3cfd6fdf9822900d5455bfb7f5 --- /dev/null +++ b/src/routes/v1.js @@ -0,0 +1,1924 @@ +const express = require('express'); +const router = express.Router(); +const { fetch, ProxyAgent, Agent } = require('undici'); + +const $root = require('../proto/message.js'); +const { v4: uuidv4, v5: uuidv5 } = require('uuid'); +const { generateCursorBody, chunkToUtf8String, generateHashed64Hex, generateCursorChecksum } = require('../utils/utils.js'); +const keyManager = require('../utils/keyManager.js'); +const { spawn } = require('child_process'); +const path = require('path'); +const admin = require('../models/admin'); +const config = require('../config/config'); +const crypto = require('crypto'); +const logger = require('../utils/logger'); + +const activeRequestControllers = new Map(); // 用于存储 API Key -> AbortController 的映射 + +// 存储刷新状态的变量 +let refreshStatus = { + isRunning: false, + status: 'idle', // idle, running, completed, failed + message: '', + startTime: null, + endTime: null, + error: null +}; + +// 储存当前正在处理的Cookie获取请求 +const pendingCookieRequests = new Map(); + +// 检查是否已有管理员账号 +router.get('/admin/check', (req, res) => { + try { + return res.json({ + success: true, + exists: admin.hasAdmin() + }); + } catch (error) { + logger.error('检查管理员账号失败:', error); + return res.status(500).json({ + success: false, + message: error.message + }); + } +}); + +// 注册管理员 +router.post('/admin/register', (req, res) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ + success: false, + message: '用户名和密码不能为空' + }); + } + + const token = admin.register(username, password); + + return res.json({ + success: true, + message: '注册成功', + token + }); + } catch (error) { + logger.error('注册管理员失败:', error); + return res.status(400).json({ + success: false, + message: error.message + }); + } +}); + +// 管理员登录 +router.post('/admin/login', (req, res) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ + success: false, + message: '用户名和密码不能为空' + }); + } + + const token = admin.login(username, password); + + return res.json({ + success: true, + message: '登录成功', + token + }); + } catch (error) { + logger.error('登录失败:', error); + return res.status(400).json({ + success: false, + message: error.message + }); + } +}); + +// 验证token +router.get('/admin/verify', (req, res) => { + try { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + success: false, + message: '未提供认证token' + }); + } + + const token = authHeader.split(' ')[1]; + const result = admin.verifyToken(token); + + return res.json(result); + } catch (error) { + logger.error('验证token失败:', error); + return res.status(401).json({ + success: false, + message: error.message + }); + } +}); + +// 添加API key管理路由 +router.post("/api-keys", async (req, res) => { + try { + const { apiKey, cookieValues } = req.body; + + if (!apiKey || !cookieValues) { + return res.status(400).json({ + error: 'API key and cookie values are required', + }); + } + + keyManager.addOrUpdateApiKey(apiKey, cookieValues); + + return res.json({ + success: true, + message: 'API key added or updated successfully', + }); + } catch (error) { + logger.error(error); + return res.status(500).json({ + error: 'Internal server error', + }); + } +}); + +// 获取所有API Keys +router.get("/api-keys", async (req, res) => { + try { + logger.info('收到获取API Keys请求'); + const apiKeys = keyManager.getAllApiKeys(); + logger.info('获取到的API Keys:', apiKeys); + + const result = { + success: true, + apiKeys: apiKeys.map(apiKey => ({ + key: apiKey, + cookieCount: keyManager.getAllCookiesForApiKey(apiKey).length, + })), + }; + logger.info('返回结果:', result); + + return res.json(result); + } catch (error) { + logger.error('获取API Keys失败:', error); + return res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// 删除API key +router.delete("/api-keys/:apiKey", async (req, res) => { + try { + const { apiKey } = req.params; + + keyManager.removeApiKey(apiKey); + + return res.json({ + success: true, + message: 'API key removed successfully', + }); + } catch (error) { + logger.error(error); + return res.status(500).json({ + error: 'Internal server error', + }); + } +}); + +// 获取特定API Key的Cookie值 +router.get("/api-keys/:apiKey/cookies", async (req, res) => { + try { + const { apiKey } = req.params; + logger.info(`收到获取API Key ${apiKey}的Cookie值请求`); + + const cookies = keyManager.getAllCookiesForApiKey(apiKey); + logger.info(`API Key ${apiKey}的Cookie值:`, cookies); + + return res.json({ + success: true, + cookies: cookies + }); + } catch (error) { + logger.error(`获取API Key ${req.params.apiKey}的Cookie值失败:`, error); + return res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// 获取所有无效的cookie +router.get("/invalid-cookies", async (req, res) => { + try { + const invalidCookies = keyManager.getInvalidCookies(); + + return res.json({ + success: true, + invalidCookies: Array.from(invalidCookies) + }); + } catch (error) { + logger.error('获取无效cookie失败:', error); + return res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// 清除特定的无效cookie +router.delete("/invalid-cookies/:cookie", async (req, res) => { + try { + const { cookie } = req.params; + const success = keyManager.clearInvalidCookie(cookie); + + return res.json({ + success: success, + message: success ? '无效cookie已清除' : '未找到指定的无效cookie' + }); + } catch (error) { + logger.error('清除无效cookie失败:', error); + return res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// 清除所有无效cookie +router.delete("/invalid-cookies", async (req, res) => { + try { + keyManager.clearAllInvalidCookies(); + + return res.json({ + success: true, + message: '所有无效cookie已清除' + }); + } catch (error) { + logger.error('清除所有无效cookie失败:', error); + return res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// 批量添加无效cookie +router.post("/invalid-cookies", async (req, res) => { + try { + const { invalidCookies } = req.body; + + if (!Array.isArray(invalidCookies)) { + return res.status(400).json({ + success: false, + error: 'Invalid request', + message: 'invalidCookies必须是一个数组' + }); + } + + // 获取当前无效cookie集合 + const currentInvalidCookies = keyManager.getInvalidCookies(); + + // 添加新的无效cookie + for (const cookie of invalidCookies) { + if (typeof cookie === 'string' && cookie.trim()) { + currentInvalidCookies.add(cookie.trim()); + } + } + + // 保存到文件 + keyManager.saveInvalidCookiesToFile(); + + return res.json({ + success: true, + message: `已添加${invalidCookies.length}个无效cookie` + }); + } catch (error) { + logger.error('添加无效cookie失败:', error); + return res.status(500).json({ + error: 'Internal server error', + message: error.message + }); + } +}); + +// 获取可用模型列表 +router.get("/models", async (req, res) => { + try{ + let bearerToken = req.headers.authorization?.replace('Bearer ', ''); + + // 使用keyManager获取实际的cookie + let authToken = keyManager.getCookieForApiKey(bearerToken); + + if (authToken && authToken.includes('%3A%3A')) { + authToken = authToken.split('%3A%3A')[1]; + } + else if (authToken && authToken.includes('::')) { + authToken = authToken.split('::')[1]; + } + + const checksum = req.headers['x-cursor-checksum'] + ?? process.env['x-cursor-checksum'] + ?? generateCursorChecksum(authToken.trim()); + //const cursorClientVersion = "0.45.11" + const cursorClientVersion = "0.50.4"; + + const availableModelsResponse = await fetch("https://api2.cursor.sh/aiserver.v1.AiService/AvailableModels", { + method: 'POST', + headers: { + 'accept-encoding': 'gzip', + 'authorization': `Bearer ${authToken}`, + 'connect-protocol-version': '1', + 'content-type': 'application/proto', + 'user-agent': 'connect-es/1.6.1', + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'Host': 'api2.cursor.sh', + }, + }) + const data = await availableModelsResponse.arrayBuffer(); + const buffer = Buffer.from(data); + try{ + const models = $root.AvailableModelsResponse.decode(buffer).models; + + // 生成带前缀的模型列表 + const prefixedModels = models.map(model => ({ + id: `[auto]-${model.name}`, + created: Date.now(), + object: 'model', + owned_by: 'cursor' + })); + + // 合并原始模型和带前缀的模型 + const combinedModels = models.map(model => ({ + id: model.name, + created: Date.now(), + object: 'model', + owned_by: 'cursor' + })).concat(prefixedModels); + + return res.json({ + object: "list", + data: combinedModels + }) + } catch (error) { + const text = buffer.toString('utf-8'); + throw new Error(text); + } + } + catch (error) { + logger.error(error); + return res.status(500).json({ + error: 'Internal server error', + }); + } +}) + + +router.post('/chat/completions', async (req, res) => { + // 检查请求体是否存在 + if (!req.body) { + return res.status(400).json({ + error: '请求体不能为空', + }); + } + + // 检查模型属性是否存在 + if (!req.body.model) { + return res.status(400).json({ + error: '缺少必要参数: model', + }); + } + + // 检查未支持的模型和流式传输 (对原始模型进行检查) + if (typeof req.body.model === 'string' && req.body.model.replace('[auto]-', '').startsWith('o1-') && req.body.stream) { + return res.status(400).json({ + error: 'Model not supported stream', + }); + } + + try { + const { model, messages, stream = false } = req.body; + let extractedStopTokens = []; + let processedMessages = JSON.parse(JSON.stringify(messages)); // 复制一份,避免修改原始请求体 + let foundStopStringPattern = false; + + let actualModel = model; // 实际发送给Cursor的模型名称 + const autoPrefix = '[auto]-'; + + // 检查并处理带前缀的模型 + if (typeof model === 'string' && model.startsWith(autoPrefix)) { + actualModel = model.substring(autoPrefix.length); // 移除前缀 + logger.info(`检测到预定模板模型: ${model}, 实际使用模型: ${actualModel}`); + + // 定义模板和随机标签 + const template = ` +<|Stop-String|>::::::::::::<|Stop-String|> +###Please ensure to output the following stop string wrapped in xml tag {{random}} at the end of each reply: + +Ending this round of conversation: Ten, nine, eight, seven, six, five, four, three, two, one. This round of replies has been perfectly completed! +`; + const tags = ['', '', '', '', '', '', '']; + + // 1. 随机选择一个标签,确保本次请求中所有注入都使用这一个 + const randomTag = tags[Math.floor(Math.random() * tags.length)]; + + // 2. 构建注入系统消息的指令 + const processedTemplate = template.replace('{{random}}', randomTag); + + // 3. 构建追加到assistant消息的声明 + const declarationString = ` +${randomTag} +Ending this round of conversation: Ten, nine, eight, seven, six, five, four, three, two, one. This round of replies has been perfectly completed! +${randomTag.replace('<', ' m.role === 'system'); + if (systemMessage) { + systemMessage.content = (systemMessage.content || '') + '\n\n' + processedTemplate; + logger.debug('已将模板追加到现有系统消息'); + } else { + processedMessages.unshift({ role: 'system', content: processedTemplate }); + logger.debug('未找到系统消息,已创建并添加新的系统消息'); + } + + // 5. 将声明追加到最后5条assistant消息 + let assistantMessagesToModify = 5; + for (let i = processedMessages.length - 1; i >= 0 && assistantMessagesToModify > 0; i--) { + if (processedMessages[i].role === 'assistant') { + processedMessages[i].content = (processedMessages[i].content || '') + declarationString; + assistantMessagesToModify--; + } + } + logger.debug(`已将声明追加到 ${5 - assistantMessagesToModify} 条assistant消息`); + + // 在处理完预设模板后,确保foundStopStringPattern为false,以便后续的停止字符串提取逻辑能够运行在processedMessages上 + foundStopStringPattern = false; // 重置foundStopStringPattern + } + + // 从messages中提取停止字符串并移除标记 (现在会作用于可能修改过的processedMessages) + for (const message of processedMessages) { + let content = message.content || ''; + const stopStringPattern = /<\|Stop-String\|>(.*?)<\|Stop-String\|>/s; + const match = content.match(stopStringPattern); + + if (match && match[1] && !foundStopStringPattern) { + // 只处理找到的第一个匹配 + const stopStrings = match[1].split('::').map(s => s.trim()).filter(s => s.length > 0); + extractedStopTokens = stopStrings; + foundStopStringPattern = true; + + // 移除content中的停止字符串标记 + content = content.replace(stopStringPattern, '').trim(); + + // 如果移除后内容为空,考虑删除该消息或保留角色信息 + if (content === '') { + // Option 1: Keep role but empty content, prevents removing valid turn. + message.content = ''; // 直接修改processedMessages中的对象 + // Option 2: Remove message entirely if content becomes empty. + // continue; // 这需要重建processedMessages数组 + } else { + message.content = content; // 直接修改processedMessages中的对象 + } + } else if (foundStopStringPattern) { + // 如果已经找到模式,直接使用原始内容,不再进行移除操作 + // message.content 保持不变 + } + } + + // 如果没有找到停止字符串模式,返回错误 (现在只有在没有[auto]-前缀模型且没有找到标记时才会触发) + // 对于[auto]-前缀模型,由于模板中包含了标记,foundStopStringPattern会被设置为true + if (!foundStopStringPattern) { + return res.json({ + id: `chatcmpl-${uuidv4()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: model || 'unknown', + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: '预设错误,请使用指定预设结构', + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }); + } + + // 使用提取的停止字符串 + const stopTokens = extractedStopTokens; + + // 记录本次回复的所有停止字符串 + logger.info(`本次回复使用的停止字符串: [${stopTokens.join(', ')}]`); + + let bearerToken = req.headers.authorization?.replace('Bearer ', ''); + + // 使用keyManager获取实际的cookie + let authToken = keyManager.getCookieForApiKey(bearerToken); + // 保存原始cookie,用于后续可能的错误处理 + const originalAuthToken = authToken; + //console.log('原始cookie:', originalAuthToken); + + if (authToken && authToken.includes('%3A%3A')) { + authToken = authToken.split('%3A%3A')[1]; + } + else if (authToken && authToken.includes('::')) { + authToken = authToken.split('::')[1]; + } + + // 使用processedMessages (可能包含追加的模板) + if (!processedMessages || processedMessages.length === 0 || !authToken) { + return res.status(400).json({ + error: 'Invalid request. Messages should be a non-empty array and authorization is required', + }); + } + + const checksum = req.headers['x-cursor-checksum'] + ?? process.env['x-cursor-checksum'] + ?? generateCursorChecksum(authToken.trim()); + + const sessionid = uuidv5(authToken, uuidv5.DNS); + const clientKey = generateHashed64Hex(authToken); + const cursorClientVersion = "0.50.4"; + + // 在请求聊天接口前,依次调用6个接口 + if (process.env.USE_OTHERS === 'true') { + try{ + others(authToken, clientKey, checksum, cursorClientVersion, sessionid).then( () => { + logger.info("其它接口异步调用成功"); + }); + } catch (error) { + logger.error(error.message); + } + } + + // 使用实际发送给Cursor的模型名称 (不带前缀) + logger.info('发送给Cursor的完整消息上下文:', JSON.stringify(processedMessages, null, 2)); + logger.info('发送给Cursor的实际模型:', actualModel); + const cursorBody = generateCursorBody(processedMessages, actualModel); + + // 添加代理支持 + const dispatcher = config.proxy && config.proxy.enabled + ? new ProxyAgent(config.proxy.url, { allowH2: true }) + : new Agent({ allowH2: true }); + + // 根据.env配置决定是否使用TLS代理 + const useTlsProxy = process.env.USE_TLS_PROXY === 'true'; + + // 创建AbortController用于能够中止请求 + const controller = new AbortController(); + const signal = controller.signal; + + let response; + + try { + if (useTlsProxy) { + // 使用JA3指纹伪造代理服务器 + logger.info(`使用TLS代理服务器`); + response = await fetch('http://localhost:8080/proxy', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + url: 'https://api2.cursor.sh/aiserver.v1.ChatService/StreamUnifiedChatWithTools', + method: 'POST', + headers: { + 'authorization': `Bearer ${authToken}`, + 'connect-accept-encoding': 'gzip', + 'connect-content-encoding': 'gzip', + 'connect-protocol-version': '1', + 'content-type': 'application/connect+proto', + 'user-agent': 'connect-es/1.6.1', + 'x-amzn-trace-id': `Root=${uuidv4()}`, + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'x-request-id': uuidv4(), + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + body: cursorBody, + stream: true // 启用流式响应 + }), + timeout: { + connect: 5000, + read: 30000 + }, + signal // 添加AbortController的signal + }); + } else { + // 直接调用API,不使用TLS代理 + logger.info('不使用TLS代理服务器,直接请求API'); + response = await fetch('https://api2.cursor.sh/aiserver.v1.ChatService/StreamUnifiedChatWithTools', { + method: 'POST', + headers: { + 'authorization': `Bearer ${authToken}`, + 'connect-accept-encoding': 'gzip', + 'connect-content-encoding': 'gzip', + 'connect-protocol-version': '1', + 'content-type': 'application/connect+proto', + 'user-agent': 'connect-es/1.6.1', + 'x-amzn-trace-id': `Root=${uuidv4()}`, + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Shanghai', + 'x-ghost-mode': 'true', + 'x-request-id': uuidv4(), + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + body: cursorBody, + dispatcher: dispatcher, + timeout: { + connect: 5000, + read: 30000 + }, + signal // 添加AbortController的signal + }); + } + } catch (fetchError) { + logger.error(`Fetch错误: ${fetchError.message}`); + + // 处理连接超时错误 + const isConnectTimeout = fetchError.cause && + (fetchError.cause.code === 'UND_ERR_CONNECT_TIMEOUT' || + fetchError.message.includes('Connect Timeout Error')); + + // 构建错误响应 + const errorMessage = isConnectTimeout + ? `⚠️ 连接超时 ⚠️\\n\\n无法连接到API服务器(api2.cursor.sh),请检查您的网络连接或尝试使用代理。` + : `⚠️ 请求失败 ⚠️\\n\\n错误: ${fetchError.message}`; + + if (stream) { + // 流式响应格式的错误 + const responseId = `chatcmpl-${uuidv4()}`; + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model || 'unknown', + choices: [ + { + index: 0, + delta: { + content: errorMessage, + }, + }, + ], + })}\n\n` + ); + res.write('data: [DONE]\n\n'); + res.end(); + } else { + // 非流式响应格式的错误 + res.json({ + id: `chatcmpl-${uuidv4()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: req.body.model || 'unknown', + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: errorMessage, + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }); + } + return; // 重要:提前返回 + } + + // 处理响应 + if (stream) { + // 如果存在此 API Key 的旧请求,则中止它 + if (bearerToken && activeRequestControllers.has(bearerToken)) { + const oldController = activeRequestControllers.get(bearerToken); + logger.info(`API Key [${bearerToken}] 的新流式请求到达,正在中止旧请求...`); + oldController.abort(); + // activeRequestControllers.delete(bearerToken); // 旧的会被新的覆盖,或在旧请求的清理逻辑中移除 + } + // 存储当前请求的 AbortController + if (bearerToken) { + activeRequestControllers.set(bearerToken, controller); + } + + // 清理当前请求的 AbortController 的辅助函数 + const cleanupCurrentController = () => { + if (bearerToken && activeRequestControllers.get(bearerToken) === controller) { + activeRequestControllers.delete(bearerToken); + logger.debug(`API Key [${bearerToken}] 的流式请求处理完毕,已清理 AbortController。`); + } + }; + res.on('finish', cleanupCurrentController); // 响应正常结束时清理 + res.on('close', cleanupCurrentController); // 响应因任何原因关闭时清理 (包括客户端断开) + + res.setHeader('Content-Type', 'text/event-stream'); + res.setHeader('Cache-Control', 'no-cache'); + res.setHeader('Connection', 'keep-alive'); + + // 监听客户端断开连接事件 + req.on('close', () => { + if (!responseEnded) { + logger.warn(`客户端已断开连接 (API Key: [${bearerToken}]), 正在中止对Cursor服务端的请求...`); + controller.abort(); + responseEnded = true; + // cleanupCurrentController 会在 res 'close' 时被调用 + } + }); + + const responseId = `chatcmpl-${uuidv4()}`; + + let isThinking_status = 0; //0为没有思考,1为处于思考状态 + try { + let responseEnded = false; // 添加标志,标记响应是否已结束 + let hasWrittenThinkingStart = false; // 标记是否已发送thinking开始标签 + let hasWrittenThinkingEnd = false; // 标记是否已发送thinking结束标签 + let hasWrittenContent = false; // 标记是否已发送content + let accumulatedThinking = ''; // 累积thinking内容 + let accumulatedContent = ''; // 累积content内容 + + for await (const chunk of response.body) { + // 如果响应已结束,不再处理后续数据 + if (responseEnded) { + continue; + } + + let result = {}; + try { + result = chunkToUtf8String(chunk); + } catch (error) { + logger.error('解析响应块失败:', error); + // 提供默认的空结果,避免后续处理出错 + result = { + isThink: false, + thinkingContent: '', + content: '', + error: `解析错误: ${error.message}` + }; + } + + // 检查是否返回了错误对象 + if (result && typeof result === 'object' && result.error) { + // 检查是否包含特定的无效cookie错误信息 + const errorStr = typeof result.error === 'string' ? result.error : JSON.stringify(result.error); + + // 处理错误并获取结果 + const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken); + + // 如果是需要移除的cookie,从API Key中移除 + if (errorResult.shouldRemoveCookie) { + const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken); + logger.info(`Cookie移除${removed ? '成功' : '失败'}`); + + // 如果成功移除,在错误消息中添加明确提示 + if (removed) { + errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\\n\\n${errorResult.message}`; + } + } + + // 返回错误信息给客户端,作为assistant消息 + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: errorResult.message, + }, + }, + ], + })}\n\n` + ); + + res.write('data: [DONE]\n\n'); + responseEnded = true; // 标记响应已结束 + break; // 跳出循环,不再处理后续数据 + } + + // 处理thinking内容 + if (result.isThink && result.thinkingContent && result.thinkingContent.length > 0) { + // 累积thinking内容 + accumulatedThinking += result.thinkingContent; + + // 如果没有发送thinking开始标记,则发送 + if (!hasWrittenThinkingStart) { + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: "\\n", + }, + }, + ], + })}\n\n` + ); + hasWrittenThinkingStart = true; + } + + // 发送accumulated thinking内容片段 + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: result.thinkingContent, + }, + }, + ], + })}\n\n` + ); + } + + // 处理常规内容 + if (result.content && result.content.length > 0) { + // 累积content内容 + accumulatedContent += result.content; + + // 如果已经有thinking内容,且尚未发送thinking结束标记,则发送 + if (hasWrittenThinkingStart && !hasWrittenThinkingEnd) { + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: "\\n\\n", + }, + }, + ], + })}\n\n` + ); + hasWrittenThinkingEnd = true; + } + + // 检查是否遇到停止字符串 + let shouldStop = false; + let contentToSend = result.content; + + // 检查停止字符串 + if (stopTokens.length > 0) { + for (const stopToken of stopTokens) { + const stopIndex = accumulatedContent.indexOf(stopToken); + if (stopIndex !== -1) { + // 记录检测到停止字符串的日志 + logger.info(`检测到停止字符串: "${stopToken}" 在位置 ${stopIndex},累积内容长度: ${accumulatedContent.length}`); + + // 如果找到停止字符串,立即停止,不管停止字符串在哪个chunk中 + const lastChunkIndex = accumulatedContent.length - result.content.length; + + if (stopIndex >= lastChunkIndex) { + // 停止字符串在当前块中,只发送到停止字符串之前的内容 + contentToSend = result.content.substring(0, stopIndex - lastChunkIndex); + } else { + // 停止字符串在之前的chunks中,不发送当前chunk的任何内容 + contentToSend = ''; + } + + shouldStop = true; + break; + } + } + } + + // 只有当有内容要发送时才发送 + if (contentToSend.length > 0) { + // 发送content内容 + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: contentToSend, + }, + }, + ], + })}\n\n` + ); + hasWrittenContent = true; + } + + // 如果需要停止,发送[DONE]并结束响应 + if (shouldStop) { + res.write('data: [DONE]\n\n'); + res.end(); + responseEnded = true; + + try { + controller.abort(); + } catch (abortError) { + logger.error('中止Cursor连接时出错 (停止字符串):', abortError); + } + // cleanupCurrentController 会在 res 'finish' 或 'close' 时被调用 + break; + } + } + } + + // 处理结束逻辑:确保thinking标签被正确关闭 + if (!responseEnded) { + // 如果有thinking内容但没有发送结束标记,则发送 + if (hasWrittenThinkingStart && !hasWrittenThinkingEnd) { + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: "\\n\\n", + }, + }, + ], + })}\n\n` + ); + } + + res.write('data: [DONE]\n\n'); + res.end(); + // cleanupCurrentController 会在 res 'finish' 时被调用 + } + } catch (streamError) { + // 区分正常的中止操作和真正的错误 + if (streamError.name === 'AbortError') { + logger.info(`流处理被中止 (API Key: [${bearerToken}]), 原因可能为: 新请求覆盖, 客户端断开, 或停止字符串触发。`); + } else { + logger.error(`Stream error (API Key: [${bearerToken}]):`, streamError); + } + + if (!res.writableEnded) { + if (streamError.name === 'AbortError') { + // AbortError 通常意味着我们主动中止,响应可能已处理或将由 'close' 事件处理 + // 但为确保万无一失,如果响应未结束,尝试结束它 + if (!res.headersSent) { // 避免在已发送头后再次发送 + res.status(500).json({ error: 'Stream aborted' }); + } else { + res.end(); //尝试结束流 + } + return; // AbortError 处理完毕 + } else if (streamError.name === 'TimeoutError') { + // 将超时错误作为assistant消息发送 + const errorMessage = `⚠️ 请求超时 ⚠️\\n\\n错误:服务器响应超时,请稍后重试。`; + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: errorMessage, + }, + }, + ], + })}\n\n` + ); + } else { + // 将处理错误作为assistant消息发送 + const errorMessage = `⚠️ 处理错误 ⚠️\\n\\n错误:流处理出错,请稍后重试。\\n\\n${streamError.message || ''}`; + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model, + choices: [ + { + index: 0, + delta: { + content: errorMessage, + }, + }, + ], + })}\n\n` + ); + } + res.write('data: [DONE]\n\n'); + res.end(); + } + } + } else { + try { + let text = ''; + let thinkingText = ''; + let hasThinking = false; + let responseEnded = false; // 添加标志,标记响应是否已结束 + + for await (const chunk of response.body) { + // 如果响应已结束,不再处理后续数据 + if (responseEnded) { + continue; + } + + let result = {}; + try { + result = chunkToUtf8String(chunk); + } catch (error) { + logger.error('非流式响应解析块失败:', error); + // 提供默认的空结果,避免后续处理出错 + result = { + thinkingContent: '', + content: '', + error: `解析错误: ${error.message}` + }; + } + // 输出完整的result内容和类型,便于调试 + //console.log("收到的非流式响应:", typeof result, result && typeof result === 'object' ? JSON.stringify(result) : result); + + // 检查是否返回了错误对象 + if (result && typeof result === 'object' && result.error) { + //console.error('检测到错误响应:', result.error); + + // 检查是否包含特定的无效cookie错误信息 + const errorStr = typeof result.error === 'string' ? result.error : JSON.stringify(result.error); + + // 处理错误并获取结果 + const errorResult = handleCursorError(errorStr, bearerToken, originalAuthToken); + + // 如果是需要移除的cookie,从API Key中移除 + if (errorResult.shouldRemoveCookie) { + const removed = keyManager.removeCookieFromApiKey(bearerToken, originalAuthToken); + logger.info(`Cookie移除${removed ? '成功' : '失败'}`); + + // 如果成功移除,在错误消息中添加明确提示 + if (removed) { + errorResult.message = `⚠️ 目前Cookie已从API Key中移除 ⚠️\\n\\n${errorResult.message}`; + } + } + + // 无效cookie错误,格式化为assistant消息 + res.json({ + id: `chatcmpl-${uuidv4()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model, + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: errorResult.message, + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }); + + responseEnded = true; // 标记响应已结束 + break; // 跳出循环,不再处理后续数据 + } + + // 处理thinking内容 + if (result.thinkingContent && result.thinkingContent.length > 0) { + thinkingText += result.thinkingContent; + hasThinking = true; + } + + // 处理正常文本内容 + if (result.content && typeof result.content === 'string') { + text += result.content; + } + } + + // 只有在响应尚未结束的情况下,才处理和返回结果 + if (!responseEnded) { + // 对解析后的字符串进行进一步处理 + text = text.replace(/^.*<\|END_USER\|>/s, ''); + text = text.replace(/^\n[a-zA-Z]?/, '').trim(); + + // 检查停止字符串并截断内容 + if (stopTokens.length > 0) { + for (const stopToken of stopTokens) { + const stopIndex = text.indexOf(stopToken); + if (stopIndex !== -1) { + // 记录检测到停止字符串的日志 + logger.info(`非流式响应检测到停止字符串: "${stopToken}" 在位置 ${stopIndex}`); + + // 截断到停止字符串之前的内容 + text = text.substring(0, stopIndex); + break; + } + } + } + + // 如果存在thinking内容,添加标签 + let finalContent = text; + if (hasThinking && thinkingText.length > 0) { + finalContent = `\\n${thinkingText}\\n\\n${text}`; + } + + res.json({ + id: `chatcmpl-${uuidv4()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model, + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: finalContent, + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }); + } + } catch (error) { + logger.error('Non-stream error:', error); + // 确保在发送错误信息前检查响应是否已结束 + if (!res.headersSent) { + if (error.name === 'TimeoutError') { + // 使用统一的错误格式 + const errorMessage = `⚠️ 请求超时 ⚠️\\n\\n错误:服务器响应超时,请稍后重试。`; + return res.json({ + id: `chatcmpl-${uuidv4()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: req.body.model || 'unknown', + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: errorMessage, + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }); + } + throw error; + } + } + } + } catch (error) { + logger.error('Error:', error); + if (!res.headersSent) { + const errorText = error.name === 'TimeoutError' ? '请求超时' : '服务器内部错误'; + + if (req.body.stream) { + // 流式响应格式的错误 + const responseId = `chatcmpl-${uuidv4()}`; + // 添加清晰的错误提示 + const errorMessage = `⚠️ 请求失败 ⚠️\\n\\n错误:${errorText},请稍后重试。\\n\\n${error.message || ''}`; + res.write( + `data: ${JSON.stringify({ + id: responseId, + object: 'chat.completion.chunk', + created: Math.floor(Date.now() / 1000), + model: req.body.model || 'unknown', + choices: [ + { + index: 0, + delta: { + content: errorMessage, + }, + }, + ], + })}\n\n` + ); + res.write('data: [DONE]\n\n'); + res.end(); + } else { + // 非流式响应格式的错误 + // 添加清晰的错误提示 + const errorMessage = `⚠️ 请求失败 ⚠️\\n\\n错误:${errorText},请稍后重试。\\n\\n${error.message || ''}`; + res.json({ + id: `chatcmpl-${uuidv4()}`, + object: 'chat.completion', + created: Math.floor(Date.now() / 1000), + model: req.body.model || 'unknown', + choices: [ + { + index: 0, + message: { + role: 'assistant', + content: errorMessage, + }, + finish_reason: 'stop', + }, + ], + usage: { + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + }, + }); + } + } + } +}); + +// 触发Cookie刷新 +router.post("/refresh-cookies", async (req, res) => { + try { + // 如果已经有刷新进程在运行,则返回错误 + if (refreshStatus.isRunning) { + return res.status(409).json({ + success: false, + message: '已有刷新进程在运行,请等待完成后再试' + }); + } + + // 获取请求参数 + const apiKey = req.query.apiKey || ''; + + // 重置刷新状态 + refreshStatus = { + isRunning: true, + status: 'running', + message: '正在启动刷新进程...', + startTime: new Date(), + endTime: null, + error: null + }; + + logger.info(`收到刷新Cookie请求,API Key: ${apiKey || '所有'}`); + + // 构建命令行参数 + const args = []; + if (apiKey) { + args.push(apiKey); + } + + // 获取auto-refresh-cookies.js的绝对路径 + const scriptPath = path.resolve(__dirname, '../../auto-refresh-cookies.js'); + + // 启动子进程执行刷新脚本 + const refreshProcess = spawn('node', [scriptPath, ...args], { + stdio: ['ignore', 'pipe', 'pipe'] + }); + + // 收集输出 + let output = ''; + + refreshProcess.stdout.on('data', (data) => { + const text = data.toString(); + output += text; + logger.info(`刷新进程输出: ${text}`); + + // 更新状态消息 + if (text.includes('开始自动刷新')) { + refreshStatus.message = '正在刷新Cookie...'; + } else if (text.includes('刷新结果:')) { + refreshStatus.message = text.trim(); + } + }); + + refreshProcess.stderr.on('data', (data) => { + const text = data.toString(); + output += text; + logger.error(`刷新进程错误: ${text}`); + + // 更新错误信息 + refreshStatus.error = text.trim(); + refreshStatus.message = `发生错误: ${text.trim()}`; + }); + + refreshProcess.on('close', (code) => { + logger.info(`刷新进程退出,代码: ${code}`); + + refreshStatus.isRunning = false; + refreshStatus.endTime = new Date(); + + if (code === 0) { + refreshStatus.status = 'completed'; + + // 提取成功信息 + const successMatch = output.match(/成功刷新 (\d+) 个/); + if (successMatch) { + refreshStatus.message = `成功刷新 ${successMatch[1]} 个API Key的Cookie`; + } else { + refreshStatus.message = '刷新完成'; + } + + // 子进程执行完成后,重新初始化API Keys来加载新的Cookie + try { + const keyManager = require('../utils/keyManager'); + logger.info('子进程刷新Cookie完成,重新初始化主进程中的API Keys...'); + keyManager.initializeApiKeys(); + logger.info('主进程API Keys重新加载完成'); + } catch (initError) { + logger.error('重新初始化API Keys失败:', initError); + } + } else { + refreshStatus.status = 'failed'; + refreshStatus.message = refreshStatus.error || '刷新失败,请查看服务器日志'; + } + }); + + // 立即返回响应,不等待刷新完成 + return res.json({ + success: true, + message: '刷新请求已接受,正在后台处理' + }); + } catch (error) { + logger.error('触发刷新Cookie失败:', error); + + // 更新刷新状态 + refreshStatus.isRunning = false; + refreshStatus.status = 'failed'; + refreshStatus.endTime = new Date(); + refreshStatus.error = error.message; + refreshStatus.message = `触发刷新失败: ${error.message}`; + + return res.status(500).json({ + success: false, + message: `触发刷新失败: ${error.message}` + }); + } +}); + +// 查询Cookie刷新状态 +router.get("/refresh-status", (req, res) => { + try { + // 返回当前刷新状态 + return res.json({ + success: true, + data: { + ...refreshStatus, + isRunning: refreshStatus.isRunning || false, + status: refreshStatus.status || 'unknown', + message: refreshStatus.message || '未触发刷新', + startTime: refreshStatus.startTime || null, + endTime: refreshStatus.endTime || null + } + }); + } catch (error) { + logger.error('获取刷新状态失败:', error); + return res.status(500).json({ + success: false, + message: `获取刷新状态失败: ${error.message}` + }); + } +}); + +// 生成获取Cookie的链接 +router.post('/generate-cookie-link', async (req, res) => { + try { + // 验证管理员权限 + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return res.status(401).json({ + success: false, + message: '未提供认证token' + }); + } + + const token = authHeader.split(' ')[1]; + const authResult = admin.verifyToken(token); + + if (!authResult.success) { + return res.status(401).json({ + success: false, + message: '认证失败' + }); + } + + // 生成UUID和PKCE验证器 + const uuid = uuidv4(); + const verifier = crypto.randomBytes(32).toString('base64url'); + const challenge = crypto.createHash('sha256').update(verifier).digest('base64url'); + + // 生成登录链接 + const loginUrl = `https://www.cursor.com/ja/loginDeepControl?challenge=${challenge}&uuid=${uuid}&mode=login`; + + // 记录请求信息 + pendingCookieRequests.set(uuid, { + uuid, + verifier, + status: 'waiting', + created: Date.now(), + apiKey: req.body.apiKey || '', // 目标API Key,空字符串表示所有API Key + lastCheck: Date.now(), + cookie: null + }); + + // 设置60分钟后自动清理 + setTimeout(() => { + if (pendingCookieRequests.has(uuid)) { + pendingCookieRequests.delete(uuid); + } + }, 60 * 60 * 1000); + + return res.json({ + success: true, + url: loginUrl, + uuid: uuid + }); + } catch (error) { + logger.error('生成Cookie链接失败:', error); + return res.status(500).json({ + success: false, + message: error.message + }); + } +}); + +// 查询Cookie获取状态 +router.get('/check-cookie-status', async (req, res) => { + try { + const { uuid } = req.query; + + if (!uuid || !pendingCookieRequests.has(uuid)) { + return res.json({ + success: false, + status: 'failed', + message: '无效的UUID或请求已过期' + }); + } + + const request = pendingCookieRequests.get(uuid); + request.lastCheck = Date.now(); + + // 检查状态 + if (request.status === 'waiting') { + // 检查Cursor API获取token + try { + const apiUrl = `https://api2.cursor.sh/auth/poll?uuid=${uuid}&verifier=${request.verifier}`; + const response = await fetch(apiUrl, { + method: 'GET', + headers: { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.6834.210 Safari/537.36', + 'Accept': '*/*', + 'Origin': 'vscode-file://vscode-app', + 'x-ghost-mode': 'true' + }, + timeout: 5000 + }); + + if (response.ok) { + const data = await response.json(); + + if (data && data.accessToken) { + // 获取到了Cookie + request.cookie = data.accessToken; + request.status = 'success'; + + // 将Cookie添加到目标API Key + let message = ''; + + if (request.apiKey) { + // 添加到特定API Key + const apiKey = request.apiKey; + const cookies = keyManager.getAllCookiesForApiKey(apiKey) || []; + cookies.push(request.cookie); + keyManager.addOrUpdateApiKey(apiKey, cookies); + message = `Cookie已添加到API Key: ${apiKey}`; + } else { + // 添加到所有API Key + const apiKeys = keyManager.getAllApiKeys(); + for (const apiKey of apiKeys) { + const cookies = keyManager.getAllCookiesForApiKey(apiKey) || []; + cookies.push(request.cookie); + keyManager.addOrUpdateApiKey(apiKey, cookies); + } + message = `Cookie已添加到所有API Key,共${apiKeys.length}个`; + } + + // 完成后从等待列表中移除 + pendingCookieRequests.delete(uuid); + + return res.json({ + success: true, + message: message + }); + } + } + + // 如果没有获取到Cookie,继续等待 + return res.json({ + success: false, + status: 'waiting' + }); + + } catch (error) { + logger.error('查询Cursor API失败:', error); + // 发生错误但继续等待,不改变状态 + return res.json({ + success: false, + status: 'waiting', + message: '轮询过程中出现错误,继续等待' + }); + } + } else if (request.status === 'success') { + // 已成功,返回结果 + const message = request.apiKey + ? `Cookie已添加到API Key: ${request.apiKey}` + : `Cookie已添加到所有API Key`; + + // 完成后从等待列表中移除 + pendingCookieRequests.delete(uuid); + + return res.json({ + success: true, + message: message + }); + } else { + // 失败 + pendingCookieRequests.delete(uuid); + return res.json({ + success: false, + status: 'failed', + message: '获取Cookie失败' + }); + } + } catch (error) { + logger.error('检查Cookie状态失败:', error); + return res.status(500).json({ + success: false, + status: 'failed', + message: error.message + }); + } +}); + +// 获取日志API +router.get("/logs", (req, res) => { + try { + // 获取查询参数 + const level = req.query.level; + const search = req.query.search; + const page = parseInt(req.query.page) || 1; + const pageSize = parseInt(req.query.pageSize) || 100; + const startTime = req.query.startTime; + const endTime = req.query.endTime; + + // 过滤参数 + const filter = { + level, + search, + page, + pageSize, + startTime, + endTime + }; + + // 获取日志 + const logs = logger.getLogs(filter); + + return res.json({ + success: true, + data: logs + }); + } catch (error) { + logger.error('获取日志失败:', error); + return res.status(500).json({ + success: false, + message: `获取日志失败: ${error.message}` + }); + } +}); + +// 清除内存日志 +router.delete("/logs", (req, res) => { + try { + logger.clearMemoryLogs(); + return res.json({ + success: true, + message: '日志已清除' + }); + } catch (error) { + logger.error('清除日志失败:', error); + return res.status(500).json({ + success: false, + message: `清除日志失败: ${error.message}` + }); + } +}); +async function others(authToken, clientKey, checksum, cursorClientVersion, sessionid){ + try { + // 定义所有API端点配置 + const endpoints = [ + { + url: 'https://api2.cursor.sh/aiserver.v1.AiService/CheckFeatureStatus', + method: 'POST', + headers: { + 'accept-encoding': 'gzip', + 'authorization': `Bearer ${authToken}`, + 'connect-protocol-version': '1', + 'content-type': 'application/proto', + 'user-agent': 'connect-es/1.6.1', + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'x-new-onboarding-completed': 'false', + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + body: '', // 实际长度为23字节 + timeout: { + connect: 5000, + read: 30000 + } + }, + { + url: 'https://api2.cursor.sh/aiserver.v1.AiService/AvailableDocs', + method: 'POST', + headers: { + 'authorization': `Bearer ${authToken}`, + 'connect-accept-encoding': 'gzip', + 'connect-protocol-version': '1', + 'content-type': 'application/proto', + 'user-agent': 'connect-es/1.6.1', + 'x-amzn-trace-id': `Root=${uuidv4()}`, + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'x-request-id': uuidv4(), + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + timeout: { + connect: 5000, + read: 30000 + } + }, + { + url: 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetTeams', + method: 'POST', + headers: { + 'accept-encoding': 'gzip', + 'authorization': `Bearer ${authToken}`, + 'connect-protocol-version': '1', + 'content-type': 'application/proto', + 'user-agent': 'connect-es/1.6.1', + 'x-amzn-trace-id': `Root=${uuidv4()}`, + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'x-new-onboarding-completed': 'false', + 'x-request-id': uuidv4(), + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + body: '', + timeout: { + connect: 5000, + read: 30000 + } + }, + { + url: 'https://api2.cursor.sh/auth/full_stripe_profile', + method: 'GET', + headers: { + 'Host': 'api2.cursor.sh', + 'Connection': 'keep-alive', + 'Authorization': `Bearer ${authToken}`, + 'x-new-onboarding-completed': 'false', + 'x-ghost-mode': 'true', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/0.49.4 Chrome/132.0.6834.210 Electron/34.3.4 Safari/537.36', + 'Accept': '*/*', + 'Origin': 'vscode-file://vscode-app', + 'Sec-Fetch-Site': 'cross-site', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + 'Accept-Encoding': 'gzip, deflate, br, zstd', + 'Accept-Language': 'zh-CN' + }, + timeout: { + connect: 5000, + read: 30000 + } + }, + { + url: 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests', + method: 'POST', + headers: { + 'accept-encoding': 'gzip', + 'authorization': `Bearer ${authToken}`, + 'connect-protocol-version': '1', + 'content-type': 'application/proto', + 'user-agent': 'connect-es/1.6.1', + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'x-new-onboarding-completed': 'false', + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + body: '', + timeout: { + connect: 5000, + read: 30000 + } + }, + { + url: 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetHardLimit', + method: 'POST', + headers: { + 'accept-encoding': 'gzip', + 'authorization': `Bearer ${authToken}`, + 'connect-protocol-version': '1', + 'content-type': 'application/proto', + 'user-agent': 'connect-es/1.6.1', + 'x-client-key': clientKey, + 'x-cursor-checksum': checksum, + 'x-cursor-client-version': cursorClientVersion, + 'x-cursor-config-version': uuidv4(), + 'x-cursor-timezone': 'Asia/Tokyo', + 'x-ghost-mode': 'true', + 'x-new-onboarding-completed': 'false', + 'x-session-id': sessionid, + 'Host': 'api2.cursor.sh', + }, + body: '', + timeout: { + connect: 5000, + read: 30000 + } + } + ]; + + // 随机选择2-4个接口调用 + const minApis = 2; + const maxApis = 4; + const numApisToCall = Math.floor(Math.random() * (maxApis - minApis + 1)) + minApis; + + // 随机打乱数组并取前几个元素 + const shuffledEndpoints = [...endpoints].sort(() => 0.5 - Math.random()).slice(0, numApisToCall); + + // 使用Promise.allSettled确保即使一个请求失败也不会影响其他请求 + const results = await Promise.allSettled(shuffledEndpoints.map(async (endpoint) => { + try { + const response = await fetch(endpoint.url, { + method: endpoint.method, + headers: endpoint.headers, + body: endpoint.body || undefined, + timeout: endpoint.timeout + }); + + return { + url: endpoint.url, + status: response.status, + success: true + }; + } catch (error) { + // 记录单个请求的错误,但不中断整体流程 + logger.debug(`其它API调用失败 (${endpoint.url}): ${error.message}`); + return { + url: endpoint.url, + success: false, + error: error.message + }; + } + })); + + // 记录请求结果统计 + const successCount = results.filter(r => r.status === 'fulfilled' && r.value.success).length; + logger.debug(`其它API调用完成: 成功 ${successCount}/${results.length}`); + + return true; + } catch (error) { + // 记录整体错误,但不影响主流程 + logger.error(`others函数执行出错: ${error.message}`); + return false; + } +} +// 在文件末尾添加错误处理函数 +function handleCursorError(errorStr, bearerToken, originalAuthToken) { + let message = ''; + let shouldRemoveCookie = false; + + if (errorStr.includes('Not logged in')) { + // 更明确的错误日志 + if (originalAuthToken === bearerToken) { + logger.error(`检测到API Key "${bearerToken}" 中没有可用Cookie,正在尝试以向后兼容模式使用API Key本身`); + message = `错误:API Key "${bearerToken}" 中没有可用的Cookie。请添加有效的Cookie到此API Key,或使用其他有效的API Key。\\n\\n详细信息:${errorStr}`; + } else { + logger.error('检测到无效cookie:', originalAuthToken); + message = `错误:Cookie无效或已过期,请更新Cookie。\\n\\n详细信息:${errorStr}`; + } + shouldRemoveCookie = true; + } else if (errorStr.includes('You\'ve reached your trial request limit') || errorStr.includes('You\'ve reached the usage limit for free usage')) { + logger.error('检测到额度用尽cookie:', originalAuthToken); + message = `错误:Cookie使用额度已用完,请更换Cookie或等待刷新。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = true; + } else if (errorStr.includes('User is unauthorized')) { + logger.error('检测到未授权cookie:', originalAuthToken); + message = `错误:Cookie已被封禁或失效,请更换Cookie。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = true; + } else if (errorStr.includes('suspicious activity checks')) { + logger.error('检测到IP黑名单:', originalAuthToken); + message = `错误:IP可能被列入黑名单,请尝试更换网络环境或使用代理。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = false; + } else if (errorStr.includes('Too many computers')) { + logger.error('检测到账户暂时被封禁:', originalAuthToken); + message = `错误:账户因在多台设备登录而暂时被封禁,请稍后再试或更换账户。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = true; + } else if (errorStr.includes('Login expired') || errorStr.includes('login expired')) { + logger.error('检测到登录过期cookie:', originalAuthToken); + message = `错误:Cookie登录已过期,请更新Cookie。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = true; + } else if(errorStr.includes('your request has been blocked due to the use of a temporary email service for this account')) { + logger.error('检测到临时邮箱:', originalAuthToken); + message = `错误:请求被阻止,检测到临时邮箱服务,请更换邮箱。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = true; + } else if (errorStr.includes('Your request has been blocked as our system has detected suspicious activity from your account')) { + logger.error('检测到账户异常:', originalAuthToken); + message = `错误:请求被阻止,可能是假ban,多重试几次/更换cookie/更换设备。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = false; + } else { + // 非Cookie相关错误 + logger.error('检测到其他错误:', errorStr); + message = `错误:请求失败。\\n\\n详细信息:${errorStr}`; + shouldRemoveCookie = false; + } + + return { + message, + shouldRemoveCookie + }; +} + +module.exports = router; diff --git a/src/utils/cookieRefresher.js b/src/utils/cookieRefresher.js new file mode 100644 index 0000000000000000000000000000000000000000..c32fc9c02d07af80e44ce5e0abf013aadd48ecaf --- /dev/null +++ b/src/utils/cookieRefresher.js @@ -0,0 +1,818 @@ +const fs = require('fs'); +const path = require('path'); +const csv = require('csv-parser'); +const axios = require('axios'); +const AdmZip = require('adm-zip'); +const { Octokit } = require('@octokit/rest'); +const keyManager = require('./keyManager'); +const config = require('../config/config'); +const { extractCookiesFromCsv } = require('./extractCookieFromCsv'); +const logger = require('./logger'); + +// GitHub 仓库信息从环境变量中获取 +const GITHUB_OWNER = process.env.GITHUB_OWNER || 'liuw1535'; +const GITHUB_REPO = process.env.GITHUB_REPO || 'Cursor-Register'; +const GITHUB_TOKEN = process.env.GITHUB_TOKEN; // 需要在环境变量中设置 +const GITHUB_WORKFLOW_ID = process.env.GITHUB_WORKFLOW_ID || 'register.yml'; +const TRIGGER_WORKFLOW = process.env.TRIGGER_WORKFLOW === 'true'; + +// 下载目录 +const DOWNLOAD_DIR = path.join(__dirname, '../../downloads'); +const EXTRACT_DIR = path.join(__dirname, '../../extracted'); + +// 确保目录存在 +function ensureDirectoryExists(dir) { + if (!fs.existsSync(dir)) { + try { + fs.mkdirSync(dir, { recursive: true }); + logger.info(`创建目录成功: ${dir}`); + } catch (err) { + logger.error(`创建目录失败: ${dir}`, err); + throw err; + } + } +} + +// 触发 GitHub Actions 工作流 +async function triggerWorkflow() { + try { + if (!GITHUB_TOKEN) { + logger.error('未设置 GITHUB_TOKEN,无法触发工作流'); + return null; + } + + logger.info(`正在触发 GitHub Actions 工作流: ${GITHUB_WORKFLOW_ID}...`); + const octokit = new Octokit({ + auth: GITHUB_TOKEN + }); + + // 从环境变量获取工作流参数 + const number = process.env.REGISTER_NUMBER || '2'; + const maxWorkers = process.env.REGISTER_MAX_WORKERS || '1'; + const emailServer = process.env.REGISTER_EMAIL_SERVER || 'TempEmail'; + const ingestToOneapi = process.env.REGISTER_INGEST_TO_ONEAPI === 'true'; + const uploadArtifact = process.env.REGISTER_UPLOAD_ARTIFACT !== 'false'; // 默认为true + const useConfigFile = process.env.REGISTER_USE_CONFIG_FILE !== 'false'; // 默认为true + const emailConfigs = process.env.REGISTER_EMAIL_CONFIGS || '[]'; + + logger.info(`工作流参数: number=${number}, maxWorkers=${maxWorkers}, emailServer=${emailServer}, ingestToOneapi=${ingestToOneapi}, uploadArtifact=${uploadArtifact}, useConfigFile=${useConfigFile}`); + + // 获取触发前的最新工作流ID,用于后续识别新触发的工作流 + const { data: beforeWorkflowRuns } = await octokit.actions.listWorkflowRuns({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + workflow_id: GITHUB_WORKFLOW_ID, + per_page: 1 + }); + + const latestWorkflowIdBefore = beforeWorkflowRuns.workflow_runs && beforeWorkflowRuns.workflow_runs.length > 0 + ? beforeWorkflowRuns.workflow_runs[0].id + : 0; + + logger.info(`触发前最新工作流ID: ${latestWorkflowIdBefore}`); + + // 触发工作流 + const response = await octokit.actions.createWorkflowDispatch({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + workflow_id: GITHUB_WORKFLOW_ID, + ref: 'main', // 默认使用 main 分支,可以根据需要修改 + inputs: { + number: number, + max_workers: maxWorkers, + email_server: emailServer, + ingest_to_oneapi: ingestToOneapi.toString(), + upload_artifact: uploadArtifact.toString(), + use_config_file: useConfigFile.toString(), + email_configs: emailConfigs + } + }); + + logger.info('工作流触发成功,等待工作流开始运行...'); + + // 等待新工作流出现并获取其ID + let newWorkflowRunId = null; + let findAttempts = 0; + const maxFindAttempts = 30; // 最多等待30次,每次5秒 + + while (findAttempts < maxFindAttempts && !newWorkflowRunId) { + findAttempts++; + logger.info(`查找新触发的工作流,尝试 ${findAttempts}/${maxFindAttempts}...`); + + try { + const { data: afterWorkflowRuns } = await octokit.actions.listWorkflowRuns({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + workflow_id: GITHUB_WORKFLOW_ID, + per_page: 5 + }); + + if (afterWorkflowRuns.workflow_runs && afterWorkflowRuns.workflow_runs.length > 0) { + // 查找ID大于之前最新工作流ID的工作流(即新触发的工作流) + const newWorkflow = afterWorkflowRuns.workflow_runs.find(run => run.id > latestWorkflowIdBefore); + if (newWorkflow) { + newWorkflowRunId = newWorkflow.id; + logger.info(`找到新触发的工作流,ID: ${newWorkflowRunId}, 状态: ${newWorkflow.status}`); + } + } + } catch (error) { + logger.error(`查找工作流时出错 (尝试 ${findAttempts}/${maxFindAttempts}): ${error.message}`); + // 出错时继续尝试,不中断循环 + } + + if (!newWorkflowRunId) { + // 等待5秒后再次检查 + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + + if (!newWorkflowRunId) { + logger.info('未能找到新触发的工作流,可能触发失败'); + return null; + } + + // 等待工作流完成 + let attempts = 0; + const maxAttempts = 120; // 最多等待120次,每次30秒,总共60分钟 + let consecutiveErrors = 0; + const maxConsecutiveErrors = 5; // 最多允许连续5次错误 + + while (attempts < maxAttempts) { + attempts++; + logger.info(`等待工作流完成,尝试 ${attempts}/${maxAttempts}...`); + + try { + // 获取工作流状态 + const { data: workflowRun } = await octokit.actions.getWorkflowRun({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + run_id: newWorkflowRunId + }); + + // 重置连续错误计数 + consecutiveErrors = 0; + + logger.info(`工作流状态: ${workflowRun.status}, 结果: ${workflowRun.conclusion || '进行中'}`); + + // 检查工作流是否完成 + if (workflowRun.status === 'completed') { + if (workflowRun.conclusion === 'success') { + logger.info(`工作流运行成功,ID: ${newWorkflowRunId}`); + return workflowRun; + } else { + logger.info(`工作流运行失败,结果: ${workflowRun.conclusion}`); + return null; + } + } + } catch (error) { + consecutiveErrors++; + logger.error(`获取工作流状态时出错 (尝试 ${attempts}/${maxAttempts}, 连续错误 ${consecutiveErrors}/${maxConsecutiveErrors}): ${error.message}`); + + // 如果连续错误次数超过阈值,则放弃 + if (consecutiveErrors >= maxConsecutiveErrors) { + logger.error(`连续错误次数超过阈值 (${maxConsecutiveErrors}),放弃等待`); + throw new Error(`连续 ${maxConsecutiveErrors} 次获取工作流状态失败: ${error.message}`); + } + + // 错误后等待时间稍微延长 + await new Promise(resolve => setTimeout(resolve, 10000)); + // 继续循环,不中断 + continue; + } + + // 等待30秒后再次检查 + await new Promise(resolve => setTimeout(resolve, 30000)); + } + + logger.info('等待工作流完成超时'); + return null; + } catch (error) { + logger.error('触发工作流失败:', error); + throw error; // 重新抛出错误,让调用者处理 + } +} + +// 从 GitHub Actions 获取最新的 Artifact +async function getLatestArtifact() { + try { + logger.info('正在连接 GitHub API...'); + const octokit = new Octokit({ + auth: GITHUB_TOKEN + }); + + // 如果配置了自动触发工作流,则先触发工作流 + let workflowRun = null; + if (TRIGGER_WORKFLOW) { + logger.info('配置了自动触发工作流,正在触发...'); + try { + workflowRun = await triggerWorkflow(); + } catch (error) { + logger.error('触发工作流过程中出现错误:', error.message); + logger.info('尝试继续使用已找到的工作流ID...'); + + // 尝试获取最新的工作流,看是否有正在运行的工作流 + const { data: runningWorkflows } = await octokit.actions.listWorkflowRuns({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + workflow_id: GITHUB_WORKFLOW_ID, + status: 'in_progress', + per_page: 5 + }); + + if (runningWorkflows.workflow_runs && runningWorkflows.workflow_runs.length > 0) { + // 找到正在运行的工作流 + const runningWorkflow = runningWorkflows.workflow_runs[0]; + logger.info(`找到正在运行的工作流,ID: ${runningWorkflow.id}, 状态: ${runningWorkflow.status}`); + + // 等待工作流完成 + let attempts = 0; + const maxAttempts = 120; // 最多等待120次,每次30秒,总共60分钟 + let consecutiveErrors = 0; + const maxConsecutiveErrors = 5; // 最多允许连续5次错误 + + while (attempts < maxAttempts) { + attempts++; + logger.info(`等待工作流完成,尝试 ${attempts}/${maxAttempts}...`); + + try { + // 获取工作流状态 + const { data: currentWorkflow } = await octokit.actions.getWorkflowRun({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + run_id: runningWorkflow.id + }); + + // 重置连续错误计数 + consecutiveErrors = 0; + + logger.info(`工作流状态: ${currentWorkflow.status}, 结果: ${currentWorkflow.conclusion || '进行中'}`); + + // 检查工作流是否完成 + if (currentWorkflow.status === 'completed') { + if (currentWorkflow.conclusion === 'success') { + logger.info(`工作流运行成功,ID: ${currentWorkflow.id}`); + workflowRun = currentWorkflow; + break; + } else { + logger.info(`工作流运行失败,结果: ${currentWorkflow.conclusion}`); + break; + } + } + } catch (err) { + consecutiveErrors++; + logger.error(`获取工作流状态时出错 (尝试 ${attempts}/${maxAttempts}, 连续错误 ${consecutiveErrors}/${maxConsecutiveErrors}): ${err.message}`); + + // 如果连续错误次数超过阈值,则放弃 + if (consecutiveErrors >= maxConsecutiveErrors) { + logger.error(`连续错误次数超过阈值 (${maxConsecutiveErrors}),放弃等待`); + break; + } + + // 错误后等待时间稍微延长 + await new Promise(resolve => setTimeout(resolve, 10000)); + // 继续循环,不中断 + continue; + } + + // 等待30秒后再次检查 + await new Promise(resolve => setTimeout(resolve, 30000)); + } + } + } + + if (!workflowRun) { + logger.info('触发工作流失败或等待超时,尝试获取最新的工作流运行'); + } + } + + // 如果没有触发工作流或触发失败,则获取最新的工作流运行 + if (!workflowRun) { + logger.info('获取最新的工作流运行...'); + const { data: workflowRuns } = await octokit.actions.listWorkflowRunsForRepo({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + status: 'success', + per_page: 5 + }); + + if (!workflowRuns.workflow_runs || workflowRuns.workflow_runs.length === 0) { + logger.info('没有找到成功的工作流运行'); + return null; + } + + // 获取最新成功运行的 Artifacts + workflowRun = workflowRuns.workflow_runs[0]; + } + + logger.info(`找到最新的工作流运行: ${workflowRun.id}`); + + // 等待一段时间,确保Artifact已经上传完成 + logger.info('等待Artifact上传完成...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + + // 获取工作流的Artifacts + let artifacts = null; + let artifactAttempts = 0; + const maxArtifactAttempts = 10; // 最多尝试10次,每次10秒 + + while (artifactAttempts < maxArtifactAttempts && (!artifacts || !artifacts.artifacts || artifacts.artifacts.length === 0)) { + artifactAttempts++; + logger.info(`尝试获取Artifacts,尝试 ${artifactAttempts}/${maxArtifactAttempts}...`); + + try { + const response = await octokit.actions.listWorkflowRunArtifacts({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + run_id: workflowRun.id + }); + + artifacts = response.data; + } catch (error) { + logger.error(`获取Artifacts时出错 (尝试 ${artifactAttempts}/${maxArtifactAttempts}): ${error.message}`); + // 出错时继续尝试,不中断循环 + } + + if (!artifacts || !artifacts.artifacts || artifacts.artifacts.length === 0) { + logger.info('暂时没有找到Artifacts,等待10秒后重试...'); + await new Promise(resolve => setTimeout(resolve, 10000)); + } + } + + if (!artifacts || !artifacts.artifacts || artifacts.artifacts.length === 0) { + logger.info('没有找到Artifacts,可能工作流没有生成Artifact'); + return null; + } + + logger.info(`找到 ${artifacts.artifacts.length} 个Artifacts`); + + // 查找 Account info Artifact + const accountInfoArtifact = artifacts.artifacts.find(artifact => + artifact.name.toLowerCase().includes('account info')); + + if (!accountInfoArtifact) { + logger.info('没有找到 Account info Artifact'); + return null; + } + + logger.info(`找到 Account info Artifact: ${accountInfoArtifact.id}`); + return accountInfoArtifact; + } catch (error) { + logger.error('获取 Artifact 失败:', error); + return null; + } +} + +// 下载 Artifact +async function downloadArtifact(artifact) { + let downloadAttempts = 0; + const maxDownloadAttempts = 5; // 最多尝试5次下载 + + while (downloadAttempts < maxDownloadAttempts) { + downloadAttempts++; + try { + logger.info(`开始下载 Artifact: ${artifact.id}... (尝试 ${downloadAttempts}/${maxDownloadAttempts})`); + ensureDirectoryExists(DOWNLOAD_DIR); + + const octokit = new Octokit({ + auth: GITHUB_TOKEN + }); + + // 获取下载 URL + const { url } = await octokit.actions.downloadArtifact({ + owner: GITHUB_OWNER, + repo: GITHUB_REPO, + artifact_id: artifact.id, + archive_format: 'zip' + }); + + // 下载 zip 文件 + const zipFilePath = path.join(DOWNLOAD_DIR, `${artifact.id}.zip`); + const response = await axios({ + method: 'get', + url: url, + responseType: 'arraybuffer', + timeout: 60000 // 设置60秒超时 + }); + + fs.writeFileSync(zipFilePath, response.data); + logger.info(`Artifact 下载完成: ${zipFilePath}`); + return zipFilePath; + } catch (error) { + logger.error(`下载 Artifact 失败 (尝试 ${downloadAttempts}/${maxDownloadAttempts}): ${error.message}`); + + if (downloadAttempts >= maxDownloadAttempts) { + logger.error('达到最大尝试次数,放弃下载'); + return null; + } + + // 等待一段时间后重试 + const retryDelay = 10000; // 10秒 + logger.info(`等待 ${retryDelay/1000} 秒后重试...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + } + + return null; +} + +// 解压 Artifact +async function extractArtifact(zipFilePath) { + let extractAttempts = 0; + const maxExtractAttempts = 3; // 最多尝试3次解压 + + while (extractAttempts < maxExtractAttempts) { + extractAttempts++; + try { + logger.info(`开始解压 Artifact: ${zipFilePath}... (尝试 ${extractAttempts}/${maxExtractAttempts})`); + ensureDirectoryExists(EXTRACT_DIR); + + const zip = new AdmZip(zipFilePath); + zip.extractAllTo(EXTRACT_DIR, true); + logger.info(`Artifact 解压完成: ${EXTRACT_DIR}`); + + // 查找 token CSV 文件 + const files = fs.readdirSync(EXTRACT_DIR); + const tokenFile = files.find(file => file.startsWith('token_') && file.endsWith('.csv')); + + if (!tokenFile) { + logger.info('没有找到 token CSV 文件'); + + if (extractAttempts >= maxExtractAttempts) { + return null; + } + + // 等待一段时间后重试 + const retryDelay = 5000; // 5秒 + logger.info(`等待 ${retryDelay/1000} 秒后重试...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + continue; + } + + logger.info(`找到 token CSV 文件: ${tokenFile}`); + return path.join(EXTRACT_DIR, tokenFile); + } catch (error) { + logger.error(`解压 Artifact 失败 (尝试 ${extractAttempts}/${maxExtractAttempts}): ${error.message}`); + + if (extractAttempts >= maxExtractAttempts) { + logger.error('达到最大尝试次数,放弃解压'); + return null; + } + + // 等待一段时间后重试 + const retryDelay = 5000; // 5秒 + logger.info(`等待 ${retryDelay/1000} 秒后重试...`); + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } + } + + return null; +} + +/** + * 从CSV文件中提取cookies + * @param {string} csvFilePath - CSV文件路径 + * @returns {Promise} - 提取到的cookie数组 + */ +async function extractCookiesFromCsvFile(csvFilePath) { + const maxExtractAttempts = 3; + let attempt = 1; + + while (attempt <= maxExtractAttempts) { + logger.info(`尝试从CSV文件提取cookies (尝试 ${attempt}/${maxExtractAttempts})...`); + + try { + // 读取文件内容 + if (!fs.existsSync(csvFilePath)) { + logger.error(`CSV文件不存在: ${csvFilePath}`); + return []; + } + + // 读取文件内容并处理可能的换行符 + let fileContent = fs.readFileSync(csvFilePath, 'utf8'); + fileContent = fileContent.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); + + // 首先尝试直接从文件内容中提取所有可能的cookie + const cookies = []; + + // 1. 检查是否有JWT格式的token (新格式) + const jwtRegex = /ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g; + const jwtMatches = fileContent.match(jwtRegex); + + if (jwtMatches && jwtMatches.length > 0) { + logger.info(`直接从文件内容中提取到 ${jwtMatches.length} 个JWT token格式的Cookie`); + jwtMatches.forEach(match => { + if (!cookies.includes(match)) { + cookies.push(match); + } + }); + } + + // 2. 检查是否有旧格式的cookie + if (fileContent.includes('user_')) { + logger.info('文件包含旧格式cookie标识"user_"'); + + // 使用旧的提取函数尝试提取 + try { + const oldFormatCookies = await extractCookiesFromCsv(csvFilePath); + if (oldFormatCookies && oldFormatCookies.length > 0) { + logger.info(`通过提取模块获取到 ${oldFormatCookies.length} 个cookie`); + oldFormatCookies.forEach(cookie => { + if (!cookies.includes(cookie)) { + cookies.push(cookie); + } + }); + } + } catch (e) { + logger.warn('通过提取模块获取cookie失败:', e.message); + } + } + + // 3. 如果找到了cookie,返回结果 + if (cookies.length > 0) { + const newFormatCount = cookies.filter(c => c.startsWith('ey')).length; + const oldFormatCount = cookies.filter(c => c.includes('%3A%3A')).length; + + logger.info(`总共找到 ${cookies.length} 个cookie`); + logger.info(`新格式cookie(ey开头): ${newFormatCount}个`); + logger.info(`旧格式cookie(包含%3A%3A): ${oldFormatCount}个`); + logger.info(`其他格式cookie: ${cookies.length - newFormatCount - oldFormatCount}个`); + + return cookies; + } + + logger.warn(`未能从文件中提取到任何cookie (尝试 ${attempt}/${maxExtractAttempts})`); + } catch (error) { + logger.error(`从CSV文件提取cookies时出错 (尝试 ${attempt}/${maxExtractAttempts}):`, error); + } + + attempt++; + if (attempt <= maxExtractAttempts) { + logger.info(`等待5秒后重试...`); + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + + logger.error(`在 ${maxExtractAttempts} 次尝试后未能从CSV文件提取到cookies`); + return []; +} + +// 将新的有效cookie添加到系统中 +function addNewCookiesToSystem(apiKey, newCookies) { + try { + logger.info(`准备添加 ${newCookies.length} 个新cookie到系统中`); + + // 获取当前的cookies + const currentCookies = keyManager.getAllCookiesForApiKey(apiKey) || []; + logger.info(`当前API密钥 ${apiKey} 有 ${currentCookies.length} 个cookies`); + + // 获取无效的cookies + const invalidCookies = keyManager.getInvalidCookies() || []; + logger.info(`系统中有 ${invalidCookies.length || 0} 个无效cookies`); + + // 过滤出新的有效cookie + let newValidCookies = []; + + // 检查invalidCookies的类型并相应处理 + if (invalidCookies instanceof Set) { + newValidCookies = newCookies.filter(cookie => + !currentCookies.includes(cookie) && !invalidCookies.has(cookie) + ); + } else if (Array.isArray(invalidCookies)) { + newValidCookies = newCookies.filter(cookie => + !currentCookies.includes(cookie) && !invalidCookies.includes(cookie) + ); + } else if (invalidCookies && typeof invalidCookies === 'object') { + // 如果是普通对象,检查cookie是否作为键存在 + newValidCookies = newCookies.filter(cookie => + !currentCookies.includes(cookie) && !(cookie in invalidCookies) + ); + } else { + // 如果invalidCookies不是预期的类型,只过滤当前cookies + newValidCookies = newCookies.filter(cookie => !currentCookies.includes(cookie)); + } + + logger.info(`过滤后有 ${newValidCookies.length} 个新的有效cookies`); + + // 验证cookie是否完整 + const validatedCookies = newValidCookies.filter(cookie => { + // 检查是否是新格式的JWT token (ey开头) + if (cookie.startsWith('ey') && cookie.includes('.')) { + const parts = cookie.split('.'); + // 检查JWT是否包含三个部分 + if (parts.length !== 3) { + logger.warn(`跳过不完整的JWT cookie (新格式): ${cookie}`); + return false; + } + return true; + } + // 检查旧格式cookie是否包含JWT的三个部分 + else if (cookie.includes('%3A%3A')) { + const parts = cookie.split('%3A%3A'); + if (parts.length === 2) { + const jwt = parts[1]; + // 检查JWT是否包含点(表示JWT的三个部分) + if (!jwt.includes('.') || jwt.split('.').length !== 3) { + logger.warn(`跳过不完整的cookie (旧格式): ${cookie}`); + return false; + } + } + } + return true; + }); + + logger.info(`验证完整性后有 ${validatedCookies.length} 个有效cookies`); + + if (validatedCookies.length > 0) { + // 添加新的有效cookie到系统 + keyManager.addOrUpdateApiKey(apiKey, [...currentCookies, ...validatedCookies]); + logger.info(`成功添加 ${validatedCookies.length} 个新cookie到API密钥 ${apiKey}`); + return validatedCookies.length; // 返回添加的cookie数量 + } else { + logger.info(`没有新的有效cookie需要添加到API密钥 ${apiKey}`); + return 0; // 没有添加cookie,返回0 + } + } catch (error) { + logger.error('添加新cookie到系统时出错:', error); + return 0; // 出错时返回0 + } +} + +// 清理临时文件 +function cleanupTempFiles() { + try { + logger.info('开始清理临时文件...'); + + // 清理下载目录 + if (fs.existsSync(DOWNLOAD_DIR)) { + fs.readdirSync(DOWNLOAD_DIR).forEach(file => { + fs.unlinkSync(path.join(DOWNLOAD_DIR, file)); + }); + } + + // 清理解压目录 + if (fs.existsSync(EXTRACT_DIR)) { + fs.readdirSync(EXTRACT_DIR).forEach(file => { + fs.unlinkSync(path.join(EXTRACT_DIR, file)); + }); + } + + logger.info('临时文件清理完成'); + } catch (error) { + logger.error('清理临时文件失败:', error); + } +} + +// 检查 API Key 是否需要补充 Cookie +function checkApiKeyNeedRefresh(apiKey, minCookieCount = config.refresh.minCookieCount) { + const cookies = keyManager.getAllCookiesForApiKey(apiKey); + return cookies.length < minCookieCount; +} + +// 将现有cookie全部设为无效并从API Key中移除 +function markExistingCookiesAsInvalid(apiKey) { + try { + // 获取当前API Key的所有cookie + const currentCookies = keyManager.getAllCookiesForApiKey(apiKey) || []; + logger.info(`正在将API Key ${apiKey} 的 ${currentCookies.length} 个现有cookie标记为无效...`); + + // 如果没有cookie,直接返回 + if (currentCookies.length === 0) { + logger.info(`API Key ${apiKey} 没有现有cookie,无需标记为无效`); + return 0; + } + + // 获取无效cookie列表 + const invalidCookies = keyManager.getInvalidCookies(); + let markedCount = 0; + + // 遍历cookie并添加到无效列表 + for (const cookie of currentCookies) { + // 将cookie添加到无效集合中 + if (invalidCookies instanceof Set) { + invalidCookies.add(cookie); + } + markedCount++; + } + + // 保存无效cookie到文件 + keyManager.saveInvalidCookiesToFile(); + + // 清空当前API Key的cookie列表 + keyManager.addOrUpdateApiKey(apiKey, []); + + // 保存更新后的API Keys + keyManager.saveApiKeysToFile(); + + logger.info(`已将API Key ${apiKey} 的 ${markedCount} 个cookie标记为无效并从API Key中移除`); + return markedCount; + } catch (error) { + logger.error(`标记现有cookie为无效时出错:`, error); + return 0; + } +} + +// 主函数:自动刷新 Cookie +async function autoRefreshCookies(apiKey, minCookieCount = config.refresh.minCookieCount) { + logger.info(`开始自动刷新 Cookie,目标 API Key: ${apiKey},最小 Cookie 数量: ${minCookieCount}`); + + try { + // 检查是否需要刷新 + if (!checkApiKeyNeedRefresh(apiKey, minCookieCount)) { + logger.info(`API Key ${apiKey} 的 Cookie 数量足够,不需要刷新`); + return { + success: true, + message: '当前 Cookie 数量足够,不需要刷新', + refreshed: 0 + }; + } + + // 获取最新的 Artifact + const artifact = await getLatestArtifact(); + if (!artifact) { + return { + success: false, + message: '获取 Artifact 失败', + refreshed: 0 + }; + } + + // 下载 Artifact + const zipFilePath = await downloadArtifact(artifact); + if (!zipFilePath) { + return { + success: false, + message: '下载 Artifact 失败', + refreshed: 0 + }; + } + + // 解压 Artifact + const csvFilePath = await extractArtifact(zipFilePath); + if (!csvFilePath) { + return { + success: false, + message: '解压 Artifact 失败', + refreshed: 0 + }; + } + + // 提取 Cookie + const cookies = await extractCookiesFromCsvFile(csvFilePath); + if (cookies.length === 0) { + return { + success: false, + message: '没有找到有效的 Cookie', + refreshed: 0 + }; + } + + // 分析提取到的cookie格式 + const newFormatCookies = cookies.filter(cookie => cookie.startsWith('ey')); + const oldFormatCookies = cookies.filter(cookie => cookie.includes('%3A%3A')); + logger.info(`提取到 ${newFormatCookies.length} 个新格式cookie(ey开头)`); + logger.info(`提取到 ${oldFormatCookies.length} 个旧格式cookie(包含%3A%3A)`); + + // 根据配置决定是否将现有cookie标记为无效 + const refreshMode = process.env.COOKIE_REFRESH_MODE || 'append'; + + if (refreshMode === 'replace') { + // 将现有cookie标记为无效并从API Key中移除 + logger.info('使用替换模式: 将现有cookie标记为无效'); + markExistingCookiesAsInvalid(apiKey); + } else { + logger.info('使用追加模式: 保留现有cookie,只添加新cookie'); + } + + // 添加新的 Cookie 到系统 + const addedCount = addNewCookiesToSystem(apiKey, cookies); + + // 清理临时文件 + cleanupTempFiles(); + + return { + success: true, + message: `成功添加 ${addedCount} 个新 Cookie (新格式: ${newFormatCookies.length}, 旧格式: ${oldFormatCookies.length})`, + refreshed: addedCount + }; + } catch (error) { + logger.error('自动刷新 Cookie 失败:', error); + return { + success: false, + message: `刷新失败: ${error.message}`, + refreshed: 0 + }; + } +} + +module.exports = { + autoRefreshCookies, + checkApiKeyNeedRefresh, + getLatestArtifact, + downloadArtifact, + extractArtifact, + extractCookiesFromCsvFile, + addNewCookiesToSystem, + cleanupTempFiles, + triggerWorkflow, + markExistingCookiesAsInvalid +}; \ No newline at end of file diff --git a/src/utils/envChecker.js b/src/utils/envChecker.js new file mode 100644 index 0000000000000000000000000000000000000000..8554ceb550e008660efeb85883f466faede1cce4 --- /dev/null +++ b/src/utils/envChecker.js @@ -0,0 +1,89 @@ +const fs = require('fs'); +const path = require('path'); + +// 添加自己的简单日志函数,防止循环依赖 +function log(level, message) { + // 只在控制台输出,不写入文件 + const timestamp = new Date().toISOString(); + if (level === 'ERROR') { + console.error(`[ERROR] ${timestamp} ${message}`); + } else if (level === 'WARN') { + console.warn(`[WARN] ${timestamp} ${message}`); + } else { + console.log(`[INFO] ${timestamp} ${message}`); + } +} + +/** + * 检查 .env 文件是否存在 + * @returns {boolean} 文件是否存在 + */ +function checkEnvFileExists() { + const envPath = path.resolve(process.cwd(), '.env'); + return fs.existsSync(envPath); +} + +/** + * 检查必要的环境变量是否已设置 + * @returns {Object} 检查结果,包含是否通过和缺失的变量列表 + */ +function checkRequiredEnvVars() { + // 定义必要的环境变量列表 + const requiredVars = [ + 'API_KEYS', // API Keys 配置 + ]; + + // 如果启用了自动刷新,则需要检查相关配置 + if (process.env.ENABLE_AUTO_REFRESH === 'true') { + requiredVars.push( + 'GITHUB_TOKEN', + 'GITHUB_OWNER', + 'GITHUB_REPO', + 'GITHUB_WORKFLOW_ID', + 'TRIGGER_WORKFLOW' + ); + } + + // 检查每个必要的环境变量 + const missingVars = requiredVars.filter(varName => !process.env[varName]); + + return { + passed: missingVars.length === 0, + missingVars + }; +} + +/** + * 执行环境检查,如果不符合要求则退出程序 + */ +function enforceEnvCheck() { + log('INFO', '正在检查环境配置...'); + + // 检查 .env 文件是否存在 + const envFileExists = checkEnvFileExists(); + if (!envFileExists) { + log('ERROR', '\n错误: 未找到 .env 文件!'); + log('ERROR', '请根据 .env.example 创建 .env 文件并配置必要的环境变量。'); + log('ERROR', '执行以下命令复制示例文件: cp .env.example .env,或执行npm run setup\n'); + process.exit(1); // 退出程序,状态码 1 表示错误 + } + + // 检查必要的环境变量 + const { passed, missingVars } = checkRequiredEnvVars(); + if (!passed) { + log('ERROR', '\n错误: 以下必要的环境变量未在 .env 文件中设置:'); + missingVars.forEach(varName => { + log('ERROR', ` - ${varName}`); + }); + log('ERROR', '\n请在 .env 文件中配置这些变量后重新启动程序。\n'); + process.exit(1); // 退出程序,状态码 1 表示错误 + } + + log('INFO', '环境检查通过,继续启动程序...'); +} + +module.exports = { + checkEnvFileExists, + checkRequiredEnvVars, + enforceEnvCheck +}; \ No newline at end of file diff --git a/src/utils/extractCookieFromCsv.js b/src/utils/extractCookieFromCsv.js new file mode 100644 index 0000000000000000000000000000000000000000..8be2bafbe6b9e2336bc1bc62d4a4410a68e1f7b3 --- /dev/null +++ b/src/utils/extractCookieFromCsv.js @@ -0,0 +1,269 @@ +const fs = require('fs'); +const path = require('path'); +const csv = require('csv-parser'); + +/** + * 从CSV文件中提取完整的cookie + * @param {string} csvFilePath - CSV文件路径 + * @returns {Promise} - 提取到的cookie数组 + */ +async function extractCookiesFromCsv(csvFilePath) { + return new Promise((resolve, reject) => { + try { + // 检查文件是否存在 + if (!fs.existsSync(csvFilePath)) { + console.error(`CSV文件不存在: ${csvFilePath}`); + return resolve([]); + } + + // 读取文件内容 + const fileContent = fs.readFileSync(csvFilePath, 'utf8'); + console.log(`文件内容前200个字符: ${fileContent.substring(0, 200)}`); + + // 检查文件是否为空 + if (!fileContent || fileContent.trim() === '') { + console.error('CSV文件为空'); + return resolve([]); + } + + // 首先尝试直接从文件内容中提取所有可能的cookie + const cookies = []; + + // 检查是否有JWT格式的token (新格式) + const jwtRegex = /ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g; + const jwtMatches = fileContent.match(jwtRegex); + + if (jwtMatches && jwtMatches.length > 0) { + console.log(`直接从文件内容中提取到 ${jwtMatches.length} 个JWT token格式的Cookie`); + jwtMatches.forEach(match => { + if (!cookies.includes(match)) { + cookies.push(match); + } + }); + } + + // 检查文件内容是否包含关键字 + const hasTokenKeyword = fileContent.includes('token'); + const hasUserPrefix = fileContent.includes('user_'); + console.log(`文件包含"token"关键字: ${hasTokenKeyword}`); + console.log(`文件包含"user_"前缀: ${hasUserPrefix}`); + + // 如果文件包含user_前缀,尝试提取旧格式cookie + if (hasUserPrefix) { + const oldFormatCookies = extractCookiesFromText(fileContent); + if (oldFormatCookies.length > 0) { + console.log(`从文件内容中提取到 ${oldFormatCookies.length} 个旧格式Cookie`); + oldFormatCookies.forEach(cookie => { + if (!cookies.includes(cookie)) { + cookies.push(cookie); + } + }); + } + } + + // 如果已经找到cookie,返回结果 + if (cookies.length > 0) { + console.log(`总共提取到 ${cookies.length} 个Cookie`); + return resolve(validateCookies(cookies)); + } + + // 使用csv-parser解析CSV文件 + const possibleTokenFields = ['token', 'cookie', 'value', 'Token', 'Cookie', 'Value', 'jwt', 'JWT']; + + fs.createReadStream(csvFilePath) + .pipe(csv()) + .on('data', (row) => { + // 检查所有可能的字段名 + for (const field of possibleTokenFields) { + if (row[field]) { + // 检查是否是JWT格式 + if (row[field].startsWith('ey') && row[field].includes('.')) { + if (!cookies.includes(row[field])) { + cookies.push(row[field]); + } + break; + } + // 检查是否是旧格式 + else if (row[field].includes('user_')) { + if (!cookies.includes(row[field])) { + cookies.push(row[field]); + } + break; + } + } + } + + // 如果没有找到预定义的字段,遍历所有字段 + if (cookies.length === 0) { + for (const field in row) { + if (row[field] && typeof row[field] === 'string') { + // 检查是否是JWT格式 + if (row[field].startsWith('ey') && row[field].includes('.')) { + if (!cookies.includes(row[field])) { + cookies.push(row[field]); + } + break; + } + // 检查是否是旧格式 + else if (row[field].includes('user_')) { + if (!cookies.includes(row[field])) { + cookies.push(row[field]); + } + break; + } + } + } + } + }) + .on('end', () => { + console.log(`从CSV解析中提取到 ${cookies.length} 个Cookie`); + + // 如果通过CSV解析没有找到cookie,尝试按行读取 + if (cookies.length === 0) { + console.log('尝试按行读取文件...'); + const lines = fileContent.split('\n'); + for (const line of lines) { + // 检查是否有JWT格式token + if (line.includes('ey')) { + const jwtMatches = line.match(jwtRegex); + if (jwtMatches) { + jwtMatches.forEach(match => { + if (!cookies.includes(match)) { + cookies.push(match); + } + }); + } + } + + // 检查是否有旧格式cookie + if (line.includes('user_')) { + const extractedCookies = extractCookiesFromText(line); + extractedCookies.forEach(cookie => { + if (!cookies.includes(cookie)) { + cookies.push(cookie); + } + }); + } + } + console.log(`按行读取后提取到 ${cookies.length} 个Cookie`); + } + + // 验证提取的cookie是否完整 + const validatedCookies = validateCookies(cookies); + + resolve(validatedCookies); + }) + .on('error', (error) => { + console.error('解析CSV文件时出错:', error); + + // 如果已经提取到cookie,直接返回 + if (cookies.length > 0) { + console.log(`解析出错但已提取到 ${cookies.length} 个Cookie,进行验证后返回`); + resolve(validateCookies(cookies)); + } else { + // 否则尝试其他方法提取 + console.log('尝试其他方法提取Cookie...'); + + // 尝试提取JWT格式token + const jwtMatches = fileContent.match(jwtRegex); + if (jwtMatches) { + jwtMatches.forEach(match => { + if (!cookies.includes(match)) { + cookies.push(match); + } + }); + } + + // 尝试提取旧格式cookie + const oldFormatCookies = extractCookiesFromText(fileContent); + oldFormatCookies.forEach(cookie => { + if (!cookies.includes(cookie)) { + cookies.push(cookie); + } + }); + + console.log(`通过其他方法提取到 ${cookies.length} 个Cookie`); + resolve(validateCookies(cookies)); + } + }); + } catch (error) { + console.error('提取Cookie时出错:', error); + reject(error); + } + }); +} + +/** + * 从文本中提取cookie + * @param {string} text - 要提取cookie的文本 + * @returns {string[]} - 提取到的cookie数组 + */ +function extractCookiesFromText(text) { + const cookies = []; + + // 使用正则表达式匹配user_开头的cookie(旧格式) + const oldFormatRegex = /user_[a-zA-Z0-9%]+%3A%3A[a-zA-Z0-9%\.\_\-]+/g; + const oldFormatMatches = text.match(oldFormatRegex); + + if (oldFormatMatches) { + oldFormatMatches.forEach(match => { + if (!cookies.includes(match)) { + cookies.push(match); + } + }); + } + + // 使用正则表达式匹配以ey开头的JWT格式cookie(新格式) + const jwtRegex = /ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g; + const jwtMatches = text.match(jwtRegex); + + if (jwtMatches) { + jwtMatches.forEach(match => { + if (!cookies.includes(match)) { + cookies.push(match); + } + }); + } + + return cookies; +} + +/** + * 验证cookie是否完整 + * @param {string[]} cookies - 要验证的cookie数组 + * @returns {string[]} - 验证后的cookie数组 + */ +function validateCookies(cookies) { + return cookies.filter(cookie => { + // 检查是否是新格式的JWT token (ey开头) + if (cookie.startsWith('ey') && cookie.includes('.')) { + const parts = cookie.split('.'); + // 检查JWT是否包含三个部分 + if (parts.length === 3) { + return true; // cookie有效 + } else { + console.warn(`检测到不完整的JWT(新格式): ${cookie}`); + return false; + } + } + // 检查旧格式cookie是否完整 + else if (cookie.includes('%3A%3A')) { + const parts = cookie.split('%3A%3A'); + if (parts.length === 2) { + const jwt = parts[1]; + // 检查JWT是否包含两个点(表示三个部分) + if (jwt.includes('.') && jwt.split('.').length === 3) { + return true; // cookie完整 + } else { + console.warn(`检测到不完整的JWT(旧格式): ${cookie}`); + return false; + } + } + } + return true; // 对于无法识别的格式,默认保留 + }); +} + +module.exports = { + extractCookiesFromCsv +}; \ No newline at end of file diff --git a/src/utils/keyManager.js b/src/utils/keyManager.js new file mode 100644 index 0000000000000000000000000000000000000000..1f63326fec21e74b3c97ae353916065be118a5a1 --- /dev/null +++ b/src/utils/keyManager.js @@ -0,0 +1,410 @@ +const config = require('../config/config'); +const fs = require('fs'); +const path = require('path'); +const logger = require('./logger'); + +// 定义无效cookie的存储文件路径 +const INVALID_COOKIES_FILE = path.join(__dirname, '../../data/invalid_cookies.json'); +// 定义API Keys的存储文件路径 +const API_KEYS_FILE = path.join(__dirname, '../../data/api_keys.json'); + +// 确保data目录存在 +function ensureDataDirExists() { + const dataDir = path.join(__dirname, '../../data'); + if (!fs.existsSync(dataDir)) { + try { + fs.mkdirSync(dataDir, { recursive: true }); + logger.info(`创建data目录: ${dataDir}`); + } catch (err) { + logger.error('创建data目录失败:', err); + } + } +} + +// 存储API key与Cursor cookie的映射关系 +let apiKeyMap = new Map(); + +// 存储每个API key对应的cookie轮询索引 +let rotationIndexes = new Map(); + +// 存储被标记为无效的cookie +let invalidCookies = new Set(); + +// 从文件加载无效cookie +function loadInvalidCookiesFromFile() { + ensureDataDirExists(); + + try { + if (fs.existsSync(INVALID_COOKIES_FILE)) { + const data = fs.readFileSync(INVALID_COOKIES_FILE, 'utf8'); + const cookiesArray = JSON.parse(data); + + // 清空当前集合并添加从文件加载的cookie + invalidCookies.clear(); + cookiesArray.forEach(cookie => invalidCookies.add(cookie)); + + logger.info(`从文件加载了 ${cookiesArray.length} 个无效cookie`); + } else { + saveInvalidCookiesToFile(); // 如果文件不存在,创建新文件 + } + } catch (err) { + logger.error('加载无效cookie文件失败:', err); + saveInvalidCookiesToFile(); // 如果加载失败,尝试创建新文件 + } +} + +// 将无效cookie保存到文件 +function saveInvalidCookiesToFile() { + ensureDataDirExists(); + + try { + const cookiesArray = Array.from(invalidCookies); + fs.writeFileSync(INVALID_COOKIES_FILE, JSON.stringify(cookiesArray, null, 2), 'utf8'); + logger.info(`已将 ${cookiesArray.length} 个无效cookie保存到文件`); + } catch (err) { + logger.error('保存无效cookie文件失败:', err); + } +} + +// 从文件加载API Keys +function loadApiKeysFromFile() { + ensureDataDirExists(); + + try { + if (fs.existsSync(API_KEYS_FILE)) { + const data = fs.readFileSync(API_KEYS_FILE, 'utf8'); + const apiKeysObj = JSON.parse(data); + + // 清空现有映射 + apiKeyMap.clear(); + rotationIndexes.clear(); + + // 统计总cookie数量 + let totalCookies = 0; + + // 添加从文件加载的API Keys + for (const [apiKey, cookies] of Object.entries(apiKeysObj)) { + if (Array.isArray(cookies)) { + apiKeyMap.set(apiKey, cookies); + rotationIndexes.set(apiKey, 0); + totalCookies += cookies.length; + } else { + logger.error(`API Key ${apiKey} 的cookies不是数组,跳过`); + } + } + + const apiKeyCount = Object.keys(apiKeysObj).length; + logger.info(`从文件加载了 ${apiKeyCount} 个API Key,共 ${totalCookies} 个Cookie`); + return apiKeyCount > 0; + } else { + logger.info('API Keys文件不存在,将使用配置中的API Keys'); + return false; + } + } catch (err) { + logger.error('加载API Keys文件失败:', err); + return false; + } +} + +// 将API Keys保存到文件 +function saveApiKeysToFile() { + ensureDataDirExists(); + + try { + // 将Map转换为普通对象 + const apiKeysObj = {}; + for (const [apiKey, cookies] of apiKeyMap.entries()) { + apiKeysObj[apiKey] = cookies; + } + + // 使用JSON.stringify时避免特殊字符处理问题 + const jsonString = JSON.stringify(apiKeysObj, null, 2); + fs.writeFileSync(API_KEYS_FILE, jsonString, 'utf8'); + logger.info(`已将 ${Object.keys(apiKeysObj).length} 个API Key保存到文件`); + + // 简化验证过程 + try { + const savedContent = fs.readFileSync(API_KEYS_FILE, 'utf8'); + JSON.parse(savedContent); // 只验证JSON格式是否正确 + logger.info('验证通过: 所有cookie都被完整保存'); + } catch (verifyErr) { + logger.error('验证保存内容时出错:', verifyErr); + } + } catch (err) { + logger.error('保存API Keys文件失败:', err); + } +} + +// API Keys初始化函数 +function initializeApiKeys() { + // 首先从文件加载现有的API Keys + const loadedFromFile = loadApiKeysFromFile(); + + // 检查环境变量中是否有API Keys配置 + const configApiKeys = config.apiKeys; + const hasEnvApiKeys = Object.keys(configApiKeys).length > 0; + + if (hasEnvApiKeys) { + logger.info('从环境变量检测到API Keys配置,将合并到现有配置...'); + + // 记录合并前的Cookie数量 + let beforeMergeCookies = 0; + for (const cookies of apiKeyMap.values()) { + beforeMergeCookies += cookies.length; + } + + // 合并环境变量中的API Keys到现有映射 + for (const [apiKey, cookieValue] of Object.entries(configApiKeys)) { + // 获取现有的cookies(如果有) + const existingCookies = apiKeyMap.get(apiKey) || []; + + // 准备要添加的新cookies + let newCookies = []; + if (typeof cookieValue === 'string') { + newCookies = [cookieValue]; + } else if (Array.isArray(cookieValue)) { + newCookies = cookieValue; + } + + // 合并cookies,确保不重复 + const mergedCookies = [...existingCookies]; + for (const cookie of newCookies) { + if (!mergedCookies.includes(cookie)) { + mergedCookies.push(cookie); + } + } + + // 更新映射 + apiKeyMap.set(apiKey, mergedCookies); + + // 确保轮询索引存在 + if (!rotationIndexes.has(apiKey)) { + rotationIndexes.set(apiKey, 0); + } + } + + // 记录合并后的Cookie数量 + let afterMergeCookies = 0; + for (const cookies of apiKeyMap.values()) { + afterMergeCookies += cookies.length; + } + + logger.info(`合并前共有 ${beforeMergeCookies} 个Cookie,合并后共有 ${afterMergeCookies} 个Cookie`); + + // 保存合并后的结果到文件 + saveApiKeysToFile(); + } else if (!loadedFromFile) { + logger.warn('警告: 未能从文件加载API Keys,且环境变量中也没有配置API Keys'); + } + + // 统计API Keys和Cookies数量 + let totalCookies = 0; + for (const cookies of apiKeyMap.values()) { + totalCookies += cookies.length; + } + + logger.info(`API Keys初始化完成,共有 ${apiKeyMap.size} 个API Key,${totalCookies} 个Cookie`); + + // 加载无效cookie + loadInvalidCookiesFromFile(); + + // 从API Key中移除已知的无效cookie + logger.info('开始从API Keys中移除无效cookie...'); + removeInvalidCookiesFromApiKeys(); +} + +// 从所有API Key中移除已知的无效cookie +function removeInvalidCookiesFromApiKeys() { + let totalRemoved = 0; + + for (const [apiKey, cookies] of apiKeyMap.entries()) { + const initialLength = cookies.length; + + // 过滤掉无效的cookie + const filteredCookies = cookies.filter(cookie => !invalidCookies.has(cookie)); + + // 如果有cookie被移除,更新API Key的cookie列表 + if (filteredCookies.length < initialLength) { + const removedCount = initialLength - filteredCookies.length; + totalRemoved += removedCount; + + apiKeyMap.set(apiKey, filteredCookies); + rotationIndexes.set(apiKey, 0); + + logger.info(`从API Key ${apiKey} 中移除了 ${removedCount} 个无效cookie,剩余 ${filteredCookies.length} 个`); + } + } + + logger.info(`总共从API Keys中移除了 ${totalRemoved} 个无效cookie`); + + // 如果有cookie被移除,保存更新后的API Keys + if (totalRemoved > 0) { + saveApiKeysToFile(); + } +} + +// 添加或更新API key映射 +function addOrUpdateApiKey(apiKey, cookieValues) { + if (!Array.isArray(cookieValues)) { + cookieValues = [cookieValues]; + } + + // 过滤掉已知的无效cookie + const validCookies = cookieValues.filter(cookie => !invalidCookies.has(cookie)); + + if (validCookies.length < cookieValues.length) { + logger.info(`API Key ${apiKey} 中有 ${cookieValues.length - validCookies.length} 个无效cookie被过滤`); + } + + apiKeyMap.set(apiKey, validCookies); + rotationIndexes.set(apiKey, 0); + + // 保存更新后的API Keys + saveApiKeysToFile(); +} + +// 删除API key映射 +function removeApiKey(apiKey) { + apiKeyMap.delete(apiKey); + rotationIndexes.delete(apiKey); + + // 保存更新后的API Keys + saveApiKeysToFile(); +} + +// 获取API key对应的cookie值(根据轮询策略) +function getCookieForApiKey(apiKey, strategy = config.defaultRotationStrategy) { + // 如果API key不存在,也许是cookie本身,直接返回API key本身(向后兼容) + if (!apiKeyMap.has(apiKey)) { + return apiKey; + } + const cookies = apiKeyMap.get(apiKey); + + if (!cookies || cookies.length === 0) { + return apiKey; + } + + if (cookies.length === 1) { + return cookies[0]; + } + + // 根据策略选择cookie + if (strategy === 'random') { + // 随机策略 + const randomIndex = Math.floor(Math.random() * cookies.length); + return cookies[randomIndex]; + } else if(strategy === 'round-robin') { + // 轮询策略(round-robin) + let currentIndex = rotationIndexes.get(apiKey) || 0; + const cookie = cookies[currentIndex]; + + // 更新索引 + currentIndex = (currentIndex + 1) % cookies.length; + rotationIndexes.set(apiKey, currentIndex); + + return cookie; + } else { + // 默认策略(default) + return cookies[0]; + } +} + +// 获取所有API key +function getAllApiKeys() { + return Array.from(apiKeyMap.keys()); +} + +// 获取API key对应的所有cookie +function getAllCookiesForApiKey(apiKey) { + return apiKeyMap.get(apiKey) || []; +} + +// 从API key的cookie列表中移除特定cookie +function removeCookieFromApiKey(apiKey, cookieToRemove) { + if (!apiKeyMap.has(apiKey)) { + logger.info(`API Key ${apiKey} 不存在,无法移除cookie`); + return false; + } + + const cookies = apiKeyMap.get(apiKey); + const initialLength = cookies.length; + + // 检查是否尝试移除与API Key相同的值(可能是向后兼容模式) + if (cookieToRemove === apiKey && initialLength === 0) { + logger.info(`API Key ${apiKey} 中没有任何cookie,系统正在尝试以向后兼容模式使用API Key本身`); + return false; + } + + // 过滤掉要移除的cookie + const filteredCookies = cookies.filter(cookie => cookie !== cookieToRemove); + + // 如果长度没变,说明没有找到要移除的cookie + if (filteredCookies.length === initialLength) { + logger.info(`未找到要移除的cookie: ${cookieToRemove}`); + return false; + } + + // 更新cookie列表 + apiKeyMap.set(apiKey, filteredCookies); + + // 重置轮询索引 + rotationIndexes.set(apiKey, 0); + + // 将移除的cookie添加到无效cookie集合中 + invalidCookies.add(cookieToRemove); + + // 保存无效cookie到文件 + saveInvalidCookiesToFile(); + + // 保存更新后的API Keys + saveApiKeysToFile(); + + logger.info(`已从API Key ${apiKey} 中移除cookie: ${cookieToRemove}`); + logger.info(`剩余cookie数量: ${filteredCookies.length}`); + + return true; +} + +// 获取所有被标记为无效的cookie +function getInvalidCookies() { + return invalidCookies; +} + +// 清除特定的无效cookie记录 +function clearInvalidCookie(cookie) { + const result = invalidCookies.delete(cookie); + + if (result) { + // 保存更新后的无效cookie到文件 + saveInvalidCookiesToFile(); + } + + return result; +} + +// 清除所有无效cookie记录 +function clearAllInvalidCookies() { + invalidCookies.clear(); + + // 保存更新后的无效cookie到文件 + saveInvalidCookiesToFile(); + + return true; +} + +module.exports = { + addOrUpdateApiKey, + removeApiKey, + getCookieForApiKey, + getAllApiKeys, + getAllCookiesForApiKey, + initializeApiKeys, + removeCookieFromApiKey, + getInvalidCookies, + clearInvalidCookie, + clearAllInvalidCookies, + loadInvalidCookiesFromFile, + saveInvalidCookiesToFile, + loadApiKeysFromFile, + saveApiKeysToFile +}; \ No newline at end of file diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 0000000000000000000000000000000000000000..3e0b3ad3d0e22562889071dc16fc3523dd8b5284 --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,392 @@ +// logger.js - 统一的日志系统模块 +const fs = require('fs'); +const path = require('path'); + +// 避免循环依赖 +let config = null; +// 延迟加载配置 +function getConfig() { + if (!config) { + try { + config = require('../config/config'); + } catch (err) { + console.error('加载配置文件失败:', err.message); + config = { log: { level: 'INFO', format: 'colored' } }; + } + } + return config; +} + +const LOG_LEVELS = { + ERROR: 0, + WARN: 1, + INFO: 2, + DEBUG: 3, + TRACE: 4, + HTTP: 2 // HTTP日志级别与INFO相同 +}; + +// 默认日志级别 +let currentLogLevel = LOG_LEVELS.INFO; + +// 日志格式 +let logFormat = 'colored'; // colored, json, text + +// 带颜色的控制台输出 +const COLORS = { + RESET: '\x1b[0m', + RED: '\x1b[31m', + YELLOW: '\x1b[33m', + GREEN: '\x1b[32m', + BLUE: '\x1b[34m', + CYAN: '\x1b[36m' +}; + +// 日志文件配置 +const LOG_DIR = path.join(__dirname, '../../logs'); +const LOG_FILE = path.join(LOG_DIR, 'app.log'); +const MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB +let logToFile = false; + +// 内存中存储的日志(用于网页显示) +const memoryLogs = []; +const MAX_MEMORY_LOGS = 1000; // 内存中最多保存的日志条数 + +// 确保日志目录存在 +function ensureLogDirExists() { + try { + if (!fs.existsSync(LOG_DIR)) { + fs.mkdirSync(LOG_DIR, { recursive: true }); + } + return true; + } catch (err) { + console.error(`创建日志目录失败: ${err.message}`); + return false; + } +} + +// 初始化文件日志 +function initFileLogging() { + const conf = getConfig(); + if (process.env.LOG_TO_FILE === 'true' || (conf.log && conf.log.toFile)) { + if (ensureLogDirExists()) { + logToFile = true; + // 检查日志文件大小,如果超过最大值则进行轮转 + if (fs.existsSync(LOG_FILE)) { + const stats = fs.statSync(LOG_FILE); + if (stats.size > MAX_LOG_SIZE) { + rotateLogFile(); + } + } + return true; + } + } + return false; +} + +// 日志文件轮转 +function rotateLogFile() { + try { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const newLogFile = path.join(LOG_DIR, `app-${timestamp}.log`); + if (fs.existsSync(LOG_FILE)) { + fs.renameSync(LOG_FILE, newLogFile); + } + // 清理旧日志文件,保留最近10个 + const logFiles = fs.readdirSync(LOG_DIR) + .filter(file => file.startsWith('app-') && file.endsWith('.log')) + .sort() + .reverse(); + + if (logFiles.length > 10) { + logFiles.slice(10).forEach(file => { + try { + fs.unlinkSync(path.join(LOG_DIR, file)); + } catch (err) { + console.error(`删除旧日志文件失败: ${err.message}`); + } + }); + } + } catch (err) { + console.error(`日志文件轮转失败: ${err.message}`); + logToFile = false; + } +} + +// 添加日志到内存 +function addLogToMemory(level, timestamp, ...args) { + // 将日志对象添加到内存数组 + const logEntry = { + level, + timestamp, + message: args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : String(arg)).join(' ') + }; + + memoryLogs.unshift(logEntry); // 新日志添加到数组开头 + + // 保持数组在最大长度以内 + if (memoryLogs.length > MAX_MEMORY_LOGS) { + memoryLogs.pop(); // 移除最旧的日志 + } +} + +// 将日志写入文件 +function writeLogToFile(level, timestamp, ...args) { + if (!logToFile) return; + + try { + let logEntry; + + if (logFormat === 'json') { + // JSON格式 + const data = args.map(arg => typeof arg === 'object' ? arg : String(arg)); + const logObject = { + level, + timestamp, + message: data.length === 1 ? data[0] : data + }; + logEntry = JSON.stringify(logObject) + '\n'; + } else { + // 文本格式 + logEntry = `[${level}] ${timestamp} ${args.map(arg => + typeof arg === 'object' ? JSON.stringify(arg) : arg + ).join(' ')}\n`; + } + + fs.appendFileSync(LOG_FILE, logEntry); + + // 检查文件大小,必要时进行轮转 + const stats = fs.statSync(LOG_FILE); + if (stats.size > MAX_LOG_SIZE) { + rotateLogFile(); + } + } catch (err) { + console.error(`写入日志文件失败: ${err.message}`); + logToFile = false; + } +} + +// 获取时间戳 +function getTimestamp() { + return new Date().toISOString(); +} + +// 设置日志级别 +function setLogLevel(level) { + if (typeof level === 'string') { + level = level.toUpperCase(); + if (LOG_LEVELS[level] !== undefined) { + currentLogLevel = LOG_LEVELS[level]; + } else { + error(`无效的日志级别: ${level}`); + } + } else if (typeof level === 'number' && level >= 0 && level <= 4) { + currentLogLevel = level; + } else { + error(`无效的日志级别: ${level}`); + } +} + +// 设置日志格式 +function setLogFormat(format) { + const validFormats = ['colored', 'json', 'text']; + if (validFormats.includes(format)) { + logFormat = format; + return true; + } else { + error(`无效的日志格式: ${format}`); + return false; + } +} + +// 格式化控制台日志 +function formatConsoleLog(level, timestamp, color, ...args) { + if (logFormat === 'json') { + // JSON格式 + const data = args.map(arg => typeof arg === 'object' ? arg : String(arg)); + return JSON.stringify({ + level, + timestamp, + message: data.length === 1 ? data[0] : data + }); + } else if (logFormat === 'text') { + // 纯文本格式(无颜色) + return `[${level}] ${timestamp} ${args.join(' ')}`; + } else { + // 默认:带颜色格式 + return `${color}[${level}] ${timestamp}${COLORS.RESET} ${args.join(' ')}`; + } +} + +// 错误日志 +function error(...args) { + if (currentLogLevel >= LOG_LEVELS.ERROR) { + const timestamp = getTimestamp(); + const formattedLog = formatConsoleLog('ERROR', timestamp, COLORS.RED, ...args); + console.error(formattedLog); + writeLogToFile('ERROR', timestamp, ...args); + addLogToMemory('ERROR', timestamp, ...args); + } +} + +// 警告日志 +function warn(...args) { + if (currentLogLevel >= LOG_LEVELS.WARN) { + const timestamp = getTimestamp(); + const formattedLog = formatConsoleLog('WARN', timestamp, COLORS.YELLOW, ...args); + console.warn(formattedLog); + writeLogToFile('WARN', timestamp, ...args); + addLogToMemory('WARN', timestamp, ...args); + } +} + +// 信息日志 +function info(...args) { + if (currentLogLevel >= LOG_LEVELS.INFO) { + const timestamp = getTimestamp(); + const formattedLog = formatConsoleLog('INFO', timestamp, COLORS.GREEN, ...args); + console.log(formattedLog); + writeLogToFile('INFO', timestamp, ...args); + addLogToMemory('INFO', timestamp, ...args); + } +} + +// 调试日志 +function debug(...args) { + if (currentLogLevel >= LOG_LEVELS.DEBUG) { + const timestamp = getTimestamp(); + const formattedLog = formatConsoleLog('DEBUG', timestamp, COLORS.BLUE, ...args); + console.log(formattedLog); + writeLogToFile('DEBUG', timestamp, ...args); + addLogToMemory('DEBUG', timestamp, ...args); + } +} + +// 跟踪日志 +function trace(...args) { + if (currentLogLevel >= LOG_LEVELS.TRACE) { + const timestamp = getTimestamp(); + const formattedLog = formatConsoleLog('TRACE', timestamp, COLORS.CYAN, ...args); + console.log(formattedLog); + writeLogToFile('TRACE', timestamp, ...args); + addLogToMemory('TRACE', timestamp, ...args); + } +} + +// HTTP请求日志 (特殊处理,方便筛选) +function http(...args) { + if (currentLogLevel >= LOG_LEVELS.INFO) { + const timestamp = getTimestamp(); + const formattedLog = formatConsoleLog('HTTP', timestamp, COLORS.CYAN, ...args); + console.log(formattedLog); + writeLogToFile('HTTP', timestamp, ...args); + addLogToMemory('HTTP', timestamp, ...args); + } +} + +// 获取内存中的日志 +function getLogs(filter = {}) { + let filteredLogs = [...memoryLogs]; + + // 按日志级别筛选 + if (filter.level) { + filteredLogs = filteredLogs.filter(log => log.level === filter.level); + } + + // 按时间范围筛选 + if (filter.startTime) { + filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) >= new Date(filter.startTime)); + } + + if (filter.endTime) { + filteredLogs = filteredLogs.filter(log => new Date(log.timestamp) <= new Date(filter.endTime)); + } + + // 按关键词搜索 + if (filter.search) { + const searchTerm = filter.search.toLowerCase(); + filteredLogs = filteredLogs.filter(log => + log.message.toLowerCase().includes(searchTerm) || + log.level.toLowerCase().includes(searchTerm) + ); + } + + // 分页 + const page = filter.page || 1; + const pageSize = filter.pageSize || 100; + const start = (page - 1) * pageSize; + const end = start + pageSize; + + return { + logs: filteredLogs.slice(start, end), + total: filteredLogs.length, + page, + pageSize + }; +} + +// 清除内存日志 +function clearMemoryLogs() { + memoryLogs.length = 0; + info('内存日志已清除'); +} + +// 初始化配置 +function initialize() { + try { + const conf = getConfig(); + + // 初始化日志级别 + const envLevel = process.env.LOG_LEVEL; + if (envLevel) { + setLogLevel(envLevel); + } else if (conf && conf.log && conf.log.level) { + setLogLevel(conf.log.level); + } + + // 初始化日志格式 + const envFormat = process.env.LOG_FORMAT; + if (envFormat) { + setLogFormat(envFormat); + } else if (conf && conf.log && conf.log.format) { + setLogFormat(conf.log.format); + } + + // 初始化文件日志 + initFileLogging(); + } catch (err) { + console.error(`初始化日志系统出错: ${err.message}`); + } +} + +// 初始化 +initialize(); + +module.exports = { + LOG_LEVELS, + setLogLevel, + setLogFormat, + error, + warn, + info, + debug, + trace, + http, + // 暴露文件日志相关方法 + enableFileLogging: () => { + if (ensureLogDirExists()) { + logToFile = true; + info('文件日志已启用'); + return true; + } + return false; + }, + disableFileLogging: () => { + logToFile = false; + info('文件日志已禁用'); + }, + rotateLogFile, + // 添加内存日志相关方法 + getLogs, + clearMemoryLogs +}; \ No newline at end of file diff --git a/src/utils/proxyLauncher.js b/src/utils/proxyLauncher.js new file mode 100644 index 0000000000000000000000000000000000000000..24fc2146f65d8d37d95c3b09ac8c35a54debe785 --- /dev/null +++ b/src/utils/proxyLauncher.js @@ -0,0 +1,317 @@ +const { spawn } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const logger = require('./logger'); + +let mainProxyProcess = null; +let othersProxyProcess = null; +let mainProxyLogStream = null; +let othersProxyLogStream = null; + +/** + * 获取当前系统平台 + * @returns {string} 平台标识 + */ +function detectPlatform() { + const platform = os.platform(); + const arch = os.arch(); + + if (platform === 'win32' && arch === 'x64') { + return 'windows_x64'; + } else if (platform === 'linux' && arch === 'x64') { + return 'linux_x64'; + } else if ((platform === 'android' || platform === 'linux') && (arch === 'arm64' || arch === 'aarch64')) { + return 'android_arm64'; + } + + // 默认返回linux版本 + logger.warn(`未识别的平台: ${platform} ${arch},将使用linux_x64代理`); + return 'linux_x64'; +} + +/** + * 获取代理服务器可执行文件路径 + * @param {string} platform 平台类型 + * @param {string} proxyType 代理类型 ('main' 或 'others') + * @returns {string} 可执行文件路径 + */ +function getProxyExecutablePath(platform, proxyType = 'main') { + let proxyDir; + + if (proxyType === 'others') { + proxyDir = path.join(process.cwd(), 'src', 'proxy', 'others'); + } else { + proxyDir = path.join(process.cwd(), 'src', 'proxy'); + } + + // 根据平台选择可执行文件 + switch (platform) { + case 'windows_x64': + return path.join(proxyDir, 'cursor_proxy_server_windows_amd64.exe'); + case 'linux_x64': + return path.join(proxyDir, 'cursor_proxy_server_linux_amd64'); + case 'android_arm64': + return path.join(proxyDir, 'cursor_proxy_server_android_arm64'); + default: + logger.warn(`未知平台: ${platform},将使用linux_x64代理`); + return path.join(proxyDir, 'cursor_proxy_server_linux_amd64'); + } +} + +/** + * 创建并打开代理服务器日志文件 + * @param {string} platform 平台类型 + * @param {string} proxyType 代理类型 ('main' 或 'others') + * @returns {fs.WriteStream} 日志文件写入流 + */ +function createProxyLogFile(platform, proxyType = 'main') { + try { + // 确保logs目录存在 + const logsDir = path.join(process.cwd(), 'logs'); + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + + // 创建日志文件名,包含日期和平台信息 + const now = new Date(); + const dateStr = now.toISOString().split('T')[0]; + const logFileName = `proxy_server_${proxyType}_${platform}_${dateStr}.log`; + const logFilePath = path.join(logsDir, logFileName); + + // 创建日志文件流 + const logStream = fs.createWriteStream(logFilePath, { flags: 'a' }); + + // 写入日志文件头 + const headerLine = `\n\n========== ${proxyType}代理服务器日志 - ${platform} - ${now.toISOString()} ==========\n\n`; + logStream.write(headerLine); + + logger.info(`${proxyType}代理服务器详细日志将记录到: ${logFilePath}`); + + return logStream; + } catch (error) { + logger.error(`创建${proxyType}代理服务器日志文件失败: ${error.message}`); + return null; + } +} + +/** + * 写入日志到代理服务器日志文件 + * @param {fs.WriteStream} logStream 日志文件流 + * @param {string} message 日志消息 + * @param {string} type 日志类型 (stdout 或 stderr) + */ +function writeToProxyLog(logStream, message, type = 'stdout') { + if (!logStream) return; + + try { + const timestamp = new Date().toISOString(); + const logLine = `[${timestamp}] [${type}] ${message}\n`; + logStream.write(logLine); + } catch (error) { + logger.error(`写入代理服务器日志失败: ${error.message}`); + } +} + +/** + * 启动单个代理服务器 + * @param {string} platform 平台类型 + * @param {string} proxyType 代理类型 ('main' 或 'others') + * @param {number} port 代理服务器端口 + * @returns {object} 包含进程和日志流的对象 + */ +function startSingleProxyServer(platform, proxyType, port) { + try { + // 获取可执行文件路径 + const execPath = getProxyExecutablePath(platform, proxyType); + + // 检查文件是否存在 + if (!fs.existsSync(execPath)) { + logger.error(`${proxyType}代理服务器可执行文件不存在: ${execPath}`); + return { process: null, logStream: null }; + } + + // 在Linux/Android上,设置可执行权限 + if (platform !== 'windows_x64') { + try { + fs.chmodSync(execPath, '755'); + } catch (err) { + logger.warn(`无法设置${proxyType}代理服务器可执行权限: ${err.message}`); + } + } + + // 创建代理服务器日志文件 + const logStream = createProxyLogFile(platform, proxyType); + + // 启动代理服务器进程 + logger.info(`正在启动${platform}平台的${proxyType}代理服务器: ${execPath},端口: ${port}`); + + // 添加端口参数 + const args = port ? [`--port=${port}`] : []; + + const proxyProcess = spawn(execPath, args, { + detached: false, + stdio: ['ignore', 'pipe', 'pipe'] + }); + + // 记录代理服务器的详细日志到文件 + proxyProcess.stdout.on('data', (data) => { + const output = data.toString().trim(); + writeToProxyLog(logStream, output, 'stdout'); + }); + + proxyProcess.stderr.on('data', (data) => { + const errorOutput = data.toString().trim(); + writeToProxyLog(logStream, errorOutput, 'stderr'); + + // 只在启动失败时记录错误信息到控制台 + if (!proxyProcess.startSuccessful && errorOutput.includes('error')) { + logger.error(`${proxyType}代理服务器启动错误: ${errorOutput.split('\n')[0]}`); + } + }); + + proxyProcess.on('error', (err) => { + logger.error(`${proxyType}代理服务器启动失败: ${err.message}`); + writeToProxyLog(logStream, `启动失败: ${err.message}`, 'error'); + return { process: null, logStream: null }; + }); + + proxyProcess.on('close', (code) => { + // 只有在非正常退出时记录到控制台 + if (code !== 0) { + logger.warn(`${proxyType}代理服务器已退出,代码: ${code}`); + } + + writeToProxyLog(logStream, `进程已退出,退出代码: ${code}`, 'info'); + + // 关闭日志文件 + if (logStream) { + logStream.end(); + } + }); + + // 等待一段时间确保启动成功 + setTimeout(() => { + if (proxyProcess && proxyProcess.exitCode === null) { + proxyProcess.startSuccessful = true; + logger.info(`${proxyType}代理服务器已成功启动`); + writeToProxyLog(logStream, `${proxyType}代理服务器已成功启动`, 'info'); + } else { + logger.error(`${proxyType}代理服务器启动失败或异常退出`); + writeToProxyLog(logStream, `${proxyType}代理服务器启动失败或异常退出`, 'error'); + } + }, 1000); + + return { process: proxyProcess, logStream }; + } catch (error) { + logger.error(`启动${proxyType}代理服务器出错: ${error.message}`); + return { process: null, logStream: null }; + } +} + +/** + * 启动代理服务器 + * @returns {boolean} 是否成功启动 + */ +function startProxyServer() { + try { + // 检查是否启用代理 + const useTlsProxy = process.env.USE_TLS_PROXY === 'true'; + if (!useTlsProxy) { + logger.warn('TLS代理服务器未启用,跳过启动'); + return true; + } + + // 检查是否启用辅助代理服务器 + const useOthersProxy = process.env.USE_OTHERS_PROXY === 'true'; + + // 确定要使用的平台 + let platform = process.env.PROXY_PLATFORM || 'auto'; + if (platform === 'auto') { + platform = detectPlatform(); + } + + // 启动主代理服务器(默认使用8080端口) + const mainProxy = startSingleProxyServer(platform, 'main', 8080); + mainProxyProcess = mainProxy.process; + mainProxyLogStream = mainProxy.logStream; + + // 根据配置决定是否启动辅助代理服务器 + if (useOthersProxy) { + logger.info('辅助代理服务器已启用,正在启动...'); + // 启动others代理服务器(端口 10654) + const othersProxy = startSingleProxyServer(platform, 'others', 10654); + othersProxyProcess = othersProxy.process; + othersProxyLogStream = othersProxy.logStream; + + // 如果辅助代理启动失败,记录警告 + if (!othersProxyProcess) { + logger.warn('辅助代理服务器启动失败'); + } else { + logger.info('辅助代理服务器启动成功'); + } + } else { + logger.warn('辅助代理服务器未启用,跳过启动'); + } + + // 如果主代理启动失败,记录警告 + if (!mainProxyProcess) { + logger.warn('主代理服务器启动失败'); + return false; + } + + return true; + } catch (error) { + logger.error(`启动代理服务器出错: ${error.message}`); + return false; + } +} + +/** + * 停止代理服务器 + */ +function stopProxyServer() { + const stopSingleProxy = (proxyProcess, logStream, proxyType) => { + if (proxyProcess) { + logger.info(`正在停止${proxyType}代理服务器...`); + writeToProxyLog(logStream, `正在停止${proxyType}代理服务器`, 'info'); + + // 在Windows上,使用taskkill强制终止 + if (os.platform() === 'win32') { + try { + spawn('taskkill', ['/pid', proxyProcess.pid, '/f', '/t']); + } catch (err) { + logger.error(`使用taskkill终止${proxyType}代理进程失败: ${err.message}`); + writeToProxyLog(logStream, `使用taskkill终止${proxyType}代理进程失败: ${err.message}`, 'error'); + } + } else { + // 在Linux/Mac上直接kill + proxyProcess.kill('SIGTERM'); + } + + // 允许一些时间写入最后的日志 + setTimeout(() => { + // 关闭日志文件 + if (logStream) { + logStream.end(); + } + }, 500); + } + }; + + // 停止主代理服务器 + stopSingleProxy(mainProxyProcess, mainProxyLogStream, 'main'); + mainProxyProcess = null; + mainProxyLogStream = null; + + // 停止others代理服务器 + stopSingleProxy(othersProxyProcess, othersProxyLogStream, 'others'); + othersProxyProcess = null; + othersProxyLogStream = null; +} + +// 导出模块 +module.exports = { + startProxyServer, + stopProxyServer +}; \ No newline at end of file diff --git a/src/utils/utils.js b/src/utils/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..d683885580ac4d32c2485038a99b9aae9e4a80c8 --- /dev/null +++ b/src/utils/utils.js @@ -0,0 +1,206 @@ +const os = require('os'); +const zlib = require('zlib'); +const crypto = require('crypto'); +const { v4: uuidv4 } = require('uuid'); +const $root = require('../proto/message.js'); + +function generateCursorBody(messages, modelName) { + + const instruction = messages + .filter(msg => msg.role === 'system') + .map(msg => msg.content) + .join('\n') + + const formattedMessages = messages + .filter(msg => msg.role !== 'system') + .map(msg => ({ + content: msg.content, + role: msg.role === 'user' ? 1 : 2, + messageId: uuidv4(), + ...(msg.role === 'user' ? { chatModeEnum: 1 } : {}) + //...(msg.role !== 'user' ? { summaryId: uuidv4() } : {}) + })); + + const messageIds = formattedMessages.map(msg => { + const { role, messageId, summaryId } = msg; + return summaryId ? { role, messageId, summaryId } : { role, messageId }; + }); + + const body = { + request:{ + messages: formattedMessages, + unknown2: 1, + instruction: { + instruction: instruction + }, + unknown4: 1, + model: { + name: modelName, + empty: '', + }, + webTool: "", + unknown13: 1, + cursorSetting: { + name: "cursor\\aisettings", + unknown3: "", + unknown6: { + unknwon1: "", + unknown2: "" + }, + unknown8: 1, + unknown9: 1 + }, + unknown19: 1, + //unknown22: 1, + conversationId: uuidv4(), + metadata: { + os: "win32", + arch: "x64", + version: "10.0.22631", + path: "C:\\Program Files\\PowerShell\\7\\pwsh.exe", + timestamp: new Date().toISOString(), + }, + unknown27: 0, + //unknown29: "", + messageIds: messageIds, + largeContext: 0, + unknown38: 0, + chatModeEnum: 1, + unknown47: "", + unknown48: 0, + unknown49: 0, + unknown51: 0, + unknown53: 1, + chatMode: "Ask" + } + }; + + const errMsg = $root.StreamUnifiedChatWithToolsRequest.verify(body); + if (errMsg) throw Error(errMsg); + const instance = $root.StreamUnifiedChatWithToolsRequest.create(body); + let buffer = $root.StreamUnifiedChatWithToolsRequest.encode(instance).finish(); + let magicNumber = 0x00 + if (formattedMessages.length >= 3){ + buffer = zlib.gzipSync(buffer) + magicNumber = 0x01 + } + + const finalBody = Buffer.concat([ + Buffer.from([magicNumber]), + Buffer.from(buffer.length.toString(16).padStart(8, '0'), 'hex'), + buffer + ]) + + return finalBody +} + +function chunkToUtf8String(chunk) { + const results = [] + const thinkingResults = [] + const contentResults = [] + const errorResults = { hasError: false, errorMessage: '' } + const buffer = Buffer.from(chunk, 'hex'); + //console.log("Chunk buffer:", buffer.toString('hex')) + + try { + for(let i = 0; i < buffer.length; i++){ + const magicNumber = parseInt(buffer.subarray(i, i + 1).toString('hex'), 16) + const dataLength = parseInt(buffer.subarray(i + 1, i + 5).toString('hex'), 16) + const data = buffer.subarray(i + 5, i + 5 + dataLength) + //console.log("Parsed buffer:", magicNumber, dataLength, data.toString('hex')) + + if (magicNumber == 0 || magicNumber == 1) { + const gunzipData = magicNumber == 0 ? data : zlib.gunzipSync(data) + const response = $root.StreamUnifiedChatWithToolsResponse.decode(gunzipData); + const thinking = response?.message?.thinking?.content + if (thinking !== undefined && thinking.length > 0){ + thinkingResults.push(thinking); + // console.log('[DEBUG] 收到 thinking:', thinking); + } + const content = response?.message?.content + if (content !== undefined && content.length > 0){ + contentResults.push(content) + // console.log('[DEBUG] 收到 content:', content); + } + } + else if (magicNumber == 2 || magicNumber == 3) { + // Json message + const gunzipData = magicNumber == 2 ? data : zlib.gunzipSync(data) + const utf8 = gunzipData.toString('utf-8') + const message = JSON.parse(utf8) + + if (message != null && (typeof message !== 'object' || + (Array.isArray(message) ? message.length > 0 : Object.keys(message).length > 0))){ + //results.push(utf8) + console.error(utf8) + + // 检查是否为错误消息 + if (message && message.error) { + errorResults.hasError = true; + errorResults.errorMessage = utf8; + } + } + } + else { + //console.log('Unknown magic number when parsing chunk response: ' + magicNumber) + } + + i += 5 + dataLength - 1 + } + } catch (err) { + console.log('Error parsing chunk response:', err) + } + + // 如果存在错误,返回错误对象 + if (errorResults.hasError) { + return { error: errorResults.errorMessage }; + } + + // 分别返回thinking和content内容 + return { + reasoning_content: thinkingResults.join(''), + content: contentResults.join('') + }; +} + +function generateHashed64Hex(input, salt = '') { + const hash = crypto.createHash('sha256'); + hash.update(input + salt); + return hash.digest('hex'); +} + +function obfuscateBytes(byteArray) { + let t = 165; + for (let r = 0; r < byteArray.length; r++) { + byteArray[r] = (byteArray[r] ^ t) + (r % 256); + t = byteArray[r]; + } + return byteArray; +} + +function generateCursorChecksum(token) { + const machineId = generateHashed64Hex(token, 'machineId'); + const macMachineId = generateHashed64Hex(token, 'macMachineId'); + + const timestamp = Math.floor(Date.now() / 1e6); + const byteArray = new Uint8Array([ + (timestamp >> 40) & 255, + (timestamp >> 32) & 255, + (timestamp >> 24) & 255, + (timestamp >> 16) & 255, + (timestamp >> 8) & 255, + 255 & timestamp, + ]); + + const obfuscatedBytes = obfuscateBytes(byteArray); + const encodedChecksum = Buffer.from(obfuscatedBytes).toString('base64'); + + return `${encodedChecksum}${machineId}/${macMachineId}`; +} + +module.exports = { + generateCursorBody, + chunkToUtf8String, + generateHashed64Hex, + generateCursorChecksum +}; diff --git a/start.bat b/start.bat new file mode 100644 index 0000000000000000000000000000000000000000..4a12a321a6440c551df8615db74d13212ddd8b13 --- /dev/null +++ b/start.bat @@ -0,0 +1,17 @@ +@echo off +chcp 65001 >nul +REM 编码:UTF-8 + +REM 安装依赖 +echo install dependencies... +call npm install --no-fund --quiet --no-audit + +REM 检查上一个命令的退出状态 +if %ERRORLEVEL% neq 0 ( + echo dependencies installation failed,maybe start application failed +) + +REM 启动应用 +echo start application... +call npm start +pause \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 0000000000000000000000000000000000000000..ec76f1ed687a64439eb9c3b6b5f9e413dbb33fab --- /dev/null +++ b/start.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# 编码:UTF-8 + +# 安装依赖 +echo "install dependencies..." +npm install --no-fund --quiet --no-audit + +# 检查上一个命令的退出状态 +if [ $? -ne 0 ]; then + echo "dependencies installation failed,maybe start application failed" +fi + +# 启动应用 +echo "start application..." +npm start \ No newline at end of file diff --git a/update.bat b/update.bat new file mode 100644 index 0000000000000000000000000000000000000000..8365cd05f39eeec7a3dc6565d828f0b37ec53f01 --- /dev/null +++ b/update.bat @@ -0,0 +1,33 @@ +@echo off +chcp 65001 >nul +REM 编码:UTF-8 + +echo start update process... + +REM 还原特定的代理服务器文件 +git checkout HEAD -- src/proxy/cursor_proxy_server_android_arm64 ^ + src/proxy/cursor_proxy_server_linux_amd64 ^ + src/proxy/cursor_proxy_server_windows_amd64.exe + +if %ERRORLEVEL% neq 0 ( + echo error:restore proxy server file failed + goto fail +) + +REM 拉取远程更新,保留服务器端更改 +git pull -X theirs + +if %ERRORLEVEL% neq 0 ( + echo error:pull update failed,maybe network problem or conflict + goto fail +) + +echo update success +goto end + +:fail +echo update failed,please check network connection or solve conflict +exit /b 1 + +:end +pause \ No newline at end of file diff --git a/update.sh b/update.sh new file mode 100644 index 0000000000000000000000000000000000000000..2ad938c733204e4fe4a16f33186c84985cc8c1b3 --- /dev/null +++ b/update.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +echo "start update process..." + +# 还原特定的代理服务器文件 +git checkout HEAD -- src/proxy/cursor_proxy_server_android_arm64 \ + src/proxy/cursor_proxy_server_linux_amd64 \ + src/proxy/cursor_proxy_server_windows_amd64.exe + +if [ $? -ne 0 ]; then + echo "error:restore proxy server file failed" + exit 1 +fi + +# 拉取远程更新,保留服务器端更改 +git pull -X theirs + +if [ $? -ne 0 ]; then + echo "error:pull update failed,maybe network problem or conflict" + exit 1 +fi + +echo "update success"