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 +4 -4
- package/PUBLISH.md +1 -0
- package/README.md +28 -28
- package/bin/qwen-alpha.js +16 -12
- package/package.json +2 -2
- package/src/bot/bot.js +19 -19
- package/src/bot/handlers/admin.js +35 -29
- package/src/bot/handlers/file.js +30 -35
- package/src/bot/handlers/help.js +2 -2
- package/src/bot/handlers/message.js +21 -21
- package/src/bot/handlers/reset.js +11 -14
- package/src/bot/handlers/settings.js +2 -3
- package/src/bot/handlers/start.js +21 -22
- package/src/bot/handlers/stats.js +14 -11
- package/src/bot/middleware/auth.js +8 -8
- package/src/bot/middleware/logging.js +23 -17
- package/src/bot/middleware/rateLimit.js +13 -15
- package/src/bot/middleware/session.js +14 -13
- package/src/config/index.js +5 -5
- package/src/index.js +19 -16
- package/src/services/db/admins.js +20 -20
- package/src/services/db/index.js +14 -14
- package/src/services/db/sessions.js +66 -52
- package/src/services/db/stats.js +48 -45
- package/src/services/db/users.js +34 -34
- package/src/services/qwenService.js +71 -45
- package/src/utils/logger.js +29 -26
- package/src/utils/paths.js +1 -1
package/.eslintrc.js
CHANGED
|
@@ -10,12 +10,12 @@ module.exports = {
|
|
|
10
10
|
},
|
|
11
11
|
rules: {
|
|
12
12
|
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
semi: ['error', 'always'],
|
|
14
|
+
quotes: ['warn', 'single'],
|
|
15
15
|
'no-console': 'off',
|
|
16
16
|
'no-eval': 'error',
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
eqeqeq: ['error', 'always'],
|
|
18
|
+
curly: ['error', 'all'],
|
|
19
19
|
},
|
|
20
20
|
ignorePatterns: ['node_modules/', 'logs/', '*.log'],
|
|
21
21
|
};
|
package/PUBLISH.md
CHANGED
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>`
|
|
84
|
-
| `--log-level <LEVEL>`
|
|
85
|
-
| `--allowed-users <IDS>` | Whitelist user_id
|
|
86
|
-
| `--init-only`
|
|
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`
|
|
157
|
-
| `max_file_size_mb`
|
|
158
|
-
| `requests_per_user_per_hour` | 60
|
|
159
|
-
| `group_mode`
|
|
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)
|
|
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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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(
|
|
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(
|
|
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 (
|
|
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
|
}
|
package/src/bot/handlers/file.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
|
package/src/bot/handlers/help.js
CHANGED
|
@@ -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
|
|