qwen-api-proxy 1.0.13 → 1.0.15

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/README.md CHANGED
@@ -311,7 +311,7 @@ docker run -d \
311
311
  -v $(pwd)/logs:/app/logs \
312
312
  -v $(pwd)/uploads:/app/uploads \
313
313
  -v $(pwd)/temp:/app/temp \
314
- endykaufman/qwen-api-proxy:1.0.13
314
+ endykaufman/qwen-api-proxy:1.0.15
315
315
 
316
316
  # 3. Смотрим логи
317
317
  docker logs -f qwen-proxy
@@ -320,7 +320,7 @@ docker logs -f qwen-proxy
320
320
  ### Доступные теги
321
321
 
322
322
  - `latest` - последняя стабильная версия
323
- - `1.0.13` - текущая версия
323
+ - `1.0.15` - текущая версия
324
324
  - `1.0.x` - предыдущие версии
325
325
 
326
326
  > **💡 Важно:** Перед первым запуском добавьте аккаунт через `npm run auth` или загрузите сессию через Telegram бота.
@@ -341,7 +341,7 @@ docker logs -f qwen-proxy
341
341
  ```yaml
342
342
  services:
343
343
  qwen-proxy:
344
- image: endykaufman/qwen-api-proxy:1.0.13
344
+ image: endykaufman/qwen-api-proxy:1.0.15
345
345
  container_name: qwen-proxy
346
346
  env_file:
347
347
  - .env
@@ -396,7 +396,7 @@ docker run -d \
396
396
  -v $(pwd)/logs:/app/logs \
397
397
  -v $(pwd)/uploads:/app/uploads \
398
398
  -v $(pwd)/temp:/app/temp \
399
- endykaufman/qwen-api-proxy:1.0.13
399
+ endykaufman/qwen-api-proxy:1.0.15
400
400
  ```
401
401
 
402
402
  Файл `docker-compose.yml`:
@@ -405,7 +405,7 @@ docker run -d \
405
405
  services:
406
406
  qwen-proxy:
407
407
  build: .
408
- image: endykaufman/qwen-api-proxy:1.0.13
408
+ image: endykaufman/qwen-api-proxy:1.0.15
409
409
  container_name: qwen-proxy
410
410
  env_file:
411
411
  - .env # Автоматическая загрузка переменных
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwen-api-proxy",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "Proxy server for accessing Qwen API through browser emulation with OpenAI-compatible API and Telegram bot",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -27,6 +27,60 @@ export function hasCookies(accountId) {
27
27
  return fs.existsSync(cookiesPath);
28
28
  }
29
29
 
30
+ /**
31
+ * Извлекает JWT токен из cookies.json
32
+ * @param {string} accountId - ID аккаунта
33
+ * @returns {string|null} - JWT токен или null
34
+ */
35
+ function extractTokenFromCookies(accountId) {
36
+ try {
37
+ const cookiesPath = path.join(ACCOUNTS_PATH, accountId, 'cookies.json');
38
+ if (!fs.existsSync(cookiesPath)) {return null;}
39
+
40
+ const cookies = JSON.parse(fs.readFileSync(cookiesPath, 'utf8'));
41
+
42
+ // Ищем cookie с именем 'token'
43
+ const tokenCookie = cookies.find((cookie) => cookie.name === 'token');
44
+
45
+ if (tokenCookie && tokenCookie.value) {
46
+ return tokenCookie.value;
47
+ }
48
+
49
+ return null;
50
+ } catch (error) {
51
+ // Тихо возвращаем null - это не критичная операция
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Восстанавливает token.txt из cookies.json если token.txt отсутствует
58
+ * @param {string} accountId - ID аккаунта
59
+ * @returns {boolean} - true если токен был восстановлен
60
+ */
61
+ function restoreTokenFromFile(accountId) {
62
+ const tokenPath = path.join(ACCOUNTS_PATH, accountId, 'token.txt');
63
+
64
+ // Если token.txt уже существует, ничего не делаем
65
+ if (fs.existsSync(tokenPath)) {return false;}
66
+
67
+ // Пытаемся извлечь токен из cookies.json
68
+ const token = extractTokenFromCookies(accountId);
69
+
70
+ if (token) {
71
+ try {
72
+ fs.writeFileSync(tokenPath, token, 'utf8');
73
+ logInfo(`✅ ${accountId}: Токен восстановлен из cookies.json`);
74
+ return true;
75
+ } catch (error) {
76
+ logWarn(`⚠️ ${accountId}: Не удалось создать token.txt: ${error.message}`);
77
+ return false;
78
+ }
79
+ }
80
+
81
+ return false;
82
+ }
83
+
30
84
  /**
31
85
  * Декодирует JWT токен и извлекает время истечения
32
86
  * @param {string} token - JWT токен
@@ -69,6 +123,13 @@ export function loadTokens() {
69
123
  try {
70
124
  const tokens = JSON.parse(fs.readFileSync(TOKENS_FILE, 'utf8'));
71
125
 
126
+ // Автоматически восстанавливаем token.txt из cookies.json если нужно
127
+ tokens.forEach((token) => {
128
+ if (token.id) {
129
+ restoreTokenFromFile(token.id);
130
+ }
131
+ });
132
+
72
133
  // Добавляем expiryTime для каждого токена, если его нет
73
134
  return tokens.map((token) => {
74
135
  if (!token.expiryTime && token.token) {
@@ -1567,27 +1567,55 @@ async function extractZip(zipPath, sessionPath, chatId) {
1567
1567
  const zip = new AdmZip(zipPath);
1568
1568
  const zipEntries = zip.getEntries();
1569
1569
 
1570
+ // Нормализуем пути: заменяем обратные слеши на прямые (Windows -> Unix)
1571
+ const normalizedEntries = zipEntries.map((entry) => ({
1572
+ ...entry,
1573
+ normalizedPath: entry.entryName.replace(/\\/g, '/')
1574
+ }));
1575
+
1570
1576
  // Для отладки: показываем первые несколько записей в архиве
1571
- const firstEntries = zipEntries.slice(0, 20).map((e) => e.entryName);
1577
+ const firstEntries = normalizedEntries.slice(0, 20).map((e) => e.normalizedPath);
1572
1578
  logInfo('📂 Первые 20 записей в ZIP архиве:');
1573
1579
  logInfo(firstEntries.join('\n'));
1574
1580
 
1575
- // Проверяем что есть папка session
1576
- const hasSessionFolder = zipEntries.some((entry) =>
1577
- entry.entryName.startsWith('session/') || entry.entryName === 'session'
1578
- );
1581
+ // Проверяем что есть папка session (с нормализованными путями)
1582
+ // Поддерживаем разные варианты:
1583
+ // 1. "session/" - папка session в корне
1584
+ // 2. "session" - ровно папка session (без слеша)
1585
+ // 3. "session/accounts/..." - содержимое session
1586
+ const hasSessionFolder = normalizedEntries.some((entry) => {
1587
+ const p = entry.normalizedPath;
1588
+ // Точное совпадение "session" или "session/"
1589
+ if (p === 'session' || p === 'session/') {return true;}
1590
+ // Начинается с "session/"
1591
+ if (p.startsWith('session/')) {return true;}
1592
+ // Для ZIP созданных на Windows может быть "session\" который уже нормализован
1593
+ return false;
1594
+ });
1595
+
1596
+ if (hasSessionFolder) {
1597
+ logInfo('✅ Найдена папка session в корне архива');
1598
+ // Логируем пример найденного пути для отладки
1599
+ const sampleEntry = normalizedEntries.find((e) => {
1600
+ const p = e.normalizedPath;
1601
+ return p === 'session' || p === 'session/' || p.startsWith('session/');
1602
+ });
1603
+ if (sampleEntry) {
1604
+ logInfo(`📂 Пример: ${sampleEntry.normalizedPath}`);
1605
+ }
1606
+ }
1579
1607
 
1580
1608
  if (!hasSessionFolder) {
1581
1609
  // Пытаемся найти session в любом месте архива
1582
- const sessionEntries = zipEntries.filter((e) =>
1583
- e.entryName.includes('session') || e.entryName.includes('Session')
1610
+ const sessionEntries = normalizedEntries.filter((e) =>
1611
+ e.normalizedPath.includes('session') || e.normalizedPath.includes('Session')
1584
1612
  );
1585
1613
 
1586
1614
  if (sessionEntries.length > 0) {
1587
1615
  logInfo('🔍 Найдены записи содержащие \'session\':');
1588
- logInfo(sessionEntries.slice(0, 10).map((e) => e.entryName).join('\n'));
1616
+ logInfo(sessionEntries.slice(0, 10).map((e) => e.normalizedPath).join('\n'));
1589
1617
  reject(new Error(
1590
- `Архив содержит '${sessionEntries[0].entryName}', но не содержит 'session/' в корне. ` +
1618
+ `Архив содержит '${sessionEntries[0].normalizedPath}', но не содержит 'session/' в корне. ` +
1591
1619
  'Переместите папку session/ в корень архива'
1592
1620
  ));
1593
1621
  } else {
@@ -1596,8 +1624,6 @@ async function extractZip(zipPath, sessionPath, chatId) {
1596
1624
  return;
1597
1625
  }
1598
1626
 
1599
- logInfo('✅ Найдена папка session в корне архива');
1600
-
1601
1627
  // Создаем папку session если не существует
1602
1628
  if (!fs.existsSync(sessionPath)) {
1603
1629
  fs.mkdirSync(sessionPath, { recursive: true });
@@ -1607,10 +1633,10 @@ async function extractZip(zipPath, sessionPath, chatId) {
1607
1633
  let successCount = 0;
1608
1634
  let errorCount = 0;
1609
1635
 
1610
- zipEntries.forEach((entry) => {
1611
- if (entry.entryName.startsWith('session/')) {
1636
+ normalizedEntries.forEach((entry) => {
1637
+ if (entry.normalizedPath.startsWith('session/')) {
1612
1638
  try {
1613
- const relativePath = entry.entryName.substring('session/'.length);
1639
+ const relativePath = entry.normalizedPath.substring('session/'.length);
1614
1640
  if (relativePath) {
1615
1641
  const targetPath = path.join(sessionPath, relativePath);
1616
1642
 
@@ -1631,7 +1657,7 @@ async function extractZip(zipPath, sessionPath, chatId) {
1631
1657
  }
1632
1658
  } catch (err) {
1633
1659
  errorCount++;
1634
- logWarn(`⚠️ Ошибка распаковки ${entry.entryName}: ${err.message}`);
1660
+ logWarn(`⚠️ Ошибка распаковки ${entry.normalizedPath}: ${err.message}`);
1635
1661
  }
1636
1662
  }
1637
1663
  });