blockmine 1.4.8 → 1.5.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.
Files changed (29) hide show
  1. package/backend/package.json +5 -0
  2. package/backend/prisma/migrations/20250627144030_add_visual_editor_fields/migration.sql +26 -0
  3. package/backend/prisma/migrations/20250628113254_add_event_graphs/migration.sql +14 -0
  4. package/backend/prisma/migrations/20250628122517_added_eventgraph_name/migration.sql +26 -0
  5. package/backend/prisma/migrations/20250628122710_complex_events/migration.sql +36 -0
  6. package/backend/prisma/migrations/migration_lock.toml +2 -2
  7. package/backend/prisma/schema.prisma +45 -14
  8. package/backend/src/api/routes/bots.js +530 -286
  9. package/backend/src/api/routes/eventGraphs.js +375 -0
  10. package/backend/src/api/routes/plugins.js +5 -3
  11. package/backend/src/core/BotManager.js +297 -170
  12. package/backend/src/core/BotProcess.js +312 -44
  13. package/backend/src/core/EventGraphManager.js +164 -0
  14. package/backend/src/core/GraphExecutionEngine.js +706 -0
  15. package/backend/src/core/NodeRegistry.js +888 -0
  16. package/backend/src/core/PluginManager.js +12 -2
  17. package/backend/src/core/UserService.js +15 -2
  18. package/backend/src/core/services.js +12 -0
  19. package/backend/src/core/system/CommandManager.js +2 -0
  20. package/backend/src/lib/logger.js +15 -0
  21. package/backend/src/server.js +12 -4
  22. package/frontend/dist/assets/index-CY4JKfFL.js +8203 -0
  23. package/frontend/dist/assets/index-DC4RjP6E.css +1 -0
  24. package/frontend/dist/index.html +2 -2
  25. package/frontend/package.json +4 -0
  26. package/package.json +7 -2
  27. package/test_visual_command.json +9 -0
  28. package/frontend/dist/assets/index-CLCxr_rh.js +0 -8179
  29. package/frontend/dist/assets/index-Dk9CeSuD.css +0 -1
@@ -2,17 +2,22 @@ const mineflayer = require('mineflayer');
2
2
  const { SocksClient } = require('socks');
3
3
  const EventEmitter = require('events');
4
4
  const { v4: uuidv4 } = require('uuid');
5
+ const { Vec3 } = require('vec3');
6
+ const { PrismaClient } = require('@prisma/client');
5
7
  const { loadCommands } = require('./system/CommandRegistry');
6
8
  const { initializePlugins } = require('./PluginLoader');
7
9
  const MessageQueue = require('./MessageQueue');
8
10
  const Command = require('./system/Command');
9
11
  const { parseArguments } = require('./system/parseArguments');
12
+ const GraphExecutionEngine = require('./GraphExecutionEngine');
13
+ const NodeRegistry = require('./NodeRegistry');
10
14
 
11
15
  const UserService = require('./ipc/UserService.stub.js');
12
16
  const PermissionManager = require('./ipc/PermissionManager.stub.js');
13
17
 
14
18
  let bot = null;
15
- const pendingRequests = new Map();
19
+ const pendingRequests = new Map();
20
+ const entityMoveThrottles = new Map();
16
21
 
17
22
  function sendLog(content) {
18
23
  if (process.send) {
@@ -22,7 +27,15 @@ function sendLog(content) {
22
27
  }
23
28
  }
24
29
 
30
+
31
+ function sendEvent(eventName, eventArgs) {
32
+ if (process.send) {
33
+ process.send({ type: 'event', eventType: eventName, args: eventArgs });
34
+ }
35
+ }
36
+
25
37
  async function fetchNewConfig(botId) {
38
+ const prisma = new PrismaClient();
26
39
  try {
27
40
  const botData = await prisma.bot.findUnique({
28
41
  where: { id: botId },
@@ -42,6 +55,8 @@ async function fetchNewConfig(botId) {
42
55
  } catch (error) {
43
56
  sendLog(`[fetchNewConfig] Error: ${error.message}`);
44
57
  return null;
58
+ } finally {
59
+ await prisma.$disconnect();
45
60
  }
46
61
  }
47
62
 
@@ -63,7 +78,7 @@ function handleIncomingCommand(type, username, message) {
63
78
  const parsedArgs = parseArguments(restOfMessage);
64
79
  let currentArgIndex = 0;
65
80
 
66
- for (const argDef of commandInstance.args) {
81
+ for (const argDef of commandInstance.isVisual ? JSON.parse(dbCommand.argumentsJson || '[]') : commandInstance.args) {
67
82
  if (argDef.type === 'greedy_string') {
68
83
  if (currentArgIndex < parsedArgs.length) {
69
84
  processedArgs[argDef.name] = parsedArgs.slice(currentArgIndex).join(' ');
@@ -86,10 +101,10 @@ function handleIncomingCommand(type, username, message) {
86
101
  if (processedArgs[argDef.name] === undefined) {
87
102
  if (argDef.required) {
88
103
  const usage = commandInstance.args.map(arg => {
89
- return arg.required ? `<${arg.description}>` : `[${arg.description}]`;
104
+ return arg.required ? `<${arg.description || arg.name}>` : `[${arg.description || arg.name}]`;
90
105
  }).join(' ');
91
106
 
92
- bot.api.sendMessage(type, `Ошибка: Необходимо указать: ${argDef.description}`, username);
107
+ bot.api.sendMessage(type, `Ошибка: Необходимо указать: ${argDef.description || argDef.name}`, username);
93
108
  bot.api.sendMessage(type, `Использование: ${bot.config.prefix}${commandInstance.name} ${usage}`, username);
94
109
  return;
95
110
  }
@@ -124,6 +139,15 @@ process.on('message', async (message) => {
124
139
  }
125
140
  pendingRequests.delete(message.requestId);
126
141
  }
142
+ } else if (message.type === 'system:get_player_list') {
143
+ const playerList = bot ? Object.keys(bot.players) : [];
144
+ if (process.send) {
145
+ process.send({
146
+ type: 'get_player_list_response',
147
+ requestId: message.requestId,
148
+ payload: { players: playerList }
149
+ });
150
+ }
127
151
  } else if (message.type === 'start') {
128
152
  const config = message.config;
129
153
  sendLog(`[System] Получена команда на запуск бота ${config.username}...`);
@@ -168,6 +192,8 @@ process.on('message', async (message) => {
168
192
 
169
193
  bot = mineflayer.createBot(botOptions);
170
194
 
195
+ let isReady = false;
196
+
171
197
  bot.events = new EventEmitter();
172
198
  bot.events.setMaxListeners(30);
173
199
  bot.config = config;
@@ -180,31 +206,107 @@ process.on('message', async (message) => {
180
206
  events: bot.events,
181
207
  sendMessage: (type, message, username) => bot.messageQueue.enqueue(type, message, username),
182
208
  sendMessageAndWaitForReply: (command, patterns, timeout) => bot.messageQueue.enqueueAndWait(command, patterns, timeout),
183
- getUser: (username) => UserService.getUser(username, bot.config.id, bot.config),
209
+ getUser: async (username) => {
210
+ const userData = await UserService.getUser(username, bot.config.id, bot.config);
211
+ if (!userData) return null;
212
+
213
+ return {
214
+ ...userData,
215
+ addGroup: (group) => bot.api.performUserAction(username, 'addGroup', { group }),
216
+ removeGroup: (group) => bot.api.performUserAction(username, 'removeGroup', { group }),
217
+ addPermission: (permission) => bot.api.performUserAction(username, 'addPermission', { permission }),
218
+ removePermission: (permission) => bot.api.performUserAction(username, 'removePermission', { permission }),
219
+ getGroups: () => bot.api.performUserAction(username, 'getGroups'),
220
+ getPermissions: () => bot.api.performUserAction(username, 'getPermissions'),
221
+ isBlacklisted: () => bot.api.performUserAction(username, 'isBlacklisted'),
222
+ setBlacklisted: (value) => bot.api.performUserAction(username, 'setBlacklisted', { value }),
223
+ };
224
+ },
184
225
  registerPermissions: (permissions) => PermissionManager.registerPermissions(bot.config.id, permissions),
185
226
  registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
186
227
  addPermissionsToGroup: (groupName, permissionNames) => PermissionManager.addPermissionsToGroup(bot.config.id, groupName, permissionNames),
187
228
  installedPlugins: installedPluginNames,
188
- registerCommand: (commandInstance) => {
189
- if (!(commandInstance instanceof Command)) {
190
- throw new Error('registerCommand ожидает экземпляр класса Command.');
191
- }
192
- bot.commands.set(commandInstance.name, commandInstance);
193
- if (process.send) {
194
- process.send({
195
- type: 'register_command',
196
- commandConfig: {
197
- name: commandInstance.name,
198
- description: commandInstance.description,
199
- aliases: commandInstance.aliases,
200
- owner: commandInstance.owner,
201
- permissions: commandInstance.permissions,
202
- cooldown: commandInstance.cooldown,
203
- allowedChatTypes: commandInstance.allowedChatTypes,
229
+ registerCommand: async (command) => { // похуй. пляшем
230
+ const prisma = new PrismaClient();
231
+ try {
232
+ let permissionId = null;
233
+ if (command.permissions) {
234
+ const permission = await prisma.permission.findUnique({
235
+ where: {
236
+ botId_name: {
237
+ botId: bot.config.id,
238
+ name: command.permissions,
239
+ },
240
+ },
241
+ });
242
+
243
+ if (permission) {
244
+ permissionId = permission.id;
245
+ } else {
246
+ sendLog(`[API] Внимание: право "${command.permissions}" не найдено для команды "${command.name}". Команда будет создана без привязанного права.`);
204
247
  }
248
+ }
249
+
250
+ const commandData = {
251
+ botId: bot.config.id,
252
+ name: command.name,
253
+ description: command.description || '',
254
+ owner: command.owner || 'unknown',
255
+ permissionId: permissionId,
256
+ cooldown: command.cooldown || 0,
257
+ isEnabled: command.isActive !== undefined ? command.isActive : true,
258
+ aliases: JSON.stringify(command.aliases || []),
259
+ allowedChatTypes: JSON.stringify(command.allowedChatTypes || ['chat', 'private']),
260
+ argumentsJson: JSON.stringify(command.args || []),
261
+ };
262
+
263
+ await prisma.command.upsert({
264
+ where: {
265
+ botId_name: {
266
+ botId: commandData.botId,
267
+ name: commandData.name,
268
+ }
269
+ },
270
+ update: {
271
+ description: commandData.description,
272
+ aliases: commandData.aliases,
273
+ allowedChatTypes: commandData.allowedChatTypes,
274
+ cooldown: commandData.cooldown,
275
+ isEnabled: commandData.isEnabled,
276
+ argumentsJson: commandData.argumentsJson,
277
+ permissionId: commandData.permissionId,
278
+ },
279
+ create: commandData,
205
280
  });
281
+
282
+ if (process.send) {
283
+ process.send({
284
+ type: 'register_command',
285
+ commandConfig: {
286
+ name: command.name,
287
+ description: command.description,
288
+ aliases: command.aliases,
289
+ owner: command.owner,
290
+ permissions: command.permissions,
291
+ cooldown: command.cooldown,
292
+ allowedChatTypes: command.allowedChatTypes,
293
+ }
294
+ });
295
+ }
296
+ sendLog(`[API] Команда "${command.name}" от плагина "${command.owner}" зарегистрирована в процессе.`);
297
+
298
+ if (!bot.commands) bot.commands = new Map();
299
+ bot.commands.set(command.name, command);
300
+ if (Array.isArray(command.aliases)) {
301
+ for (const alias of command.aliases) {
302
+ bot.commands.set(alias, command);
303
+ }
304
+ }
305
+ } catch (error) {
306
+ sendLog(`[API] Ошибка при регистрации команды: ${error.message}`);
307
+ } finally {
308
+ await prisma.$disconnect();
206
309
  }
207
- sendLog(`[API] Команда \"${commandInstance.name}\" от плагина \"${commandInstance.owner}\" зарегистрирована в процессе.`);
208
310
  },
209
311
  performUserAction: (username, action, data = {}) => {
210
312
  return new Promise((resolve, reject) => {
@@ -230,12 +332,75 @@ process.on('message', async (message) => {
230
332
  reject(new Error('Request to main process timed out.'));
231
333
  pendingRequests.delete(requestId);
232
334
  }
233
- }, 5000);
335
+ }, 10000);
234
336
  });
337
+ },
338
+ executeCommand: (command) => {
339
+ sendLog(`[Graph] Выполнение серверной команды: ${command}`);
340
+ bot.chat(command);
341
+ },
342
+ lookAt: (position) => {
343
+ if (bot && position) {
344
+ bot.lookAt(position);
345
+ }
346
+ }
347
+ };
348
+
349
+ const processApi = {
350
+ appendLog: (botId, message) => {
351
+ if (process.send) {
352
+ process.send({ type: 'log', content: message });
353
+ }
235
354
  }
236
355
  };
237
356
 
357
+ bot.graphExecutionEngine = new GraphExecutionEngine(NodeRegistry, processApi);
358
+
238
359
  bot.commands = await loadCommands();
360
+
361
+ const prisma = new PrismaClient();
362
+ const dbCommands = await prisma.command.findMany({ where: { botId: config.id } });
363
+ await prisma.$disconnect();
364
+
365
+ for (const dbCommand of dbCommands) {
366
+ if (!dbCommand.isEnabled) {
367
+ if (bot.commands.has(dbCommand.name)) {
368
+ bot.commands.delete(dbCommand.name);
369
+ }
370
+ continue;
371
+ }
372
+
373
+ const existingCommand = bot.commands.get(dbCommand.name);
374
+
375
+ if (existingCommand) {
376
+ existingCommand.description = dbCommand.description;
377
+ existingCommand.cooldown = dbCommand.cooldown;
378
+ existingCommand.aliases = JSON.parse(dbCommand.aliases || '[]');
379
+ existingCommand.permissionId = dbCommand.permissionId;
380
+ existingCommand.allowedChatTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
381
+ } else if (dbCommand.isVisual) {
382
+ const visualCommand = new Command({
383
+ name: dbCommand.name,
384
+ description: dbCommand.description,
385
+ aliases: JSON.parse(dbCommand.aliases || '[]'),
386
+ cooldown: dbCommand.cooldown,
387
+ allowedChatTypes: JSON.parse(dbCommand.allowedChatTypes || '[]'),
388
+ args: JSON.parse(dbCommand.argumentsJson || '[]'),
389
+ owner: 'visual_editor',
390
+ });
391
+ visualCommand.permissionId = dbCommand.permissionId;
392
+ visualCommand.graphJson = dbCommand.graphJson;
393
+ visualCommand.owner = 'visual_editor';
394
+ visualCommand.handler = (botInstance, typeChat, user, args) => {
395
+ const playerList = bot ? Object.keys(bot.players) : [];
396
+ const botState = bot ? { yaw: bot.entity.yaw, pitch: bot.entity.pitch } : {};
397
+ const context = { bot: botInstance.api, user, args, typeChat, players: playerList, botState };
398
+ return bot.graphExecutionEngine.execute(visualCommand.graphJson, context);
399
+ };
400
+ bot.commands.set(visualCommand.name, visualCommand);
401
+ }
402
+ }
403
+
239
404
  if (process.send) {
240
405
  for (const cmd of bot.commands.values()) {
241
406
  process.send({
@@ -256,16 +421,8 @@ process.on('message', async (message) => {
256
421
  await initializePlugins(bot, config.plugins);
257
422
  sendLog('[System] Все системы инициализированы.');
258
423
 
259
-
260
424
  let messageHandledByCustomParser = false;
261
425
 
262
- bot.events.on('chat:message', (data) => {
263
- const { type, username, message } = data;
264
- if (username && message) {
265
- messageHandledByCustomParser = true;
266
- handleIncomingCommand(type, username, message);
267
- }
268
- });
269
426
  bot.on('message', (jsonMsg) => {
270
427
  const ansiMessage = jsonMsg.toAnsi();
271
428
  if (ansiMessage.trim()) {
@@ -276,32 +433,107 @@ process.on('message', async (message) => {
276
433
  const rawMessageText = jsonMsg.toString();
277
434
  bot.events.emit('core:raw_message', rawMessageText, jsonMsg);
278
435
  });
436
+
437
+ bot.events.on('chat:message', (data) => {
438
+ messageHandledByCustomParser = true;
439
+ sendEvent('chat', {
440
+ username: data.username,
441
+ message: data.message,
442
+ chatType: data.type,
443
+ raw: data.raw,
444
+ });
445
+ handleIncomingCommand(data.type, data.username, data.message);
446
+ });
447
+
279
448
  bot.on('chat', (username, message) => {
280
- if (messageHandledByCustomParser) {
281
- return;
282
- }
449
+ if (messageHandledByCustomParser) return;
283
450
  handleIncomingCommand('chat', username, message);
284
451
  });
452
+
453
+ bot.on('whisper', (username, message) => {
454
+ if (messageHandledByCustomParser) return;
455
+ handleIncomingCommand('whisper', username, message);
456
+ });
457
+
458
+ bot.on('userAction', async ({ action, target, ...data }) => {
459
+ if (!target) return;
460
+
461
+ try {
462
+ switch (action) {
463
+ case 'addGroup':
464
+ if (data.group) {
465
+ await bot.api.performUserAction(target, 'addGroup', { group: data.group });
466
+ }
467
+ break;
468
+ case 'removeGroup':
469
+ if (data.group) {
470
+ await bot.api.performUserAction(target, 'removeGroup', { group: data.group });
471
+ }
472
+ break;
473
+ }
474
+ } catch (error) {
475
+ sendLog(`Ошибка при обработке userAction: ${error.message}`);
476
+ }
477
+ });
478
+
285
479
  bot.on('login', () => {
286
480
  sendLog('[Event: login] Успешно залогинился!');
287
481
  if (process.send) {
482
+ process.send({ type: 'bot_ready' });
288
483
  process.send({ type: 'status', status: 'running' });
289
484
  }
290
485
  });
291
- bot.on('spawn', () => {
292
- sendLog('[Event: spawn] Бот заспавнился в мире. Полностью готов к работе!');
293
- });
486
+
294
487
  bot.on('kicked', (reason) => {
295
488
  let reasonText;
296
489
  try { reasonText = JSON.parse(reason).text || reason; } catch (e) { reasonText = reason; }
297
490
  sendLog(`[Event: kicked] Меня кикнули. Причина: ${reasonText}.`);
298
491
  process.exit(0);
299
492
  });
493
+
300
494
  bot.on('error', (err) => sendLog(`[Event: error] Произошла ошибка: ${err.stack || err.message}`));
495
+
301
496
  bot.on('end', (reason) => {
302
497
  sendLog(`[Event: end] Отключен от сервера. Причина: ${reason}`);
303
498
  process.exit(0);
304
499
  });
500
+
501
+ bot.on('playerJoined', (player) => {
502
+ if (!isReady) return;
503
+ sendEvent('playerJoined', { user: { username: player.username, uuid: player.uuid } });
504
+ });
505
+
506
+ bot.on('playerLeft', (player) => {
507
+ if (!isReady) return;
508
+ sendEvent('playerLeft', { user: { username: player.username, uuid: player.uuid } });
509
+ });
510
+
511
+ bot.on('entitySpawn', (entity) => {
512
+ const serialized = serializeEntity(entity);
513
+ sendEvent('entitySpawn', { entity: serialized });
514
+ });
515
+
516
+ bot.on('entityMoved', (entity) => {
517
+ const now = Date.now();
518
+ const lastSent = entityMoveThrottles.get(entity.id);
519
+ if (!lastSent || now - lastSent > 500) {
520
+ entityMoveThrottles.set(entity.id, now);
521
+ sendEvent('entityMoved', { entity: serializeEntity(entity) });
522
+ }
523
+ });
524
+
525
+ bot.on('entityGone', (entity) => {
526
+ sendEvent('entityGone', { entity: serializeEntity(entity) });
527
+ entityMoveThrottles.delete(entity.id);
528
+ });
529
+
530
+ bot.on('spawn', () => {
531
+ sendLog('[Event: spawn] Бот заспавнился в мире.');
532
+ setTimeout(() => {
533
+ isReady = true;
534
+ sendLog('[BotProcess] Бот готов к приему событий playerJoined/playerLeft.');
535
+ }, 3000);
536
+ });
305
537
  } catch (err) {
306
538
  sendLog(`[CRITICAL] Критическая ошибка при создании бота: ${err.stack}`);
307
539
  process.exit(1);
@@ -312,13 +544,10 @@ process.on('message', async (message) => {
312
544
  const newConfig = await fetchNewConfig(bot.config.id);
313
545
  if (newConfig) {
314
546
  bot.config = { ...bot.config, ...newConfig };
315
-
316
547
  const newCommands = await loadCommands();
317
548
  const newPlugins = bot.config.plugins;
318
-
319
549
  bot.commands = newCommands;
320
550
  await initializePlugins(bot, newPlugins, true);
321
-
322
551
  sendLog('[System] Bot configuration and plugins reloaded successfully.');
323
552
  } else {
324
553
  sendLog('[System] Failed to fetch new configuration.');
@@ -371,9 +600,30 @@ process.on('message', async (message) => {
371
600
  if (commandInstance) {
372
601
  commandInstance.onBlacklisted(bot, typeChat, { username });
373
602
  }
374
- } else if (message.type === 'invalidate_user_cache') {
375
- if (message.username && bot && bot.config) {
376
- UserService.clearCache(message.username, bot.config.id);
603
+ } else if (message.type === 'action') {
604
+ if (message.name === 'lookAt' && bot && message.payload.position) {
605
+ const { x, y, z } = message.payload.position;
606
+ if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {
607
+ bot.lookAt(new Vec3(x, y, z));
608
+ } else {
609
+ sendLog(`[BotProcess] Ошибка lookAt: получены невалидные координаты: ${JSON.stringify(message.payload.position)}`);
610
+ }
611
+ }
612
+ } else if (message.type === 'plugins:reload') {
613
+ sendLog('[System] Получена команда на перезагрузку плагинов...');
614
+ const newConfig = await fetchNewConfig(bot.config.id);
615
+ if (newConfig) {
616
+ bot.config.plugins = newConfig.installedPlugins;
617
+ bot.commands.clear();
618
+ await loadCommands(bot, newConfig.commands);
619
+ await initializePlugins(bot, newConfig.installedPlugins);
620
+ sendLog('[System] Плагины успешно перезагружены.');
621
+ } else {
622
+ sendLog('[System] Не удалось получить новую конфигурацию для перезагрузки плагинов.');
623
+ }
624
+ } else if (message.type === 'server_command') {
625
+ if (bot && message.payload && message.payload.command) {
626
+ bot.chat(message.payload.command);
377
627
  }
378
628
  }
379
629
  });
@@ -381,6 +631,24 @@ process.on('message', async (message) => {
381
631
  process.on('unhandledRejection', (reason, promise) => {
382
632
  const errorMsg = `[FATAL] Необработанная ошибка процесса: ${reason?.stack || reason}`;
383
633
  sendLog(errorMsg);
384
- console.error(errorMsg, promise);
385
634
  process.exit(1);
386
635
  });
636
+
637
+ function serializeEntity(entity) {
638
+ if (!entity) return null;
639
+ return {
640
+ id: entity.id,
641
+ type: entity.type,
642
+ username: entity.username,
643
+ displayName: entity.displayName,
644
+ position: entity.position,
645
+ yaw: entity.yaw,
646
+ pitch: entity.pitch,
647
+ onGround: entity.onGround,
648
+ isValid: entity.isValid,
649
+ heldItem: entity.heldItem,
650
+ equipment: entity.equipment,
651
+ metadata: entity.metadata
652
+ };
653
+ }
654
+
@@ -0,0 +1,164 @@
1
+ const { PrismaClient } = require('@prisma/client');
2
+
3
+ const prisma = new PrismaClient();
4
+
5
+ class EventGraphManager {
6
+ constructor(botManager) {
7
+ this.botManager = botManager;
8
+ this.graphEngine = botManager.graphEngine;
9
+ this.activeGraphs = new Map();
10
+ // Хранилище для состояний (переменных) каждого графа
11
+ this.graphStates = new Map();
12
+ }
13
+
14
+ async loadGraphsForBot(botId) {
15
+ console.log(`[EventGraphs] Загрузка графов для бота ${botId}...`);
16
+ const botGraphs = await prisma.eventGraph.findMany({
17
+ where: { botId, isEnabled: true },
18
+ include: { triggers: true },
19
+ });
20
+
21
+ const graphsByEvent = new Map();
22
+ for (const graph of botGraphs) {
23
+ if (!graph.triggers || graph.triggers.length === 0 || !graph.graphJson) continue;
24
+
25
+ try {
26
+ const parsedGraph = JSON.parse(graph.graphJson);
27
+ if (!parsedGraph.nodes) continue;
28
+
29
+ // Инициализация начального состояния переменных для этого графа
30
+ const initialState = {};
31
+ if (graph.variables) {
32
+ try {
33
+ const parsedVars = JSON.parse(graph.variables);
34
+ for (const v of parsedVars) {
35
+ let val;
36
+ switch (v.type) {
37
+ case 'number': val = Number(v.value) || 0; break;
38
+ case 'boolean': val = v.value === 'true'; break;
39
+ case 'array': try { val = Array.isArray(JSON.parse(v.value)) ? JSON.parse(v.value) : []; } catch { val = []; } break;
40
+ default: val = v.value;
41
+ }
42
+ initialState[v.name] = val;
43
+ }
44
+ } catch (e) {
45
+ console.error(`[EventGraphs] Ошибка парсинга переменных графа ID ${graph.id}:`, e);
46
+ }
47
+ }
48
+ this.graphStates.set(`${botId}-${graph.id}`, initialState);
49
+
50
+ for (const trigger of graph.triggers) {
51
+ if (!graphsByEvent.has(trigger.eventType)) {
52
+ graphsByEvent.set(trigger.eventType, []);
53
+ }
54
+ graphsByEvent.get(trigger.eventType).push({
55
+ id: graph.id,
56
+ name: graph.name,
57
+ nodes: parsedGraph.nodes,
58
+ connections: parsedGraph.connections,
59
+ });
60
+ }
61
+ } catch (e) {
62
+ console.error(`[EventGraphs] Ошибка парсинга JSON для графа ID ${graph.id}:`, e);
63
+ }
64
+ }
65
+
66
+ this.activeGraphs.set(botId, graphsByEvent);
67
+ console.log(`[EventGraphs] Загружено ${botGraphs.length} графов, сгруппировано по ${graphsByEvent.size} событиям для бота ${botId}.`);
68
+ }
69
+
70
+ unloadGraphsForBot(botId) {
71
+ this.activeGraphs.delete(botId);
72
+ // Также очищаем состояния при выгрузке
73
+ for (const key of this.graphStates.keys()) {
74
+ if (key.startsWith(`${botId}-`)) {
75
+ this.graphStates.delete(key);
76
+ }
77
+ }
78
+ console.log(`[EventGraphs] Графы и их состояния для бота ${botId} выгружены.`);
79
+ }
80
+
81
+ async handleEvent(botId, eventType, args) {
82
+ const graphsForBot = this.activeGraphs.get(botId);
83
+ if (!graphsForBot) return;
84
+
85
+ const graphsToRun = graphsForBot.get(eventType);
86
+ if (!graphsToRun || graphsToRun.length === 0) return;
87
+
88
+ for (const graph of graphsToRun) {
89
+ try {
90
+ await this.executeGraph(botId, eventType, graph, args);
91
+ } catch (error) {
92
+ console.error(`[EventGraphManager] Uncaught error during graph execution for event '${eventType}':`, error);
93
+ this.botManager.appendLog(botId, `[ERROR] Uncaught error in graph execution: ${error.message}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ async executeGraph(botId, eventType, graph, eventArgs) {
99
+ if (!graph || !graph.nodes || graph.nodes.length === 0) return;
100
+
101
+ const players = await this.botManager.getPlayerList(botId);
102
+
103
+ const botApi = {
104
+ sendMessage: (chatType, message, recipient) => {
105
+ this.botManager.sendMessageToBot(botId, message, chatType, recipient);
106
+ },
107
+ executeCommand: (command) => {
108
+ this.botManager.sendMessageToBot(botId, command, 'command');
109
+ },
110
+ lookAt: (position) => {
111
+ this.botManager.lookAt(botId, position);
112
+ },
113
+ getPlayerList: () => players,
114
+ };
115
+
116
+ const stateKey = `${botId}-${graph.id}`;
117
+
118
+ const initialContext = this.getInitialContextForEvent(eventType, eventArgs);
119
+ initialContext.bot = botApi;
120
+ initialContext.botId = botId;
121
+ initialContext.players = players;
122
+ initialContext.botState = eventArgs.botState || {};
123
+ initialContext.variables = { ...(this.graphStates.get(stateKey) || {}) };
124
+
125
+ try {
126
+ const finalContext = await this.graphEngine.execute(graph, initialContext, eventType);
127
+
128
+ if (finalContext && finalContext.variables) {
129
+ this.graphStates.set(stateKey, finalContext.variables);
130
+ }
131
+ } catch (error) {
132
+ console.error(`[EventGraphManager] Error during execution or saving state for graph '${graph.name}'`, error);
133
+ }
134
+ }
135
+
136
+ getInitialContextForEvent(eventType, args) {
137
+ const context = {};
138
+ switch (eventType) {
139
+ case 'chat':
140
+ case 'private':
141
+ case 'global':
142
+ case 'clan':
143
+ context.user = { username: args.username };
144
+ context.username = args.username;
145
+ context.message = args.message;
146
+ context.chat_type = args.chatType;
147
+ break;
148
+ case 'playerJoined':
149
+ case 'playerLeft':
150
+ context.user = args.user;
151
+ break;
152
+ case 'tick':
153
+ break;
154
+ case 'entitySpawn':
155
+ case 'entityMoved':
156
+ case 'entityGone':
157
+ context.entity = args.entity;
158
+ break;
159
+ }
160
+ return context;
161
+ }
162
+ }
163
+
164
+ module.exports = EventGraphManager;