qwen-alpha 1.0.19 → 1.0.20

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/.eslintrc.js CHANGED
@@ -10,12 +10,12 @@ module.exports = {
10
10
  },
11
11
  rules: {
12
12
  'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
13
- 'semi': ['error', 'always'],
14
- 'quotes': ['warn', 'single'],
13
+ semi: ['error', 'always'],
14
+ quotes: ['warn', 'single'],
15
15
  'no-console': 'off',
16
16
  'no-eval': 'error',
17
- 'eqeqeq': ['error', 'always'],
18
- 'curly': ['error', 'all'],
17
+ eqeqeq: ['error', 'always'],
18
+ curly: ['error', 'all'],
19
19
  },
20
20
  ignorePatterns: ['node_modules/', 'logs/', '*.log'],
21
21
  };
package/PUBLISH.md CHANGED
@@ -177,6 +177,7 @@ npm-views qwen-alpha
177
177
  ### Перед публикацией проверьте:
178
178
 
179
179
  1. **Нет ли .env файлов в репозитории**
180
+
180
181
  ```bash
181
182
  git ls-files | grep .env
182
183
  ```
package/README.md CHANGED
@@ -41,25 +41,25 @@ qwen-alpha --token $BOT_TOKEN
41
41
 
42
42
  ### Для пользователей
43
43
 
44
- | Функция | Описание |
45
- |---------|----------|
46
- | **Code Review** | Анализ кода, поиск багов и уязвимостей |
47
- | **Генерация кода** | Создание кода по описанию |
48
- | **Объяснение кода** | Подробное объяснение сложных участков |
49
- | **Рефакторинг** | Улучшение читаемости и производительности |
50
- | **Работа с файлами** | Анализ файлов с кодом (до 2MB) |
51
- | **Контекст диалога** | Бот помнит историю обсуждения |
44
+ | Функция | Описание |
45
+ | -------------------- | ----------------------------------------- |
46
+ | **Code Review** | Анализ кода, поиск багов и уязвимостей |
47
+ | **Генерация кода** | Создание кода по описанию |
48
+ | **Объяснение кода** | Подробное объяснение сложных участков |
49
+ | **Рефакторинг** | Улучшение читаемости и производительности |
50
+ | **Работа с файлами** | Анализ файлов с кодом (до 2MB) |
51
+ | **Контекст диалога** | Бот помнит историю обсуждения |
52
52
 
53
53
  ### Команды бота
54
54
 
55
- | Команда | Описание |
56
- |---------|----------|
57
- | `/start` | Запуск бота и приветствие |
58
- | `/help` | Список команд и информация |
59
- | `/reset` | Сброс текущей сессии |
60
- | `/stats` | Статистика пользователя |
61
- | `/settings` | Настройки бота |
62
- | `/admin` | Панель администратора |
55
+ | Команда | Описание |
56
+ | ----------- | -------------------------- |
57
+ | `/start` | Запуск бота и приветствие |
58
+ | `/help` | Список команд и информация |
59
+ | `/reset` | Сброс текущей сессии |
60
+ | `/stats` | Статистика пользователя |
61
+ | `/settings` | Настройки бота |
62
+ | `/admin` | Панель администратора |
63
63
 
64
64
  ### Для групповых чатов
65
65
 
@@ -78,12 +78,12 @@ qwen-alpha --token $BOT_TOKEN
78
78
  qwen-alpha --token <TOKEN> [опции]
79
79
  ```
80
80
 
81
- | Опция | Описание | По умолчанию |
82
- |-------|----------|--------------|
83
- | `--token <TOKEN>` | Telegram Bot API токен | **Обязательно** |
84
- | `--log-level <LEVEL>` | Уровень логирования | `info` |
85
- | `--allowed-users <IDS>` | Whitelist user_id | все |
86
- | `--init-only` | Только инициализация хранилищ | `false` |
81
+ | Опция | Описание | По умолчанию |
82
+ | ----------------------- | ----------------------------- | --------------- |
83
+ | `--token <TOKEN>` | Telegram Bot API токен | **Обязательно** |
84
+ | `--log-level <LEVEL>` | Уровень логирования | `info` |
85
+ | `--allowed-users <IDS>` | Whitelist user_id | все |
86
+ | `--init-only` | Только инициализация хранилищ | `false` |
87
87
 
88
88
  ### Переменные окружения
89
89
 
@@ -151,12 +151,12 @@ RATE_LIMIT_MAX=10
151
151
 
152
152
  ### Настройки бота
153
153
 
154
- | Настройка | По умолчанию | Описание |
155
- |-----------|--------------|----------|
156
- | `session_timeout_hours` | 24 | Срок жизни сессии (часы) |
157
- | `max_file_size_mb` | 2 | Макс. размер файла (MB) |
158
- | `requests_per_user_per_hour` | 60 | Лимит запросов/час |
159
- | `group_mode` | mention | Режим в группах |
154
+ | Настройка | По умолчанию | Описание |
155
+ | ---------------------------- | ------------ | ------------------------ |
156
+ | `session_timeout_hours` | 24 | Срок жизни сессии (часы) |
157
+ | `max_file_size_mb` | 2 | Макс. размер файла (MB) |
158
+ | `requests_per_user_per_hour` | 60 | Лимит запросов/час |
159
+ | `group_mode` | mention | Режим в группах |
160
160
 
161
161
  ---
162
162
 
package/bin/qwen-alpha.js CHANGED
@@ -11,11 +11,13 @@ const { logger } = require('../src/utils/logger');
11
11
  * @returns {number[]} Массив user_id
12
12
  */
13
13
  function parseUserList(value) {
14
- if (!value) return [];
14
+ if (!value) {
15
+ return [];
16
+ }
15
17
  return value
16
18
  .split(',')
17
- .map(id => parseInt(id.trim(), 10))
18
- .filter(id => !isNaN(id));
19
+ .map((id) => parseInt(id.trim(), 10))
20
+ .filter((id) => !isNaN(id));
19
21
  }
20
22
 
21
23
  const program = new Command();
@@ -30,14 +32,17 @@ program
30
32
  .option('--log-level <LEVEL>', 'Уровень логирования (debug, info, warn, error)', 'info')
31
33
  .option('--allowed-users <USERS>', 'Whitelist user_id через запятую (опционально)', '')
32
34
  .option('--init-only', 'Только инициализация хранилищ и выход', false)
33
- .addHelpText('after', `
35
+ .addHelpText(
36
+ 'after',
37
+ `
34
38
  Примеры использования:
35
39
  $ qwen-alpha --token 123456789:ABCdefGHIjklMNOpqrsTUVwxyz
36
40
  $ qwen-alpha --token <TOKEN> --log-level debug
37
41
  $ qwen-alpha --token <TOKEN> --allowed-users 123456789,987654321
38
42
 
39
43
  Документация: https://github.com/JeBance/QwenAlpha
40
- `);
44
+ `
45
+ );
41
46
 
42
47
  program.parse(process.argv);
43
48
 
@@ -48,17 +53,17 @@ async function main() {
48
53
  // Инициализация хранилищ
49
54
  const { initDirectories } = require('../src/utils/paths');
50
55
  const { storeManager } = require('../src/services/db');
51
-
56
+
52
57
  initDirectories();
53
58
  storeManager.init();
54
-
59
+
55
60
  // Если только инициализация
56
61
  if (options.initOnly) {
57
62
  console.log('✅ Qwen Alpha хранилища инициализированы');
58
63
  console.log(`📁 Данные хранятся в: ~/.qwen-alpha/`);
59
64
  process.exit(0);
60
65
  }
61
-
66
+
62
67
  // Валидация токена
63
68
  if (!options.token || options.token === '<TOKEN>') {
64
69
  console.error('❌ Ошибка: Telegram Bot API токен не указан');
@@ -71,20 +76,19 @@ async function main() {
71
76
  console.error(' qwen-alpha --token $BOT_TOKEN');
72
77
  process.exit(1);
73
78
  }
74
-
79
+
75
80
  // Подготовка опций для startBot
76
81
  const botOptions = {
77
82
  token: options.token,
78
83
  logLevel: options.logLevel,
79
84
  allowedUsers: parseUserList(options.allowedUsers),
80
85
  };
81
-
86
+
82
87
  // Запуск бота
83
88
  await startBot(botOptions);
84
-
89
+
85
90
  console.log('✅ Qwen Alpha запущен');
86
91
  console.log(' Нажмите Ctrl+C для остановки');
87
-
88
92
  } catch (error) {
89
93
  logger.error({ error }, 'Failed to start Qwen Alpha');
90
94
  console.error('❌ Ошибка при запуске:', error.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qwen-alpha",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "description": "Telegram bot for Qwen Code integration — AI-powered code review, bug detection, and code generation",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/qwen-alpha.js",
11
- "test": "node --test tests/",
11
+ "test": "node --test tests/cli.test.js",
12
12
  "lint": "eslint .",
13
13
  "format": "prettier --write .",
14
14
  "format:check": "prettier --check ."
package/src/bot/bot.js CHANGED
@@ -19,35 +19,35 @@ const fileHandler = require('./handlers/file');
19
19
  /**
20
20
  * Инициализация Telegraf бота
21
21
  * @param {string} token - Telegram Bot API токен
22
- * @param {Object} config - Конфигурация
23
22
  * @returns {Promise<import('telegraf').Telegraf>} Бот
24
23
  */
25
- async function initBot(token, config = {}) {
24
+ async function initBot(token) {
26
25
  const bot = new Telegraf(token, {
27
26
  handlerTimeout: 120000, // Таймаут обработки (120 секунд)
28
27
  });
29
-
30
- // Глобальная обработка ошибок
31
28
  bot.catch((err, ctx) => {
32
- logger.error({
33
- err,
34
- userId: ctx?.from?.id,
35
- chatId: ctx?.chat?.id,
36
- updateType: ctx?.updateType,
37
- }, 'Bot error caught');
38
-
29
+ logger.error(
30
+ {
31
+ err,
32
+ userId: ctx?.from?.id,
33
+ chatId: ctx?.chat?.id,
34
+ updateType: ctx?.updateType,
35
+ },
36
+ 'Bot error caught'
37
+ );
38
+
39
39
  // Не показываем пользователю технические детали
40
40
  if (ctx && ctx.reply) {
41
41
  ctx.reply('⚠️ Произошла ошибка. Попробуйте позже.').catch(() => {});
42
42
  }
43
43
  });
44
-
44
+
45
45
  // Middleware
46
46
  bot.use(loggingMiddleware);
47
47
  bot.use(rateLimitMiddleware);
48
48
  bot.use(authMiddleware);
49
49
  bot.use(sessionMiddleware);
50
-
50
+
51
51
  // Commands
52
52
  bot.command('start', startHandler);
53
53
  bot.command('help', helpHandler);
@@ -55,26 +55,26 @@ async function initBot(token, config = {}) {
55
55
  bot.command('stats', statsHandler);
56
56
  bot.command('settings', settingsHandler);
57
57
  bot.command('admin', adminHandler);
58
-
58
+
59
59
  // Сообщения
60
60
  bot.on(message('text'), messageHandler);
61
61
  bot.on(message('document'), fileHandler);
62
62
  bot.on(message('photo'), fileHandler);
63
-
63
+
64
64
  // Упоминания бота (для групповых чатов)
65
65
  bot.on('message', async (ctx, next) => {
66
66
  const botInfo = await bot.telegram.getMe();
67
67
  const botUsername = `@${botInfo.username}`;
68
-
68
+
69
69
  if (ctx.message.text?.includes(botUsername)) {
70
70
  return messageHandler(ctx);
71
71
  }
72
-
72
+
73
73
  return next();
74
74
  });
75
-
75
+
76
76
  logger.info('Bot initialized with middleware and handlers');
77
-
77
+
78
78
  return bot;
79
79
  }
80
80
 
@@ -14,19 +14,19 @@ async function adminHandler(ctx) {
14
14
  const userId = ctx.state.userId;
15
15
  const args = ctx.message?.text?.split(' ').slice(1) || [];
16
16
  const command = args[0]?.toLowerCase();
17
-
17
+
18
18
  // Проверка прав администратора
19
19
  if (!ctx.state.isAdmin) {
20
20
  await ctx.reply('⛔ Доступ запрещён. Требуются права администратора.');
21
21
  logger.warn({ userId }, 'Non-admin tried to access /admin');
22
22
  return;
23
23
  }
24
-
24
+
25
25
  // Без подкоманды — показать меню
26
26
  if (!command) {
27
27
  const admins = adminService.getAllAdmins();
28
28
  const globalStats = statsService.getGlobal();
29
-
29
+
30
30
  const menuText = `
31
31
  🛡 **Панель администратора**
32
32
 
@@ -66,11 +66,11 @@ async function adminHandler(ctx) {
66
66
  /admin stats — подробная статистика
67
67
  /admin broadcast <message> — рассылка всем
68
68
  `.trim();
69
-
69
+
70
70
  await ctx.reply(menuText, { parse_mode: 'Markdown' });
71
71
  return;
72
72
  }
73
-
73
+
74
74
  // Обработка подкоманд
75
75
  switch (command) {
76
76
  case 'add': {
@@ -79,7 +79,7 @@ async function adminHandler(ctx) {
79
79
  await ctx.reply('❌ Usage: /admin add <user_id>');
80
80
  return;
81
81
  }
82
-
82
+
83
83
  const success = adminService.addAdmin(targetId, userId);
84
84
  if (success) {
85
85
  await ctx.reply(`✅ Пользователь ${targetId} добавлен в админы.`);
@@ -89,14 +89,14 @@ async function adminHandler(ctx) {
89
89
  }
90
90
  break;
91
91
  }
92
-
92
+
93
93
  case 'remove': {
94
94
  const targetId = parseInt(args[1], 10);
95
95
  if (!targetId || isNaN(targetId)) {
96
96
  await ctx.reply('❌ Usage: /admin remove <user_id>');
97
97
  return;
98
98
  }
99
-
99
+
100
100
  const success = adminService.removeAdmin(targetId, userId);
101
101
  if (success) {
102
102
  await ctx.reply(`✅ Пользователь ${targetId} удалён из админов.`);
@@ -106,14 +106,14 @@ async function adminHandler(ctx) {
106
106
  }
107
107
  break;
108
108
  }
109
-
109
+
110
110
  case 'ban': {
111
111
  const targetId = parseInt(args[1], 10);
112
112
  if (!targetId || isNaN(targetId)) {
113
113
  await ctx.reply('❌ Usage: /admin ban <user_id>');
114
114
  return;
115
115
  }
116
-
116
+
117
117
  const success = userService.ban(targetId);
118
118
  if (success) {
119
119
  await ctx.reply(`✅ Пользователь ${targetId} забанен.`);
@@ -123,7 +123,7 @@ async function adminHandler(ctx) {
123
123
  }
124
124
  break;
125
125
  }
126
-
126
+
127
127
  case 'unban': {
128
128
  const targetId = parseInt(args[1], 10);
129
129
  if (!targetId || isNaN(targetId)) {
@@ -147,8 +147,11 @@ async function adminHandler(ctx) {
147
147
  const data = settings.getData();
148
148
  data.locked = true;
149
149
  settings.setData(data);
150
-
151
- await ctx.reply('🔒 **Бот заблокирован для всех пользователей кроме админов.**\n\nИспользуйте /admin unlock для разблокировки.', { parse_mode: 'Markdown' });
150
+
151
+ await ctx.reply(
152
+ '🔒 **Бот заблокирован для всех пользователей кроме админов.**\n\nИспользуйте /admin unlock для разблокировки.',
153
+ { parse_mode: 'Markdown' }
154
+ );
152
155
  logger.info({ userId }, 'Bot locked for all users except admins');
153
156
  break;
154
157
  }
@@ -159,15 +162,18 @@ async function adminHandler(ctx) {
159
162
  const data = settings.getData();
160
163
  data.locked = false;
161
164
  settings.setData(data);
162
-
163
- await ctx.reply('🔓 **Бот разблокирован.**\n\nВсе пользователи могут снова использовать бота.', { parse_mode: 'Markdown' });
165
+
166
+ await ctx.reply(
167
+ '🔓 **Бот разблокирован.**\n\nВсе пользователи могут снова использовать бота.',
168
+ { parse_mode: 'Markdown' }
169
+ );
164
170
  logger.info({ userId }, 'Bot unlocked for all users');
165
171
  break;
166
172
  }
167
173
 
168
174
  case 'sessions': {
169
175
  const subCommand = args[2];
170
-
176
+
171
177
  if (subCommand === 'list') {
172
178
  const allSessions = sessionService._store.getData();
173
179
  const sessionCount = Object.keys(allSessions).length;
@@ -178,11 +184,11 @@ async function adminHandler(ctx) {
178
184
  await ctx.reply('❌ Usage: /admin sessions clear <chat_id>');
179
185
  return;
180
186
  }
181
-
187
+
182
188
  const data = sessionService._store.getData();
183
189
  delete data[`chat:${chatId}`];
184
190
  sessionService._store.setData(data);
185
-
191
+
186
192
  await ctx.reply(`✅ Сессии чата ${chatId} очищены.`);
187
193
  logger.info({ userId, chatId }, 'Chat sessions cleared');
188
194
  } else {
@@ -190,19 +196,19 @@ async function adminHandler(ctx) {
190
196
  }
191
197
  break;
192
198
  }
193
-
199
+
194
200
  case 'set': {
195
201
  const key = args[1];
196
202
  const value = args[2];
197
-
203
+
198
204
  if (!key || value === undefined) {
199
205
  await ctx.reply('❌ Usage: /admin set <key> <value>');
200
206
  return;
201
207
  }
202
-
208
+
203
209
  const settings = storeManager.get('settings');
204
210
  const data = settings.getData();
205
-
211
+
206
212
  // Преобразование значения
207
213
  let parsedValue = value;
208
214
  if (!isNaN(Number(value))) {
@@ -212,8 +218,8 @@ async function adminHandler(ctx) {
212
218
  } else if (value.toLowerCase() === 'false') {
213
219
  parsedValue = false;
214
220
  }
215
-
216
- if (data.hasOwnProperty(key)) {
221
+
222
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
217
223
  data[key] = parsedValue;
218
224
  settings.setData(data);
219
225
  await ctx.reply(`✅ Настройка '${key}' установлена в '${parsedValue}'.`);
@@ -223,17 +229,17 @@ async function adminHandler(ctx) {
223
229
  }
224
230
  break;
225
231
  }
226
-
232
+
227
233
  case 'settings': {
228
234
  const settings = storeManager.get('settings').getData();
229
235
  const settingsText = Object.entries(settings)
230
236
  .map(([key, value]) => `• ${key}: ${value}`)
231
237
  .join('\n');
232
-
238
+
233
239
  await ctx.reply(`⚙️ **Настройки бота:**\n\n${settingsText}`, { parse_mode: 'Markdown' });
234
240
  break;
235
241
  }
236
-
242
+
237
243
  case 'stats': {
238
244
  const periodStats = statsService.getPeriod(7);
239
245
  const statsText = `
@@ -244,11 +250,11 @@ async function adminHandler(ctx) {
244
250
  **Файлы:** ${periodStats.total_files}
245
251
  **Сессии:** ${periodStats.total_sessions}
246
252
  `.trim();
247
-
253
+
248
254
  await ctx.reply(statsText, { parse_mode: 'Markdown' });
249
255
  break;
250
256
  }
251
-
257
+
252
258
  default:
253
259
  await ctx.reply('❌ Неизвестная команда. Используйте /admin для просмотра меню.');
254
260
  }
@@ -15,13 +15,12 @@ const { logger } = require('../../utils/logger');
15
15
  async function fileHandler(ctx) {
16
16
  const userId = ctx.state.userId;
17
17
  const chatId = ctx.state.chatId;
18
- const isPrivate = ctx.state.isPrivate;
19
-
18
+
20
19
  // Получение файла из сообщения
21
20
  let fileId;
22
21
  let fileName = 'unknown';
23
22
  let fileSize = 0;
24
-
23
+
25
24
  if (ctx.message.document) {
26
25
  fileId = ctx.message.document.file_id;
27
26
  fileName = ctx.message.document.file_name || 'code.txt';
@@ -35,39 +34,38 @@ async function fileHandler(ctx) {
35
34
  } else {
36
35
  return;
37
36
  }
38
-
37
+
39
38
  // Проверка размера файла
40
39
  const maxFileSize = config.qwen.maxFileSize;
41
40
  if (fileSize > maxFileSize) {
42
41
  const maxMB = (maxFileSize / 1024 / 1024).toFixed(2);
43
- await ctx.reply(
44
- `❌ Файл слишком большой. Максимальный размер: ${maxMB}MB`,
45
- { reply_parameters: { message_id: ctx.message.message_id } }
46
- );
42
+ await ctx.reply(`❌ Файл слишком большой. Максимальный размер: ${maxMB}MB`, {
43
+ reply_parameters: { message_id: ctx.message.message_id },
44
+ });
47
45
  return;
48
46
  }
49
-
47
+
50
48
  // Загрузка индикатора
51
49
  const loadingMsg = await ctx.reply('⏳ Скачиваю и анализирую файл...');
52
-
50
+
53
51
  let tempFilePath = null;
54
-
52
+
55
53
  try {
56
54
  const startTime = Date.now();
57
-
55
+
58
56
  // Скачивание файла
59
57
  const fileLink = await ctx.telegram.getFileLink(fileId);
60
58
  const response = await fetch(fileLink.href);
61
-
59
+
62
60
  if (!response.ok) {
63
61
  throw new Error('Failed to download file');
64
62
  }
65
-
63
+
66
64
  // Сохранение во временный файл
67
65
  tempFilePath = path.join(os.tmpdir(), `qwen-alpha-${Date.now()}-${fileName}`);
68
66
  const buffer = Buffer.from(await response.arrayBuffer());
69
67
  fs.writeFileSync(tempFilePath, buffer);
70
-
68
+
71
69
  // Чтение содержимого (для текстовых файлов)
72
70
  let fileContent;
73
71
  try {
@@ -75,7 +73,7 @@ async function fileHandler(ctx) {
75
73
  } catch (readError) {
76
74
  throw new Error('Не удалось прочитать файл. Поддерживаются только текстовые файлы.');
77
75
  }
78
-
76
+
79
77
  // Проверка размера содержимого
80
78
  if (fileContent.length > maxFileSize) {
81
79
  await ctx.editMessageText(
@@ -83,31 +81,31 @@ async function fileHandler(ctx) {
83
81
  );
84
82
  return;
85
83
  }
86
-
84
+
87
85
  // Получение контекста из сессии
88
86
  let contextMessages = [];
89
87
  const session = ctx.state.session;
90
-
88
+
91
89
  if (session) {
92
90
  const replyToMessageId = ctx.message?.reply_to_message?.message_id;
93
91
  if (replyToMessageId && session.message_tree[replyToMessageId]) {
94
92
  contextMessages = sessionService.getMessageChain(session, replyToMessageId);
95
93
  }
96
94
  }
97
-
95
+
98
96
  // Формирование промпта для анализа файла
99
97
  const prompt = `Проанализируй этот файл (${fileName}):\n\n${fileContent}`;
100
-
98
+
101
99
  // Запрос к Qwen
102
100
  const result = await qwenService.analyzeCode(prompt, contextMessages);
103
-
101
+
104
102
  const duration = Date.now() - startTime;
105
103
  statsService.updateAvgResponseTime(duration);
106
-
104
+
107
105
  // Отправка ответа (разбиваем на части если длинный)
108
106
  const maxMessageLength = 4096;
109
107
  const chunks = splitMessage(result, maxMessageLength);
110
-
108
+
111
109
  for (let i = 0; i < chunks.length; i++) {
112
110
  if (i === 0 && loadingMsg) {
113
111
  await ctx.editMessageText(chunks[i], { parse_mode: 'Markdown' });
@@ -115,7 +113,7 @@ async function fileHandler(ctx) {
115
113
  await ctx.reply(chunks[i], { parse_mode: 'Markdown' });
116
114
  }
117
115
  }
118
-
116
+
119
117
  // Обновление статистики
120
118
  statsService.incrementRequest();
121
119
  statsService.incrementFile();
@@ -123,16 +121,13 @@ async function fileHandler(ctx) {
123
121
  userService.updateStats(userId, {
124
122
  total_files: (userService.getById(userId)?.stats?.total_files || 0) + 1,
125
123
  });
126
-
124
+
127
125
  logger.info({ userId, chatId, fileName, fileSize, duration }, 'File analyzed');
128
-
129
126
  } catch (error) {
130
127
  logger.error({ userId, chatId, fileName, error }, 'File analysis failed');
131
-
132
- await ctx.editMessageText(
133
- `❌ Ошибка при анализе файла: ${error.message}`
134
- );
135
-
128
+
129
+ await ctx.editMessageText(`❌ Ошибка при анализе файла: ${error.message}`);
130
+
136
131
  statsService.incrementError();
137
132
  } finally {
138
133
  // Очистка временного файла
@@ -155,9 +150,9 @@ async function fileHandler(ctx) {
155
150
  function splitMessage(text, maxLength) {
156
151
  const chunks = [];
157
152
  let currentChunk = '';
158
-
153
+
159
154
  const lines = text.split('\n');
160
-
155
+
161
156
  for (const line of lines) {
162
157
  if (currentChunk.length + line.length + 1 > maxLength) {
163
158
  chunks.push(currentChunk);
@@ -166,11 +161,11 @@ function splitMessage(text, maxLength) {
166
161
  currentChunk += (currentChunk ? '\n' : '') + line;
167
162
  }
168
163
  }
169
-
164
+
170
165
  if (currentChunk) {
171
166
  chunks.push(currentChunk);
172
167
  }
173
-
168
+
174
169
  return chunks;
175
170
  }
176
171
 
@@ -5,7 +5,7 @@
5
5
  */
6
6
  async function helpHandler(ctx) {
7
7
  const isAdmin = ctx.state.isAdmin;
8
-
8
+
9
9
  const helpText = `
10
10
  📖 **Qwen Alpha — Команды бота**
11
11
 
@@ -36,7 +36,7 @@ ${isAdmin ? '**Админ команды:**\n/admin — Панель админ
36
36
  • Telegraf (Node.js)
37
37
  • JSON хранилище (~/.qwen-alpha/)
38
38
  `.trim();
39
-
39
+
40
40
  await ctx.reply(helpText, { parse_mode: 'Markdown' });
41
41
  }
42
42