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 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>Консоль</strong></p>
23
+ <p><strong>Дашборд</strong></p>
24
24
  <img src="./image/1.png" alt="Скриншот дашборда" width="100%">
25
- <em>Консоль бота</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>Управление</strong></p>
33
+ <p><strong>Управление командами</strong></p>
34
34
  <img src="./image/3.png" alt="Скриншот страницы управления" width="100%">
35
- <em>Настраивайте роли, права и так далее</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
- Это самый простой способ запустить панель у себя на компьютере или сервере. Убедитесь, что у вас установлен **Node.js v18+**.
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
 
@@ -1,5 +1,6 @@
1
1
  generator client {
2
2
  provider = "prisma-client-js"
3
+ binaryTargets = ["native", "linux-arm64-openssl-1.1.x"]
3
4
  }
4
5
 
5
6
  datasource db {
@@ -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
- let permissions = await prisma.permission.findMany({ where: { botId } });
447
+ const templatesMap = new Map(commandTemplates.map(t => [t.name, t]));
447
448
 
448
- for (const template of commandTemplates) {
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
- if (!exists) {
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
- let requiredPermission = permissions.find(p => p.name === template.permissions);
457
- if (!requiredPermission) {
458
- requiredPermission = await prisma.permission.create({
459
- data: {
460
- botId,
461
- name: template.permissions,
462
- description: `Авто-создано для команды ${template.name}`,
463
- owner: template.owner || 'system',
464
- }
465
- });
466
- permissions.push(requiredPermission);
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
- await prisma.command.create({
472
- data: {
473
- botId,
474
- name: template.name,
475
- isEnabled: template.isActive !== undefined ? template.isActive : true,
476
- cooldown: template.cooldown || 0,
477
- aliases: JSON.stringify(template.aliases || []),
478
- description: template.description,
479
- owner: template.owner,
480
- permissionId: permissionId,
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
- const [groups, users, dbCommands, allPermissions] = await Promise.all([
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
- const user = await RealUserService.getUser(username, botConfig.id, botConfig);
340
-
341
- if (user.isBlacklisted) return;
378
+ let botConfigCache = this.botConfigs.get(botId);
379
+ if (!botConfigCache) {
380
+ botConfigCache = await this.loadConfigForBot(botId);
381
+ }
342
382
 
343
- const dbCommand = await prisma.command.findFirst({
344
- where: {
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 (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) return;
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 ? await prisma.permission.findUnique({ where: { id: dbCommand.permissionId } }) : null;
412
+ const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
370
413
  if (permission && !user.hasPermission(permission.name)) {
371
- this._sendThrottledWarning(
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 = `${botConfig.id}:${dbCommand.name}:${user.id}`;
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
- const child = this.bots.get(botConfig.id);
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] Ошибка валидации команды ${commandName}:`, error);
410
- this.sendMessageToBot(botConfig.id, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
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
- const data = {
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: data,
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, `Ошибка: Неверный тип аргумента "${argDef.description}". Ожидалось число.`, username);
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) {