qwenproxy-cli 1.6.9 → 1.6.11
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/dist/clean-cache.js +199 -55
- package/dist/delete-chats.js +269 -78
- package/dist/index.js +290 -88
- package/dist/login.js +203 -67
- package/dist/reset-cooldowns.js +199 -55
- package/package.json +2 -2
- package/src/api/server.ts +8 -4
- package/src/core/version.ts +1 -1
- package/src/core/watchdog.ts +4 -3
- package/src/routes/chat/account.ts +12 -0
- package/src/routes/chat/streaming.ts +18 -2
- package/src/services/captcha-coordinator.ts +36 -8
- package/src/services/chat-cleanup.ts +11 -2
- package/src/services/playwright.ts +244 -74
- package/src/services/qwen.ts +29 -3
package/dist/delete-chats.js
CHANGED
|
@@ -2185,6 +2185,26 @@ function clearAccountCooldown(accountId) {
|
|
|
2185
2185
|
}
|
|
2186
2186
|
}
|
|
2187
2187
|
}
|
|
2188
|
+
function getAccountCooldownInfo(accountId) {
|
|
2189
|
+
const entry = cooldowns.get(accountId);
|
|
2190
|
+
if (!entry) return null;
|
|
2191
|
+
const remaining = entry.until - Date.now();
|
|
2192
|
+
if (remaining <= 0) {
|
|
2193
|
+
cooldowns.delete(accountId);
|
|
2194
|
+
if (accountId !== "global") {
|
|
2195
|
+
try {
|
|
2196
|
+
updateAccountCooldown(accountId, 0, null);
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
console.error(
|
|
2199
|
+
`\u274C [AccountManager] Failed to clear expired cooldown in DB:`,
|
|
2200
|
+
err.message
|
|
2201
|
+
);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
return { onCooldown: true, remainingMs: remaining, reason: entry.reason };
|
|
2207
|
+
}
|
|
2188
2208
|
function markAccountHeadersReady(accountId) {
|
|
2189
2209
|
if (!accountId || accountId === "global") return;
|
|
2190
2210
|
headersReadyAccounts.add(accountId);
|
|
@@ -3519,7 +3539,19 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
|
|
|
3519
3539
|
if (localStorage.getItem("qwen_token_logged_out_marker")) {
|
|
3520
3540
|
return false;
|
|
3521
3541
|
}
|
|
3522
|
-
const
|
|
3542
|
+
const lsToken = (localStorage.getItem("token") || "").trim();
|
|
3543
|
+
if (lsToken && !/(?:^|;\s*)token=/.test(document.cookie || "")) {
|
|
3544
|
+
try {
|
|
3545
|
+
document.cookie = `token=${encodeURIComponent(lsToken)}; path=/; domain=.qwen.ai; max-age=31536000`;
|
|
3546
|
+
} catch {
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
const authHeaders = lsToken ? { Authorization: `Bearer ${lsToken}` } : {};
|
|
3550
|
+
const res = await fetch("/api/v1/auths/", {
|
|
3551
|
+
method: "GET",
|
|
3552
|
+
headers: authHeaders,
|
|
3553
|
+
credentials: "include"
|
|
3554
|
+
});
|
|
3523
3555
|
if (res.status !== 200) return false;
|
|
3524
3556
|
const json = await res.json().catch(() => null);
|
|
3525
3557
|
if (!json) return false;
|
|
@@ -3537,6 +3569,7 @@ async function isPageLoggedIn(page, timeoutMs = SESSION_PROBE_NAVIGATION_TIMEOUT
|
|
|
3537
3569
|
try {
|
|
3538
3570
|
const settingsRes = await fetch("/api/v2/users/user/settings", {
|
|
3539
3571
|
method: "GET",
|
|
3572
|
+
headers: authHeaders,
|
|
3540
3573
|
credentials: "include",
|
|
3541
3574
|
signal: AbortSignal.timeout(3e3)
|
|
3542
3575
|
});
|
|
@@ -4324,14 +4357,15 @@ async function getBasicHeaders(accountId) {
|
|
|
4324
4357
|
`\u26A0\uFE0F [Playwright] Header refresh failed for ${accountId}; retaining the previous valid cache: ${getErrorMessage(error)}`
|
|
4325
4358
|
);
|
|
4326
4359
|
}
|
|
4327
|
-
} else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress
|
|
4328
|
-
|
|
4360
|
+
} else if (hadUsableHeaders && headersAge > HEADER_CACHE_TTL * HEADER_REFRESH_THRESHOLD && !cache.refreshInProgress && // Header capture types into the chat input and sends a probe message on
|
|
4361
|
+
// the account's page. Doing that while the account is serving a stream
|
|
4362
|
+
// would mutate the live conversation (and can navigate the SPA), so the
|
|
4363
|
+
// proactive refresh waits until the account has no active lease.
|
|
4364
|
+
!hasActiveAccountLease(accountId)) {
|
|
4329
4365
|
refreshHeadersInternal(accountId).catch((error) => {
|
|
4330
4366
|
console.warn(
|
|
4331
4367
|
`\u26A0\uFE0F [Playwright] Background header refresh failed for ${accountId}: ${getErrorMessage(error)}`
|
|
4332
4368
|
);
|
|
4333
|
-
}).finally(() => {
|
|
4334
|
-
cache.refreshInProgress = false;
|
|
4335
4369
|
});
|
|
4336
4370
|
}
|
|
4337
4371
|
if (!hasRequiredQwenHeaders(cache.headers)) {
|
|
@@ -4469,8 +4503,12 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4469
4503
|
if (accountPages.has(account.id)) {
|
|
4470
4504
|
return;
|
|
4471
4505
|
}
|
|
4472
|
-
|
|
4473
|
-
|
|
4506
|
+
if (config.playwright.maxActiveContexts > 0) {
|
|
4507
|
+
await evictIdlePlaywrightContextsToLimit(
|
|
4508
|
+
Math.max(1, config.playwright.maxActiveContexts - 1)
|
|
4509
|
+
).catch(() => {
|
|
4510
|
+
});
|
|
4511
|
+
}
|
|
4474
4512
|
const profilePath = getAccountProfilePath(account.id);
|
|
4475
4513
|
const fingerprint = getFingerprintProfile(account.id);
|
|
4476
4514
|
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
@@ -4695,8 +4733,15 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4695
4733
|
const hasAuthCookie = cookies.some(
|
|
4696
4734
|
(c) => c.name.toLowerCase().includes("token") || c.name.toLowerCase().includes("session")
|
|
4697
4735
|
);
|
|
4736
|
+
let initialLoginAttempted = false;
|
|
4698
4737
|
if (!hasAuthCookie && account.email && account.password) {
|
|
4699
|
-
|
|
4738
|
+
initialLoginAttempted = true;
|
|
4739
|
+
const loggedIn = await loginToQwen(account.id, account.email, account.password);
|
|
4740
|
+
if (!loggedIn) {
|
|
4741
|
+
throw new Error(
|
|
4742
|
+
`Authentication failed for account ${maskEmail(account.email)}`
|
|
4743
|
+
);
|
|
4744
|
+
}
|
|
4700
4745
|
}
|
|
4701
4746
|
try {
|
|
4702
4747
|
await acctPage.goto(qwenUrl("/"), {
|
|
@@ -4706,13 +4751,21 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4706
4751
|
await sleep2(1500);
|
|
4707
4752
|
const currentUrl = acctPage.url();
|
|
4708
4753
|
const isAuthUrl = currentUrl.includes("/auth") || currentUrl.includes("/login");
|
|
4709
|
-
if (isAuthUrl && account.email && account.password) {
|
|
4754
|
+
if (isAuthUrl && !initialLoginAttempted && account.email && account.password) {
|
|
4710
4755
|
console.log(
|
|
4711
4756
|
`[Playwright] Initial login needed for ${maskEmail(account.email)}, authenticating...`
|
|
4712
4757
|
);
|
|
4713
|
-
await loginToQwen(account.id, account.email, account.password);
|
|
4758
|
+
const loggedIn = await loginToQwen(account.id, account.email, account.password);
|
|
4759
|
+
if (!loggedIn) {
|
|
4760
|
+
throw new Error(
|
|
4761
|
+
`Authentication failed for account ${maskEmail(account.email)}`
|
|
4762
|
+
);
|
|
4763
|
+
}
|
|
4714
4764
|
}
|
|
4715
4765
|
} catch (err) {
|
|
4766
|
+
if (err?.message?.includes("Authentication failed for account")) {
|
|
4767
|
+
throw err;
|
|
4768
|
+
}
|
|
4716
4769
|
console.warn(
|
|
4717
4770
|
`\u26A0\uFE0F [Playwright] Initial navigation check for ${maskEmail(account.email)}: ${err.message}`
|
|
4718
4771
|
);
|
|
@@ -4724,7 +4777,7 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4724
4777
|
if (!acctPage.isClosed()) {
|
|
4725
4778
|
try {
|
|
4726
4779
|
const currentUrl = new URL(acctPage.url());
|
|
4727
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
4780
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
4728
4781
|
await acctPage.goto(qwenUrl("/"), {
|
|
4729
4782
|
waitUntil: "domcontentloaded",
|
|
4730
4783
|
timeout: config.timeouts.navigation
|
|
@@ -4742,6 +4795,8 @@ async function initPlaywrightForAccount(rawAccount, headless = config.playwright
|
|
|
4742
4795
|
} finally {
|
|
4743
4796
|
release();
|
|
4744
4797
|
}
|
|
4798
|
+
await evictIdlePlaywrightContextsToLimit().catch(() => {
|
|
4799
|
+
});
|
|
4745
4800
|
})();
|
|
4746
4801
|
try {
|
|
4747
4802
|
await initPromise;
|
|
@@ -5261,10 +5316,30 @@ async function loginToQwen(accountId, email, password) {
|
|
|
5261
5316
|
const uiResult = await loginViaUi(page, email, password);
|
|
5262
5317
|
if (uiResult.success) {
|
|
5263
5318
|
cookieCaches.delete(accountId);
|
|
5264
|
-
await saveStorageState(page.context(), accountId);
|
|
5265
5319
|
try {
|
|
5266
|
-
|
|
5267
|
-
|
|
5320
|
+
let liveCookies = await page.context().cookies();
|
|
5321
|
+
let tokenCookie = liveCookies.find((c) => c.name === "token");
|
|
5322
|
+
if (!tokenCookie) {
|
|
5323
|
+
const lsTok = uiResult.token || await page.evaluate(() => (localStorage.getItem("token") || "").trim()).catch(() => "");
|
|
5324
|
+
if (lsTok) {
|
|
5325
|
+
await page.context().addCookies([
|
|
5326
|
+
{
|
|
5327
|
+
name: "token",
|
|
5328
|
+
value: lsTok,
|
|
5329
|
+
domain: ".qwen.ai",
|
|
5330
|
+
path: "/",
|
|
5331
|
+
expires: Math.floor(Date.now() / 1e3) + 31536e3,
|
|
5332
|
+
httpOnly: false,
|
|
5333
|
+
secure: true,
|
|
5334
|
+
sameSite: "Lax"
|
|
5335
|
+
}
|
|
5336
|
+
]).catch(() => {
|
|
5337
|
+
});
|
|
5338
|
+
liveCookies = await page.context().cookies();
|
|
5339
|
+
tokenCookie = liveCookies.find((c) => c.name === "token");
|
|
5340
|
+
}
|
|
5341
|
+
}
|
|
5342
|
+
await saveStorageState(page.context(), accountId);
|
|
5268
5343
|
if (tokenCookie) {
|
|
5269
5344
|
const cookieStr = liveCookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
5270
5345
|
const cache = getHeaderCache(accountId);
|
|
@@ -5273,7 +5348,7 @@ async function loginToQwen(accountId, email, password) {
|
|
|
5273
5348
|
markAccountHeadersReady(accountId);
|
|
5274
5349
|
const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
5275
5350
|
const exp = parseJwtExpiry(tokenCookie.value);
|
|
5276
|
-
let rTok = uiResult.refreshToken;
|
|
5351
|
+
let rTok = uiResult.refreshToken || liveCookies.find((c) => c.name === "refresh_token")?.value;
|
|
5277
5352
|
if (!rTok) {
|
|
5278
5353
|
try {
|
|
5279
5354
|
rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
|
|
@@ -5354,8 +5429,9 @@ async function loginViaApi(page, email, password) {
|
|
|
5354
5429
|
}
|
|
5355
5430
|
const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
|
|
5356
5431
|
const requestId = crypto3.randomUUID();
|
|
5432
|
+
const webVersion = getQwenWebVersion() || "0.3.26";
|
|
5357
5433
|
const evalRes = await page.evaluate(
|
|
5358
|
-
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz }) => {
|
|
5434
|
+
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2, tz, version }) => {
|
|
5359
5435
|
try {
|
|
5360
5436
|
const response = await fetch(signinUrl2, {
|
|
5361
5437
|
method: "POST",
|
|
@@ -5365,7 +5441,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5365
5441
|
accept: "application/json, text/plain, */*",
|
|
5366
5442
|
"content-type": "application/json",
|
|
5367
5443
|
source: "web",
|
|
5368
|
-
version
|
|
5444
|
+
version,
|
|
5369
5445
|
"x-request-origin": "https://chat.qwen.ai",
|
|
5370
5446
|
timezone: tz,
|
|
5371
5447
|
"x-request-id": requestId2
|
|
@@ -5378,7 +5454,14 @@ async function loginViaApi(page, email, password) {
|
|
|
5378
5454
|
return { ok: false, error: e.message };
|
|
5379
5455
|
}
|
|
5380
5456
|
},
|
|
5381
|
-
{
|
|
5457
|
+
{
|
|
5458
|
+
email,
|
|
5459
|
+
password: hashedPassword,
|
|
5460
|
+
signinUrl,
|
|
5461
|
+
requestId,
|
|
5462
|
+
tz: formatAsciiTimezone(),
|
|
5463
|
+
version: webVersion
|
|
5464
|
+
}
|
|
5382
5465
|
).catch(() => null);
|
|
5383
5466
|
let data = evalRes?.data;
|
|
5384
5467
|
let signinSuccess = Boolean(
|
|
@@ -5387,7 +5470,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5387
5470
|
if (!signinSuccess && !data?.data?.code) {
|
|
5388
5471
|
const fallbackUrl = qwenUrl("/api/v2/auths/signin");
|
|
5389
5472
|
const fallbackRes = await page.evaluate(
|
|
5390
|
-
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz }) => {
|
|
5473
|
+
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2, tz, version }) => {
|
|
5391
5474
|
try {
|
|
5392
5475
|
const response = await fetch(fallbackUrl2, {
|
|
5393
5476
|
method: "POST",
|
|
@@ -5397,7 +5480,7 @@ async function loginViaApi(page, email, password) {
|
|
|
5397
5480
|
accept: "application/json, text/plain, */*",
|
|
5398
5481
|
"content-type": "application/json",
|
|
5399
5482
|
source: "web",
|
|
5400
|
-
version
|
|
5483
|
+
version,
|
|
5401
5484
|
timezone: tz,
|
|
5402
5485
|
"x-request-id": requestId2
|
|
5403
5486
|
},
|
|
@@ -5409,7 +5492,14 @@ async function loginViaApi(page, email, password) {
|
|
|
5409
5492
|
return { ok: false, error: e.message };
|
|
5410
5493
|
}
|
|
5411
5494
|
},
|
|
5412
|
-
{
|
|
5495
|
+
{
|
|
5496
|
+
email,
|
|
5497
|
+
password: hashedPassword,
|
|
5498
|
+
fallbackUrl,
|
|
5499
|
+
requestId,
|
|
5500
|
+
tz: formatAsciiTimezone(),
|
|
5501
|
+
version: webVersion
|
|
5502
|
+
}
|
|
5413
5503
|
).catch(() => null);
|
|
5414
5504
|
if (fallbackRes?.data) {
|
|
5415
5505
|
data = fallbackRes.data;
|
|
@@ -5634,28 +5724,34 @@ async function loginViaUi(page, email, password) {
|
|
|
5634
5724
|
await sleep2(500);
|
|
5635
5725
|
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
5636
5726
|
const submitButton = page.locator(submitSelector).first();
|
|
5637
|
-
|
|
5638
|
-
try {
|
|
5639
|
-
const btn = document.querySelector(sel);
|
|
5640
|
-
if (btn) {
|
|
5641
|
-
btn.removeAttribute("disabled");
|
|
5642
|
-
btn.classList.remove("ant-btn-disabled");
|
|
5643
|
-
btn.click();
|
|
5644
|
-
}
|
|
5645
|
-
} catch {
|
|
5646
|
-
}
|
|
5647
|
-
}, submitSelector).catch(() => {
|
|
5648
|
-
});
|
|
5727
|
+
let submitted = false;
|
|
5649
5728
|
try {
|
|
5650
5729
|
if (await submitButton.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
5651
|
-
await submitButton.click({
|
|
5730
|
+
await submitButton.click({ timeout: 2e3 }).catch(() => {
|
|
5652
5731
|
});
|
|
5732
|
+
submitted = true;
|
|
5653
5733
|
}
|
|
5654
5734
|
} catch {
|
|
5655
5735
|
}
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5736
|
+
if (!submitted) {
|
|
5737
|
+
await page.evaluate(() => {
|
|
5738
|
+
try {
|
|
5739
|
+
const btn = document.querySelector(
|
|
5740
|
+
'button.qwenchat-auth-pc-submit-button, button[type="submit"]'
|
|
5741
|
+
);
|
|
5742
|
+
if (btn) {
|
|
5743
|
+
btn.removeAttribute("disabled");
|
|
5744
|
+
btn.classList.remove("ant-btn-disabled");
|
|
5745
|
+
btn.click();
|
|
5746
|
+
}
|
|
5747
|
+
} catch {
|
|
5748
|
+
}
|
|
5749
|
+
}).catch(() => {
|
|
5750
|
+
});
|
|
5751
|
+
const passInput = page.locator(passwordSelector).first();
|
|
5752
|
+
await passInput.press("Enter", { timeout: 2e3 }).catch(() => {
|
|
5753
|
+
});
|
|
5754
|
+
}
|
|
5659
5755
|
const postSubmitDeadline = Date.now() + 15e3;
|
|
5660
5756
|
while (Date.now() < postSubmitDeadline) {
|
|
5661
5757
|
if (page.isClosed()) break;
|
|
@@ -5673,9 +5769,12 @@ async function loginViaUi(page, email, password) {
|
|
|
5673
5769
|
".ant-form-item-explain-error",
|
|
5674
5770
|
".ant-message-error",
|
|
5675
5771
|
".ant-message-notice",
|
|
5772
|
+
".ant-message-custom-content",
|
|
5773
|
+
".ant-notification-notice",
|
|
5676
5774
|
'[role="alert"]',
|
|
5677
5775
|
".qwenchat-auth-error",
|
|
5678
|
-
".auth-error-message"
|
|
5776
|
+
".auth-error-message",
|
|
5777
|
+
".qwenchat-toast"
|
|
5679
5778
|
].join(", ");
|
|
5680
5779
|
const errorEl = page.locator(errorSelector).first();
|
|
5681
5780
|
if (await errorEl.isVisible().catch(() => false)) {
|
|
@@ -5689,7 +5788,15 @@ async function loginViaUi(page, email, password) {
|
|
|
5689
5788
|
};
|
|
5690
5789
|
}
|
|
5691
5790
|
}
|
|
5692
|
-
const codeSelector =
|
|
5791
|
+
const codeSelector = [
|
|
5792
|
+
'input[placeholder*="code" i]',
|
|
5793
|
+
'input[placeholder*="c\xF3digo" i]',
|
|
5794
|
+
'input[name*="code" i]',
|
|
5795
|
+
".qwenchat-verification-code-input",
|
|
5796
|
+
".qwenchat-verification-code-input-cell",
|
|
5797
|
+
'[aria-label*="Verification code" i]',
|
|
5798
|
+
'[aria-label*="c\xF3digo de verifica\xE7\xE3o" i]'
|
|
5799
|
+
].join(", ");
|
|
5693
5800
|
const codeInput = page.locator(codeSelector).first();
|
|
5694
5801
|
if (await codeInput.isVisible().catch(() => false)) {
|
|
5695
5802
|
return {
|
|
@@ -5699,17 +5806,45 @@ async function loginViaUi(page, email, password) {
|
|
|
5699
5806
|
}
|
|
5700
5807
|
try {
|
|
5701
5808
|
const liveCookies = await page.context().cookies();
|
|
5702
|
-
|
|
5703
|
-
|
|
5809
|
+
const rTokCookie = liveCookies.find((c) => c.name === "refresh_token")?.value;
|
|
5810
|
+
const tokCookie = liveCookies.find((c) => c.name === "token")?.value;
|
|
5811
|
+
if (tokCookie) {
|
|
5812
|
+
return { success: true, token: tokCookie, refreshToken: rTokCookie };
|
|
5813
|
+
}
|
|
5814
|
+
const currentUrl = page.url();
|
|
5815
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
5816
|
+
const lsTok = await page.evaluate(() => {
|
|
5817
|
+
const t = (localStorage.getItem("token") || "").trim();
|
|
5818
|
+
if (t) {
|
|
5819
|
+
try {
|
|
5820
|
+
document.cookie = `token=${encodeURIComponent(t)}; path=/; domain=.qwen.ai; max-age=31536000`;
|
|
5821
|
+
} catch {
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
return t;
|
|
5825
|
+
}).catch(() => "");
|
|
5826
|
+
if (lsTok) {
|
|
5827
|
+
await page.context().addCookies([
|
|
5828
|
+
{
|
|
5829
|
+
name: "token",
|
|
5830
|
+
value: lsTok,
|
|
5831
|
+
domain: ".qwen.ai",
|
|
5832
|
+
path: "/",
|
|
5833
|
+
expires: Math.floor(Date.now() / 1e3) + 31536e3,
|
|
5834
|
+
httpOnly: false,
|
|
5835
|
+
secure: true,
|
|
5836
|
+
sameSite: "Lax"
|
|
5837
|
+
}
|
|
5838
|
+
]).catch(() => {
|
|
5839
|
+
});
|
|
5840
|
+
return { success: true, token: lsTok, refreshToken: rTokCookie };
|
|
5841
|
+
}
|
|
5842
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
5843
|
+
return { success: true, refreshToken: rTokCookie };
|
|
5844
|
+
}
|
|
5704
5845
|
}
|
|
5705
5846
|
} catch {
|
|
5706
5847
|
}
|
|
5707
|
-
const currentUrl = page.url();
|
|
5708
|
-
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
5709
|
-
if (await isPageLoggedIn(page, 3e3)) {
|
|
5710
|
-
return { success: true };
|
|
5711
|
-
}
|
|
5712
|
-
}
|
|
5713
5848
|
await sleep2(1e3);
|
|
5714
5849
|
}
|
|
5715
5850
|
if (await isPageLoggedIn(page, 3e3)) {
|
|
@@ -5738,10 +5873,18 @@ async function refreshAccountToken(accountId) {
|
|
|
5738
5873
|
} catch {
|
|
5739
5874
|
}
|
|
5740
5875
|
}
|
|
5876
|
+
if (!refreshToken) {
|
|
5877
|
+
try {
|
|
5878
|
+
const liveCookies = await page.context().cookies();
|
|
5879
|
+
refreshToken = liveCookies.find((c) => c.name === "refresh_token")?.value;
|
|
5880
|
+
} catch {
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5741
5883
|
if (refreshToken) {
|
|
5742
5884
|
try {
|
|
5885
|
+
const webVersion = getQwenWebVersion() || "0.3.26";
|
|
5743
5886
|
const refreshResult = await page.evaluate(
|
|
5744
|
-
async ({ rTok, tz }) => {
|
|
5887
|
+
async ({ rTok, tz, version }) => {
|
|
5745
5888
|
try {
|
|
5746
5889
|
const response = await fetch("https://auth.qwen.ai/api/v2/auths/refresh", {
|
|
5747
5890
|
method: "GET",
|
|
@@ -5749,7 +5892,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5749
5892
|
headers: {
|
|
5750
5893
|
accept: "application/json, text/plain, */*",
|
|
5751
5894
|
source: "web",
|
|
5752
|
-
version
|
|
5895
|
+
version,
|
|
5753
5896
|
"x-request-origin": "https://chat.qwen.ai",
|
|
5754
5897
|
timezone: tz,
|
|
5755
5898
|
authorization: `Bearer ${rTok}`
|
|
@@ -5761,7 +5904,7 @@ async function refreshAccountToken(accountId) {
|
|
|
5761
5904
|
return { ok: false, error: e.message };
|
|
5762
5905
|
}
|
|
5763
5906
|
},
|
|
5764
|
-
{ rTok: refreshToken, tz: formatAsciiTimezone() }
|
|
5907
|
+
{ rTok: refreshToken, tz: formatAsciiTimezone(), version: webVersion }
|
|
5765
5908
|
);
|
|
5766
5909
|
const json = refreshResult?.data;
|
|
5767
5910
|
if (json && json.success === true && json.data) {
|
|
@@ -6422,19 +6565,26 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
6422
6565
|
await executeReauth();
|
|
6423
6566
|
} else {
|
|
6424
6567
|
try {
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6568
|
+
const currentUrl = typeof page.url === "function" ? page.url() : "";
|
|
6569
|
+
const needsNavigation = !currentUrl.startsWith(qwenOrigin()) || currentUrl.includes("/auth") || currentUrl.includes("/login") || currentUrl.includes("_____tmd_____");
|
|
6570
|
+
if (needsNavigation) {
|
|
6571
|
+
await page.goto(qwenUrl("/"), {
|
|
6572
|
+
waitUntil: "domcontentloaded",
|
|
6573
|
+
timeout: Math.min(
|
|
6574
|
+
config.timeouts.navigation,
|
|
6575
|
+
boundedTimeoutMs,
|
|
6576
|
+
SESSION_PROBE_NAVIGATION_TIMEOUT_MS
|
|
6577
|
+
)
|
|
6578
|
+
});
|
|
6579
|
+
await sleep2(2e3);
|
|
6580
|
+
}
|
|
6581
|
+
const url = typeof page.url === "function" ? page.url() : "";
|
|
6435
6582
|
const isAuthUrl = url.includes("auth") || url.includes("login");
|
|
6436
6583
|
if (isAuthUrl) {
|
|
6437
|
-
await
|
|
6584
|
+
const refreshed = await refreshAccountToken(accountId);
|
|
6585
|
+
if (!refreshed.success) {
|
|
6586
|
+
await executeReauth();
|
|
6587
|
+
}
|
|
6438
6588
|
}
|
|
6439
6589
|
} catch (navErr) {
|
|
6440
6590
|
console.warn(
|
|
@@ -6491,7 +6641,7 @@ async function refreshHeadersInternal(accountId, timeoutMs = config.timeouts.hea
|
|
|
6491
6641
|
if (capturedPage && !capturedPage.isClosed()) {
|
|
6492
6642
|
try {
|
|
6493
6643
|
const currentUrl = new URL(capturedPage.url());
|
|
6494
|
-
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname
|
|
6644
|
+
if (currentUrl.origin !== qwenOrigin() || currentUrl.pathname.includes("/auth") || currentUrl.pathname.includes("/login") || currentUrl.pathname.includes("_____tmd_____")) {
|
|
6495
6645
|
await capturedPage.goto(qwenUrl("/"), {
|
|
6496
6646
|
waitUntil: "domcontentloaded",
|
|
6497
6647
|
timeout: Math.min(config.timeouts.navigation, boundedTimeoutMs)
|
|
@@ -6807,7 +6957,11 @@ function priorityOrderForEviction(accountIds) {
|
|
|
6807
6957
|
const ranked = getAccountsByPriority(accountIds.map((id) => ({ id })));
|
|
6808
6958
|
const rank = /* @__PURE__ */ new Map();
|
|
6809
6959
|
for (const [index, account] of ranked.entries()) {
|
|
6810
|
-
|
|
6960
|
+
if (getAccountCooldownInfo(account.id)) {
|
|
6961
|
+
rank.set(account.id, Number.MAX_SAFE_INTEGER);
|
|
6962
|
+
} else {
|
|
6963
|
+
rank.set(account.id, index);
|
|
6964
|
+
}
|
|
6811
6965
|
}
|
|
6812
6966
|
return orderContextsForEviction(
|
|
6813
6967
|
accountIds,
|
|
@@ -6844,14 +6998,15 @@ async function closeIdlePlaywrightAccounts(idleMs) {
|
|
|
6844
6998
|
}
|
|
6845
6999
|
return closed;
|
|
6846
7000
|
}
|
|
6847
|
-
async function evictIdlePlaywrightContextsToLimit() {
|
|
6848
|
-
const
|
|
6849
|
-
if (
|
|
7001
|
+
async function evictIdlePlaywrightContextsToLimit(targetLimit) {
|
|
7002
|
+
const configuredMax = config.playwright.maxActiveContexts;
|
|
7003
|
+
if (configuredMax <= 0) return 0;
|
|
7004
|
+
const max = targetLimit !== void 0 ? Math.max(0, targetLimit) : configuredMax;
|
|
6850
7005
|
if (accountPages.size <= max) return 0;
|
|
6851
7006
|
const candidates = priorityOrderForEviction(
|
|
6852
7007
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
6853
7008
|
if (isAccountServingStream(accountId)) return false;
|
|
6854
|
-
if (
|
|
7009
|
+
if (hasActiveAccountLease(accountId)) return false;
|
|
6855
7010
|
const mutex = accountMutexes.get(accountId);
|
|
6856
7011
|
return mutex?.isIdle();
|
|
6857
7012
|
})
|
|
@@ -6864,6 +7019,7 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
6864
7019
|
for (const candidate of candidates) {
|
|
6865
7020
|
if (accountPages.size <= max) break;
|
|
6866
7021
|
if (isAccountServingStream(candidate.accountId)) continue;
|
|
7022
|
+
if (hasActiveAccountLease(candidate.accountId)) continue;
|
|
6867
7023
|
const mutex = accountMutexes.get(candidate.accountId);
|
|
6868
7024
|
if (!mutex?.isIdle()) continue;
|
|
6869
7025
|
await closePlaywrightForAccount(candidate.accountId).catch((error) => {
|
|
@@ -7702,6 +7858,7 @@ var init_model_registry = __esm({
|
|
|
7702
7858
|
var captcha_coordinator_exports = {};
|
|
7703
7859
|
__export(captcha_coordinator_exports, {
|
|
7704
7860
|
isCaptchaRecoveryActive: () => isCaptchaRecoveryActive,
|
|
7861
|
+
isCaptchaRecoveryInBackoff: () => isCaptchaRecoveryInBackoff,
|
|
7705
7862
|
recoverBaxiaCaptcha: () => recoverBaxiaCaptcha,
|
|
7706
7863
|
solveChallengeOnPage: () => solveChallengeOnPage
|
|
7707
7864
|
});
|
|
@@ -7740,6 +7897,11 @@ async function solveChallengeOnPage(page, challengeUrl, waitForMs = config.captc
|
|
|
7740
7897
|
}
|
|
7741
7898
|
}
|
|
7742
7899
|
}
|
|
7900
|
+
function isCaptchaRecoveryInBackoff(accountId) {
|
|
7901
|
+
if (!accountId) return false;
|
|
7902
|
+
const lastFailure = lastFailedRecoveryAt.get(accountId) ?? 0;
|
|
7903
|
+
return Date.now() - lastFailure < FAILED_RECOVERY_BACKOFF_MS;
|
|
7904
|
+
}
|
|
7743
7905
|
async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
7744
7906
|
if (!config.captcha.enabled || !accountId) return false;
|
|
7745
7907
|
const solver = "baxia";
|
|
@@ -7757,17 +7919,34 @@ async function recoverBaxiaCaptcha(accountId, label, options = {}) {
|
|
|
7757
7919
|
const challengeUrl = options.challengeBody ? extractBaxiaChallengeUrl(options.challengeBody, config.qwen.baseUrl) : null;
|
|
7758
7920
|
activeCaptchaRecoveries.add(accountId);
|
|
7759
7921
|
try {
|
|
7760
|
-
const {
|
|
7761
|
-
|
|
7762
|
-
accountId,
|
|
7763
|
-
"Account stream interrupted by captcha challenge recovery"
|
|
7764
|
-
);
|
|
7765
|
-
} catch {
|
|
7766
|
-
}
|
|
7767
|
-
try {
|
|
7922
|
+
const { hasActiveBrowserStreamsForAccount: hasActiveBrowserStreamsForAccount2 } = await Promise.resolve().then(() => (init_qwen(), qwen_exports));
|
|
7923
|
+
const hasConcurrentStream = hasActiveBrowserStreamsForAccount2(accountId);
|
|
7768
7924
|
const solved = await withAccountPage(
|
|
7769
7925
|
accountId,
|
|
7770
|
-
(page) =>
|
|
7926
|
+
async (page) => {
|
|
7927
|
+
if (hasConcurrentStream) {
|
|
7928
|
+
const solverOptions = {
|
|
7929
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
7930
|
+
retryDelayMs: config.captcha.retryDelayMs,
|
|
7931
|
+
settleMs: config.captcha.settleMs
|
|
7932
|
+
};
|
|
7933
|
+
if (await solveBaxiaCaptcha(page, { ...solverOptions, waitForMs: 0 })) {
|
|
7934
|
+
return true;
|
|
7935
|
+
}
|
|
7936
|
+
const ctx = typeof page.context === "function" ? page.context() : null;
|
|
7937
|
+
if (ctx && typeof ctx.newPage === "function") {
|
|
7938
|
+
const tempPage = await ctx.newPage();
|
|
7939
|
+
try {
|
|
7940
|
+
return await solveChallengeOnPage(tempPage, challengeUrl);
|
|
7941
|
+
} finally {
|
|
7942
|
+
await tempPage.close({ runBeforeUnload: false }).catch(() => {
|
|
7943
|
+
});
|
|
7944
|
+
}
|
|
7945
|
+
}
|
|
7946
|
+
return false;
|
|
7947
|
+
}
|
|
7948
|
+
return await solveChallengeOnPage(page, challengeUrl);
|
|
7949
|
+
},
|
|
7771
7950
|
solverOperationTimeoutMs,
|
|
7772
7951
|
solverOperationTimeoutMs,
|
|
7773
7952
|
false
|
|
@@ -8566,6 +8745,7 @@ __export(qwen_exports, {
|
|
|
8566
8745
|
getLogicalThreadState: () => getLogicalThreadState,
|
|
8567
8746
|
getQwenErrorCode: () => getQwenErrorCode,
|
|
8568
8747
|
getSessionParent: () => getSessionParent,
|
|
8748
|
+
hasActiveBrowserStreamsForAccount: () => hasActiveBrowserStreamsForAccount,
|
|
8569
8749
|
invalidateLogicalThreadParent: () => invalidateLogicalThreadParent,
|
|
8570
8750
|
isChatSessionActive: () => isChatSessionActive,
|
|
8571
8751
|
isRetryableFetchErrorMessage: () => isRetryableFetchErrorMessage,
|
|
@@ -8587,6 +8767,14 @@ __export(qwen_exports, {
|
|
|
8587
8767
|
});
|
|
8588
8768
|
import crypto5 from "crypto";
|
|
8589
8769
|
import { v4 as uuidv43 } from "uuid";
|
|
8770
|
+
function hasActiveBrowserStreamsForAccount(accountId) {
|
|
8771
|
+
for (const state of browserStreamStates.values()) {
|
|
8772
|
+
if (state.accountId === accountId && !state.done) {
|
|
8773
|
+
return true;
|
|
8774
|
+
}
|
|
8775
|
+
}
|
|
8776
|
+
return false;
|
|
8777
|
+
}
|
|
8590
8778
|
function abortActiveBrowserStreamsForAccount(accountId, reason = "Account browser stream interrupted (page context unloaded)") {
|
|
8591
8779
|
let count = 0;
|
|
8592
8780
|
for (const [requestId, state] of browserStreamStates) {
|
|
@@ -9364,7 +9552,7 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
9364
9552
|
const onFrameNavigated = (frame) => {
|
|
9365
9553
|
if (!settled && typeof page.mainFrame === "function" && frame === page.mainFrame()) {
|
|
9366
9554
|
const url2 = typeof frame.url === "function" ? frame.url() : String(frame.url || "");
|
|
9367
|
-
if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
|
|
9555
|
+
if (url2.startsWith(qwenOrigin()) && !url2.includes("/auth") && !url2.includes("/login") && !url2.includes("_____tmd_____") && !url2.includes("/punish")) {
|
|
9368
9556
|
return;
|
|
9369
9557
|
}
|
|
9370
9558
|
handleBrowserStreamEvent(requestId, {
|
|
@@ -9385,10 +9573,12 @@ async function createQwenBrowserResponse(accountId, url, method, headers, body,
|
|
|
9385
9573
|
};
|
|
9386
9574
|
page.on("framenavigated", onFrameNavigated);
|
|
9387
9575
|
page.on("close", onPageClosed);
|
|
9576
|
+
page.on("crash", onPageClosed);
|
|
9388
9577
|
pageCleanup = () => {
|
|
9389
9578
|
try {
|
|
9390
9579
|
page.off?.("framenavigated", onFrameNavigated);
|
|
9391
9580
|
page.off?.("close", onPageClosed);
|
|
9581
|
+
page.off?.("crash", onPageClosed);
|
|
9392
9582
|
} catch {
|
|
9393
9583
|
}
|
|
9394
9584
|
};
|
|
@@ -11176,6 +11366,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
11176
11366
|
const account = accounts[i];
|
|
11177
11367
|
const currentIdx = i + 1;
|
|
11178
11368
|
const totalCount = accounts.length;
|
|
11369
|
+
const wasInitialized = isPlaywrightInitialized(account.id);
|
|
11179
11370
|
try {
|
|
11180
11371
|
const ok = await deleteChatsForAccount(account, currentIdx, totalCount);
|
|
11181
11372
|
if (ok) succeeded++;
|
|
@@ -11185,7 +11376,7 @@ async function deleteChatsForConfiguredAccounts(keepBrowserOpen = false) {
|
|
|
11185
11376
|
error instanceof Error ? error.message : String(error)
|
|
11186
11377
|
);
|
|
11187
11378
|
} finally {
|
|
11188
|
-
if (!keepBrowserOpen) {
|
|
11379
|
+
if ((!keepBrowserOpen || !wasInitialized) && !isAuthMockEnabled()) {
|
|
11189
11380
|
const { closePlaywrightForAccount: closePlaywrightForAccount2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
11190
11381
|
await closePlaywrightForAccount2(account.id).catch(() => {
|
|
11191
11382
|
});
|