qwenproxy-cli 1.4.3 → 1.4.8

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.
@@ -314,6 +314,7 @@ function runMigrations(db2) {
314
314
  "sec_ch_ua_mobile TEXT",
315
315
  "sec_ch_ua_platform TEXT",
316
316
  "version TEXT",
317
+ "refresh_token TEXT",
317
318
  "captured_at INTEGER DEFAULT 0"
318
319
  ];
319
320
  for (const col of authSessionCols) {
@@ -392,11 +393,11 @@ function saveAuthSession(accountId, session) {
392
393
  INSERT OR REPLACE INTO qwen_auth_sessions (
393
394
  account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
394
395
  sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
395
- user_id, token_expires_at, captured_at, updated_at
396
+ user_id, token_expires_at, refresh_token, captured_at, updated_at
396
397
  ) VALUES (
397
398
  ?, ?, ?, ?, ?, ?,
398
399
  ?, ?, ?, ?,
399
- ?, ?, ?, datetime('now')
400
+ ?, ?, ?, ?, datetime('now')
400
401
  )
401
402
  `);
402
403
  stmt.run(
@@ -412,6 +413,7 @@ function saveAuthSession(accountId, session) {
412
413
  session.version || null,
413
414
  session.userId || null,
414
415
  session.tokenExpiresAt || null,
416
+ session.refreshToken || null,
415
417
  session.capturedAt ?? Date.now()
416
418
  );
417
419
  }
@@ -420,7 +422,7 @@ function getValidAuthSession(accountId, maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
420
422
  const row = database.prepare(
421
423
  `SELECT account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
422
424
  sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
423
- user_id, token_expires_at, captured_at
425
+ user_id, token_expires_at, refresh_token, captured_at
424
426
  FROM qwen_auth_sessions WHERE account_id = ?`
425
427
  ).get(accountId);
426
428
  if (!row) return null;
@@ -428,7 +430,7 @@ function getValidAuthSession(accountId, maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
428
430
  if (capturedAt <= 0 || Date.now() - capturedAt > maxAgeMs) {
429
431
  return null;
430
432
  }
431
- if (row.token_expires_at) {
433
+ if (!row.refresh_token && row.token_expires_at) {
432
434
  const tokenExpMs = Number(row.token_expires_at) * 1e3;
433
435
  if (tokenExpMs <= Date.now() + 5 * 60 * 1e3) {
434
436
  return null;
@@ -450,6 +452,7 @@ function getValidAuthSession(accountId, maxAgeMs = 30 * 24 * 60 * 60 * 1e3) {
450
452
  version: row.version || void 0,
451
453
  userId: row.user_id || void 0,
452
454
  tokenExpiresAt: row.token_expires_at ? Number(row.token_expires_at) : void 0,
455
+ refreshToken: row.refresh_token || void 0,
453
456
  capturedAt
454
457
  };
455
458
  }
@@ -3287,8 +3290,29 @@ function getStealthScript(profile) {
3287
3290
  const _toBlob = HTMLCanvasElement.prototype.toBlob;
3288
3291
  const _getImageData = CanvasRenderingContext2D.prototype.getImageData;
3289
3292
 
3293
+ function isCaptchaCanvas(canvas) {
3294
+ try {
3295
+ if (!canvas) return false;
3296
+ const id = (canvas.id || "").toLowerCase();
3297
+ const cls = (canvas.className || "").toLowerCase();
3298
+ return (
3299
+ id.includes("baxia") ||
3300
+ id.includes("nc_") ||
3301
+ id.includes("captcha") ||
3302
+ id.includes("punish") ||
3303
+ cls.includes("baxia") ||
3304
+ cls.includes("nc_") ||
3305
+ cls.includes("captcha") ||
3306
+ Boolean(canvas.closest?.("#baxia-dialog, #baxia-punish, [class*='baxia'], [class*='nc_'], .auth-layout"))
3307
+ );
3308
+ } catch(e) {
3309
+ return false;
3310
+ }
3311
+ }
3312
+
3290
3313
  function addNoise(canvas) {
3291
3314
  try {
3315
+ if (isCaptchaCanvas(canvas)) return;
3292
3316
  const ctx = canvas.getContext('2d');
3293
3317
  if (!ctx) return;
3294
3318
  const style = ctx.fillStyle;
@@ -3312,6 +3336,9 @@ function getStealthScript(profile) {
3312
3336
 
3313
3337
  CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
3314
3338
  const imageData = _getImageData.apply(this, arguments);
3339
+ if (isCaptchaCanvas(this.canvas)) {
3340
+ return imageData;
3341
+ }
3315
3342
  const data = imageData.data;
3316
3343
  const maxPixels = Math.min(data.length / 4, 2500);
3317
3344
  for (let i = 0; i < maxPixels * 4; i += 4) {
@@ -3788,6 +3815,7 @@ async function loginToQwen(accountId, email, password) {
3788
3815
  secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
3789
3816
  version: cache.headers["version"] || void 0,
3790
3817
  tokenExpiresAt: exp || void 0,
3818
+ refreshToken: apiResult.refreshToken,
3791
3819
  capturedAt: Date.now()
3792
3820
  });
3793
3821
  }
@@ -3821,6 +3849,13 @@ async function loginToQwen(accountId, email, password) {
3821
3849
  markAccountHeadersReady(accountId);
3822
3850
  const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
3823
3851
  const exp = parseJwtExpiry(tokenCookie.value);
3852
+ let rTok = uiResult.refreshToken;
3853
+ if (!rTok) {
3854
+ try {
3855
+ rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
3856
+ } catch {
3857
+ }
3858
+ }
3824
3859
  saveAuthSession2(accountId, {
3825
3860
  cookie: cookieStr,
3826
3861
  userAgent: cache.headers["user-agent"] || "",
@@ -3832,6 +3867,7 @@ async function loginToQwen(accountId, email, password) {
3832
3867
  secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
3833
3868
  version: cache.headers["version"] || void 0,
3834
3869
  tokenExpiresAt: exp || void 0,
3870
+ refreshToken: rTok,
3835
3871
  capturedAt: Date.now()
3836
3872
  });
3837
3873
  }
@@ -3884,37 +3920,52 @@ async function loginToQwen(accountId, email, password) {
3884
3920
  async function loginViaApi(page, email, password) {
3885
3921
  try {
3886
3922
  const hashedPassword = crypto3.createHash("sha256").update(password).digest("hex");
3887
- const signinUrl = qwenUrl("/api/v2/auths/signin");
3888
- let signinSuccess = false;
3889
- let data = null;
3890
- if (page.request && typeof page.request.post === "function") {
3891
- try {
3892
- const response = await page.request.post(signinUrl, {
3893
- data: {
3894
- email,
3895
- password: hashedPassword,
3896
- login_type: "email"
3897
- },
3898
- headers: {
3899
- "content-type": "application/json",
3900
- accept: "application/json, text/plain, */*",
3901
- referer: qwenUrl("/auth"),
3902
- origin: qwenOrigin(),
3903
- source: "web"
3904
- },
3905
- timeout: 1e4
3906
- });
3907
- data = await response.json().catch(() => null);
3908
- signinSuccess = Boolean(data && (data.success === true || data.token));
3909
- } catch {
3910
- }
3923
+ if (!page.url().includes("/auth")) {
3924
+ await page.goto(qwenUrl("/auth"), {
3925
+ waitUntil: "domcontentloaded",
3926
+ timeout: config.timeouts.navigation
3927
+ }).catch(() => {
3928
+ });
3929
+ await sleep2(1e3);
3911
3930
  }
3912
- if (!signinSuccess && !data) {
3913
- const requestId = crypto3.randomUUID();
3914
- const evalRes = await page.evaluate(
3915
- async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
3931
+ const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
3932
+ const requestId = crypto3.randomUUID();
3933
+ const evalRes = await page.evaluate(
3934
+ async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
3935
+ try {
3936
+ const response = await fetch(signinUrl2, {
3937
+ method: "POST",
3938
+ signal: AbortSignal.timeout(15e3),
3939
+ credentials: "include",
3940
+ headers: {
3941
+ accept: "application/json, text/plain, */*",
3942
+ "content-type": "application/json",
3943
+ source: "web",
3944
+ version: "0.3.11",
3945
+ "x-request-origin": "https://chat.qwen.ai",
3946
+ timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
3947
+ "x-request-id": requestId2
3948
+ },
3949
+ body: JSON.stringify({ email: email2, password: password2 })
3950
+ });
3951
+ const json = await response.json().catch(() => null);
3952
+ return { ok: response.ok, status: response.status, data: json };
3953
+ } catch (e) {
3954
+ return { ok: false, error: e.message };
3955
+ }
3956
+ },
3957
+ { email, password: hashedPassword, signinUrl, requestId }
3958
+ ).catch(() => null);
3959
+ let data = evalRes?.data;
3960
+ let signinSuccess = Boolean(
3961
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
3962
+ );
3963
+ if (!signinSuccess && !data?.data?.code) {
3964
+ const fallbackUrl = qwenUrl("/api/v2/auths/signin");
3965
+ const fallbackRes = await page.evaluate(
3966
+ async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
3916
3967
  try {
3917
- const response = await fetch(signinUrl2, {
3968
+ const response = await fetch(fallbackUrl2, {
3918
3969
  method: "POST",
3919
3970
  signal: AbortSignal.timeout(1e4),
3920
3971
  credentials: "include",
@@ -3922,10 +3973,11 @@ async function loginViaApi(page, email, password) {
3922
3973
  accept: "application/json, text/plain, */*",
3923
3974
  "content-type": "application/json",
3924
3975
  source: "web",
3976
+ version: "0.3.11",
3925
3977
  timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
3926
3978
  "x-request-id": requestId2
3927
3979
  },
3928
- body: JSON.stringify({ email: email2, password: password2, login_type: "email" })
3980
+ body: JSON.stringify({ email: email2, password: password2 })
3929
3981
  });
3930
3982
  const json = await response.json().catch(() => null);
3931
3983
  return { ok: response.ok, status: response.status, data: json };
@@ -3933,11 +3985,13 @@ async function loginViaApi(page, email, password) {
3933
3985
  return { ok: false, error: e.message };
3934
3986
  }
3935
3987
  },
3936
- { email, password: hashedPassword, signinUrl, requestId }
3988
+ { email, password: hashedPassword, fallbackUrl, requestId }
3937
3989
  ).catch(() => null);
3938
- if (evalRes?.data) {
3939
- data = evalRes.data;
3940
- signinSuccess = Boolean(data && (data.success === true || data.token));
3990
+ if (fallbackRes?.data) {
3991
+ data = fallbackRes.data;
3992
+ signinSuccess = Boolean(
3993
+ data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
3994
+ );
3941
3995
  }
3942
3996
  }
3943
3997
  if (data?.success === false) {
@@ -3951,7 +4005,8 @@ async function loginViaApi(page, email, password) {
3951
4005
  };
3952
4006
  }
3953
4007
  if (signinSuccess) {
3954
- const token = data?.data?.token || data?.token;
4008
+ const token = data?.data?.access_token || data?.data?.token || data?.access_token || data?.token;
4009
+ const refreshToken = data?.data?.refresh_token || data?.refresh_token;
3955
4010
  if (token) {
3956
4011
  try {
3957
4012
  await page.context().addCookies([
@@ -3975,18 +4030,23 @@ async function loginViaApi(page, email, password) {
3975
4030
  }).catch(() => {
3976
4031
  });
3977
4032
  if (token) {
3978
- await page.evaluate((tok) => {
3979
- try {
3980
- localStorage.removeItem("qwen_token_logged_out_marker");
3981
- localStorage.setItem("token", tok);
3982
- document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
3983
- } catch {
3984
- }
3985
- }, token).catch(() => {
4033
+ await page.evaluate(
4034
+ ({ tok, rTok }) => {
4035
+ try {
4036
+ localStorage.removeItem("qwen_token_logged_out_marker");
4037
+ localStorage.setItem("token", tok);
4038
+ localStorage.setItem("access_token", tok);
4039
+ if (rTok) localStorage.setItem("refresh_token", rTok);
4040
+ document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
4041
+ } catch {
4042
+ }
4043
+ },
4044
+ { tok: token, rTok: refreshToken }
4045
+ ).catch(() => {
3986
4046
  });
3987
4047
  await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {
3988
4048
  });
3989
- return { success: true };
4049
+ return { success: true, token, refreshToken };
3990
4050
  }
3991
4051
  }
3992
4052
  return {
@@ -4010,11 +4070,23 @@ async function loginViaApi(page, email, password) {
4010
4070
  }
4011
4071
  async function loginViaUi(page, email, password) {
4012
4072
  try {
4013
- await page.goto(qwenUrl("/auth"), {
4014
- waitUntil: "domcontentloaded",
4015
- timeout: config.timeouts.navigation
4016
- });
4017
- await sleep2(1500);
4073
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3).catch(() => false)) {
4074
+ return { success: true };
4075
+ }
4076
+ try {
4077
+ await page.goto(qwenUrl("/auth"), {
4078
+ waitUntil: "domcontentloaded",
4079
+ timeout: Math.min(config.timeouts.navigation, 15e3)
4080
+ });
4081
+ await sleep2(1e3);
4082
+ } catch (gotoErr) {
4083
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4084
+ return { success: true };
4085
+ }
4086
+ }
4087
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
4088
+ return { success: true };
4089
+ }
4018
4090
  const emailSelector = [
4019
4091
  'input[type="email"]',
4020
4092
  'input[name="email"]',
@@ -4024,7 +4096,7 @@ async function loginViaUi(page, email, password) {
4024
4096
  ].join(", ");
4025
4097
  try {
4026
4098
  await page.waitForSelector(emailSelector, {
4027
- timeout: Math.min(config.timeouts.page, 8e3)
4099
+ timeout: Math.min(config.timeouts.page, 5e3)
4028
4100
  });
4029
4101
  } catch {
4030
4102
  if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
@@ -4037,30 +4109,55 @@ async function loginViaUi(page, email, password) {
4037
4109
  return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
4038
4110
  }
4039
4111
  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();
4112
+ const isPasswordInputPresent = async () => {
4113
+ return await page.locator('input[type="password"], input[name="password"]').count().catch(() => 0) > 0;
4044
4114
  };
4045
- try {
4046
- const pwdModeBtn = findPasswordModeButton();
4047
- if (await pwdModeBtn.isVisible({ timeout: 2e3 }).catch(() => false)) {
4048
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4115
+ const revealPasswordMode = async () => {
4116
+ if (page.url().includes("/forget")) {
4117
+ await page.goto(qwenUrl("/auth"), { waitUntil: "domcontentloaded", timeout: 1e4 }).catch(() => {
4049
4118
  });
4050
- await sleep2(500);
4119
+ await sleep2(1e3);
4051
4120
  }
4052
- } catch {
4053
- }
4054
- await page.fill(emailSelector, email);
4055
- await sleep2(300);
4056
- try {
4057
- const pwdModeBtn = findPasswordModeButton();
4058
- if (await pwdModeBtn.isVisible({ timeout: 1500 }).catch(() => false)) {
4059
- await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
4121
+ if (await isPasswordInputPresent()) return;
4122
+ const btn = page.locator("button").filter({
4123
+ 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
4124
+ }).filter({
4125
+ hasNotText: /(?:esqueci|forgot|olvid|reset|recover)/i
4126
+ }).first();
4127
+ if (await btn.isVisible({ timeout: 1500 }).catch(() => false)) {
4128
+ await btn.click({ timeout: 2e3 }).catch(() => {
4060
4129
  });
4061
4130
  await sleep2(500);
4062
4131
  }
4132
+ if (await isPasswordInputPresent()) return;
4133
+ await page.evaluate(() => {
4134
+ try {
4135
+ const buttons = Array.from(document.querySelectorAll("button, [role='button'], a"));
4136
+ for (const b of buttons) {
4137
+ const txt = (b.innerText || "").trim().toLowerCase();
4138
+ 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")) {
4139
+ b.click();
4140
+ return true;
4141
+ }
4142
+ }
4143
+ } catch {
4144
+ }
4145
+ return false;
4146
+ }).catch(() => false);
4147
+ await sleep2(500);
4148
+ };
4149
+ await revealPasswordMode();
4150
+ try {
4151
+ await page.fill(emailSelector, email, { timeout: 8e3 });
4152
+ await sleep2(300);
4063
4153
  } catch {
4154
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4155
+ return { success: true };
4156
+ }
4157
+ throw new Error(`Campo de e-mail n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4158
+ }
4159
+ if (!await isPasswordInputPresent()) {
4160
+ await revealPasswordMode();
4064
4161
  }
4065
4162
  const passwordSelector = 'input[type="password"], input[name="password"]';
4066
4163
  try {
@@ -4079,17 +4176,50 @@ async function loginViaUi(page, email, password) {
4079
4176
  reason: "Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o"
4080
4177
  };
4081
4178
  }
4082
- await page.fill(passwordSelector, password);
4083
- await sleep2(500);
4179
+ try {
4180
+ await page.fill(passwordSelector, password, { timeout: 8e3 });
4181
+ await sleep2(500);
4182
+ } catch {
4183
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
4184
+ return { success: true };
4185
+ }
4186
+ throw new Error(`Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o`);
4187
+ }
4188
+ await page.evaluate(() => {
4189
+ try {
4190
+ const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
4191
+ const passEl = document.querySelector('input[type="password"], input[name="password"]');
4192
+ if (emailEl) {
4193
+ emailEl.dispatchEvent(new Event("input", { bubbles: true }));
4194
+ emailEl.dispatchEvent(new Event("change", { bubbles: true }));
4195
+ emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
4196
+ }
4197
+ if (passEl) {
4198
+ passEl.dispatchEvent(new Event("input", { bubbles: true }));
4199
+ passEl.dispatchEvent(new Event("change", { bubbles: true }));
4200
+ passEl.dispatchEvent(new Event("blur", { bubbles: true }));
4201
+ }
4202
+ } catch {
4203
+ }
4204
+ }).catch(() => {
4205
+ });
4084
4206
  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")';
4085
4207
  const submitButton = page.locator(submitSelector).first();
4086
4208
  try {
4087
4209
  await page.waitForSelector('button[type="submit"]:not([disabled])', {
4088
- timeout: 5e3
4210
+ timeout: 4e3
4089
4211
  });
4090
4212
  await submitButton.click();
4091
4213
  } catch {
4092
- await page.keyboard.press("Enter");
4214
+ const isDisabled = await submitButton.isDisabled().catch(() => true);
4215
+ if (!isDisabled) {
4216
+ await submitButton.click({ force: true }).catch(() => {
4217
+ });
4218
+ } else {
4219
+ if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
4220
+ return { success: true };
4221
+ }
4222
+ }
4093
4223
  }
4094
4224
  const postSubmitDeadline = Date.now() + 15e3;
4095
4225
  while (Date.now() < postSubmitDeadline) {