qwenproxy-cli 1.4.1 → 1.4.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 +67 -46
- package/dist/delete-chats.js +67 -46
- package/dist/index.js +68 -47
- package/dist/login.js +69 -47
- package/dist/reset-cooldowns.js +67 -46
- package/package.json +1 -1
- package/src/core/version.ts +1 -1
- package/src/login.ts +1 -1
- package/src/services/playwright.ts +83 -53
package/dist/clean-cache.js
CHANGED
|
@@ -3852,8 +3852,9 @@ async function loginToQwen(accountId, email, password) {
|
|
|
3852
3852
|
}
|
|
3853
3853
|
if (attempt < maxAttempts) {
|
|
3854
3854
|
const backoffMs = attempt * 5e3;
|
|
3855
|
+
const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
|
|
3855
3856
|
console.warn(
|
|
3856
|
-
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${
|
|
3857
|
+
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
|
|
3857
3858
|
);
|
|
3858
3859
|
await sleep2(backoffMs);
|
|
3859
3860
|
}
|
|
@@ -4035,10 +4036,16 @@ async function loginViaUi(page, email, password) {
|
|
|
4035
4036
|
);
|
|
4036
4037
|
return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
|
|
4037
4038
|
}
|
|
4039
|
+
await clearVisibleChallenge(page);
|
|
4040
|
+
const findPasswordModeButton = () => {
|
|
4041
|
+
return page.locator('button, [role="button"], a, div').filter({
|
|
4042
|
+
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
|
|
4043
|
+
}).first();
|
|
4044
|
+
};
|
|
4038
4045
|
try {
|
|
4039
|
-
const pwdModeBtn =
|
|
4040
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
4041
|
-
await pwdModeBtn.click().catch(() => {
|
|
4046
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
4047
|
+
if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
4048
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4042
4049
|
});
|
|
4043
4050
|
await sleep2(500);
|
|
4044
4051
|
}
|
|
@@ -4047,9 +4054,9 @@ async function loginViaUi(page, email, password) {
|
|
|
4047
4054
|
await page.fill(emailSelector, email);
|
|
4048
4055
|
await sleep2(300);
|
|
4049
4056
|
try {
|
|
4050
|
-
const pwdModeBtn =
|
|
4051
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
4052
|
-
await pwdModeBtn.click().catch(() => {
|
|
4057
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
4058
|
+
if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
4059
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4053
4060
|
});
|
|
4054
4061
|
await sleep2(500);
|
|
4055
4062
|
}
|
|
@@ -4074,7 +4081,7 @@ async function loginViaUi(page, email, password) {
|
|
|
4074
4081
|
}
|
|
4075
4082
|
await page.fill(passwordSelector, password);
|
|
4076
4083
|
await sleep2(500);
|
|
4077
|
-
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in")';
|
|
4084
|
+
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
4078
4085
|
const submitButton = page.locator(submitSelector).first();
|
|
4079
4086
|
try {
|
|
4080
4087
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
@@ -4084,49 +4091,63 @@ async function loginViaUi(page, email, password) {
|
|
|
4084
4091
|
} catch {
|
|
4085
4092
|
await page.keyboard.press("Enter");
|
|
4086
4093
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
|
|
4093
|
-
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4094
|
+
const postSubmitDeadline = Date.now() + 15e3;
|
|
4095
|
+
while (Date.now() < postSubmitDeadline) {
|
|
4096
|
+
if (page.isClosed()) break;
|
|
4097
|
+
if (config.captcha.enabled) {
|
|
4098
|
+
await solveBaxiaCaptcha(page, {
|
|
4099
|
+
waitForMs: 1500,
|
|
4100
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
4101
|
+
retryDelayMs: config.captcha.retryDelayMs
|
|
4102
|
+
}).catch(() => {
|
|
4103
|
+
});
|
|
4104
|
+
}
|
|
4105
|
+
const errorSelector = [
|
|
4106
|
+
".qwen-chat-v2-toast-text",
|
|
4107
|
+
".qwen-chat-v2-toast-content",
|
|
4108
|
+
".ant-form-item-explain-error",
|
|
4109
|
+
".ant-message-error",
|
|
4110
|
+
".ant-message-notice",
|
|
4111
|
+
'[role="alert"]',
|
|
4112
|
+
".qwenchat-auth-error",
|
|
4113
|
+
".auth-error-message"
|
|
4114
|
+
].join(", ");
|
|
4115
|
+
const errorEl = page.locator(errorSelector).first();
|
|
4116
|
+
if (await errorEl.isVisible().catch(() => false)) {
|
|
4117
|
+
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
4118
|
+
if (errorText) {
|
|
4119
|
+
const classified = classifyQwenAuthError(void 0, errorText);
|
|
4120
|
+
return {
|
|
4121
|
+
success: false,
|
|
4122
|
+
permanentFailure: classified.isPermanent,
|
|
4123
|
+
reason: `Formul\xE1rio: ${classified.reason}`
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
|
|
4128
|
+
const codeInput = page.locator(codeSelector).first();
|
|
4129
|
+
if (await codeInput.isVisible().catch(() => false)) {
|
|
4117
4130
|
return {
|
|
4118
4131
|
success: false,
|
|
4119
|
-
|
|
4120
|
-
reason: `Formul\xE1rio: ${classified.reason}`
|
|
4132
|
+
reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
|
|
4121
4133
|
};
|
|
4122
4134
|
}
|
|
4135
|
+
try {
|
|
4136
|
+
const liveCookies = await page.context().cookies();
|
|
4137
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
4138
|
+
return { success: true };
|
|
4139
|
+
}
|
|
4140
|
+
} catch {
|
|
4141
|
+
}
|
|
4142
|
+
const currentUrl = page.url();
|
|
4143
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
4144
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
4145
|
+
return { success: true };
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
await sleep2(1e3);
|
|
4123
4149
|
}
|
|
4124
|
-
|
|
4125
|
-
if (isLoggedIn) {
|
|
4126
|
-
await page.goto(qwenUrl("/"), {
|
|
4127
|
-
waitUntil: "domcontentloaded",
|
|
4128
|
-
timeout: config.timeouts.navigation
|
|
4129
|
-
});
|
|
4150
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
4130
4151
|
return { success: true };
|
|
4131
4152
|
}
|
|
4132
4153
|
return {
|
package/dist/delete-chats.js
CHANGED
|
@@ -4727,8 +4727,9 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4727
4727
|
}
|
|
4728
4728
|
if (attempt < maxAttempts) {
|
|
4729
4729
|
const backoffMs = attempt * 5e3;
|
|
4730
|
+
const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
|
|
4730
4731
|
console.warn(
|
|
4731
|
-
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${
|
|
4732
|
+
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
|
|
4732
4733
|
);
|
|
4733
4734
|
await sleep2(backoffMs);
|
|
4734
4735
|
}
|
|
@@ -4910,10 +4911,16 @@ async function loginViaUi(page, email, password) {
|
|
|
4910
4911
|
);
|
|
4911
4912
|
return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
|
|
4912
4913
|
}
|
|
4914
|
+
await clearVisibleChallenge(page);
|
|
4915
|
+
const findPasswordModeButton = () => {
|
|
4916
|
+
return page.locator('button, [role="button"], a, div').filter({
|
|
4917
|
+
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
|
|
4918
|
+
}).first();
|
|
4919
|
+
};
|
|
4913
4920
|
try {
|
|
4914
|
-
const pwdModeBtn =
|
|
4915
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
4916
|
-
await pwdModeBtn.click().catch(() => {
|
|
4921
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
4922
|
+
if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
4923
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4917
4924
|
});
|
|
4918
4925
|
await sleep2(500);
|
|
4919
4926
|
}
|
|
@@ -4922,9 +4929,9 @@ async function loginViaUi(page, email, password) {
|
|
|
4922
4929
|
await page.fill(emailSelector, email);
|
|
4923
4930
|
await sleep2(300);
|
|
4924
4931
|
try {
|
|
4925
|
-
const pwdModeBtn =
|
|
4926
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
4927
|
-
await pwdModeBtn.click().catch(() => {
|
|
4932
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
4933
|
+
if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
4934
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4928
4935
|
});
|
|
4929
4936
|
await sleep2(500);
|
|
4930
4937
|
}
|
|
@@ -4949,7 +4956,7 @@ async function loginViaUi(page, email, password) {
|
|
|
4949
4956
|
}
|
|
4950
4957
|
await page.fill(passwordSelector, password);
|
|
4951
4958
|
await sleep2(500);
|
|
4952
|
-
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in")';
|
|
4959
|
+
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
4953
4960
|
const submitButton = page.locator(submitSelector).first();
|
|
4954
4961
|
try {
|
|
4955
4962
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
@@ -4959,49 +4966,63 @@ async function loginViaUi(page, email, password) {
|
|
|
4959
4966
|
} catch {
|
|
4960
4967
|
await page.keyboard.press("Enter");
|
|
4961
4968
|
}
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4969
|
+
const postSubmitDeadline = Date.now() + 15e3;
|
|
4970
|
+
while (Date.now() < postSubmitDeadline) {
|
|
4971
|
+
if (page.isClosed()) break;
|
|
4972
|
+
if (config.captcha.enabled) {
|
|
4973
|
+
await solveBaxiaCaptcha(page, {
|
|
4974
|
+
waitForMs: 1500,
|
|
4975
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
4976
|
+
retryDelayMs: config.captcha.retryDelayMs
|
|
4977
|
+
}).catch(() => {
|
|
4978
|
+
});
|
|
4979
|
+
}
|
|
4980
|
+
const errorSelector = [
|
|
4981
|
+
".qwen-chat-v2-toast-text",
|
|
4982
|
+
".qwen-chat-v2-toast-content",
|
|
4983
|
+
".ant-form-item-explain-error",
|
|
4984
|
+
".ant-message-error",
|
|
4985
|
+
".ant-message-notice",
|
|
4986
|
+
'[role="alert"]',
|
|
4987
|
+
".qwenchat-auth-error",
|
|
4988
|
+
".auth-error-message"
|
|
4989
|
+
].join(", ");
|
|
4990
|
+
const errorEl = page.locator(errorSelector).first();
|
|
4991
|
+
if (await errorEl.isVisible().catch(() => false)) {
|
|
4992
|
+
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
4993
|
+
if (errorText) {
|
|
4994
|
+
const classified = classifyQwenAuthError(void 0, errorText);
|
|
4995
|
+
return {
|
|
4996
|
+
success: false,
|
|
4997
|
+
permanentFailure: classified.isPermanent,
|
|
4998
|
+
reason: `Formul\xE1rio: ${classified.reason}`
|
|
4999
|
+
};
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
|
|
5003
|
+
const codeInput = page.locator(codeSelector).first();
|
|
5004
|
+
if (await codeInput.isVisible().catch(() => false)) {
|
|
4992
5005
|
return {
|
|
4993
5006
|
success: false,
|
|
4994
|
-
|
|
4995
|
-
reason: `Formul\xE1rio: ${classified.reason}`
|
|
5007
|
+
reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
|
|
4996
5008
|
};
|
|
4997
5009
|
}
|
|
5010
|
+
try {
|
|
5011
|
+
const liveCookies = await page.context().cookies();
|
|
5012
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
5013
|
+
return { success: true };
|
|
5014
|
+
}
|
|
5015
|
+
} catch {
|
|
5016
|
+
}
|
|
5017
|
+
const currentUrl = page.url();
|
|
5018
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
5019
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
5020
|
+
return { success: true };
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
5023
|
+
await sleep2(1e3);
|
|
4998
5024
|
}
|
|
4999
|
-
|
|
5000
|
-
if (isLoggedIn) {
|
|
5001
|
-
await page.goto(qwenUrl("/"), {
|
|
5002
|
-
waitUntil: "domcontentloaded",
|
|
5003
|
-
timeout: config.timeouts.navigation
|
|
5004
|
-
});
|
|
5025
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
5005
5026
|
return { success: true };
|
|
5006
5027
|
}
|
|
5007
5028
|
return {
|
package/dist/index.js
CHANGED
|
@@ -5944,8 +5944,9 @@ async function loginToQwen(accountId, email, password) {
|
|
|
5944
5944
|
}
|
|
5945
5945
|
if (attempt < maxAttempts) {
|
|
5946
5946
|
const backoffMs = attempt * 5e3;
|
|
5947
|
+
const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
|
|
5947
5948
|
console.warn(
|
|
5948
|
-
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${
|
|
5949
|
+
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
|
|
5949
5950
|
);
|
|
5950
5951
|
await sleep2(backoffMs);
|
|
5951
5952
|
}
|
|
@@ -6127,10 +6128,16 @@ async function loginViaUi(page, email, password) {
|
|
|
6127
6128
|
);
|
|
6128
6129
|
return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
|
|
6129
6130
|
}
|
|
6131
|
+
await clearVisibleChallenge(page);
|
|
6132
|
+
const findPasswordModeButton = () => {
|
|
6133
|
+
return page.locator('button, [role="button"], a, div').filter({
|
|
6134
|
+
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
|
|
6135
|
+
}).first();
|
|
6136
|
+
};
|
|
6130
6137
|
try {
|
|
6131
|
-
const pwdModeBtn =
|
|
6132
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
6133
|
-
await pwdModeBtn.click().catch(() => {
|
|
6138
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
6139
|
+
if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
6140
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
6134
6141
|
});
|
|
6135
6142
|
await sleep2(500);
|
|
6136
6143
|
}
|
|
@@ -6139,9 +6146,9 @@ async function loginViaUi(page, email, password) {
|
|
|
6139
6146
|
await page.fill(emailSelector, email);
|
|
6140
6147
|
await sleep2(300);
|
|
6141
6148
|
try {
|
|
6142
|
-
const pwdModeBtn =
|
|
6143
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
6144
|
-
await pwdModeBtn.click().catch(() => {
|
|
6149
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
6150
|
+
if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
6151
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
6145
6152
|
});
|
|
6146
6153
|
await sleep2(500);
|
|
6147
6154
|
}
|
|
@@ -6166,7 +6173,7 @@ async function loginViaUi(page, email, password) {
|
|
|
6166
6173
|
}
|
|
6167
6174
|
await page.fill(passwordSelector, password);
|
|
6168
6175
|
await sleep2(500);
|
|
6169
|
-
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in")';
|
|
6176
|
+
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
6170
6177
|
const submitButton = page.locator(submitSelector).first();
|
|
6171
6178
|
try {
|
|
6172
6179
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
@@ -6176,49 +6183,63 @@ async function loginViaUi(page, email, password) {
|
|
|
6176
6183
|
} catch {
|
|
6177
6184
|
await page.keyboard.press("Enter");
|
|
6178
6185
|
}
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6186
|
+
const postSubmitDeadline = Date.now() + 15e3;
|
|
6187
|
+
while (Date.now() < postSubmitDeadline) {
|
|
6188
|
+
if (page.isClosed()) break;
|
|
6189
|
+
if (config.captcha.enabled) {
|
|
6190
|
+
await solveBaxiaCaptcha(page, {
|
|
6191
|
+
waitForMs: 1500,
|
|
6192
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
6193
|
+
retryDelayMs: config.captcha.retryDelayMs
|
|
6194
|
+
}).catch(() => {
|
|
6195
|
+
});
|
|
6196
|
+
}
|
|
6197
|
+
const errorSelector = [
|
|
6198
|
+
".qwen-chat-v2-toast-text",
|
|
6199
|
+
".qwen-chat-v2-toast-content",
|
|
6200
|
+
".ant-form-item-explain-error",
|
|
6201
|
+
".ant-message-error",
|
|
6202
|
+
".ant-message-notice",
|
|
6203
|
+
'[role="alert"]',
|
|
6204
|
+
".qwenchat-auth-error",
|
|
6205
|
+
".auth-error-message"
|
|
6206
|
+
].join(", ");
|
|
6207
|
+
const errorEl = page.locator(errorSelector).first();
|
|
6208
|
+
if (await errorEl.isVisible().catch(() => false)) {
|
|
6209
|
+
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
6210
|
+
if (errorText) {
|
|
6211
|
+
const classified = classifyQwenAuthError(void 0, errorText);
|
|
6212
|
+
return {
|
|
6213
|
+
success: false,
|
|
6214
|
+
permanentFailure: classified.isPermanent,
|
|
6215
|
+
reason: `Formul\xE1rio: ${classified.reason}`
|
|
6216
|
+
};
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
6219
|
+
const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
|
|
6220
|
+
const codeInput = page.locator(codeSelector).first();
|
|
6221
|
+
if (await codeInput.isVisible().catch(() => false)) {
|
|
6209
6222
|
return {
|
|
6210
6223
|
success: false,
|
|
6211
|
-
|
|
6212
|
-
reason: `Formul\xE1rio: ${classified.reason}`
|
|
6224
|
+
reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
|
|
6213
6225
|
};
|
|
6214
6226
|
}
|
|
6227
|
+
try {
|
|
6228
|
+
const liveCookies = await page.context().cookies();
|
|
6229
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
6230
|
+
return { success: true };
|
|
6231
|
+
}
|
|
6232
|
+
} catch {
|
|
6233
|
+
}
|
|
6234
|
+
const currentUrl = page.url();
|
|
6235
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
6236
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
6237
|
+
return { success: true };
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
await sleep2(1e3);
|
|
6215
6241
|
}
|
|
6216
|
-
|
|
6217
|
-
if (isLoggedIn) {
|
|
6218
|
-
await page.goto(qwenUrl("/"), {
|
|
6219
|
-
waitUntil: "domcontentloaded",
|
|
6220
|
-
timeout: config.timeouts.navigation
|
|
6221
|
-
});
|
|
6242
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
6222
6243
|
return { success: true };
|
|
6223
6244
|
}
|
|
6224
6245
|
return {
|
|
@@ -25492,7 +25513,7 @@ function getAppVersion() {
|
|
|
25492
25513
|
}
|
|
25493
25514
|
} catch {
|
|
25494
25515
|
}
|
|
25495
|
-
cachedVersion = "v1.4.
|
|
25516
|
+
cachedVersion = "v1.4.3";
|
|
25496
25517
|
return cachedVersion;
|
|
25497
25518
|
}
|
|
25498
25519
|
var cachedVersion;
|
package/dist/login.js
CHANGED
|
@@ -4812,8 +4812,9 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4812
4812
|
}
|
|
4813
4813
|
if (attempt < maxAttempts) {
|
|
4814
4814
|
const backoffMs = attempt * 5e3;
|
|
4815
|
+
const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
|
|
4815
4816
|
console.warn(
|
|
4816
|
-
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${
|
|
4817
|
+
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
|
|
4817
4818
|
);
|
|
4818
4819
|
await sleep2(backoffMs);
|
|
4819
4820
|
}
|
|
@@ -4995,10 +4996,16 @@ async function loginViaUi(page, email, password) {
|
|
|
4995
4996
|
);
|
|
4996
4997
|
return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
|
|
4997
4998
|
}
|
|
4999
|
+
await clearVisibleChallenge(page);
|
|
5000
|
+
const findPasswordModeButton = () => {
|
|
5001
|
+
return page.locator('button, [role="button"], a, div').filter({
|
|
5002
|
+
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
|
|
5003
|
+
}).first();
|
|
5004
|
+
};
|
|
4998
5005
|
try {
|
|
4999
|
-
const pwdModeBtn =
|
|
5000
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
5001
|
-
await pwdModeBtn.click().catch(() => {
|
|
5006
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
5007
|
+
if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
5008
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
5002
5009
|
});
|
|
5003
5010
|
await sleep2(500);
|
|
5004
5011
|
}
|
|
@@ -5007,9 +5014,9 @@ async function loginViaUi(page, email, password) {
|
|
|
5007
5014
|
await page.fill(emailSelector, email);
|
|
5008
5015
|
await sleep2(300);
|
|
5009
5016
|
try {
|
|
5010
|
-
const pwdModeBtn =
|
|
5011
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
5012
|
-
await pwdModeBtn.click().catch(() => {
|
|
5017
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
5018
|
+
if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
5019
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
5013
5020
|
});
|
|
5014
5021
|
await sleep2(500);
|
|
5015
5022
|
}
|
|
@@ -5034,7 +5041,7 @@ async function loginViaUi(page, email, password) {
|
|
|
5034
5041
|
}
|
|
5035
5042
|
await page.fill(passwordSelector, password);
|
|
5036
5043
|
await sleep2(500);
|
|
5037
|
-
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in")';
|
|
5044
|
+
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
5038
5045
|
const submitButton = page.locator(submitSelector).first();
|
|
5039
5046
|
try {
|
|
5040
5047
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
@@ -5044,49 +5051,63 @@ async function loginViaUi(page, email, password) {
|
|
|
5044
5051
|
} catch {
|
|
5045
5052
|
await page.keyboard.press("Enter");
|
|
5046
5053
|
}
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5054
|
+
const postSubmitDeadline = Date.now() + 15e3;
|
|
5055
|
+
while (Date.now() < postSubmitDeadline) {
|
|
5056
|
+
if (page.isClosed()) break;
|
|
5057
|
+
if (config.captcha.enabled) {
|
|
5058
|
+
await solveBaxiaCaptcha(page, {
|
|
5059
|
+
waitForMs: 1500,
|
|
5060
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
5061
|
+
retryDelayMs: config.captcha.retryDelayMs
|
|
5062
|
+
}).catch(() => {
|
|
5063
|
+
});
|
|
5064
|
+
}
|
|
5065
|
+
const errorSelector = [
|
|
5066
|
+
".qwen-chat-v2-toast-text",
|
|
5067
|
+
".qwen-chat-v2-toast-content",
|
|
5068
|
+
".ant-form-item-explain-error",
|
|
5069
|
+
".ant-message-error",
|
|
5070
|
+
".ant-message-notice",
|
|
5071
|
+
'[role="alert"]',
|
|
5072
|
+
".qwenchat-auth-error",
|
|
5073
|
+
".auth-error-message"
|
|
5074
|
+
].join(", ");
|
|
5075
|
+
const errorEl = page.locator(errorSelector).first();
|
|
5076
|
+
if (await errorEl.isVisible().catch(() => false)) {
|
|
5077
|
+
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
5078
|
+
if (errorText) {
|
|
5079
|
+
const classified = classifyQwenAuthError(void 0, errorText);
|
|
5080
|
+
return {
|
|
5081
|
+
success: false,
|
|
5082
|
+
permanentFailure: classified.isPermanent,
|
|
5083
|
+
reason: `Formul\xE1rio: ${classified.reason}`
|
|
5084
|
+
};
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
|
|
5088
|
+
const codeInput = page.locator(codeSelector).first();
|
|
5089
|
+
if (await codeInput.isVisible().catch(() => false)) {
|
|
5077
5090
|
return {
|
|
5078
5091
|
success: false,
|
|
5079
|
-
|
|
5080
|
-
reason: `Formul\xE1rio: ${classified.reason}`
|
|
5092
|
+
reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
|
|
5081
5093
|
};
|
|
5082
5094
|
}
|
|
5095
|
+
try {
|
|
5096
|
+
const liveCookies = await page.context().cookies();
|
|
5097
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
5098
|
+
return { success: true };
|
|
5099
|
+
}
|
|
5100
|
+
} catch {
|
|
5101
|
+
}
|
|
5102
|
+
const currentUrl = page.url();
|
|
5103
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
5104
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
5105
|
+
return { success: true };
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
await sleep2(1e3);
|
|
5083
5109
|
}
|
|
5084
|
-
|
|
5085
|
-
if (isLoggedIn) {
|
|
5086
|
-
await page.goto(qwenUrl("/"), {
|
|
5087
|
-
waitUntil: "domcontentloaded",
|
|
5088
|
-
timeout: config.timeouts.navigation
|
|
5089
|
-
});
|
|
5110
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
5090
5111
|
return { success: true };
|
|
5091
5112
|
}
|
|
5092
5113
|
return {
|
|
@@ -6440,7 +6461,8 @@ async function addAccountFlow() {
|
|
|
6440
6461
|
const { config: config3 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
6441
6462
|
const ok = await validateAccountLogin2(
|
|
6442
6463
|
{ id: tempId, email: trimmedEmail, password: trimmedPassword },
|
|
6443
|
-
|
|
6464
|
+
false,
|
|
6465
|
+
// Visible browser so user can see and complete challenges if needed
|
|
6444
6466
|
config3.playwright.browser
|
|
6445
6467
|
);
|
|
6446
6468
|
if (ok) {
|
package/dist/reset-cooldowns.js
CHANGED
|
@@ -4088,8 +4088,9 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4088
4088
|
}
|
|
4089
4089
|
if (attempt < maxAttempts) {
|
|
4090
4090
|
const backoffMs = attempt * 5e3;
|
|
4091
|
+
const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
|
|
4091
4092
|
console.warn(
|
|
4092
|
-
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${
|
|
4093
|
+
`\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
|
|
4093
4094
|
);
|
|
4094
4095
|
await sleep2(backoffMs);
|
|
4095
4096
|
}
|
|
@@ -4271,10 +4272,16 @@ async function loginViaUi(page, email, password) {
|
|
|
4271
4272
|
);
|
|
4272
4273
|
return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
|
|
4273
4274
|
}
|
|
4275
|
+
await clearVisibleChallenge(page);
|
|
4276
|
+
const findPasswordModeButton = () => {
|
|
4277
|
+
return page.locator('button, [role="button"], a, div').filter({
|
|
4278
|
+
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i
|
|
4279
|
+
}).first();
|
|
4280
|
+
};
|
|
4274
4281
|
try {
|
|
4275
|
-
const pwdModeBtn =
|
|
4276
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
4277
|
-
await pwdModeBtn.click().catch(() => {
|
|
4282
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
4283
|
+
if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
|
|
4284
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4278
4285
|
});
|
|
4279
4286
|
await sleep2(500);
|
|
4280
4287
|
}
|
|
@@ -4283,9 +4290,9 @@ async function loginViaUi(page, email, password) {
|
|
|
4283
4290
|
await page.fill(emailSelector, email);
|
|
4284
4291
|
await sleep2(300);
|
|
4285
4292
|
try {
|
|
4286
|
-
const pwdModeBtn =
|
|
4287
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
4288
|
-
await pwdModeBtn.click().catch(() => {
|
|
4293
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
4294
|
+
if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
4295
|
+
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4289
4296
|
});
|
|
4290
4297
|
await sleep2(500);
|
|
4291
4298
|
}
|
|
@@ -4310,7 +4317,7 @@ async function loginViaUi(page, email, password) {
|
|
|
4310
4317
|
}
|
|
4311
4318
|
await page.fill(passwordSelector, password);
|
|
4312
4319
|
await sleep2(500);
|
|
4313
|
-
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in")';
|
|
4320
|
+
const submitSelector = 'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesi\xF3n"), button:has-text("\u767B\u5F55")';
|
|
4314
4321
|
const submitButton = page.locator(submitSelector).first();
|
|
4315
4322
|
try {
|
|
4316
4323
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
@@ -4320,49 +4327,63 @@ async function loginViaUi(page, email, password) {
|
|
|
4320
4327
|
} catch {
|
|
4321
4328
|
await page.keyboard.press("Enter");
|
|
4322
4329
|
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4330
|
+
const postSubmitDeadline = Date.now() + 15e3;
|
|
4331
|
+
while (Date.now() < postSubmitDeadline) {
|
|
4332
|
+
if (page.isClosed()) break;
|
|
4333
|
+
if (config.captcha.enabled) {
|
|
4334
|
+
await solveBaxiaCaptcha(page, {
|
|
4335
|
+
waitForMs: 1500,
|
|
4336
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
4337
|
+
retryDelayMs: config.captcha.retryDelayMs
|
|
4338
|
+
}).catch(() => {
|
|
4339
|
+
});
|
|
4340
|
+
}
|
|
4341
|
+
const errorSelector = [
|
|
4342
|
+
".qwen-chat-v2-toast-text",
|
|
4343
|
+
".qwen-chat-v2-toast-content",
|
|
4344
|
+
".ant-form-item-explain-error",
|
|
4345
|
+
".ant-message-error",
|
|
4346
|
+
".ant-message-notice",
|
|
4347
|
+
'[role="alert"]',
|
|
4348
|
+
".qwenchat-auth-error",
|
|
4349
|
+
".auth-error-message"
|
|
4350
|
+
].join(", ");
|
|
4351
|
+
const errorEl = page.locator(errorSelector).first();
|
|
4352
|
+
if (await errorEl.isVisible().catch(() => false)) {
|
|
4353
|
+
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
4354
|
+
if (errorText) {
|
|
4355
|
+
const classified = classifyQwenAuthError(void 0, errorText);
|
|
4356
|
+
return {
|
|
4357
|
+
success: false,
|
|
4358
|
+
permanentFailure: classified.isPermanent,
|
|
4359
|
+
reason: `Formul\xE1rio: ${classified.reason}`
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
}
|
|
4363
|
+
const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
|
|
4364
|
+
const codeInput = page.locator(codeSelector).first();
|
|
4365
|
+
if (await codeInput.isVisible().catch(() => false)) {
|
|
4353
4366
|
return {
|
|
4354
4367
|
success: false,
|
|
4355
|
-
|
|
4356
|
-
reason: `Formul\xE1rio: ${classified.reason}`
|
|
4368
|
+
reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
|
|
4357
4369
|
};
|
|
4358
4370
|
}
|
|
4371
|
+
try {
|
|
4372
|
+
const liveCookies = await page.context().cookies();
|
|
4373
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
4374
|
+
return { success: true };
|
|
4375
|
+
}
|
|
4376
|
+
} catch {
|
|
4377
|
+
}
|
|
4378
|
+
const currentUrl = page.url();
|
|
4379
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
4380
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
4381
|
+
return { success: true };
|
|
4382
|
+
}
|
|
4383
|
+
}
|
|
4384
|
+
await sleep2(1e3);
|
|
4359
4385
|
}
|
|
4360
|
-
|
|
4361
|
-
if (isLoggedIn) {
|
|
4362
|
-
await page.goto(qwenUrl("/"), {
|
|
4363
|
-
waitUntil: "domcontentloaded",
|
|
4364
|
-
timeout: config.timeouts.navigation
|
|
4365
|
-
});
|
|
4386
|
+
if (await isPageLoggedIn(page, 3e3)) {
|
|
4366
4387
|
return { success: true };
|
|
4367
4388
|
}
|
|
4368
4389
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.3",
|
|
4
4
|
"description": "High-performance OpenAI & Anthropic compatible API gateway for Qwen with multi-account rotation, interactive TUI, and resilient tool calling.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"bin": {
|
package/src/core/version.ts
CHANGED
package/src/login.ts
CHANGED
|
@@ -120,7 +120,7 @@ async function addAccountFlow() {
|
|
|
120
120
|
|
|
121
121
|
const ok = await validateAccountLogin(
|
|
122
122
|
{ id: tempId, email: trimmedEmail, password: trimmedPassword },
|
|
123
|
-
|
|
123
|
+
false, // Visible browser so user can see and complete challenges if needed
|
|
124
124
|
config.playwright.browser,
|
|
125
125
|
);
|
|
126
126
|
|
|
@@ -2163,8 +2163,9 @@ async function loginToQwen(
|
|
|
2163
2163
|
|
|
2164
2164
|
if (attempt < maxAttempts) {
|
|
2165
2165
|
const backoffMs = attempt * 5_000;
|
|
2166
|
+
const failReason = uiResult.reason || apiResult.reason || "falha temporária";
|
|
2166
2167
|
console.warn(
|
|
2167
|
-
`⚠️ [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${
|
|
2168
|
+
`⚠️ [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1000}s`,
|
|
2168
2169
|
);
|
|
2169
2170
|
await sleep(backoffMs);
|
|
2170
2171
|
}
|
|
@@ -2375,11 +2376,22 @@ async function loginViaUi(
|
|
|
2375
2376
|
}
|
|
2376
2377
|
|
|
2377
2378
|
// In Qwen Web, if the page opens on the default email OTP panel, click "Log in with a password"
|
|
2378
|
-
// to reveal the standard email + password form.
|
|
2379
|
+
// (supporting English, Portuguese, Spanish, Chinese) to reveal the standard email + password form.
|
|
2380
|
+
await clearVisibleChallenge(page);
|
|
2381
|
+
|
|
2382
|
+
const findPasswordModeButton = () => {
|
|
2383
|
+
return page
|
|
2384
|
+
.locator('button, [role="button"], a, div')
|
|
2385
|
+
.filter({
|
|
2386
|
+
hasText: /(?:log\s*in\s*with\s*(?:a\s*)?password|fazer\s*login\s*com\s*senha|entrar\s*com\s*(?:uma\s*)?senha|iniciar\s*sesi[oó]n\s*con\s*contrase[nñ]a|密码登录)/i,
|
|
2387
|
+
})
|
|
2388
|
+
.first();
|
|
2389
|
+
};
|
|
2390
|
+
|
|
2379
2391
|
try {
|
|
2380
|
-
const pwdModeBtn =
|
|
2381
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
2382
|
-
await pwdModeBtn.click().catch(() => {});
|
|
2392
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
2393
|
+
if (await pwdModeBtn.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
2394
|
+
await pwdModeBtn.click({ timeout: 2000 }).catch(() => {});
|
|
2383
2395
|
await sleep(500);
|
|
2384
2396
|
}
|
|
2385
2397
|
} catch {}
|
|
@@ -2390,9 +2402,9 @@ async function loginViaUi(
|
|
|
2390
2402
|
|
|
2391
2403
|
// If "Log in with a password" button appeared after typing email, click it
|
|
2392
2404
|
try {
|
|
2393
|
-
const pwdModeBtn =
|
|
2394
|
-
if (await pwdModeBtn.isVisible({ timeout:
|
|
2395
|
-
await pwdModeBtn.click().catch(() => {});
|
|
2405
|
+
const pwdModeBtn = findPasswordModeButton();
|
|
2406
|
+
if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
2407
|
+
await pwdModeBtn.click({ timeout: 2000 }).catch(() => {});
|
|
2396
2408
|
await sleep(500);
|
|
2397
2409
|
}
|
|
2398
2410
|
} catch {}
|
|
@@ -2426,7 +2438,7 @@ async function loginViaUi(
|
|
|
2426
2438
|
// Prefer clicking the submit button; fall back to pressing Enter.
|
|
2427
2439
|
// The button starts disabled and only enables once both fields are filled.
|
|
2428
2440
|
const submitSelector =
|
|
2429
|
-
'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in")';
|
|
2441
|
+
'button.qwenchat-auth-pc-submit-button, button[type="submit"], button:has-text("Log in"), button:has-text("Sign in"), button:has-text("Fazer login"), button:has-text("Entrar"), button:has-text("Iniciar sesión"), button:has-text("登录")';
|
|
2430
2442
|
const submitButton = page.locator(submitSelector).first();
|
|
2431
2443
|
try {
|
|
2432
2444
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
@@ -2436,58 +2448,76 @@ async function loginViaUi(
|
|
|
2436
2448
|
} catch {
|
|
2437
2449
|
await page.keyboard.press("Enter");
|
|
2438
2450
|
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2451
|
+
// Post-submit verification loop: wait up to 15s for captcha, redirect, cookies, or errors
|
|
2452
|
+
const postSubmitDeadline = Date.now() + 15_000;
|
|
2453
|
+
while (Date.now() < postSubmitDeadline) {
|
|
2454
|
+
if (page.isClosed()) break;
|
|
2455
|
+
|
|
2456
|
+
// If a slider puzzle captcha appeared after submit, solve it automatically
|
|
2457
|
+
if (config.captcha.enabled) {
|
|
2458
|
+
await solveBaxiaCaptcha(page, {
|
|
2459
|
+
waitForMs: 1500,
|
|
2460
|
+
maxAttempts: config.captcha.maxAttempts,
|
|
2461
|
+
retryDelayMs: config.captcha.retryDelayMs,
|
|
2462
|
+
}).catch(() => {});
|
|
2463
|
+
}
|
|
2447
2464
|
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2465
|
+
// Check for UI error elements in DOM (Ant Design errors, alerts, toasts)
|
|
2466
|
+
const errorSelector = [
|
|
2467
|
+
".qwen-chat-v2-toast-text",
|
|
2468
|
+
".qwen-chat-v2-toast-content",
|
|
2469
|
+
".ant-form-item-explain-error",
|
|
2470
|
+
".ant-message-error",
|
|
2471
|
+
".ant-message-notice",
|
|
2472
|
+
'[role="alert"]',
|
|
2473
|
+
".qwenchat-auth-error",
|
|
2474
|
+
".auth-error-message",
|
|
2475
|
+
].join(", ");
|
|
2476
|
+
const errorEl = page.locator(errorSelector).first();
|
|
2477
|
+
if (await errorEl.isVisible().catch(() => false)) {
|
|
2478
|
+
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
2479
|
+
if (errorText) {
|
|
2480
|
+
const classified = classifyQwenAuthError(undefined, errorText);
|
|
2481
|
+
return {
|
|
2482
|
+
success: false,
|
|
2483
|
+
permanentFailure: classified.isPermanent,
|
|
2484
|
+
reason: `Formulário: ${classified.reason}`,
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2458
2488
|
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
".ant-message-error",
|
|
2465
|
-
".ant-message-notice",
|
|
2466
|
-
'[role="alert"]',
|
|
2467
|
-
".qwenchat-auth-error",
|
|
2468
|
-
".auth-error-message",
|
|
2469
|
-
].join(", ");
|
|
2470
|
-
const errorEl = page.locator(errorSelector).first();
|
|
2471
|
-
if (await errorEl.isVisible().catch(() => false)) {
|
|
2472
|
-
const errorText = (await errorEl.innerText().catch(() => "")).trim();
|
|
2473
|
-
if (errorText) {
|
|
2474
|
-
const classified = classifyQwenAuthError(undefined, errorText);
|
|
2489
|
+
// Check if an OTP code verification screen appeared (e.g. 2FA / email verification code)
|
|
2490
|
+
const codeSelector =
|
|
2491
|
+
'input[placeholder*="code" i], input[placeholder*="código" i], input[name*="code" i]';
|
|
2492
|
+
const codeInput = page.locator(codeSelector).first();
|
|
2493
|
+
if (await codeInput.isVisible().catch(() => false)) {
|
|
2475
2494
|
return {
|
|
2476
2495
|
success: false,
|
|
2477
|
-
|
|
2478
|
-
reason: `Formulário: ${classified.reason}`,
|
|
2496
|
+
reason: "Conta requer código de verificação enviado por e-mail (2FA/OTP)",
|
|
2479
2497
|
};
|
|
2480
2498
|
}
|
|
2481
|
-
}
|
|
2482
2499
|
|
|
2483
|
-
|
|
2484
|
-
|
|
2500
|
+
// Check if auth cookie 'token' appeared
|
|
2501
|
+
try {
|
|
2502
|
+
const liveCookies = await page.context().cookies();
|
|
2503
|
+
if (liveCookies.some((c) => c.name === "token")) {
|
|
2504
|
+
return { success: true };
|
|
2505
|
+
}
|
|
2506
|
+
} catch {}
|
|
2485
2507
|
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2508
|
+
// Check if page redirected away from /auth and is logged in
|
|
2509
|
+
const currentUrl = page.url();
|
|
2510
|
+
if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
|
|
2511
|
+
if (await isPageLoggedIn(page, 3000)) {
|
|
2512
|
+
return { success: true };
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
await sleep(1000);
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// Final check
|
|
2520
|
+
if (await isPageLoggedIn(page, 3000)) {
|
|
2491
2521
|
return { success: true };
|
|
2492
2522
|
}
|
|
2493
2523
|
|