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.
@@ -3523,8 +3523,29 @@ function getStealthScript(profile) {
3523
3523
  const _toBlob = HTMLCanvasElement.prototype.toBlob;
3524
3524
  const _getImageData = CanvasRenderingContext2D.prototype.getImageData;
3525
3525
 
3526
+ function isCaptchaCanvas(canvas) {
3527
+ try {
3528
+ if (!canvas) return false;
3529
+ const id = (canvas.id || "").toLowerCase();
3530
+ const cls = (canvas.className || "").toLowerCase();
3531
+ return (
3532
+ id.includes("baxia") ||
3533
+ id.includes("nc_") ||
3534
+ id.includes("captcha") ||
3535
+ id.includes("punish") ||
3536
+ cls.includes("baxia") ||
3537
+ cls.includes("nc_") ||
3538
+ cls.includes("captcha") ||
3539
+ Boolean(canvas.closest?.("#baxia-dialog, #baxia-punish, [class*='baxia'], [class*='nc_'], .auth-layout"))
3540
+ );
3541
+ } catch(e) {
3542
+ return false;
3543
+ }
3544
+ }
3545
+
3526
3546
  function addNoise(canvas) {
3527
3547
  try {
3548
+ if (isCaptchaCanvas(canvas)) return;
3528
3549
  const ctx = canvas.getContext('2d');
3529
3550
  if (!ctx) return;
3530
3551
  const style = ctx.fillStyle;
@@ -3548,6 +3569,9 @@ function getStealthScript(profile) {
3548
3569
 
3549
3570
  CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
3550
3571
  const imageData = _getImageData.apply(this, arguments);
3572
+ if (isCaptchaCanvas(this.canvas)) {
3573
+ return imageData;
3574
+ }
3551
3575
  const data = imageData.data;
3552
3576
  const maxPixels = Math.min(data.length / 4, 2500);
3553
3577
  for (let i = 0; i < maxPixels * 4; i += 4) {
@@ -4088,8 +4112,9 @@ async function loginToQwen(accountId, email, password) {
4088
4112
  }
4089
4113
  if (attempt < maxAttempts) {
4090
4114
  const backoffMs = attempt * 5e3;
4115
+ const failReason = uiResult.reason || apiResult.reason || "falha tempor\xE1ria";
4091
4116
  console.warn(
4092
- `\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${apiResult.reason || uiResult.reason || "falha tempor\xE1ria"}), retrying in ${backoffMs / 1e3}s`
4117
+ `\u26A0\uFE0F [Playwright] Login attempt ${attempt}/${maxAttempts} failed for ${maskEmail(email)} (${failReason}), retrying in ${backoffMs / 1e3}s`
4093
4118
  );
4094
4119
  await sleep2(backoffMs);
4095
4120
  }
@@ -4119,37 +4144,52 @@ async function loginToQwen(accountId, email, password) {
4119
4144
  async function loginViaApi(page, email, password) {
4120
4145
  try {
4121
4146
  const hashedPassword = crypto3.createHash("sha256").update(password).digest("hex");
4122
- const signinUrl = qwenUrl("/api/v2/auths/signin");
4123
- let signinSuccess = false;
4124
- let data = null;
4125
- if (page.request && typeof page.request.post === "function") {
4126
- try {
4127
- const response = await page.request.post(signinUrl, {
4128
- data: {
4129
- email,
4130
- password: hashedPassword,
4131
- login_type: "email"
4132
- },
4133
- headers: {
4134
- "content-type": "application/json",
4135
- accept: "application/json, text/plain, */*",
4136
- referer: qwenUrl("/auth"),
4137
- origin: qwenOrigin(),
4138
- source: "web"
4139
- },
4140
- timeout: 1e4
4141
- });
4142
- data = await response.json().catch(() => null);
4143
- signinSuccess = Boolean(data && (data.success === true || data.token));
4144
- } catch {
4145
- }
4147
+ if (!page.url().includes("/auth")) {
4148
+ await page.goto(qwenUrl("/auth"), {
4149
+ waitUntil: "domcontentloaded",
4150
+ timeout: config.timeouts.navigation
4151
+ }).catch(() => {
4152
+ });
4153
+ await sleep2(1e3);
4146
4154
  }
4147
- if (!signinSuccess && !data) {
4148
- const requestId = crypto3.randomUUID();
4149
- const evalRes = await page.evaluate(
4150
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
4155
+ const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
4156
+ const requestId = crypto3.randomUUID();
4157
+ const evalRes = await page.evaluate(
4158
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
4159
+ try {
4160
+ const response = await fetch(signinUrl2, {
4161
+ method: "POST",
4162
+ signal: AbortSignal.timeout(15e3),
4163
+ credentials: "include",
4164
+ headers: {
4165
+ accept: "application/json, text/plain, */*",
4166
+ "content-type": "application/json",
4167
+ source: "web",
4168
+ version: "0.3.11",
4169
+ "x-request-origin": "https://chat.qwen.ai",
4170
+ timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4171
+ "x-request-id": requestId2
4172
+ },
4173
+ body: JSON.stringify({ email: email2, password: password2 })
4174
+ });
4175
+ const json = await response.json().catch(() => null);
4176
+ return { ok: response.ok, status: response.status, data: json };
4177
+ } catch (e) {
4178
+ return { ok: false, error: e.message };
4179
+ }
4180
+ },
4181
+ { email, password: hashedPassword, signinUrl, requestId }
4182
+ ).catch(() => null);
4183
+ let data = evalRes?.data;
4184
+ let signinSuccess = Boolean(
4185
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
4186
+ );
4187
+ if (!signinSuccess && !data?.data?.code) {
4188
+ const fallbackUrl = qwenUrl("/api/v2/auths/signin");
4189
+ const fallbackRes = await page.evaluate(
4190
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
4151
4191
  try {
4152
- const response = await fetch(signinUrl2, {
4192
+ const response = await fetch(fallbackUrl2, {
4153
4193
  method: "POST",
4154
4194
  signal: AbortSignal.timeout(1e4),
4155
4195
  credentials: "include",
@@ -4157,10 +4197,11 @@ async function loginViaApi(page, email, password) {
4157
4197
  accept: "application/json, text/plain, */*",
4158
4198
  "content-type": "application/json",
4159
4199
  source: "web",
4200
+ version: "0.3.11",
4160
4201
  timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
4161
4202
  "x-request-id": requestId2
4162
4203
  },
4163
- body: JSON.stringify({ email: email2, password: password2, login_type: "email" })
4204
+ body: JSON.stringify({ email: email2, password: password2 })
4164
4205
  });
4165
4206
  const json = await response.json().catch(() => null);
4166
4207
  return { ok: response.ok, status: response.status, data: json };
@@ -4168,11 +4209,13 @@ async function loginViaApi(page, email, password) {
4168
4209
  return { ok: false, error: e.message };
4169
4210
  }
4170
4211
  },
4171
- { email, password: hashedPassword, signinUrl, requestId }
4212
+ { email, password: hashedPassword, fallbackUrl, requestId }
4172
4213
  ).catch(() => null);
4173
- if (evalRes?.data) {
4174
- data = evalRes.data;
4175
- signinSuccess = Boolean(data && (data.success === true || data.token));
4214
+ if (fallbackRes?.data) {
4215
+ data = fallbackRes.data;
4216
+ signinSuccess = Boolean(
4217
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
4218
+ );
4176
4219
  }
4177
4220
  }
4178
4221
  if (data?.success === false) {
@@ -4186,7 +4229,8 @@ async function loginViaApi(page, email, password) {
4186
4229
  };
4187
4230
  }
4188
4231
  if (signinSuccess) {
4189
- const token = data?.data?.token || data?.token;
4232
+ const token = data?.data?.access_token || data?.data?.token || data?.access_token || data?.token;
4233
+ const refreshToken = data?.data?.refresh_token || data?.refresh_token;
4190
4234
  if (token) {
4191
4235
  try {
4192
4236
  await page.context().addCookies([
@@ -4210,14 +4254,19 @@ async function loginViaApi(page, email, password) {
4210
4254
  }).catch(() => {
4211
4255
  });
4212
4256
  if (token) {
4213
- await page.evaluate((tok) => {
4214
- try {
4215
- localStorage.removeItem("qwen_token_logged_out_marker");
4216
- localStorage.setItem("token", tok);
4217
- document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
4218
- } catch {
4219
- }
4220
- }, token).catch(() => {
4257
+ await page.evaluate(
4258
+ ({ tok, rTok }) => {
4259
+ try {
4260
+ localStorage.removeItem("qwen_token_logged_out_marker");
4261
+ localStorage.setItem("token", tok);
4262
+ localStorage.setItem("access_token", tok);
4263
+ if (rTok) localStorage.setItem("refresh_token", rTok);
4264
+ document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
4265
+ } catch {
4266
+ }
4267
+ },
4268
+ { tok: token, rTok: refreshToken }
4269
+ ).catch(() => {
4221
4270
  });
4222
4271
  await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {
4223
4272
  });
@@ -4245,11 +4294,23 @@ async function loginViaApi(page, email, password) {
4245
4294
  }
4246
4295
  async function loginViaUi(page, email, password) {
4247
4296
  try {
4248
- await page.goto(qwenUrl("/auth"), {
4249
- waitUntil: "domcontentloaded",
4250
- timeout: config.timeouts.navigation
4251
- });
4252
- await sleep2(1500);
4297
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3).catch(() => false)) {
4298
+ return { success: true };
4299
+ }
4300
+ try {
4301
+ await page.goto(qwenUrl("/auth"), {
4302
+ waitUntil: "domcontentloaded",
4303
+ timeout: Math.min(config.timeouts.navigation, 15e3)
4304
+ });
4305
+ await sleep2(1e3);
4306
+ } catch (gotoErr) {
4307
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4308
+ return { success: true };
4309
+ }
4310
+ }
4311
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4312
+ return { success: true };
4313
+ }
4253
4314
  const emailSelector = [
4254
4315
  'input[type="email"]',
4255
4316
  'input[name="email"]',
@@ -4259,7 +4320,7 @@ async function loginViaUi(page, email, password) {
4259
4320
  ].join(", ");
4260
4321
  try {
4261
4322
  await page.waitForSelector(emailSelector, {
4262
- timeout: Math.min(config.timeouts.page, 8e3)
4323
+ timeout: Math.min(config.timeouts.page, 5e3)
4263
4324
  });
4264
4325
  } catch {
4265
4326
  if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
@@ -4272,30 +4333,55 @@ async function loginViaUi(page, email, password) {
4272
4333
  return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
4273
4334
  }
4274
4335
  await clearVisibleChallenge(page);
4275
- const findPasswordModeButton = () => {
4276
- return page.locator('button, [role="button"], a, div').filter({
4277
- 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
4278
- }).first();
4336
+ const isPasswordInputPresent = async () => {
4337
+ return await page.locator('input[type="password"], input[name="password"]').count().catch(() => 0) > 0;
4279
4338
  };
4280
- try {
4281
- const pwdModeBtn = findPasswordModeButton();
4282
- if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
4283
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4339
+ const revealPasswordMode = async () => {
4340
+ if (page.url().includes("/forget")) {
4341
+ await page.goto(qwenUrl("/auth"), { waitUntil: "domcontentloaded", timeout: 1e4 }).catch(() => {
4284
4342
  });
4285
- await sleep2(500);
4343
+ await sleep2(1e3);
4286
4344
  }
4287
- } catch {
4288
- }
4289
- await page.fill(emailSelector, email);
4290
- await sleep2(300);
4291
- try {
4292
- const pwdModeBtn = findPasswordModeButton();
4293
- if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
4294
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4345
+ if (await isPasswordInputPresent()) return;
4346
+ const btn = page.locator("button").filter({
4347
+ 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
4348
+ }).filter({
4349
+ hasNotText: /(?:esqueci|forgot|olvid|reset|recover)/i
4350
+ }).first();
4351
+ if (await btn.isVisible({ timeout: 1500 }).catch(() => false)) {
4352
+ await btn.click({ timeout: 2e3 }).catch(() => {
4295
4353
  });
4296
4354
  await sleep2(500);
4297
4355
  }
4356
+ if (await isPasswordInputPresent()) return;
4357
+ await page.evaluate(() => {
4358
+ try {
4359
+ const buttons = Array.from(document.querySelectorAll("button, [role='button'], a"));
4360
+ for (const b of buttons) {
4361
+ const txt = (b.innerText || "").trim().toLowerCase();
4362
+ 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")) {
4363
+ b.click();
4364
+ return true;
4365
+ }
4366
+ }
4367
+ } catch {
4368
+ }
4369
+ return false;
4370
+ }).catch(() => false);
4371
+ await sleep2(500);
4372
+ };
4373
+ await revealPasswordMode();
4374
+ try {
4375
+ await page.fill(emailSelector, email, { timeout: 8e3 });
4376
+ await sleep2(300);
4298
4377
  } catch {
4378
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4379
+ return { success: true };
4380
+ }
4381
+ throw new Error(`Campo de e-mail n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4382
+ }
4383
+ if (!await isPasswordInputPresent()) {
4384
+ await revealPasswordMode();
4299
4385
  }
4300
4386
  const passwordSelector = 'input[type="password"], input[name="password"]';
4301
4387
  try {
@@ -4314,61 +4400,108 @@ async function loginViaUi(page, email, password) {
4314
4400
  reason: "Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o"
4315
4401
  };
4316
4402
  }
4317
- await page.fill(passwordSelector, password);
4318
- await sleep2(500);
4403
+ try {
4404
+ await page.fill(passwordSelector, password, { timeout: 8e3 });
4405
+ await sleep2(500);
4406
+ } catch {
4407
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4408
+ return { success: true };
4409
+ }
4410
+ throw new Error(`Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4411
+ }
4412
+ await page.evaluate(() => {
4413
+ try {
4414
+ const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
4415
+ const passEl = document.querySelector('input[type="password"], input[name="password"]');
4416
+ if (emailEl) {
4417
+ emailEl.dispatchEvent(new Event("input", { bubbles: true }));
4418
+ emailEl.dispatchEvent(new Event("change", { bubbles: true }));
4419
+ emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
4420
+ }
4421
+ if (passEl) {
4422
+ passEl.dispatchEvent(new Event("input", { bubbles: true }));
4423
+ passEl.dispatchEvent(new Event("change", { bubbles: true }));
4424
+ passEl.dispatchEvent(new Event("blur", { bubbles: true }));
4425
+ }
4426
+ } catch {
4427
+ }
4428
+ }).catch(() => {
4429
+ });
4319
4430
  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")';
4320
4431
  const submitButton = page.locator(submitSelector).first();
4321
4432
  try {
4322
4433
  await page.waitForSelector('button[type="submit"]:not([disabled])', {
4323
- timeout: 5e3
4434
+ timeout: 4e3
4324
4435
  });
4325
4436
  await submitButton.click();
4326
4437
  } catch {
4327
- await page.keyboard.press("Enter");
4328
- }
4329
- await sleep2(3e3);
4330
- await solveBaxiaCaptcha(page, {
4331
- waitForMs: 2e3,
4332
- maxAttempts: config.captcha.maxAttempts,
4333
- retryDelayMs: config.captcha.retryDelayMs
4334
- }).catch(() => {
4335
- });
4336
- const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
4337
- const codeInput = page.locator(codeSelector).first();
4338
- if (await codeInput.isVisible().catch(() => false)) {
4339
- return {
4340
- success: false,
4341
- reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
4342
- };
4438
+ const isDisabled = await submitButton.isDisabled().catch(() => true);
4439
+ if (!isDisabled) {
4440
+ await submitButton.click({ force: true }).catch(() => {
4441
+ });
4442
+ } else {
4443
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
4444
+ return { success: true };
4445
+ }
4446
+ }
4343
4447
  }
4344
- const errorSelector = [
4345
- ".qwen-chat-v2-toast-text",
4346
- ".qwen-chat-v2-toast-content",
4347
- ".ant-form-item-explain-error",
4348
- ".ant-message-error",
4349
- ".ant-message-notice",
4350
- '[role="alert"]',
4351
- ".qwenchat-auth-error",
4352
- ".auth-error-message"
4353
- ].join(", ");
4354
- const errorEl = page.locator(errorSelector).first();
4355
- if (await errorEl.isVisible().catch(() => false)) {
4356
- const errorText = (await errorEl.innerText().catch(() => "")).trim();
4357
- if (errorText) {
4358
- const classified = classifyQwenAuthError(void 0, errorText);
4448
+ const postSubmitDeadline = Date.now() + 15e3;
4449
+ while (Date.now() < postSubmitDeadline) {
4450
+ if (page.isClosed()) break;
4451
+ if (config.captcha.enabled) {
4452
+ await solveBaxiaCaptcha(page, {
4453
+ waitForMs: 1500,
4454
+ maxAttempts: config.captcha.maxAttempts,
4455
+ retryDelayMs: config.captcha.retryDelayMs
4456
+ }).catch(() => {
4457
+ });
4458
+ }
4459
+ const errorSelector = [
4460
+ ".qwen-chat-v2-toast-text",
4461
+ ".qwen-chat-v2-toast-content",
4462
+ ".ant-form-item-explain-error",
4463
+ ".ant-message-error",
4464
+ ".ant-message-notice",
4465
+ '[role="alert"]',
4466
+ ".qwenchat-auth-error",
4467
+ ".auth-error-message"
4468
+ ].join(", ");
4469
+ const errorEl = page.locator(errorSelector).first();
4470
+ if (await errorEl.isVisible().catch(() => false)) {
4471
+ const errorText = (await errorEl.innerText().catch(() => "")).trim();
4472
+ if (errorText) {
4473
+ const classified = classifyQwenAuthError(void 0, errorText);
4474
+ return {
4475
+ success: false,
4476
+ permanentFailure: classified.isPermanent,
4477
+ reason: `Formul\xE1rio: ${classified.reason}`
4478
+ };
4479
+ }
4480
+ }
4481
+ const codeSelector = 'input[placeholder*="code" i], input[placeholder*="c\xF3digo" i], input[name*="code" i]';
4482
+ const codeInput = page.locator(codeSelector).first();
4483
+ if (await codeInput.isVisible().catch(() => false)) {
4359
4484
  return {
4360
4485
  success: false,
4361
- permanentFailure: classified.isPermanent,
4362
- reason: `Formul\xE1rio: ${classified.reason}`
4486
+ reason: "Conta requer c\xF3digo de verifica\xE7\xE3o enviado por e-mail (2FA/OTP)"
4363
4487
  };
4364
4488
  }
4489
+ try {
4490
+ const liveCookies = await page.context().cookies();
4491
+ if (liveCookies.some((c) => c.name === "token")) {
4492
+ return { success: true };
4493
+ }
4494
+ } catch {
4495
+ }
4496
+ const currentUrl = page.url();
4497
+ if (!currentUrl.includes("/auth") && !currentUrl.includes("/login")) {
4498
+ if (await isPageLoggedIn(page, 3e3)) {
4499
+ return { success: true };
4500
+ }
4501
+ }
4502
+ await sleep2(1e3);
4365
4503
  }
4366
- const isLoggedIn = await isPageLoggedIn(page);
4367
- if (isLoggedIn) {
4368
- await page.goto(qwenUrl("/"), {
4369
- waitUntil: "domcontentloaded",
4370
- timeout: config.timeouts.navigation
4371
- });
4504
+ if (await isPageLoggedIn(page, 3e3)) {
4372
4505
  return { success: true };
4373
4506
  }
4374
4507
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.4.2",
3
+ "version": "1.4.7",
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": {
@@ -17,6 +17,6 @@ export function getAppVersion(): string {
17
17
  }
18
18
  }
19
19
  } catch {}
20
- cachedVersion = "v1.4.2";
20
+ cachedVersion = "v1.4.7";
21
21
  return cachedVersion;
22
22
  }
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
- config.playwright.headless,
123
+ false, // Visible browser so user can see and complete challenges if needed
124
124
  config.playwright.browser,
125
125
  );
126
126