qwenproxy-cli 1.0.6 → 1.0.7
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/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/services/human-behavior.ts +36 -20
- package/src/services/playwright.ts +107 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
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
|
}
|
|
@@ -1201,12 +1201,13 @@ export async function initPlaywrightForAccount(
|
|
|
1201
1201
|
// If a context limit is configured, make room by closing idle contexts.
|
|
1202
1202
|
await evictIdlePlaywrightContextsToLimit().catch(() => {});
|
|
1203
1203
|
|
|
1204
|
+
const profilePath = getAccountProfilePath(account.id);
|
|
1204
1205
|
const fingerprint = getFingerprintProfile(account.id);
|
|
1205
|
-
const
|
|
1206
|
-
const storageState = loadStorageState(account.id);
|
|
1206
|
+
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
1207
1207
|
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1208
|
+
const launchOptions = {
|
|
1209
|
+
headless,
|
|
1210
|
+
channel,
|
|
1210
1211
|
userAgent: fingerprint.userAgent,
|
|
1211
1212
|
locale: fingerprint.locale,
|
|
1212
1213
|
timezoneId: fingerprint.timezoneId,
|
|
@@ -1215,13 +1216,37 @@ export async function initPlaywrightForAccount(
|
|
|
1215
1216
|
deviceScaleFactor: 1,
|
|
1216
1217
|
isMobile: false,
|
|
1217
1218
|
hasTouch: false,
|
|
1218
|
-
colorScheme: "light",
|
|
1219
|
+
colorScheme: "light" as const,
|
|
1219
1220
|
extraHTTPHeaders: {
|
|
1220
1221
|
"sec-ch-ua": fingerprint.secChUa,
|
|
1221
1222
|
"sec-ch-ua-mobile": "?0",
|
|
1222
1223
|
"sec-ch-ua-platform": '"Windows"',
|
|
1223
1224
|
},
|
|
1224
|
-
|
|
1225
|
+
ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
|
|
1226
|
+
args: buildChromiumLaunchArgs(fingerprint.viewport),
|
|
1227
|
+
};
|
|
1228
|
+
|
|
1229
|
+
let acctContext: BrowserContext;
|
|
1230
|
+
try {
|
|
1231
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1232
|
+
} catch (launchErr: any) {
|
|
1233
|
+
if (launchErr?.message?.includes("Executable doesn't exist")) {
|
|
1234
|
+
autoInstallPlaywrightChromium();
|
|
1235
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1236
|
+
} else {
|
|
1237
|
+
throw launchErr;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
try {
|
|
1242
|
+
const v = acctContext.browser()?.version();
|
|
1243
|
+
if (v) {
|
|
1244
|
+
const major = parseInt(v.split(".")[0], 10);
|
|
1245
|
+
if (major >= 100) {
|
|
1246
|
+
updateChromeMajor(major);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
} catch {}
|
|
1225
1250
|
|
|
1226
1251
|
try {
|
|
1227
1252
|
// Comprehensive stealth scripts for anti-bot evasion
|
|
@@ -1230,7 +1255,38 @@ export async function initPlaywrightForAccount(
|
|
|
1230
1255
|
await hook(acctContext);
|
|
1231
1256
|
}
|
|
1232
1257
|
|
|
1233
|
-
|
|
1258
|
+
// If native profile cookies are empty but a backup storage_state.json exists, restore cookies
|
|
1259
|
+
const storageState = loadStorageState(account.id);
|
|
1260
|
+
if (storageState) {
|
|
1261
|
+
try {
|
|
1262
|
+
const raw = fs.readFileSync(storageState, "utf8");
|
|
1263
|
+
const parsed = JSON.parse(raw);
|
|
1264
|
+
if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
|
|
1265
|
+
const currentCookies = await acctContext.cookies();
|
|
1266
|
+
if (currentCookies.length === 0) {
|
|
1267
|
+
await acctContext.addCookies(parsed.cookies).catch(() => {});
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
} catch {}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// Persistent contexts may already contain an initial about:blank tab.
|
|
1274
|
+
// Reuse it instead of creating a second tab. Prefer a tab already on the
|
|
1275
|
+
// Qwen origin when one exists.
|
|
1276
|
+
const existingPages = acctContext.pages().filter((p) => !p.isClosed());
|
|
1277
|
+
const acctPage =
|
|
1278
|
+
existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
|
|
1279
|
+
existingPages[0] ??
|
|
1280
|
+
(await acctContext.newPage());
|
|
1281
|
+
|
|
1282
|
+
// Close any extra blank tabs that may have been created by the browser
|
|
1283
|
+
// profile/startup, but keep the primary page selected above.
|
|
1284
|
+
for (const extraPage of existingPages.slice(1)) {
|
|
1285
|
+
if (extraPage !== acctPage && extraPage.url() === "about:blank") {
|
|
1286
|
+
await extraPage.close({ runBeforeUnload: false }).catch(() => {});
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1234
1290
|
acctPage.setDefaultTimeout(config.timeouts.page);
|
|
1235
1291
|
acctPage.setDefaultNavigationTimeout(config.timeouts.navigation);
|
|
1236
1292
|
accountContexts.set(account.id, acctContext);
|
|
@@ -1345,13 +1401,12 @@ export async function validateAccountLogin(
|
|
|
1345
1401
|
);
|
|
1346
1402
|
try {
|
|
1347
1403
|
if (accountPages.has(account.id)) return true;
|
|
1348
|
-
|
|
1404
|
+
const profilePath = getAccountProfilePath(account.id);
|
|
1349
1405
|
const fingerprint = getFingerprintProfile(account.id);
|
|
1350
|
-
const
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
...(storageState ? { storageState } : {}),
|
|
1406
|
+
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
1407
|
+
const launchOptions = {
|
|
1408
|
+
headless,
|
|
1409
|
+
channel,
|
|
1355
1410
|
userAgent: fingerprint.userAgent,
|
|
1356
1411
|
locale: fingerprint.locale,
|
|
1357
1412
|
timezoneId: fingerprint.timezoneId,
|
|
@@ -1360,18 +1415,51 @@ export async function validateAccountLogin(
|
|
|
1360
1415
|
deviceScaleFactor: 1,
|
|
1361
1416
|
isMobile: false,
|
|
1362
1417
|
hasTouch: false,
|
|
1363
|
-
colorScheme: "light",
|
|
1418
|
+
colorScheme: "light" as const,
|
|
1364
1419
|
extraHTTPHeaders: {
|
|
1365
1420
|
"sec-ch-ua": fingerprint.secChUa,
|
|
1366
1421
|
"sec-ch-ua-mobile": "?0",
|
|
1367
1422
|
"sec-ch-ua-platform": '"Windows"',
|
|
1368
1423
|
},
|
|
1369
|
-
|
|
1424
|
+
ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
|
|
1425
|
+
args: buildChromiumLaunchArgs(fingerprint.viewport),
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
let acctContext: BrowserContext;
|
|
1429
|
+
try {
|
|
1430
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1431
|
+
} catch (launchErr: any) {
|
|
1432
|
+
if (launchErr?.message?.includes("Executable doesn't exist")) {
|
|
1433
|
+
autoInstallPlaywrightChromium();
|
|
1434
|
+
acctContext = await engine.launchPersistentContext(profilePath, launchOptions);
|
|
1435
|
+
} else {
|
|
1436
|
+
throw launchErr;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1370
1439
|
|
|
1371
1440
|
try {
|
|
1372
1441
|
await acctContext.addInitScript(getStealthScript(fingerprint));
|
|
1373
|
-
const acctPage = await acctContext.newPage();
|
|
1374
1442
|
|
|
1443
|
+
// If native profile cookies are empty but a backup storage_state.json exists, restore cookies
|
|
1444
|
+
const storageState = loadStorageState(account.id);
|
|
1445
|
+
if (storageState) {
|
|
1446
|
+
try {
|
|
1447
|
+
const raw = fs.readFileSync(storageState, "utf8");
|
|
1448
|
+
const parsed = JSON.parse(raw);
|
|
1449
|
+
if (Array.isArray(parsed.cookies) && parsed.cookies.length > 0) {
|
|
1450
|
+
const currentCookies = await acctContext.cookies();
|
|
1451
|
+
if (currentCookies.length === 0) {
|
|
1452
|
+
await acctContext.addCookies(parsed.cookies).catch(() => {});
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
} catch {}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const existingPages = acctContext.pages().filter((p) => !p.isClosed());
|
|
1459
|
+
const acctPage =
|
|
1460
|
+
existingPages.find((p) => p.url().startsWith(qwenOrigin())) ??
|
|
1461
|
+
existingPages[0] ??
|
|
1462
|
+
(await acctContext.newPage());
|
|
1375
1463
|
// Check if already logged in via cookies
|
|
1376
1464
|
const cookies = await acctContext.cookies();
|
|
1377
1465
|
const hasAuthCookie = cookies.some(
|
|
@@ -2789,6 +2877,9 @@ export function isPlaywrightAlreadyClosedError(error: unknown): boolean {
|
|
|
2789
2877
|
message.includes("Target page, context or browser has been closed") ||
|
|
2790
2878
|
message.includes("Browser has been closed") ||
|
|
2791
2879
|
message.includes("Target closed") ||
|
|
2880
|
+
message.includes("Target crashed") ||
|
|
2881
|
+
message.includes("Page crashed") ||
|
|
2882
|
+
message.includes("Assertion error") ||
|
|
2792
2883
|
message.includes("Cannot find parent object") ||
|
|
2793
2884
|
message.includes("Connection closed")
|
|
2794
2885
|
);
|