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/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
|
}
|
|
@@ -3182,6 +3182,7 @@ var playwright_exports = {};
|
|
|
3182
3182
|
__export(playwright_exports, {
|
|
3183
3183
|
assertAntiBotHeaders: () => assertAntiBotHeaders,
|
|
3184
3184
|
buildChromiumLaunchArgs: () => buildChromiumLaunchArgs,
|
|
3185
|
+
cancelEmailOtpLogin: () => cancelEmailOtpLogin,
|
|
3185
3186
|
captureQwenHeaders: () => captureQwenHeaders,
|
|
3186
3187
|
classifyQwenAuthError: () => classifyQwenAuthError,
|
|
3187
3188
|
cleanupOrphanProfiles: () => cleanupOrphanProfiles,
|
|
@@ -3222,8 +3223,10 @@ __export(playwright_exports, {
|
|
|
3222
3223
|
removePlaywrightProfile: () => removePlaywrightProfile,
|
|
3223
3224
|
saveStorageState: () => saveStorageState,
|
|
3224
3225
|
schedulePlaywrightProfileReset: () => schedulePlaywrightProfileReset,
|
|
3226
|
+
startEmailOtpLogin: () => startEmailOtpLogin,
|
|
3225
3227
|
unregisterPlaywrightAccountForTests: () => unregisterPlaywrightAccountForTests,
|
|
3226
3228
|
validateAccountLogin: () => validateAccountLogin,
|
|
3229
|
+
verifyEmailOtpLogin: () => verifyEmailOtpLogin,
|
|
3227
3230
|
withAccountPage: () => withAccountPage
|
|
3228
3231
|
});
|
|
3229
3232
|
import { chromium as chromium2 } from "patchright";
|
|
@@ -3565,6 +3568,10 @@ async function clearVisibleChallenge(page) {
|
|
|
3565
3568
|
"button[aria-label*='Close' i]",
|
|
3566
3569
|
"button[aria-label*='Fechar' i]",
|
|
3567
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')",
|
|
3568
3575
|
"button:has-text('Cancel')",
|
|
3569
3576
|
"button:has-text('Cancelar')",
|
|
3570
3577
|
"button:has-text('I understand')",
|
|
@@ -4759,6 +4766,292 @@ async function validateAccountLogin(rawAccount, headless = true, browserType = "
|
|
|
4759
4766
|
release();
|
|
4760
4767
|
}
|
|
4761
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
|
+
}
|
|
4762
5055
|
function classifyQwenAuthError(code, details) {
|
|
4763
5056
|
const c = (code || "").trim().toLowerCase();
|
|
4764
5057
|
const d = (details || "").trim().toLowerCase();
|
|
@@ -5456,7 +5749,24 @@ async function refreshAccountToken(accountId) {
|
|
|
5456
5749
|
return { success: false, error: "Could not refresh or re-authenticate token" };
|
|
5457
5750
|
}
|
|
5458
5751
|
async function captureQwenHeaders(accountId, pageOverride, timeoutMs = config.timeouts.headers, triggerGraceMs = HEADER_CAPTURE_TRIGGER_GRACE_MS) {
|
|
5459
|
-
|
|
5752
|
+
let page = pageOverride ?? accountPages.get(accountId);
|
|
5753
|
+
if (!page || page.isClosed()) {
|
|
5754
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
5755
|
+
if (!isRunningUnderNodeTest3()) {
|
|
5756
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
5757
|
+
const creds = getAccountCredentials2(accountId);
|
|
5758
|
+
if (creds) {
|
|
5759
|
+
await initPlaywrightForAccount(
|
|
5760
|
+
creds,
|
|
5761
|
+
config.playwright.headless,
|
|
5762
|
+
config.playwright.browser,
|
|
5763
|
+
{ skipHeaderCapture: true }
|
|
5764
|
+
).catch(() => {
|
|
5765
|
+
});
|
|
5766
|
+
page = accountPages.get(accountId);
|
|
5767
|
+
}
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5460
5770
|
if (!page || page.isClosed()) {
|
|
5461
5771
|
throw new Error(`Playwright page unavailable for header capture: ${accountId}`);
|
|
5462
5772
|
}
|
|
@@ -6099,16 +6409,38 @@ async function refreshHeaders(accountId, timeoutMs = config.timeouts.headers, fo
|
|
|
6099
6409
|
release();
|
|
6100
6410
|
}
|
|
6101
6411
|
}
|
|
6102
|
-
async function
|
|
6412
|
+
async function ensureAccountPageAvailable(accountId) {
|
|
6103
6413
|
const inFlightInit = inFlightAccountInits.get(accountId);
|
|
6104
6414
|
if (inFlightInit) {
|
|
6105
6415
|
await inFlightInit.catch(() => {
|
|
6106
6416
|
});
|
|
6107
6417
|
}
|
|
6108
|
-
|
|
6418
|
+
let page = accountPages.get(accountId);
|
|
6419
|
+
if (page && !page.isClosed()) {
|
|
6420
|
+
return page;
|
|
6421
|
+
}
|
|
6422
|
+
const { isRunningUnderNodeTest: isRunningUnderNodeTest3 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
|
|
6423
|
+
if (isRunningUnderNodeTest3()) {
|
|
6424
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6425
|
+
}
|
|
6426
|
+
const { getAccountCredentials: getAccountCredentials2 } = await Promise.resolve().then(() => (init_accounts(), accounts_exports));
|
|
6427
|
+
const creds = getAccountCredentials2(accountId);
|
|
6428
|
+
if (!creds) {
|
|
6429
|
+
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6430
|
+
}
|
|
6431
|
+
await initPlaywrightForAccount(
|
|
6432
|
+
creds,
|
|
6433
|
+
config.playwright.headless,
|
|
6434
|
+
config.playwright.browser
|
|
6435
|
+
);
|
|
6436
|
+
page = accountPages.get(accountId);
|
|
6109
6437
|
if (!page || page.isClosed()) {
|
|
6110
6438
|
throw new Error(`Playwright page unavailable for account: ${accountId}`);
|
|
6111
6439
|
}
|
|
6440
|
+
return page;
|
|
6441
|
+
}
|
|
6442
|
+
async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION_TIMEOUT_MS, mutexTimeoutMs = PLAYWRIGHT_MUTEX_WAIT_MS, recoverOnTimeout = true) {
|
|
6443
|
+
const page = await ensureAccountPageAvailable(accountId);
|
|
6112
6444
|
const release = await acquireAccountMutex(
|
|
6113
6445
|
accountId,
|
|
6114
6446
|
`page:${accountId.substring(0, 12)}`,
|
|
@@ -6127,7 +6459,7 @@ async function withAccountPage(accountId, fn, timeoutMs = ACCOUNT_PAGE_OPERATION
|
|
|
6127
6459
|
return result;
|
|
6128
6460
|
} catch (error) {
|
|
6129
6461
|
const message = getErrorMessage(error);
|
|
6130
|
-
if (recoverOnTimeout && message.includes("Playwright page operation timed out")) {
|
|
6462
|
+
if (recoverOnTimeout && message.includes("Playwright page operation timed out") && (!page || page.isClosed())) {
|
|
6131
6463
|
console.warn(
|
|
6132
6464
|
`\u23F1\uFE0F [Playwright] Resetting account context after a stuck page operation: ${accountId}`
|
|
6133
6465
|
);
|
|
@@ -6409,8 +6741,10 @@ async function evictIdlePlaywrightContextsToLimit() {
|
|
|
6409
6741
|
if (accountPages.size <= max) return 0;
|
|
6410
6742
|
const candidates = priorityOrderForEviction(
|
|
6411
6743
|
Array.from(accountPages.keys()).filter((accountId) => {
|
|
6744
|
+
if (isAccountServingStream(accountId)) return false;
|
|
6745
|
+
if (isAccountRecentlyActive(accountId, 3e4)) return false;
|
|
6412
6746
|
const mutex = accountMutexes.get(accountId);
|
|
6413
|
-
return mutex?.isIdle()
|
|
6747
|
+
return mutex?.isIdle();
|
|
6414
6748
|
})
|
|
6415
6749
|
).map((accountId) => ({
|
|
6416
6750
|
accountId,
|
|
@@ -6676,7 +7010,7 @@ async function getTokenDiagnostics(accountId) {
|
|
|
6676
7010
|
}
|
|
6677
7011
|
};
|
|
6678
7012
|
}
|
|
6679
|
-
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;
|
|
7013
|
+
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;
|
|
6680
7014
|
var init_playwright = __esm({
|
|
6681
7015
|
"src/services/playwright.ts"() {
|
|
6682
7016
|
"use strict";
|
|
@@ -6721,11 +7055,13 @@ var init_playwright = __esm({
|
|
|
6721
7055
|
PLAYWRIGHT_MUTEX_WAIT_MS = 6e4;
|
|
6722
7056
|
ACCOUNT_PAGE_OPERATION_TIMEOUT_MS = config.timeouts.page;
|
|
6723
7057
|
SESSION_PROBE_NAVIGATION_TIMEOUT_MS = 15e3;
|
|
6724
|
-
HEADER_CAPTURE_TRIGGER_GRACE_MS =
|
|
6725
|
-
FIRST_TRIGGER_GRACE_MS =
|
|
7058
|
+
HEADER_CAPTURE_TRIGGER_GRACE_MS = 2e4;
|
|
7059
|
+
FIRST_TRIGGER_GRACE_MS = 12e3;
|
|
6726
7060
|
HEADER_CAPTURE_TRIGGER_ATTEMPTS = 3;
|
|
6727
7061
|
CHAT_INPUT_APPEAR_TIMEOUT_MS = 15e3;
|
|
6728
7062
|
CHAT_INPUT_ACTION_TIMEOUT_MS = 1e4;
|
|
7063
|
+
activeOtpSessions = /* @__PURE__ */ new Map();
|
|
7064
|
+
mockOtpSessions = /* @__PURE__ */ new Map();
|
|
6729
7065
|
PROFILE_RESET_TIMEOUT_MS = Math.max(9e4, config.timeouts.headers);
|
|
6730
7066
|
setWafContextResetListener((accountId) => {
|
|
6731
7067
|
if (!accountPages.has(accountId)) return;
|
|
@@ -6779,7 +7115,8 @@ async function showMenu() {
|
|
|
6779
7115
|
console.log("No accounts configured yet.\n");
|
|
6780
7116
|
}
|
|
6781
7117
|
console.log("\nOptions:");
|
|
6782
|
-
console.log(" [A] Add account");
|
|
7118
|
+
console.log(" [A] Add account (Password)");
|
|
7119
|
+
console.log(" [O] Add account (Passwordless / Email OTP)");
|
|
6783
7120
|
if (accounts.length > 0) {
|
|
6784
7121
|
console.log(" [R] Remove an account");
|
|
6785
7122
|
}
|
|
@@ -6790,7 +7127,21 @@ async function showMenu() {
|
|
|
6790
7127
|
process.exit(0);
|
|
6791
7128
|
}
|
|
6792
7129
|
if (choice === "A") {
|
|
6793
|
-
|
|
7130
|
+
clear();
|
|
7131
|
+
console.log("=== Add New Account ===\n");
|
|
7132
|
+
console.log("Authentication method:");
|
|
7133
|
+
console.log(" [1] Traditional Password (Email + Password)");
|
|
7134
|
+
console.log(" [2] Passwordless (Email Verification Code / OTP)\n");
|
|
7135
|
+
const method = (await askQuestion("Select method [1/2] (default 1): ")).trim();
|
|
7136
|
+
if (method === "2") {
|
|
7137
|
+
await addAccountOtpFlow();
|
|
7138
|
+
} else {
|
|
7139
|
+
await addAccountPasswordFlow();
|
|
7140
|
+
}
|
|
7141
|
+
continue;
|
|
7142
|
+
}
|
|
7143
|
+
if (choice === "O") {
|
|
7144
|
+
await addAccountOtpFlow();
|
|
6794
7145
|
continue;
|
|
6795
7146
|
}
|
|
6796
7147
|
if (choice === "R" && accounts.length > 0) {
|
|
@@ -6799,9 +7150,9 @@ async function showMenu() {
|
|
|
6799
7150
|
}
|
|
6800
7151
|
}
|
|
6801
7152
|
}
|
|
6802
|
-
async function
|
|
7153
|
+
async function addAccountPasswordFlow() {
|
|
6803
7154
|
clear();
|
|
6804
|
-
console.log("=== Add New Account ===\n");
|
|
7155
|
+
console.log("=== Add New Account (Password Login) ===\n");
|
|
6805
7156
|
const email = await askQuestion("Email: ");
|
|
6806
7157
|
if (!email || !email.trim()) {
|
|
6807
7158
|
console.log("Email is required.");
|
|
@@ -6870,6 +7221,70 @@ async function addAccountFlow() {
|
|
|
6870
7221
|
}
|
|
6871
7222
|
await askQuestion("\nPress Enter to continue...");
|
|
6872
7223
|
}
|
|
7224
|
+
async function addAccountOtpFlow() {
|
|
7225
|
+
clear();
|
|
7226
|
+
console.log("=== Add New Account (Passwordless / Email OTP) ===\n");
|
|
7227
|
+
const email = await askQuestion("Email: ");
|
|
7228
|
+
if (!email || !email.trim()) {
|
|
7229
|
+
console.log("Email is required.");
|
|
7230
|
+
await askQuestion("Press Enter to continue...");
|
|
7231
|
+
return;
|
|
7232
|
+
}
|
|
7233
|
+
const trimmedEmail = email.trim();
|
|
7234
|
+
const existingAccounts = listAccounts();
|
|
7235
|
+
if (existingAccounts.some(
|
|
7236
|
+
(a) => a.email.toLowerCase() === trimmedEmail.toLowerCase()
|
|
7237
|
+
)) {
|
|
7238
|
+
console.log(
|
|
7239
|
+
`
|
|
7240
|
+
\u274C Error: An account with email "${trimmedEmail}" already exists.`
|
|
7241
|
+
);
|
|
7242
|
+
await askQuestion("Press Enter to continue...");
|
|
7243
|
+
return;
|
|
7244
|
+
}
|
|
7245
|
+
console.log(
|
|
7246
|
+
`
|
|
7247
|
+
\u23F3 Requesting OTP verification code for ${maskEmail(trimmedEmail)}...`
|
|
7248
|
+
);
|
|
7249
|
+
const { startEmailOtpLogin: startEmailOtpLogin2, verifyEmailOtpLogin: verifyEmailOtpLogin2, cancelEmailOtpLogin: cancelEmailOtpLogin2 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
|
|
7250
|
+
const startRes = await startEmailOtpLogin2(trimmedEmail, { headless: false });
|
|
7251
|
+
if (!startRes.success || !startRes.sessionId) {
|
|
7252
|
+
console.log(
|
|
7253
|
+
`
|
|
7254
|
+
\u274C Failed to request code: ${startRes.error || "Unknown error"}`
|
|
7255
|
+
);
|
|
7256
|
+
await askQuestion("Press Enter to continue...");
|
|
7257
|
+
return;
|
|
7258
|
+
}
|
|
7259
|
+
console.log(`
|
|
7260
|
+
\u2709\uFE0F Verification code sent to ${trimmedEmail}!`);
|
|
7261
|
+
console.log("Check your email inbox (and spam folder).\n");
|
|
7262
|
+
while (true) {
|
|
7263
|
+
const code = await askQuestion("Enter 6-digit code (or 'C' to cancel): ");
|
|
7264
|
+
if (!code || !code.trim()) continue;
|
|
7265
|
+
if (code.trim().toUpperCase() === "C") {
|
|
7266
|
+
await cancelEmailOtpLogin2(startRes.sessionId);
|
|
7267
|
+
console.log("\nLogin cancelled.");
|
|
7268
|
+
await askQuestion("Press Enter to continue...");
|
|
7269
|
+
return;
|
|
7270
|
+
}
|
|
7271
|
+
console.log("\n\u23F3 Verifying code and establishing 30-day session...");
|
|
7272
|
+
const verifyRes = await verifyEmailOtpLogin2(startRes.sessionId, code.trim());
|
|
7273
|
+
if (verifyRes.success && verifyRes.account) {
|
|
7274
|
+
console.log(
|
|
7275
|
+
`
|
|
7276
|
+
\u2705 Account verified and saved: ${maskEmail(verifyRes.account.email)} (${verifyRes.account.id})`
|
|
7277
|
+
);
|
|
7278
|
+
console.log("Passwordless 30-day session active.");
|
|
7279
|
+
await askQuestion("\nPress Enter to continue...");
|
|
7280
|
+
return;
|
|
7281
|
+
} else {
|
|
7282
|
+
console.log(`
|
|
7283
|
+
\u274C ${verifyRes.error || "Invalid code"}`);
|
|
7284
|
+
console.log("Please try again or enter 'C' to cancel.\n");
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
6873
7288
|
async function removeAccountFlow() {
|
|
6874
7289
|
const accounts = listAccounts();
|
|
6875
7290
|
if (accounts.length === 0) return;
|