blockmine 1.3.1 → 1.3.21
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 +40 -16
- package/backend/prisma/schema.prisma +1 -0
- package/backend/src/api/routes/bots.js +61 -51
- package/backend/src/core/BotManager.js +94 -53
- package/backend/src/core/BotProcess.js +34 -1
- package/backend/src/core/system/Command.js +0 -11
- package/backend/src/core/system/CommandManager.js +5 -4
- package/frontend/dist/assets/index-BP4_jhRx.css +1 -0
- package/frontend/dist/assets/{index-4S5VJ11r.js → index-Bz8G6akb.js} +1423 -1423
- package/frontend/dist/index.html +2 -2
- package/package.json +3 -3
- package/frontend/dist/assets/index-B5_cke-P.css +0 -1
package/README.md
CHANGED
|
@@ -5,24 +5,24 @@
|
|
|
5
5
|
<strong>Мощная и удобная панель управления для ваших Minecraft-ботов на базе Mineflayer.</strong>
|
|
6
6
|
</p>
|
|
7
7
|
<p>
|
|
8
|
-
<img src="https://img.shields.io/github/stars/blockmineJS/blockmine?style=for-the-badge&logo=github" alt="Stars">
|
|
9
|
-
<img src="https://img.shields.io/github/last-commit/blockmineJS/blockmine?style=for-the-badge&logo=git" alt="Last Commit">
|
|
8
|
+
<a href="https://github.com/blockmineJS/blockmine/stargazers"><img src="https://img.shields.io/github/stars/blockmineJS/blockmine?style=for-the-badge&logo=github" alt="Stars"></a>
|
|
9
|
+
<a href="https://github.com/blockmineJS/blockmine/commits/main"><img src="https://img.shields.io/github/last-commit/blockmineJS/blockmine?style=for-the-badge&logo=git" alt="Last Commit"></a>
|
|
10
10
|
<a href="http://185.65.200.184:3000/api/stats" target="_blank">
|
|
11
11
|
<img src="https://img.shields.io/endpoint?url=https://blockmine-proxy.vercel.app/api/shield&style=for-the-badge&logo=minecraft&logoColor=white" alt="Ботов онлайн">
|
|
12
|
+
</a>
|
|
12
13
|
</p>
|
|
13
14
|
</div>
|
|
14
15
|
|
|
15
|
-
BlockMine — это open-source решение для централизованного управления и автоматизации ботов Minecraft. Запускайте ботов, управляйте ими в реальном времени через удобный веб-интерфейс, расширяйте их возможности с помощью плагинов и настраивайте всё под свои нужды.
|
|
16
|
+
**BlockMine** — это open-source решение для централизованного управления и автоматизации ботов Minecraft. Запускайте ботов, управляйте ими в реальном времени через удобный веб-интерфейс, расширяйте их возможности с помощью плагинов и настраивайте всё под свои нужды.
|
|
16
17
|
|
|
17
18
|
---
|
|
18
19
|
|
|
19
|
-
|
|
20
20
|
<table align="center">
|
|
21
21
|
<tr>
|
|
22
22
|
<td align="center">
|
|
23
|
-
<p><strong
|
|
23
|
+
<p><strong>Дашборд</strong></p>
|
|
24
24
|
<img src="./image/1.png" alt="Скриншот дашборда" width="100%">
|
|
25
|
-
<em
|
|
25
|
+
<em>Общая сводка и быстрое управление всеми ботами.</em>
|
|
26
26
|
</td>
|
|
27
27
|
<td align="center">
|
|
28
28
|
<p><strong>Магазин плагинов</strong></p>
|
|
@@ -30,9 +30,9 @@ BlockMine — это open-source решение для централизова
|
|
|
30
30
|
<em>Расширяйте возможности с помощью удобного браузера плагинов.</em>
|
|
31
31
|
</td>
|
|
32
32
|
<td align="center">
|
|
33
|
-
<p><strong
|
|
33
|
+
<p><strong>Управление командами</strong></p>
|
|
34
34
|
<img src="./image/3.png" alt="Скриншот страницы управления" width="100%">
|
|
35
|
-
<em>Настраивайте
|
|
35
|
+
<em>Настраивайте права, алиасы и кулдауны для каждой команды.</em>
|
|
36
36
|
</td>
|
|
37
37
|
</tr>
|
|
38
38
|
</table>
|
|
@@ -67,7 +67,7 @@ BlockMine — это open-source решение для централизова
|
|
|
67
67
|
|
|
68
68
|
## ✨ Быстрый старт с `npx`
|
|
69
69
|
|
|
70
|
-
Это самый простой способ запустить
|
|
70
|
+
Это самый простой способ запустить панель. Убедитесь, что у вас установлен **Node.js v18+**.
|
|
71
71
|
|
|
72
72
|
1. Откройте терминал (командную строку).
|
|
73
73
|
2. Выполните одну команду:
|
|
@@ -77,8 +77,7 @@ BlockMine — это open-source решение для централизова
|
|
|
77
77
|
```
|
|
78
78
|
3. Готово! Скрипт автоматически скачает все необходимое, настроит базу данных и запустит сервер.
|
|
79
79
|
|
|
80
|
-
|
|
81
|
-
> ⚠️ **Важно**: Если выскакивает ошибка - Невозможно загрузить файл C:\Program Files\nodejs\npx.ps1, так как выполнение сценариев отключено в этой системе. - Выполните `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`
|
|
80
|
+
> ⚠️ **Для пользователей Windows**: Если появляется ошибка `Невозможно загрузить файл ... npx.ps1, так как выполнение сценариев отключено`, откройте PowerShell от имени администратора и выполните `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`. Нажмите 'Y' для подтверждения.
|
|
82
81
|
|
|
83
82
|
После успешного запуска вы увидите в консоли:
|
|
84
83
|
```
|
|
@@ -86,20 +85,45 @@ BlockMine — это open-source решение для централизова
|
|
|
86
85
|
```
|
|
87
86
|
Откройте этот адрес в вашем браузере, чтобы начать работу.
|
|
88
87
|
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 💡 Основные концепции BlockMine
|
|
91
|
+
|
|
92
|
+
Чтобы максимально эффективно использовать панель, важно понимать её ключевые компоненты.
|
|
93
|
+
|
|
94
|
+
### 🔌 Плагины
|
|
95
|
+
Плагины — это основа функциональности вашего бота. Они добавляют новые команды, автоматизируют действия и интегрируются с другими системами.
|
|
96
|
+
|
|
97
|
+
* **Магазин плагинов**: Встроенный браузер позволяет легко находить и устанавливать плагины в один клик.
|
|
98
|
+
* **Зависимости**: Система автоматически определит и предложит установить плагины, необходимые для работы выбранного вами.
|
|
99
|
+
* **Настройка**: Большинство плагинов можно настроить прямо в веб-интерфейсе, не редактируя файлы конфигурации вручную.
|
|
100
|
+
|
|
101
|
+
### ⚙️ Команды
|
|
102
|
+
Каждый плагин может регистрировать в системе свои команды. BlockMine предоставляет мощный интерфейс для управления ими.
|
|
103
|
+
|
|
104
|
+
* **Алиасы**: Назначайте командам короткие псевдонимы (например, `@p` для `@ping`).
|
|
105
|
+
* **Кулдауны**: Устанавливайте задержку между использованиями команды для предотвращения спама.
|
|
106
|
+
* **Включение/выключение**: Временно отключайте команды, не удаляя плагин.
|
|
107
|
+
* **Аргументы**: Панель автоматически обрабатывает аргументы команд, делая их использование предсказуемым.
|
|
108
|
+
|
|
109
|
+
### 🔐 Права и Группы (Permissions)
|
|
110
|
+
Это гибкая система, позволяющая точно контролировать, кто и что может делать с вашим ботом.
|
|
111
|
+
|
|
112
|
+
* **Права (Permissions)**: Каждое действие (например, использование команды `@fly`) защищено правом (`user.fly`). Права обычно создаются плагинами.
|
|
113
|
+
* **Группы (Groups)**: Группы объединяют несколько прав. Например, группа `Member` может иметь право `member.say`, а группа `Admin` — право `admin.*` (доступ ко всем командам у admin.).
|
|
114
|
+
* **Пользователи**: Каждый игрок, взаимодействующий с ботом, становится пользователем в системе. Вы можете добавлять пользователей в разные группы, выдавая им соответствующие права.
|
|
115
|
+
|
|
116
|
+
Эта система позволяет создавать сложные иерархии доступа, например: обычные игроки, участники клана, модераторы и администраторы, каждый со своим набором возможностей.
|
|
117
|
+
|
|
89
118
|
## 🧑💻 Для разработчиков и контрибьюторов
|
|
90
119
|
|
|
91
120
|
Если вы хотите внести свой вклад в проект или запустить его в режиме разработки.
|
|
92
121
|
|
|
93
122
|
### 1. Установка
|
|
94
123
|
|
|
95
|
-
Сначала клонируйте репозиторий и установите все зависимости.
|
|
96
|
-
|
|
97
124
|
```bash
|
|
98
|
-
# 1. Клонировать репозиторий
|
|
99
125
|
git clone https://github.com/blockmineJS/blockmine.git
|
|
100
126
|
cd blockmine
|
|
101
|
-
|
|
102
|
-
# 2. Установить все зависимости для всех частей проекта (backend, frontend)
|
|
103
127
|
npm install
|
|
104
128
|
```
|
|
105
129
|
|
|
@@ -8,6 +8,7 @@ const PluginManager = require('../../core/PluginManager');
|
|
|
8
8
|
const UserService = require('../../core/UserService');
|
|
9
9
|
const commandManager = require('../../core/system/CommandManager');
|
|
10
10
|
|
|
11
|
+
|
|
11
12
|
const multer = require('multer');
|
|
12
13
|
const archiver = require('archiver');
|
|
13
14
|
const AdmZip = require('adm-zip');
|
|
@@ -443,71 +444,65 @@ router.get('/:botId/management-data', async (req, res) => {
|
|
|
443
444
|
const botId = parseInt(req.params.botId);
|
|
444
445
|
|
|
445
446
|
const commandTemplates = commandManager.getCommandTemplates();
|
|
446
|
-
|
|
447
|
+
const templatesMap = new Map(commandTemplates.map(t => [t.name, t]));
|
|
447
448
|
|
|
448
|
-
|
|
449
|
-
const exists = await prisma.command.findUnique({
|
|
450
|
-
where: { botId_name: { botId, name: template.name } }
|
|
451
|
-
});
|
|
449
|
+
let dbCommands = await prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] });
|
|
452
450
|
|
|
453
|
-
|
|
451
|
+
const dbCommandNames = new Set(dbCommands.map(cmd => cmd.name));
|
|
452
|
+
const commandsToCreate = [];
|
|
453
|
+
|
|
454
|
+
for (const template of commandTemplates) {
|
|
455
|
+
if (!dbCommandNames.has(template.name)) {
|
|
454
456
|
let permissionId = null;
|
|
455
457
|
if (template.permissions) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
permissionId = requiredPermission.id;
|
|
458
|
+
const permission = await prisma.permission.upsert({
|
|
459
|
+
where: { botId_name: { botId, name: template.permissions } },
|
|
460
|
+
update: { description: `Авто-создано для команды ${template.name}` },
|
|
461
|
+
create: {
|
|
462
|
+
botId,
|
|
463
|
+
name: template.permissions,
|
|
464
|
+
description: `Авто-создано для команды ${template.name}`,
|
|
465
|
+
owner: template.owner || 'system',
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
permissionId = permission.id;
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
allowedChatTypes: JSON.stringify(template.allowedChatTypes || ['chat', 'private']),
|
|
482
|
-
}
|
|
471
|
+
commandsToCreate.push({
|
|
472
|
+
botId,
|
|
473
|
+
name: template.name,
|
|
474
|
+
isEnabled: template.isActive,
|
|
475
|
+
cooldown: template.cooldown,
|
|
476
|
+
aliases: JSON.stringify(template.aliases),
|
|
477
|
+
description: template.description,
|
|
478
|
+
owner: template.owner,
|
|
479
|
+
permissionId: permissionId,
|
|
480
|
+
allowedChatTypes: JSON.stringify(template.allowedChatTypes),
|
|
483
481
|
});
|
|
484
482
|
}
|
|
485
483
|
}
|
|
486
|
-
|
|
487
|
-
|
|
484
|
+
|
|
485
|
+
if (commandsToCreate.length > 0) {
|
|
486
|
+
await prisma.command.createMany({ data: commandsToCreate });
|
|
487
|
+
dbCommands = await prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] });
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const finalCommands = dbCommands.map(cmd => {
|
|
491
|
+
const template = templatesMap.get(cmd.name);
|
|
492
|
+
return {
|
|
493
|
+
...cmd,
|
|
494
|
+
args: template ? template.args : [],
|
|
495
|
+
aliases: JSON.parse(cmd.aliases || '[]'),
|
|
496
|
+
allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]')
|
|
497
|
+
};
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const [groups, users, allPermissions] = await Promise.all([
|
|
488
501
|
prisma.group.findMany({ where: { botId }, include: { permissions: { include: { permission: true } } }, orderBy: { name: 'asc' } }),
|
|
489
502
|
prisma.user.findMany({ where: { botId }, include: { groups: { include: { group: true } } }, orderBy: { username: 'asc' } }),
|
|
490
|
-
prisma.command.findMany({ where: { botId }, orderBy: [{ owner: 'asc' }, { name: 'asc' }] }),
|
|
491
503
|
prisma.permission.findMany({ where: { botId }, orderBy: { name: 'asc' } })
|
|
492
504
|
]);
|
|
493
505
|
|
|
494
|
-
const finalCommands = dbCommands.map(cmd => {
|
|
495
|
-
try {
|
|
496
|
-
return {
|
|
497
|
-
...cmd,
|
|
498
|
-
aliases: JSON.parse(cmd.aliases || '[]'),
|
|
499
|
-
allowedChatTypes: JSON.parse(cmd.allowedChatTypes || '[]')
|
|
500
|
-
};
|
|
501
|
-
} catch (e) {
|
|
502
|
-
console.error(`Ошибка парсинга JSON для команды ${cmd.name} (ID: ${cmd.id})`, e);
|
|
503
|
-
return {
|
|
504
|
-
...cmd,
|
|
505
|
-
aliases: [],
|
|
506
|
-
allowedChatTypes: []
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
});
|
|
510
|
-
|
|
511
506
|
res.json({ groups, permissions: allPermissions, users, commands: finalCommands });
|
|
512
507
|
|
|
513
508
|
} catch (error) {
|
|
@@ -542,6 +537,8 @@ router.put('/:botId/commands/:commandId', async (req, res) => {
|
|
|
542
537
|
data: dataToUpdate,
|
|
543
538
|
});
|
|
544
539
|
|
|
540
|
+
BotManager.invalidateConfigCache(botId);
|
|
541
|
+
|
|
545
542
|
res.json(updatedCommand);
|
|
546
543
|
} catch (error) {
|
|
547
544
|
console.error(`[API Error] /commands/:commandId PUT:`, error);
|
|
@@ -563,6 +560,10 @@ router.post('/:botId/groups', async (req, res) => {
|
|
|
563
560
|
permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
|
|
564
561
|
}
|
|
565
562
|
});
|
|
563
|
+
|
|
564
|
+
BotManager.invalidateConfigCache(botId);
|
|
565
|
+
|
|
566
|
+
|
|
566
567
|
res.status(201).json(newGroup);
|
|
567
568
|
} catch (error) {
|
|
568
569
|
if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
|
|
@@ -596,6 +597,8 @@ router.put('/:botId/groups/:groupId', async (req, res) => {
|
|
|
596
597
|
BotManager.invalidateUserCache(botId, user.username);
|
|
597
598
|
}
|
|
598
599
|
|
|
600
|
+
BotManager.invalidateConfigCache(botId);
|
|
601
|
+
|
|
599
602
|
res.status(200).send();
|
|
600
603
|
} catch (error) {
|
|
601
604
|
if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
|
|
@@ -605,12 +608,16 @@ router.put('/:botId/groups/:groupId', async (req, res) => {
|
|
|
605
608
|
|
|
606
609
|
router.delete('/:botId/groups/:groupId', async (req, res) => {
|
|
607
610
|
try {
|
|
611
|
+
const botId = parseInt(req.params.botId, 10);
|
|
608
612
|
const groupId = parseInt(req.params.groupId);
|
|
609
613
|
const group = await prisma.group.findUnique({ where: { id: groupId } });
|
|
610
614
|
if (group && group.owner !== 'admin') {
|
|
611
615
|
return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
|
|
612
616
|
}
|
|
613
617
|
await prisma.group.delete({ where: { id: groupId } });
|
|
618
|
+
BotManager.invalidateConfigCache(botId);
|
|
619
|
+
|
|
620
|
+
|
|
614
621
|
res.status(204).send();
|
|
615
622
|
} catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
|
|
616
623
|
});
|
|
@@ -623,6 +630,9 @@ router.post('/:botId/permissions', async (req, res) => {
|
|
|
623
630
|
const newPermission = await prisma.permission.create({
|
|
624
631
|
data: { name, description, botId, owner: 'admin' }
|
|
625
632
|
});
|
|
633
|
+
|
|
634
|
+
BotManager.invalidateConfigCache(botId);
|
|
635
|
+
|
|
626
636
|
res.status(201).json(newPermission);
|
|
627
637
|
} catch (error) {
|
|
628
638
|
if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
const { fork } = require('child_process');
|
|
3
2
|
const path = require('path');
|
|
4
3
|
const { getIO } = require('../real-time/socketHandler');
|
|
@@ -43,7 +42,8 @@ class BotManager {
|
|
|
43
42
|
this.bots = new Map();
|
|
44
43
|
this.logCache = new Map();
|
|
45
44
|
this.resourceUsage = new Map();
|
|
46
|
-
|
|
45
|
+
this.botConfigs = new Map();
|
|
46
|
+
|
|
47
47
|
setInterval(() => this.updateAllResourceUsage(), 5000);
|
|
48
48
|
|
|
49
49
|
|
|
@@ -52,6 +52,42 @@ class BotManager {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
async loadConfigForBot(botId) {
|
|
56
|
+
console.log(`[BotManager] Caching configuration for bot ID ${botId}...`);
|
|
57
|
+
try {
|
|
58
|
+
const [commands, permissions] = await Promise.all([
|
|
59
|
+
prisma.command.findMany({ where: { botId } }),
|
|
60
|
+
prisma.permission.findMany({ where: { botId } }),
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
const config = {
|
|
64
|
+
commands: new Map(commands.map(cmd => [cmd.name, cmd])),
|
|
65
|
+
permissionsById: new Map(permissions.map(p => [p.id, p])),
|
|
66
|
+
commandAliases: new Map()
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
for (const cmd of commands) {
|
|
70
|
+
const aliases = JSON.parse(cmd.aliases || '[]');
|
|
71
|
+
for (const alias of aliases) {
|
|
72
|
+
config.commandAliases.set(alias, cmd.name);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.botConfigs.set(botId, config);
|
|
77
|
+
console.log(`[BotManager] Configuration for bot ID ${botId} cached successfully.`);
|
|
78
|
+
return config;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`[BotManager] Failed to cache configuration for bot ${botId}:`, error);
|
|
81
|
+
throw new Error(`Failed to load/cache bot configuration for botId ${botId}: ${error.message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
invalidateConfigCache(botId) {
|
|
86
|
+
if (this.botConfigs.has(botId)) {
|
|
87
|
+
this.botConfigs.delete(botId);
|
|
88
|
+
console.log(`[BotManager] Invalidated config cache for bot ID ${botId}. It will be reloaded on next command.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
55
91
|
|
|
56
92
|
triggerHeartbeat() {
|
|
57
93
|
if (this.heartbeatDebounceTimer) {
|
|
@@ -260,6 +296,7 @@ class BotManager {
|
|
|
260
296
|
}
|
|
261
297
|
|
|
262
298
|
await this._syncSystemPermissions(botConfig.id);
|
|
299
|
+
await this.loadConfigForBot(botConfig.id);
|
|
263
300
|
|
|
264
301
|
this.logCache.set(botConfig.id, []);
|
|
265
302
|
this.emitStatusUpdate(botConfig.id, 'starting', '');
|
|
@@ -319,6 +356,7 @@ class BotManager {
|
|
|
319
356
|
const botId = botConfig.id;
|
|
320
357
|
this.bots.delete(botId);
|
|
321
358
|
this.resourceUsage.delete(botId);
|
|
359
|
+
this.botConfigs.delete(botId);
|
|
322
360
|
this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
|
|
323
361
|
this.updateAllResourceUsage();
|
|
324
362
|
});
|
|
@@ -334,83 +372,78 @@ class BotManager {
|
|
|
334
372
|
|
|
335
373
|
async handleCommandValidation(botConfig, message) {
|
|
336
374
|
const { commandName, username, args, typeChat } = message;
|
|
337
|
-
|
|
375
|
+
const botId = botConfig.id;
|
|
376
|
+
|
|
338
377
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
378
|
+
let botConfigCache = this.botConfigs.get(botId);
|
|
379
|
+
if (!botConfigCache) {
|
|
380
|
+
botConfigCache = await this.loadConfigForBot(botId);
|
|
381
|
+
}
|
|
342
382
|
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
botId: botConfig.id,
|
|
346
|
-
OR: [ { name: commandName }, { aliases: { contains: `"${commandName}"` } } ]
|
|
347
|
-
}
|
|
348
|
-
});
|
|
383
|
+
const user = await RealUserService.getUser(username, botId, botConfig);
|
|
384
|
+
const child = this.bots.get(botId);
|
|
349
385
|
|
|
350
|
-
if (!
|
|
386
|
+
if (!child) {
|
|
387
|
+
console.warn(`[BotManager] No running bot process found for botId: ${botId} during command validation. Aborting.`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (user.isBlacklisted) {
|
|
392
|
+
child.send({ type: 'handle_blacklist', commandName, username, typeChat });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
|
|
397
|
+
const dbCommand = botConfigCache.commands.get(mainCommandName);
|
|
398
|
+
|
|
399
|
+
if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
351
402
|
|
|
352
403
|
const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
|
|
353
404
|
if (!allowedTypes.includes(typeChat) && !user.isOwner) {
|
|
354
|
-
|
|
355
405
|
if (typeChat === 'global') {
|
|
356
406
|
return;
|
|
357
407
|
}
|
|
358
|
-
|
|
359
|
-
this._sendThrottledWarning(
|
|
360
|
-
botConfig.id,
|
|
361
|
-
username,
|
|
362
|
-
`wrong_chat:${dbCommand.name}:${typeChat}`,
|
|
363
|
-
`Команду ${dbCommand.name} нельзя использовать в этом типе чата.`,
|
|
364
|
-
typeChat
|
|
365
|
-
);
|
|
408
|
+
child.send({ type: 'handle_wrong_chat', commandName: dbCommand.name, username, typeChat });
|
|
366
409
|
return;
|
|
367
410
|
}
|
|
368
411
|
|
|
369
|
-
const permission = dbCommand.permissionId ?
|
|
412
|
+
const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
|
|
370
413
|
if (permission && !user.hasPermission(permission.name)) {
|
|
371
|
-
|
|
372
|
-
botConfig.id,
|
|
373
|
-
username,
|
|
374
|
-
`no_permission:${dbCommand.name}`,
|
|
375
|
-
`У вас нет прав для выполнения команды ${dbCommand.name}.`,
|
|
376
|
-
typeChat
|
|
377
|
-
);
|
|
414
|
+
child.send({ type: 'handle_permission_error', commandName: dbCommand.name, username, typeChat });
|
|
378
415
|
return;
|
|
379
416
|
}
|
|
380
417
|
|
|
381
418
|
const domain = (permission?.name || '').split('.')[0] || 'user';
|
|
382
419
|
const bypassCooldownPermission = `${domain}.cooldown.bypass`;
|
|
383
420
|
if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
|
|
384
|
-
const cooldownKey = `${
|
|
421
|
+
const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
|
|
385
422
|
const now = Date.now();
|
|
386
423
|
const lastUsed = cooldowns.get(cooldownKey);
|
|
387
424
|
|
|
388
425
|
if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
|
|
389
426
|
const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
|
|
390
|
-
|
|
391
|
-
this._sendThrottledWarning(
|
|
392
|
-
botConfig.id,
|
|
393
|
-
username,
|
|
394
|
-
`cooldown:${dbCommand.name}`,
|
|
395
|
-
`Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`,
|
|
396
|
-
typeChat
|
|
397
|
-
);
|
|
427
|
+
child.send({ type: 'handle_cooldown', commandName: dbCommand.name, username, typeChat, timeLeft });
|
|
398
428
|
return;
|
|
399
429
|
}
|
|
400
430
|
cooldowns.set(cooldownKey, now);
|
|
401
431
|
}
|
|
402
432
|
|
|
403
|
-
|
|
404
|
-
if (child) {
|
|
405
|
-
child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
|
|
406
|
-
}
|
|
433
|
+
child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
|
|
407
434
|
|
|
408
435
|
} catch (error) {
|
|
409
|
-
console.error(`[BotManager]
|
|
410
|
-
|
|
436
|
+
console.error(`[BotManager] Command validation error for botId: ${botId}`, {
|
|
437
|
+
command: commandName,
|
|
438
|
+
user: username,
|
|
439
|
+
error: error.message,
|
|
440
|
+
stack: error.stack
|
|
441
|
+
});
|
|
442
|
+
this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
|
|
411
443
|
}
|
|
412
444
|
}
|
|
413
445
|
|
|
446
|
+
|
|
414
447
|
async handleCommandRegistration(botId, commandConfig) {
|
|
415
448
|
try {
|
|
416
449
|
let permissionId = null;
|
|
@@ -431,7 +464,10 @@ class BotManager {
|
|
|
431
464
|
permissionId = permission.id;
|
|
432
465
|
}
|
|
433
466
|
|
|
434
|
-
|
|
467
|
+
|
|
468
|
+
const createData = {
|
|
469
|
+
botId,
|
|
470
|
+
name: commandConfig.name,
|
|
435
471
|
description: commandConfig.description,
|
|
436
472
|
aliases: JSON.stringify(commandConfig.aliases || []),
|
|
437
473
|
owner: commandConfig.owner,
|
|
@@ -439,16 +475,20 @@ class BotManager {
|
|
|
439
475
|
allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
|
|
440
476
|
cooldown: commandConfig.cooldown || 0,
|
|
441
477
|
};
|
|
442
|
-
|
|
478
|
+
|
|
479
|
+
const updateData = {
|
|
480
|
+
description: commandConfig.description,
|
|
481
|
+
owner: commandConfig.owner,
|
|
482
|
+
};
|
|
483
|
+
|
|
443
484
|
await prisma.command.upsert({
|
|
444
485
|
where: { botId_name: { botId, name: commandConfig.name } },
|
|
445
|
-
update:
|
|
446
|
-
create:
|
|
447
|
-
botId,
|
|
448
|
-
name: commandConfig.name,
|
|
449
|
-
...data
|
|
450
|
-
}
|
|
486
|
+
update: updateData,
|
|
487
|
+
create: createData,
|
|
451
488
|
});
|
|
489
|
+
|
|
490
|
+
this.invalidateConfigCache(botId);
|
|
491
|
+
|
|
452
492
|
} catch (error) {
|
|
453
493
|
console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
|
|
454
494
|
}
|
|
@@ -458,6 +498,7 @@ class BotManager {
|
|
|
458
498
|
const child = this.bots.get(botId);
|
|
459
499
|
if (child) {
|
|
460
500
|
child.send({ type: 'stop' });
|
|
501
|
+
this.botConfigs.delete(botId);
|
|
461
502
|
return { success: true };
|
|
462
503
|
}
|
|
463
504
|
return { success: false, message: 'Бот не найден или уже остановлен' };
|
|
@@ -50,7 +50,7 @@ function handleIncomingCommand(type, username, message) {
|
|
|
50
50
|
if (argDef.type === 'number') {
|
|
51
51
|
const numValue = parseFloat(value);
|
|
52
52
|
if (isNaN(numValue)) {
|
|
53
|
-
bot.api.sendMessage(type, `Ошибка:
|
|
53
|
+
bot.api.sendMessage(type, `Ошибка: Аргумент "${argDef.description}" должен быть числом.`, username);
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
value = numValue;
|
|
@@ -61,7 +61,12 @@ function handleIncomingCommand(type, username, message) {
|
|
|
61
61
|
|
|
62
62
|
if (processedArgs[argDef.name] === undefined) {
|
|
63
63
|
if (argDef.required) {
|
|
64
|
+
const usage = commandInstance.args.map(arg => {
|
|
65
|
+
return arg.required ? `<${arg.description}>` : `[${arg.description}]`;
|
|
66
|
+
}).join(' ');
|
|
67
|
+
|
|
64
68
|
bot.api.sendMessage(type, `Ошибка: Необходимо указать: ${argDef.description}`, username);
|
|
69
|
+
bot.api.sendMessage(type, `Использование: ${bot.config.prefix}${commandInstance.name} ${usage}`, username);
|
|
65
70
|
return;
|
|
66
71
|
}
|
|
67
72
|
if (argDef.default !== undefined) {
|
|
@@ -271,6 +276,34 @@ process.on('message', async (message) => {
|
|
|
271
276
|
if (message.username && bot && bot.config) {
|
|
272
277
|
UserService.clearCache(message.username, bot.config.id);
|
|
273
278
|
}
|
|
279
|
+
} else if (message.type === 'handle_permission_error') {
|
|
280
|
+
const { commandName, username, typeChat } = message;
|
|
281
|
+
const commandInstance = bot.commands.get(commandName);
|
|
282
|
+
if (commandInstance) {
|
|
283
|
+
commandInstance.onInsufficientPermissions(bot, typeChat, { username });
|
|
284
|
+
}
|
|
285
|
+
} else if (message.type === 'handle_wrong_chat') {
|
|
286
|
+
const { commandName, username, typeChat } = message;
|
|
287
|
+
const commandInstance = bot.commands.get(commandName);
|
|
288
|
+
if (commandInstance) {
|
|
289
|
+
commandInstance.onWrongChatType(bot, typeChat, { username });
|
|
290
|
+
}
|
|
291
|
+
} else if (message.type === 'handle_cooldown') {
|
|
292
|
+
const { commandName, username, typeChat, timeLeft } = message;
|
|
293
|
+
const commandInstance = bot.commands.get(commandName);
|
|
294
|
+
if (commandInstance) {
|
|
295
|
+
commandInstance.onCooldown(bot, typeChat, { username }, timeLeft);
|
|
296
|
+
}
|
|
297
|
+
} else if (message.type === 'handle_blacklist') {
|
|
298
|
+
const { commandName, username, typeChat } = message;
|
|
299
|
+
const commandInstance = bot.commands.get(commandName);
|
|
300
|
+
if (commandInstance) {
|
|
301
|
+
commandInstance.onBlacklisted(bot, typeChat, { username });
|
|
302
|
+
}
|
|
303
|
+
} else if (message.type === 'invalidate_user_cache') {
|
|
304
|
+
if (message.username && bot && bot.config) {
|
|
305
|
+
UserService.clearCache(message.username, bot.config.id);
|
|
306
|
+
}
|
|
274
307
|
}
|
|
275
308
|
});
|
|
276
309
|
|
|
@@ -19,17 +19,6 @@ class Command {
|
|
|
19
19
|
this.allowedChatTypes = allowedChatTypes;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
onInvalidArguments(bot, typeChat, user, error) {
|
|
23
|
-
bot.api.sendMessage(typeChat, `Ошибка: ${error.message}`, user.username);
|
|
24
|
-
|
|
25
|
-
const usage = this.args.map(arg => {
|
|
26
|
-
const part = arg.required ? `<${arg.description}>` : `[${arg.description}]`;
|
|
27
|
-
return part;
|
|
28
|
-
}).join(' ');
|
|
29
|
-
|
|
30
|
-
bot.api.sendMessage(typeChat, `Использование: ${bot.config.prefix}${this.name} ${usage}`, user.username);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
22
|
onInsufficientPermissions(bot, typeChat, user) {
|
|
34
23
|
bot.api.sendMessage(typeChat, `У вас нет прав для выполнения команды ${this.name}.`, user.username);
|
|
35
24
|
}
|
|
@@ -26,13 +26,14 @@ class CommandManager {
|
|
|
26
26
|
const commandInstance = new CommandClass();
|
|
27
27
|
const config = {
|
|
28
28
|
name: commandInstance.name,
|
|
29
|
-
argsCount: commandInstance.argsCount,
|
|
30
|
-
permissions: commandInstance.permissions,
|
|
31
|
-
cooldown: commandInstance.cooldown,
|
|
32
|
-
isActive: commandInstance.isActive,
|
|
33
29
|
description: commandInstance.description || '',
|
|
34
30
|
aliases: commandInstance.aliases || [],
|
|
35
31
|
owner: commandInstance.owner || 'system',
|
|
32
|
+
permissions: commandInstance.permissions,
|
|
33
|
+
allowedChatTypes: commandInstance.allowedChatTypes || ['chat', 'private'],
|
|
34
|
+
cooldown: commandInstance.cooldown || 0,
|
|
35
|
+
args: commandInstance.args || [],
|
|
36
|
+
isActive: commandInstance.isActive !== undefined ? commandInstance.isActive : true,
|
|
36
37
|
};
|
|
37
38
|
|
|
38
39
|
if (config.name) {
|