qwenproxy-cli 1.6.1 → 1.6.3
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 +77 -5
- package/dist/delete-chats.js +415 -12
- package/dist/index.js +721 -107
- package/dist/login.js +428 -13
- package/dist/reset-cooldowns.js +87 -5
- package/package.json +3 -3
- package/src/core/accounts.ts +1 -1
- package/src/core/version.ts +1 -1
- package/src/login.ts +90 -4
- package/src/routes/chat/account.ts +11 -4
- package/src/routes/chat/retry-policy.ts +60 -18
- package/src/routes/chat/streaming.ts +27 -0
- package/src/services/playwright.ts +436 -14
- package/src/services/qwen.ts +22 -6
- package/src/tui/views/accounts-view.ts +313 -96
- package/src/utils/overload-detector.ts +55 -0
package/dist/delete-chats.js
CHANGED
|
@@ -52,6 +52,25 @@ var init_process_guard = __esm({
|
|
|
52
52
|
});
|
|
53
53
|
|
|
54
54
|
// src/core/paths.ts
|
|
55
|
+
var paths_exports = {};
|
|
56
|
+
__export(paths_exports, {
|
|
57
|
+
ensureDataDirs: () => ensureDataDirs,
|
|
58
|
+
getAccountPriorityPath: () => getAccountPriorityPath,
|
|
59
|
+
getAccountProfilePath: () => getAccountProfilePath,
|
|
60
|
+
getDataDir: () => getDataDir,
|
|
61
|
+
getDbDir: () => getDbDir,
|
|
62
|
+
getDbPath: () => getDbPath,
|
|
63
|
+
getEncryptionKeyPath: () => getEncryptionKeyPath,
|
|
64
|
+
getEnvFilePath: () => getEnvFilePath,
|
|
65
|
+
getLogsDir: () => getLogsDir,
|
|
66
|
+
getOsGlobalDataDir: () => getOsGlobalDataDir,
|
|
67
|
+
getProfilesDir: () => getProfilesDir,
|
|
68
|
+
getServerLogFilePath: () => getServerLogFilePath,
|
|
69
|
+
getSyncStatePath: () => getSyncStatePath,
|
|
70
|
+
getTuiSettingsPath: () => getTuiSettingsPath,
|
|
71
|
+
isRunningUnderNodeTest: () => isRunningUnderNodeTest,
|
|
72
|
+
resolveDataDir: () => resolveDataDir
|
|
73
|
+
});
|
|
55
74
|
import fs from "node:fs";
|
|
56
75
|
import path from "node:path";
|
|
57
76
|
import os from "node:os";
|
|
@@ -116,6 +135,12 @@ function getAccountProfilePath(accountId, customDataDir) {
|
|
|
116
135
|
function getAccountPriorityPath(customDataDir) {
|
|
117
136
|
return path.join(customDataDir || getDataDir(), "account-priority.json");
|
|
118
137
|
}
|
|
138
|
+
function getSyncStatePath(customDataDir) {
|
|
139
|
+
return path.join(customDataDir || getDataDir(), "sync-state.json");
|
|
140
|
+
}
|
|
141
|
+
function getTuiSettingsPath(customDataDir) {
|
|
142
|
+
return path.join(customDataDir || getDataDir(), "tui-settings.json");
|
|
143
|
+
}
|
|
119
144
|
function getEnvFilePath(customDataDir) {
|
|
120
145
|
if (isRunningUnderNodeTest()) {
|
|
121
146
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
@@ -126,6 +151,26 @@ function getEnvFilePath(customDataDir) {
|
|
|
126
151
|
}
|
|
127
152
|
return path.join(customDataDir || getDataDir(), ".env");
|
|
128
153
|
}
|
|
154
|
+
function getLogsDir(customDataDir) {
|
|
155
|
+
return path.join(customDataDir || getDataDir(), "logs");
|
|
156
|
+
}
|
|
157
|
+
function getServerLogFilePath(customDataDir) {
|
|
158
|
+
return path.join(getLogsDir(customDataDir), "server.log");
|
|
159
|
+
}
|
|
160
|
+
function ensureDataDirs(targetDataDir) {
|
|
161
|
+
const root = targetDataDir || getDataDir();
|
|
162
|
+
const dbDir = getDbDir(root);
|
|
163
|
+
const profilesDir = getProfilesDir(root);
|
|
164
|
+
const logsDir = getLogsDir(root);
|
|
165
|
+
try {
|
|
166
|
+
if (!fs.existsSync(root)) fs.mkdirSync(root, { recursive: true });
|
|
167
|
+
if (!fs.existsSync(dbDir)) fs.mkdirSync(dbDir, { recursive: true });
|
|
168
|
+
if (!fs.existsSync(profilesDir)) fs.mkdirSync(profilesDir, { recursive: true });
|
|
169
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
170
|
+
} catch (err) {
|
|
171
|
+
console.error(`[Paths] Error ensuring data directories at ${root}:`, err?.message || String(err));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
129
174
|
var init_paths = __esm({
|
|
130
175
|
"src/core/paths.ts"() {
|
|
131
176
|
"use strict";
|
|
@@ -819,7 +864,7 @@ function addAccountsBatch(entries) {
|
|
|
819
864
|
}
|
|
820
865
|
return { added, skipped, invalid };
|
|
821
866
|
}
|
|
822
|
-
function addAccount(email, password, id) {
|
|
867
|
+
function addAccount(email, password = "", id) {
|
|
823
868
|
if (!email || typeof email !== "string" || email.trim().length === 0) {
|
|
824
869
|
throw new Error("Email is required");
|
|
825
870
|
}
|
|
@@ -3160,6 +3205,7 @@ var playwright_exports = {};
|
|
|
3160
3205
|
__export(playwright_exports, {
|
|
3161
3206
|
assertAntiBotHeaders: () => assertAntiBotHeaders,
|
|
3162
3207
|
buildChromiumLaunchArgs: () => buildChromiumLaunchArgs,
|
|
3208
|
+
cancelEmailOtpLogin: () => cancelEmailOtpLogin,
|
|
3163
3209
|
captureQwenHeaders: () => captureQwenHeaders,
|
|
3164
3210
|
classifyQwenAuthError: () => classifyQwenAuthError,
|
|
3165
3211
|
cleanupOrphanProfiles: () => cleanupOrphanProfiles,
|
|
@@ -3200,8 +3246,10 @@ __export(playwright_exports, {
|
|
|
3200
3246
|
removePlaywrightProfile: () => removePlaywrightProfile,
|
|
3201
3247
|
saveStorageState: () => saveStorageState,
|
|
3202
3248
|
schedulePlaywrightProfileReset: () => schedulePlaywrightProfileReset,
|
|
3249
|
+
startEmailOtpLogin: () => startEmailOtpLogin,
|
|
3203
3250
|
unregisterPlaywrightAccountForTests: () => unregisterPlaywrightAccountForTests,
|
|
3204
3251
|
validateAccountLogin: () => validateAccountLogin,
|
|
3252
|
+
verifyEmailOtpLogin: () => verifyEmailOtpLogin,
|
|
3205
3253
|
withAccountPage: () => withAccountPage
|
|
3206
3254
|
});
|
|
3207
3255
|
import { chromium as chromium2 } from "patchright";
|
|
@@ -3543,6 +3591,10 @@ async function clearVisibleChallenge(page) {
|
|
|
3543
3591
|
"button[aria-label*='Close' i]",
|
|
3544
3592
|
"button[aria-label*='Fechar' i]",
|
|
3545
3593
|
".index-module__whats-new-modal-close",
|
|
3594
|
+
".age-confirmation-modal button",
|
|
3595
|
+
".age-confirmation-modal .qwen-chat-v2-btn-black",
|
|
3596
|
+
"button:has-text('Continuar')",
|
|
3597
|
+
"button:has-text('Continue')",
|
|
3546
3598
|
"button:has-text('Cancel')",
|
|
3547
3599
|
"button:has-text('Cancelar')",
|
|
3548
3600
|
"button:has-text('I understand')",
|
|
@@ -4737,6 +4789,292 @@ async function validateAccountLogin(rawAccount, headless = true, browserType = "
|
|
|
4737
4789
|
release();
|
|
4738
4790
|
}
|
|
4739
4791
|
}
|
|
4792
|
+
async function startEmailOtpLogin(email, options = {}) {
|
|
4793
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
4794
|
+
if (!normalizedEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
|
|
4795
|
+
return { success: false, error: "Formato de e-mail inv\xE1lido" };
|
|
4796
|
+
}
|
|
4797
|
+
const { listAccounts: listAccounts2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4798
|
+
if (listAccounts2().some((a) => a.email.toLowerCase() === normalizedEmail)) {
|
|
4799
|
+
return {
|
|
4800
|
+
success: false,
|
|
4801
|
+
error: `Uma conta com o e-mail "${normalizedEmail}" j\xE1 est\xE1 cadastrada.`
|
|
4802
|
+
};
|
|
4803
|
+
}
|
|
4804
|
+
if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
|
|
4805
|
+
const mockSessionId = "mock-otp-" + Date.now();
|
|
4806
|
+
mockOtpSessions.set(mockSessionId, normalizedEmail);
|
|
4807
|
+
return { success: true, sessionId: mockSessionId };
|
|
4808
|
+
}
|
|
4809
|
+
const accountId = crypto3.randomUUID();
|
|
4810
|
+
const sessionId = accountId;
|
|
4811
|
+
const profilePath = getAccountProfilePath(accountId);
|
|
4812
|
+
const fingerprint = getFingerprintProfile(accountId);
|
|
4813
|
+
const browserType = options.browserType ?? config.playwright.browser;
|
|
4814
|
+
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
4815
|
+
const headless = options.headless ?? config.playwright.headless;
|
|
4816
|
+
const launchOptions = {
|
|
4817
|
+
headless,
|
|
4818
|
+
channel,
|
|
4819
|
+
userAgent: fingerprint.userAgent,
|
|
4820
|
+
locale: fingerprint.locale,
|
|
4821
|
+
timezoneId: fingerprint.timezoneId,
|
|
4822
|
+
viewport: fingerprint.viewport,
|
|
4823
|
+
screen: fingerprint.viewport,
|
|
4824
|
+
deviceScaleFactor: 1,
|
|
4825
|
+
isMobile: false,
|
|
4826
|
+
hasTouch: false,
|
|
4827
|
+
colorScheme: "light",
|
|
4828
|
+
extraHTTPHeaders: {
|
|
4829
|
+
"sec-ch-ua": fingerprint.secChUa,
|
|
4830
|
+
"sec-ch-ua-mobile": "?0",
|
|
4831
|
+
"sec-ch-ua-platform": '"Windows"'
|
|
4832
|
+
},
|
|
4833
|
+
ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
|
|
4834
|
+
args: buildChromiumLaunchArgs(fingerprint.viewport)
|
|
4835
|
+
};
|
|
4836
|
+
let acctContext;
|
|
4837
|
+
try {
|
|
4838
|
+
acctContext = await withTimeout(
|
|
4839
|
+
engine.launchPersistentContext(profilePath, launchOptions),
|
|
4840
|
+
3e4,
|
|
4841
|
+
`O navegador n\xE3o iniciou em 30s para a conta ${maskEmail(normalizedEmail)}.`
|
|
4842
|
+
);
|
|
4843
|
+
} catch (launchErr) {
|
|
4844
|
+
if (launchErr?.message?.includes("Executable doesn't exist")) {
|
|
4845
|
+
autoInstallPlaywrightChromium();
|
|
4846
|
+
acctContext = await withTimeout(
|
|
4847
|
+
engine.launchPersistentContext(profilePath, launchOptions),
|
|
4848
|
+
3e4,
|
|
4849
|
+
`O navegador n\xE3o iniciou em 30s para a conta ${maskEmail(normalizedEmail)}.`
|
|
4850
|
+
);
|
|
4851
|
+
} else {
|
|
4852
|
+
return { success: false, error: launchErr?.message || String(launchErr) };
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
try {
|
|
4856
|
+
await acctContext.addInitScript(getStealthScript(fingerprint));
|
|
4857
|
+
const existingPages = acctContext.pages().filter((p) => !p.isClosed());
|
|
4858
|
+
const acctPage = existingPages.find((p) => p.url().startsWith(qwenOrigin())) ?? existingPages[0] ?? await acctContext.newPage();
|
|
4859
|
+
await acctPage.goto(qwenUrl("/auth?mode=register"), {
|
|
4860
|
+
waitUntil: "domcontentloaded",
|
|
4861
|
+
timeout: Math.min(config.timeouts.navigation, 2e4)
|
|
4862
|
+
}).catch(() => {
|
|
4863
|
+
});
|
|
4864
|
+
await sleep2(1e3);
|
|
4865
|
+
await clearVisibleChallenge(acctPage);
|
|
4866
|
+
const requestId = crypto3.randomUUID();
|
|
4867
|
+
const evalRes = await acctPage.evaluate(
|
|
4868
|
+
async ({ email: email2, requestId: requestId2, version, timezone }) => {
|
|
4869
|
+
try {
|
|
4870
|
+
const response = await fetch("https://auth.qwen.ai/api/v2/auths/otp/email/request", {
|
|
4871
|
+
method: "POST",
|
|
4872
|
+
credentials: "include",
|
|
4873
|
+
headers: {
|
|
4874
|
+
accept: "application/json, text/plain, */*",
|
|
4875
|
+
"content-type": "application/json",
|
|
4876
|
+
source: "web",
|
|
4877
|
+
version,
|
|
4878
|
+
"x-request-origin": "https://chat.qwen.ai",
|
|
4879
|
+
timezone,
|
|
4880
|
+
"x-request-id": requestId2
|
|
4881
|
+
},
|
|
4882
|
+
body: JSON.stringify({
|
|
4883
|
+
identifier: email2,
|
|
4884
|
+
accept_terms: true,
|
|
4885
|
+
language: "pt-BR"
|
|
4886
|
+
})
|
|
4887
|
+
});
|
|
4888
|
+
const json = await response.json().catch(() => null);
|
|
4889
|
+
return { ok: response.ok, status: response.status, data: json };
|
|
4890
|
+
} catch (e) {
|
|
4891
|
+
return { ok: false, error: e.message };
|
|
4892
|
+
}
|
|
4893
|
+
},
|
|
4894
|
+
{
|
|
4895
|
+
email: normalizedEmail,
|
|
4896
|
+
requestId,
|
|
4897
|
+
version: getQwenWebVersion() || "0.3.12",
|
|
4898
|
+
timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
|
|
4899
|
+
}
|
|
4900
|
+
).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
4901
|
+
if (!evalRes.ok || evalRes.data?.success !== true) {
|
|
4902
|
+
const errorMsg = evalRes.data?.data?.details || evalRes.data?.data?.message || evalRes.data?.message || evalRes.error || "Falha ao solicitar c\xF3digo de verifica\xE7\xE3o";
|
|
4903
|
+
await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
|
|
4904
|
+
});
|
|
4905
|
+
cleanupPlaywrightAccountState(accountId);
|
|
4906
|
+
return { success: false, error: errorMsg };
|
|
4907
|
+
}
|
|
4908
|
+
const timer = setTimeout(async () => {
|
|
4909
|
+
activeOtpSessions.delete(sessionId);
|
|
4910
|
+
await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
|
|
4911
|
+
});
|
|
4912
|
+
cleanupPlaywrightAccountState(accountId);
|
|
4913
|
+
}, 5 * 60 * 1e3);
|
|
4914
|
+
timer.unref?.();
|
|
4915
|
+
activeOtpSessions.set(sessionId, {
|
|
4916
|
+
sessionId,
|
|
4917
|
+
accountId,
|
|
4918
|
+
email: normalizedEmail,
|
|
4919
|
+
context: acctContext,
|
|
4920
|
+
page: acctPage,
|
|
4921
|
+
fingerprint,
|
|
4922
|
+
createdAt: Date.now(),
|
|
4923
|
+
timer
|
|
4924
|
+
});
|
|
4925
|
+
return { success: true, sessionId };
|
|
4926
|
+
} catch (err) {
|
|
4927
|
+
await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
|
|
4928
|
+
});
|
|
4929
|
+
cleanupPlaywrightAccountState(accountId);
|
|
4930
|
+
return { success: false, error: err?.message || String(err) };
|
|
4931
|
+
}
|
|
4932
|
+
}
|
|
4933
|
+
async function verifyEmailOtpLogin(sessionId, code) {
|
|
4934
|
+
const cleanCode = code.trim();
|
|
4935
|
+
if (!cleanCode) {
|
|
4936
|
+
return { success: false, error: "C\xF3digo de verifica\xE7\xE3o \xE9 obrigat\xF3rio" };
|
|
4937
|
+
}
|
|
4938
|
+
if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
|
|
4939
|
+
const mockEmail = mockOtpSessions.get(sessionId);
|
|
4940
|
+
if (!mockEmail) {
|
|
4941
|
+
return { success: false, error: "Sess\xE3o OTP inv\xE1lida ou expirada" };
|
|
4942
|
+
}
|
|
4943
|
+
if (cleanCode === "000000") {
|
|
4944
|
+
return { success: false, error: "C\xF3digo de verifica\xE7\xE3o incorreto ou expirado" };
|
|
4945
|
+
}
|
|
4946
|
+
const { addAccount: addAccount2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4947
|
+
const mockAccId = "mock-otp-acc-" + Date.now();
|
|
4948
|
+
const newAccount = addAccount2(mockEmail, "", mockAccId);
|
|
4949
|
+
mockOtpSessions.delete(sessionId);
|
|
4950
|
+
return { success: true, account: newAccount };
|
|
4951
|
+
}
|
|
4952
|
+
const session = activeOtpSessions.get(sessionId);
|
|
4953
|
+
if (!session) {
|
|
4954
|
+
return { success: false, error: "Sess\xE3o OTP expirada ou n\xE3o encontrada. Solicite um novo c\xF3digo." };
|
|
4955
|
+
}
|
|
4956
|
+
if (session.page.isClosed()) {
|
|
4957
|
+
activeOtpSessions.delete(sessionId);
|
|
4958
|
+
return { success: false, error: "Navegador foi fechado. Solicite um novo c\xF3digo." };
|
|
4959
|
+
}
|
|
4960
|
+
const requestId = crypto3.randomUUID();
|
|
4961
|
+
const evalRes = await session.page.evaluate(
|
|
4962
|
+
async ({ email, code: code2, requestId: requestId2, version, timezone }) => {
|
|
4963
|
+
try {
|
|
4964
|
+
const response = await fetch("https://auth.qwen.ai/api/v2/auths/otp/email/verify", {
|
|
4965
|
+
method: "POST",
|
|
4966
|
+
credentials: "include",
|
|
4967
|
+
headers: {
|
|
4968
|
+
accept: "application/json, text/plain, */*",
|
|
4969
|
+
"content-type": "application/json",
|
|
4970
|
+
source: "web",
|
|
4971
|
+
version,
|
|
4972
|
+
"x-request-origin": "https://chat.qwen.ai",
|
|
4973
|
+
timezone,
|
|
4974
|
+
"x-request-id": requestId2
|
|
4975
|
+
},
|
|
4976
|
+
body: JSON.stringify({
|
|
4977
|
+
identifier: email,
|
|
4978
|
+
code: code2,
|
|
4979
|
+
name: null
|
|
4980
|
+
})
|
|
4981
|
+
});
|
|
4982
|
+
const json = await response.json().catch(() => null);
|
|
4983
|
+
return { ok: response.ok, status: response.status, data: json };
|
|
4984
|
+
} catch (e) {
|
|
4985
|
+
return { ok: false, error: e.message };
|
|
4986
|
+
}
|
|
4987
|
+
},
|
|
4988
|
+
{
|
|
4989
|
+
email: session.email,
|
|
4990
|
+
code: cleanCode,
|
|
4991
|
+
requestId,
|
|
4992
|
+
version: getQwenWebVersion() || "0.3.12",
|
|
4993
|
+
timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
|
|
4994
|
+
}
|
|
4995
|
+
).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
4996
|
+
if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
|
|
4997
|
+
const errorMsg = evalRes.data?.data?.details || evalRes.data?.data?.message || evalRes.data?.message || evalRes.error || "C\xF3digo de verifica\xE7\xE3o incorreto ou expirado";
|
|
4998
|
+
return { success: false, error: errorMsg };
|
|
4999
|
+
}
|
|
5000
|
+
const resData = evalRes.data.data;
|
|
5001
|
+
const accessToken = resData.access_token || resData.token;
|
|
5002
|
+
const refreshToken = resData.refresh_token;
|
|
5003
|
+
const userId = resData.id;
|
|
5004
|
+
try {
|
|
5005
|
+
await session.page.evaluate(({ token, rTok }) => {
|
|
5006
|
+
try {
|
|
5007
|
+
localStorage.setItem("token", token);
|
|
5008
|
+
localStorage.setItem("access_token", token);
|
|
5009
|
+
if (rTok) localStorage.setItem("refresh_token", rTok);
|
|
5010
|
+
document.cookie = `token=${encodeURIComponent(token)}; path=/; domain=.qwen.ai; max-age=31536000`;
|
|
5011
|
+
} catch {
|
|
5012
|
+
}
|
|
5013
|
+
}, { token: accessToken, rTok: refreshToken });
|
|
5014
|
+
await session.context.addCookies([
|
|
5015
|
+
{
|
|
5016
|
+
name: "token",
|
|
5017
|
+
value: accessToken,
|
|
5018
|
+
domain: ".qwen.ai",
|
|
5019
|
+
path: "/",
|
|
5020
|
+
expires: Math.floor(Date.now() / 1e3) + 31536e3,
|
|
5021
|
+
httpOnly: false,
|
|
5022
|
+
secure: true,
|
|
5023
|
+
sameSite: "Lax"
|
|
5024
|
+
}
|
|
5025
|
+
]);
|
|
5026
|
+
await session.page.goto(qwenUrl("/"), {
|
|
5027
|
+
waitUntil: "domcontentloaded",
|
|
5028
|
+
timeout: Math.min(config.timeouts.navigation, 15e3)
|
|
5029
|
+
}).catch(() => {
|
|
5030
|
+
});
|
|
5031
|
+
await sleep2(1e3);
|
|
5032
|
+
await saveStorageState(session.context, session.accountId);
|
|
5033
|
+
const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
5034
|
+
const { parseJwtExpiry: parseJwtExpiry2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
5035
|
+
const exp = parseJwtExpiry2(accessToken);
|
|
5036
|
+
saveAuthSession2(session.accountId, {
|
|
5037
|
+
cookie: `token=${accessToken}`,
|
|
5038
|
+
userAgent: session.fingerprint.userAgent,
|
|
5039
|
+
bxV: "2.5.37",
|
|
5040
|
+
version: getQwenWebVersion() || "0.3.12",
|
|
5041
|
+
userId: userId || void 0,
|
|
5042
|
+
tokenExpiresAt: exp || void 0,
|
|
5043
|
+
refreshToken: refreshToken || void 0,
|
|
5044
|
+
capturedAt: Date.now()
|
|
5045
|
+
});
|
|
5046
|
+
const { addAccount: addAccount2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5047
|
+
const newAccount = addAccount2(session.email, "", session.accountId);
|
|
5048
|
+
clearTimeout(session.timer);
|
|
5049
|
+
activeOtpSessions.delete(sessionId);
|
|
5050
|
+
await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
|
|
5051
|
+
});
|
|
5052
|
+
cleanupPlaywrightAccountState(session.accountId);
|
|
5053
|
+
return { success: true, account: newAccount };
|
|
5054
|
+
} catch (err) {
|
|
5055
|
+
clearTimeout(session.timer);
|
|
5056
|
+
activeOtpSessions.delete(sessionId);
|
|
5057
|
+
await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
|
|
5058
|
+
});
|
|
5059
|
+
cleanupPlaywrightAccountState(session.accountId);
|
|
5060
|
+
return { success: false, error: err?.message || String(err) };
|
|
5061
|
+
}
|
|
5062
|
+
}
|
|
5063
|
+
async function cancelEmailOtpLogin(sessionId) {
|
|
5064
|
+
if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
|
|
5065
|
+
mockOtpSessions.delete(sessionId);
|
|
5066
|
+
return;
|
|
5067
|
+
}
|
|
5068
|
+
const session = activeOtpSessions.get(sessionId);
|
|
5069
|
+
if (!session) return;
|
|
5070
|
+
clearTimeout(session.timer);
|
|
5071
|
+
activeOtpSessions.delete(sessionId);
|
|
5072
|
+
await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
|
|
5073
|
+
});
|
|
5074
|
+
cleanupPlaywrightAccountState(session.accountId);
|
|
5075
|
+
const { wipeAccountSessionFiles: wipeAccountSessionFiles2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5076
|
+
wipeAccountSessionFiles2(session.accountId);
|
|
5077
|
+
}
|
|
4740
5078
|
function classifyQwenAuthError(code, details) {
|
|
4741
5079
|
const c = (code || "").trim().toLowerCase();
|
|
4742
5080
|
const d = (details || "").trim().toLowerCase();
|
|
@@ -5434,7 +5772,24 @@ async function refreshAccountToken(accountId) {
|
|
|
5434
5772
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
5435
5773
|
}
|
|
5436
5774
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
5437
|
-
|
|
5775
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
5776
|
+
if (!page || page.isClosed()) {
|
|
5777
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
5778
|
+
if (!isRunningUnderNodeTest4()) {
|
|
5779
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5780
|
+
const creds = getAccountCredentials2(accountId);
|
|
5781
|
+
if (creds) {
|
|
5782
|
+
await initPlaywrightForAccount(
|
|
5783
|
+
creds,
|
|
5784
|
+
config.playwright.headless,
|
|
5785
|
+
config.playwright.browser,
|
|
5786
|
+
{ skipHeaderCapture: true }
|
|
5787
|
+
).catch(() => {
|
|
5788
|
+
});
|
|
5789
|
+
page = accountPages.get(accountId);
|
|
5790
|
+
}
|
|
5791
|
+
}
|
|
5792
|
+
}
|
|
5438
5793
|
if (!page || page.isClosed()) {
|
|
5439
5794
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
5440
5795
|
}
|
|
@@ -6077,16 +6432,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
|
|
|
6077
6432
|
release();
|
|
6078
6433
|
}
|
|
6079
6434
|
}
|
|
6080
|
-
async function
|
|
6435
|
+
async function ensureAccountPageAvailable(accountId) {
|
|
6081
6436
|
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
6082
6437
|
if (inFlightInit) {
|
|
6083
6438
|
await inFlightInit.catch(() => {
|
|
6084
6439
|
});
|
|
6085
6440
|
}
|
|
6086
|
-
|
|
6441
|
+
let page = accountPages.get(accountId);
|
|
6442
|
+
if (page && !page.isClosed()) {
|
|
6443
|
+
return page;
|
|
6444
|
+
}
|
|
6445
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest4 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
6446
|
+
if (isRunningUnderNodeTest4()) {
|
|
6447
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6448
|
+
}
|
|
6449
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
6450
|
+
const creds = getAccountCredentials2(accountId);
|
|
6451
|
+
if (!creds) {
|
|
6452
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6453
|
+
}
|
|
6454
|
+
await initPlaywrightForAccount(
|
|
6455
|
+
creds,
|
|
6456
|
+
config.playwright.headless,
|
|
6457
|
+
config.playwright.browser
|
|
6458
|
+
);
|
|
6459
|
+
page = accountPages.get(accountId);
|
|
6087
6460
|
if (!page || page.isClosed()) {
|
|
6088
6461
|
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6089
6462
|
}
|
|
6463
|
+
return page;
|
|
6464
|
+
}
|
|
6465
|
+
async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
|
|
6466
|
+
const page = await ensureAccountPageAvailable(accountId);
|
|
6090
6467
|
const release = await acquireAccountMutex(
|
|
6091
6468
|
accountId,
|
|
6092
6469
|
`page:${accountId.substring(0, 12)}`,
|
|
@@ -6105,7 +6482,7 @@ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION
|
|
|
6105
6482
|
return result;
|
|
6106
6483
|
} catch (error) {
|
|
6107
6484
|
const message = getErrorMessage(error);
|
|
6108
|
-
if (recoverOnTimeout && message.includes("Playwright page operation timed out")) {
|
|
6485
|
+
if (recoverOnTimeout && message.includes("Playwright page operation timed out") && (!page || page.isClosed())) {
|
|
6109
6486
|
console.warn(
|
|
6110
6487
|
`\u23F1\uFE0F [Playwright] Resetting account context after a stuck page operation: ${accountId}`
|
|
6111
6488
|
);
|
|
@@ -6387,8 +6764,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
6387
6764
|
if (accountPages.size <= max) return 0;
|
|
6388
6765
|
const candidates = priorityOrderForEviction(
|
|
6389
6766
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
6767
|
+
if (isAccountServingStream(accountId)) return false;
|
|
6768
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
6390
6769
|
const mutex = accountMutexes.get(accountId);
|
|
6391
|
-
return mutex?.isIdle()
|
|
6770
|
+
return mutex?.isIdle();
|
|
6392
6771
|
})
|
|
6393
6772
|
).map((accountId) => ({
|
|
6394
6773
|
accountId,
|
|
@@ -6654,7 +7033,7 @@ async function getTokenDiagnostics(accountId) {
|
|
|
6654
7033
|
}
|
|
6655
7034
|
};
|
|
6656
7035
|
}
|
|
6657
|
-
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, PROFILE_RESET_TIMEOUT_MS;
|
|
7036
|
+
var requireLocal, contextInitHooks, ACCOUNT_MUTEX_MAX_HOLD_MS, accountMutexes, accountContexts, accountPages, cachedUserAgents, inFlightAccountInits, sharedBrowser, sharedBrowserPromise, headerCaches, HEADER_CACHE_TTL, HEADER_REFRESH_THRESHOLD, COOKIE_CACHE_TTL, cookieCaches, lastAccountActivity, lastKeepAliveNavigation, lastProactiveTokenRefresh, profileResetQueue, profileResetChain, closingAllPlaywright, sleep2, HEADER_CAPTURE_SETTLE_MS, PLAYWRIGHT_MUTEX_WAIT_MS, ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, SESSION_PROBE_NAVIGATION_TIMEOUT_MS, HEADER_CAPTURE_TRIGGER_GRACE_MS, FIRST_TRIGGER_GRACE_MS, HEADER_CAPTURE_TRIGGER_ATTEMPTS, CHAT_INPUT_APPEAR_TIMEOUT_MS, CHAT_INPUT_ACTION_TIMEOUT_MS, activeOtpSessions, mockOtpSessions, PROFILE_RESET_TIMEOUT_MS;
|
|
6658
7037
|
var init_playwright = __esm({
|
|
6659
7038
|
"src/services/playwright.ts"() {
|
|
6660
7039
|
"use strict";
|
|
@@ -6699,11 +7078,13 @@ var init_playwright = __esm({
|
|
|
6699
7078
|
PLAYWRIGHT_MUTEX_WAIT_MS = 6e4;
|
|
6700
7079
|
ACCOUNT_PAGE_OPERATION_TIMEOUT_MS = config.timeouts.page;
|
|
6701
7080
|
SESSION_PROBE_NAVIGATION_TIMEOUT_MS = 15e3;
|
|
6702
|
-
HEADER_CAPTURE_TRIGGER_GRACE_MS =
|
|
6703
|
-
FIRST_TRIGGER_GRACE_MS =
|
|
7081
|
+
HEADER_CAPTURE_TRIGGER_GRACE_MS = 2e4;
|
|
7082
|
+
FIRST_TRIGGER_GRACE_MS = 12e3;
|
|
6704
7083
|
HEADER_CAPTURE_TRIGGER_ATTEMPTS = 3;
|
|
6705
7084
|
CHAT_INPUT_APPEAR_TIMEOUT_MS = 15e3;
|
|
6706
7085
|
CHAT_INPUT_ACTION_TIMEOUT_MS = 1e4;
|
|
7086
|
+
activeOtpSessions = /* @__PURE__ */ new Map();
|
|
7087
|
+
mockOtpSessions = /* @__PURE__ */ new Map();
|
|
6707
7088
|
PROFILE_RESET_TIMEOUT_MS = Math.max(9e4, config.timeouts.headers);
|
|
6708
7089
|
setWafContextResetListener((accountId) => {
|
|
6709
7090
|
if (!accountPages.has(accountId)) return;
|
|
@@ -7715,6 +8096,15 @@ var init_helpers = __esm({
|
|
|
7715
8096
|
}
|
|
7716
8097
|
});
|
|
7717
8098
|
|
|
8099
|
+
// src/utils/overload-detector.ts
|
|
8100
|
+
var OVERLOAD_COOLDOWN_MS;
|
|
8101
|
+
var init_overload_detector = __esm({
|
|
8102
|
+
"src/utils/overload-detector.ts"() {
|
|
8103
|
+
"use strict";
|
|
8104
|
+
OVERLOAD_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
8105
|
+
}
|
|
8106
|
+
});
|
|
8107
|
+
|
|
7718
8108
|
// src/routes/chat/retry-policy.ts
|
|
7719
8109
|
var init_retry_policy = __esm({
|
|
7720
8110
|
"src/routes/chat/retry-policy.ts"() {
|
|
@@ -7725,6 +8115,7 @@ var init_retry_policy = __esm({
|
|
|
7725
8115
|
init_qwen();
|
|
7726
8116
|
init_errors();
|
|
7727
8117
|
init_helpers();
|
|
8118
|
+
init_overload_detector();
|
|
7728
8119
|
init_account_manager();
|
|
7729
8120
|
}
|
|
7730
8121
|
});
|
|
@@ -8688,7 +9079,8 @@ async function requestQwenPersonalizationInBrowser(accountId, method, path6, hea
|
|
|
8688
9079
|
payload === void 0 ? void 0 : JSON.stringify(payload),
|
|
8689
9080
|
{
|
|
8690
9081
|
settingsPage: true,
|
|
8691
|
-
referrer: qwenUrl("/settings/personalization")
|
|
9082
|
+
referrer: qwenUrl("/settings/personalization"),
|
|
9083
|
+
noMutexRecovery: true
|
|
8692
9084
|
}
|
|
8693
9085
|
);
|
|
8694
9086
|
const { raw, json } = await readJsonTextResponse(response);
|
|
@@ -9189,8 +9581,9 @@ async function syncQwenRequestPersonalizationInternal(instruction, accountId, me
|
|
|
9189
9581
|
const returnedInstruction = json?.data?.personalization?.instruction;
|
|
9190
9582
|
const returned = textSize(returnedInstruction);
|
|
9191
9583
|
let stored = { chars: null, bytes: null, hash: null };
|
|
9584
|
+
const matchReturned = isEmptyInstruction && (!returnedInstruction || returned.chars === 0) || returned.hash !== null && returned.hash === sent.hash;
|
|
9192
9585
|
let verifyData = null;
|
|
9193
|
-
if (config.qwen.personalizationVerifyGet) {
|
|
9586
|
+
if (config.qwen.personalizationVerifyGet && !matchReturned) {
|
|
9194
9587
|
const { json: verifyJson } = await requestQwenPersonalizationInBrowser(
|
|
9195
9588
|
accountId,
|
|
9196
9589
|
"GET",
|
|
@@ -9200,7 +9593,6 @@ async function syncQwenRequestPersonalizationInternal(instruction, accountId, me
|
|
|
9200
9593
|
verifyData = verifyJson?.data?.personalization;
|
|
9201
9594
|
stored = textSize(verifyData?.instruction);
|
|
9202
9595
|
}
|
|
9203
|
-
const matchReturned = isEmptyInstruction && (!returnedInstruction || returned.chars === 0) || returned.hash !== null && returned.hash === sent.hash;
|
|
9204
9596
|
const matchStored = stored.hash === null ? null : isEmptyInstruction && (!verifyData?.instruction || stored.chars === 0) || stored.hash === sent.hash;
|
|
9205
9597
|
const applied = matchReturned || matchStored === true;
|
|
9206
9598
|
if (syncHash && applied) {
|
|
@@ -9574,6 +9966,17 @@ async function fetchQwenModels(accountId) {
|
|
|
9574
9966
|
);
|
|
9575
9967
|
return cached.models;
|
|
9576
9968
|
}
|
|
9969
|
+
if (accountId && !isAuthMockEnabled() && !isPlaywrightInitialized(accountId)) {
|
|
9970
|
+
for (const [, entry] of modelsCache.entries()) {
|
|
9971
|
+
if (entry && now - entry.fetchedAt < MODEL_CACHE_TTL_MS * 4) {
|
|
9972
|
+
syncModelMetadata(
|
|
9973
|
+
entry.models,
|
|
9974
|
+
accountId
|
|
9975
|
+
);
|
|
9976
|
+
return entry.models;
|
|
9977
|
+
}
|
|
9978
|
+
}
|
|
9979
|
+
}
|
|
9577
9980
|
if (accountId && !isAuthMockEnabled() && isAccountBusy(accountId)) {
|
|
9578
9981
|
try {
|
|
9579
9982
|
const result = await withAccountPage(
|