blockmine 1.22.0 → 1.23.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 (102) hide show
  1. package/.claude/settings.json +5 -1
  2. package/.claude/settings.local.json +10 -1
  3. package/CHANGELOG.md +27 -3
  4. package/CLAUDE.md +284 -0
  5. package/README.md +302 -152
  6. package/backend/package-lock.json +681 -9
  7. package/backend/package.json +8 -0
  8. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
  9. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
  10. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
  11. package/backend/prisma/migrations/migration_lock.toml +2 -2
  12. package/backend/prisma/schema.prisma +70 -1
  13. package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
  14. package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
  15. package/backend/src/api/middleware/auth.js +27 -0
  16. package/backend/src/api/middleware/botAccess.js +7 -3
  17. package/backend/src/api/middleware/panelApiAuth.js +135 -0
  18. package/backend/src/api/routes/aiAssistant.js +995 -0
  19. package/backend/src/api/routes/auth.js +669 -633
  20. package/backend/src/api/routes/botCommands.js +107 -0
  21. package/backend/src/api/routes/botGroups.js +165 -0
  22. package/backend/src/api/routes/botHistory.js +108 -0
  23. package/backend/src/api/routes/botPermissions.js +99 -0
  24. package/backend/src/api/routes/botStatus.js +36 -0
  25. package/backend/src/api/routes/botUsers.js +162 -0
  26. package/backend/src/api/routes/bots.js +2451 -2402
  27. package/backend/src/api/routes/eventGraphs.js +4 -1
  28. package/backend/src/api/routes/logs.js +13 -3
  29. package/backend/src/api/routes/panel.js +66 -66
  30. package/backend/src/api/routes/panelApiKeys.js +179 -0
  31. package/backend/src/api/routes/pluginIde.js +1715 -135
  32. package/backend/src/api/routes/plugins.js +376 -219
  33. package/backend/src/api/routes/proxies.js +130 -0
  34. package/backend/src/api/routes/search.js +4 -0
  35. package/backend/src/api/routes/servers.js +20 -3
  36. package/backend/src/api/routes/settings.js +5 -0
  37. package/backend/src/api/routes/system.js +174 -174
  38. package/backend/src/api/routes/traces.js +131 -0
  39. package/backend/src/config/debug.config.js +36 -0
  40. package/backend/src/core/BotHistoryStore.js +180 -0
  41. package/backend/src/core/BotManager.js +14 -4
  42. package/backend/src/core/BotProcess.js +1517 -1092
  43. package/backend/src/core/EventGraphManager.js +37 -123
  44. package/backend/src/core/GraphExecutionEngine.js +977 -321
  45. package/backend/src/core/MessageQueue.js +12 -6
  46. package/backend/src/core/PluginLoader.js +99 -5
  47. package/backend/src/core/PluginManager.js +74 -13
  48. package/backend/src/core/TaskScheduler.js +1 -1
  49. package/backend/src/core/commands/whois.js +1 -1
  50. package/backend/src/core/node-registries/actions.js +70 -0
  51. package/backend/src/core/node-registries/arrays.js +18 -0
  52. package/backend/src/core/node-registries/data.js +1 -1
  53. package/backend/src/core/node-registries/events.js +14 -0
  54. package/backend/src/core/node-registries/logic.js +17 -0
  55. package/backend/src/core/node-registries/strings.js +34 -0
  56. package/backend/src/core/node-registries/type.js +25 -0
  57. package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
  58. package/backend/src/core/nodes/actions/create_command.js +189 -0
  59. package/backend/src/core/nodes/actions/delete_command.js +92 -0
  60. package/backend/src/core/nodes/actions/update_command.js +133 -0
  61. package/backend/src/core/nodes/arrays/join.js +28 -0
  62. package/backend/src/core/nodes/data/cast.js +2 -1
  63. package/backend/src/core/nodes/logic/not.js +22 -0
  64. package/backend/src/core/nodes/strings/starts_with.js +1 -1
  65. package/backend/src/core/nodes/strings/to_lower.js +22 -0
  66. package/backend/src/core/nodes/strings/to_upper.js +22 -0
  67. package/backend/src/core/nodes/type/to_string.js +32 -0
  68. package/backend/src/core/services/BotLifecycleService.js +255 -16
  69. package/backend/src/core/services/CommandExecutionService.js +430 -351
  70. package/backend/src/core/services/DebugSessionManager.js +347 -0
  71. package/backend/src/core/services/GraphCollaborationManager.js +501 -0
  72. package/backend/src/core/services/MinecraftBotManager.js +259 -0
  73. package/backend/src/core/services/MinecraftViewerService.js +216 -0
  74. package/backend/src/core/services/TraceCollectorService.js +545 -0
  75. package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
  76. package/backend/src/core/system/Transport.js +0 -4
  77. package/backend/src/core/validation/nodeSchemas.js +6 -6
  78. package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
  79. package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
  80. package/backend/src/real-time/botApi/utils.js +11 -0
  81. package/backend/src/real-time/panelNamespace.js +387 -0
  82. package/backend/src/real-time/presence.js +7 -2
  83. package/backend/src/real-time/socketHandler.js +395 -4
  84. package/backend/src/server.js +18 -0
  85. package/frontend/dist/assets/index-B1serztM.js +11210 -0
  86. package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
  87. package/frontend/dist/index.html +2 -2
  88. package/frontend/package-lock.json +9437 -0
  89. package/frontend/package.json +8 -0
  90. package/package.json +2 -2
  91. package/screen/console.png +0 -0
  92. package/screen/dashboard.png +0 -0
  93. package/screen/graph_collabe.png +0 -0
  94. package/screen/graph_live_debug.png +0 -0
  95. package/screen/management_command.png +0 -0
  96. package/screen/node_debug_trace.png +0 -0
  97. package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
  98. package/screen/websocket.png +0 -0
  99. package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
  100. package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
  101. package/frontend/dist/assets/index-CfTo92bP.css +0 -1
  102. package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
@@ -1,1092 +1,1517 @@
1
- const mineflayer = require('mineflayer');
2
- const { SocksClient } = require('socks');
3
- const EventEmitter = require('events');
4
- const { v4: uuidv4 } = require('uuid');
5
- const { Vec3 } = require('vec3');
6
- const { PrismaClient } = require('@prisma/client');
7
- const { loadCommands } = require('./system/CommandRegistry');
8
- const { initializePlugins } = require('./PluginLoader');
9
- const MessageQueue = require('./MessageQueue');
10
- const Command = require('./system/Command');
11
- const { parseArguments } = require('./system/parseArguments');
12
- const GraphExecutionEngine = require('./GraphExecutionEngine');
13
- const NodeRegistry = require('./NodeRegistry');
14
-
15
- const UserService = require('./UserService');
16
- const PermissionManager = require('./ipc/PermissionManager.stub.js');
17
- const Transport = require('./system/Transport');
18
- const CommandContext = require('./system/CommandContext');
19
-
20
- let bot = null;
21
- const prisma = new PrismaClient();
22
- const pluginUiState = new Map();
23
- const pendingRequests = new Map();
24
- const entityMoveThrottles = new Map();
25
- let connectionTimeout = null;
26
-
27
- const originalJSONParse = JSON.parse
28
- JSON.parse = function(text, reviver) {
29
- if (typeof text !== 'string') return originalJSONParse(text, reviver)
30
- try {
31
- return originalJSONParse(text, reviver)
32
- } catch (e) {
33
- const fixed = text.replace(/([{,])\s*([a-zA-Z0-9_]+)\s*:/g, '$1"$2":')
34
- return originalJSONParse(fixed, reviver)
35
- }
36
- }
37
-
38
- function sendLog(content) {
39
- if (process.send) {
40
- process.send({ type: 'log', content });
41
- } else {
42
- console.log(`[ChildProcess Log] ${content}`);
43
- }
44
- }
45
-
46
-
47
- function sendEvent(eventName, eventArgs) {
48
- if (process.send) {
49
- // Добавляем информацию о боте (позицию) во все события
50
- const enrichedArgs = {
51
- ...eventArgs,
52
- botEntity: bot && bot.entity ? {
53
- position: bot.entity.position,
54
- yaw: bot.entity.yaw,
55
- pitch: bot.entity.pitch
56
- } : null
57
- };
58
- process.send({ type: 'event', eventType: eventName, args: enrichedArgs });
59
- }
60
- }
61
-
62
- async function fetchNewConfig(botId, prisma) {
63
- try {
64
- const botData = await prisma.bot.findUnique({
65
- where: { id: botId },
66
- include: {
67
- server: true,
68
- installedPlugins: {
69
- where: { isEnabled: true }
70
- },
71
- }
72
- });
73
-
74
- if (!botData) return null;
75
-
76
- const commands = await prisma.command.findMany({ where: { botId } });
77
-
78
- return { ...botData, commands };
79
- } catch (error) {
80
- sendLog(`[fetchNewConfig] Error: ${error.message}`);
81
- return null;
82
- }
83
- }
84
-
85
- function handleIncomingCommand(type, username, message) {
86
- if (!message.startsWith(bot.config.prefix || '@')) return;
87
-
88
- const rawMessage = message.slice((bot.config.prefix || '@').length).trim();
89
- const commandParts = rawMessage.split(/ +/);
90
- const commandName = commandParts.shift().toLowerCase();
91
- const restOfMessage = commandParts.join(' ');
92
-
93
- const commandInstance = bot.commands.get(commandName) ||
94
- Array.from(bot.commands.values()).find(cmd => cmd.aliases.includes(commandName));
95
-
96
- if (!commandInstance) return;
97
-
98
- try {
99
- const processedArgs = {};
100
- const parsedArgs = parseArguments(restOfMessage);
101
- let currentArgIndex = 0;
102
-
103
- const argsDef = commandInstance.isVisual && commandInstance.args ? commandInstance.args : (commandInstance.args || []);
104
- for (const argDef of argsDef) {
105
- if (argDef.type === 'greedy_string') {
106
- if (currentArgIndex < parsedArgs.length) {
107
- processedArgs[argDef.name] = parsedArgs.slice(currentArgIndex).join(' ');
108
- currentArgIndex = parsedArgs.length;
109
- }
110
- } else if (currentArgIndex < parsedArgs.length) {
111
- let value = parsedArgs[currentArgIndex];
112
- if (argDef.type === 'number') {
113
- const numValue = parseFloat(value);
114
- if (isNaN(numValue)) {
115
- bot.api.sendMessage(type, `Ошибка: Аргумент \"${argDef.description}\" должен быть числом.`, username);
116
- return;
117
- }
118
- value = numValue;
119
- }
120
- processedArgs[argDef.name] = value;
121
- currentArgIndex++;
122
- }
123
-
124
- if (processedArgs[argDef.name] === undefined) {
125
- if (argDef.required) {
126
- const usage = commandInstance.args.map(arg => {
127
- return arg.required ? `<${arg.description || arg.name}>` : `[${arg.description || arg.name}]`;
128
- }).join(' ');
129
-
130
- bot.api.sendMessage(type, `Ошибка: Необходимо указать: ${argDef.description || argDef.name}`, username);
131
- bot.api.sendMessage(type, `Использование: ${bot.config.prefix}${commandInstance.name} ${usage}`, username);
132
- return;
133
- }
134
- if (argDef.default !== undefined) {
135
- processedArgs[argDef.name] = argDef.default;
136
- }
137
- }
138
- }
139
-
140
- if (process.send) {
141
- process.send({
142
- type: 'validate_and_run_command',
143
- commandName: commandInstance.name,
144
- username,
145
- args: processedArgs,
146
- typeChat: type
147
- });
148
- }
149
- } catch (e) {
150
- sendLog(`[BotProcess] Ошибка парсинга аргументов: ${e.message}`);
151
- }
152
- }
153
-
154
- process.on('message', async (message) => {
155
- if (message.type === 'plugin:ui:start-updates') {
156
- const { pluginName } = message;
157
- const state = pluginUiState.get(pluginName);
158
- if (state && process.send) {
159
- process.send({
160
- type: 'plugin:data',
161
- plugin: pluginName,
162
- payload: state
163
- });
164
- }
165
- } else if (message.type === 'user_action_response') {
166
- if (pendingRequests.has(message.requestId)) {
167
- const { resolve, reject } = pendingRequests.get(message.requestId);
168
- if (message.error) {
169
- reject(new Error(message.error));
170
- } else {
171
- resolve(message.payload);
172
- }
173
- pendingRequests.delete(message.requestId);
174
- }
175
- } else if (message.type === 'system:get_player_list') {
176
- const playerList = bot ? Object.keys(bot.players) : [];
177
- if (process.send) {
178
- process.send({
179
- type: 'get_player_list_response',
180
- requestId: message.requestId,
181
- payload: { players: playerList }
182
- });
183
- }
184
- } else if (message.type === 'system:get_nearby_entities') {
185
- const entities = [];
186
- if (bot && bot.entities) {
187
- const centerPos = message.payload?.position || bot.entity?.position;
188
- const radius = message.payload?.radius || 32;
189
-
190
- if (centerPos) {
191
- // Перебираем все сущности
192
- for (const entity of Object.values(bot.entities)) {
193
- if (entity && entity.position && entity.isValid) {
194
- // Вычисляем расстояние
195
- const dx = entity.position.x - centerPos.x;
196
- const dy = entity.position.y - centerPos.y;
197
- const dz = entity.position.z - centerPos.z;
198
- const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
199
-
200
- // Если существо в радиусе, добавляем в список
201
- if (distance <= radius) {
202
- entities.push(serializeEntity(entity));
203
- }
204
- }
205
- }
206
- }
207
- }
208
-
209
- if (process.send) {
210
- process.send({
211
- type: 'get_nearby_entities_response',
212
- requestId: message.requestId,
213
- payload: { entities }
214
- });
215
- }
216
- } else if (message.type === 'start') {
217
- const config = message.config;
218
- sendLog(`[System] Получена команда на запуск бота ${config.username}...`);
219
- try {
220
- const botOptions = {
221
- host: config.server.host,
222
- port: config.server.port,
223
- username: config.username,
224
- password: config.password,
225
- version: config.server.version,
226
- auth: 'offline',
227
- hideErrors: false,
228
- chat: 'enabled',
229
- };
230
-
231
- if (config.proxyHost && config.proxyPort) {
232
- sendLog(`[System] Используется прокси: ${config.proxyHost}:${config.proxyPort}`);
233
-
234
- const cleanProxyUsername = config.proxyUsername ? config.proxyUsername.trim() : null;
235
- const cleanProxyPassword = config.proxyPassword || null;
236
-
237
- botOptions.connect = (client) => {
238
- SocksClient.createConnection({
239
- proxy: {
240
- host: config.proxyHost,
241
- port: config.proxyPort,
242
- type: 5,
243
- userId: cleanProxyUsername,
244
- password: cleanProxyPassword
245
- },
246
- command: 'connect',
247
- destination: {
248
- host: config.server.host,
249
- port: config.server.port
250
- }
251
- }).then(info => {
252
- client.setSocket(info.socket);
253
- client.emit('connect');
254
- }).catch(err => {
255
- sendLog(`[Proxy Error] SOCKS connection failed: ${err.message}. Bot will attempt to restart.`);
256
- client.emit('error', err);
257
- process.exit(1);
258
- });
259
- }
260
- } else {
261
- sendLog(`[System] Прокси не настроен, используется прямое подключение.`);
262
- }
263
-
264
- bot = mineflayer.createBot(botOptions);
265
-
266
- connectionTimeout = setTimeout(() => {
267
- if (bot && !bot.player) {
268
- sendLog('[System] Таймаут подключения к серверу (30 секунд). Завершение работы...');
269
- process.exit(1);
270
- }
271
- }, 30000);
272
-
273
- bot.pluginUiState = pluginUiState;
274
-
275
- let isReady = false;
276
-
277
- bot.events = new EventEmitter();
278
- bot.events.setMaxListeners(30);
279
- bot.config = config;
280
- bot.sendLog = sendLog;
281
- bot.messageQueue = new MessageQueue(bot);
282
-
283
- const installedPluginNames = config.plugins.map(p => p.name);
284
- bot.api = {
285
- Command: Command,
286
- events: bot.events,
287
- sendMessage: (type, message, username) => {
288
- if (type === 'websocket') {
289
- if (process.send) {
290
- process.send({
291
- type: 'send_websocket_message',
292
- payload: {
293
- botId: bot.config.id,
294
- message: message,
295
- }
296
- });
297
- }
298
- } else {
299
- bot.messageQueue.enqueue(type, message, username);
300
- }
301
- },
302
- sendMessageAndWaitForReply: (command, patterns, timeout) => bot.messageQueue.enqueueAndWait(command, patterns, timeout),
303
- getUser: async (username) => {
304
- return await UserService.getUser(username, bot.config.id, bot.config);
305
- },
306
- registerPermissions: (permissions) => PermissionManager.registerPermissions(bot.config.id, permissions),
307
- registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
308
- addPermissionsToGroup: (groupName, permissionNames) => PermissionManager.addPermissionsToGroup(bot.config.id, groupName, permissionNames),
309
- installedPlugins: installedPluginNames,
310
- registerCommand: async (command) => {
311
- try {
312
- let permissionId = null;
313
- if (command.permissions) {
314
- let permission = await prisma.permission.findUnique({
315
- where: {
316
- botId_name: {
317
- botId: bot.config.id,
318
- name: command.permissions,
319
- },
320
- },
321
- });
322
-
323
- if (!permission) {
324
- // Автоматически создаем право, если оно не найдено
325
- permission = await prisma.permission.create({
326
- data: {
327
- botId: bot.config.id,
328
- name: command.permissions,
329
- description: `Автоматически создано для команды ${command.name}`,
330
- owner: command.owner || 'system',
331
- },
332
- });
333
- sendLog(`[API] Право \"${command.permissions}\" автоматически создано для команды \"${command.name}\".`);
334
- }
335
- permissionId = permission.id;
336
- }
337
-
338
- let pluginOwnerId = null;
339
- if (command.owner && command.owner.startsWith('plugin:')) {
340
- const pluginName = command.owner.replace('plugin:', '');
341
- const plugin = await prisma.installedPlugin.findFirst({
342
- where: {
343
- botId: bot.config.id,
344
- name: pluginName
345
- }
346
- });
347
- if (plugin) {
348
- pluginOwnerId = plugin.id;
349
- }
350
- }
351
-
352
- const commandData = {
353
- botId: bot.config.id,
354
- name: command.name,
355
- description: command.description || '',
356
- owner: command.owner || 'unknown',
357
- permissionId: permissionId,
358
- cooldown: command.cooldown || 0,
359
- isEnabled: command.isActive !== undefined ? command.isActive : true,
360
- aliases: JSON.stringify(command.aliases || []),
361
- allowedChatTypes: JSON.stringify(command.allowedChatTypes || ['chat', 'private']),
362
- argumentsJson: JSON.stringify(command.args || []),
363
- pluginOwnerId: pluginOwnerId,
364
- };
365
-
366
- await prisma.command.upsert({
367
- where: {
368
- botId_name: {
369
- botId: commandData.botId,
370
- name: commandData.name,
371
- }
372
- },
373
- update: {
374
- description: commandData.description,
375
- aliases: commandData.aliases,
376
- allowedChatTypes: commandData.allowedChatTypes,
377
- cooldown: commandData.cooldown,
378
- isEnabled: commandData.isEnabled,
379
- argumentsJson: commandData.argumentsJson,
380
- permissionId: commandData.permissionId,
381
- },
382
- create: commandData,
383
- });
384
-
385
- if (process.send) {
386
- process.send({
387
- type: 'register_command',
388
- commandConfig: {
389
- name: command.name,
390
- description: command.description,
391
- aliases: command.aliases,
392
- owner: command.owner,
393
- permissions: command.permissions,
394
- cooldown: command.cooldown,
395
- allowedChatTypes: command.allowedChatTypes,
396
- }
397
- });
398
- }
399
- sendLog(`[API] Команда \"${command.name}\" от плагина \"${command.owner}\" зарегистрирована в процессе.`);
400
-
401
- if (!bot.commands) bot.commands = new Map();
402
- bot.commands.set(command.name, command);
403
- if (Array.isArray(command.aliases)) {
404
- for (const alias of command.aliases) {
405
- bot.commands.set(alias, command);
406
- }
407
- }
408
- } catch (error) {
409
- sendLog(`[API] Ошибка при регистрации команды: ${error.message}`);
410
- }
411
- },
412
- performUserAction: (username, action, data = {}) => {
413
- return new Promise((resolve, reject) => {
414
- const requestId = uuidv4();
415
- pendingRequests.set(requestId, { resolve, reject });
416
-
417
- if (process.send) {
418
- process.send({
419
- type: 'request_user_action',
420
- requestId,
421
- payload: {
422
- targetUsername: username,
423
- action,
424
- data
425
- }
426
- });
427
- } else {
428
- reject(new Error('IPC channel is not available.'));
429
- }
430
-
431
- setTimeout(() => {
432
- if (pendingRequests.has(requestId)) {
433
- reject(new Error('Request to main process timed out.'));
434
- pendingRequests.delete(requestId);
435
- }
436
- }, 10000);
437
- });
438
- },
439
- registerEventGraph: async (graphData) => {
440
- try {
441
- let pluginOwnerId = null;
442
- if (graphData.owner && graphData.owner.startsWith('plugin:')) {
443
- const pluginName = graphData.owner.replace('plugin:', '');
444
- const plugin = await prisma.installedPlugin.findFirst({
445
- where: {
446
- botId: bot.config.id,
447
- name: pluginName
448
- }
449
- });
450
- if (plugin) {
451
- pluginOwnerId = plugin.id;
452
- }
453
- }
454
-
455
- const graphDataToSave = {
456
- botId: bot.config.id,
457
- name: graphData.name,
458
- isEnabled: graphData.isEnabled !== undefined ? graphData.isEnabled : true,
459
- graphJson: graphData.graphJson || JSON.stringify({ nodes: [], connections: [] }),
460
- variables: JSON.stringify(graphData.variables || []),
461
- pluginOwnerId: pluginOwnerId,
462
- };
463
-
464
- const eventGraph = await prisma.eventGraph.upsert({
465
- where: {
466
- botId_name: {
467
- botId: bot.config.id,
468
- name: graphData.name
469
- }
470
- },
471
- update: {
472
- isEnabled: graphDataToSave.isEnabled,
473
- graphJson: graphDataToSave.graphJson,
474
- variables: graphDataToSave.variables,
475
- pluginOwnerId: graphDataToSave.pluginOwnerId,
476
- },
477
- create: graphDataToSave,
478
- });
479
-
480
- if (graphData.triggers && Array.isArray(graphData.triggers)) {
481
- await prisma.eventTrigger.deleteMany({
482
- where: { graphId: eventGraph.id }
483
- });
484
-
485
- if (graphData.triggers.length > 0) {
486
- await prisma.eventTrigger.createMany({
487
- data: graphData.triggers.map(eventType => ({
488
- graphId: eventGraph.id,
489
- eventType
490
- }))
491
- });
492
- }
493
- }
494
-
495
- sendLog(`[API] Граф события "${graphData.name}" от плагина "${graphData.owner}" зарегистрирован.`);
496
- return eventGraph;
497
- } catch (error) {
498
- sendLog(`[API] Ошибка при регистрации графа события: ${error.message}`);
499
- throw error;
500
- }
501
- },
502
- executeCommand: (command) => {
503
- sendLog(`[Graph] Выполнение серверной команды: ${command}`);
504
- bot.chat(command);
505
- },
506
- lookAt: (position) => {
507
- if (bot && position) {
508
- bot.lookAt(position);
509
- }
510
- },
511
- getNearbyEntities: (position = null, radius = 32) => {
512
- const entities = [];
513
- if (bot && bot.entities) {
514
- const centerPos = position || bot.entity?.position;
515
-
516
- if (centerPos) {
517
- for (const entity of Object.values(bot.entities)) {
518
- if (entity && entity.position && entity.isValid) {
519
- const dx = entity.position.x - centerPos.x;
520
- const dy = entity.position.y - centerPos.y;
521
- const dz = entity.position.z - centerPos.z;
522
- const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
523
-
524
- if (distance <= radius) {
525
- entities.push(serializeEntity(entity));
526
- }
527
- }
528
- }
529
- }
530
- }
531
- return entities;
532
- },
533
- sendLog: (message) => {
534
- sendLog(message);
535
- },
536
- sendUiUpdate: (pluginName, stateUpdate) => {
537
- const currentState = pluginUiState.get(pluginName) || {};
538
- const newState = { ...currentState, ...stateUpdate };
539
- pluginUiState.set(pluginName, newState);
540
-
541
-
542
- if (process.send) {
543
- process.send({
544
- type: 'plugin:data',
545
- plugin: pluginName,
546
- payload: newState
547
- });
548
- }
549
- }
550
- };
551
-
552
- // Упрощенный alias для отправки сообщений (используется в командах и нодах)
553
- bot.sendMessage = (type, message, username) => {
554
- bot.api.sendMessage(type, message, username);
555
- };
556
-
557
- // Добавляем bot.sendLog для команд
558
- bot.sendLog = (message) => sendLog(message);
559
-
560
- const processApi = {
561
- appendLog: (botId, message) => {
562
- if (process.send) {
563
- process.send({ type: 'log', content: message });
564
- }
565
- }
566
- };
567
-
568
- bot.graphExecutionEngine = new GraphExecutionEngine(NodeRegistry, processApi);
569
-
570
- bot.commands = await loadCommands();
571
-
572
- const dbCommands = await prisma.command.findMany({ where: { botId: config.id } });
573
-
574
- for (const dbCommand of dbCommands) {
575
- const existingCommand = bot.commands.get(dbCommand.name);
576
-
577
- // Не удаляем выключенные команды, а помечаем их
578
- // Владельцы смогут использовать выключенные команды через проверку в CommandExecutionService
579
-
580
- if (existingCommand) {
581
- existingCommand.isEnabled = dbCommand.isEnabled;
582
- existingCommand.description = dbCommand.description;
583
- existingCommand.cooldown = dbCommand.cooldown;
584
- existingCommand.aliases = JSON.parse(dbCommand.aliases || '[]');
585
- existingCommand.permissionId = dbCommand.permissionId;
586
- existingCommand.allowedChatTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
587
-
588
- // Добавляем алиасы в bot.commands для быстрого доступа
589
- const aliases = JSON.parse(dbCommand.aliases || '[]');
590
- for (const alias of aliases) {
591
- bot.commands.set(alias, existingCommand);
592
- }
593
- } else if (dbCommand.isVisual) {
594
- const visualCommand = new Command({
595
- name: dbCommand.name,
596
- description: dbCommand.description,
597
- aliases: JSON.parse(dbCommand.aliases || '[]'),
598
- cooldown: dbCommand.cooldown,
599
- allowedChatTypes: JSON.parse(dbCommand.allowedChatTypes || '[]'),
600
- args: JSON.parse(dbCommand.argumentsJson || '[]'),
601
- owner: 'visual_editor',
602
- });
603
- visualCommand.permissionId = dbCommand.permissionId;
604
- visualCommand.graphJson = dbCommand.graphJson;
605
- visualCommand.owner = 'visual_editor';
606
- visualCommand.handler = (botInstance, typeChat, user, args) => {
607
- const playerList = bot ? Object.keys(bot.players) : [];
608
- const botState = bot ? { yaw: bot.entity.yaw, pitch: bot.entity.pitch } : {};
609
- const botEntity = bot && bot.entity ? {
610
- position: bot.entity.position,
611
- yaw: bot.entity.yaw,
612
- pitch: bot.entity.pitch
613
- } : null;
614
- const context = {
615
- bot: botInstance.api,
616
- user,
617
- args,
618
- typeChat,
619
- players: playerList,
620
- botState,
621
- botEntity
622
- };
623
- return bot.graphExecutionEngine.execute(visualCommand.graphJson, context);
624
- };
625
- bot.commands.set(visualCommand.name, visualCommand);
626
-
627
- // Добавляем алиасы визуальных команд
628
- const visualAliases = JSON.parse(dbCommand.aliases || '[]');
629
- for (const alias of visualAliases) {
630
- bot.commands.set(alias, visualCommand);
631
- }
632
- }
633
- }
634
-
635
- // Добавляем алиасы для всех загруженных команд (системных и плагинов)
636
- for (const cmd of bot.commands.values()) {
637
- if (cmd.aliases && Array.isArray(cmd.aliases)) {
638
- for (const alias of cmd.aliases) {
639
- if (!bot.commands.has(alias)) {
640
- bot.commands.set(alias, cmd);
641
- }
642
- }
643
- }
644
- }
645
-
646
- if (process.send) {
647
- for (const cmd of bot.commands.values()) {
648
- process.send({
649
- type: 'register_command',
650
- commandConfig: {
651
- name: cmd.name,
652
- description: cmd.description,
653
- aliases: cmd.aliases,
654
- owner: cmd.owner,
655
- permissions: cmd.permissions,
656
- cooldown: cmd.cooldown,
657
- allowedChatTypes: cmd.allowedChatTypes,
658
- }
659
- });
660
- }
661
- }
662
-
663
- await initializePlugins(bot, config.plugins, prisma);
664
- sendLog('[System] Все системы инициализированы.');
665
-
666
- let messageHandledByCustomParser = false;
667
-
668
- bot.on('message', (jsonMsg) => {
669
- const logContent = jsonMsg.toAnsi();
670
-
671
- if (logContent.trim()) {
672
- sendLog(logContent);
673
- }
674
-
675
- messageHandledByCustomParser = false;
676
- const rawMessageText = jsonMsg.toString();
677
- bot.events.emit('core:raw_message', rawMessageText, jsonMsg);
678
-
679
- sendEvent('raw_message', {
680
- rawText: rawMessageText,
681
- json: jsonMsg
682
- });
683
- });
684
-
685
- bot.events.on('chat:message', (data) => {
686
- messageHandledByCustomParser = true;
687
- sendEvent('chat', {
688
- username: data.username,
689
- message: data.message,
690
- chatType: data.type,
691
- raw: data.raw,
692
- });
693
- handleIncomingCommand(data.type, data.username, data.message);
694
- });
695
-
696
- bot.on('chat', (username, message) => {
697
- if (messageHandledByCustomParser) return;
698
- handleIncomingCommand('chat', username, message);
699
- });
700
-
701
- bot.on('whisper', (username, message) => {
702
- if (messageHandledByCustomParser) return;
703
- handleIncomingCommand('whisper', username, message);
704
- });
705
-
706
- bot.on('userAction', async ({ action, target, ...data }) => {
707
- if (!target) return;
708
-
709
- try {
710
- switch (action) {
711
- case 'addGroup':
712
- if (data.group) {
713
- await bot.api.performUserAction(target, 'addGroup', { group: data.group });
714
- }
715
- break;
716
- case 'removeGroup':
717
- if (data.group) {
718
- await bot.api.performUserAction(target, 'removeGroup', { group: data.group });
719
- }
720
- break;
721
- }
722
- } catch (error) {
723
- sendLog(`Ошибка при обработке userAction: ${error.message}`);
724
- }
725
- });
726
-
727
- bot.on('login', () => {
728
- if (connectionTimeout) {
729
- clearTimeout(connectionTimeout);
730
- connectionTimeout = null;
731
- }
732
- sendLog('[Event: login] Успешно залогинился!');
733
- if (process.send) {
734
- process.send({ type: 'bot_ready' });
735
- process.send({ type: 'status', status: 'running' });
736
- }
737
- });
738
-
739
- bot.on('death', () => {
740
- sendEvent('botDied', { user: { username: bot.username } });
741
- });
742
-
743
- bot.on('health', () => {
744
- sendEvent('health', {
745
- health: bot.health,
746
- food: bot.food,
747
- saturation: bot.foodSaturation
748
- });
749
- });
750
-
751
- bot.on('kicked', (reason) => {
752
- let reasonText;
753
- try { reasonText = JSON.parse(reason).text || reason; } catch (e) { reasonText = reason; }
754
- sendLog(`[Event: kicked] Меня кикнули. Причина: ${reasonText}.`);
755
- process.exit(0);
756
- });
757
-
758
- bot.on('error', (err) => {
759
- if (connectionTimeout) {
760
- clearTimeout(connectionTimeout);
761
- connectionTimeout = null;
762
- }
763
- sendLog(`[Event: error] Произошла ошибка: ${err.stack || err.message}`);
764
- });
765
-
766
- bot.on('end', (reason) => {
767
- if (connectionTimeout) {
768
- clearTimeout(connectionTimeout);
769
- connectionTimeout = null;
770
- }
771
- const restartableReasons = ['socketClosed', 'keepAliveError'];
772
- const exitCode = restartableReasons.includes(reason) ? 1 : 0;
773
-
774
- sendLog(`[Event: end] Отключен от сервера. Причина: ${reason}`);
775
- process.exit(exitCode);
776
- });
777
-
778
- bot.on('playerJoined', (player) => {
779
- if (!isReady) return;
780
- sendEvent('playerJoined', { user: { username: player.username, uuid: player.uuid } });
781
- });
782
-
783
- bot.on('playerLeft', (player) => {
784
- if (!isReady) return;
785
- sendEvent('playerLeft', { user: { username: player.username, uuid: player.uuid } });
786
- });
787
-
788
- bot.on('entitySpawn', (entity) => {
789
- if (!isReady) return;
790
- const serialized = serializeEntity(entity);
791
- sendEvent('entitySpawn', { entity: serialized });
792
- });
793
-
794
- bot.on('entityMoved', (entity) => {
795
- if (!isReady) return;
796
- const now = Date.now();
797
- const lastSent = entityMoveThrottles.get(entity.id);
798
- if (!lastSent || now - lastSent > 500) {
799
- entityMoveThrottles.set(entity.id, now);
800
- sendEvent('entityMoved', { entity: serializeEntity(entity) });
801
- }
802
- });
803
-
804
- bot.on('entityGone', (entity) => {
805
- if (!isReady) return;
806
- sendEvent('entityGone', { entity: serializeEntity(entity) });
807
- entityMoveThrottles.delete(entity.id);
808
- });
809
-
810
- bot.on('spawn', () => {
811
- try {
812
- if (bot._client && bot._client.options) {
813
- bot._client.options.chat = 'enabled';
814
- }
815
- if (bot.chatEnabled !== undefined) {
816
- bot.chatEnabled = true;
817
- }
818
- } catch (err) {
819
- }
820
- setTimeout(() => {
821
- isReady = true;
822
- }, 3000);
823
- });
824
- } catch (err) {
825
- sendLog(`[CRITICAL] Критическая ошибка при создании бота: ${err.stack}`);
826
- process.exit(1);
827
- }
828
- } else if (message.type === 'config:reload') {
829
- sendLog('[System] Received config:reload command. Reloading configuration...');
830
- try {
831
- const newConfig = await fetchNewConfig(bot.config.id, prisma);
832
- if (newConfig) {
833
- bot.config = { ...bot.config, ...newConfig };
834
- const newCommands = await loadCommands();
835
- const newPlugins = bot.config.plugins;
836
- bot.commands = newCommands;
837
- await initializePlugins(bot, newPlugins, prisma);
838
- sendLog('[System] Bot configuration and plugins reloaded successfully.');
839
- } else {
840
- sendLog('[System] Failed to fetch new configuration.');
841
- }
842
- } catch (error) {
843
- sendLog(`[System] Error reloading configuration: ${error.message}`);
844
- }
845
- } else if (message.type === 'stop') {
846
- if (connectionTimeout) {
847
- clearTimeout(connectionTimeout);
848
- connectionTimeout = null;
849
- }
850
- if (bot) bot.quit();
851
- else process.exit(0);
852
- } else if (message.type === 'chat') {
853
- if (bot && bot.entity) {
854
- const { message: msg, chatType, username } = message.payload;
855
- bot.messageQueue.enqueue(chatType, msg, username);
856
- }
857
- } else if (message.type === 'execute_handler') {
858
- const { commandName, username, args, typeChat } = message;
859
- const commandInstance = bot.commands.get(commandName);
860
- if (commandInstance) {
861
- (async () => {
862
- try {
863
- // Получаем полный объект User из базы данных
864
- const user = await UserService.getUser(username, bot.config.id, bot.config);
865
-
866
- // Проверяем сигнатуру handler - старая (4 аргумента) или новая (1 аргумент context)
867
- const handlerParamCount = commandInstance.handler.length;
868
-
869
- if (handlerParamCount === 1) {
870
- // Новая сигнатура: handler(context)
871
- const transport = new Transport(typeChat, bot);
872
- const context = new CommandContext(bot, user, args, transport);
873
- await commandInstance.handler(context);
874
- } else {
875
- // Старая сигнатура: handler(bot, typeChat, user, args)
876
- await commandInstance.handler(bot, typeChat, user, args);
877
- }
878
- } catch (e) {
879
- sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
880
- sendLog(`[Handler Error] Stack trace: ${e.stack}`);
881
- }
882
- })();
883
- }
884
- } else if (message.type === 'execute_command_request') {
885
- const { requestId, payload } = message;
886
- const { commandName, args, username, typeChat } = payload;
887
-
888
- (async () => {
889
- try {
890
- const commandInstance = bot.commands.get(commandName);
891
- if (!commandInstance) {
892
- throw new Error(`Command '${commandName}' not found.`);
893
- }
894
-
895
- // Восстанавливаем полный User объект из username
896
- const user = await UserService.getUser(username, bot.config.id, bot.config);
897
-
898
- let result;
899
-
900
- // Проверяем сигнатуру handler - старая (4 аргумента) или новая (1 аргумент context)
901
- const handlerParamCount = commandInstance.handler.length;
902
-
903
- if (handlerParamCount === 1) {
904
- // Новая сигнатура: handler(context)
905
- const transport = new Transport(typeChat, bot);
906
- const context = new CommandContext(bot, user, args, transport);
907
-
908
- if (typeChat === 'websocket') {
909
- result = await commandInstance.handler(context);
910
- if (process.send) {
911
- process.send({ type: 'execute_command_response', requestId, result });
912
- }
913
- } else {
914
- commandInstance.handler(context).catch(e => {
915
- sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
916
- });
917
- }
918
- } else {
919
- // Старая сигнатура: handler(bot, typeChat, user, args)
920
- if (typeChat === 'websocket') {
921
- // Для websocket перехватываем bot.sendMessage
922
- const originalSendMessage = bot.sendMessage;
923
- let resultFromSendMessage = null;
924
- let sendMessageCalled = false;
925
-
926
- bot.sendMessage = (type, message, username) => {
927
- if (type === 'websocket') {
928
- resultFromSendMessage = message;
929
- sendMessageCalled = true;
930
- } else {
931
- originalSendMessage.call(bot, type, message, username);
932
- }
933
- };
934
-
935
- try {
936
- const returnValue = await commandInstance.handler(bot, typeChat, user, args);
937
- result = sendMessageCalled ? resultFromSendMessage : returnValue;
938
-
939
- if (process.send) {
940
- process.send({ type: 'execute_command_response', requestId, result });
941
- }
942
- } finally {
943
- bot.sendMessage = originalSendMessage;
944
- }
945
- } else {
946
- // Для игровых команд просто выполняем
947
- commandInstance.handler(bot, typeChat, user, args).catch(e => {
948
- sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
949
- });
950
- }
951
- }
952
-
953
- } catch (error) {
954
- if (process.send) {
955
- process.send({ type: 'execute_command_response', requestId, error: error.message });
956
- }
957
- }
958
- })();
959
- } else if (message.type === 'invalidate_user_cache') {
960
- if (message.username && bot && bot.config) {
961
- UserService.clearCache(message.username, bot.config.id);
962
- }
963
- } else if (message.type === 'invalidate_all_user_cache') {
964
- if (bot && bot.config) {
965
- for (const [cacheKey, user] of UserService.cache.entries()) {
966
- if (cacheKey.startsWith(`${bot.config.id}:`)) {
967
- UserService.cache.delete(cacheKey);
968
- }
969
- }
970
- sendLog(`[BotProcess] Кэш пользователей очищен для бота ${bot.config.id}`);
971
- }
972
- } else if (message.type === 'handle_permission_error') {
973
- const { commandName, username, typeChat } = message;
974
- const commandInstance = bot.commands.get(commandName);
975
- if (commandInstance) {
976
- if (commandInstance.onInsufficientPermissions !== Command.prototype.onInsufficientPermissions) {
977
- commandInstance.onInsufficientPermissions(bot, typeChat, { username });
978
- } else {
979
- bot.api.sendMessage(typeChat, вас нет прав для выполнения команды ${commandName}.`, username);
980
- }
981
- }
982
- } else if (message.type === 'handle_wrong_chat') {
983
- const { commandName, username, typeChat } = message;
984
- const commandInstance = bot.commands.get(commandName);
985
- if (commandInstance) {
986
- if (commandInstance.onWrongChatType !== Command.prototype.onWrongChatType) {
987
- commandInstance.onWrongChatType(bot, typeChat, { username });
988
- } else {
989
- bot.api.sendMessage('private', `Команду ${commandName} нельзя использовать в этом типе чата - ${typeChat}.`, username);
990
- }
991
- }
992
- } else if (message.type === 'handle_cooldown') {
993
- const { commandName, username, typeChat, timeLeft } = message;
994
- const commandInstance = bot.commands.get(commandName);
995
- if (commandInstance) {
996
- if (commandInstance.onCooldown !== Command.prototype.onCooldown) {
997
- commandInstance.onCooldown(bot, typeChat, { username }, timeLeft);
998
- } else {
999
- bot.api.sendMessage(typeChat, `Команду ${commandName} можно будет использовать через ${timeLeft} сек.`, username);
1000
- }
1001
- }
1002
- } else if (message.type === 'handle_blacklist') {
1003
- const { commandName, username, typeChat } = message;
1004
- const commandInstance = bot.commands.get(commandName);
1005
- if (commandInstance) {
1006
- if (commandInstance.onBlacklisted !== Command.prototype.onBlacklisted) {
1007
- commandInstance.onBlacklisted(bot, typeChat, { username });
1008
- }
1009
- }
1010
- } else if (message.type === 'action') {
1011
- if (message.name === 'lookAt' && bot && message.payload.position) {
1012
- const { x, y, z } = message.payload.position;
1013
- if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {
1014
- bot.lookAt(new Vec3(x, y, z));
1015
- } else {
1016
- sendLog(`[BotProcess] Ошибка lookAt: получены невалидные координаты: ${JSON.stringify(message.payload.position)}`);
1017
- }
1018
- }
1019
- } else if (message.type === 'plugins:reload') {
1020
- sendLog('[System] Получена команда на перезагрузку плагинов...');
1021
- const newConfig = await fetchNewConfig(bot.config.id, prisma);
1022
- if (newConfig) {
1023
- bot.config.plugins = newConfig.installedPlugins;
1024
- bot.commands.clear();
1025
- await loadCommands(bot, newConfig.commands);
1026
- await initializePlugins(bot, newConfig.installedPlugins, prisma);
1027
- sendLog('[System] Плагины успешно перезагружены.');
1028
- } else {
1029
- sendLog('[System] Не удалось получить новую конфигурацию для перезагрузки плагинов.');
1030
- }
1031
- } else if (message.type === 'server_command') {
1032
- if (bot && message.payload && message.payload.command) {
1033
- bot.chat(message.payload.command);
1034
- }
1035
- }
1036
- });
1037
-
1038
- process.on('unhandledRejection', (reason, promise) => {
1039
- const errorMsg = `[FATAL] Необработанная ошибка процесса: ${reason?.stack || reason}`;
1040
- sendLog(errorMsg);
1041
- setTimeout(() => process.exit(1), 100);
1042
- });
1043
-
1044
- process.on('uncaughtException', (error) => {
1045
- const errorMsg = `[FATAL] Необработанное исключение: ${error.stack || error.message}`;
1046
- sendLog(errorMsg);
1047
- setTimeout(() => process.exit(1), 100);
1048
- });
1049
-
1050
- process.on('SIGTERM', () => {
1051
- sendLog('[System] Получен сигнал SIGTERM. Завершение работы...');
1052
- if (bot) {
1053
- try {
1054
- bot.quit();
1055
- } catch (error) {
1056
- sendLog(`[System] Ошибка при корректном завершении бота: ${error.message}`);
1057
- }
1058
- }
1059
- setTimeout(() => process.exit(0), 100);
1060
- });
1061
-
1062
- process.on('SIGINT', () => {
1063
- sendLog('[System] Получен сигнал SIGINT. Завершение работы...');
1064
- if (bot) {
1065
- try {
1066
- bot.quit();
1067
- } catch (error) {
1068
- sendLog(`[System] Ошибка при корректном завершении бота: ${error.message}`);
1069
- }
1070
- }
1071
- setTimeout(() => process.exit(0), 100);
1072
- });
1073
-
1074
-
1075
- function serializeEntity(entity) {
1076
- if (!entity) return null;
1077
- return {
1078
- id: entity.id,
1079
- type: entity.type,
1080
- username: entity.username,
1081
- displayName: entity.displayName,
1082
- position: entity.position,
1083
- yaw: entity.yaw,
1084
- pitch: entity.pitch,
1085
- onGround: entity.onGround,
1086
- isValid: entity.isValid,
1087
- heldItem: entity.heldItem,
1088
- equipment: entity.equipment,
1089
- metadata: entity.metadata
1090
- };
1091
- }
1092
-
1
+ const mineflayer = require('mineflayer');
2
+ const { SocksClient } = require('socks');
3
+ const EventEmitter = require('events');
4
+ const { v4: uuidv4 } = require('uuid');
5
+ const { Vec3 } = require('vec3');
6
+ const { PrismaClient } = require('@prisma/client');
7
+ const { loadCommands } = require('./system/CommandRegistry');
8
+ const { getRuntimeCommandRegistry } = require('./system/RuntimeCommandRegistry');
9
+ const { initializePlugins } = require('./PluginLoader');
10
+ const MessageQueue = require('./MessageQueue');
11
+ const Command = require('./system/Command');
12
+ const { parseArguments } = require('./system/parseArguments');
13
+ const GraphExecutionEngine = require('./GraphExecutionEngine');
14
+ const NodeRegistry = require('./NodeRegistry');
15
+
16
+ const UserService = require('./UserService');
17
+ const PermissionManager = require('./ipc/PermissionManager.stub.js');
18
+ const Transport = require('./system/Transport');
19
+ const CommandContext = require('./system/CommandContext');
20
+
21
+ let bot = null;
22
+ const prisma = new PrismaClient();
23
+ const pluginUiState = new Map();
24
+ const pendingRequests = new Map();
25
+ const entityMoveThrottles = new Map();
26
+ let connectionTimeout = null;
27
+ let botReadySent = false;
28
+ let viewerRenderDistance = 24; // Динамический радиус отображения для viewer
29
+
30
+ const originalJSONParse = JSON.parse
31
+ JSON.parse = function (text, reviver) {
32
+ if (typeof text !== 'string') return originalJSONParse(text, reviver)
33
+ try {
34
+ return originalJSONParse(text, reviver)
35
+ } catch (e) {
36
+ const fixed = text.replace(/([{,])\s*([a-zA-Z0-9_]+)\s*:/g, '$1"$2":')
37
+ return originalJSONParse(fixed, reviver)
38
+ }
39
+ }
40
+
41
+ function sendLog(content) {
42
+ if (process.send) {
43
+ process.send({ type: 'log', content });
44
+ } else {
45
+ console.log(`[ChildProcess Log] ${content}`);
46
+ }
47
+ }
48
+
49
+
50
+ function sendEvent(eventName, eventArgs) {
51
+ if (process.send) {
52
+ // Добавляем информацию о боте (позицию) во все события
53
+ const enrichedArgs = {
54
+ ...eventArgs,
55
+ botEntity: bot && bot.entity ? {
56
+ position: bot.entity.position,
57
+ yaw: bot.entity.yaw,
58
+ pitch: bot.entity.pitch
59
+ } : null
60
+ };
61
+ process.send({ type: 'event', eventType: eventName, args: enrichedArgs });
62
+ }
63
+ }
64
+
65
+ async function fetchNewConfig(botId, prisma) {
66
+ try {
67
+ const botData = await prisma.bot.findUnique({
68
+ where: { id: botId },
69
+ include: {
70
+ server: true,
71
+ installedPlugins: {
72
+ where: { isEnabled: true }
73
+ },
74
+ }
75
+ });
76
+
77
+ if (!botData) return null;
78
+
79
+ const commands = await prisma.command.findMany({ where: { botId } });
80
+
81
+ return { ...botData, commands };
82
+ } catch (error) {
83
+ sendLog(`[fetchNewConfig] Error: ${error.message}`);
84
+ return null;
85
+ }
86
+ }
87
+
88
+ function handleIncomingCommand(type, username, message) {
89
+ if (!message.startsWith(bot.config.prefix || '@')) return;
90
+
91
+ const rawMessage = message.slice((bot.config.prefix || '@').length).trim();
92
+ const commandParts = rawMessage.split(/ +/);
93
+ const commandName = commandParts.shift().toLowerCase();
94
+ const restOfMessage = commandParts.join(' ');
95
+
96
+ // Сначала проверяем стандартные команды
97
+ let commandInstance = bot.commands.get(commandName) ||
98
+ Array.from(bot.commands.values()).find(cmd => cmd.aliases.includes(commandName));
99
+
100
+ if (!commandInstance) {
101
+ // Если не найдена, проверяем временные команды из runtime registry
102
+ const runtimeRegistry = getRuntimeCommandRegistry();
103
+ commandInstance = runtimeRegistry.get(bot.config.id, commandName);
104
+ }
105
+
106
+ if (!commandInstance) {
107
+ return;
108
+ }
109
+
110
+ try {
111
+ const processedArgs = {};
112
+ const parsedArgs = parseArguments(restOfMessage);
113
+ let currentArgIndex = 0;
114
+
115
+ const argsDef = commandInstance.isVisual && commandInstance.args ? commandInstance.args : (commandInstance.args || []);
116
+ for (const argDef of argsDef) {
117
+ if (argDef.type === 'greedy_string') {
118
+ if (currentArgIndex < parsedArgs.length) {
119
+ processedArgs[argDef.name] = parsedArgs.slice(currentArgIndex).join(' ');
120
+ currentArgIndex = parsedArgs.length;
121
+ }
122
+ } else if (currentArgIndex < parsedArgs.length) {
123
+ let value = parsedArgs[currentArgIndex];
124
+ if (argDef.type === 'number') {
125
+ const numValue = parseFloat(value);
126
+ if (isNaN(numValue)) {
127
+ bot.api.sendMessage(type, `Ошибка: Аргумент \"${argDef.description}\" должен быть числом.`, username);
128
+ return;
129
+ }
130
+ value = numValue;
131
+ }
132
+ processedArgs[argDef.name] = value;
133
+ currentArgIndex++;
134
+ }
135
+
136
+ if (processedArgs[argDef.name] === undefined) {
137
+ // Не проверяем required здесь - это будет сделано в CommandExecutionService
138
+ // после проверки типа чата и прав владельца
139
+ if (argDef.default !== undefined) {
140
+ processedArgs[argDef.name] = argDef.default;
141
+ }
142
+ }
143
+ }
144
+
145
+ if (process.send) {
146
+ process.send({
147
+ type: 'validate_and_run_command',
148
+ commandName: commandInstance.name,
149
+ username,
150
+ args: processedArgs,
151
+ typeChat: type,
152
+ commandArgs: argsDef // Передаем определение аргументов для валидации
153
+ });
154
+ }
155
+ } catch (e) {
156
+ sendLog(`[BotProcess] Ошибка парсинга аргументов: ${e.message}`);
157
+ }
158
+ }
159
+
160
+ process.on('message', async (message) => {
161
+ if (message.type === 'plugin:ui:start-updates') {
162
+ const { pluginName } = message;
163
+ const state = pluginUiState.get(pluginName);
164
+ if (state && process.send) {
165
+ process.send({
166
+ type: 'plugin:data',
167
+ plugin: pluginName,
168
+ payload: state
169
+ });
170
+ }
171
+ } else if (message.type === 'user_action_response') {
172
+ if (pendingRequests.has(message.requestId)) {
173
+ const { resolve, reject } = pendingRequests.get(message.requestId);
174
+ if (message.error) {
175
+ reject(new Error(message.error));
176
+ } else {
177
+ resolve(message.payload);
178
+ }
179
+ pendingRequests.delete(message.requestId);
180
+ }
181
+ } else if (message.type === 'system:get_player_list') {
182
+ const playerList = bot ? Object.keys(bot.players) : [];
183
+ if (process.send) {
184
+ process.send({
185
+ type: 'get_player_list_response',
186
+ requestId: message.requestId,
187
+ payload: { players: playerList }
188
+ });
189
+ }
190
+ } else if (message.type === 'system:get_nearby_entities') {
191
+ const entities = [];
192
+ if (bot && bot.entities) {
193
+ const centerPos = message.payload?.position || bot.entity?.position;
194
+ const radius = message.payload?.radius || 32;
195
+
196
+ if (centerPos) {
197
+ // Перебираем все сущности
198
+ for (const entity of Object.values(bot.entities)) {
199
+ if (entity && entity.position && entity.isValid) {
200
+ // Вычисляем расстояние
201
+ const dx = entity.position.x - centerPos.x;
202
+ const dy = entity.position.y - centerPos.y;
203
+ const dz = entity.position.z - centerPos.z;
204
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
205
+
206
+ // Если существо в радиусе, добавляем в список
207
+ if (distance <= radius) {
208
+ entities.push(serializeEntity(entity));
209
+ }
210
+ }
211
+ }
212
+ }
213
+ }
214
+
215
+ if (process.send) {
216
+ process.send({
217
+ type: 'get_nearby_entities_response',
218
+ requestId: message.requestId,
219
+ payload: { entities }
220
+ });
221
+ }
222
+ } else if (message.type === 'viewer:get_state') {
223
+ if (bot && process.send) {
224
+ let blocks = undefined;
225
+
226
+ if (message.includeBlocks && bot.entity?.position) {
227
+ blocks = [];
228
+ const pos = bot.entity.position;
229
+ const horizontalRange = viewerRenderDistance;
230
+ const verticalRangeDown = Math.min(viewerRenderDistance / 2, 16);
231
+ const verticalRangeUp = viewerRenderDistance;
232
+
233
+ for (let x = Math.floor(pos.x - horizontalRange); x <= Math.floor(pos.x + horizontalRange); x++) {
234
+ for (let y = Math.floor(pos.y - verticalRangeDown); y <= Math.floor(pos.y + verticalRangeUp); y++) {
235
+ for (let z = Math.floor(pos.z - horizontalRange); z <= Math.floor(pos.z + horizontalRange); z++) {
236
+ const block = bot.blockAt(new Vec3(x, y, z));
237
+ if (block && block.type !== 0) {
238
+ blocks.push({
239
+ x, y, z,
240
+ type: block.type,
241
+ name: block.name
242
+ });
243
+ }
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ const state = {
250
+ status: bot._client ? 'online' : 'offline',
251
+ health: bot.health || 20,
252
+ food: bot.food || 20,
253
+ position: bot.entity?.position ? {
254
+ x: bot.entity.position.x,
255
+ y: bot.entity.position.y,
256
+ z: bot.entity.position.z
257
+ } : null,
258
+ yaw: bot.entity?.yaw || 0,
259
+ pitch: bot.entity?.pitch || 0,
260
+ gameMode: bot.game?.gameMode,
261
+ dimension: bot.game?.dimension,
262
+ blocks,
263
+ inventory: bot.inventory ? bot.inventory.items().map(item => ({
264
+ name: item.name,
265
+ displayName: item.displayName,
266
+ count: item.count,
267
+ slot: item.slot
268
+ })) : [],
269
+ nearbyPlayers: bot.entities ? Object.values(bot.entities)
270
+ .filter(e => e.type === 'player' && e.username !== bot.username)
271
+ .map(e => ({
272
+ username: e.username,
273
+ position: { x: e.position.x, y: e.position.y, z: e.position.z },
274
+ yaw: e.yaw || 0,
275
+ pitch: e.pitch || 0,
276
+ distance: bot.entity ? bot.entity.position.distanceTo(e.position) : 0
277
+ })) : [],
278
+ nearbyMobs: bot.entities ? Object.values(bot.entities)
279
+ .filter(e => e.type === 'mob')
280
+ .map(e => ({
281
+ name: e.name || e.displayName,
282
+ mobType: e.mobType,
283
+ position: { x: e.position.x, y: e.position.y, z: e.position.z },
284
+ distance: bot.entity ? bot.entity.position.distanceTo(e.position) : 0
285
+ })) : []
286
+ };
287
+
288
+ process.send({
289
+ type: 'viewer:state_response',
290
+ requestId: message.requestId,
291
+ payload: state
292
+ });
293
+ }
294
+ } else if (message.type === 'viewer:control') {
295
+ const { command } = message;
296
+ if (!bot) return;
297
+
298
+ try {
299
+ switch (command.type) {
300
+ case 'move':
301
+ bot.setControlState(command.direction, command.active);
302
+ break;
303
+
304
+ case 'look':
305
+ if (command.yaw !== undefined) bot.entity.yaw = command.yaw;
306
+ if (command.pitch !== undefined) bot.entity.pitch = command.pitch;
307
+ break;
308
+
309
+ case 'chat':
310
+ bot.chat(command.message);
311
+ break;
312
+
313
+ case 'dig':
314
+ if (command.position) {
315
+ const block = bot.blockAt(new Vec3(command.position.x, command.position.y, command.position.z));
316
+ if (block) bot.dig(block).catch(err => sendLog(`[Viewer] Dig error: ${err.message}`));
317
+ }
318
+ break;
319
+
320
+ case 'place':
321
+ if (command.position && command.blockType) {
322
+ const referenceBlock = bot.blockAt(new Vec3(command.position.x, command.position.y, command.position.z));
323
+ if (referenceBlock) {
324
+ const itemToPlace = bot.inventory.items().find(item => item.name === command.blockType);
325
+ if (itemToPlace) {
326
+ bot.equip(itemToPlace, 'hand')
327
+ .then(() => bot.placeBlock(referenceBlock, new Vec3(0, 1, 0)))
328
+ .catch(err => sendLog(`[Viewer] Place error: ${err.message}`));
329
+ }
330
+ }
331
+ }
332
+ break;
333
+
334
+ case 'sync_position':
335
+ if (command.position && bot.entity) {
336
+ bot.entity.position.x = command.position.x;
337
+ bot.entity.position.y = command.position.y;
338
+ bot.entity.position.z = command.position.z;
339
+ }
340
+ break;
341
+
342
+ case 'set_render_distance':
343
+ if (command.distance && command.distance >= 8 && command.distance <= 64) {
344
+ viewerRenderDistance = command.distance;
345
+ sendLog(`[Viewer] Render distance set to ${viewerRenderDistance}`);
346
+ }
347
+ break;
348
+ }
349
+ } catch (error) {
350
+ sendLog(`[Viewer] Control error: ${error.message}`);
351
+ }
352
+ } else if (message.type === 'execute_event_graph') {
353
+ // Выполнение event графа в child process
354
+ const { botId, graph, eventType, eventArgs } = message;
355
+
356
+ try {
357
+
358
+ const playerList = bot ? Object.keys(bot.players) : [];
359
+ const botApi = {
360
+ sendMessage: (chatType, message, recipient) => {
361
+ if (!bot || !bot.messageQueue) {
362
+ sendLog('[EventGraph] Bot not ready');
363
+ return;
364
+ }
365
+
366
+ bot.messageQueue.enqueue(chatType, message, recipient);
367
+ },
368
+ executeCommand: (command) => {
369
+ if (!bot || !bot.messageQueue) {
370
+ sendLog('[EventGraph] Bot not ready');
371
+ return;
372
+ }
373
+ bot.messageQueue.enqueue('command', command);
374
+ },
375
+ lookAt: async (x, y, z) => {
376
+ if (!bot) return;
377
+ const target = new Vec3(x, y, z);
378
+ await bot.lookAt(target);
379
+ },
380
+ navigate: async (x, y, z) => {
381
+ if (!bot || !bot.pathfinder) return;
382
+ const goal = new (require('mineflayer-pathfinder').goals.GoalBlock)(x, y, z);
383
+ await bot.pathfinder.goto(goal);
384
+ },
385
+ attack: (entityId) => {
386
+ if (!bot) return;
387
+ const entity = bot.entities[entityId];
388
+ if (entity) bot.attack(entity);
389
+ },
390
+ follow: (username) => {
391
+ if (!bot || !bot.pathfinder) return;
392
+ const player = bot.players[username];
393
+ if (player && player.entity) {
394
+ const goal = new (require('mineflayer-pathfinder').goals.GoalFollow)(player.entity, 3);
395
+ bot.pathfinder.setGoal(goal, true);
396
+ }
397
+ },
398
+ stopFollow: () => {
399
+ if (bot && bot.pathfinder) {
400
+ bot.pathfinder.setGoal(null);
401
+ }
402
+ }
403
+ };
404
+
405
+ const context = {
406
+ bot: botApi,
407
+ eventArgs: eventArgs || {},
408
+ players: playerList,
409
+ botState: bot ? {
410
+ health: bot.health,
411
+ food: bot.food,
412
+ position: bot.entity?.position,
413
+ gameMode: bot.game?.gameMode
414
+ } : {},
415
+ botEntity: bot && bot.entity ? serializeEntity(bot.entity) : null,
416
+ botId: botId,
417
+ graphId: graph.id,
418
+ eventType: eventType,
419
+ eventArgs: eventArgs
420
+ };
421
+
422
+ const engine = new GraphExecutionEngine(NodeRegistry, botApi);
423
+ await engine.execute(graph, context, eventType);
424
+
425
+
426
+ } catch (error) {
427
+ sendLog(`[EventGraph] Error executing ${eventType} graph: ${error.message}`);
428
+ sendLog(`[EventGraph] Stack: ${error.stack}`);
429
+ }
430
+ } else if (message.type === 'start') {
431
+ const config = message.config;
432
+ sendLog(`[System] Получена команда на запуск бота ${config.username}...`);
433
+ try {
434
+ const botOptions = {
435
+ host: config.server.host,
436
+ port: config.server.port,
437
+ username: config.username,
438
+ password: config.password,
439
+ version: config.server.version,
440
+ auth: 'offline',
441
+ hideErrors: false,
442
+ chat: 'enabled',
443
+ };
444
+
445
+ if (config.proxyHost && config.proxyPort) {
446
+ sendLog(`[System] Используется прокси: ${config.proxyHost}:${config.proxyPort}`);
447
+
448
+ const cleanProxyUsername = config.proxyUsername ? config.proxyUsername.trim() : null;
449
+ const cleanProxyPassword = config.proxyPassword || null;
450
+
451
+ botOptions.connect = (client) => {
452
+ SocksClient.createConnection({
453
+ proxy: {
454
+ host: config.proxyHost,
455
+ port: config.proxyPort,
456
+ type: 5,
457
+ userId: cleanProxyUsername,
458
+ password: cleanProxyPassword
459
+ },
460
+ command: 'connect',
461
+ destination: {
462
+ host: config.server.host,
463
+ port: config.server.port
464
+ }
465
+ }).then(info => {
466
+ client.setSocket(info.socket);
467
+ client.emit('connect');
468
+ }).catch(err => {
469
+ sendLog(`[Proxy Error] SOCKS connection failed: ${err.message}. Bot will attempt to restart.`);
470
+ client.emit('error', err);
471
+ process.exit(1);
472
+ });
473
+ }
474
+ } else {
475
+ sendLog(`[System] Прокси не настроен, используется прямое подключение.`);
476
+ }
477
+
478
+ bot = mineflayer.createBot(botOptions);
479
+
480
+ connectionTimeout = setTimeout(() => {
481
+ if (bot && !bot.player) {
482
+ sendLog('[System] Таймаут подключения к серверу (30 секунд). Завершение работы...');
483
+ process.exit(1);
484
+ }
485
+ }, 30000);
486
+
487
+ bot.pluginUiState = pluginUiState;
488
+
489
+ let isReady = false;
490
+
491
+ bot.events = new EventEmitter();
492
+ bot.events.setMaxListeners(30);
493
+ bot.config = config;
494
+ bot.sendLog = sendLog;
495
+ bot.messageQueue = new MessageQueue(bot);
496
+
497
+ const installedPluginNames = config.plugins.map(p => p.name);
498
+ bot.api = {
499
+ Command: Command,
500
+ events: bot.events,
501
+ sendMessage: (type, message, username) => {
502
+ if (type === 'websocket') {
503
+ if (process.send) {
504
+ process.send({
505
+ type: 'send_websocket_message',
506
+ payload: {
507
+ botId: bot.config.id,
508
+ message: message,
509
+ }
510
+ });
511
+ }
512
+ } else {
513
+ bot.messageQueue.enqueue(type, message, username);
514
+ }
515
+ },
516
+ sendMessageAndWaitForReply: (command, patterns, timeout) => bot.messageQueue.enqueueAndWait(command, patterns, timeout),
517
+ getUser: async (username) => {
518
+ return await UserService.getUser(username, bot.config.id, bot.config);
519
+ },
520
+ registerPermissions: (permissions) => PermissionManager.registerPermissions(bot.config.id, permissions),
521
+ registerGroup: (groupConfig) => PermissionManager.registerGroup(bot.config.id, groupConfig),
522
+ addPermissionsToGroup: (groupName, permissionNames) => PermissionManager.addPermissionsToGroup(bot.config.id, groupName, permissionNames),
523
+ installedPlugins: installedPluginNames,
524
+ registerCommand: async (command) => {
525
+ try {
526
+ const existingCommand = await prisma.command.findUnique({
527
+ where: {
528
+ botId_name: {
529
+ botId: bot.config.id,
530
+ name: command.name,
531
+ }
532
+ }
533
+ });
534
+
535
+ if (existingCommand) {
536
+ if (existingCommand.permissionId === null && command.permissions) {
537
+ const permission = await prisma.permission.upsert({
538
+ where: {
539
+ botId_name: {
540
+ botId: bot.config.id,
541
+ name: command.permissions,
542
+ },
543
+ },
544
+ update: {},
545
+ create: {
546
+ botId: bot.config.id,
547
+ name: command.permissions,
548
+ description: `Автоматически создано для команды ${command.name}`,
549
+ owner: command.owner || 'system',
550
+ },
551
+ });
552
+
553
+ await prisma.command.update({
554
+ where: { id: existingCommand.id },
555
+ data: { permissionId: permission.id }
556
+ });
557
+ }
558
+ } else {
559
+ let permissionId = null;
560
+ if (command.permissions) {
561
+ const permission = await prisma.permission.upsert({
562
+ where: {
563
+ botId_name: {
564
+ botId: bot.config.id,
565
+ name: command.permissions,
566
+ },
567
+ },
568
+ update: {},
569
+ create: {
570
+ botId: bot.config.id,
571
+ name: command.permissions,
572
+ description: `Автоматически создано для команды ${command.name}`,
573
+ owner: command.owner || 'system',
574
+ },
575
+ });
576
+ permissionId = permission.id;
577
+ }
578
+
579
+ let pluginOwnerId = null;
580
+ if (command.owner && command.owner.startsWith('plugin:')) {
581
+ const pluginName = command.owner.replace('plugin:', '');
582
+ const plugin = await prisma.installedPlugin.findFirst({
583
+ where: {
584
+ botId: bot.config.id,
585
+ name: pluginName
586
+ }
587
+ });
588
+ if (plugin) {
589
+ pluginOwnerId = plugin.id;
590
+ }
591
+ }
592
+
593
+ const commandData = {
594
+ botId: bot.config.id,
595
+ name: command.name,
596
+ description: command.description || '',
597
+ owner: command.owner || 'unknown',
598
+ permissionId: permissionId,
599
+ cooldown: command.cooldown || 0,
600
+ isEnabled: command.isActive !== undefined ? command.isActive : true,
601
+ aliases: JSON.stringify(command.aliases || []),
602
+ allowedChatTypes: JSON.stringify(command.allowedChatTypes || ['chat', 'private']),
603
+ argumentsJson: JSON.stringify(command.args || []),
604
+ pluginOwnerId: pluginOwnerId,
605
+ };
606
+
607
+ await prisma.command.create({
608
+ data: commandData,
609
+ });
610
+ }
611
+
612
+ if (process.send) {
613
+ process.send({
614
+ type: 'register_command',
615
+ commandConfig: {
616
+ name: command.name,
617
+ description: command.description,
618
+ aliases: command.aliases,
619
+ owner: command.owner,
620
+ permissions: command.permissions,
621
+ cooldown: command.cooldown,
622
+ allowedChatTypes: command.allowedChatTypes,
623
+ }
624
+ });
625
+ }
626
+
627
+ if (!bot.commands) bot.commands = new Map();
628
+ bot.commands.set(command.name, command);
629
+ if (Array.isArray(command.aliases)) {
630
+ for (const alias of command.aliases) {
631
+ bot.commands.set(alias, command);
632
+ }
633
+ }
634
+ } catch (error) {
635
+ sendLog(`[API] Ошибка при регистрации команды: ${error.message}`);
636
+ }
637
+ },
638
+ performUserAction: (username, action, data = {}) => {
639
+ return new Promise((resolve, reject) => {
640
+ const requestId = uuidv4();
641
+ pendingRequests.set(requestId, { resolve, reject });
642
+
643
+ if (process.send) {
644
+ process.send({
645
+ type: 'request_user_action',
646
+ requestId,
647
+ payload: {
648
+ targetUsername: username,
649
+ action,
650
+ data
651
+ }
652
+ });
653
+ } else {
654
+ reject(new Error('IPC channel is not available.'));
655
+ }
656
+
657
+ setTimeout(() => {
658
+ if (pendingRequests.has(requestId)) {
659
+ reject(new Error('Request to main process timed out.'));
660
+ pendingRequests.delete(requestId);
661
+ }
662
+ }, 10000);
663
+ });
664
+ },
665
+ registerEventGraph: async (graphData) => {
666
+ try {
667
+ let pluginOwnerId = null;
668
+ if (graphData.owner && graphData.owner.startsWith('plugin:')) {
669
+ const pluginName = graphData.owner.replace('plugin:', '');
670
+ const plugin = await prisma.installedPlugin.findFirst({
671
+ where: {
672
+ botId: bot.config.id,
673
+ name: pluginName
674
+ }
675
+ });
676
+ if (plugin) {
677
+ pluginOwnerId = plugin.id;
678
+ }
679
+ }
680
+
681
+ const graphDataToSave = {
682
+ botId: bot.config.id,
683
+ name: graphData.name,
684
+ isEnabled: graphData.isEnabled !== undefined ? graphData.isEnabled : true,
685
+ graphJson: graphData.graphJson || JSON.stringify({ nodes: [], connections: [] }),
686
+ variables: JSON.stringify(graphData.variables || []),
687
+ pluginOwnerId: pluginOwnerId,
688
+ };
689
+
690
+ const eventGraph = await prisma.eventGraph.upsert({
691
+ where: {
692
+ botId_name: {
693
+ botId: bot.config.id,
694
+ name: graphData.name
695
+ }
696
+ },
697
+ update: {
698
+ isEnabled: graphDataToSave.isEnabled,
699
+ graphJson: graphDataToSave.graphJson,
700
+ variables: graphDataToSave.variables,
701
+ pluginOwnerId: graphDataToSave.pluginOwnerId,
702
+ },
703
+ create: graphDataToSave,
704
+ });
705
+
706
+ if (graphData.triggers && Array.isArray(graphData.triggers)) {
707
+ await prisma.eventTrigger.deleteMany({
708
+ where: { graphId: eventGraph.id }
709
+ });
710
+
711
+ if (graphData.triggers.length > 0) {
712
+ await prisma.eventTrigger.createMany({
713
+ data: graphData.triggers.map(eventType => ({
714
+ graphId: eventGraph.id,
715
+ eventType
716
+ }))
717
+ });
718
+ }
719
+ }
720
+
721
+ sendLog(`[API] Граф события "${graphData.name}" от плагина "${graphData.owner}" зарегистрирован.`);
722
+ return eventGraph;
723
+ } catch (error) {
724
+ sendLog(`[API] Ошибка при регистрации графа события: ${error.message}`);
725
+ throw error;
726
+ }
727
+ },
728
+ executeCommand: (command) => {
729
+ sendLog(`[Graph] Выполнение серверной команды: ${command}`);
730
+ if (bot && bot.messageQueue) {
731
+ bot.messageQueue.enqueue('command', command);
732
+ }
733
+ },
734
+ lookAt: (position) => {
735
+ if (bot && position) {
736
+ bot.lookAt(position);
737
+ }
738
+ },
739
+ getNearbyEntities: (position = null, radius = 32) => {
740
+ const entities = [];
741
+ if (bot && bot.entities) {
742
+ const centerPos = position || bot.entity?.position;
743
+
744
+ if (centerPos) {
745
+ for (const entity of Object.values(bot.entities)) {
746
+ if (entity && entity.position && entity.isValid) {
747
+ const dx = entity.position.x - centerPos.x;
748
+ const dy = entity.position.y - centerPos.y;
749
+ const dz = entity.position.z - centerPos.z;
750
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
751
+
752
+ if (distance <= radius) {
753
+ entities.push(serializeEntity(entity));
754
+ }
755
+ }
756
+ }
757
+ }
758
+ }
759
+ return entities;
760
+ },
761
+ sendLog: (message) => {
762
+ sendLog(message);
763
+ },
764
+ sendUiUpdate: (pluginName, stateUpdate) => {
765
+ const currentState = pluginUiState.get(pluginName) || {};
766
+ const newState = { ...currentState, ...stateUpdate };
767
+ pluginUiState.set(pluginName, newState);
768
+
769
+
770
+ if (process.send) {
771
+ process.send({
772
+ type: 'plugin:data',
773
+ plugin: pluginName,
774
+ payload: newState
775
+ });
776
+ }
777
+ }
778
+ };
779
+
780
+ // Упрощенный alias для отправки сообщений (используется в командах и нодах)
781
+ bot.sendMessage = (type, message, username) => {
782
+ bot.api.sendMessage(type, message, username);
783
+ };
784
+
785
+ // Добавляем bot.sendLog для команд
786
+ bot.sendLog = (message) => sendLog(message);
787
+
788
+ const processApi = {
789
+ appendLog: (botId, message) => {
790
+ if (process.send) {
791
+ process.send({ type: 'log', content: message });
792
+ }
793
+ }
794
+ };
795
+
796
+ bot.graphExecutionEngine = new GraphExecutionEngine(NodeRegistry, processApi);
797
+
798
+ bot.commands = await loadCommands();
799
+
800
+ const dbCommands = await prisma.command.findMany({ where: { botId: config.id } });
801
+
802
+ for (const dbCommand of dbCommands) {
803
+ const existingCommand = bot.commands.get(dbCommand.name);
804
+
805
+ // Не удаляем выключенные команды, а помечаем их
806
+ // Владельцы смогут использовать выключенные команды через проверку в CommandExecutionService
807
+
808
+ if (existingCommand) {
809
+ existingCommand.isEnabled = dbCommand.isEnabled;
810
+ existingCommand.description = dbCommand.description;
811
+ existingCommand.cooldown = dbCommand.cooldown;
812
+ existingCommand.aliases = JSON.parse(dbCommand.aliases || '[]');
813
+ existingCommand.permissionId = dbCommand.permissionId;
814
+ existingCommand.allowedChatTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
815
+
816
+ // Добавляем алиасы в bot.commands для быстрого доступа
817
+ const aliases = JSON.parse(dbCommand.aliases || '[]');
818
+ for (const alias of aliases) {
819
+ bot.commands.set(alias, existingCommand);
820
+ }
821
+ } else if (dbCommand.isVisual) {
822
+ const visualCommand = new Command({
823
+ name: dbCommand.name,
824
+ description: dbCommand.description,
825
+ aliases: JSON.parse(dbCommand.aliases || '[]'),
826
+ cooldown: dbCommand.cooldown,
827
+ allowedChatTypes: JSON.parse(dbCommand.allowedChatTypes || '[]'),
828
+ args: JSON.parse(dbCommand.argumentsJson || '[]'),
829
+ owner: 'visual_editor',
830
+ });
831
+ visualCommand.permissionId = dbCommand.permissionId;
832
+ visualCommand.graphJson = dbCommand.graphJson;
833
+ visualCommand.owner = 'visual_editor';
834
+ visualCommand.handler = (botInstance, typeChat, user, args) => {
835
+ const playerList = botInstance ? Object.keys(botInstance.players) : [];
836
+ const botState = botInstance ? { yaw: botInstance.entity.yaw, pitch: botInstance.entity.pitch } : {};
837
+ const botEntity = botInstance && botInstance.entity ? {
838
+ position: botInstance.entity.position,
839
+ yaw: botInstance.entity.yaw,
840
+ pitch: botInstance.entity.pitch
841
+ } : null;
842
+
843
+ const context = {
844
+ bot: botInstance.api,
845
+ user,
846
+ args,
847
+ typeChat,
848
+ players: playerList,
849
+ botState,
850
+ botEntity,
851
+ botId: botInstance.config.id,
852
+ graphId: dbCommand.id,
853
+ eventType: 'command',
854
+ eventArgs: {
855
+ commandName: dbCommand.name,
856
+ user: { username: user?.username },
857
+ args,
858
+ typeChat
859
+ }
860
+ };
861
+
862
+ return botInstance.graphExecutionEngine.execute(visualCommand.graphJson, context);
863
+ };
864
+ bot.commands.set(visualCommand.name, visualCommand);
865
+
866
+ // Добавляем алиасы визуальных команд
867
+ const visualAliases = JSON.parse(dbCommand.aliases || '[]');
868
+ for (const alias of visualAliases) {
869
+ bot.commands.set(alias, visualCommand);
870
+ }
871
+ }
872
+ }
873
+
874
+ // Добавляем алиасы для всех загруженных команд (системных и плагинов)
875
+ for (const cmd of bot.commands.values()) {
876
+ if (cmd.aliases && Array.isArray(cmd.aliases)) {
877
+ for (const alias of cmd.aliases) {
878
+ if (!bot.commands.has(alias)) {
879
+ bot.commands.set(alias, cmd);
880
+ }
881
+ }
882
+ }
883
+ }
884
+
885
+ if (process.send) {
886
+ for (const cmd of bot.commands.values()) {
887
+ process.send({
888
+ type: 'register_command',
889
+ commandConfig: {
890
+ name: cmd.name,
891
+ description: cmd.description,
892
+ aliases: cmd.aliases,
893
+ owner: cmd.owner,
894
+ permissions: cmd.permissions,
895
+ cooldown: cmd.cooldown,
896
+ allowedChatTypes: cmd.allowedChatTypes,
897
+ }
898
+ });
899
+ }
900
+ }
901
+
902
+ await initializePlugins(bot, config.plugins, prisma);
903
+ sendLog('[System] Все системы инициализированы.');
904
+
905
+ let messageHandledByCustomParser = false;
906
+
907
+ bot.on('message', (jsonMsg) => {
908
+ const logContent = jsonMsg.toAnsi();
909
+
910
+ if (logContent.trim()) {
911
+ sendLog(logContent);
912
+ }
913
+
914
+ messageHandledByCustomParser = false;
915
+ const rawMessageText = jsonMsg.toString();
916
+ bot.events.emit('core:raw_message', rawMessageText, jsonMsg);
917
+
918
+ sendEvent('raw_message', {
919
+ rawText: rawMessageText,
920
+ json: jsonMsg
921
+ });
922
+
923
+ if (process.send && rawMessageText.trim()) {
924
+ process.send({
925
+ type: 'viewer:chat',
926
+ payload: {
927
+ rawText: rawMessageText,
928
+ timestamp: Date.now()
929
+ }
930
+ });
931
+ }
932
+ });
933
+
934
+ bot.events.on('chat:message', (data) => {
935
+ messageHandledByCustomParser = true;
936
+ sendEvent('chat', {
937
+ username: data.username,
938
+ message: data.message,
939
+ chatType: data.type,
940
+ raw: data.raw,
941
+ });
942
+ handleIncomingCommand(data.type, data.username, data.message);
943
+ });
944
+
945
+ bot.on('chat', (username, message) => {
946
+ if (messageHandledByCustomParser) return;
947
+ handleIncomingCommand('chat', username, message);
948
+ });
949
+
950
+ bot.on('whisper', (username, message) => {
951
+ if (messageHandledByCustomParser) return;
952
+ handleIncomingCommand('whisper', username, message);
953
+ });
954
+
955
+ bot.on('userAction', async ({ action, target, ...data }) => {
956
+ if (!target) return;
957
+
958
+ try {
959
+ switch (action) {
960
+ case 'addGroup':
961
+ if (data.group) {
962
+ await bot.api.performUserAction(target, 'addGroup', { group: data.group });
963
+ }
964
+ break;
965
+ case 'removeGroup':
966
+ if (data.group) {
967
+ await bot.api.performUserAction(target, 'removeGroup', { group: data.group });
968
+ }
969
+ break;
970
+ }
971
+ } catch (error) {
972
+ sendLog(`Ошибка при обработке userAction: ${error.message}`);
973
+ }
974
+ });
975
+
976
+ bot.on('login', () => {
977
+ if (connectionTimeout) {
978
+ clearTimeout(connectionTimeout);
979
+ connectionTimeout = null;
980
+ }
981
+ sendLog('[Event: login] Успешно залогинился!');
982
+ if (process.send && !botReadySent) {
983
+ process.send({ type: 'bot_ready' });
984
+ process.send({ type: 'status', status: 'running' });
985
+ botReadySent = true;
986
+ }
987
+ });
988
+
989
+ bot.on('death', () => {
990
+ sendEvent('botDied', { user: { username: bot.username } });
991
+ });
992
+
993
+ bot.on('health', () => {
994
+ sendEvent('health', {
995
+ health: bot.health,
996
+ food: bot.food,
997
+ saturation: bot.foodSaturation
998
+ });
999
+ });
1000
+
1001
+ bot.on('kicked', (reason) => {
1002
+ let reasonText;
1003
+ try { reasonText = JSON.parse(reason).text || reason; } catch (e) { reasonText = reason; }
1004
+ sendLog(`[Event: kicked] Меня кикнули. Причина: ${reasonText}.`);
1005
+ process.exit(0);
1006
+ });
1007
+
1008
+ bot.on('error', (err) => {
1009
+ if (connectionTimeout) {
1010
+ clearTimeout(connectionTimeout);
1011
+ connectionTimeout = null;
1012
+ }
1013
+ sendLog(`[Event: error] Произошла ошибка: ${err.stack || err.message}`);
1014
+ });
1015
+
1016
+ bot.on('end', (reason) => {
1017
+ if (connectionTimeout) {
1018
+ clearTimeout(connectionTimeout);
1019
+ connectionTimeout = null;
1020
+ }
1021
+ const restartableReasons = ['socketClosed', 'keepAliveError'];
1022
+ const exitCode = restartableReasons.includes(reason) ? 1 : 0;
1023
+
1024
+ sendLog(`[Event: end] Отключен от сервера. Причина: ${reason}`);
1025
+ process.exit(exitCode);
1026
+ });
1027
+
1028
+ bot.on('playerJoined', (player) => {
1029
+ if (!isReady) return;
1030
+ sendEvent('playerJoined', { user: { username: player.username, uuid: player.uuid } });
1031
+ });
1032
+
1033
+ bot.on('playerLeft', (player) => {
1034
+ if (!isReady) return;
1035
+ sendEvent('playerLeft', { user: { username: player.username, uuid: player.uuid } });
1036
+ });
1037
+
1038
+ bot.on('entitySpawn', (entity) => {
1039
+ if (!isReady) return;
1040
+ const serialized = serializeEntity(entity);
1041
+ sendEvent('entitySpawn', { entity: serialized });
1042
+ });
1043
+
1044
+ bot.on('entityMoved', (entity) => {
1045
+ if (!isReady) return;
1046
+ const now = Date.now();
1047
+ const lastSent = entityMoveThrottles.get(entity.id);
1048
+ if (!lastSent || now - lastSent > 500) {
1049
+ entityMoveThrottles.set(entity.id, now);
1050
+ sendEvent('entityMoved', { entity: serializeEntity(entity) });
1051
+ }
1052
+ });
1053
+
1054
+ bot.on('entityGone', (entity) => {
1055
+ if (!isReady) return;
1056
+ sendEvent('entityGone', { entity: serializeEntity(entity) });
1057
+ entityMoveThrottles.delete(entity.id);
1058
+ });
1059
+
1060
+ bot.on('spawn', () => {
1061
+ try {
1062
+ if (bot._client && bot._client.options) {
1063
+ bot._client.options.chat = 'enabled';
1064
+ }
1065
+ if (bot.chatEnabled !== undefined) {
1066
+ bot.chatEnabled = true;
1067
+ }
1068
+ } catch (err) {
1069
+ }
1070
+ setTimeout(() => {
1071
+ isReady = true;
1072
+ }, 3000);
1073
+
1074
+ // Отправка события для viewer
1075
+ if (process.send) {
1076
+ process.send({
1077
+ type: 'viewer:spawn',
1078
+ payload: {
1079
+ position: bot.entity?.position,
1080
+ yaw: bot.entity?.yaw,
1081
+ pitch: bot.entity?.pitch
1082
+ }
1083
+ });
1084
+ }
1085
+ });
1086
+
1087
+ bot.on('health', () => {
1088
+ if (process.send) {
1089
+ process.send({
1090
+ type: 'viewer:health',
1091
+ payload: {
1092
+ health: bot.health,
1093
+ food: bot.food
1094
+ }
1095
+ });
1096
+ }
1097
+ });
1098
+
1099
+ bot.on('move', () => {
1100
+ if (process.send) {
1101
+ process.send({
1102
+ type: 'viewer:move',
1103
+ payload: {
1104
+ position: bot.entity?.position,
1105
+ yaw: bot.entity?.yaw,
1106
+ pitch: bot.entity?.pitch
1107
+ }
1108
+ });
1109
+ }
1110
+ });
1111
+ } catch (err) {
1112
+ sendLog(`[CRITICAL] Критическая ошибка при создании бота: ${err.stack}`);
1113
+ process.exit(1);
1114
+ }
1115
+ } else if (message.type === 'config:reload') {
1116
+ sendLog('[System] Received config:reload command. Reloading configuration...');
1117
+ try {
1118
+ const newConfig = await fetchNewConfig(bot.config.id, prisma);
1119
+ if (newConfig) {
1120
+ bot.config = { ...bot.config, ...newConfig };
1121
+ const newCommands = await loadCommands();
1122
+ const newPlugins = bot.config.plugins;
1123
+ bot.commands = newCommands;
1124
+ await initializePlugins(bot, newPlugins, prisma);
1125
+ sendLog('[System] Bot configuration and plugins reloaded successfully.');
1126
+ } else {
1127
+ sendLog('[System] Failed to fetch new configuration.');
1128
+ }
1129
+ } catch (error) {
1130
+ sendLog(`[System] Error reloading configuration: ${error.message}`);
1131
+ }
1132
+ } else if (message.type === 'stop') {
1133
+ if (connectionTimeout) {
1134
+ clearTimeout(connectionTimeout);
1135
+ connectionTimeout = null;
1136
+ }
1137
+ botReadySent = false;
1138
+ if (bot) bot.quit();
1139
+ else process.exit(0);
1140
+ } else if (message.type === 'chat') {
1141
+ if (bot && bot.entity) {
1142
+ const { message: msg, chatType, username } = message.payload;
1143
+ bot.messageQueue.enqueue(chatType, msg, username);
1144
+ }
1145
+ } else if (message.type === 'register_temp_command') {
1146
+ // Регистрация временной команды из главного процесса
1147
+ const { commandData } = message;
1148
+
1149
+ try {
1150
+ const tempCommand = new Command({
1151
+ name: commandData.name,
1152
+ description: commandData.description || '',
1153
+ aliases: commandData.aliases || [],
1154
+ cooldown: commandData.cooldown || 0,
1155
+ allowedChatTypes: commandData.allowedChatTypes || ['chat', 'private'],
1156
+ args: [],
1157
+ owner: 'runtime',
1158
+ });
1159
+
1160
+ tempCommand.permissionId = commandData.permissionId || null;
1161
+ tempCommand.isTemporary = true;
1162
+ tempCommand.tempId = commandData.tempId;
1163
+ tempCommand.isVisual = false;
1164
+ tempCommand.handler = () => { };
1165
+
1166
+ // Регистрируем команду в bot.commands
1167
+ bot.commands.set(commandData.name, tempCommand);
1168
+
1169
+ if (Array.isArray(commandData.aliases)) {
1170
+ for (const alias of commandData.aliases) {
1171
+ bot.commands.set(alias, tempCommand);
1172
+ }
1173
+ }
1174
+ } catch (error) {
1175
+ sendLog(`[BotProcess] Ошибка регистрации временной команды: ${error.message}`);
1176
+ }
1177
+ } else if (message.type === 'unregister_temp_command') {
1178
+ const { commandName, aliases } = message;
1179
+
1180
+ try {
1181
+ if (bot.commands.has(commandName)) {
1182
+ bot.commands.delete(commandName);
1183
+ }
1184
+
1185
+ if (Array.isArray(aliases)) {
1186
+ for (const alias of aliases) {
1187
+ if (bot.commands.has(alias)) {
1188
+ bot.commands.delete(alias);
1189
+ }
1190
+ }
1191
+ }
1192
+ } catch (error) {
1193
+ sendLog(`[BotProcess] Ошибка удаления временной команды: ${error.message}`);
1194
+ }
1195
+ } else if (message.type === 'execute_handler') {
1196
+ const { commandName, username, args, typeChat } = message;
1197
+ const commandInstance = bot.commands.get(commandName);
1198
+ if (commandInstance) {
1199
+ (async () => {
1200
+ try {
1201
+ const user = await UserService.getUser(username, bot.config.id, bot.config);
1202
+
1203
+ const handlerParamCount = commandInstance.handler.length;
1204
+
1205
+ if (handlerParamCount === 1) {
1206
+ const transport = new Transport(typeChat, bot);
1207
+ const context = new CommandContext(bot, user, args, transport);
1208
+ await commandInstance.handler(context);
1209
+ } else {
1210
+ await commandInstance.handler(bot, typeChat, user, args);
1211
+ }
1212
+ } catch (e) {
1213
+ sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
1214
+ sendLog(`[Handler Error] Stack trace: ${e.stack}`);
1215
+ }
1216
+ })();
1217
+ }
1218
+ } else if (message.type === 'execute_command_request') {
1219
+ const { requestId, payload } = message;
1220
+ const { commandName, args, username, typeChat } = payload;
1221
+
1222
+ (async () => {
1223
+ try {
1224
+ const commandInstance = bot.commands.get(commandName);
1225
+ if (!commandInstance) {
1226
+ throw new Error(`Command '${commandName}' not found.`);
1227
+ }
1228
+
1229
+ const user = await UserService.getUser(username, bot.config.id, bot.config);
1230
+
1231
+ let result;
1232
+
1233
+ const handlerParamCount = commandInstance.handler.length;
1234
+
1235
+ if (handlerParamCount === 1) {
1236
+ const transport = new Transport(typeChat, bot);
1237
+ const context = new CommandContext(bot, user, args, transport);
1238
+
1239
+ if (typeChat === 'websocket') {
1240
+ result = await commandInstance.handler(context);
1241
+ if (process.send) {
1242
+ process.send({ type: 'execute_command_response', requestId, result });
1243
+ }
1244
+ } else {
1245
+ commandInstance.handler(context).catch(e => {
1246
+ sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
1247
+ });
1248
+ }
1249
+ } else {
1250
+ // Старая сигнатура: handler(bot, typeChat, user, args)
1251
+ if (typeChat === 'websocket') {
1252
+ // Для websocket перехватываем bot.sendMessage
1253
+ const originalSendMessage = bot.sendMessage;
1254
+ let resultFromSendMessage = null;
1255
+ let sendMessageCalled = false;
1256
+
1257
+ bot.sendMessage = (type, message, username) => {
1258
+ if (type === 'websocket') {
1259
+ resultFromSendMessage = message;
1260
+ sendMessageCalled = true;
1261
+ } else {
1262
+ originalSendMessage.call(bot, type, message, username);
1263
+ }
1264
+ };
1265
+
1266
+ try {
1267
+ const returnValue = await commandInstance.handler(bot, typeChat, user, args);
1268
+ result = sendMessageCalled ? resultFromSendMessage : returnValue;
1269
+
1270
+ if (process.send) {
1271
+ process.send({ type: 'execute_command_response', requestId, result });
1272
+ }
1273
+ } finally {
1274
+ bot.sendMessage = originalSendMessage;
1275
+ }
1276
+ } else {
1277
+ // Для игровых команд просто выполняем
1278
+ commandInstance.handler(bot, typeChat, user, args).catch(e => {
1279
+ sendLog(`[Handler Error] Ошибка в handler-е команды ${commandName}: ${e.message}`);
1280
+ });
1281
+ }
1282
+ }
1283
+
1284
+ } catch (error) {
1285
+ if (process.send) {
1286
+ process.send({ type: 'execute_command_response', requestId, error: error.message });
1287
+ }
1288
+ }
1289
+ })();
1290
+ } else if (message.type === 'invalidate_user_cache') {
1291
+ if (message.username && bot && bot.config) {
1292
+ UserService.clearCache(message.username, bot.config.id);
1293
+ }
1294
+ } else if (message.type === 'invalidate_all_user_cache') {
1295
+ if (bot && bot.config) {
1296
+ for (const [cacheKey, user] of UserService.cache.entries()) {
1297
+ if (cacheKey.startsWith(`${bot.config.id}:`)) {
1298
+ UserService.cache.delete(cacheKey);
1299
+ }
1300
+ }
1301
+ sendLog(`[BotProcess] Кэш пользователей очищен для бота ${bot.config.id}`);
1302
+ }
1303
+ } else if (message.type === 'handle_permission_error') {
1304
+ const { commandName, username, typeChat } = message;
1305
+ const commandInstance = bot.commands.get(commandName);
1306
+ if (commandInstance) {
1307
+ if (commandInstance.onInsufficientPermissions !== Command.prototype.onInsufficientPermissions) {
1308
+ commandInstance.onInsufficientPermissions(bot, typeChat, { username });
1309
+ } else {
1310
+ bot.api.sendMessage(typeChat, `У вас нет прав для выполнения команды ${commandName}.`, username);
1311
+ }
1312
+ }
1313
+ } else if (message.type === 'handle_wrong_chat') {
1314
+ const { commandName, username, typeChat } = message;
1315
+ const commandInstance = bot.commands.get(commandName);
1316
+ if (commandInstance) {
1317
+ if (commandInstance.onWrongChatType !== Command.prototype.onWrongChatType) {
1318
+ commandInstance.onWrongChatType(bot, typeChat, { username });
1319
+ } else {
1320
+ bot.api.sendMessage('private', `Команду ${commandName} нельзя использовать в этом типе чата - ${typeChat}.`, username);
1321
+ }
1322
+ }
1323
+ } else if (message.type === 'handle_cooldown') {
1324
+ const { commandName, username, typeChat, timeLeft } = message;
1325
+ const commandInstance = bot.commands.get(commandName);
1326
+ if (commandInstance) {
1327
+ if (commandInstance.onCooldown !== Command.prototype.onCooldown) {
1328
+ commandInstance.onCooldown(bot, typeChat, { username }, timeLeft);
1329
+ } else {
1330
+ bot.api.sendMessage(typeChat, `Команду ${commandName} можно будет использовать через ${timeLeft} сек.`, username);
1331
+ }
1332
+ }
1333
+ } else if (message.type === 'handle_blacklist') {
1334
+ const { commandName, username, typeChat } = message;
1335
+ const commandInstance = bot.commands.get(commandName);
1336
+ if (commandInstance) {
1337
+ if (commandInstance.onBlacklisted !== Command.prototype.onBlacklisted) {
1338
+ commandInstance.onBlacklisted(bot, typeChat, { username });
1339
+ }
1340
+ }
1341
+ } else if (message.type === 'send_message') {
1342
+ const { typeChat, message: msg, username } = message;
1343
+ if (bot && bot.api) {
1344
+ bot.api.sendMessage(typeChat, msg, username);
1345
+ }
1346
+ } else if (message.type === 'action') {
1347
+ if (message.name === 'lookAt' && bot && message.payload.position) {
1348
+ const { x, y, z } = message.payload.position;
1349
+ if (typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {
1350
+ bot.lookAt(new Vec3(x, y, z));
1351
+ } else {
1352
+ sendLog(`[BotProcess] Ошибка lookAt: получены невалидные координаты: ${JSON.stringify(message.payload.position)}`);
1353
+ }
1354
+ }
1355
+ } else if (message.type === 'plugins:reload') {
1356
+ sendLog('[System] Получена команда на перезагрузку плагинов...');
1357
+ const newConfig = await fetchNewConfig(bot.config.id, prisma);
1358
+ if (newConfig) {
1359
+ bot.config.plugins = newConfig.installedPlugins;
1360
+ bot.commands.clear();
1361
+ await loadCommands(bot, newConfig.commands);
1362
+ await initializePlugins(bot, newConfig.installedPlugins, prisma);
1363
+ sendLog('[System] Плагины успешно перезагружены.');
1364
+ } else {
1365
+ sendLog('[System] Не удалось получить новую конфигурацию для перезагрузки плагинов.');
1366
+ }
1367
+ } else if (message.type === 'server_command') {
1368
+ if (bot && bot.messageQueue && message.payload && message.payload.command) {
1369
+ bot.messageQueue.enqueue('command', message.payload.command);
1370
+ }
1371
+ } else if (message.type === 'execute_event_graph') {
1372
+ // Выполнение event графа в child process
1373
+ const { graph, eventType, eventArgs } = message;
1374
+
1375
+ try {
1376
+ if (!graph || !graph.nodes || graph.nodes.length === 0) {
1377
+ return;
1378
+ }
1379
+
1380
+ const config = bot?.config || bot?.botConfig || message.botConfig;
1381
+
1382
+ if (!config) {
1383
+ sendLog('[ERROR] Bot config not available for event graph execution');
1384
+ return;
1385
+ }
1386
+
1387
+ const botApi = {
1388
+ sendMessage: (chatType, messageText, recipient) => {
1389
+ if (!bot || !bot.messageQueue) return;
1390
+ bot.messageQueue.enqueue(chatType, messageText, recipient);
1391
+ },
1392
+ executeCommand: (command) => {
1393
+ if (!bot || !bot.messageQueue) return;
1394
+ bot.messageQueue.enqueue('command', command);
1395
+ },
1396
+ lookAt: async (x, y, z) => {
1397
+ if (!bot) return;
1398
+ const target = new Vec3(x, y, z);
1399
+ await bot.lookAt(target);
1400
+ },
1401
+ navigate: async (x, y, z) => {
1402
+ if (!bot || !bot.pathfinder) return;
1403
+ const goal = new (require('mineflayer-pathfinder').goals.GoalBlock)(x, y, z);
1404
+ await bot.pathfinder.goto(goal);
1405
+ },
1406
+ attack: (entityId) => {
1407
+ if (!bot) return;
1408
+ const entity = bot.entities[entityId];
1409
+ if (entity) bot.attack(entity);
1410
+ },
1411
+ follow: (username) => {
1412
+ if (!bot || !bot.pathfinder) return;
1413
+ const player = bot.players[username];
1414
+ if (player && player.entity) {
1415
+ const goal = new (require('mineflayer-pathfinder').goals.GoalFollow)(player.entity, 3);
1416
+ bot.pathfinder.setGoal(goal, true);
1417
+ }
1418
+ },
1419
+ stopFollow: () => {
1420
+ if (bot && bot.pathfinder) {
1421
+ bot.pathfinder.setGoal(null);
1422
+ }
1423
+ }
1424
+ };
1425
+
1426
+ const players = bot ? Object.keys(bot.players) : [];
1427
+
1428
+ const context = {
1429
+ bot: botApi,
1430
+ players,
1431
+ botState: {
1432
+ health: bot?.health,
1433
+ food: bot?.food,
1434
+ position: bot?.entity?.position
1435
+ },
1436
+ botEntity: bot && bot.entity ? {
1437
+ position: bot.entity.position,
1438
+ velocity: bot.entity.velocity,
1439
+ yaw: bot.entity.yaw,
1440
+ pitch: bot.entity.pitch,
1441
+ onGround: bot.entity.onGround,
1442
+ height: bot.entity.height,
1443
+ width: bot.entity.width
1444
+ } : null,
1445
+ eventArgs,
1446
+ botId: config.id,
1447
+ graphId: graph.id,
1448
+ eventType: eventType,
1449
+ eventArgs: eventArgs
1450
+ };
1451
+
1452
+ const engine = new GraphExecutionEngine(NodeRegistry, botApi);
1453
+
1454
+ await engine.execute(graph, context, eventType);
1455
+
1456
+ } catch (error) {
1457
+ sendLog(`[ERROR] Error executing event graph for '${eventType}': ${error.message}`);
1458
+ console.error(error);
1459
+ }
1460
+ }
1461
+ });
1462
+
1463
+ process.on('unhandledRejection', (reason, promise) => {
1464
+ const errorMsg = `[FATAL] Необработанная ошибка процесса: ${reason?.stack || reason}`;
1465
+ sendLog(errorMsg);
1466
+ setTimeout(() => process.exit(1), 100);
1467
+ });
1468
+
1469
+ process.on('uncaughtException', (error) => {
1470
+ const errorMsg = `[FATAL] Необработанное исключение: ${error.stack || error.message}`;
1471
+ sendLog(errorMsg);
1472
+ setTimeout(() => process.exit(1), 100);
1473
+ });
1474
+
1475
+ process.on('SIGTERM', () => {
1476
+ sendLog('[System] Получен сигнал SIGTERM. Завершение работы...');
1477
+ if (bot) {
1478
+ try {
1479
+ bot.quit();
1480
+ } catch (error) {
1481
+ sendLog(`[System] Ошибка при корректном завершении бота: ${error.message}`);
1482
+ }
1483
+ }
1484
+ setTimeout(() => process.exit(0), 100);
1485
+ });
1486
+
1487
+ process.on('SIGINT', () => {
1488
+ sendLog('[System] Получен сигнал SIGINT. Завершение работы...');
1489
+ if (bot) {
1490
+ try {
1491
+ bot.quit();
1492
+ } catch (error) {
1493
+ sendLog(`[System] Ошибка при корректном завершении бота: ${error.message}`);
1494
+ }
1495
+ }
1496
+ setTimeout(() => process.exit(0), 100);
1497
+ });
1498
+
1499
+
1500
+ function serializeEntity(entity) {
1501
+ if (!entity) return null;
1502
+ return {
1503
+ id: entity.id,
1504
+ type: entity.type,
1505
+ username: entity.username,
1506
+ displayName: entity.displayName,
1507
+ position: entity.position,
1508
+ yaw: entity.yaw,
1509
+ pitch: entity.pitch,
1510
+ onGround: entity.onGround,
1511
+ isValid: entity.isValid,
1512
+ heldItem: entity.heldItem,
1513
+ equipment: entity.equipment,
1514
+ metadata: entity.metadata
1515
+ };
1516
+ }
1517
+