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.
- package/dist/clean-cache.js +205 -75
- package/dist/delete-chats.js +397 -93
- package/dist/index.js +399 -95
- package/dist/login.js +345 -83
- package/dist/reset-cooldowns.js +205 -75
- package/package.json +1 -1
- package/src/core/database.ts +10 -5
- package/src/core/version.ts +1 -1
- package/src/routes/chat/retry-policy.ts +4 -3
- package/src/services/playwright.ts +408 -83
- package/src/services/qwen-chat-pool.ts +9 -0
- package/src/services/qwen.ts +43 -10
package/dist/reset-cooldowns.js
CHANGED
|
@@ -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
|
}
|
|
@@ -3523,8 +3526,29 @@ function getStealthScript(profile) {
|
|
|
3523
3526
|
const _toBlob = HTMLCanvasElement.prototype.toBlob;
|
|
3524
3527
|
const _getImageData = CanvasRenderingContext2D.prototype.getImageData;
|
|
3525
3528
|
|
|
3529
|
+
function isCaptchaCanvas(canvas) {
|
|
3530
|
+
try {
|
|
3531
|
+
if (!canvas) return false;
|
|
3532
|
+
const id = (canvas.id || "").toLowerCase();
|
|
3533
|
+
const cls = (canvas.className || "").toLowerCase();
|
|
3534
|
+
return (
|
|
3535
|
+
id.includes("baxia") ||
|
|
3536
|
+
id.includes("nc_") ||
|
|
3537
|
+
id.includes("captcha") ||
|
|
3538
|
+
id.includes("punish") ||
|
|
3539
|
+
cls.includes("baxia") ||
|
|
3540
|
+
cls.includes("nc_") ||
|
|
3541
|
+
cls.includes("captcha") ||
|
|
3542
|
+
Boolean(canvas.closest?.("#baxia-dialog, #baxia-punish, [class*='baxia'], [class*='nc_'], .auth-layout"))
|
|
3543
|
+
);
|
|
3544
|
+
} catch(e) {
|
|
3545
|
+
return false;
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
|
|
3526
3549
|
function addNoise(canvas) {
|
|
3527
3550
|
try {
|
|
3551
|
+
if (isCaptchaCanvas(canvas)) return;
|
|
3528
3552
|
const ctx = canvas.getContext('2d');
|
|
3529
3553
|
if (!ctx) return;
|
|
3530
3554
|
const style = ctx.fillStyle;
|
|
@@ -3548,6 +3572,9 @@ function getStealthScript(profile) {
|
|
|
3548
3572
|
|
|
3549
3573
|
CanvasRenderingContext2D.prototype.getImageData = function(x, y, w, h) {
|
|
3550
3574
|
const imageData = _getImageData.apply(this, arguments);
|
|
3575
|
+
if (isCaptchaCanvas(this.canvas)) {
|
|
3576
|
+
return imageData;
|
|
3577
|
+
}
|
|
3551
3578
|
const data = imageData.data;
|
|
3552
3579
|
const maxPixels = Math.min(data.length / 4, 2500);
|
|
3553
3580
|
for (let i = 0; i < maxPixels * 4; i += 4) {
|
|
@@ -4024,6 +4051,7 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4024
4051
|
secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
|
|
4025
4052
|
version: cache.headers["version"] || void 0,
|
|
4026
4053
|
tokenExpiresAt: exp || void 0,
|
|
4054
|
+
refreshToken: apiResult.refreshToken,
|
|
4027
4055
|
capturedAt: Date.now()
|
|
4028
4056
|
});
|
|
4029
4057
|
}
|
|
@@ -4057,6 +4085,13 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4057
4085
|
markAccountHeadersReady(accountId);
|
|
4058
4086
|
const { saveAuthSession: saveAuthSession2 } = await Promise.resolve().then(() => (init_database(), database_exports));
|
|
4059
4087
|
const exp = parseJwtExpiry(tokenCookie.value);
|
|
4088
|
+
let rTok = uiResult.refreshToken;
|
|
4089
|
+
if (!rTok) {
|
|
4090
|
+
try {
|
|
4091
|
+
rTok = await page.evaluate(() => localStorage.getItem("refresh_token") || "") || void 0;
|
|
4092
|
+
} catch {
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4060
4095
|
saveAuthSession2(accountId, {
|
|
4061
4096
|
cookie: cookieStr,
|
|
4062
4097
|
userAgent: cache.headers["user-agent"] || "",
|
|
@@ -4068,6 +4103,7 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4068
4103
|
secChUaPlatform: cache.headers["sec-ch-ua-platform"] || void 0,
|
|
4069
4104
|
version: cache.headers["version"] || void 0,
|
|
4070
4105
|
tokenExpiresAt: exp || void 0,
|
|
4106
|
+
refreshToken: rTok,
|
|
4071
4107
|
capturedAt: Date.now()
|
|
4072
4108
|
});
|
|
4073
4109
|
}
|
|
@@ -4120,37 +4156,52 @@ async function loginToQwen(accountId, email, password) {
|
|
|
4120
4156
|
async function loginViaApi(page, email, password) {
|
|
4121
4157
|
try {
|
|
4122
4158
|
const hashedPassword = crypto3.createHash("sha256").update(password).digest("hex");
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
email,
|
|
4131
|
-
password: hashedPassword,
|
|
4132
|
-
login_type: "email"
|
|
4133
|
-
},
|
|
4134
|
-
headers: {
|
|
4135
|
-
"content-type": "application/json",
|
|
4136
|
-
accept: "application/json, text/plain, */*",
|
|
4137
|
-
referer: qwenUrl("/auth"),
|
|
4138
|
-
origin: qwenOrigin(),
|
|
4139
|
-
source: "web"
|
|
4140
|
-
},
|
|
4141
|
-
timeout: 1e4
|
|
4142
|
-
});
|
|
4143
|
-
data = await response.json().catch(() => null);
|
|
4144
|
-
signinSuccess = Boolean(data && (data.success === true || data.token));
|
|
4145
|
-
} catch {
|
|
4146
|
-
}
|
|
4159
|
+
if (!page.url().includes("/auth")) {
|
|
4160
|
+
await page.goto(qwenUrl("/auth"), {
|
|
4161
|
+
waitUntil: "domcontentloaded",
|
|
4162
|
+
timeout: config.timeouts.navigation
|
|
4163
|
+
}).catch(() => {
|
|
4164
|
+
});
|
|
4165
|
+
await sleep2(1e3);
|
|
4147
4166
|
}
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4167
|
+
const signinUrl = "https://auth.qwen.ai/api/v2/auths/signin";
|
|
4168
|
+
const requestId = crypto3.randomUUID();
|
|
4169
|
+
const evalRes = await page.evaluate(
|
|
4170
|
+
async ({ email: email2, password: password2, signinUrl: signinUrl2, requestId: requestId2 }) => {
|
|
4171
|
+
try {
|
|
4172
|
+
const response = await fetch(signinUrl2, {
|
|
4173
|
+
method: "POST",
|
|
4174
|
+
signal: AbortSignal.timeout(15e3),
|
|
4175
|
+
credentials: "include",
|
|
4176
|
+
headers: {
|
|
4177
|
+
accept: "application/json, text/plain, */*",
|
|
4178
|
+
"content-type": "application/json",
|
|
4179
|
+
source: "web",
|
|
4180
|
+
version: "0.3.11",
|
|
4181
|
+
"x-request-origin": "https://chat.qwen.ai",
|
|
4182
|
+
timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
|
|
4183
|
+
"x-request-id": requestId2
|
|
4184
|
+
},
|
|
4185
|
+
body: JSON.stringify({ email: email2, password: password2 })
|
|
4186
|
+
});
|
|
4187
|
+
const json = await response.json().catch(() => null);
|
|
4188
|
+
return { ok: response.ok, status: response.status, data: json };
|
|
4189
|
+
} catch (e) {
|
|
4190
|
+
return { ok: false, error: e.message };
|
|
4191
|
+
}
|
|
4192
|
+
},
|
|
4193
|
+
{ email, password: hashedPassword, signinUrl, requestId }
|
|
4194
|
+
).catch(() => null);
|
|
4195
|
+
let data = evalRes?.data;
|
|
4196
|
+
let signinSuccess = Boolean(
|
|
4197
|
+
data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
|
|
4198
|
+
);
|
|
4199
|
+
if (!signinSuccess && !data?.data?.code) {
|
|
4200
|
+
const fallbackUrl = qwenUrl("/api/v2/auths/signin");
|
|
4201
|
+
const fallbackRes = await page.evaluate(
|
|
4202
|
+
async ({ email: email2, password: password2, fallbackUrl: fallbackUrl2, requestId: requestId2 }) => {
|
|
4152
4203
|
try {
|
|
4153
|
-
const response = await fetch(
|
|
4204
|
+
const response = await fetch(fallbackUrl2, {
|
|
4154
4205
|
method: "POST",
|
|
4155
4206
|
signal: AbortSignal.timeout(1e4),
|
|
4156
4207
|
credentials: "include",
|
|
@@ -4158,10 +4209,11 @@ async function loginViaApi(page, email, password) {
|
|
|
4158
4209
|
accept: "application/json, text/plain, */*",
|
|
4159
4210
|
"content-type": "application/json",
|
|
4160
4211
|
source: "web",
|
|
4212
|
+
version: "0.3.11",
|
|
4161
4213
|
timezone: (/* @__PURE__ */ new Date()).toString().split(" (")[0],
|
|
4162
4214
|
"x-request-id": requestId2
|
|
4163
4215
|
},
|
|
4164
|
-
body: JSON.stringify({ email: email2, password: password2
|
|
4216
|
+
body: JSON.stringify({ email: email2, password: password2 })
|
|
4165
4217
|
});
|
|
4166
4218
|
const json = await response.json().catch(() => null);
|
|
4167
4219
|
return { ok: response.ok, status: response.status, data: json };
|
|
@@ -4169,11 +4221,13 @@ async function loginViaApi(page, email, password) {
|
|
|
4169
4221
|
return { ok: false, error: e.message };
|
|
4170
4222
|
}
|
|
4171
4223
|
},
|
|
4172
|
-
{ email, password: hashedPassword,
|
|
4224
|
+
{ email, password: hashedPassword, fallbackUrl, requestId }
|
|
4173
4225
|
).catch(() => null);
|
|
4174
|
-
if (
|
|
4175
|
-
data =
|
|
4176
|
-
signinSuccess = Boolean(
|
|
4226
|
+
if (fallbackRes?.data) {
|
|
4227
|
+
data = fallbackRes.data;
|
|
4228
|
+
signinSuccess = Boolean(
|
|
4229
|
+
data && (data.success === true || data.token || data.data?.access_token || data.data?.token)
|
|
4230
|
+
);
|
|
4177
4231
|
}
|
|
4178
4232
|
}
|
|
4179
4233
|
if (data?.success === false) {
|
|
@@ -4187,7 +4241,8 @@ async function loginViaApi(page, email, password) {
|
|
|
4187
4241
|
};
|
|
4188
4242
|
}
|
|
4189
4243
|
if (signinSuccess) {
|
|
4190
|
-
const token = data?.data?.token || data?.token;
|
|
4244
|
+
const token = data?.data?.access_token || data?.data?.token || data?.access_token || data?.token;
|
|
4245
|
+
const refreshToken = data?.data?.refresh_token || data?.refresh_token;
|
|
4191
4246
|
if (token) {
|
|
4192
4247
|
try {
|
|
4193
4248
|
await page.context().addCookies([
|
|
@@ -4211,18 +4266,23 @@ async function loginViaApi(page, email, password) {
|
|
|
4211
4266
|
}).catch(() => {
|
|
4212
4267
|
});
|
|
4213
4268
|
if (token) {
|
|
4214
|
-
await page.evaluate(
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4269
|
+
await page.evaluate(
|
|
4270
|
+
({ tok, rTok }) => {
|
|
4271
|
+
try {
|
|
4272
|
+
localStorage.removeItem("qwen_token_logged_out_marker");
|
|
4273
|
+
localStorage.setItem("token", tok);
|
|
4274
|
+
localStorage.setItem("access_token", tok);
|
|
4275
|
+
if (rTok) localStorage.setItem("refresh_token", rTok);
|
|
4276
|
+
document.cookie = `token=${encodeURIComponent(tok)}; path=/; domain=.qwen.ai; max-age=31536000`;
|
|
4277
|
+
} catch {
|
|
4278
|
+
}
|
|
4279
|
+
},
|
|
4280
|
+
{ tok: token, rTok: refreshToken }
|
|
4281
|
+
).catch(() => {
|
|
4222
4282
|
});
|
|
4223
4283
|
await page.reload({ waitUntil: "domcontentloaded" }).catch(() => {
|
|
4224
4284
|
});
|
|
4225
|
-
return { success: true };
|
|
4285
|
+
return { success: true, token, refreshToken };
|
|
4226
4286
|
}
|
|
4227
4287
|
}
|
|
4228
4288
|
return {
|
|
@@ -4246,11 +4306,23 @@ async function loginViaApi(page, email, password) {
|
|
|
4246
4306
|
}
|
|
4247
4307
|
async function loginViaUi(page, email, password) {
|
|
4248
4308
|
try {
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4309
|
+
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3).catch(() => false)) {
|
|
4310
|
+
return { success: true };
|
|
4311
|
+
}
|
|
4312
|
+
try {
|
|
4313
|
+
await page.goto(qwenUrl("/auth"), {
|
|
4314
|
+
waitUntil: "domcontentloaded",
|
|
4315
|
+
timeout: Math.min(config.timeouts.navigation, 15e3)
|
|
4316
|
+
});
|
|
4317
|
+
await sleep2(1e3);
|
|
4318
|
+
} catch (gotoErr) {
|
|
4319
|
+
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
|
|
4320
|
+
return { success: true };
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3).catch(() => false)) {
|
|
4324
|
+
return { success: true };
|
|
4325
|
+
}
|
|
4254
4326
|
const emailSelector = [
|
|
4255
4327
|
'input[type="email"]',
|
|
4256
4328
|
'input[name="email"]',
|
|
@@ -4260,7 +4332,7 @@ async function loginViaUi(page, email, password) {
|
|
|
4260
4332
|
].join(", ");
|
|
4261
4333
|
try {
|
|
4262
4334
|
await page.waitForSelector(emailSelector, {
|
|
4263
|
-
timeout: Math.min(config.timeouts.page,
|
|
4335
|
+
timeout: Math.min(config.timeouts.page, 5e3)
|
|
4264
4336
|
});
|
|
4265
4337
|
} catch {
|
|
4266
4338
|
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
|
|
@@ -4273,30 +4345,55 @@ async function loginViaUi(page, email, password) {
|
|
|
4273
4345
|
return { success: false, reason: "Campo de e-mail n\xE3o encontrado (poss\xEDvel captcha)" };
|
|
4274
4346
|
}
|
|
4275
4347
|
await clearVisibleChallenge(page);
|
|
4276
|
-
const
|
|
4277
|
-
return page.locator('
|
|
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();
|
|
4348
|
+
const isPasswordInputPresent = async () => {
|
|
4349
|
+
return await page.locator('input[type="password"], input[name="password"]').count().catch(() => 0) > 0;
|
|
4280
4350
|
};
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
await pwdModeBtn.click({ timeout: 2e3 }).catch(() => {
|
|
4351
|
+
const revealPasswordMode = async () => {
|
|
4352
|
+
if (page.url().includes("/forget")) {
|
|
4353
|
+
await page.goto(qwenUrl("/auth"), { waitUntil: "domcontentloaded", timeout: 1e4 }).catch(() => {
|
|
4285
4354
|
});
|
|
4286
|
-
await sleep2(
|
|
4355
|
+
await sleep2(1e3);
|
|
4287
4356
|
}
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
if (await
|
|
4295
|
-
await
|
|
4357
|
+
if (await isPasswordInputPresent()) return;
|
|
4358
|
+
const btn = page.locator("button").filter({
|
|
4359
|
+
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
|
|
4360
|
+
}).filter({
|
|
4361
|
+
hasNotText: /(?:esqueci|forgot|olvid|reset|recover)/i
|
|
4362
|
+
}).first();
|
|
4363
|
+
if (await btn.isVisible({ timeout: 1500 }).catch(() => false)) {
|
|
4364
|
+
await btn.click({ timeout: 2e3 }).catch(() => {
|
|
4296
4365
|
});
|
|
4297
4366
|
await sleep2(500);
|
|
4298
4367
|
}
|
|
4368
|
+
if (await isPasswordInputPresent()) return;
|
|
4369
|
+
await page.evaluate(() => {
|
|
4370
|
+
try {
|
|
4371
|
+
const buttons = Array.from(document.querySelectorAll("button, [role='button'], a"));
|
|
4372
|
+
for (const b of buttons) {
|
|
4373
|
+
const txt = (b.innerText || "").trim().toLowerCase();
|
|
4374
|
+
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")) {
|
|
4375
|
+
b.click();
|
|
4376
|
+
return true;
|
|
4377
|
+
}
|
|
4378
|
+
}
|
|
4379
|
+
} catch {
|
|
4380
|
+
}
|
|
4381
|
+
return false;
|
|
4382
|
+
}).catch(() => false);
|
|
4383
|
+
await sleep2(500);
|
|
4384
|
+
};
|
|
4385
|
+
await revealPasswordMode();
|
|
4386
|
+
try {
|
|
4387
|
+
await page.fill(emailSelector, email, { timeout: 8e3 });
|
|
4388
|
+
await sleep2(300);
|
|
4299
4389
|
} catch {
|
|
4390
|
+
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
|
|
4391
|
+
return { success: true };
|
|
4392
|
+
}
|
|
4393
|
+
throw new Error(`Campo de e-mail n\xE3o encontrado na tela de autentica\xE7\xE3o`);
|
|
4394
|
+
}
|
|
4395
|
+
if (!await isPasswordInputPresent()) {
|
|
4396
|
+
await revealPasswordMode();
|
|
4300
4397
|
}
|
|
4301
4398
|
const passwordSelector = 'input[type="password"], input[name="password"]';
|
|
4302
4399
|
try {
|
|
@@ -4315,17 +4412,50 @@ async function loginViaUi(page, email, password) {
|
|
|
4315
4412
|
reason: "Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o"
|
|
4316
4413
|
};
|
|
4317
4414
|
}
|
|
4318
|
-
|
|
4319
|
-
|
|
4415
|
+
try {
|
|
4416
|
+
await page.fill(passwordSelector, password, { timeout: 8e3 });
|
|
4417
|
+
await sleep2(500);
|
|
4418
|
+
} catch {
|
|
4419
|
+
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 3e3)) {
|
|
4420
|
+
return { success: true };
|
|
4421
|
+
}
|
|
4422
|
+
throw new Error(`Campo de senha n\xE3o encontrado na tela de autentica\xE7\xE3o`);
|
|
4423
|
+
}
|
|
4424
|
+
await page.evaluate(() => {
|
|
4425
|
+
try {
|
|
4426
|
+
const emailEl = document.querySelector('input[name="email"], input[type="email"], input[type="text"]');
|
|
4427
|
+
const passEl = document.querySelector('input[type="password"], input[name="password"]');
|
|
4428
|
+
if (emailEl) {
|
|
4429
|
+
emailEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4430
|
+
emailEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4431
|
+
emailEl.dispatchEvent(new Event("blur", { bubbles: true }));
|
|
4432
|
+
}
|
|
4433
|
+
if (passEl) {
|
|
4434
|
+
passEl.dispatchEvent(new Event("input", { bubbles: true }));
|
|
4435
|
+
passEl.dispatchEvent(new Event("change", { bubbles: true }));
|
|
4436
|
+
passEl.dispatchEvent(new Event("blur", { bubbles: true }));
|
|
4437
|
+
}
|
|
4438
|
+
} catch {
|
|
4439
|
+
}
|
|
4440
|
+
}).catch(() => {
|
|
4441
|
+
});
|
|
4320
4442
|
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")';
|
|
4321
4443
|
const submitButton = page.locator(submitSelector).first();
|
|
4322
4444
|
try {
|
|
4323
4445
|
await page.waitForSelector('button[type="submit"]:not([disabled])', {
|
|
4324
|
-
timeout:
|
|
4446
|
+
timeout: 4e3
|
|
4325
4447
|
});
|
|
4326
4448
|
await submitButton.click();
|
|
4327
4449
|
} catch {
|
|
4328
|
-
await
|
|
4450
|
+
const isDisabled = await submitButton.isDisabled().catch(() => true);
|
|
4451
|
+
if (!isDisabled) {
|
|
4452
|
+
await submitButton.click({ force: true }).catch(() => {
|
|
4453
|
+
});
|
|
4454
|
+
} else {
|
|
4455
|
+
if (!page.url().includes("/auth") && await isPageLoggedIn(page, 2e3)) {
|
|
4456
|
+
return { success: true };
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4329
4459
|
}
|
|
4330
4460
|
const postSubmitDeadline = Date.now() + 15e3;
|
|
4331
4461
|
while (Date.now() < postSubmitDeadline) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qwenproxy-cli",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.8",
|
|
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/database.ts
CHANGED
|
@@ -193,6 +193,7 @@ function runMigrations(db: Database.Database): void {
|
|
|
193
193
|
"sec_ch_ua_mobile TEXT",
|
|
194
194
|
"sec_ch_ua_platform TEXT",
|
|
195
195
|
"version TEXT",
|
|
196
|
+
"refresh_token TEXT",
|
|
196
197
|
"captured_at INTEGER DEFAULT 0",
|
|
197
198
|
];
|
|
198
199
|
for (const col of authSessionCols) {
|
|
@@ -309,6 +310,7 @@ export interface PersistedAuthSession {
|
|
|
309
310
|
version?: string;
|
|
310
311
|
userId?: string;
|
|
311
312
|
tokenExpiresAt?: number;
|
|
313
|
+
refreshToken?: string;
|
|
312
314
|
capturedAt: number;
|
|
313
315
|
}
|
|
314
316
|
|
|
@@ -326,6 +328,7 @@ export function saveAuthSession(
|
|
|
326
328
|
version?: string;
|
|
327
329
|
userId?: string;
|
|
328
330
|
tokenExpiresAt?: number;
|
|
331
|
+
refreshToken?: string;
|
|
329
332
|
capturedAt?: number;
|
|
330
333
|
},
|
|
331
334
|
): void {
|
|
@@ -334,11 +337,11 @@ export function saveAuthSession(
|
|
|
334
337
|
INSERT OR REPLACE INTO qwen_auth_sessions (
|
|
335
338
|
account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
|
|
336
339
|
sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
|
|
337
|
-
user_id, token_expires_at, captured_at, updated_at
|
|
340
|
+
user_id, token_expires_at, refresh_token, captured_at, updated_at
|
|
338
341
|
) VALUES (
|
|
339
342
|
?, ?, ?, ?, ?, ?,
|
|
340
343
|
?, ?, ?, ?,
|
|
341
|
-
?, ?, ?, datetime('now')
|
|
344
|
+
?, ?, ?, ?, datetime('now')
|
|
342
345
|
)
|
|
343
346
|
`);
|
|
344
347
|
stmt.run(
|
|
@@ -354,6 +357,7 @@ export function saveAuthSession(
|
|
|
354
357
|
session.version || null,
|
|
355
358
|
session.userId || null,
|
|
356
359
|
session.tokenExpiresAt || null,
|
|
360
|
+
session.refreshToken || null,
|
|
357
361
|
session.capturedAt ?? Date.now(),
|
|
358
362
|
);
|
|
359
363
|
}
|
|
@@ -367,7 +371,7 @@ export function getValidAuthSession(
|
|
|
367
371
|
.prepare(
|
|
368
372
|
`SELECT account_id, cookie, user_agent, bx_v, bx_ua, bx_umidtoken,
|
|
369
373
|
sec_ch_ua, sec_ch_ua_mobile, sec_ch_ua_platform, version,
|
|
370
|
-
user_id, token_expires_at, captured_at
|
|
374
|
+
user_id, token_expires_at, refresh_token, captured_at
|
|
371
375
|
FROM qwen_auth_sessions WHERE account_id = ?`,
|
|
372
376
|
)
|
|
373
377
|
.get(accountId) as any;
|
|
@@ -379,9 +383,9 @@ export function getValidAuthSession(
|
|
|
379
383
|
return null;
|
|
380
384
|
}
|
|
381
385
|
|
|
382
|
-
if (row.token_expires_at) {
|
|
386
|
+
if (!row.refresh_token && row.token_expires_at) {
|
|
383
387
|
const tokenExpMs = Number(row.token_expires_at) * 1000;
|
|
384
|
-
// Safety margin of 5 minutes before token expires
|
|
388
|
+
// Safety margin of 5 minutes before token expires (only when no refresh_token exists)
|
|
385
389
|
if (tokenExpMs <= Date.now() + 5 * 60 * 1000) {
|
|
386
390
|
return null;
|
|
387
391
|
}
|
|
@@ -405,6 +409,7 @@ export function getValidAuthSession(
|
|
|
405
409
|
version: row.version || undefined,
|
|
406
410
|
userId: row.user_id || undefined,
|
|
407
411
|
tokenExpiresAt: row.token_expires_at ? Number(row.token_expires_at) : undefined,
|
|
412
|
+
refreshToken: row.refresh_token || undefined,
|
|
408
413
|
capturedAt,
|
|
409
414
|
};
|
|
410
415
|
}
|
package/src/core/version.ts
CHANGED
|
@@ -479,8 +479,8 @@ export function classifyRetryAction(
|
|
|
479
479
|
// Agent instructions ride ONLY the account-level personalization. An
|
|
480
480
|
// unconfirmed sync means this account cannot serve the request as-is —
|
|
481
481
|
// rotate to another account (each attempt re-syncs on its own account).
|
|
482
|
-
//
|
|
483
|
-
//
|
|
482
|
+
// Do NOT park the account with a 300s cooldown: rotation across accounts
|
|
483
|
+
// is already guarded by triedAccounts, preventing pool collapse.
|
|
484
484
|
if (err instanceof PersonalizationSyncError) {
|
|
485
485
|
return makeRetryAction("personalization_sync_failed", {
|
|
486
486
|
switchAccount: true,
|
|
@@ -502,7 +502,8 @@ export function classifyRetryAction(
|
|
|
502
502
|
message.includes("não tem permissão para acessar") ||
|
|
503
503
|
message.includes("401 unauthorized") ||
|
|
504
504
|
message.includes('"code":"unauthorized"') ||
|
|
505
|
-
message.includes('"code": "unauthorized"')
|
|
505
|
+
message.includes('"code": "unauthorized"') ||
|
|
506
|
+
message.includes("token has expired")
|
|
506
507
|
) {
|
|
507
508
|
return makeRetryAction("account_initialization_failed", {
|
|
508
509
|
switchAccount: true,
|