qwen-alpha 1.0.0
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 +21 -0
- package/.nvmrc +1 -0
- package/.prettierrc +9 -0
- package/LICENSE +21 -0
- package/PUBLISH.md +252 -0
- package/README.md +282 -0
- package/bin/qwen-alpha.js +95 -0
- package/package.json +50 -0
- package/src/bot/bot.js +81 -0
- package/src/bot/handlers/admin.js +231 -0
- package/src/bot/handlers/file.js +177 -0
- package/src/bot/handlers/help.js +43 -0
- package/src/bot/handlers/message.js +132 -0
- package/src/bot/handlers/reset.js +53 -0
- package/src/bot/handlers/settings.js +39 -0
- package/src/bot/handlers/start.js +65 -0
- package/src/bot/handlers/stats.js +76 -0
- package/src/bot/middleware/auth.js +70 -0
- package/src/bot/middleware/logging.js +55 -0
- package/src/bot/middleware/rateLimit.js +74 -0
- package/src/bot/middleware/session.js +63 -0
- package/src/config/index.js +57 -0
- package/src/index.js +86 -0
- package/src/services/db/admins.js +146 -0
- package/src/services/db/index.js +177 -0
- package/src/services/db/sessions.js +342 -0
- package/src/services/db/stats.js +223 -0
- package/src/services/db/users.js +215 -0
- package/src/services/qwenService.js +254 -0
- package/src/utils/logger.js +127 -0
- package/src/utils/paths.js +63 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const { initDirectories } = require('./utils/paths');
|
|
2
|
+
const { logger } = require('./utils/logger');
|
|
3
|
+
const config = require('./config');
|
|
4
|
+
const { initBot } = require('./bot/bot');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Инициализация и запуск бота
|
|
8
|
+
* @param {Object} options - Опции запуска
|
|
9
|
+
* @param {string} options.token - Telegram Bot API токен
|
|
10
|
+
* @param {string} [options.logLevel] - Уровень логирования
|
|
11
|
+
* @param {number[]} [options.allowedUsers] - Whitelist пользователей
|
|
12
|
+
*/
|
|
13
|
+
async function startBot(options = {}) {
|
|
14
|
+
try {
|
|
15
|
+
// Инициализация директорий
|
|
16
|
+
initDirectories();
|
|
17
|
+
|
|
18
|
+
// Применение опций из CLI
|
|
19
|
+
if (options.token) {
|
|
20
|
+
config.bot.token = options.token;
|
|
21
|
+
}
|
|
22
|
+
if (options.logLevel) {
|
|
23
|
+
config.bot.logLevel = options.logLevel;
|
|
24
|
+
}
|
|
25
|
+
if (options.allowedUsers) {
|
|
26
|
+
config.bot.allowedUsers = options.allowedUsers;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Валидация токена
|
|
30
|
+
if (!config.bot.token) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
'Telegram Bot API токен не указан. ' +
|
|
33
|
+
'Используйте --token <TOKEN> или установите переменную окружения BOT_TOKEN'
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
logger.info({
|
|
38
|
+
logLevel: config.bot.logLevel,
|
|
39
|
+
allowedUsers: config.bot.allowedUsers.length > 0 ? config.bot.allowedUsers : 'all',
|
|
40
|
+
}, 'Starting Qwen Alpha Bot');
|
|
41
|
+
|
|
42
|
+
// Инициализация и запуск бота
|
|
43
|
+
const bot = await initBot(config.bot.token, config);
|
|
44
|
+
|
|
45
|
+
// Запуск в режиме polling
|
|
46
|
+
await bot.launch();
|
|
47
|
+
|
|
48
|
+
logger.info('Bot launched successfully');
|
|
49
|
+
|
|
50
|
+
// Обработчики graceful shutdown
|
|
51
|
+
const shutdown = async (signal) => {
|
|
52
|
+
logger.info({ signal }, 'Graceful shutdown initiated');
|
|
53
|
+
bot.stop(signal);
|
|
54
|
+
|
|
55
|
+
// Закрываем соединение с Telegram
|
|
56
|
+
await bot.telegram.getMe().catch(() => {});
|
|
57
|
+
|
|
58
|
+
logger.info('Bot stopped');
|
|
59
|
+
process.exit(0);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
process.once('SIGINT', () => shutdown('SIGINT'));
|
|
63
|
+
process.once('SIGTERM', () => shutdown('SIGTERM'));
|
|
64
|
+
|
|
65
|
+
return bot;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
logger.error({ error }, 'Failed to start bot');
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Остановка бота
|
|
74
|
+
* @param {import('telegraf').Telegraf} bot - Экземпляр бота
|
|
75
|
+
*/
|
|
76
|
+
async function stopBot(bot) {
|
|
77
|
+
logger.info('Stopping bot');
|
|
78
|
+
bot.stop('manual');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = {
|
|
82
|
+
startBot,
|
|
83
|
+
stopBot,
|
|
84
|
+
config,
|
|
85
|
+
logger,
|
|
86
|
+
};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const { storeManager } = require('./index');
|
|
2
|
+
const { logger } = require('../../utils/logger');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Сервис для управления администраторами
|
|
6
|
+
*/
|
|
7
|
+
class AdminService {
|
|
8
|
+
/**
|
|
9
|
+
* Получение хранилища админов
|
|
10
|
+
* @private
|
|
11
|
+
*/
|
|
12
|
+
get _store() {
|
|
13
|
+
return storeManager.get('admins');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Получение данных об админах
|
|
18
|
+
* @returns {Object} Данные об админах
|
|
19
|
+
*/
|
|
20
|
+
getData() {
|
|
21
|
+
return this._store.getData();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Проверка, является ли пользователь супер-админом
|
|
26
|
+
* @param {number} userId - Telegram user ID
|
|
27
|
+
* @returns {boolean} true если супер-админ
|
|
28
|
+
*/
|
|
29
|
+
isSuperAdmin(userId) {
|
|
30
|
+
const data = this._store.getData();
|
|
31
|
+
return data.super_admin === userId;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Проверка, является ли пользователь админом
|
|
36
|
+
* @param {number} userId - Telegram user ID
|
|
37
|
+
* @returns {boolean} true если админ
|
|
38
|
+
*/
|
|
39
|
+
isAdmin(userId) {
|
|
40
|
+
const data = this._store.getData();
|
|
41
|
+
return data.super_admin === userId || data.admins.includes(userId);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Регистрация супер-админа (первый пользователь)
|
|
46
|
+
* @param {number} userId - Telegram user ID
|
|
47
|
+
* @returns {boolean} true если успешно зарегистрирован
|
|
48
|
+
*/
|
|
49
|
+
registerSuperAdmin(userId) {
|
|
50
|
+
const data = this._store.getData();
|
|
51
|
+
|
|
52
|
+
// Если супер-админ уже есть, возвращаем false
|
|
53
|
+
if (data.super_admin !== null) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
data.super_admin = userId;
|
|
58
|
+
this._store.setData(data);
|
|
59
|
+
logger.info({ userId }, 'Super admin registered');
|
|
60
|
+
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Добавление админа
|
|
66
|
+
* @param {number} userId - Telegram user ID
|
|
67
|
+
* @param {number} addedBy - Telegram user ID добавившего (должен быть супер-админом)
|
|
68
|
+
* @returns {boolean} Успешность
|
|
69
|
+
*/
|
|
70
|
+
addAdmin(userId, addedBy) {
|
|
71
|
+
if (!this.isSuperAdmin(addedBy)) {
|
|
72
|
+
logger.warn({ userId, addedBy }, 'Non-super-admin tried to add admin');
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const data = this._store.getData();
|
|
77
|
+
|
|
78
|
+
if (data.admins.includes(userId)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
data.admins.push(userId);
|
|
83
|
+
this._store.setData(data);
|
|
84
|
+
logger.info({ userId, addedBy }, 'Admin added');
|
|
85
|
+
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Удаление админа
|
|
91
|
+
* @param {number} userId - Telegram user ID
|
|
92
|
+
* @param {number} removedBy - Telegram user ID удаляющего (должен быть супер-админом)
|
|
93
|
+
* @returns {boolean} Успешность
|
|
94
|
+
*/
|
|
95
|
+
removeAdmin(userId, removedBy) {
|
|
96
|
+
if (!this.isSuperAdmin(removedBy)) {
|
|
97
|
+
logger.warn({ userId, removedBy }, 'Non-super-admin tried to remove admin');
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const data = this._store.getData();
|
|
102
|
+
const index = data.admins.indexOf(userId);
|
|
103
|
+
|
|
104
|
+
if (index === -1) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
data.admins.splice(index, 1);
|
|
109
|
+
this._store.setData(data);
|
|
110
|
+
logger.info({ userId, removedBy }, 'Admin removed');
|
|
111
|
+
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Получение списка всех админов
|
|
117
|
+
* @returns {Object} Список админов
|
|
118
|
+
*/
|
|
119
|
+
getAllAdmins() {
|
|
120
|
+
const data = this._store.getData();
|
|
121
|
+
return {
|
|
122
|
+
super_admin: data.super_admin,
|
|
123
|
+
admins: [...data.admins],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Получение списка обычных админов (не супер-админ)
|
|
129
|
+
* @returns {Array} Массив user_id
|
|
130
|
+
*/
|
|
131
|
+
getAdmins() {
|
|
132
|
+
const data = this._store.getData();
|
|
133
|
+
return [...data.admins];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Получение супер-админа
|
|
138
|
+
* @returns {number|null} user_id или null
|
|
139
|
+
*/
|
|
140
|
+
getSuperAdmin() {
|
|
141
|
+
const data = this._store.getData();
|
|
142
|
+
return data.super_admin;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = new AdminService();
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { DB_FILES, initDirectories } = require('../utils/paths');
|
|
4
|
+
const { logger } = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Базовый класс для JSON хранилища
|
|
8
|
+
* Обеспечивает CRUD операции с файлами и синхронизацию
|
|
9
|
+
*/
|
|
10
|
+
class JsonStore {
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} filePath - Путь к JSON файлу
|
|
13
|
+
* @param {Object} defaultData - Данные по умолчанию
|
|
14
|
+
*/
|
|
15
|
+
constructor(filePath, defaultData = {}) {
|
|
16
|
+
this.filePath = filePath;
|
|
17
|
+
this.defaultData = defaultData;
|
|
18
|
+
this.data = null;
|
|
19
|
+
this.isInitialized = false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Инициализация хранилища
|
|
24
|
+
* Загружает данные из файла или создаёт новый
|
|
25
|
+
*/
|
|
26
|
+
init() {
|
|
27
|
+
try {
|
|
28
|
+
if (fs.existsSync(this.filePath)) {
|
|
29
|
+
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
30
|
+
this.data = JSON.parse(content);
|
|
31
|
+
logger.debug({ file: this.filePath }, 'Loaded existing store');
|
|
32
|
+
} else {
|
|
33
|
+
this.data = JSON.parse(JSON.stringify(this.defaultData));
|
|
34
|
+
this.save();
|
|
35
|
+
logger.debug({ file: this.filePath }, 'Created new store');
|
|
36
|
+
}
|
|
37
|
+
this.isInitialized = true;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
logger.error({ error, file: this.filePath }, 'Failed to initialize store');
|
|
40
|
+
this.data = JSON.parse(JSON.stringify(this.defaultData));
|
|
41
|
+
this.isInitialized = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Сохранение данных в файл
|
|
47
|
+
*/
|
|
48
|
+
save() {
|
|
49
|
+
try {
|
|
50
|
+
const dir = path.dirname(this.filePath);
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf-8');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger.error({ error, file: this.filePath }, 'Failed to save store');
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Получение данных
|
|
63
|
+
* @returns {Object} Копия данных хранилища
|
|
64
|
+
*/
|
|
65
|
+
getData() {
|
|
66
|
+
if (!this.isInitialized) {
|
|
67
|
+
this.init();
|
|
68
|
+
}
|
|
69
|
+
return JSON.parse(JSON.stringify(this.data));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Установка данных
|
|
74
|
+
* @param {Object} newData - Новые данные
|
|
75
|
+
*/
|
|
76
|
+
setData(newData) {
|
|
77
|
+
if (!this.isInitialized) {
|
|
78
|
+
this.init();
|
|
79
|
+
}
|
|
80
|
+
this.data = JSON.parse(JSON.stringify(newData));
|
|
81
|
+
this.save();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Обновление данных (merge)
|
|
86
|
+
* @param {Object} updates - Данные для обновления
|
|
87
|
+
*/
|
|
88
|
+
updateData(updates) {
|
|
89
|
+
if (!this.isInitialized) {
|
|
90
|
+
this.init();
|
|
91
|
+
}
|
|
92
|
+
this.data = { ...this.data, ...updates };
|
|
93
|
+
this.save();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Менеджер хранилищ
|
|
99
|
+
* Управляет инициализацией и доступом ко всем хранилищам
|
|
100
|
+
*/
|
|
101
|
+
class StoreManager {
|
|
102
|
+
constructor() {
|
|
103
|
+
this.stores = {};
|
|
104
|
+
this.isInitialized = false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Инициализация всех хранилищ
|
|
109
|
+
*/
|
|
110
|
+
init() {
|
|
111
|
+
if (this.isInitialized) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
initDirectories();
|
|
116
|
+
|
|
117
|
+
// Инициализация хранилищ
|
|
118
|
+
this.stores.users = new JsonStore(DB_FILES.users, {});
|
|
119
|
+
this.stores.sessions = new JsonStore(DB_FILES.sessions, {});
|
|
120
|
+
this.stores.admins = new JsonStore(DB_FILES.admins, { super_admin: null, admins: [] });
|
|
121
|
+
this.stores.stats = new JsonStore(DB_FILES.stats, {
|
|
122
|
+
global: {
|
|
123
|
+
total_users: 0,
|
|
124
|
+
active_24h: 0,
|
|
125
|
+
requests_today: 0,
|
|
126
|
+
errors_24h: 0,
|
|
127
|
+
avg_response_time_ms: 0,
|
|
128
|
+
},
|
|
129
|
+
daily: {},
|
|
130
|
+
});
|
|
131
|
+
this.stores.settings = new JsonStore(DB_FILES.settings, {
|
|
132
|
+
session_timeout_hours: 24,
|
|
133
|
+
max_file_size_mb: 2,
|
|
134
|
+
requests_per_user_per_hour: 60,
|
|
135
|
+
log_rotation_days: 1,
|
|
136
|
+
group_mode: 'mention',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Инициализация каждого хранилища
|
|
140
|
+
Object.values(this.stores).forEach(store => store.init());
|
|
141
|
+
|
|
142
|
+
this.isInitialized = true;
|
|
143
|
+
logger.info('All stores initialized');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Получение хранилища
|
|
148
|
+
* @param {string} name - Имя хранилища
|
|
149
|
+
* @returns {JsonStore} Хранилище
|
|
150
|
+
*/
|
|
151
|
+
get(name) {
|
|
152
|
+
if (!this.isInitialized) {
|
|
153
|
+
this.init();
|
|
154
|
+
}
|
|
155
|
+
return this.stores[name];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Сохранение всех хранилищ
|
|
160
|
+
*/
|
|
161
|
+
saveAll() {
|
|
162
|
+
Object.values(this.stores).forEach(store => {
|
|
163
|
+
if (store.isInitialized) {
|
|
164
|
+
store.save();
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Экспорт синглтона
|
|
171
|
+
const storeManager = new StoreManager();
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
JsonStore,
|
|
175
|
+
StoreManager,
|
|
176
|
+
storeManager,
|
|
177
|
+
};
|