qwenproxy-cli 1.4.2 → 1.4.7

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.
@@ -3287,8 +3287,29 @@ function getStealthScript(profile) {
3287
3287
  const _toBlob = HTMLCanvasElement.prototype.toBlob;
3288
3288
  const _getImageData = CanvasRenderingContext2D.prototype.getImageData;
3289
3289
 
3290
+ function isCaptchaCanvas(canvas) {
3291
+ try {
3292
+ if (!canvas) return false;
3293
+ const id = (canvas.id || "").toLowerCase();
3294
+ const cls = (canvas.className || "").toLowerCase();
3295
+ return (
3296
+ id.includes("baxia") ||
3297
+ id.includes("nc_") ||
3298
+ id.includes("captcha") ||
3299
+ id.includes("punish") ||
3300
+ cls.includes("baxia") ||
3301
+ cls.includes("nc_") ||
3302
+ cls.includes("captcha") ||
3303
+ Boolean(canvas.closest?.("#baxia-dialog, #baxia-punish, [class*='baxia'], [class*='nc_'], .auth-layout"))
3304
+ );
3305
+ } catch(e) {
3306
+ return false;
3307
+ }
3308
+ }
3309
+
3290
3310
  function addNoise(canvas) {
3291
3311
  try {
3312
+ if (isCaptchaCanvas(canvas)) return;
3292
3313
  const ctx = canvas.getContext('2d');
3293
3314
  if (!ctx) return;
3294
3315
  const style = ctx.fillStyle;
@@ -3312,6 +3333,9 @@ function getStealthScript(profile) {
3312
3333
 
3313
3334
  CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
3314
3335
  const imageData = _getImageData.apply(this, arguments);
3336
+ if (isCaptchaCanvas(this.canvas)) {
3337
+ return imageData;
3338
+ }
3315
3339
  const data = imageData.data;
3316
3340
  const maxPixels = Math.min(data.length / 4, 2500);
3317
3341
  for (let i = 0; i < maxPixels * 4; i += 4) {
@@ -3852,8 +3876,9 @@ async function loginToQwen(accountId, email, password) {
3852
3876
  }
3853
3877
  if (attempt < maxAttempts) {
3854
3878
  const backoffMs = attempt * 5e3;
3879
+ const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
3855
3880
  console.warn(
3856
- `\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${apiResult.reason || uiResult.reason || "falha tempor\xE1ria"}), retrying in ${backoffMs / 1e3}s`
3881
+ `\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
3857
3882
  );
3858
3883
  await sleep2(backoffMs);
3859
3884
  }
@@ -3883,37 +3908,52 @@ async function loginToQwen(accountId, email, password) {
3883
3908
  async function loginViaApi(page, email, password) {
3884
3909
  try {
3885
3910
  const hashedPassword = crypto3.createHash("sha256").update(password).digest("hex");
3886
- const signinUrl = qwenUrl("/api/v2/auths/signin");
3887
- let signinSuccess = false;
3888
- let data = null;
3889
- if (page.request && typeof page.request.post === "function") {
3890
- try {
3891
- const response = await page.request.post(signinUrl, {
3892
- data: {
3893
- email,
3894
- password: hashedPassword,
3895
- login_type: "email"
3896
- },
3897
- headers: {
3898
- "content-type": "application/json",
3899
- accept: "application/json, text/plain, */*",
3900
- referer: qwenUrl("/auth"),
3901
- origin: qwenOrigin(),
3902
- source: "web"
3903
- },
3904
- timeout: 1e4
3905
- });
3906
- data = await response.json().catch(() => null);
3907
- signinSuccess = Boolean(data && (data.success === true || data.token));
3908
- } catch {
3909
- }
3911
+ if (!page.url().includes("/auth")) {
3912
+ await page.goto(qwenUrl("/auth"), {
3913
+ waitUntil: "domcontentloaded",
3914
+ timeout: config.timeouts.navigation
3915
+ }).catch(() => {
3916
+ });
3917
+ await sleep2(1e3);
3910
3918
  }
3911
- if (!signinSuccess && !data) {
3912
- const requestId = crypto3.randomUUID();
3913
- const evalRes = await page.evaluate(
3914
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
3919
+ const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
3920
+ const requestId = crypto3.randomUUID();
3921
+ const evalRes = await page.evaluate(
3922
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
3923
+ try {
3924
+ const response = await fetch(signinUrl2, {
3925
+ method: "POST",
3926
+ signal: AbortSignal.timeout(15e3),
3927
+ credentials: "include",
3928
+ headers: {
3929
+ accept: "application/json, text/plain, */*",
3930
+ "content-type": "application/json",
3931
+ source: "web",
3932
+ version: "0.3.11",
3933
+ "x-request-origin": "https://chat.qwen.ai",
3934
+ timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
3935
+ "x-request-id": requestId2
3936
+ },
3937
+ body: JSON.stringify({ email: email2, password: password2 })
3938
+ });
3939
+ const json = await response.json().catch(() => null);
3940
+ return { ok: response.ok, status: response.status, data: json };
3941
+ } catch (e) {
3942
+ return { ok: false, error: e.message };
3943
+ }
3944
+ },
3945
+ { email, password: hashedPassword, signinUrl, requestId }
3946
+ ).catch(() => null);
3947
+ let data = evalRes?.data;
3948
+ let signinSuccess = Boolean(
3949
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
3950
+ );
3951
+ if (!signinSuccess && !data?.data?.code) {
3952
+ const fallbackUrl = qwenUrl("/api/v2/auths/signin");
3953
+ const fallbackRes = await page.evaluate(
3954
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
3915
3955
  try {
3916
- const response = await fetch(signinUrl2, {
3956
+ const response = await fetch(fallbackUrl2, {
3917
3957
  method: "POST",
3918
3958
  signal: AbortSignal.timeout(1e4),
3919
3959
  credentials: "include",
@@ -3921,10 +3961,11 @@ async function loginViaApi(page, email, password) {
3921
3961
  accept: "application/json, text/plain, */*",
3922
3962
  "content-type": "application/json",
3923
3963
  source: "web",
3964
+ version: "0.3.11",
3924
3965
  timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
3925
3966
  "x-request-id": requestId2
3926
3967
  },
3927
- body: JSON.stringify({ email: email2, password: password2, login_type: "email" })
3968
+ body: JSON.stringify({ email: email2, password: password2 })
3928
3969
  });
3929
3970
  const json = await response.json().catch(() => null);
3930
3971
  return { ok: response.ok, status: response.status, data: json };
@@ -3932,11 +3973,13 @@ async function loginViaApi(page, email, password) {
3932
3973
  return { ok: false, error: e.message };
3933
3974
  }
3934
3975
  },
3935
- { email, password: hashedPassword, signinUrl, requestId }
3976
+ { email, password: hashedPassword, fallbackUrl, requestId }
3936
3977
  ).catch(() => null);
3937
- if (evalRes?.data) {
3938
- data = evalRes.data;
3939
- signinSuccess = Boolean(data && (data.success === true || data.token));
3978
+ if (fallbackRes?.data) {
3979
+ data = fallbackRes.data;
3980
+ signinSuccess = Boolean(
3981
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
3982
+ );
3940
3983
  }
3941
3984
  }
3942
3985
  if (data?.success === false) {
@@ -3950,7 +3993,8 @@ async function loginViaApi(page, email, password) {
3950
3993
  };
3951
3994
  }
3952
3995
  if (signinSuccess) {
3953
- const token = data?.data?.token || data?.token;
3996
+ const token = data?.data?.access_token || data?.data?.token || data?.access_token || data?.token;
3997
+ const refreshToken = data?.data?.refresh_token || data?.refresh_token;
3954
3998
  if (token) {
3955
3999
  try {
3956
4000
  await page.context().addCookies([
@@ -3974,14 +4018,19 @@ async function loginViaApi(page, email, password) {
3974
4018
  }).catch(() => {
3975
4019
  });
3976
4020
  if (token) {
3977
- await page.evaluate((tok) => {
3978
- try {
3979
- localStorage.removeItem("qwen_token_logged_out_marker");
3980
- localStorage.setItem("token", tok);
3981
- document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
3982
- } catch {
3983
- }
3984
- }, token).catch(() => {
4021
+ await page.evaluate(
4022
+ ({ tok, rTok }) => {
4023
+ try {
4024
+ localStorage.removeItem("qwen_token_logged_out_marker");
4025
+ localStorage.setItem("token", tok);
4026
+ localStorage.setItem("access_token", tok);
4027
+ if (rTok) localStorage.setItem("refresh_token", rTok);
4028
+ document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
4029
+ } catch {
4030
+ }
4031
+ },
4032
+ { tok: token, rTok: refreshToken }
4033
+ ).catch(() => {
3985
4034
  });
3986
4035
  await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {
3987
4036
  });
@@ -4009,11 +4058,23 @@ async function loginViaApi(page, email, password) {
4009
4058
  }
4010
4059
  async function loginViaUi(page, email, password) {
4011
4060
  try {
4012
- await page.goto(qwenUrl("/auth"), {
4013
- waitUntil: "domcontentloaded",
4014
- timeout: config.timeouts.navigation
4015
- });
4016
- await sleep2(1500);
4061
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3).catch(() => false)) {
4062
+ return { success: true };
4063
+ }
4064
+ try {
4065
+ await page.goto(qwenUrl("/auth"), {
4066
+ waitUntil: "domcontentloaded",
4067
+ timeout: Math.min(config.timeouts.navigation, 15e3)
4068
+ });
4069
+ await sleep2(1e3);
4070
+ } catch (gotoErr) {
4071
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4072
+ return { success: true };
4073
+ }
4074
+ }
4075
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4076
+ return { success: true };
4077
+ }
4017
4078
  const emailSelector = [
4018
4079
  'input[type="email"]',
4019
4080
  'input[name="email"]',
@@ -4023,7 +4084,7 @@ async function loginViaUi(page, email, password) {
4023
4084
  ].join(", ");
4024
4085
  try {
4025
4086
  await page.waitForSelector(emailSelector, {
4026
- timeout: Math.min(config.timeouts.page, 8e3)
4087
+ timeout: Math.min(config.timeouts.page, 5e3)
4027
4088
  });
4028
4089
  } catch {
4029
4090
  if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
@@ -4036,30 +4097,55 @@ async function loginViaUi(page, email, password) {
4036
4097
  return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
4037
4098
  }
4038
4099
  await clearVisibleChallenge(page);
4039
- const findPasswordModeButton = () => {
4040
- return page.locator('button, [role="button"], a, div').filter({
4041
- 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
4042
- }).first();
4100
+ const isPasswordInputPresent = async () => {
4101
+ return await page.locator('input[type="password"], input[name="password"]').count().catch(() => 0) > 0;
4043
4102
  };
4044
- try {
4045
- const pwdModeBtn = findPasswordModeButton();
4046
- if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
4047
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4103
+ const revealPasswordMode = async () => {
4104
+ if (page.url().includes("/forget")) {
4105
+ await page.goto(qwenUrl("/auth"), { waitUntil: "domcontentloaded", timeout: 1e4 }).catch(() => {
4048
4106
  });
4049
- await sleep2(500);
4107
+ await sleep2(1e3);
4050
4108
  }
4051
- } catch {
4052
- }
4053
- await page.fill(emailSelector, email);
4054
- await sleep2(300);
4055
- try {
4056
- const pwdModeBtn = findPasswordModeButton();
4057
- if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
4058
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4109
+ if (await isPasswordInputPresent()) return;
4110
+ const btn = page.locator("button").filter({
4111
+ 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
4112
+ }).filter({
4113
+ hasNotText: /(?:esqueci|forgot|olvid|reset|recover)/i
4114
+ }).first();
4115
+ if (await btn.isVisible({ timeout: 1500 }).catch(() => false)) {
4116
+ await btn.click({ timeout: 2e3 }).catch(() => {
4059
4117
  });
4060
4118
  await sleep2(500);
4061
4119
  }
4120
+ if (await isPasswordInputPresent()) return;
4121
+ await page.evaluate(() => {
4122
+ try {
4123
+ const buttons = Array.from(document.querySelectorAll("button, [role='button'], a"));
4124
+ for (const b of buttons) {
4125
+ const txt = (b.innerText || "").trim().toLowerCase();
4126
+ if (/(?: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.test(txt) && !txt.includes("esqueci") && !txt.includes("forgot") && !txt.includes("olvid") && !txt.includes("reset")) {
4127
+ b.click();
4128
+ return true;
4129
+ }
4130
+ }
4131
+ } catch {
4132
+ }
4133
+ return false;
4134
+ }).catch(() => false);
4135
+ await sleep2(500);
4136
+ };
4137
+ await revealPasswordMode();
4138
+ try {
4139
+ await page.fill(emailSelector, email, { timeout: 8e3 });
4140
+ await sleep2(300);
4062
4141
  } catch {
4142
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4143
+ return { success: true };
4144
+ }
4145
+ throw new Error(`Campo de e-mail n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4146
+ }
4147
+ if (!await isPasswordInputPresent()) {
4148
+ await revealPasswordMode();
4063
4149
  }
4064
4150
  const passwordSelector = 'input[type="password"], input[name="password"]';
4065
4151
  try {
@@ -4078,61 +4164,108 @@ async function loginViaUi(page, email, password) {
4078
4164
  reason: "Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o"
4079
4165
  };
4080
4166
  }
4081
- await page.fill(passwordSelector, password);
4082
- await sleep2(500);
4167
+ try {
4168
+ await page.fill(passwordSelector, password, { timeout: 8e3 });
4169
+ await sleep2(500);
4170
+ } catch {
4171
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4172
+ return { success: true };
4173
+ }
4174
+ throw new Error(`Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4175
+ }
4176
+ await page.evaluate(() => {
4177
+ try {
4178
+ const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
4179
+ const passEl = document.querySelector('input[type="password"], input[name="password"]');
4180
+ if (emailEl) {
4181
+ emailEl.dispatchEvent(new Event("input", { bubbles: true }));
4182
+ emailEl.dispatchEvent(new Event("change", { bubbles: true }));
4183
+ emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
4184
+ }
4185
+ if (passEl) {
4186
+ passEl.dispatchEvent(new Event("input", { bubbles: true }));
4187
+ passEl.dispatchEvent(new Event("change", { bubbles: true }));
4188
+ passEl.dispatchEvent(new Event("blur", { bubbles: true }));
4189
+ }
4190
+ } catch {
4191
+ }
4192
+ }).catch(() => {
4193
+ });
4083
4194
  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")';
4084
4195
  const submitButton = page.locator(submitSelector).first();
4085
4196
  try {
4086
4197
  await page.waitForSelector('button[type="submit"]:not([disabled])', {
4087
- timeout: 5e3
4198
+ timeout: 4e3
4088
4199
  });
4089
4200
  await submitButton.click();
4090
4201
  } catch {
4091
- await page.keyboard.press("Enter");
4092
- }
4093
- await sleep2(3e3);
4094
- await solveBaxiaCaptcha(page, {
4095
- waitForMs: 2e3,
4096
- maxAttempts: config.captcha.maxAttempts,
4097
- retryDelayMs: config.captcha.retryDelayMs
4098
- }).catch(() => {
4099
- });
4100
- const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
4101
- const codeInput = page.locator(codeSelector).first();
4102
- if (await codeInput.isVisible().catch(() => false)) {
4103
- return {
4104
- success: false,
4105
- reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
4106
- };
4202
+ const isDisabled = await submitButton.isDisabled().catch(() => true);
4203
+ if (!isDisabled) {
4204
+ await submitButton.click({ force: true }).catch(() => {
4205
+ });
4206
+ } else {
4207
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
4208
+ return { success: true };
4209
+ }
4210
+ }
4107
4211
  }
4108
- const errorSelector = [
4109
- ".qwen-chat-v2-toast-text",
4110
- ".qwen-chat-v2-toast-content",
4111
- ".ant-form-item-explain-error",
4112
- ".ant-message-error",
4113
- ".ant-message-notice",
4114
- '[role="alert"]',
4115
- ".qwenchat-auth-error",
4116
- ".auth-error-message"
4117
- ].join(", ");
4118
- const errorEl = page.locator(errorSelector).first();
4119
- if (await errorEl.isVisible().catch(() => false)) {
4120
- const errorText = (await errorEl.innerText().catch(() => "")).trim();
4121
- if (errorText) {
4122
- const classified = classifyQwenAuthError(void 0, errorText);
4212
+ const postSubmitDeadline = Date.now() + 15e3;
4213
+ while (Date.now() < postSubmitDeadline) {
4214
+ if (page.isClosed()) break;
4215
+ if (config.captcha.enabled) {
4216
+ await solveBaxiaCaptcha(page, {
4217
+ waitForMs: 1500,
4218
+ maxAttempts: config.captcha.maxAttempts,
4219
+ retryDelayMs: config.captcha.retryDelayMs
4220
+ }).catch(() => {
4221
+ });
4222
+ }
4223
+ const errorSelector = [
4224
+ ".qwen-chat-v2-toast-text",
4225
+ ".qwen-chat-v2-toast-content",
4226
+ ".ant-form-item-explain-error",
4227
+ ".ant-message-error",
4228
+ ".ant-message-notice",
4229
+ '[role="alert"]',
4230
+ ".qwenchat-auth-error",
4231
+ ".auth-error-message"
4232
+ ].join(", ");
4233
+ const errorEl = page.locator(errorSelector).first();
4234
+ if (await errorEl.isVisible().catch(() => false)) {
4235
+ const errorText = (await errorEl.innerText().catch(() => "")).trim();
4236
+ if (errorText) {
4237
+ const classified = classifyQwenAuthError(void 0, errorText);
4238
+ return {
4239
+ success: false,
4240
+ permanentFailure: classified.isPermanent,
4241
+ reason: `Formul\xE1rio: ${classified.reason}`
4242
+ };
4243
+ }
4244
+ }
4245
+ const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
4246
+ const codeInput = page.locator(codeSelector).first();
4247
+ if (await codeInput.isVisible().catch(() => false)) {
4123
4248
  return {
4124
4249
  success: false,
4125
- permanentFailure: classified.isPermanent,
4126
- reason: `Formul\xE1rio: ${classified.reason}`
4250
+ reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
4127
4251
  };
4128
4252
  }
4253
+ try {
4254
+ const liveCookies = await page.context().cookies();
4255
+ if (liveCookies.some((c) => c.name === "token")) {
4256
+ return { success: true };
4257
+ }
4258
+ } catch {
4259
+ }
4260
+ const currentUrl = page.url();
4261
+ if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
4262
+ if (await isPageLoggedIn(page, 3e3)) {
4263
+ return { success: true };
4264
+ }
4265
+ }
4266
+ await sleep2(1e3);
4129
4267
  }
4130
- const isLoggedIn = await isPageLoggedIn(page);
4131
- if (isLoggedIn) {
4132
- await page.goto(qwenUrl("/"), {
4133
- waitUntil: "domcontentloaded",
4134
- timeout: config.timeouts.navigation
4135
- });
4268
+ if (await isPageLoggedIn(page, 3e3)) {
4136
4269
  return { success: true };
4137
4270
  }
4138
4271
  return {