qwenproxy-cli 1.2.7 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwenproxy-cli",
3
- "version": "1.2.7",
3
+ "version": "1.2.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": {
@@ -23,12 +23,30 @@ function parseEnvAccounts(): QwenAccount[] {
23
23
  const envAccounts = process.env.QWEN_ACCOUNTS;
24
24
  if (!envAccounts) return [];
25
25
 
26
- const separator = envAccounts.includes(";") ? ";" : ",";
26
+ const clean = (s: string) => s.trim().replace(/^[,;\s"'`]+|[,;\s"'`]+$/g, "").trim();
27
27
 
28
- return envAccounts
29
- .split(separator)
28
+ const lines = envAccounts.split(/[\r\n;]+/);
29
+ const rawEntries: string[] = [];
30
+
31
+ for (const rawLine of lines) {
32
+ const line = clean(rawLine);
33
+ if (!line || line.startsWith("#") || line.startsWith("//")) continue;
34
+
35
+ // Check if line contains multiple accounts separated by comma (e.g. "a@b.com:p1, c@d.com:p2")
36
+ if (line.includes(",") && (line.match(/@/g) || []).length > 1) {
37
+ for (const seg of line.split(",")) {
38
+ const trimmed = clean(seg);
39
+ if (trimmed) rawEntries.push(trimmed);
40
+ }
41
+ } else {
42
+ const trimmed = clean(line);
43
+ if (trimmed) rawEntries.push(trimmed);
44
+ }
45
+ }
46
+
47
+ return rawEntries
30
48
  .map((entry, index) => {
31
- const trimmed = entry.trim();
49
+ const trimmed = clean(entry);
32
50
  const colonIdx = trimmed.indexOf(":");
33
51
  if (colonIdx === -1) {
34
52
  console.warn(
@@ -36,8 +54,8 @@ function parseEnvAccounts(): QwenAccount[] {
36
54
  );
37
55
  return null;
38
56
  }
39
- const email = trimmed.substring(0, colonIdx);
40
- const password = trimmed.substring(colonIdx + 1);
57
+ const email = clean(trimmed.substring(0, colonIdx));
58
+ const password = clean(trimmed.substring(colonIdx + 1));
41
59
  if (!email || !password) {
42
60
  console.warn(
43
61
  `[Accounts] Invalid QWEN_ACCOUNTS entry at index ${index}: "${trimmed}"`,
@@ -46,8 +64,8 @@ function parseEnvAccounts(): QwenAccount[] {
46
64
  }
47
65
  return {
48
66
  id: generateId(email),
49
- email: email.trim(),
50
- password: password.trim(),
67
+ email,
68
+ password,
51
69
  };
52
70
  })
53
71
  .filter((a): a is QwenAccount => a !== null);
@@ -160,26 +178,27 @@ export function parseBatchAccounts(rawInput: string): {
160
178
  let email = "";
161
179
  let password = "";
162
180
 
181
+ const clean = (s: string) => s.trim().replace(/^[,;\s"'`]+|[,;\s"'`]+$/g, "").trim();
163
182
  if (seg.includes("---")) {
164
183
  const parts = seg.split("---");
165
- email = parts[0].trim();
166
- password = parts.slice(1).join("---").trim();
184
+ email = clean(parts[0]);
185
+ password = clean(parts.slice(1).join("---"));
167
186
  } else if (seg.includes("\t")) {
168
187
  const parts = seg.split("\t");
169
- email = parts[0].trim();
170
- password = parts.slice(1).join("\t").trim();
188
+ email = clean(parts[0]);
189
+ password = clean(parts.slice(1).join("\t"));
171
190
  } else if (seg.includes(" | ")) {
172
191
  const parts = seg.split(" | ");
173
- email = parts[0].trim();
174
- password = parts.slice(1).join(" | ").trim();
192
+ email = clean(parts[0]);
193
+ password = clean(parts.slice(1).join(" | "));
175
194
  } else if (seg.includes(":")) {
176
195
  const colonIdx = seg.indexOf(":");
177
- email = seg.slice(0, colonIdx).trim();
178
- password = seg.slice(colonIdx + 1).trim();
196
+ email = clean(seg.slice(0, colonIdx));
197
+ password = clean(seg.slice(colonIdx + 1));
179
198
  } else if (seg.includes(",")) {
180
199
  const commaIdx = seg.indexOf(",");
181
- email = seg.slice(0, commaIdx).trim();
182
- password = seg.slice(commaIdx + 1).trim();
200
+ email = clean(seg.slice(0, commaIdx));
201
+ password = clean(seg.slice(commaIdx + 1));
183
202
  } else {
184
203
  invalid.push(seg);
185
204
  continue;
@@ -80,9 +80,11 @@ export function recordServerLog(level: "INFO" | "WARN" | "ERROR", text: string):
80
80
  .replace(/([\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2300}-\u{23FF}]\uFE0F?)\s{2,}/gu, "$1 ");
81
81
  if (!line) continue;
82
82
 
83
- // Prevent identical consecutive duplicate logs in the same second
84
- const last = logHistory[logHistory.length - 1];
85
- if (last && last.time === time && last.level === level && last.message === line) {
83
+ // Prevent identical duplicate logs within the same second window
84
+ const isDuplicate = logHistory
85
+ .slice(-10)
86
+ .some((entry) => entry.time === time && entry.level === level && entry.message === line);
87
+ if (isDuplicate) {
86
88
  continue;
87
89
  }
88
90
 
@@ -352,10 +352,7 @@ export async function isPageLoggedIn(
352
352
  const res = await fetch("/api/v1/auths/", { method: "GET" });
353
353
  return res.status === 200;
354
354
  } catch {
355
- const btn = document.querySelector(
356
- ".header-right-auth-button, button.header-right-auth-button, a[href*='/auth'], a[href*='/login']",
357
- );
358
- return !btn || (btn as HTMLElement).offsetWidth === 0;
355
+ return false;
359
356
  }
360
357
  })
361
358
  .catch(() => false);
@@ -1844,8 +1841,9 @@ async function loginViaApi(
1844
1841
  .digest("hex");
1845
1842
  const signinUrl = qwenUrl("/api/v2/auths/signin");
1846
1843
 
1844
+ const requestId = crypto.randomUUID();
1847
1845
  const result = await page.evaluate(
1848
- async ({ email, password, signinUrl }) => {
1846
+ async ({ email, password, signinUrl, requestId }) => {
1849
1847
  try {
1850
1848
  const response = await fetch(signinUrl, {
1851
1849
  method: "POST",
@@ -1855,7 +1853,7 @@ async function loginViaApi(
1855
1853
  "content-type": "application/json",
1856
1854
  source: "web",
1857
1855
  timezone: new Date().toString().split(" (")[0],
1858
- "x-request-id": crypto.randomUUID(),
1856
+ "x-request-id": requestId,
1859
1857
  },
1860
1858
  body: JSON.stringify({ email, password, login_type: "email" }),
1861
1859
  });
@@ -1865,7 +1863,7 @@ async function loginViaApi(
1865
1863
  return { ok: false, error: e.message };
1866
1864
  }
1867
1865
  },
1868
- { email, password: hashedPassword, signinUrl },
1866
+ { email, password: hashedPassword, signinUrl, requestId },
1869
1867
  );
1870
1868
 
1871
1869
  if (result.data) {
@@ -1793,8 +1793,9 @@ export async function disableNativeTools(accountId?: string): Promise<void> {
1793
1793
  const result = await withAccountPage(
1794
1794
  accountId,
1795
1795
  async (page) => {
1796
+ const requestId = crypto.randomUUID();
1796
1797
  const response = await page.evaluate(
1797
- async ({ payload, timeoutMs }: { payload: any; timeoutMs: number }) => {
1798
+ async ({ payload, timeoutMs, requestId }: { payload: any; timeoutMs: number; requestId: string }) => {
1798
1799
  const controller = new AbortController();
1799
1800
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
1800
1801
  try {
@@ -1805,7 +1806,7 @@ export async function disableNativeTools(accountId?: string): Promise<void> {
1805
1806
  headers: {
1806
1807
  accept: "application/json, text/plain, */*",
1807
1808
  "content-type": "application/json",
1808
- "x-request-id": crypto.randomUUID(),
1809
+ "x-request-id": requestId,
1809
1810
  timezone: new Date().toString().split(" (")[0],
1810
1811
  source: "web",
1811
1812
  },
@@ -1818,7 +1819,7 @@ export async function disableNativeTools(accountId?: string): Promise<void> {
1818
1819
  clearTimeout(timeoutId);
1819
1820
  }
1820
1821
  },
1821
- { payload, timeoutMs: config.timeouts.http },
1822
+ { payload, timeoutMs: config.timeouts.http, requestId },
1822
1823
  );
1823
1824
  return response;
1824
1825
  },
@@ -2152,25 +2153,29 @@ export async function fetchQwenModels(
2152
2153
  const result = await withAccountPage(
2153
2154
  accountId,
2154
2155
  async (page) => {
2155
- const response = await page.evaluate(async (timeoutMs: number) => {
2156
- const controller = new AbortController();
2157
- const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2158
- try {
2159
- const resp = await fetch("https://chat.qwen.ai/api/models", {
2160
- method: "GET",
2161
- headers: {
2162
- accept: "application/json, text/plain, */*",
2163
- "x-request-id": crypto.randomUUID(),
2164
- timezone: new Date().toString().split(" (")[0],
2165
- source: "web",
2166
- },
2167
- signal: controller.signal,
2168
- });
2169
- return { status: resp.status, body: await resp.text() };
2170
- } finally {
2171
- clearTimeout(timeoutId);
2172
- }
2173
- }, config.timeouts.http);
2156
+ const requestId = crypto.randomUUID();
2157
+ const response = await page.evaluate(
2158
+ async ({ timeoutMs, requestId }: { timeoutMs: number; requestId: string }) => {
2159
+ const controller = new AbortController();
2160
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
2161
+ try {
2162
+ const resp = await fetch("https://chat.qwen.ai/api/models", {
2163
+ method: "GET",
2164
+ headers: {
2165
+ accept: "application/json, text/plain, */*",
2166
+ "x-request-id": requestId,
2167
+ timezone: new Date().toString().split(" (")[0],
2168
+ source: "web",
2169
+ },
2170
+ signal: controller.signal,
2171
+ });
2172
+ return { status: resp.status, body: await resp.text() };
2173
+ } finally {
2174
+ clearTimeout(timeoutId);
2175
+ }
2176
+ },
2177
+ { timeoutMs: config.timeouts.http, requestId },
2178
+ );
2174
2179
  return response;
2175
2180
  },
2176
2181
  );