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