gemini-proxy-client 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -60
- package/dist/browser/manager.js +7 -3
- package/dist/cli.js +1 -1
- package/dist/commands/start.js +18 -3
- package/dist/utils/helpers.js +72 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
- **反指纹检测**: 使用 Camoufox (基于 Firefox) 防止 Google 检测多账号
|
|
8
8
|
- **自动登录管理**: 首次需要手动登录,之后自动保持登录状态
|
|
9
|
+
- **自动连接**: 自动点击 "Continue to the app" 和 "Connect WS" 按钮
|
|
10
|
+
- **连接验证**: 自动检测 WebSocket 连接是否成功(按钮文字变为 "Disconnect WS")
|
|
9
11
|
- **自动保活**: 每 2 小时检查登录状态,断线自动重连
|
|
10
12
|
- **无头运行**: 登录后可在后台无头运行,节省资源
|
|
11
13
|
|
|
12
14
|
## 安装
|
|
13
15
|
|
|
14
|
-
### 方式 1: 从 npm 安装(推荐)
|
|
15
|
-
|
|
16
16
|
```bash
|
|
17
17
|
npm install -g gemini-proxy-client
|
|
18
18
|
```
|
|
@@ -23,19 +23,9 @@ npm install -g gemini-proxy-client
|
|
|
23
23
|
npx camoufox-js fetch
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
### 方式 2: 从源码安装
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
git clone https://github.com/user/gemini-proxy-panel.git
|
|
30
|
-
cd gemini-proxy-panel/client
|
|
31
|
-
npm install
|
|
32
|
-
npm run build
|
|
33
|
-
npm link # 可选,全局安装
|
|
34
|
-
```
|
|
35
|
-
|
|
36
26
|
## 使用方法
|
|
37
27
|
|
|
38
|
-
###
|
|
28
|
+
### 启动客户端
|
|
39
29
|
|
|
40
30
|
```bash
|
|
41
31
|
# 连接到代理服务器
|
|
@@ -47,17 +37,81 @@ gemini-client start -s wss://your-server.com/v1/ws
|
|
|
47
37
|
|
|
48
38
|
**首次运行**会打开浏览器窗口,请登录您的 Google 账号。登录成功后,cookies 会自动保存,之后运行将使用无头模式。
|
|
49
39
|
|
|
50
|
-
|
|
40
|
+
**成功启动**后会显示:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
✅ 点击了 "Continue to the app" 按钮
|
|
44
|
+
✅ 点击了连接按钮
|
|
45
|
+
检测按钮文字: "Disconnect WS"
|
|
46
|
+
✅ WebSocket 连接成功!
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 后台运行
|
|
50
|
+
|
|
51
|
+
#### 方式 1: 使用 nohup(简单)
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
nohup gemini-client start -s wss://your-server.com/v1/ws > gemini-client.log 2>&1 &
|
|
55
|
+
|
|
56
|
+
# 查看日志
|
|
57
|
+
tail -f gemini-client.log
|
|
55
58
|
|
|
56
|
-
#
|
|
57
|
-
gemini-client start
|
|
59
|
+
# 停止
|
|
60
|
+
kill $(ps aux | grep 'gemini-client start' | grep -v grep | awk '{print $2}')
|
|
58
61
|
```
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
#### 方式 2: 使用 screen
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# 安装 screen
|
|
67
|
+
sudo apt-get install screen # Ubuntu/Debian
|
|
68
|
+
sudo yum install screen # CentOS/RHEL
|
|
69
|
+
|
|
70
|
+
# 创建会话并运行
|
|
71
|
+
screen -S gemini
|
|
72
|
+
gemini-client start
|
|
73
|
+
|
|
74
|
+
# 按 Ctrl+A 然后按 D 分离会话
|
|
75
|
+
# 重新连接: screen -r gemini
|
|
76
|
+
# 查看所有会话: screen -ls
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
#### 方式 3: 使用 pm2(推荐)
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 安装 pm2
|
|
83
|
+
npm install -g pm2
|
|
84
|
+
|
|
85
|
+
# 启动
|
|
86
|
+
pm2 start "gemini-client start" --name gemini-client
|
|
87
|
+
|
|
88
|
+
# 查看日志
|
|
89
|
+
pm2 logs gemini-client
|
|
90
|
+
|
|
91
|
+
# 停止
|
|
92
|
+
pm2 stop gemini-client
|
|
93
|
+
|
|
94
|
+
# 重启
|
|
95
|
+
pm2 restart gemini-client
|
|
96
|
+
|
|
97
|
+
# 开机自启
|
|
98
|
+
pm2 startup
|
|
99
|
+
pm2 save
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### 方式 4: 使用 Xvfb(无图形界面服务器)
|
|
103
|
+
|
|
104
|
+
如果是纯命令行服务器,需要安装虚拟显示:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Ubuntu/Debian
|
|
108
|
+
sudo apt-get install xvfb
|
|
109
|
+
|
|
110
|
+
# 使用 xvfb-run 运行
|
|
111
|
+
nohup xvfb-run gemini-client start -s wss://your-server.com/v1/ws > gemini-client.log 2>&1 &
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 其他命令
|
|
61
115
|
|
|
62
116
|
```bash
|
|
63
117
|
# 单独登录 Google 账号
|
|
@@ -77,42 +131,14 @@ gemini-client --help
|
|
|
77
131
|
|
|
78
132
|
### start - 启动客户端
|
|
79
133
|
|
|
80
|
-
```bash
|
|
81
|
-
gemini-client start [options]
|
|
82
|
-
```
|
|
83
|
-
|
|
84
134
|
| 选项 | 说明 | 默认值 |
|
|
85
135
|
|------|------|--------|
|
|
86
136
|
| `-s, --server <url>` | 代理服务器 WebSocket 地址 | `ws://localhost:5345/v1/ws` |
|
|
87
|
-
| `-t, --token <token>` | 客户端认证令牌 | 自动生成 |
|
|
137
|
+
| `-t, --token <token>` | 客户端认证令牌 | 自动生成 `gp_client_YYYY_MM_DD_HH_mm_ss` |
|
|
88
138
|
| `-d, --daemon` | 后台运行模式 | false |
|
|
89
139
|
| `--headless` | 强制无头模式 | 自动判断 |
|
|
90
140
|
| `--data-dir <path>` | 数据目录路径 | `~/.gemini-client` |
|
|
91
141
|
|
|
92
|
-
### login - 登录 Google
|
|
93
|
-
|
|
94
|
-
```bash
|
|
95
|
-
gemini-client login [options]
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
打开浏览器让用户登录 Google 账号,保存 cookies 供后续使用。
|
|
99
|
-
|
|
100
|
-
### status - 查看状态
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
gemini-client status [options]
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
显示当前登录状态和配置信息。
|
|
107
|
-
|
|
108
|
-
### logout - 清除登录
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
gemini-client logout [options]
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
清除保存的 Google 登录 cookies。
|
|
115
|
-
|
|
116
142
|
## 数据存储
|
|
117
143
|
|
|
118
144
|
默认数据目录: `~/.gemini-client/`
|
|
@@ -122,10 +148,21 @@ gemini-client logout [options]
|
|
|
122
148
|
| `cookies.json` | Google 登录 cookies |
|
|
123
149
|
| `config.json` | 配置信息 |
|
|
124
150
|
|
|
151
|
+
## 工作流程
|
|
152
|
+
|
|
153
|
+
1. 启动 Camoufox 浏览器
|
|
154
|
+
2. 打开 Build App 页面
|
|
155
|
+
3. 检查 Google 登录状态,未登录则等待用户手动登录
|
|
156
|
+
4. 点击 "Continue to the app" 按钮(如果存在)
|
|
157
|
+
5. 关闭可能遮挡的模态框
|
|
158
|
+
6. 在 Build App iframe 中点击 "Connect WS" 按钮
|
|
159
|
+
7. 验证连接成功(按钮文字变为 "Disconnect WS")
|
|
160
|
+
8. 启动保活机制(每 2 小时检查登录状态)
|
|
161
|
+
|
|
125
162
|
## 保活机制
|
|
126
163
|
|
|
127
164
|
- **登录状态检查**: 每 2 小时检查一次 Google 登录状态
|
|
128
|
-
- **连接状态检查**: 每 30
|
|
165
|
+
- **连接状态检查**: 每 30 秒检查一次页面是否正常
|
|
129
166
|
- **自动重连**: 连接断开时自动尝试重新连接
|
|
130
167
|
- **登录过期处理**: 登录过期时会弹出浏览器让用户重新登录
|
|
131
168
|
|
|
@@ -134,39 +171,36 @@ gemini-client logout [options]
|
|
|
134
171
|
- Node.js >= 18.0.0
|
|
135
172
|
- 能够访问 GitHub(下载 Camoufox 浏览器)
|
|
136
173
|
- 能够访问 Google AI Studio(非中国大陆 IP)
|
|
174
|
+
- 图形界面或 Xvfb(虚拟显示)
|
|
137
175
|
|
|
138
176
|
## 常见问题
|
|
139
177
|
|
|
140
178
|
### Q: Camoufox 下载失败怎么办?
|
|
141
179
|
|
|
142
|
-
|
|
180
|
+
设置代理或手动下载:
|
|
143
181
|
|
|
144
182
|
```bash
|
|
145
|
-
# 设置代理
|
|
146
183
|
export https_proxy=http://127.0.0.1:7890
|
|
147
|
-
|
|
148
|
-
# 重新下载
|
|
149
184
|
npx camoufox-js fetch
|
|
150
185
|
```
|
|
151
186
|
|
|
152
187
|
### Q: Google 登录状态能保持多久?
|
|
153
188
|
|
|
154
|
-
|
|
155
|
-
- IP 地址变化
|
|
156
|
-
- 长时间无活动
|
|
157
|
-
- Google 安全检测
|
|
158
|
-
|
|
159
|
-
客户端会每 2 小时检查一次,失效时会提示重新登录。
|
|
189
|
+
通常 1-2 周,但可能因 IP 变化、长时间无活动或 Google 安全检测而提前失效。
|
|
160
190
|
|
|
161
191
|
### Q: 可以同时运行多个客户端吗?
|
|
162
192
|
|
|
163
|
-
|
|
193
|
+
可以,使用不同的 `--data-dir` 参数:
|
|
164
194
|
|
|
165
195
|
```bash
|
|
166
196
|
gemini-client start -s wss://server.com/v1/ws --data-dir ~/.gemini-client-1
|
|
167
197
|
gemini-client start -s wss://server.com/v1/ws --data-dir ~/.gemini-client-2
|
|
168
198
|
```
|
|
169
199
|
|
|
200
|
+
### Q: 如何查看客户端是否连接成功?
|
|
201
|
+
|
|
202
|
+
启动后查看日志,如果看到 `✅ WebSocket 连接成功!` 和 `检测按钮文字: "Disconnect WS"`,说明连接成功。
|
|
203
|
+
|
|
170
204
|
## License
|
|
171
205
|
|
|
172
206
|
MIT
|
package/dist/browser/manager.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Camoufox } from 'camoufox-js';
|
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
-
import { sleep, formatUptime } from '../utils/helpers.js';
|
|
5
|
+
import { sleep, formatUptime, getRandomFingerprint } from '../utils/helpers.js';
|
|
6
6
|
// Build App URL
|
|
7
7
|
const BUILD_APP_URL = 'https://aistudio.google.com/apps/drive/1JqM2cAKs9japdW99aJwkg2k6GZSeCSHR?showPreview=true&pli=1&showAssistant=true';
|
|
8
8
|
const GOOGLE_LOGIN_URL = 'https://accounts.google.com/';
|
|
@@ -28,12 +28,16 @@ export class BrowserManager {
|
|
|
28
28
|
* 启动浏览器
|
|
29
29
|
*/
|
|
30
30
|
async launch() {
|
|
31
|
+
// 获取随机指纹配置(同一 dataDir 会保持一致)
|
|
32
|
+
const fingerprint = getRandomFingerprint(this.options.dataDir);
|
|
33
|
+
console.log(chalk.gray(`指纹配置: ${fingerprint.viewport.width}x${fingerprint.viewport.height}, ${fingerprint.locale}, ${fingerprint.timezoneId}`));
|
|
31
34
|
this.browser = await Camoufox({
|
|
32
35
|
headless: this.options.headless,
|
|
33
36
|
});
|
|
34
37
|
this.context = await this.browser.newContext({
|
|
35
|
-
viewport:
|
|
36
|
-
locale:
|
|
38
|
+
viewport: fingerprint.viewport,
|
|
39
|
+
locale: fingerprint.locale,
|
|
40
|
+
timezoneId: fingerprint.timezoneId,
|
|
37
41
|
});
|
|
38
42
|
// 加载已保存的 cookies
|
|
39
43
|
await this.loadCookies();
|
package/dist/cli.js
CHANGED
package/dist/commands/start.js
CHANGED
|
@@ -79,7 +79,7 @@ export async function startClient(options) {
|
|
|
79
79
|
}
|
|
80
80
|
const spinner = ora('启动 Camoufox 浏览器...').start();
|
|
81
81
|
try {
|
|
82
|
-
|
|
82
|
+
let browserManager = new BrowserManager({
|
|
83
83
|
headless,
|
|
84
84
|
dataDir,
|
|
85
85
|
serverUrl: server,
|
|
@@ -103,14 +103,29 @@ export async function startClient(options) {
|
|
|
103
103
|
// 保存 cookies
|
|
104
104
|
await browserManager.saveCookies();
|
|
105
105
|
console.log(chalk.green('✅ Google 登录成功!已保存登录状态'));
|
|
106
|
-
|
|
106
|
+
// 关闭有界面的浏览器,以无头模式重新启动
|
|
107
|
+
console.log(chalk.gray('🔄 切换到无头模式...'));
|
|
108
|
+
await browserManager.close();
|
|
109
|
+
// 创建新的无头浏览器实例
|
|
110
|
+
browserManager = new BrowserManager({
|
|
111
|
+
headless: true,
|
|
112
|
+
dataDir,
|
|
113
|
+
serverUrl: server,
|
|
114
|
+
token,
|
|
115
|
+
});
|
|
116
|
+
// 重新设置信号处理
|
|
117
|
+
setupSignalHandlers(browserManager);
|
|
118
|
+
spinner.start('以无头模式重新启动...');
|
|
119
|
+
await browserManager.launch();
|
|
120
|
+
spinner.text = '打开 Build App 页面...';
|
|
121
|
+
await browserManager.openBuildApp();
|
|
107
122
|
}
|
|
108
123
|
// 点击连接按钮
|
|
109
124
|
spinner.text = '点击 ws connect 按钮...';
|
|
110
125
|
await browserManager.clickConnectButton();
|
|
111
126
|
spinner.succeed(chalk.green('已连接到代理服务器!'));
|
|
112
127
|
console.log(chalk.gray('─'.repeat(50)));
|
|
113
|
-
console.log(chalk.green(`✅
|
|
128
|
+
console.log(chalk.green(`✅ 客户端已启动 (无头模式)`));
|
|
114
129
|
console.log(chalk.white(` Token: ${token}`));
|
|
115
130
|
console.log(chalk.gray(' 按 Ctrl+C 退出'));
|
|
116
131
|
console.log(chalk.gray('─'.repeat(50)));
|
package/dist/utils/helpers.js
CHANGED
|
@@ -55,3 +55,75 @@ export function formatUptime(startTime) {
|
|
|
55
55
|
export function sleep(ms) {
|
|
56
56
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
57
57
|
}
|
|
58
|
+
// 常见的屏幕分辨率
|
|
59
|
+
const VIEWPORTS = [
|
|
60
|
+
{ width: 1920, height: 1080 },
|
|
61
|
+
{ width: 1366, height: 768 },
|
|
62
|
+
{ width: 1536, height: 864 },
|
|
63
|
+
{ width: 1440, height: 900 },
|
|
64
|
+
{ width: 1280, height: 720 },
|
|
65
|
+
{ width: 1600, height: 900 },
|
|
66
|
+
{ width: 1280, height: 800 },
|
|
67
|
+
{ width: 1680, height: 1050 },
|
|
68
|
+
{ width: 1360, height: 768 },
|
|
69
|
+
{ width: 1280, height: 1024 },
|
|
70
|
+
];
|
|
71
|
+
// 常见的语言区域
|
|
72
|
+
const LOCALES = [
|
|
73
|
+
'en-US',
|
|
74
|
+
'en-GB',
|
|
75
|
+
'en-AU',
|
|
76
|
+
'en-CA',
|
|
77
|
+
'zh-CN',
|
|
78
|
+
'zh-TW',
|
|
79
|
+
'ja-JP',
|
|
80
|
+
'ko-KR',
|
|
81
|
+
'de-DE',
|
|
82
|
+
'fr-FR',
|
|
83
|
+
];
|
|
84
|
+
// 常见的时区
|
|
85
|
+
const TIMEZONES = [
|
|
86
|
+
'America/New_York',
|
|
87
|
+
'America/Los_Angeles',
|
|
88
|
+
'America/Chicago',
|
|
89
|
+
'Europe/London',
|
|
90
|
+
'Europe/Paris',
|
|
91
|
+
'Europe/Berlin',
|
|
92
|
+
'Asia/Tokyo',
|
|
93
|
+
'Asia/Shanghai',
|
|
94
|
+
'Asia/Seoul',
|
|
95
|
+
'Australia/Sydney',
|
|
96
|
+
];
|
|
97
|
+
/**
|
|
98
|
+
* 生成随机指纹配置
|
|
99
|
+
* 如果提供了 dataDir,会尝试加载/保存固定的指纹配置
|
|
100
|
+
*/
|
|
101
|
+
export function getRandomFingerprint(dataDir) {
|
|
102
|
+
const fingerprintPath = dataDir ? path.join(dataDir, 'fingerprint.json') : null;
|
|
103
|
+
// 尝试加载已保存的指纹
|
|
104
|
+
if (fingerprintPath && fs.existsSync(fingerprintPath)) {
|
|
105
|
+
try {
|
|
106
|
+
const saved = JSON.parse(fs.readFileSync(fingerprintPath, 'utf-8'));
|
|
107
|
+
return saved;
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
// 忽略,生成新的
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// 生成新的随机指纹
|
|
114
|
+
const fingerprint = {
|
|
115
|
+
viewport: VIEWPORTS[Math.floor(Math.random() * VIEWPORTS.length)],
|
|
116
|
+
locale: LOCALES[Math.floor(Math.random() * LOCALES.length)],
|
|
117
|
+
timezoneId: TIMEZONES[Math.floor(Math.random() * TIMEZONES.length)],
|
|
118
|
+
};
|
|
119
|
+
// 保存指纹以便下次使用(同一账号应该保持一致的指纹)
|
|
120
|
+
if (fingerprintPath) {
|
|
121
|
+
try {
|
|
122
|
+
fs.writeFileSync(fingerprintPath, JSON.stringify(fingerprint, null, 2));
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
// 忽略保存错误
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return fingerprint;
|
|
129
|
+
}
|