blockmine 1.4.8 → 1.5.1

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.
Files changed (32) hide show
  1. package/README.md +26 -19
  2. package/backend/package.json +5 -0
  3. package/backend/prisma/migrations/20250627144030_add_visual_editor_fields/migration.sql +26 -0
  4. package/backend/prisma/migrations/20250628113254_add_event_graphs/migration.sql +14 -0
  5. package/backend/prisma/migrations/20250628122517_added_eventgraph_name/migration.sql +26 -0
  6. package/backend/prisma/migrations/20250628122710_complex_events/migration.sql +36 -0
  7. package/backend/prisma/migrations/migration_lock.toml +2 -2
  8. package/backend/prisma/schema.prisma +45 -14
  9. package/backend/src/api/routes/bots.js +530 -286
  10. package/backend/src/api/routes/eventGraphs.js +375 -0
  11. package/backend/src/api/routes/plugins.js +5 -3
  12. package/backend/src/core/BotManager.js +297 -170
  13. package/backend/src/core/BotProcess.js +312 -44
  14. package/backend/src/core/EventGraphManager.js +164 -0
  15. package/backend/src/core/GraphExecutionEngine.js +706 -0
  16. package/backend/src/core/NodeRegistry.js +888 -0
  17. package/backend/src/core/PluginManager.js +12 -2
  18. package/backend/src/core/UserService.js +15 -2
  19. package/backend/src/core/services.js +12 -0
  20. package/backend/src/core/system/CommandManager.js +3 -1
  21. package/backend/src/lib/logger.js +15 -0
  22. package/backend/src/server.js +12 -4
  23. package/ecosystem.config.js +11 -0
  24. package/frontend/dist/assets/index-CY4JKfFL.js +8203 -0
  25. package/frontend/dist/assets/index-DC4RjP6E.css +1 -0
  26. package/frontend/dist/index.html +2 -2
  27. package/frontend/package.json +4 -0
  28. package/image/visualcommand.png +0 -0
  29. package/package.json +8 -2
  30. package/test_visual_command.json +9 -0
  31. package/frontend/dist/assets/index-CLCxr_rh.js +0 -8179
  32. package/frontend/dist/assets/index-Dk9CeSuD.css +0 -1
@@ -10,17 +10,17 @@ const os = require('os');
10
10
  const { v4: uuidv4 } = require('uuid');
11
11
  const crypto = require('crypto');
12
12
  const { decrypt } = require('./utils/crypto');
13
+ const EventGraphManager = require('./EventGraphManager');
14
+ const nodeRegistry = require('./NodeRegistry');
15
+ const GraphExecutionEngine = require('./GraphExecutionEngine');
13
16
 
14
-
15
- const RealPermissionManager = require('./PermissionManager');
16
- const RealUserService = require('./UserService');
17
+ const UserService = require('./UserService');
17
18
 
18
19
  const prisma = new PrismaClient();
19
20
  const cooldowns = new Map();
20
21
  const warningCache = new Map();
21
22
  const WARNING_COOLDOWN = 10 * 1000;
22
23
 
23
-
24
24
  const STATS_SERVER_URL = 'http://185.65.200.184:3000';
25
25
  let instanceId = null;
26
26
  const DATA_DIR = path.join(os.homedir(), '.blockmine');
@@ -45,22 +45,31 @@ function getInstanceId() {
45
45
  return instanceId;
46
46
  }
47
47
 
48
-
49
48
  class BotManager {
50
49
  constructor() {
51
50
  this.bots = new Map();
52
51
  this.logCache = new Map();
53
52
  this.resourceUsage = new Map();
54
53
  this.botConfigs = new Map();
54
+ this.nodeRegistry = nodeRegistry;
55
+ this.pendingPlayerListRequests = new Map();
56
+ this.playerListCache = new Map();
57
+ this.graphEngine = new GraphExecutionEngine(this.nodeRegistry, this);
58
+ this.eventGraphManager = null;
55
59
 
56
60
  getInstanceId();
57
-
58
61
  setInterval(() => this.updateAllResourceUsage(), 5000);
59
62
  if (config.telemetry?.enabled) {
60
63
  setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
61
64
  }
62
65
  }
63
66
 
67
+ initialize() {
68
+ if (!this.eventGraphManager) {
69
+ this.eventGraphManager = new EventGraphManager(this);
70
+ }
71
+ }
72
+
64
73
  async loadConfigForBot(botId) {
65
74
  console.log(`[BotManager] Caching configuration for bot ID ${botId}...`);
66
75
  try {
@@ -79,7 +88,6 @@ class BotManager {
79
88
  config.commandAliases.set(alias, cmd.name);
80
89
  }
81
90
  }
82
-
83
91
  this.botConfigs.set(botId, config);
84
92
  console.log(`[BotManager] Configuration for bot ID ${botId} cached successfully.`);
85
93
  return config;
@@ -88,6 +96,10 @@ class BotManager {
88
96
  throw new Error(`Failed to load/cache bot configuration for botId ${botId}: ${error.message}`);
89
97
  }
90
98
  }
99
+
100
+ async _ensureDefaultEventGraphs(botId) {
101
+ return;
102
+ }
91
103
 
92
104
  invalidateConfigCache(botId) {
93
105
  if (this.botConfigs.has(botId)) {
@@ -102,7 +114,6 @@ class BotManager {
102
114
  if (child && !child.killed) {
103
115
  child.send({ type: 'config:reload' });
104
116
  console.log(`[BotManager] Sent config:reload to bot process ${botId}`);
105
-
106
117
  getIO().emit('bot:config_reloaded', { botId });
107
118
  }
108
119
  }
@@ -117,45 +128,22 @@ class BotManager {
117
128
  }, 3000);
118
129
  }
119
130
 
120
-
121
- _sendThrottledWarning(botId, username, warningType, message, typeChat = 'private') {
122
- const cacheKey = `${botId}:${username}:${warningType}`;
123
- const now = Date.now();
124
- const lastWarning = warningCache.get(cacheKey);
125
-
126
- if (!lastWarning || (now - lastWarning > WARNING_COOLDOWN)) {
127
- this.sendMessageToBot(botId, message, typeChat, username);
128
- warningCache.set(cacheKey, now);
129
- }
130
- }
131
-
132
-
133
131
  async sendHeartbeat() {
134
- if (!config.telemetry?.enabled) return;
135
- if (!instanceId) return;
136
-
132
+ if (!config.telemetry?.enabled || !instanceId) return;
137
133
  try {
138
- const runningBots = [];
139
- for (const botProcess of this.bots.values()) {
140
- if (botProcess.botConfig) {
141
- runningBots.push({
142
- username: botProcess.botConfig.username,
143
- serverHost: botProcess.botConfig.server.host,
144
- serverPort: botProcess.botConfig.server.port
145
- });
146
- }
147
- }
134
+ const runningBots = Array.from(this.bots.values())
135
+ .filter(p => p.botConfig)
136
+ .map(p => ({
137
+ username: p.botConfig.username,
138
+ serverHost: p.botConfig.server.host,
139
+ serverPort: p.botConfig.server.port
140
+ }));
148
141
 
149
- if (runningBots.length === 0) {
150
- return;
151
- }
152
-
142
+ if (runningBots.length === 0) return;
153
143
 
154
144
  const challengeRes = await fetch(`${STATS_SERVER_URL}/api/challenge?uuid=${instanceId}`);
155
- if (!challengeRes.ok) {
156
- console.error(`[Telemetry] Сервер статистики вернул ошибку при получении challenge: ${challengeRes.statusText}`);
157
- return;
158
- }
145
+ if (!challengeRes.ok) throw new Error(`Challenge server error: ${challengeRes.statusText}`);
146
+
159
147
  const { challenge, difficulty, prefix } = await challengeRes.json();
160
148
  let nonce = 0;
161
149
  let hash = '';
@@ -180,7 +168,6 @@ class BotManager {
180
168
  }
181
169
  }
182
170
 
183
-
184
171
  async _syncSystemPermissions(botId) {
185
172
  const systemPermissions = [
186
173
  { name: "admin.*", description: "Все права администратора" },
@@ -195,7 +182,6 @@ class BotManager {
195
182
  "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
196
183
  };
197
184
  console.log(`[Permission Sync] Синхронизация системных прав для бота ID ${botId}...`);
198
-
199
185
  for (const perm of systemPermissions) {
200
186
  await prisma.permission.upsert({
201
187
  where: { botId_name: { botId, name: perm.name } },
@@ -203,7 +189,6 @@ class BotManager {
203
189
  create: { ...perm, botId, owner: 'system' }
204
190
  });
205
191
  }
206
-
207
192
  for (const groupName of systemGroups) {
208
193
  await prisma.group.upsert({
209
194
  where: { botId_name: { botId, name: groupName } },
@@ -211,7 +196,6 @@ class BotManager {
211
196
  create: { name: groupName, botId, owner: 'system' }
212
197
  });
213
198
  }
214
-
215
199
  for (const [groupName, permNames] of Object.entries(systemGroupPermissions)) {
216
200
  const group = await prisma.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
217
201
  if (group) {
@@ -238,13 +222,11 @@ class BotManager {
238
222
  }
239
223
  return;
240
224
  }
241
-
242
225
  const pids = Array.from(this.bots.values()).map(child => child.pid).filter(Boolean);
243
226
  if (pids.length === 0) return;
244
227
  try {
245
228
  const stats = await pidusage(pids);
246
229
  const usageData = [];
247
-
248
230
  for (const pid in stats) {
249
231
  if (!stats[pid]) continue;
250
232
  const botId = this.getBotIdByPid(parseInt(pid, 10));
@@ -259,8 +241,7 @@ class BotManager {
259
241
  }
260
242
  }
261
243
  getIO().emit('bots:usage', usageData);
262
- } catch (error) {
263
- }
244
+ } catch (error) {}
264
245
  }
265
246
 
266
247
  getBotIdByPid(pid) {
@@ -287,9 +268,7 @@ class BotManager {
287
268
  }
288
269
 
289
270
  emitStatusUpdate(botId, status, message = null) {
290
- if (message) {
291
- this.appendLog(botId, `[SYSTEM] ${message}`);
292
- }
271
+ if (message) this.appendLog(botId, `[SYSTEM] ${message}`);
293
272
  getIO().emit('bot:status', { botId, status, message });
294
273
  }
295
274
 
@@ -301,13 +280,9 @@ class BotManager {
301
280
  }
302
281
 
303
282
  async startBot(botConfig) {
304
- if (this.bots.has(botConfig.id)) {
305
- const existingProcess = this.bots.get(botConfig.id);
306
- if (existingProcess && !existingProcess.killed) {
307
- console.error(`[BotManager] Попытка повторного запуска уже работающего бота ID: ${botConfig.id}. Запуск отменен.`);
308
- this.appendLog(botConfig.id, `[SYSTEM-ERROR] Попытка повторного запуска. Запуск отменен.`);
309
- return { success: false, message: 'Бот уже запущен или запускается.' };
310
- }
283
+ if (this.bots.has(botConfig.id) && !this.bots.get(botConfig.id).killed) {
284
+ this.appendLog(botConfig.id, `[SYSTEM-ERROR] Попытка повторного запуска. Запуск отменен.`);
285
+ return { success: false, message: 'Бот уже запущен или запускается.' };
311
286
  }
312
287
 
313
288
  await this._syncSystemPermissions(botConfig.id);
@@ -315,98 +290,131 @@ class BotManager {
315
290
  this.logCache.set(botConfig.id, []);
316
291
  this.emitStatusUpdate(botConfig.id, 'starting', '');
317
292
 
318
- const allPluginsForBot = await prisma.installedPlugin.findMany({
319
- where: { botId: botConfig.id },
320
- });
321
- const enabledPlugins = allPluginsForBot.filter(p => p.isEnabled);
322
-
323
- const { sortedPlugins, pluginInfo, hasCriticalIssues } = DependencyService.resolveDependencies(enabledPlugins, allPluginsForBot);
293
+ const allPluginsForBot = await prisma.installedPlugin.findMany({ where: { botId: botConfig.id, isEnabled: true } });
294
+ const { sortedPlugins, hasCriticalIssues, pluginInfo } = DependencyService.resolveDependencies(allPluginsForBot, allPluginsForBot);
295
+
324
296
  if (hasCriticalIssues) {
325
- this.appendLog(botConfig.id, '[DependencyManager] Обнаружены критические проблемы с зависимостями:');
326
- for (const plugin of Object.values(pluginInfo)) {
327
- if (plugin.issues.length > 0) {
328
- this.appendLog(botConfig.id, ` - Плагин "${plugin.name}":`);
329
- for (const issue of plugin.issues) {
330
- const logMessage = ` - [${issue.type.toUpperCase()}] ${issue.message}`;
331
- this.appendLog(botConfig.id, logMessage);
297
+ this.appendLog(botConfig.id, '[DependencyManager] Обнаружены критические проблемы с зависимостями, запуск отменен.');
298
+
299
+ const criticalIssueTypes = new Set(['missing_dependency', 'version_mismatch', 'circular_dependency']);
300
+
301
+ for (const pluginId in pluginInfo) {
302
+ const info = pluginInfo[pluginId];
303
+ if (info.issues.length === 0) continue;
304
+
305
+ const criticalIssues = info.issues.filter(issue => criticalIssueTypes.has(issue.type));
306
+
307
+ if (criticalIssues.length > 0) {
308
+ this.appendLog(botConfig.id, `* Плагин "${info.name}":`);
309
+ for (const issue of criticalIssues) {
310
+ this.appendLog(botConfig.id, ` - ${issue.message}`);
332
311
  }
333
312
  }
334
313
  }
335
- this.appendLog(botConfig.id, '[DependencyManager] [FATAL] Запуск отменен.');
314
+
336
315
  this.emitStatusUpdate(botConfig.id, 'stopped', 'Ошибка зависимостей плагинов.');
337
316
  return { success: false, message: 'Критические ошибки в зависимостях плагинов.' };
338
317
  }
339
318
 
340
319
  const decryptedConfig = { ...botConfig };
341
- if (decryptedConfig.password) {
342
- decryptedConfig.password = decrypt(decryptedConfig.password);
343
- }
344
- if (decryptedConfig.proxyPassword) {
345
- decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
346
- }
320
+ if (decryptedConfig.password) decryptedConfig.password = decrypt(decryptedConfig.password);
321
+ if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
347
322
 
348
323
  const fullBotConfig = { ...decryptedConfig, plugins: sortedPlugins };
349
324
  const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
350
325
  const child = fork(botProcessPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
351
326
 
352
327
  child.botConfig = botConfig;
353
- child.on('error', (err) => {
354
- this.appendLog(botConfig.id, `[PROCESS FATAL] КРИТИЧЕСКАЯ ОШИБКА ПРОЦЕССА: ${err.stack}`);
355
- });
356
- child.stderr.on('data', (data) => {
357
- this.appendLog(botConfig.id, `[STDERR] ${data.toString()}`);
358
- });
328
+
359
329
  child.on('message', async (message) => {
360
- if (message.type === 'log') {
361
- this.appendLog(botConfig.id, message.content);
362
- } else if (message.type === 'status') {
363
- this.emitStatusUpdate(botConfig.id, message.status);
364
- } else if (message.type === 'validate_and_run_command') {
365
- await this.handleCommandValidation(botConfig, message);
366
- } else if (message.type === 'register_command') {
367
- await this.handleCommandRegistration(botConfig.id, message.commandConfig);
368
- } else if (message.type === 'request_user_action') {
369
- const { requestId, payload } = message;
370
- const { targetUsername, action, data } = payload;
371
- const botId = botConfig.id;
372
-
373
- try {
374
- const targetUser = await RealUserService.getUser(targetUsername, botId);
375
- let replyPayload = {};
376
-
377
- switch (action) {
378
- case 'toggle_blacklist': {
379
- const isCurrentlyBlacklisted = targetUser.isBlacklisted;
380
- await targetUser.setBlacklist(!isCurrentlyBlacklisted);
381
- replyPayload.newStatus = !isCurrentlyBlacklisted;
382
- break;
330
+ const botId = botConfig.id;
331
+ try {
332
+ switch (message.type) {
333
+ case 'event':
334
+ if (this.eventGraphManager) {
335
+ this.eventGraphManager.handleEvent(botId, message.eventType, message.args);
383
336
  }
384
-
385
- case 'toggle_group': {
386
- if (!data.groupName) throw new Error('Название группы не указано.');
387
- const hasGroup = targetUser.hasGroup(data.groupName);
388
- if (hasGroup) {
389
- await targetUser.removeGroup(data.groupName);
390
- replyPayload.actionTaken = 'removed';
391
- } else {
392
- await targetUser.addGroup(data.groupName);
393
- replyPayload.actionTaken = 'added';
337
+ break;
338
+ case 'log':
339
+ this.appendLog(botId, message.content);
340
+ break;
341
+ case 'status':
342
+ this.emitStatusUpdate(botId, message.status);
343
+ break;
344
+ case 'validate_and_run_command':
345
+ await this.handleCommandValidation(botConfig, message);
346
+ break;
347
+ case 'register_command':
348
+ await this.handleCommandRegistration(botId, message.commandConfig);
349
+ break;
350
+ case 'register_group':
351
+ await this.handleGroupRegistration(botId, message.groupConfig);
352
+ break;
353
+ case 'request_user_action':
354
+ const { requestId, payload } = message;
355
+ const { targetUsername, action, data } = payload;
356
+
357
+ try {
358
+ const user = await UserService.getUser(targetUsername, botConfig.id);
359
+ if (!user) throw new Error(`Пользователь ${targetUsername} не найден.`);
360
+
361
+ let result;
362
+
363
+ switch (action) {
364
+ case 'addGroup':
365
+ result = await user.addGroup(data.group);
366
+ break;
367
+ case 'removeGroup':
368
+ result = await user.removeGroup(data.group);
369
+ break;
370
+ case 'addPermission':
371
+ break;
372
+ case 'removePermission':
373
+ break;
374
+ case 'getGroups':
375
+ result = user.groups;
376
+ break;
377
+ case 'getPermissions':
378
+ result = Array.from(user.permissionsSet);
379
+ break;
380
+ case 'isBlacklisted':
381
+ result = user.isBlacklisted;
382
+ break;
383
+ case 'setBlacklisted':
384
+ result = await user.setBlacklist(data.value);
385
+ break;
386
+ default:
387
+ throw new Error(`Неизвестное действие: ${action}`);
394
388
  }
395
- break;
389
+
390
+ child.send({ type: 'user_action_response', requestId, payload: result });
391
+ } catch (error) {
392
+ console.error(`[BotManager] Ошибка выполнения действия '${action}' для пользователя '${targetUsername}':`, error);
393
+ child.send({ type: 'user_action_response', requestId, error: error.message });
396
394
  }
397
-
398
- default:
399
- throw new Error(`Неизвестное действие: ${action}`);
395
+ break;
396
+ case 'playerListUpdate':
397
+ break;
398
+ case 'get_player_list_response': {
399
+ const { requestId, payload } = message;
400
+ const request = this.pendingPlayerListRequests.get(requestId);
401
+ if (request) {
402
+ clearTimeout(request.timeout);
403
+ request.resolve(payload.players);
404
+ this.pendingPlayerListRequests.delete(requestId);
405
+ }
406
+ break;
400
407
  }
401
-
402
- child.send({ type: 'user_action_response', requestId, payload: replyPayload });
403
-
404
- } catch (error) {
405
- console.error(`[BotManager] Ошибка выполнения действия '${action}' для пользователя '${targetUsername}':`, error);
406
- child.send({ type: 'user_action_response', requestId, error: error.message });
407
408
  }
409
+ } catch (error) {
410
+ this.appendLog(botId, `[SYSTEM-ERROR] Критическая ошибка в обработчике сообщений от бота: ${error.stack}`);
411
+ console.error(`[BotManager] Критическая ошибка в обработчике сообщений от бота ${botId}:`, error);
408
412
  }
409
413
  });
414
+
415
+ child.on('error', (err) => this.appendLog(botConfig.id, `[PROCESS FATAL] ${err.stack}`));
416
+ child.stderr.on('data', (data) => this.appendLog(botConfig.id, `[STDERR] ${data.toString()}`));
417
+
410
418
  child.on('exit', (code, signal) => {
411
419
  const botId = botConfig.id;
412
420
  this.bots.delete(botId);
@@ -415,13 +423,15 @@ class BotManager {
415
423
  this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
416
424
  this.updateAllResourceUsage();
417
425
  });
426
+
418
427
  this.bots.set(botConfig.id, child);
419
428
  child.send({ type: 'start', config: fullBotConfig });
420
429
 
421
- this.triggerHeartbeat();
430
+ await this.eventGraphManager.loadGraphsForBot(botConfig.id);
422
431
 
423
- this.appendLog(botConfig.id, '[SYSTEM] Проверка зависимостей пройдена. Запускаем процесс бота...');
424
- return { success: true, message: 'Бот запускается' };
432
+ this.triggerHeartbeat();
433
+ getIO().emit('bot:status', { botId: botConfig.id, status: 'starting' });
434
+ return child;
425
435
  }
426
436
 
427
437
  async handleCommandValidation(botConfig, message) {
@@ -431,72 +441,99 @@ class BotManager {
431
441
  try {
432
442
  let botConfigCache = this.botConfigs.get(botId);
433
443
  if (!botConfigCache) {
444
+ console.log(`[BotManager] No cache for ${botId}, loading...`);
434
445
  botConfigCache = await this.loadConfigForBot(botId);
435
446
  }
436
-
437
- const user = await RealUserService.getUser(username, botId, botConfig);
447
+
448
+ const user = await UserService.getUser(username, botId, botConfig);
449
+
438
450
  const child = this.bots.get(botId);
439
-
440
- if (!child) {
441
- console.warn(`[BotManager] No running bot process found for botId: ${botId} during command validation. Aborting.`);
442
- return;
443
- }
451
+ if (!child) return;
444
452
 
445
453
  if (user.isBlacklisted) {
446
- child.send({ type: 'handle_blacklist', commandName, username, typeChat });
454
+ this.sendMessageToBot(botId, 'Вы находитесь в черном списке и не можете использовать команды.', 'private', username);
447
455
  return;
448
456
  }
449
-
457
+
450
458
  const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
451
459
  const dbCommand = botConfigCache.commands.get(mainCommandName);
452
-
460
+
453
461
  if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
454
462
  return;
455
463
  }
456
-
464
+
457
465
  const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
458
466
  if (!allowedTypes.includes(typeChat) && !user.isOwner) {
459
- if (typeChat === 'global') {
460
- return;
461
- }
462
- child.send({ type: 'handle_wrong_chat', commandName: dbCommand.name, username, typeChat });
467
+ if (typeChat === 'global') return;
468
+ this.sendMessageToBot(botId, 'Эту команду нельзя использовать в этом чате.', 'private', username);
463
469
  return;
464
470
  }
465
-
471
+
466
472
  const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
467
473
  if (permission && !user.hasPermission(permission.name)) {
468
- child.send({ type: 'handle_permission_error', commandName: dbCommand.name, username, typeChat });
474
+ this.sendMessageToBot(botId, вас нет прав на использование этой команды.', 'private', username);
469
475
  return;
470
476
  }
471
-
477
+
472
478
  const domain = (permission?.name || '').split('.')[0] || 'user';
473
479
  const bypassCooldownPermission = `${domain}.cooldown.bypass`;
480
+
474
481
  if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
475
482
  const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
476
483
  const now = Date.now();
477
484
  const lastUsed = cooldowns.get(cooldownKey);
478
-
485
+
479
486
  if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
480
487
  const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
481
- child.send({ type: 'handle_cooldown', commandName: dbCommand.name, username, typeChat, timeLeft });
488
+ this.sendMessageToBot(botId, `Подождите еще ${timeLeft} сек. перед использованием команды.`, 'private', username);
482
489
  return;
483
490
  }
484
491
  cooldowns.set(cooldownKey, now);
485
492
  }
486
493
 
487
- child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
494
+ if (dbCommand.isVisual) {
495
+ const graph = dbCommand.graphJson;
496
+ if (graph) {
497
+ const players = await this.getPlayerList(botId);
498
+ console.log('[BotManager] Received player list for graph:', players);
499
+
500
+ const botAPI = {
501
+ sendMessage: (type, message, recipient) => {
502
+ this.sendMessageToBot(botId, message, type, recipient);
503
+ },
504
+ executeCommand: (command) => {
505
+ this.sendServerCommandToBot(botId, command);
506
+ },
507
+ };
508
+
509
+ const graphContext = {
510
+ bot: botAPI,
511
+ botId: botId,
512
+ user,
513
+ args,
514
+ typeChat,
515
+ players,
516
+ };
517
+
518
+ try {
519
+ const resultContext = await this.graphEngine.execute(graph, graphContext, 'command');
520
+ } catch (e) {
521
+ console.error(`[BotManager] Ошибка выполнения визуальной команды '${commandName}':`, e);
522
+ this.sendMessageToBot(botId, 'Произошла внутренняя ошибка при выполнении команды.', 'private', username);
523
+ }
524
+ }
525
+ } else {
526
+ child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
527
+ }
528
+
488
529
  } catch (error) {
489
530
  console.error(`[BotManager] Command validation error for botId: ${botId}`, {
490
- command: commandName,
491
- user: username,
492
- error: error.message,
493
- stack: error.stack
531
+ command: commandName, user: username, error: error.message, stack: error.stack
494
532
  });
495
533
  this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
496
534
  }
497
535
  }
498
536
 
499
-
500
537
  async handleCommandRegistration(botId, commandConfig) {
501
538
  try {
502
539
  let permissionId = null;
@@ -516,8 +553,6 @@ class BotManager {
516
553
  }
517
554
  permissionId = permission.id;
518
555
  }
519
-
520
-
521
556
  const createData = {
522
557
  botId,
523
558
  name: commandConfig.name,
@@ -538,15 +573,34 @@ class BotManager {
538
573
  create: createData,
539
574
  });
540
575
  this.invalidateConfigCache(botId);
541
-
542
576
  } catch (error) {
543
577
  console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
544
578
  }
545
579
  }
546
580
 
581
+ async handleGroupRegistration(botId, groupConfig) {
582
+ try {
583
+ await prisma.group.upsert({
584
+ where: { botId_name: { botId, name: groupConfig.name } },
585
+ update: {
586
+ owner: groupConfig.owner,
587
+ },
588
+ create: {
589
+ botId,
590
+ name: groupConfig.name,
591
+ owner: groupConfig.owner,
592
+ },
593
+ });
594
+ this.invalidateConfigCache(botId);
595
+ } catch (error) {
596
+ console.error(`[BotManager] Ошибка при регистрации группы '${groupConfig.name}':`, error);
597
+ }
598
+ }
599
+
547
600
  stopBot(botId) {
548
601
  const child = this.bots.get(botId);
549
602
  if (child) {
603
+ this.eventGraphManager.unloadGraphsForBot(botId);
550
604
  child.send({ type: 'stop' });
551
605
  this.botConfigs.delete(botId);
552
606
  return { success: true };
@@ -564,7 +618,7 @@ class BotManager {
564
618
  }
565
619
 
566
620
  invalidateUserCache(botId, username) {
567
- RealUserService.clearCache(username, botId);
621
+ UserService.clearCache(username, botId);
568
622
  const child = this.bots.get(botId);
569
623
  if (child) {
570
624
  child.send({ type: 'invalidate_user_cache', username });
@@ -572,17 +626,90 @@ class BotManager {
572
626
  return { success: true };
573
627
  }
574
628
 
575
- reloadBotConfigInRealTime(botId) {
576
- this.invalidateConfigCache(botId);
629
+ async getPlayerList(botId) {
630
+ const PLAYER_LIST_CACHE_TTL = 2000;
631
+
632
+ const child = this.bots.get(botId);
633
+ if (!child || child.killed) {
634
+ return [];
635
+ }
636
+
637
+ const cachedEntry = this.playerListCache.get(botId);
638
+ if (cachedEntry && (Date.now() - cachedEntry.timestamp < PLAYER_LIST_CACHE_TTL)) {
639
+ return cachedEntry.promise;
640
+ }
641
+
642
+ const newPromise = new Promise((resolve) => {
643
+ const requestId = uuidv4();
644
+ const timeout = setTimeout(() => {
645
+ this.pendingPlayerListRequests.delete(requestId);
646
+ if (this.playerListCache.get(botId)?.promise === newPromise) {
647
+ this.playerListCache.delete(botId);
648
+ }
649
+ resolve([]);
650
+ }, 5000);
651
+
652
+ this.pendingPlayerListRequests.set(requestId, {
653
+ resolve: (playerList) => {
654
+ clearTimeout(timeout);
655
+ this.pendingPlayerListRequests.delete(requestId);
656
+ this.playerListCache.set(botId, {
657
+ promise: Promise.resolve(playerList),
658
+ timestamp: Date.now()
659
+ });
660
+ resolve(playerList);
661
+ },
662
+ reject: (error) => {
663
+ clearTimeout(timeout);
664
+ this.pendingPlayerListRequests.delete(requestId);
665
+ if (this.playerListCache.get(botId)?.promise === newPromise) {
666
+ this.playerListCache.delete(botId);
667
+ }
668
+ resolve([]);
669
+ },
670
+ });
671
+
672
+ child.send({ type: 'system:get_player_list', requestId });
673
+ });
674
+
675
+ this.playerListCache.set(botId, {
676
+ promise: newPromise,
677
+ timestamp: Date.now()
678
+ });
679
+
680
+ return newPromise;
681
+ }
682
+
683
+ setEventGraphManager(manager) {
684
+ this.eventGraphManager = manager;
685
+ }
686
+
687
+ lookAt(botId, position) {
688
+ const botProcess = this.bots.get(botId);
689
+ if (botProcess && !botProcess.killed) {
690
+ botProcess.send({ type: 'action', name: 'lookAt', payload: { position } });
691
+ } else {
692
+ console.error(`[BotManager] Не удалось найти запущенный процесс для бота ${botId}, чтобы выполнить lookAt.`);
693
+ }
694
+ }
695
+
696
+ async reloadPlugins(botId) {
577
697
  const child = this.bots.get(botId);
578
698
  if (child && !child.killed) {
579
- child.send({ type: 'config:reload' });
580
- console.log(`[BotManager] Sent config:reload to bot process ${botId}`);
581
-
582
- const { getIO } = require('../real-time/socketHandler');
583
- getIO().emit('bot:config_reloaded', { botId });
699
+ child.send({ type: 'plugins:reload' });
700
+ console.log(`[BotManager] Sent plugins:reload to bot process ${botId}`);
701
+ getIO().emit('bot:plugins_reloaded', { botId });
702
+ return { success: true, message: 'Команда на перезагрузку плагинов отправлена.' };
703
+ }
704
+ return { success: false, message: 'Бот не запущен.' };
705
+ }
706
+
707
+ sendServerCommandToBot(botId, command) {
708
+ const child = this.bots.get(botId);
709
+ if (child) {
710
+ child.send({ type: 'server_command', payload: { command } });
584
711
  }
585
712
  }
586
713
  }
587
714
 
588
- module.exports = new BotManager();
715
+ module.exports = BotManager;