qwenproxy-cli 1.6.0 → 1.6.2
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 +54 -7
- package/dist/delete-chats.js +380 -12
- package/dist/index.js +774 -117
- package/dist/login.js +432 -13
- package/dist/reset-cooldowns.js +64 -7
- package/dist/sync-clients.js +2 -2
- package/package.json +3 -3
- package/src/clean-cache.ts +3 -0
- package/src/core/accounts.ts +1 -1
- package/src/core/config.ts +2 -2
- package/src/core/process-guard.ts +77 -0
- package/src/core/version.ts +1 -1
- package/src/delete-chats.ts +3 -0
- package/src/index.ts +3 -29
- package/src/login.ts +92 -4
- package/src/routes/chat/account.ts +11 -4
- package/src/routes/chat/retry-policy.ts +75 -3
- package/src/routes/chat/streaming.ts +68 -1
- package/src/services/chat-cleanup.ts +5 -0
- package/src/services/playwright.ts +384 -6
- 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/login.js
CHANGED
|
@@ -826,7 +826,7 @@ function addAccountsBatch(entries) {
|
|
|
826
826
|
}
|
|
827
827
|
return { added, skipped, invalid };
|
|
828
828
|
}
|
|
829
|
-
function addAccount(email, password, id) {
|
|
829
|
+
function addAccount(email, password = "", id) {
|
|
830
830
|
if (!email || typeof email !== "string" || email.trim().length === 0) {
|
|
831
831
|
throw new Error("Email is required");
|
|
832
832
|
}
|
|
@@ -1102,6 +1102,44 @@ var init_logger = __esm({
|
|
|
1102
1102
|
}
|
|
1103
1103
|
});
|
|
1104
1104
|
|
|
1105
|
+
// src/core/process-guard.ts
|
|
1106
|
+
function isPlaywrightAlreadyClosedMessage(message) {
|
|
1107
|
+
if (!message) return false;
|
|
1108
|
+
return message.includes("Target page, context or browser has been closed") || message.includes("Browser has been closed") || message.includes("Target closed") || message.includes("Target crashed") || message.includes("Page crashed") || message.includes("Assertion error") || message.includes("Cannot find parent object") || message.includes("Connection closed") || message.includes("session closed") || message.includes("Session closed") || message.includes("Network.setCacheDisabled") || message.includes("Protocol error") || message.includes("storageState timed out");
|
|
1109
|
+
}
|
|
1110
|
+
function isBenignTeardownError(error) {
|
|
1111
|
+
if (!error) return false;
|
|
1112
|
+
const message = error instanceof Error ? error.message : typeof error === "object" && "message" in error ? String(error.message) : String(error);
|
|
1113
|
+
return isPlaywrightAlreadyClosedMessage(message);
|
|
1114
|
+
}
|
|
1115
|
+
function installProcessTeardownGuards(label = "Process") {
|
|
1116
|
+
if (guardsInstalled) return;
|
|
1117
|
+
guardsInstalled = true;
|
|
1118
|
+
process.on("uncaughtException", (error) => {
|
|
1119
|
+
if (isBenignTeardownError(error)) {
|
|
1120
|
+
const msg = error instanceof Error ? error.message : typeof error === "object" && error !== null && "message" in error ? String(error.message) : String(error);
|
|
1121
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown exception: ${msg}`);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
console.error(`\u274C [${label}] Uncaught Exception:`, error);
|
|
1125
|
+
});
|
|
1126
|
+
process.on("unhandledRejection", (reason) => {
|
|
1127
|
+
if (isBenignTeardownError(reason)) {
|
|
1128
|
+
const msg = reason instanceof Error ? reason.message : typeof reason === "object" && reason !== null && "message" in reason ? String(reason.message) : String(reason);
|
|
1129
|
+
console.warn(`\u26A0\uFE0F [${label}] Handled benign driver teardown rejection: ${msg}`);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
console.error(`\u274C [${label}] Unhandled Rejection:`, reason);
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
var guardsInstalled;
|
|
1136
|
+
var init_process_guard = __esm({
|
|
1137
|
+
"src/core/process-guard.ts"() {
|
|
1138
|
+
"use strict";
|
|
1139
|
+
guardsInstalled = false;
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1105
1143
|
// src/core/config.ts
|
|
1106
1144
|
var config_exports = {};
|
|
1107
1145
|
__export(config_exports, {
|
|
@@ -1251,11 +1289,11 @@ var init_config = __esm({
|
|
|
1251
1289
|
// nothing in this window is dead and should fail fast, retryable).
|
|
1252
1290
|
QWEN_FIRST_CHUNK_TIMEOUT: z.string().default("60000"),
|
|
1253
1291
|
TOTAL_REQUEST_TIMEOUT: z.string().default("600000"),
|
|
1254
|
-
// Mid-stream silence window for thinking models:
|
|
1292
|
+
// Mid-stream silence window for thinking models: 5 min with ZERO upstream
|
|
1255
1293
|
// bytes is a dead stream (WAF swallow / dropped connection) — fail fast and
|
|
1256
1294
|
// let the retry policy rotate accounts. Flowing reasoning chunks RESET this
|
|
1257
1295
|
// timer, so legitimate slow thinking is never cut; only total silence is.
|
|
1258
|
-
REASONING_MODEL_TIMEOUT: z.string().default("
|
|
1296
|
+
REASONING_MODEL_TIMEOUT: z.string().default("300000"),
|
|
1259
1297
|
CACHE_TTL: z.string().default("3600"),
|
|
1260
1298
|
RESPONSE_TTL: z.string().default("1800"),
|
|
1261
1299
|
CACHE_COMPRESSION_ENABLED: z.string().default("true"),
|
|
@@ -3127,6 +3165,8 @@ var init_clean_cache = __esm({
|
|
|
3127
3165
|
"src/clean-cache.ts"() {
|
|
3128
3166
|
"use strict";
|
|
3129
3167
|
init_playwright();
|
|
3168
|
+
init_process_guard();
|
|
3169
|
+
installProcessTeardownGuards("CleanCache");
|
|
3130
3170
|
isDirectRun = process.argv[1] && (process.argv[1].endsWith("clean-cache.ts") || process.argv[1].endsWith("clean-cache.js"));
|
|
3131
3171
|
if (isDirectRun) {
|
|
3132
3172
|
main().catch((err) => {
|
|
@@ -3142,6 +3182,7 @@ var playwright_exports = {};
|
|
|
3142
3182
|
__export(playwright_exports, {
|
|
3143
3183
|
assertAntiBotHeaders: () => assertAntiBotHeaders,
|
|
3144
3184
|
buildChromiumLaunchArgs: () => buildChromiumLaunchArgs,
|
|
3185
|
+
cancelEmailOtpLogin: () => cancelEmailOtpLogin,
|
|
3145
3186
|
captureQwenHeaders: () => captureQwenHeaders,
|
|
3146
3187
|
classifyQwenAuthError: () => classifyQwenAuthError,
|
|
3147
3188
|
cleanupOrphanProfiles: () => cleanupOrphanProfiles,
|
|
@@ -3182,8 +3223,10 @@ __export(playwright_exports, {
|
|
|
3182
3223
|
removePlaywrightProfile: () => removePlaywrightProfile,
|
|
3183
3224
|
saveStorageState: () => saveStorageState,
|
|
3184
3225
|
schedulePlaywrightProfileReset: () => schedulePlaywrightProfileReset,
|
|
3226
|
+
startEmailOtpLogin: () => startEmailOtpLogin,
|
|
3185
3227
|
unregisterPlaywrightAccountForTests: () => unregisterPlaywrightAccountForTests,
|
|
3186
3228
|
validateAccountLogin: () => validateAccountLogin,
|
|
3229
|
+
verifyEmailOtpLogin: () => verifyEmailOtpLogin,
|
|
3187
3230
|
withAccountPage: () => withAccountPage
|
|
3188
3231
|
});
|
|
3189
3232
|
import { chromium as chromium2 } from "patchright";
|
|
@@ -3351,15 +3394,18 @@ function loadStorageState(accountId) {
|
|
|
3351
3394
|
return void 0;
|
|
3352
3395
|
}
|
|
3353
3396
|
}
|
|
3354
|
-
async function saveStorageState(context, accountId, timeoutMs =
|
|
3397
|
+
async function saveStorageState(context, accountId, timeoutMs = 1e4) {
|
|
3355
3398
|
try {
|
|
3356
3399
|
const stateFile = getStorageStatePath(accountId);
|
|
3357
3400
|
const dir = path5.dirname(stateFile);
|
|
3358
3401
|
if (!fs8.existsSync(dir)) {
|
|
3359
3402
|
fs8.mkdirSync(dir, { recursive: true });
|
|
3360
3403
|
}
|
|
3404
|
+
const rawPromise = context.storageState({ path: stateFile }).catch(() => {
|
|
3405
|
+
return void 0;
|
|
3406
|
+
});
|
|
3361
3407
|
await withTimeout(
|
|
3362
|
-
|
|
3408
|
+
rawPromise,
|
|
3363
3409
|
timeoutMs,
|
|
3364
3410
|
`storageState timed out after ${timeoutMs}ms`
|
|
3365
3411
|
);
|
|
@@ -3522,6 +3568,10 @@ async function clearVisibleChallenge(page) {
|
|
|
3522
3568
|
"button[aria-label*='Close' i]",
|
|
3523
3569
|
"button[aria-label*='Fechar' i]",
|
|
3524
3570
|
".index-module__whats-new-modal-close",
|
|
3571
|
+
".age-confirmation-modal button",
|
|
3572
|
+
".age-confirmation-modal .qwen-chat-v2-btn-black",
|
|
3573
|
+
"button:has-text('Continuar')",
|
|
3574
|
+
"button:has-text('Continue')",
|
|
3525
3575
|
"button:has-text('Cancel')",
|
|
3526
3576
|
"button:has-text('Cancelar')",
|
|
3527
3577
|
"button:has-text('I understand')",
|
|
@@ -4716,6 +4766,292 @@ async function validateAccountLogin(rawAccount, headless = true, browserType = "
|
|
|
4716
4766
|
release();
|
|
4717
4767
|
}
|
|
4718
4768
|
}
|
|
4769
|
+
async function startEmailOtpLogin(email, options = {}) {
|
|
4770
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
4771
|
+
if (!normalizedEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalizedEmail)) {
|
|
4772
|
+
return { success: false, error: "Formato de e-mail inv\xE1lido" };
|
|
4773
|
+
}
|
|
4774
|
+
const { listAccounts: listAccounts2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4775
|
+
if (listAccounts2().some((a) => a.email.toLowerCase() === normalizedEmail)) {
|
|
4776
|
+
return {
|
|
4777
|
+
success: false,
|
|
4778
|
+
error: `Uma conta com o e-mail "${normalizedEmail}" j\xE1 est\xE1 cadastrada.`
|
|
4779
|
+
};
|
|
4780
|
+
}
|
|
4781
|
+
if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
|
|
4782
|
+
const mockSessionId = "mock-otp-" + Date.now();
|
|
4783
|
+
mockOtpSessions.set(mockSessionId, normalizedEmail);
|
|
4784
|
+
return { success: true, sessionId: mockSessionId };
|
|
4785
|
+
}
|
|
4786
|
+
const accountId = crypto3.randomUUID();
|
|
4787
|
+
const sessionId = accountId;
|
|
4788
|
+
const profilePath = getAccountProfilePath(accountId);
|
|
4789
|
+
const fingerprint = getFingerprintProfile(accountId);
|
|
4790
|
+
const browserType = options.browserType ?? config.playwright.browser;
|
|
4791
|
+
const { engine, channel } = resolveBrowserEngine(browserType);
|
|
4792
|
+
const headless = options.headless ?? config.playwright.headless;
|
|
4793
|
+
const launchOptions = {
|
|
4794
|
+
headless,
|
|
4795
|
+
channel,
|
|
4796
|
+
userAgent: fingerprint.userAgent,
|
|
4797
|
+
locale: fingerprint.locale,
|
|
4798
|
+
timezoneId: fingerprint.timezoneId,
|
|
4799
|
+
viewport: fingerprint.viewport,
|
|
4800
|
+
screen: fingerprint.viewport,
|
|
4801
|
+
deviceScaleFactor: 1,
|
|
4802
|
+
isMobile: false,
|
|
4803
|
+
hasTouch: false,
|
|
4804
|
+
colorScheme: "light",
|
|
4805
|
+
extraHTTPHeaders: {
|
|
4806
|
+
"sec-ch-ua": fingerprint.secChUa,
|
|
4807
|
+
"sec-ch-ua-mobile": "?0",
|
|
4808
|
+
"sec-ch-ua-platform": '"Windows"'
|
|
4809
|
+
},
|
|
4810
|
+
ignoreDefaultArgs: ["--enable-automation", "--enable-blink-features"],
|
|
4811
|
+
args: buildChromiumLaunchArgs(fingerprint.viewport)
|
|
4812
|
+
};
|
|
4813
|
+
let acctContext;
|
|
4814
|
+
try {
|
|
4815
|
+
acctContext = await withTimeout(
|
|
4816
|
+
engine.launchPersistentContext(profilePath, launchOptions),
|
|
4817
|
+
3e4,
|
|
4818
|
+
`O navegador n\xE3o iniciou em 30s para a conta ${maskEmail(normalizedEmail)}.`
|
|
4819
|
+
);
|
|
4820
|
+
} catch (launchErr) {
|
|
4821
|
+
if (launchErr?.message?.includes("Executable doesn't exist")) {
|
|
4822
|
+
autoInstallPlaywrightChromium();
|
|
4823
|
+
acctContext = await withTimeout(
|
|
4824
|
+
engine.launchPersistentContext(profilePath, launchOptions),
|
|
4825
|
+
3e4,
|
|
4826
|
+
`O navegador n\xE3o iniciou em 30s para a conta ${maskEmail(normalizedEmail)}.`
|
|
4827
|
+
);
|
|
4828
|
+
} else {
|
|
4829
|
+
return { success: false, error: launchErr?.message || String(launchErr) };
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
try {
|
|
4833
|
+
await acctContext.addInitScript(getStealthScript(fingerprint));
|
|
4834
|
+
const existingPages = acctContext.pages().filter((p) => !p.isClosed());
|
|
4835
|
+
const acctPage = existingPages.find((p) => p.url().startsWith(qwenOrigin())) ?? existingPages[0] ?? await acctContext.newPage();
|
|
4836
|
+
await acctPage.goto(qwenUrl("/auth?mode=register"), {
|
|
4837
|
+
waitUntil: "domcontentloaded",
|
|
4838
|
+
timeout: Math.min(config.timeouts.navigation, 2e4)
|
|
4839
|
+
}).catch(() => {
|
|
4840
|
+
});
|
|
4841
|
+
await sleep2(1e3);
|
|
4842
|
+
await clearVisibleChallenge(acctPage);
|
|
4843
|
+
const requestId = crypto3.randomUUID();
|
|
4844
|
+
const evalRes = await acctPage.evaluate(
|
|
4845
|
+
async ({ email: email2, requestId: requestId2, version, timezone }) => {
|
|
4846
|
+
try {
|
|
4847
|
+
const response = await fetch("https://auth.qwen.ai/api/v2/auths/otp/email/request", {
|
|
4848
|
+
method: "POST",
|
|
4849
|
+
credentials: "include",
|
|
4850
|
+
headers: {
|
|
4851
|
+
accept: "application/json, text/plain, */*",
|
|
4852
|
+
"content-type": "application/json",
|
|
4853
|
+
source: "web",
|
|
4854
|
+
version,
|
|
4855
|
+
"x-request-origin": "https://chat.qwen.ai",
|
|
4856
|
+
timezone,
|
|
4857
|
+
"x-request-id": requestId2
|
|
4858
|
+
},
|
|
4859
|
+
body: JSON.stringify({
|
|
4860
|
+
identifier: email2,
|
|
4861
|
+
accept_terms: true,
|
|
4862
|
+
language: "pt-BR"
|
|
4863
|
+
})
|
|
4864
|
+
});
|
|
4865
|
+
const json = await response.json().catch(() => null);
|
|
4866
|
+
return { ok: response.ok, status: response.status, data: json };
|
|
4867
|
+
} catch (e) {
|
|
4868
|
+
return { ok: false, error: e.message };
|
|
4869
|
+
}
|
|
4870
|
+
},
|
|
4871
|
+
{
|
|
4872
|
+
email: normalizedEmail,
|
|
4873
|
+
requestId,
|
|
4874
|
+
version: getQwenWebVersion() || "0.3.12",
|
|
4875
|
+
timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
|
|
4876
|
+
}
|
|
4877
|
+
).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
4878
|
+
if (!evalRes.ok || evalRes.data?.success !== true) {
|
|
4879
|
+
const errorMsg = evalRes.data?.data?.details || evalRes.data?.data?.message || evalRes.data?.message || evalRes.error || "Falha ao solicitar c\xF3digo de verifica\xE7\xE3o";
|
|
4880
|
+
await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
|
|
4881
|
+
});
|
|
4882
|
+
cleanupPlaywrightAccountState(accountId);
|
|
4883
|
+
return { success: false, error: errorMsg };
|
|
4884
|
+
}
|
|
4885
|
+
const timer = setTimeout(async () => {
|
|
4886
|
+
activeOtpSessions.delete(sessionId);
|
|
4887
|
+
await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
|
|
4888
|
+
});
|
|
4889
|
+
cleanupPlaywrightAccountState(accountId);
|
|
4890
|
+
}, 5 * 60 * 1e3);
|
|
4891
|
+
timer.unref?.();
|
|
4892
|
+
activeOtpSessions.set(sessionId, {
|
|
4893
|
+
sessionId,
|
|
4894
|
+
accountId,
|
|
4895
|
+
email: normalizedEmail,
|
|
4896
|
+
context: acctContext,
|
|
4897
|
+
page: acctPage,
|
|
4898
|
+
fingerprint,
|
|
4899
|
+
createdAt: Date.now(),
|
|
4900
|
+
timer
|
|
4901
|
+
});
|
|
4902
|
+
return { success: true, sessionId };
|
|
4903
|
+
} catch (err) {
|
|
4904
|
+
await closePlaywrightContextBestEffort(accountId, acctContext).catch(() => {
|
|
4905
|
+
});
|
|
4906
|
+
cleanupPlaywrightAccountState(accountId);
|
|
4907
|
+
return { success: false, error: err?.message || String(err) };
|
|
4908
|
+
}
|
|
4909
|
+
}
|
|
4910
|
+
async function verifyEmailOtpLogin(sessionId, code) {
|
|
4911
|
+
const cleanCode = code.trim();
|
|
4912
|
+
if (!cleanCode) {
|
|
4913
|
+
return { success: false, error: "C\xF3digo de verifica\xE7\xE3o \xE9 obrigat\xF3rio" };
|
|
4914
|
+
}
|
|
4915
|
+
if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
|
|
4916
|
+
const mockEmail = mockOtpSessions.get(sessionId);
|
|
4917
|
+
if (!mockEmail) {
|
|
4918
|
+
return { success: false, error: "Sess\xE3o OTP inv\xE1lida ou expirada" };
|
|
4919
|
+
}
|
|
4920
|
+
if (cleanCode === "000000") {
|
|
4921
|
+
return { success: false, error: "C\xF3digo de verifica\xE7\xE3o incorreto ou expirado" };
|
|
4922
|
+
}
|
|
4923
|
+
const { addAccount: addAccount2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
4924
|
+
const mockAccId = "mock-otp-acc-" + Date.now();
|
|
4925
|
+
const newAccount = addAccount2(mockEmail, "", mockAccId);
|
|
4926
|
+
mockOtpSessions.delete(sessionId);
|
|
4927
|
+
return { success: true, account: newAccount };
|
|
4928
|
+
}
|
|
4929
|
+
const session = activeOtpSessions.get(sessionId);
|
|
4930
|
+
if (!session) {
|
|
4931
|
+
return { success: false, error: "Sess\xE3o OTP expirada ou n\xE3o encontrada. Solicite um novo c\xF3digo." };
|
|
4932
|
+
}
|
|
4933
|
+
if (session.page.isClosed()) {
|
|
4934
|
+
activeOtpSessions.delete(sessionId);
|
|
4935
|
+
return { success: false, error: "Navegador foi fechado. Solicite um novo c\xF3digo." };
|
|
4936
|
+
}
|
|
4937
|
+
const requestId = crypto3.randomUUID();
|
|
4938
|
+
const evalRes = await session.page.evaluate(
|
|
4939
|
+
async ({ email, code: code2, requestId: requestId2, version, timezone }) => {
|
|
4940
|
+
try {
|
|
4941
|
+
const response = await fetch("https://auth.qwen.ai/api/v2/auths/otp/email/verify", {
|
|
4942
|
+
method: "POST",
|
|
4943
|
+
credentials: "include",
|
|
4944
|
+
headers: {
|
|
4945
|
+
accept: "application/json, text/plain, */*",
|
|
4946
|
+
"content-type": "application/json",
|
|
4947
|
+
source: "web",
|
|
4948
|
+
version,
|
|
4949
|
+
"x-request-origin": "https://chat.qwen.ai",
|
|
4950
|
+
timezone,
|
|
4951
|
+
"x-request-id": requestId2
|
|
4952
|
+
},
|
|
4953
|
+
body: JSON.stringify({
|
|
4954
|
+
identifier: email,
|
|
4955
|
+
code: code2,
|
|
4956
|
+
name: null
|
|
4957
|
+
})
|
|
4958
|
+
});
|
|
4959
|
+
const json = await response.json().catch(() => null);
|
|
4960
|
+
return { ok: response.ok, status: response.status, data: json };
|
|
4961
|
+
} catch (e) {
|
|
4962
|
+
return { ok: false, error: e.message };
|
|
4963
|
+
}
|
|
4964
|
+
},
|
|
4965
|
+
{
|
|
4966
|
+
email: session.email,
|
|
4967
|
+
code: cleanCode,
|
|
4968
|
+
requestId,
|
|
4969
|
+
version: getQwenWebVersion() || "0.3.12",
|
|
4970
|
+
timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0]
|
|
4971
|
+
}
|
|
4972
|
+
).catch((err) => ({ ok: false, error: err?.message || String(err) }));
|
|
4973
|
+
if (!evalRes.ok || evalRes.data?.success !== true || !evalRes.data?.data) {
|
|
4974
|
+
const errorMsg = evalRes.data?.data?.details || evalRes.data?.data?.message || evalRes.data?.message || evalRes.error || "C\xF3digo de verifica\xE7\xE3o incorreto ou expirado";
|
|
4975
|
+
return { success: false, error: errorMsg };
|
|
4976
|
+
}
|
|
4977
|
+
const resData = evalRes.data.data;
|
|
4978
|
+
const accessToken = resData.access_token || resData.token;
|
|
4979
|
+
const refreshToken = resData.refresh_token;
|
|
4980
|
+
const userId = resData.id;
|
|
4981
|
+
try {
|
|
4982
|
+
await session.page.evaluate(({ token, rTok }) => {
|
|
4983
|
+
try {
|
|
4984
|
+
localStorage.setItem("token", token);
|
|
4985
|
+
localStorage.setItem("access_token", token);
|
|
4986
|
+
if (rTok) localStorage.setItem("refresh_token", rTok);
|
|
4987
|
+
document.cookie = `token=${encodeURIComponent(token)}; path=/; domain=.qwen.ai; max-age=31536000`;
|
|
4988
|
+
} catch {
|
|
4989
|
+
}
|
|
4990
|
+
}, { token: accessToken, rTok: refreshToken });
|
|
4991
|
+
await session.context.addCookies([
|
|
4992
|
+
{
|
|
4993
|
+
name: "token",
|
|
4994
|
+
value: accessToken,
|
|
4995
|
+
domain: ".qwen.ai",
|
|
4996
|
+
path: "/",
|
|
4997
|
+
expires: Math.floor(Date.now() / 1e3) + 31536e3,
|
|
4998
|
+
httpOnly: false,
|
|
4999
|
+
secure: true,
|
|
5000
|
+
sameSite: "Lax"
|
|
5001
|
+
}
|
|
5002
|
+
]);
|
|
5003
|
+
await session.page.goto(qwenUrl("/"), {
|
|
5004
|
+
waitUntil: "domcontentloaded",
|
|
5005
|
+
timeout: Math.min(config.timeouts.navigation, 15e3)
|
|
5006
|
+
}).catch(() => {
|
|
5007
|
+
});
|
|
5008
|
+
await sleep2(1e3);
|
|
5009
|
+
await saveStorageState(session.context, session.accountId);
|
|
5010
|
+
const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
5011
|
+
const { parseJwtExpiry: parseJwtExpiry2 } = await Promise.resolve().then(() => (init_jwt(), jwt_exports));
|
|
5012
|
+
const exp = parseJwtExpiry2(accessToken);
|
|
5013
|
+
saveAuthSession2(session.accountId, {
|
|
5014
|
+
cookie: `token=${accessToken}`,
|
|
5015
|
+
userAgent: session.fingerprint.userAgent,
|
|
5016
|
+
bxV: "2.5.37",
|
|
5017
|
+
version: getQwenWebVersion() || "0.3.12",
|
|
5018
|
+
userId: userId || void 0,
|
|
5019
|
+
tokenExpiresAt: exp || void 0,
|
|
5020
|
+
refreshToken: refreshToken || void 0,
|
|
5021
|
+
capturedAt: Date.now()
|
|
5022
|
+
});
|
|
5023
|
+
const { addAccount: addAccount2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5024
|
+
const newAccount = addAccount2(session.email, "", session.accountId);
|
|
5025
|
+
clearTimeout(session.timer);
|
|
5026
|
+
activeOtpSessions.delete(sessionId);
|
|
5027
|
+
await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
|
|
5028
|
+
});
|
|
5029
|
+
cleanupPlaywrightAccountState(session.accountId);
|
|
5030
|
+
return { success: true, account: newAccount };
|
|
5031
|
+
} catch (err) {
|
|
5032
|
+
clearTimeout(session.timer);
|
|
5033
|
+
activeOtpSessions.delete(sessionId);
|
|
5034
|
+
await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
|
|
5035
|
+
});
|
|
5036
|
+
cleanupPlaywrightAccountState(session.accountId);
|
|
5037
|
+
return { success: false, error: err?.message || String(err) };
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
async function cancelEmailOtpLogin(sessionId) {
|
|
5041
|
+
if (process.env.TEST_MOCK_QWEN_AUTH === "true" && process.env.NODE_ENV !== "production") {
|
|
5042
|
+
mockOtpSessions.delete(sessionId);
|
|
5043
|
+
return;
|
|
5044
|
+
}
|
|
5045
|
+
const session = activeOtpSessions.get(sessionId);
|
|
5046
|
+
if (!session) return;
|
|
5047
|
+
clearTimeout(session.timer);
|
|
5048
|
+
activeOtpSessions.delete(sessionId);
|
|
5049
|
+
await closePlaywrightContextBestEffort(session.accountId, session.context).catch(() => {
|
|
5050
|
+
});
|
|
5051
|
+
cleanupPlaywrightAccountState(session.accountId);
|
|
5052
|
+
const { wipeAccountSessionFiles: wipeAccountSessionFiles2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5053
|
+
wipeAccountSessionFiles2(session.accountId);
|
|
5054
|
+
}
|
|
4719
5055
|
function classifyQwenAuthError(code, details) {
|
|
4720
5056
|
const c = (code || "").trim().toLowerCase();
|
|
4721
5057
|
const d = (details || "").trim().toLowerCase();
|
|
@@ -6084,7 +6420,7 @@ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION
|
|
|
6084
6420
|
return result;
|
|
6085
6421
|
} catch (error) {
|
|
6086
6422
|
const message = getErrorMessage(error);
|
|
6087
|
-
if (recoverOnTimeout && message.includes("Playwright page operation timed out")) {
|
|
6423
|
+
if (recoverOnTimeout && message.includes("Playwright page operation timed out") && (!page || page.isClosed())) {
|
|
6088
6424
|
console.warn(
|
|
6089
6425
|
`\u23F1\uFE0F [Playwright] Resetting account context after a stuck page operation: ${accountId}`
|
|
6090
6426
|
);
|
|
@@ -6633,7 +6969,7 @@ async function getTokenDiagnostics(accountId) {
|
|
|
6633
6969
|
}
|
|
6634
6970
|
};
|
|
6635
6971
|
}
|
|
6636
|
-
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;
|
|
6972
|
+
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;
|
|
6637
6973
|
var init_playwright = __esm({
|
|
6638
6974
|
"src/services/playwright.ts"() {
|
|
6639
6975
|
"use strict";
|
|
@@ -6678,11 +7014,13 @@ var init_playwright = __esm({
|
|
|
6678
7014
|
PLAYWRIGHT_MUTEX_WAIT_MS = 6e4;
|
|
6679
7015
|
ACCOUNT_PAGE_OPERATION_TIMEOUT_MS = config.timeouts.page;
|
|
6680
7016
|
SESSION_PROBE_NAVIGATION_TIMEOUT_MS = 15e3;
|
|
6681
|
-
HEADER_CAPTURE_TRIGGER_GRACE_MS =
|
|
6682
|
-
FIRST_TRIGGER_GRACE_MS =
|
|
7017
|
+
HEADER_CAPTURE_TRIGGER_GRACE_MS = 2e4;
|
|
7018
|
+
FIRST_TRIGGER_GRACE_MS = 12e3;
|
|
6683
7019
|
HEADER_CAPTURE_TRIGGER_ATTEMPTS = 3;
|
|
6684
7020
|
CHAT_INPUT_APPEAR_TIMEOUT_MS = 15e3;
|
|
6685
7021
|
CHAT_INPUT_ACTION_TIMEOUT_MS = 1e4;
|
|
7022
|
+
activeOtpSessions = /* @__PURE__ */ new Map();
|
|
7023
|
+
mockOtpSessions = /* @__PURE__ */ new Map();
|
|
6686
7024
|
PROFILE_RESET_TIMEOUT_MS = Math.max(9e4, config.timeouts.headers);
|
|
6687
7025
|
setWafContextResetListener((accountId) => {
|
|
6688
7026
|
if (!accountPages.has(accountId)) return;
|
|
@@ -6698,10 +7036,12 @@ var init_playwright = __esm({
|
|
|
6698
7036
|
// src/login.ts
|
|
6699
7037
|
init_accounts();
|
|
6700
7038
|
init_logger();
|
|
7039
|
+
init_process_guard();
|
|
6701
7040
|
import crypto4 from "crypto";
|
|
6702
7041
|
import * as readline from "readline";
|
|
6703
7042
|
import * as dotenv2 from "dotenv";
|
|
6704
7043
|
dotenv2.config();
|
|
7044
|
+
installProcessTeardownGuards("Login");
|
|
6705
7045
|
var rl = readline.createInterface({
|
|
6706
7046
|
input: process.stdin,
|
|
6707
7047
|
output: process.stdout
|
|
@@ -6734,7 +7074,8 @@ async function showMenu() {
|
|
|
6734
7074
|
console.log("No accounts configured yet.\n");
|
|
6735
7075
|
}
|
|
6736
7076
|
console.log("\nOptions:");
|
|
6737
|
-
console.log(" [A] Add account");
|
|
7077
|
+
console.log(" [A] Add account (Password)");
|
|
7078
|
+
console.log(" [O] Add account (Passwordless / Email OTP)");
|
|
6738
7079
|
if (accounts.length > 0) {
|
|
6739
7080
|
console.log(" [R] Remove an account");
|
|
6740
7081
|
}
|
|
@@ -6745,7 +7086,21 @@ async function showMenu() {
|
|
|
6745
7086
|
process.exit(0);
|
|
6746
7087
|
}
|
|
6747
7088
|
if (choice === "A") {
|
|
6748
|
-
|
|
7089
|
+
clear();
|
|
7090
|
+
console.log("=== Add New Account ===\n");
|
|
7091
|
+
console.log("Authentication method:");
|
|
7092
|
+
console.log(" [1] Traditional Password (Email + Password)");
|
|
7093
|
+
console.log(" [2] Passwordless (Email Verification Code / OTP)\n");
|
|
7094
|
+
const method = (await askQuestion("Select method [1/2] (default 1): ")).trim();
|
|
7095
|
+
if (method === "2") {
|
|
7096
|
+
await addAccountOtpFlow();
|
|
7097
|
+
} else {
|
|
7098
|
+
await addAccountPasswordFlow();
|
|
7099
|
+
}
|
|
7100
|
+
continue;
|
|
7101
|
+
}
|
|
7102
|
+
if (choice === "O") {
|
|
7103
|
+
await addAccountOtpFlow();
|
|
6749
7104
|
continue;
|
|
6750
7105
|
}
|
|
6751
7106
|
if (choice === "R" && accounts.length > 0) {
|
|
@@ -6754,9 +7109,9 @@ async function showMenu() {
|
|
|
6754
7109
|
}
|
|
6755
7110
|
}
|
|
6756
7111
|
}
|
|
6757
|
-
async function
|
|
7112
|
+
async function addAccountPasswordFlow() {
|
|
6758
7113
|
clear();
|
|
6759
|
-
console.log("=== Add New Account ===\n");
|
|
7114
|
+
console.log("=== Add New Account (Password Login) ===\n");
|
|
6760
7115
|
const email = await askQuestion("Email: ");
|
|
6761
7116
|
if (!email || !email.trim()) {
|
|
6762
7117
|
console.log("Email is required.");
|
|
@@ -6825,6 +7180,70 @@ async function addAccountFlow() {
|
|
|
6825
7180
|
}
|
|
6826
7181
|
await askQuestion("\nPress Enter to continue...");
|
|
6827
7182
|
}
|
|
7183
|
+
async function addAccountOtpFlow() {
|
|
7184
|
+
clear();
|
|
7185
|
+
console.log("=== Add New Account (Passwordless / Email OTP) ===\n");
|
|
7186
|
+
const email = await askQuestion("Email: ");
|
|
7187
|
+
if (!email || !email.trim()) {
|
|
7188
|
+
console.log("Email is required.");
|
|
7189
|
+
await askQuestion("Press Enter to continue...");
|
|
7190
|
+
return;
|
|
7191
|
+
}
|
|
7192
|
+
const trimmedEmail = email.trim();
|
|
7193
|
+
const existingAccounts = listAccounts();
|
|
7194
|
+
if (existingAccounts.some(
|
|
7195
|
+
(a) => a.email.toLowerCase() === trimmedEmail.toLowerCase()
|
|
7196
|
+
)) {
|
|
7197
|
+
console.log(
|
|
7198
|
+
`
|
|
7199
|
+
\u274C Error: An account with email "${trimmedEmail}" already exists.`
|
|
7200
|
+
);
|
|
7201
|
+
await askQuestion("Press Enter to continue...");
|
|
7202
|
+
return;
|
|
7203
|
+
}
|
|
7204
|
+
console.log(
|
|
7205
|
+
`
|
|
7206
|
+
\u23F3 Requesting OTP verification code for ${maskEmail(trimmedEmail)}...`
|
|
7207
|
+
);
|
|
7208
|
+
const { startEmailOtpLogin: startEmailOtpLogin2, verifyEmailOtpLogin: verifyEmailOtpLogin2, cancelEmailOtpLogin: cancelEmailOtpLogin2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
7209
|
+
const startRes = await startEmailOtpLogin2(trimmedEmail, { headless: false });
|
|
7210
|
+
if (!startRes.success || !startRes.sessionId) {
|
|
7211
|
+
console.log(
|
|
7212
|
+
`
|
|
7213
|
+
\u274C Failed to request code: ${startRes.error || "Unknown error"}`
|
|
7214
|
+
);
|
|
7215
|
+
await askQuestion("Press Enter to continue...");
|
|
7216
|
+
return;
|
|
7217
|
+
}
|
|
7218
|
+
console.log(`
|
|
7219
|
+
\u2709\uFE0F Verification code sent to ${trimmedEmail}!`);
|
|
7220
|
+
console.log("Check your email inbox (and spam folder).\n");
|
|
7221
|
+
while (true) {
|
|
7222
|
+
const code = await askQuestion("Enter 6-digit code (or 'C' to cancel): ");
|
|
7223
|
+
if (!code || !code.trim()) continue;
|
|
7224
|
+
if (code.trim().toUpperCase() === "C") {
|
|
7225
|
+
await cancelEmailOtpLogin2(startRes.sessionId);
|
|
7226
|
+
console.log("\nLogin cancelled.");
|
|
7227
|
+
await askQuestion("Press Enter to continue...");
|
|
7228
|
+
return;
|
|
7229
|
+
}
|
|
7230
|
+
console.log("\n\u23F3 Verifying code and establishing 30-day session...");
|
|
7231
|
+
const verifyRes = await verifyEmailOtpLogin2(startRes.sessionId, code.trim());
|
|
7232
|
+
if (verifyRes.success && verifyRes.account) {
|
|
7233
|
+
console.log(
|
|
7234
|
+
`
|
|
7235
|
+
\u2705 Account verified and saved: ${maskEmail(verifyRes.account.email)} (${verifyRes.account.id})`
|
|
7236
|
+
);
|
|
7237
|
+
console.log("Passwordless 30-day session active.");
|
|
7238
|
+
await askQuestion("\nPress Enter to continue...");
|
|
7239
|
+
return;
|
|
7240
|
+
} else {
|
|
7241
|
+
console.log(`
|
|
7242
|
+
\u274C ${verifyRes.error || "Invalid code"}`);
|
|
7243
|
+
console.log("Please try again or enter 'C' to cancel.\n");
|
|
7244
|
+
}
|
|
7245
|
+
}
|
|
7246
|
+
}
|
|
6828
7247
|
async function removeAccountFlow() {
|
|
6829
7248
|
const accounts = listAccounts();
|
|
6830
7249
|
if (accounts.length === 0) return;
|