qwenproxy-cli 1.0.6 → 1.0.8
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/bin/qwenproxy.js +41 -13
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/services/human-behavior.ts +36 -20
- package/src/services/playwright.ts +139 -38
package/bin/qwenproxy.js
CHANGED
|
@@ -134,25 +134,53 @@ if (isBrowserCommand) {
|
|
|
134
134
|
const patchrightEntry = require.resolve("patchright");
|
|
135
135
|
cliPath = path.join(path.dirname(patchrightEntry), "cli.js");
|
|
136
136
|
} catch {}
|
|
137
|
-
const installEnv = {
|
|
138
|
-
...process.env,
|
|
139
|
-
PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
|
|
140
|
-
process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "300000",
|
|
141
|
-
};
|
|
142
137
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
const runInstall = (mirrorHost) => {
|
|
139
|
+
const installEnv = {
|
|
140
|
+
...process.env,
|
|
141
|
+
PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
|
|
142
|
+
process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "120000",
|
|
143
|
+
...(mirrorHost ? { PLAYWRIGHT_DOWNLOAD_HOST: mirrorHost } : {}),
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
if (cliPath && fs.existsSync(cliPath)) {
|
|
147
|
+
return spawnSync(process.execPath, [cliPath, "install", "chromium"], {
|
|
148
|
+
stdio: "inherit",
|
|
149
|
+
env: installEnv,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
149
152
|
const cmd = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
150
|
-
spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
|
|
153
|
+
return spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
|
|
151
154
|
stdio: "inherit",
|
|
152
155
|
env: installEnv,
|
|
153
156
|
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
let chosenHost = process.env.PLAYWRIGHT_DOWNLOAD_HOST;
|
|
160
|
+
if (!chosenHost) {
|
|
161
|
+
// Probe official CDN; if unresponsive within 2s, switch to global mirror immediately
|
|
162
|
+
const controller = new AbortController();
|
|
163
|
+
const probeTimer = setTimeout(() => controller.abort(), 2000);
|
|
164
|
+
try {
|
|
165
|
+
const probe = await fetch("https://cdn.playwright.dev", { method: "HEAD", signal: controller.signal });
|
|
166
|
+
clearTimeout(probeTimer);
|
|
167
|
+
if (probe.status >= 500) chosenHost = "https://npmmirror.com/mirrors/playwright";
|
|
168
|
+
} catch {
|
|
169
|
+
clearTimeout(probeTimer);
|
|
170
|
+
chosenHost = "https://npmmirror.com/mirrors/playwright";
|
|
171
|
+
console.log("🌐 [QwenProxy] CDN oficial indisponível na sua região. Usando espelho global de alta velocidade...");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let res = runInstall(chosenHost);
|
|
176
|
+
if (res.status !== 0 && !chosenHost) {
|
|
177
|
+
console.log("\n⚠️ [QwenProxy] Falha no CDN primário. Tentando espelho global de alta velocidade...");
|
|
178
|
+
res = runInstall("https://npmmirror.com/mirrors/playwright");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (res.status === 0) {
|
|
182
|
+
console.log("✓ [QwenProxy] Navegador instalado com sucesso!\n");
|
|
154
183
|
}
|
|
155
|
-
console.log("✓ [QwenProxy] Navegador instalado com sucesso!\n");
|
|
156
184
|
}
|
|
157
185
|
} catch {}
|
|
158
186
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,9 @@ process.on('uncaughtException', (error: unknown) => {
|
|
|
21
21
|
msg.includes('Target page, context or browser has been closed') ||
|
|
22
22
|
msg.includes('Browser has been closed') ||
|
|
23
23
|
msg.includes('Target closed') ||
|
|
24
|
+
msg.includes('Target crashed') ||
|
|
25
|
+
msg.includes('Page crashed') ||
|
|
26
|
+
msg.includes('Assertion error') ||
|
|
24
27
|
msg.includes('Connection closed')
|
|
25
28
|
) {
|
|
26
29
|
console.warn(`⚠️ [Playwright] Handled benign driver teardown exception: ${msg}`)
|
|
@@ -36,6 +39,9 @@ process.on('unhandledRejection', (reason: unknown) => {
|
|
|
36
39
|
msg.includes('Target page, context or browser has been closed') ||
|
|
37
40
|
msg.includes('Browser has been closed') ||
|
|
38
41
|
msg.includes('Target closed') ||
|
|
42
|
+
msg.includes('Target crashed') ||
|
|
43
|
+
msg.includes('Page crashed') ||
|
|
44
|
+
msg.includes('Assertion error') ||
|
|
39
45
|
msg.includes('Connection closed')
|
|
40
46
|
) {
|
|
41
47
|
console.warn(`⚠️ [Playwright] Handled benign driver teardown rejection: ${msg}`)
|
|
@@ -147,27 +147,43 @@ export async function subtlePageActivity(page: Page): Promise<void> {
|
|
|
147
147
|
const viewport = page.viewportSize();
|
|
148
148
|
if (!viewport) return;
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
try {
|
|
151
|
+
const x = Math.floor(viewport.width * (0.25 + Math.random() * 0.5));
|
|
152
|
+
const y = Math.floor(viewport.height * (0.25 + Math.random() * 0.5));
|
|
153
|
+
await page.mouse.move(x, y, { steps: 6 + Math.floor(Math.random() * 8) });
|
|
153
154
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
if (Math.random() < 0.35) {
|
|
156
|
+
await page.mouse.wheel(0, Math.random() < 0.5 ? 60 : -60).catch(() => {});
|
|
157
|
+
}
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
159
|
+
await page
|
|
160
|
+
.evaluate(() => {
|
|
161
|
+
try {
|
|
162
|
+
const target = document.querySelector(
|
|
163
|
+
'[data-testid="sidebar"], .sidebar, nav, aside, main',
|
|
164
|
+
);
|
|
165
|
+
if (target) {
|
|
166
|
+
target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: true }));
|
|
167
|
+
target.dispatchEvent(new MouseEvent("mouseleave", { bubbles: true }));
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// Best-effort keep-alive only.
|
|
167
171
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
.
|
|
172
|
+
})
|
|
173
|
+
.catch(() => {});
|
|
174
|
+
} catch (err: any) {
|
|
175
|
+
if (page.isClosed()) return;
|
|
176
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
+
if (
|
|
178
|
+
msg.includes("Target page, context or browser has been closed") ||
|
|
179
|
+
msg.includes("Browser has been closed") ||
|
|
180
|
+
msg.includes("Target closed") ||
|
|
181
|
+
msg.includes("Target crashed") ||
|
|
182
|
+
msg.includes("Page crashed") ||
|
|
183
|
+
msg.includes("Connection closed")
|
|
184
|
+
) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
173
189
|
}
|
|
@@ -13,30 +13,40 @@ import { createRequire } from "module";
|
|
|
13
13
|
const requireLocal = createRequire(import.meta.url);
|
|
14
14
|
|
|
15
15
|
function autoInstallPlaywrightChromium(): void {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
const tryInstall = (mirror?: string): number | null => {
|
|
17
|
+
const installEnv = {
|
|
18
|
+
...process.env,
|
|
19
|
+
PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT:
|
|
20
|
+
process.env.PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT || "120000",
|
|
21
|
+
...(mirror ? { PLAYWRIGHT_DOWNLOAD_HOST: mirror } : {}),
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
const patchrightEntry = requireLocal.resolve("patchright");
|
|
25
|
+
const cliPath = path.join(path.dirname(patchrightEntry), "cli.js");
|
|
26
|
+
if (fs.existsSync(cliPath)) {
|
|
27
|
+
console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando automaticamente...");
|
|
28
|
+
const res = spawnSync(process.execPath, [cliPath, "install", "chromium"], {
|
|
29
|
+
stdio: "inherit",
|
|
30
|
+
env: installEnv,
|
|
31
|
+
});
|
|
32
|
+
return res.status;
|
|
33
|
+
}
|
|
34
|
+
} catch {}
|
|
35
|
+
|
|
36
|
+
console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando via npx...");
|
|
37
|
+
const cmd = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
38
|
+
const res = spawnSync(cmd, ["--yes", "patchright", "install", "chromium"], {
|
|
39
|
+
stdio: "inherit",
|
|
40
|
+
env: installEnv,
|
|
41
|
+
});
|
|
42
|
+
return res.status;
|
|
20
43
|
};
|
|
21
|
-
try {
|
|
22
|
-
const patchrightEntry = requireLocal.resolve("patchright");
|
|
23
|
-
const cliPath = path.join(path.dirname(patchrightEntry), "cli.js");
|
|
24
|
-
if (fs.existsSync(cliPath)) {
|
|
25
|
-
console.log("⏳ [Playwright] Navegador Chromium não encontrado. Instalando automaticamente...");
|
|
26
|
-
spawnSync(process.execPath, [cliPath, "install", "chromium"], {
|
|
27
|
-
stdio: "inherit",
|
|
28
|
-
env: installEnv,
|
|
29
|
-
});
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
} catch {}
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
45
|
+
const status = tryInstall(process.env.PLAYWRIGHT_DOWNLOAD_HOST);
|
|
46
|
+
if (status !== 0 && !process.env.PLAYWRIGHT_DOWNLOAD_HOST) {
|
|
47
|
+
console.log("\n⚠️ [Playwright] Falha no CDN primário. Tentando espelho global de alta velocidade...");
|
|
48
|
+
tryInstall("https://npmmirror.com/mirrors/playwright");
|
|
49
|
+
}
|
|
40
50
|
}
|
|
41
51
|
import { loadAccounts, type QwenAccount } from "../core/accounts.ts";
|
|
42
52
|
// Imported here rather than injected from session-keeper.ts: account-concurrency
|
|
@@ -1201,12 +1211,13 @@ export async function initPlaywrightForAccount(
|
|
|
1201
1211
|
// If a context limit is configured, make room by closing idle contexts.
|
|
1202
1212
|
await evictIdlePlaywrightContextsToLimit().catch(() => {});
|
|
1203
1213
|
|
|
1214
|
+
const profilePath = getAccountProfilePath(account.id);
|
|
1204
1215
|
const fingerprint = getFingerprintProfile(account.id);
|
|
1205
|
-
const
|
|
1206
|
-
const storageState = loadStorageState(account.id);
|
|
1216
|
+
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
1207
1217
|
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1218
|
+
const launchOptions = {
|
|
1219
|
+
headless,
|
|
1220
|
+
channel,
|
|
1210
1221
|
userAgent: fingerprint.userAgent,
|
|
1211
1222
|
locale: fingerprint.locale,
|
|
1212
1223
|
timezoneId: fingerprint.timezoneId,
|
|
@@ -1215,13 +1226,37 @@ export async function initPlaywrightForAccount(
|
|
|
1215
1226
|
deviceScaleFactor: 1,
|
|
1216
1227
|
isMobile: false,
|
|
1217
1228
|
hasTouch: false,
|
|
1218
|
-
colorScheme: "light",
|
|
1229
|
+
colorScheme: "light" as const,
|
|
1219
1230
|
extraHTTPHeaders: {
|
|
1220
1231
|
"sec-ch-ua": fingerprint.secChUa,
|
|
1221
1232
|
"sec-ch-ua-mobile": "?0",
|
|
1222
1233
|
"sec-ch-ua-platform": '"Windows"',
|
|
1223
1234
|
},
|
|
1224
|
-
|
|
1235
|
+
ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
|
|
1236
|
+
args: buildChromiumLaunchArgs(fingerprint.viewport),
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
let acctContext: BrowserContext;
|
|
1240
|
+
try {
|
|
1241
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1242
|
+
} catch (launchErr: any) {
|
|
1243
|
+
if (launchErr?.message?.includes("Executable doesn't exist")) {
|
|
1244
|
+
autoInstallPlaywrightChromium();
|
|
1245
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1246
|
+
} else {
|
|
1247
|
+
throw launchErr;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
try {
|
|
1252
|
+
const v = acctContext.browser()?.version();
|
|
1253
|
+
if (v) {
|
|
1254
|
+
const major = parseInt(v.split(".")[0], 10);
|
|
1255
|
+
if (major >= 100) {
|
|
1256
|
+
updateChromeMajor(major);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
} catch {}
|
|
1225
1260
|
|
|
1226
1261
|
try {
|
|
1227
1262
|
// Comprehensive stealth scripts for anti-bot evasion
|
|
@@ -1230,7 +1265,38 @@ export async function initPlaywrightForAccount(
|
|
|
1230
1265
|
await hook(acctContext);
|
|
1231
1266
|
}
|
|
1232
1267
|
|
|
1233
|
-
|
|
1268
|
+
// If native profile cookies are empty but a backup storage_state.json exists, restore cookies
|
|
1269
|
+
const storageState = loadStorageState(account.id);
|
|
1270
|
+
if (storageState) {
|
|
1271
|
+
try {
|
|
1272
|
+
const raw = fs.readFileSync(storageState, "utf8");
|
|
1273
|
+
const parsed = JSON.parse(raw);
|
|
1274
|
+
if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
|
|
1275
|
+
const currentCookies = await acctContext.cookies();
|
|
1276
|
+
if (currentCookies.length === 0) {
|
|
1277
|
+
await acctContext.addCookies(parsed.cookies).catch(() => {});
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
} catch {}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Persistent contexts may already contain an initial about:blank tab.
|
|
1284
|
+
// Reuse it instead of creating a second tab. Prefer a tab already on the
|
|
1285
|
+
// Qwen origin when one exists.
|
|
1286
|
+
const existingPages = acctContext.pages().filter((p) => !p.isClosed());
|
|
1287
|
+
const acctPage =
|
|
1288
|
+
existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
|
|
1289
|
+
existingPages[0] ??
|
|
1290
|
+
(await acctContext.newPage());
|
|
1291
|
+
|
|
1292
|
+
// Close any extra blank tabs that may have been created by the browser
|
|
1293
|
+
// profile/startup, but keep the primary page selected above.
|
|
1294
|
+
for (const extraPage of existingPages.slice(1)) {
|
|
1295
|
+
if (extraPage !== acctPage && extraPage.url() === "about:blank") {
|
|
1296
|
+
await extraPage.close({ runBeforeUnload: false }).catch(() => {});
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1234
1300
|
acctPage.setDefaultTimeout(config.timeouts.page);
|
|
1235
1301
|
acctPage.setDefaultNavigationTimeout(config.timeouts.navigation);
|
|
1236
1302
|
accountContexts.set(account.id, acctContext);
|
|
@@ -1345,13 +1411,12 @@ export async function validateAccountLogin(
|
|
|
1345
1411
|
);
|
|
1346
1412
|
try {
|
|
1347
1413
|
if (accountPages.has(account.id)) return true;
|
|
1348
|
-
|
|
1414
|
+
const profilePath = getAccountProfilePath(account.id);
|
|
1349
1415
|
const fingerprint = getFingerprintProfile(account.id);
|
|
1350
|
-
const
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
...(storageState ? { storageState } : {}),
|
|
1416
|
+
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
1417
|
+
const launchOptions = {
|
|
1418
|
+
headless,
|
|
1419
|
+
channel,
|
|
1355
1420
|
userAgent: fingerprint.userAgent,
|
|
1356
1421
|
locale: fingerprint.locale,
|
|
1357
1422
|
timezoneId: fingerprint.timezoneId,
|
|
@@ -1360,18 +1425,51 @@ export async function validateAccountLogin(
|
|
|
1360
1425
|
deviceScaleFactor: 1,
|
|
1361
1426
|
isMobile: false,
|
|
1362
1427
|
hasTouch: false,
|
|
1363
|
-
colorScheme: "light",
|
|
1428
|
+
colorScheme: "light" as const,
|
|
1364
1429
|
extraHTTPHeaders: {
|
|
1365
1430
|
"sec-ch-ua": fingerprint.secChUa,
|
|
1366
1431
|
"sec-ch-ua-mobile": "?0",
|
|
1367
1432
|
"sec-ch-ua-platform": '"Windows"',
|
|
1368
1433
|
},
|
|
1369
|
-
|
|
1434
|
+
ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
|
|
1435
|
+
args: buildChromiumLaunchArgs(fingerprint.viewport),
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1438
|
+
let acctContext: BrowserContext;
|
|
1439
|
+
try {
|
|
1440
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1441
|
+
} catch (launchErr: any) {
|
|
1442
|
+
if (launchErr?.message?.includes("Executable doesn't exist")) {
|
|
1443
|
+
autoInstallPlaywrightChromium();
|
|
1444
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1445
|
+
} else {
|
|
1446
|
+
throw launchErr;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1370
1449
|
|
|
1371
1450
|
try {
|
|
1372
1451
|
await acctContext.addInitScript(getStealthScript(fingerprint));
|
|
1373
|
-
const acctPage = await acctContext.newPage();
|
|
1374
1452
|
|
|
1453
|
+
// If native profile cookies are empty but a backup storage_state.json exists, restore cookies
|
|
1454
|
+
const storageState = loadStorageState(account.id);
|
|
1455
|
+
if (storageState) {
|
|
1456
|
+
try {
|
|
1457
|
+
const raw = fs.readFileSync(storageState, "utf8");
|
|
1458
|
+
const parsed = JSON.parse(raw);
|
|
1459
|
+
if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
|
|
1460
|
+
const currentCookies = await acctContext.cookies();
|
|
1461
|
+
if (currentCookies.length === 0) {
|
|
1462
|
+
await acctContext.addCookies(parsed.cookies).catch(() => {});
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} catch {}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
const existingPages = acctContext.pages().filter((p) => !p.isClosed());
|
|
1469
|
+
const acctPage =
|
|
1470
|
+
existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
|
|
1471
|
+
existingPages[0] ??
|
|
1472
|
+
(await acctContext.newPage());
|
|
1375
1473
|
// Check if already logged in via cookies
|
|
1376
1474
|
const cookies = await acctContext.cookies();
|
|
1377
1475
|
const hasAuthCookie = cookies.some(
|
|
@@ -2789,6 +2887,9 @@ export function isPlaywrightAlreadyClosedError(error: unknown): boolean {
|
|
|
2789
2887
|
message.includes("Target page, context or browser has been closed") ||
|
|
2790
2888
|
message.includes("Browser has been closed") ||
|
|
2791
2889
|
message.includes("Target closed") ||
|
|
2890
|
+
message.includes("Target crashed") ||
|
|
2891
|
+
message.includes("Page crashed") ||
|
|
2892
|
+
message.includes("Assertion error") ||
|
|
2792
2893
|
message.includes("Cannot find parent object") ||
|
|
2793
2894
|
message.includes("Connection closed")
|
|
2794
2895
|
);
|