Spaces:
Paused
Paused
Upload 14 files
Browse files- .env.example +4 -1
- README.md +164 -85
- src/ProxyPool.js +117 -5
- src/ProxyServer.js +3 -11
- src/lightweight-client-express.js +4 -2
- src/lightweight-client.js +28 -2
.env.example
CHANGED
|
@@ -4,11 +4,14 @@ NOTION_COOKIE="cookie1|cookie2"
|
|
| 4 |
#COOKIE_FILE="./cookies.txt"
|
| 5 |
# (可选)反代密码,没有的话默认密码是 default_token
|
| 6 |
PROXY_AUTH_TOKEN="sk-text"
|
| 7 |
-
#
|
| 8 |
PROXY_URL=""
|
| 9 |
# (可选)是否使用本地代理池,启用前确保没有设置其它代理,否则速度会减慢或者出现其它问题,推荐启用
|
| 10 |
USE_NATIVE_PROXY_POOL=true
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
# tls代理服务器配置(可选)建议启用
|
| 13 |
# 平台选择:auto(自动检测), windows, linux, android
|
| 14 |
PROXY_SERVER_PLATFORM="auto"
|
|
|
|
| 4 |
#COOKIE_FILE="./cookies.txt"
|
| 5 |
# (可选)反代密码,没有的话默认密码是 default_token
|
| 6 |
PROXY_AUTH_TOKEN="sk-text"
|
| 7 |
+
# (可选)代理地址
|
| 8 |
PROXY_URL=""
|
| 9 |
# (可选)是否使用本地代理池,启用前确保没有设置其它代理,否则速度会减慢或者出现其它问题,推荐启用
|
| 10 |
USE_NATIVE_PROXY_POOL=true
|
| 11 |
|
| 12 |
+
# (可选)代理国家,支持us uk jp de fr ca
|
| 13 |
+
PROXY_COUNTRY="us"
|
| 14 |
+
|
| 15 |
# tls代理服务器配置(可选)建议启用
|
| 16 |
# 平台选择:auto(自动检测), windows, linux, android
|
| 17 |
PROXY_SERVER_PLATFORM="auto"
|
README.md
CHANGED
|
@@ -1,128 +1,207 @@
|
|
| 1 |
-
|
| 2 |
-
title: Notion
|
| 3 |
-
emoji: 📝
|
| 4 |
-
colorFrom: indigo
|
| 5 |
-
colorTo: blue
|
| 6 |
-
sdk: docker
|
| 7 |
-
app_port: 7860
|
| 8 |
-
---
|
| 9 |
|
| 10 |
-
|
| 11 |
|
| 12 |
-
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
2. **选择 Docker SDK**: 在 "Choose an SDK" 步骤中,选择 "Docker"。
|
| 18 |
-
3. **上传文件**: 将此项目的所有文件(包括 `Dockerfile`)上传到你的 Space Git 仓库中。
|
| 19 |
-
4. **设置 Secrets**: 这是最重要的一步。你的 Notion 凭证和其他配置需要作为 Secrets 添加到 Space 中。进入你的 Space "Settings" 页面,找到 "Repository secrets" 部分,然后点击 "New secret" 添加以下变量:
|
| 20 |
|
| 21 |
-
|
| 22 |
-
* `NOTION_COOKIE`: 你的 Notion Cookie。你可以通过浏览器开发者工具获取。
|
| 23 |
-
* **可选**:
|
| 24 |
-
* `NOTION_SPACE_ID`: 你的 Notion Space ID。
|
| 25 |
-
* `NOTION_ACTIVE_USER_HEADER`: 你的 Notion 用户 ID。
|
| 26 |
-
* `PROXY_URL`: 如果你需要通过代理访问 Notion,请设置此项 (例如 `http://user:pass@host:port`)。
|
| 27 |
-
* `PROXY_AUTH_TOKEN`: 如果你的代理需要单独的认证令牌,请设置此项。
|
| 28 |
-
* `COOKIE_FILE`: 如果你使用文件管理多个 Cookie,请设置为文件名 (例如 `cookies.txt`)。请确保该文件也已上传到仓库中。
|
| 29 |
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
|
| 35 |
|
| 36 |
-
|
| 37 |
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
-
|
| 41 |
|
| 42 |
-
|
| 43 |
-
- `POST /v1/chat/completions` - 核心的聊天完成端点
|
| 44 |
-
- `GET /health` - 健康检查端点
|
| 45 |
|
| 46 |
-
|
| 47 |
|
| 48 |
```bash
|
| 49 |
-
|
| 50 |
-
-H "Content-Type: application/json" \
|
| 51 |
-
-d '{
|
| 52 |
-
"model": "openai-gpt-4.1",
|
| 53 |
-
"messages": [
|
| 54 |
-
{"role": "user", "content": "你好,请介绍一下你自己"}
|
| 55 |
-
],
|
| 56 |
-
"stream": true
|
| 57 |
-
}'
|
| 58 |
```
|
| 59 |
-
*请将 `<your-space-name>` 替换为你的 Space 名称。*
|
| 60 |
|
| 61 |
-
|
| 62 |
|
| 63 |
-
|
| 64 |
|
| 65 |
-
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
2. 将文件上传到你的 Space 仓库。
|
| 69 |
-
3. 在 Space Secrets 中设置 `COOKIE_FILE` 为你的文件名 (例如 `cookies.txt`)。
|
| 70 |
|
| 71 |
-
|
|
|
|
|
|
|
| 72 |
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
```
|
| 75 |
-
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
```
|
| 78 |
|
| 79 |
-
|
| 80 |
-
```
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
}
|
| 87 |
```
|
| 88 |
|
| 89 |
-
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
| 92 |
|
| 93 |
-
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
-
|
| 98 |
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
### 环境变量
|
| 105 |
-
创建 `.env` 文件,设置以下环境变量:
|
| 106 |
```
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
PROXY_URL=optional_proxy_url
|
| 111 |
-
PROXY_AUTH_TOKEN=your_auth_token
|
| 112 |
-
PORT=7860
|
| 113 |
```
|
| 114 |
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
| 118 |
```
|
| 119 |
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
```bash
|
| 123 |
-
#
|
| 124 |
npm run cookie
|
| 125 |
|
| 126 |
-
#
|
| 127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
```
|
|
|
|
| 1 |
+
# Notion API 轻量级客户端
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
这个项目提供了一个轻量级的 Notion API 客户端,可以在资源受限的环境(如 Termux)中运行,无需完整的浏览器环境。
|
| 4 |
|
| 5 |
+
## 特点
|
| 6 |
|
| 7 |
+
- 使用 `node-fetch` 代替 Playwright 浏览器自动化
|
| 8 |
+
- 轻量级设计,适合在移动设备和资源受限环境中运行
|
| 9 |
+
- 支持 Notion AI 的流式响应
|
| 10 |
+
- 兼容 OpenAI API 格式的请求和响应
|
| 11 |
|
| 12 |
+
## 安装
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
### 依赖项
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
确保安装以下依赖:
|
| 17 |
|
| 18 |
+
```bash
|
| 19 |
+
npm install
|
| 20 |
+
```
|
| 21 |
|
| 22 |
+
### 环境变量
|
| 23 |
|
| 24 |
+
创建 `.env` 文件,设置以下环境变量:
|
| 25 |
|
| 26 |
+
```
|
| 27 |
+
NOTION_COOKIE=your_notion_cookie_here
|
| 28 |
+
NOTION_SPACE_ID=optional_space_id
|
| 29 |
+
NOTION_ACTIVE_USER_HEADER=optional_user_id
|
| 30 |
+
PROXY_URL=optional_proxy_url
|
| 31 |
+
PROXY_AUTH_TOKEN=your_auth_token
|
| 32 |
+
PORT=7860
|
| 33 |
+
```
|
| 34 |
|
| 35 |
+
## 使用方法
|
| 36 |
|
| 37 |
+
### 启动服务
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
运行轻量级服务器:
|
| 40 |
|
| 41 |
```bash
|
| 42 |
+
npm start
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
```
|
|
|
|
| 44 |
|
| 45 |
+
服务器将在指定端口(默认 7860)启动。
|
| 46 |
|
| 47 |
+
如果需要使用原始的基于 Playwright 的版本(不推荐在 Termux 中使用):
|
| 48 |
|
| 49 |
+
```bash
|
| 50 |
+
npm run original
|
| 51 |
+
```
|
| 52 |
|
| 53 |
+
### API 端点
|
|
|
|
|
|
|
| 54 |
|
| 55 |
+
- `GET /v1/models` - 获取可用模型列表
|
| 56 |
+
- `POST /v1/chat/completions` - 聊天完成端点
|
| 57 |
+
- `GET /health` - 健康检查
|
| 58 |
|
| 59 |
+
### 在 Termux 中运行
|
| 60 |
+
|
| 61 |
+
1. 安装 Termux 和 Node.js:
|
| 62 |
+
```bash
|
| 63 |
+
pkg update
|
| 64 |
+
pkg install nodejs
|
| 65 |
```
|
| 66 |
+
|
| 67 |
+
2. 克隆项目并安装依赖:
|
| 68 |
+
```bash
|
| 69 |
+
git clone https://github.com/yourusername/notion2api-nodejs.git
|
| 70 |
+
cd notion2api-nodejs
|
| 71 |
+
npm install
|
| 72 |
```
|
| 73 |
|
| 74 |
+
3. 设置环境变量并运行:
|
| 75 |
+
```bash
|
| 76 |
+
npm start
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
## 作为模块集成
|
| 80 |
+
|
| 81 |
+
你也可以将轻量级客户端作为模块导入到你自己的项目中:
|
| 82 |
+
|
| 83 |
+
```javascript
|
| 84 |
+
import {
|
| 85 |
+
initialize,
|
| 86 |
+
streamNotionResponse,
|
| 87 |
+
buildNotionRequest,
|
| 88 |
+
FETCHED_IDS_SUCCESSFULLY
|
| 89 |
+
} from './lightweight-client.js';
|
| 90 |
+
|
| 91 |
+
// 初始化客户端
|
| 92 |
+
await initialize();
|
| 93 |
+
|
| 94 |
+
// 检查是否成功获取 Notion IDs
|
| 95 |
+
if (FETCHED_IDS_SUCCESSFULLY) {
|
| 96 |
+
// 构建请求
|
| 97 |
+
const requestData = {
|
| 98 |
+
notion_model: "openai-gpt-4.1",
|
| 99 |
+
messages: [
|
| 100 |
+
{ role: "user", content: "你好,请介绍一下自己" }
|
| 101 |
+
]
|
| 102 |
+
};
|
| 103 |
+
|
| 104 |
+
const notionRequestBody = buildNotionRequest(requestData);
|
| 105 |
+
|
| 106 |
+
// 获取响应流
|
| 107 |
+
const stream = await streamNotionResponse(notionRequestBody);
|
| 108 |
+
|
| 109 |
+
// 处理响应
|
| 110 |
+
stream.on('data', chunk => {
|
| 111 |
+
console.log(chunk.toString());
|
| 112 |
+
});
|
| 113 |
}
|
| 114 |
```
|
| 115 |
|
| 116 |
+
## 故障排除
|
| 117 |
|
| 118 |
+
- 如果无法获取 Notion IDs,请确保提供了有效的 NOTION_COOKIE
|
| 119 |
+
- 对于网络问题,可以尝试设置 PROXY_URL
|
| 120 |
+
- 查看日志输出以获取详细的错误信息
|
| 121 |
|
| 122 |
+
## 依赖说明
|
| 123 |
|
| 124 |
+
- `node-fetch`: 用于发送 HTTP 请求
|
| 125 |
+
- `jsdom`: 提供 DOM API 的轻量级模拟
|
| 126 |
+
- `dotenv`: 加载环境变量
|
| 127 |
+
- `express`: Web 服务器框架
|
| 128 |
+
- `https-proxy-agent`: 支持 HTTPS 代理
|
| 129 |
|
| 130 |
+
## Cookie管理功能
|
| 131 |
|
| 132 |
+
本项目新增了Cookie管理功能,可以更方便地管理多个Notion Cookie,避免在.env文件中手动编辑长字符串。
|
| 133 |
+
|
| 134 |
+
### 使用方法
|
| 135 |
+
|
| 136 |
+
#### 1. 通过文件管理Cookie
|
| 137 |
+
|
| 138 |
+
在项目根目录创建一个`cookies.txt`文件,每行一个完整的Cookie字符串:
|
| 139 |
|
|
|
|
|
|
|
| 140 |
```
|
| 141 |
+
cookie1_string_here
|
| 142 |
+
cookie2_string_here
|
| 143 |
+
cookie3_string_here
|
|
|
|
|
|
|
|
|
|
| 144 |
```
|
| 145 |
|
| 146 |
+
然后在`.env`文件中设置:
|
| 147 |
+
|
| 148 |
+
```
|
| 149 |
+
COOKIE_FILE=cookies.txt
|
| 150 |
```
|
| 151 |
|
| 152 |
+
系统启动时会自动从该文件加载Cookie。
|
| 153 |
+
|
| 154 |
+
#### 2. 使用Cookie管理工具
|
| 155 |
+
|
| 156 |
+
项目提供了一个命令行工具来管理Cookie:
|
| 157 |
+
|
| 158 |
```bash
|
| 159 |
+
# 使用npm脚本运行
|
| 160 |
npm run cookie
|
| 161 |
|
| 162 |
+
# 或者全局安装后运行
|
| 163 |
+
npm link
|
| 164 |
+
notion-cookie
|
| 165 |
+
```
|
| 166 |
+
|
| 167 |
+
命令行工具支持以下功能:
|
| 168 |
+
|
| 169 |
+
- `help`: 显示帮助信息
|
| 170 |
+
- `list`: 列出所有已加载的Cookie
|
| 171 |
+
- `add`: 添加新的Cookie
|
| 172 |
+
- `validate`: 验证所有Cookie的有效性
|
| 173 |
+
- `remove`: 删除指定的Cookie
|
| 174 |
+
- `save`: 保存Cookie到文件
|
| 175 |
+
- `load`: 从文件加载Cookie
|
| 176 |
+
- `exit`: 退出程序
|
| 177 |
+
|
| 178 |
+
### Cookie轮询机制
|
| 179 |
+
|
| 180 |
+
系统会自动轮询使用所有有效的Cookie,当一个Cookie返回401错误(未授权)时,会自动将其标记为无效并切换到下一个Cookie。这样可以提高系统的可靠性和可用性。
|
| 181 |
+
|
| 182 |
+
### 文件格式支持
|
| 183 |
+
|
| 184 |
+
Cookie管理器支持两种文件格式:
|
| 185 |
+
|
| 186 |
+
1. 文本格式(.txt):每行一个Cookie
|
| 187 |
+
2. JSON格式(.json):包含Cookie数组的JSON文件
|
| 188 |
+
|
| 189 |
+
```json
|
| 190 |
+
{
|
| 191 |
+
"cookies": [
|
| 192 |
+
"cookie1_string_here",
|
| 193 |
+
"cookie2_string_here"
|
| 194 |
+
],
|
| 195 |
+
"updatedAt": "2023-08-01T12:00:00.000Z",
|
| 196 |
+
"count": 2
|
| 197 |
+
}
|
| 198 |
+
```
|
| 199 |
+
|
| 200 |
+
或者简单的数组:
|
| 201 |
+
|
| 202 |
+
```json
|
| 203 |
+
[
|
| 204 |
+
"cookie1_string_here",
|
| 205 |
+
"cookie2_string_here"
|
| 206 |
+
]
|
| 207 |
```
|
src/ProxyPool.js
CHANGED
|
@@ -21,6 +21,7 @@ class ProxyPool {
|
|
| 21 |
* @param {boolean} options.useCache - 是否使用缓存,默认true
|
| 22 |
* @param {number} options.cacheExpiry - 缓存过期时间(毫秒),默认3600000 (1小时)
|
| 23 |
* @param {string} options.logLevel - 日志级别,可选值:'debug', 'info', 'warn', 'error', 'none',默认'info'
|
|
|
|
| 24 |
*/
|
| 25 |
constructor(options = {}) {
|
| 26 |
// 配置参数
|
|
@@ -38,6 +39,7 @@ class ProxyPool {
|
|
| 38 |
this.useCache = options.useCache !== undefined ? options.useCache : true;
|
| 39 |
this.cacheExpiry = options.cacheExpiry || 3600000; // 默认1小时
|
| 40 |
this.logLevel = options.logLevel || 'info'; // 默认日志级别为info
|
|
|
|
| 41 |
|
| 42 |
// 内部状态
|
| 43 |
this.availableProxies = [];
|
|
@@ -46,6 +48,13 @@ class ProxyPool {
|
|
| 46 |
this.isRefilling = false;
|
| 47 |
this.checkTimer = null;
|
| 48 |
this.proxyCache = new Map(); // 缓存验证过的代理
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
// 日志级别权重
|
| 51 |
this.logLevels = {
|
|
@@ -61,6 +70,14 @@ class ProxyPool {
|
|
| 61 |
this.removeProxy = this.removeProxy.bind(this);
|
| 62 |
this.checkAndRefill = this.checkAndRefill.bind(this);
|
| 63 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
/**
|
| 66 |
* 根据设置的日志级别输出日志
|
|
@@ -79,6 +96,62 @@ class ProxyPool {
|
|
| 79 |
}
|
| 80 |
}
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
/**
|
| 83 |
* 初始化代理池
|
| 84 |
* @returns {Promise<void>}
|
|
@@ -87,12 +160,21 @@ class ProxyPool {
|
|
| 87 |
if (this.isInitialized) return;
|
| 88 |
|
| 89 |
this.log('info', `初始化代理池,目标数量: ${this.targetCount}`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
await this.refillProxies();
|
| 91 |
|
| 92 |
// 设置定时检查
|
| 93 |
this.checkTimer = setInterval(this.checkAndRefill, this.checkInterval);
|
| 94 |
|
| 95 |
this.isInitialized = true;
|
|
|
|
|
|
|
|
|
|
| 96 |
this.log('info', `代理池初始化完成,当前可用代理数量: ${this.availableProxies.length}`);
|
| 97 |
}
|
| 98 |
|
|
@@ -127,6 +209,11 @@ class ProxyPool {
|
|
| 127 |
this.isRefilling = true;
|
| 128 |
this.log('info', `开始补充代理,当前数量: ${this.availableProxies.length},目标数量: ${this.targetCount}`);
|
| 129 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
let attempts = 0;
|
| 131 |
|
| 132 |
try {
|
|
@@ -136,6 +223,10 @@ class ProxyPool {
|
|
| 136 |
// 优先检查缓存中的代理
|
| 137 |
if (this.useCache && this.proxyCache.size > 0) {
|
| 138 |
await this.tryUsingCachedProxies(neededProxies);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
// 如果缓存中的代理不足,继续获取新代理
|
|
@@ -165,12 +256,19 @@ class ProxyPool {
|
|
| 165 |
continue;
|
| 166 |
}
|
| 167 |
|
|
|
|
|
|
|
|
|
|
| 168 |
// 测试代理
|
| 169 |
const results = await this.testProxiesConcurrently(newProxies);
|
| 170 |
|
| 171 |
// 添加可用代理
|
| 172 |
this.addValidProxies(results);
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
// 如果已经获取到足够的代理,提前结束
|
| 175 |
if (this.availableProxies.length >= this.targetCount) {
|
| 176 |
break;
|
|
@@ -184,8 +282,7 @@ class ProxyPool {
|
|
| 184 |
} catch (error) {
|
| 185 |
this.log('error', '补充代理过程中出错:', error);
|
| 186 |
} finally {
|
| 187 |
-
this.isRefilling = false;
|
| 188 |
-
|
| 189 |
if (this.availableProxies.length >= this.targetCount) {
|
| 190 |
this.log('info', `代理补充完成,当前可用代理: ${this.availableProxies.length}/${this.targetCount}`);
|
| 191 |
} else {
|
|
@@ -331,7 +428,7 @@ class ProxyPool {
|
|
| 331 |
const requestCount = count || this.batchSize;
|
| 332 |
// 限制请求数量最大为10
|
| 333 |
const actualCount = Math.min(requestCount, 10);
|
| 334 |
-
const url = `https://proxy.doudouzi.me/random
|
| 335 |
this.log('debug', `正在获取代理,URL: ${url}`);
|
| 336 |
|
| 337 |
const response = await axios.get(url, {
|
|
@@ -387,6 +484,16 @@ class ProxyPool {
|
|
| 387 |
// 分批处理代理
|
| 388 |
for (let i = 0; i < proxies.length; i += concurrentRequests) {
|
| 389 |
const batch = proxies.slice(i, i + concurrentRequests);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
const promises = batch.map(proxy => {
|
| 391 |
// 检查缓存中是否有近期验证过的结果
|
| 392 |
if (this.useCache && this.proxyCache.has(proxy)) {
|
|
@@ -414,6 +521,8 @@ class ProxyPool {
|
|
| 414 |
}
|
| 415 |
}
|
| 416 |
|
|
|
|
|
|
|
| 417 |
return results;
|
| 418 |
}
|
| 419 |
|
|
@@ -590,7 +699,8 @@ async function example() {
|
|
| 590 |
useCache: true, // 启用缓存
|
| 591 |
maxRefillAttempts: 15, // 减少最大尝试次数
|
| 592 |
retryDelay: 1000, // 减少重试延迟
|
| 593 |
-
logLevel: 'info'
|
|
|
|
| 594 |
});
|
| 595 |
|
| 596 |
// 初始化代理池
|
|
@@ -626,5 +736,7 @@ if (typeof require !== 'undefined' && require.main === module) {
|
|
| 626 |
// 导出 ProxyPool 类和实例
|
| 627 |
export default ProxyPool;
|
| 628 |
export const proxyPool = new ProxyPool({
|
| 629 |
-
logLevel: 'info' // 默认导出的实例使用info级别的日志
|
|
|
|
| 630 |
});
|
|
|
|
|
|
| 21 |
* @param {boolean} options.useCache - 是否使用缓存,默认true
|
| 22 |
* @param {number} options.cacheExpiry - 缓存过期时间(毫秒),默认3600000 (1小时)
|
| 23 |
* @param {string} options.logLevel - 日志级别,可选值:'debug', 'info', 'warn', 'error', 'none',默认'info'
|
| 24 |
+
* @param {boolean} options.showProgressBar - 是否显示进度条,默认false
|
| 25 |
*/
|
| 26 |
constructor(options = {}) {
|
| 27 |
// 配置参数
|
|
|
|
| 39 |
this.useCache = options.useCache !== undefined ? options.useCache : true;
|
| 40 |
this.cacheExpiry = options.cacheExpiry || 3600000; // 默认1小时
|
| 41 |
this.logLevel = options.logLevel || 'info'; // 默认日志级别为info
|
| 42 |
+
this.showProgressBar = options.showProgressBar !== undefined ? options.showProgressBar : false; // 是否显示进度条
|
| 43 |
|
| 44 |
// 内部状态
|
| 45 |
this.availableProxies = [];
|
|
|
|
| 48 |
this.isRefilling = false;
|
| 49 |
this.checkTimer = null;
|
| 50 |
this.proxyCache = new Map(); // 缓存验证过的代理
|
| 51 |
+
this.proxyCountry = 'us';
|
| 52 |
+
|
| 53 |
+
// 进度条状态
|
| 54 |
+
this.progressTotal = 0;
|
| 55 |
+
this.progressCurrent = 0;
|
| 56 |
+
this.progressBarLength = 30; // 进度条长度
|
| 57 |
+
this.lastProgressString = ''; // 上次显示的进度字符串
|
| 58 |
|
| 59 |
// 日志级别权重
|
| 60 |
this.logLevels = {
|
|
|
|
| 70 |
this.removeProxy = this.removeProxy.bind(this);
|
| 71 |
this.checkAndRefill = this.checkAndRefill.bind(this);
|
| 72 |
}
|
| 73 |
+
|
| 74 |
+
/**
|
| 75 |
+
* 设置代理国家
|
| 76 |
+
* @param {string} country - 代理国家 支持us uk jp de fr ca
|
| 77 |
+
*/
|
| 78 |
+
setCountry(country) {
|
| 79 |
+
this.proxyCountry = country;
|
| 80 |
+
}
|
| 81 |
|
| 82 |
/**
|
| 83 |
* 根据设置的日志级别输出日志
|
|
|
|
| 96 |
}
|
| 97 |
}
|
| 98 |
|
| 99 |
+
/**
|
| 100 |
+
* 显示进度条
|
| 101 |
+
* @param {string} message - 进度条前的消息
|
| 102 |
+
* @param {number} current - 当前进度
|
| 103 |
+
* @param {number} total - 总进度
|
| 104 |
+
* @param {boolean} [complete=false] - 是否完成
|
| 105 |
+
*/
|
| 106 |
+
showProgress(message, current, total) {
|
| 107 |
+
if (!this.showProgressBar) return;
|
| 108 |
+
|
| 109 |
+
// 确保进度不超过总数
|
| 110 |
+
current = Math.min(current, total);
|
| 111 |
+
|
| 112 |
+
// 计算进度百分比
|
| 113 |
+
const percent = total > 0 ? Math.floor((current / total) * 100) : 0;
|
| 114 |
+
|
| 115 |
+
// 创建终端风格的箭头进度条
|
| 116 |
+
let bar = '';
|
| 117 |
+
let ProcessBarLength = 15;
|
| 118 |
+
let CurrentProcessBarLength = Math.floor(ProcessBarLength*(percent/100));
|
| 119 |
+
let ProcessBarRemainLength = ProcessBarLength-CurrentProcessBarLength;
|
| 120 |
+
let ProcessBar = '-'.repeat(CurrentProcessBarLength)+'>';
|
| 121 |
+
let ProcessBarRemain = '·'.repeat(ProcessBarRemainLength);
|
| 122 |
+
if (percent === 100) {
|
| 123 |
+
// 完成状态显示完整箭头
|
| 124 |
+
bar = '['+'-'.repeat(ProcessBarLength)+'>'+']';
|
| 125 |
+
} else {
|
| 126 |
+
bar = '['+ProcessBar+ProcessBarRemain+']';
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
// 构建进度字符串,添加百分比
|
| 130 |
+
const progressString = `${message} ${bar} ${current}/${total} (${percent}%)`;
|
| 131 |
+
|
| 132 |
+
// 如果进度字符串与上次相同,则不重复显示
|
| 133 |
+
if (progressString === this.lastProgressString) {
|
| 134 |
+
return;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
// 清除上一行
|
| 138 |
+
if (this.lastProgressString) {
|
| 139 |
+
process.stdout.clearLine(0);
|
| 140 |
+
process.stdout.cursorTo(0);
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
// 显示进度条
|
| 144 |
+
process.stdout.write(progressString);
|
| 145 |
+
|
| 146 |
+
// 如果完成,换行
|
| 147 |
+
if (percent === 100) {
|
| 148 |
+
process.stdout.write('\n');
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// 保存当前进度字符串
|
| 152 |
+
this.lastProgressString = progressString;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
/**
|
| 156 |
* 初始化代理池
|
| 157 |
* @returns {Promise<void>}
|
|
|
|
| 160 |
if (this.isInitialized) return;
|
| 161 |
|
| 162 |
this.log('info', `初始化代理池,目标数量: ${this.targetCount}`);
|
| 163 |
+
|
| 164 |
+
// 重置进度条状态
|
| 165 |
+
this.progressTotal = this.targetCount;
|
| 166 |
+
this.progressCurrent = 0;
|
| 167 |
+
//this.showProgress('初始化代理池进度', this.progressCurrent, this.progressTotal);
|
| 168 |
+
|
| 169 |
await this.refillProxies();
|
| 170 |
|
| 171 |
// 设置定时检查
|
| 172 |
this.checkTimer = setInterval(this.checkAndRefill, this.checkInterval);
|
| 173 |
|
| 174 |
this.isInitialized = true;
|
| 175 |
+
|
| 176 |
+
// 完成进度条
|
| 177 |
+
this.showProgress('补充代理进度', this.availableProxies.length, this.progressTotal);
|
| 178 |
this.log('info', `代理池初始化完成,当前可用代理数量: ${this.availableProxies.length}`);
|
| 179 |
}
|
| 180 |
|
|
|
|
| 209 |
this.isRefilling = true;
|
| 210 |
this.log('info', `开始补充代理,当前数量: ${this.availableProxies.length},目标数量: ${this.targetCount}`);
|
| 211 |
|
| 212 |
+
// 设置进度条状态
|
| 213 |
+
this.progressTotal = this.targetCount;
|
| 214 |
+
this.progressCurrent = this.availableProxies.length;
|
| 215 |
+
this.showProgress('补充代理进度', this.progressCurrent, this.progressTotal);
|
| 216 |
+
|
| 217 |
let attempts = 0;
|
| 218 |
|
| 219 |
try {
|
|
|
|
| 223 |
// 优先检查缓存中的代理
|
| 224 |
if (this.useCache && this.proxyCache.size > 0) {
|
| 225 |
await this.tryUsingCachedProxies(neededProxies);
|
| 226 |
+
|
| 227 |
+
// 更新进度条
|
| 228 |
+
this.progressCurrent = this.availableProxies.length;
|
| 229 |
+
this.showProgress('补充代理进度', this.progressCurrent, this.progressTotal);
|
| 230 |
}
|
| 231 |
|
| 232 |
// 如果缓存中的代理不足,继续获取新代理
|
|
|
|
| 256 |
continue;
|
| 257 |
}
|
| 258 |
|
| 259 |
+
// 保存当前进度,测试代理过程中会临时更新进度条
|
| 260 |
+
const previousProgress = this.progressCurrent;
|
| 261 |
+
|
| 262 |
// 测试代理
|
| 263 |
const results = await this.testProxiesConcurrently(newProxies);
|
| 264 |
|
| 265 |
// 添加可用代理
|
| 266 |
this.addValidProxies(results);
|
| 267 |
|
| 268 |
+
// 更新实际进度
|
| 269 |
+
this.progressCurrent = this.availableProxies.length;
|
| 270 |
+
this.showProgress('补充代理进度', this.progressCurrent, this.progressTotal);
|
| 271 |
+
|
| 272 |
// 如果已经获取到足够的代理,提前结束
|
| 273 |
if (this.availableProxies.length >= this.targetCount) {
|
| 274 |
break;
|
|
|
|
| 282 |
} catch (error) {
|
| 283 |
this.log('error', '补充代理过程中出错:', error);
|
| 284 |
} finally {
|
| 285 |
+
this.isRefilling = false;
|
|
|
|
| 286 |
if (this.availableProxies.length >= this.targetCount) {
|
| 287 |
this.log('info', `代理补充完成,当前可用代理: ${this.availableProxies.length}/${this.targetCount}`);
|
| 288 |
} else {
|
|
|
|
| 428 |
const requestCount = count || this.batchSize;
|
| 429 |
// 限制请求数量最大为10
|
| 430 |
const actualCount = Math.min(requestCount, 10);
|
| 431 |
+
const url = `https://proxy.doudouzi.me/random/${this.proxyCountry}?number=${actualCount}&protocol=${this.proxyProtocol}&type=json`;
|
| 432 |
this.log('debug', `正在获取代理,URL: ${url}`);
|
| 433 |
|
| 434 |
const response = await axios.get(url, {
|
|
|
|
| 484 |
// 分批处理代理
|
| 485 |
for (let i = 0; i < proxies.length; i += concurrentRequests) {
|
| 486 |
const batch = proxies.slice(i, i + concurrentRequests);
|
| 487 |
+
|
| 488 |
+
// 不再显示测试代理的单独进度条,而是更新补充代理的总体进度
|
| 489 |
+
if (this.showProgressBar) {
|
| 490 |
+
// 计算当前测试进度对应的总体进度
|
| 491 |
+
const testedPercent = i / proxies.length;
|
| 492 |
+
const progressStep = Math.min(1, testedPercent) * 0.2; // 测试部分占总体进度的20%
|
| 493 |
+
const currentProgress = this.progressCurrent + progressStep * (this.targetCount - this.progressCurrent);
|
| 494 |
+
this.showProgress('补充代理进度', Math.floor(currentProgress), this.progressTotal);
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
const promises = batch.map(proxy => {
|
| 498 |
// 检查缓存中是否有近期验证过的结果
|
| 499 |
if (this.useCache && this.proxyCache.has(proxy)) {
|
|
|
|
| 521 |
}
|
| 522 |
}
|
| 523 |
|
| 524 |
+
// 不再显示测试完成的进度条
|
| 525 |
+
|
| 526 |
return results;
|
| 527 |
}
|
| 528 |
|
|
|
|
| 699 |
useCache: true, // 启用缓存
|
| 700 |
maxRefillAttempts: 15, // 减少最大尝试次数
|
| 701 |
retryDelay: 1000, // 减少重试延迟
|
| 702 |
+
logLevel: 'info', // 设置日志级别
|
| 703 |
+
showProgressBar: true // 启用进度条
|
| 704 |
});
|
| 705 |
|
| 706 |
// 初始化代理池
|
|
|
|
| 736 |
// 导出 ProxyPool 类和实例
|
| 737 |
export default ProxyPool;
|
| 738 |
export const proxyPool = new ProxyPool({
|
| 739 |
+
logLevel: 'info', // 默认导出的实例使用info级别的日志
|
| 740 |
+
showProgressBar: true // 启用进度条
|
| 741 |
});
|
| 742 |
+
|
src/ProxyServer.js
CHANGED
|
@@ -26,13 +26,7 @@ class ProxyServer {
|
|
| 26 |
this.proxyProcess = null;
|
| 27 |
this.platform = process.env.PROXY_SERVER_PLATFORM || 'auto';
|
| 28 |
this.port = process.env.PROXY_SERVER_PORT || 10655;
|
| 29 |
-
|
| 30 |
-
// 检查是否在Linux环境下,并且没有指定日志路径
|
| 31 |
-
const defaultLogPath = os.platform() === 'linux' && !process.env.PROXY_SERVER_LOG_PATH
|
| 32 |
-
? '/tmp/proxy_server.log'
|
| 33 |
-
: './proxy_server.log';
|
| 34 |
-
|
| 35 |
-
this.logPath = process.env.PROXY_SERVER_LOG_PATH || defaultLogPath;
|
| 36 |
this.enabled = process.env.ENABLE_PROXY_SERVER === 'true';
|
| 37 |
this.proxyAuthToken = process.env.PROXY_AUTH_TOKEN || 'default_token';
|
| 38 |
this.logStream = null;
|
|
@@ -103,11 +97,9 @@ class ProxyServer {
|
|
| 103 |
// 确保可执行文件有执行权限(在Linux/Android上)
|
| 104 |
if (this.detectPlatform() !== 'windows') {
|
| 105 |
try {
|
| 106 |
-
|
| 107 |
-
// fs.chmodSync(proxyServerPath, 0o755);
|
| 108 |
} catch (err) {
|
| 109 |
-
|
| 110 |
-
// logger.warning(`无法设置执行权限: ${err.message}`);
|
| 111 |
}
|
| 112 |
}
|
| 113 |
|
|
|
|
| 26 |
this.proxyProcess = null;
|
| 27 |
this.platform = process.env.PROXY_SERVER_PLATFORM || 'auto';
|
| 28 |
this.port = process.env.PROXY_SERVER_PORT || 10655;
|
| 29 |
+
this.logPath = process.env.PROXY_SERVER_LOG_PATH || './proxy_server.log';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
this.enabled = process.env.ENABLE_PROXY_SERVER === 'true';
|
| 31 |
this.proxyAuthToken = process.env.PROXY_AUTH_TOKEN || 'default_token';
|
| 32 |
this.logStream = null;
|
|
|
|
| 97 |
// 确保可执行文件有执行权限(在Linux/Android上)
|
| 98 |
if (this.detectPlatform() !== 'windows') {
|
| 99 |
try {
|
| 100 |
+
fs.chmodSync(proxyServerPath, 0o755);
|
|
|
|
| 101 |
} catch (err) {
|
| 102 |
+
logger.warning(`无法设置执行权限: ${err.message}`);
|
|
|
|
| 103 |
}
|
| 104 |
}
|
| 105 |
|
src/lightweight-client-express.js
CHANGED
|
@@ -137,7 +137,9 @@ app.get('/v1/models', authenticate, (req, res) => {
|
|
| 137 |
{ id: "openai-gpt-4.1" },
|
| 138 |
{ id: "anthropic-opus-4" },
|
| 139 |
{ id: "anthropic-sonnet-4" },
|
| 140 |
-
{ id: "anthropic-sonnet-3.x-stable" }
|
|
|
|
|
|
|
| 141 |
]
|
| 142 |
};
|
| 143 |
|
|
@@ -319,7 +321,7 @@ app.get('/cookies/status', authenticate, (req, res) => {
|
|
| 319 |
const PORT = process.env.PORT || 7860;
|
| 320 |
|
| 321 |
// 设置代理池日志级别为warn,减少详细日志输出
|
| 322 |
-
proxyPool.logLevel = '
|
| 323 |
|
| 324 |
// 初始化并启动服务器
|
| 325 |
initialize().then(() => {
|
|
|
|
| 137 |
{ id: "openai-gpt-4.1" },
|
| 138 |
{ id: "anthropic-opus-4" },
|
| 139 |
{ id: "anthropic-sonnet-4" },
|
| 140 |
+
{ id: "anthropic-sonnet-3.x-stable" },
|
| 141 |
+
{ id: "google-gemini-2.5-pro"}, //vertex-gemini-2.5-pro
|
| 142 |
+
{ id: "google-gemini-2.5-flash"}, //vertex-gemini-2.5-flash
|
| 143 |
]
|
| 144 |
};
|
| 145 |
|
|
|
|
| 321 |
const PORT = process.env.PORT || 7860;
|
| 322 |
|
| 323 |
// 设置代理池日志级别为warn,减少详细日志输出
|
| 324 |
+
proxyPool.logLevel = 'error';
|
| 325 |
|
| 326 |
// 初始化并启动服务器
|
| 327 |
initialize().then(() => {
|
src/lightweight-client.js
CHANGED
|
@@ -100,7 +100,22 @@ function buildNotionRequest(requestData) {
|
|
| 100 |
value: new NotionTranscriptConfigValue({
|
| 101 |
})
|
| 102 |
}));
|
| 103 |
-
}else{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
transcript.push(new NotionTranscriptItem({
|
| 105 |
type: "config",
|
| 106 |
value: new NotionTranscriptConfigValue({
|
|
@@ -840,8 +855,19 @@ async function initialize() {
|
|
| 840 |
if (process.env.USE_NATIVE_PROXY_POOL === 'true') {
|
| 841 |
logger.info(`正在初始化本地代理池...`);
|
| 842 |
// 设置代理池的日志级别为warn,减少详细日志输出
|
| 843 |
-
proxyPool.logLevel = '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 844 |
await proxyPool.initialize();
|
|
|
|
|
|
|
| 845 |
}
|
| 846 |
|
| 847 |
INITIALIZED_SUCCESSFULLY = true;
|
|
|
|
| 100 |
value: new NotionTranscriptConfigValue({
|
| 101 |
})
|
| 102 |
}));
|
| 103 |
+
} else if(requestData.model === 'google-gemini-2.5-pro'){
|
| 104 |
+
transcript.push(new NotionTranscriptItem({
|
| 105 |
+
type: "config",
|
| 106 |
+
value: new NotionTranscriptConfigValue({
|
| 107 |
+
model: 'vertex-gemini-2.5-pro'
|
| 108 |
+
})
|
| 109 |
+
}));
|
| 110 |
+
} else if (requestData.model === 'google-gemini-2.5-flash'){
|
| 111 |
+
transcript.push(new NotionTranscriptItem({
|
| 112 |
+
type: "config",
|
| 113 |
+
value: new NotionTranscriptConfigValue({
|
| 114 |
+
model: 'vertex-gemini-2.5-flash'
|
| 115 |
+
})
|
| 116 |
+
}));
|
| 117 |
+
}
|
| 118 |
+
else{
|
| 119 |
transcript.push(new NotionTranscriptItem({
|
| 120 |
type: "config",
|
| 121 |
value: new NotionTranscriptConfigValue({
|
|
|
|
| 855 |
if (process.env.USE_NATIVE_PROXY_POOL === 'true') {
|
| 856 |
logger.info(`正在初始化本地代理池...`);
|
| 857 |
// 设置代理池的日志级别为warn,减少详细日志输出
|
| 858 |
+
proxyPool.logLevel = 'error';
|
| 859 |
+
// 启用进度条显示
|
| 860 |
+
proxyPool.showProgressBar = true;
|
| 861 |
+
|
| 862 |
+
if (['us', 'uk', 'jp', 'de', 'fr', 'ca'].includes(process.env.PROXY_COUNTRY)) {
|
| 863 |
+
proxyPool.setCountry(process.env.PROXY_COUNTRY);
|
| 864 |
+
} else {
|
| 865 |
+
logger.warning(`未设置正确PROXY_COUNTRY,使用默认代理国家: us`);
|
| 866 |
+
proxyPool.setCountry('us');
|
| 867 |
+
}
|
| 868 |
await proxyPool.initialize();
|
| 869 |
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
| 870 |
+
logger.success(`代理池初始化完成,当前代理国家: ${proxyPool.proxyCountry}`);
|
| 871 |
}
|
| 872 |
|
| 873 |
INITIALIZED_SUCCESSFULLY = true;
|