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.
Files changed (32) hide show
  1. package/backend/package.json +5 -0
  2. package/backend/prisma/migrations/20250627144030_add_visual_editor_fields/migration.sql +26 -0
  3. package/backend/prisma/migrations/20250628113254_add_event_graphs/migration.sql +14 -0
  4. package/backend/prisma/migrations/20250628122517_added_eventgraph_name/migration.sql +26 -0
  5. package/backend/prisma/migrations/20250628122710_complex_events/migration.sql +36 -0
  6. package/backend/prisma/migrations/migration_lock.toml +2 -2
  7. package/backend/prisma/schema.prisma +45 -14
  8. package/backend/src/api/routes/bots.js +530 -286
  9. package/backend/src/api/routes/eventGraphs.js +375 -0
  10. package/backend/src/api/routes/plugins.js +5 -3
  11. package/backend/src/core/BotManager.js +298 -171
  12. package/backend/src/core/BotProcess.js +312 -44
  13. package/backend/src/core/EventGraphManager.js +164 -0
  14. package/backend/src/core/GraphExecutionEngine.js +706 -0
  15. package/backend/src/core/NodeRegistry.js +888 -0
  16. package/backend/src/core/PluginManager.js +12 -2
  17. package/backend/src/core/UserService.js +15 -2
  18. package/backend/src/core/services.js +12 -0
  19. package/backend/src/core/system/CommandManager.js +2 -0
  20. package/backend/src/lib/logger.js +15 -0
  21. package/backend/src/server.js +12 -4
  22. package/frontend/dist/assets/index-CY4JKfFL.js +8203 -0
  23. package/frontend/dist/assets/index-DC4RjP6E.css +1 -0
  24. package/frontend/dist/index.html +2 -2
  25. package/frontend/package.json +4 -0
  26. package/image/1.png +0 -0
  27. package/image/2.png +0 -0
  28. package/image/3.png +0 -0
  29. package/package.json +7 -2
  30. package/test_visual_command.json +9 -0
  31. package/frontend/dist/assets/index-BGh31hwx.js +0 -8179
  32. package/frontend/dist/assets/index-CKAIPNvH.css +0 -1
@@ -0,0 +1,888 @@
1
+ /**
2
+ * @typedef {object} NodePin
3
+ * @property {string} id - Уникальный идентификатор пина (например, "exec", "data_result").
4
+ * @property {string} name - Читаемое имя пина.
5
+ * @property {string} type - Тип данных пина ("Exec", "String", "Boolean" и т.д.).
6
+ * @property {boolean} [required] - Является ли этот пин обязательным.
7
+ */
8
+
9
+ /**
10
+ * @typedef {object} NodeConfig
11
+ * @property {string} type - Уникальный идентификатор типа узла (например, "action:send_message").
12
+ * @property {string} label - Читаемое имя узла.
13
+ * @property {string} category - Категория для группировки в интерфейсе.
14
+ * @property {string} description - Описание узла.
15
+ * @property {NodePin[]} inputs - Массив описаний входных пинов.
16
+ * @property {NodePin[]} outputs - Массив описаний выходных пинов.
17
+ * @property {Function} [executor] - Функция для выполнения этого узла (на бэкенде).
18
+ */
19
+
20
+ /**
21
+ * Реестр для управления всеми доступными типами узлов.
22
+ */
23
+ class NodeRegistry {
24
+ constructor() {
25
+ this.nodes = new Map();
26
+ this._registerBaseNodes();
27
+ }
28
+
29
+ /**
30
+ * Регистрирует новый тип узла.
31
+ * @param {NodeConfig} nodeConfig - Конфигурация узла.
32
+ */
33
+ registerNodeType(nodeConfig) {
34
+ if (!nodeConfig.type) {
35
+ throw new Error('Node type is required');
36
+ }
37
+
38
+ if (this.nodes.has(nodeConfig.type)) {
39
+ console.warn(`Node type '${nodeConfig.type}' is already registered. Overriding.`);
40
+ }
41
+
42
+ this.nodes.set(nodeConfig.type, nodeConfig);
43
+ console.log(`Registered node type: ${nodeConfig.type}`);
44
+ }
45
+
46
+ /**
47
+ * Получает конфигурацию узла по его типу.
48
+ * @param {string} nodeType - Идентификатор типа узла.
49
+ * @returns {NodeConfig|undefined}
50
+ */
51
+ getNodeConfig(nodeType) {
52
+ return this.nodes.get(nodeType);
53
+ }
54
+
55
+ /**
56
+ * Получает все зарегистрированные типы узлов.
57
+ * @returns {NodeConfig[]}
58
+ */
59
+ getAllNodes() {
60
+ return Array.from(this.nodes.values());
61
+ }
62
+
63
+ /**
64
+ * Возвращает узлы, сгруппированные по категориям.
65
+ * @param {string} [graphType] - Тип графа ('command' или 'event') для фильтрации узлов.
66
+ * @returns {Object.<string, NodeConfig[]>} - Объект с узлами, сгруппированными по категориям.
67
+ */
68
+ getNodesByCategory(graphType) {
69
+ const result = {};
70
+ for (const node of this.nodes.values()) {
71
+ if (node.graphType === 'all' || node.graphType === graphType) {
72
+ if (!result[node.category]) {
73
+ result[node.category] = [];
74
+ }
75
+ result[node.category].push(node);
76
+ }
77
+ }
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Проверяет, существует ли тип узла.
83
+ * @param {string} nodeType - Идентификатор типа узла.
84
+ * @returns {boolean}
85
+ */
86
+ hasNodeType(nodeType) {
87
+ return this.nodes.has(nodeType);
88
+ }
89
+
90
+ /**
91
+ * Регистрирует базовую библиотеку узлов.
92
+ * @private
93
+ */
94
+ _registerBaseNodes() {
95
+ const all = 'all';
96
+ const command = 'command';
97
+ const event = 'event';
98
+
99
+ // События
100
+ this.registerNodeType({
101
+ type: 'event:command',
102
+ label: '▶️ При выполнении команды',
103
+ category: 'События',
104
+ description: 'Стартовая точка для графа команды.',
105
+ graphType: command,
106
+ pins: {
107
+ inputs: [],
108
+ outputs: [
109
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
110
+ { id: 'user', name: 'Пользователь', type: 'User' },
111
+ { id: 'args', name: 'Аргументы', type: 'Object' },
112
+ { id: 'chat_type', name: 'Тип чата', type: 'String' }
113
+ ]
114
+ }
115
+ });
116
+
117
+ this.registerNodeType({
118
+ type: 'event:chat',
119
+ name: 'Событие: Сообщение в чате',
120
+ label: '💬 Сообщение в чате',
121
+ description: 'Срабатывает, когда в чат приходит сообщение.',
122
+ category: 'События',
123
+ graphType: event,
124
+ isEvent: true,
125
+ pins: {
126
+ inputs: [],
127
+ outputs: [
128
+ { id: 'exec', type: 'Exec', name: 'Выполнить' },
129
+ { id: 'username', type: 'String', name: 'Игрок' },
130
+ { id: 'message', type: 'String', name: 'Сообщение' },
131
+ { id: 'chatType', type: 'String', name: 'Тип чата' },
132
+ { id: 'raw', type: 'String', name: 'Raw JSON' },
133
+ ]
134
+ }
135
+ });
136
+
137
+ this.registerNodeType({
138
+ type: 'event:playerJoined',
139
+ label: '👋 Игрок зашел',
140
+ category: 'События',
141
+ description: 'Срабатывает, когда игрок заходит на сервер.',
142
+ graphType: event,
143
+ pins: {
144
+ inputs: [],
145
+ outputs: [
146
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
147
+ { id: 'user', name: 'Пользователь', type: 'User' },
148
+ ]
149
+ }
150
+ });
151
+
152
+ this.registerNodeType({
153
+ type: 'event:playerLeft',
154
+ label: '🚪 Игрок вышел',
155
+ category: 'События',
156
+ description: 'Срабатывает, когда игрок покидает сервер.',
157
+ graphType: event,
158
+ pins: {
159
+ inputs: [],
160
+ outputs: [
161
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
162
+ { id: 'user', name: 'Пользователь', type: 'User' },
163
+ ]
164
+ }
165
+ });
166
+
167
+ this.registerNodeType({
168
+ type: 'event:entitySpawn',
169
+ label: '📦 Сущность появилась',
170
+ category: 'События',
171
+ description: 'Вызывается, когда новая сущность появляется в поле зрения бота.',
172
+ graphType: event,
173
+ pins: {
174
+ inputs: [],
175
+ outputs: [
176
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
177
+ { id: 'entity', name: 'Сущность', type: 'Object' }
178
+ ]
179
+ }
180
+ });
181
+
182
+ this.registerNodeType({
183
+ type: 'event:entityMoved',
184
+ label: '🧍 Сущность подвинулась',
185
+ category: 'События',
186
+ description: 'Вызывается, когда любая сущность перемещается.',
187
+ graphType: event,
188
+ pins: {
189
+ inputs: [],
190
+ outputs: [
191
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
192
+ { id: 'entity', name: 'Сущность', type: 'Object' }
193
+ ]
194
+ }
195
+ });
196
+
197
+ this.registerNodeType({
198
+ type: 'event:entityGone',
199
+ label: '❌ Сущность исчезла',
200
+ category: 'События',
201
+ description: 'Вызывается, когда сущность пропадает из зоны видимости бота.',
202
+ graphType: event,
203
+ pins: {
204
+ inputs: [],
205
+ outputs: [
206
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
207
+ { id: 'entity', name: 'Сущность', type: 'Object' }
208
+ ]
209
+ }
210
+ });
211
+
212
+ this.registerNodeType({
213
+ type: 'flow:branch',
214
+ label: '↔️ Ветвление (Branch)',
215
+ category: 'Поток',
216
+ description: 'if/else логика',
217
+ graphType: all,
218
+ pins: {
219
+ inputs: [
220
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
221
+ { id: 'condition', name: 'Условие', type: 'Boolean', required: true }
222
+ ],
223
+ outputs: [
224
+ { id: 'exec_true', name: 'True', type: 'Exec' },
225
+ { id: 'exec_false', name: 'False', type: 'Exec' }
226
+ ]
227
+ }
228
+ });
229
+
230
+ this.registerNodeType({
231
+ type: 'flow:sequence',
232
+ label: '⛓️ Последовательность',
233
+ category: 'Поток',
234
+ description: 'Выполняет действия по очереди',
235
+ graphType: all,
236
+ pins: {
237
+ inputs: [
238
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true }
239
+ ],
240
+ outputs: [
241
+ { id: 'exec_0', name: '0', type: 'Exec' },
242
+ { id: 'exec_1', name: '1', type: 'Exec' }
243
+ ]
244
+ }
245
+ });
246
+
247
+ this.registerNodeType({
248
+ type: 'flow:for_each',
249
+ label: '🔁 Перебор массива (цикл)',
250
+ category: 'Поток',
251
+ description: 'Выполняет "Тело цикла" для каждого элемента в "Массиве".',
252
+ graphType: all,
253
+ pins: {
254
+ inputs: [
255
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
256
+ { id: 'array', name: 'Массив', type: 'Array', required: true }
257
+ ],
258
+ outputs: [
259
+ { id: 'loop_body', name: 'Тело цикла', type: 'Exec' },
260
+ { id: 'element', name: 'Элемент', type: 'Any' },
261
+ { id: 'index', name: 'Индекс', type: 'Number' },
262
+ { id: 'completed', name: 'Завершено', type: 'Exec' }
263
+ ]
264
+ }
265
+ });
266
+
267
+ this.registerNodeType({
268
+ type: 'flow:break',
269
+ label: '🛑 Выйти из цикла',
270
+ category: 'Поток',
271
+ description: 'Немедленно прерывает выполнение цикла (For Each Loop) и передает управление на его выход Completed.',
272
+ graphType: all,
273
+ pins: {
274
+ inputs: [
275
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true }
276
+ ],
277
+ outputs: []
278
+ }
279
+ });
280
+
281
+ this.registerNodeType({
282
+ type: 'action:send_message',
283
+ label: '🗣️ Отправить сообщение',
284
+ category: 'Действия',
285
+ description: 'Отправляет сообщение в чат',
286
+ graphType: all,
287
+ pins: {
288
+ inputs: [
289
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
290
+ { id: 'chat_type', name: 'Тип чата', type: 'String', required: true },
291
+ { id: 'message', name: 'Сообщение', type: 'String', required: true },
292
+ { id: 'recipient', name: 'Адресат', type: 'String', required: false }
293
+ ],
294
+ outputs: [
295
+ { id: 'exec', name: 'Выполнено', type: 'Exec' }
296
+ ]
297
+ }
298
+ });
299
+
300
+ this.registerNodeType({
301
+ type: 'action:send_log',
302
+ label: '📝 Записать в лог (веб)',
303
+ category: 'Действия',
304
+ description: 'Отправляет сообщение в консоль на странице бота.',
305
+ graphType: all,
306
+ pins: {
307
+ inputs: [
308
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
309
+ { id: 'message', name: 'Сообщение', type: 'String', required: true },
310
+ ],
311
+ outputs: [
312
+ { id: 'exec', name: 'Выполнено', type: 'Exec' },
313
+ ]
314
+ }
315
+ });
316
+
317
+ this.registerNodeType({
318
+ type: 'action:bot_look_at',
319
+ label: '🤖 Бот: Посмотреть на',
320
+ category: 'Действия',
321
+ description: 'Поворачивает голову бота в сторону координат или сущности.',
322
+ graphType: all,
323
+ pins: {
324
+ inputs: [
325
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
326
+ { id: 'target', name: 'Цель (Позиция/Сущность)', type: 'Object', required: true },
327
+ { id: 'add_y', name: 'Прибавить к Y', type: 'Number', required: false }
328
+ ],
329
+ outputs: [
330
+ { id: 'exec', name: 'Выполнено', type: 'Exec' }
331
+ ]
332
+ }
333
+ });
334
+
335
+ this.registerNodeType({
336
+ type: 'action:bot_set_variable',
337
+ label: '💾 Записать переменную',
338
+ category: 'Действия',
339
+ description: 'Сохраняет значение в переменную графа.',
340
+ graphType: all,
341
+ pins: {
342
+ inputs: [
343
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
344
+ { id: 'name', name: 'Имя', type: 'String', required: true },
345
+ { id: 'value', name: 'Значение', type: 'Wildcard', required: true },
346
+ { id: 'persist', name: 'Хранить в БД?', type: 'Boolean', required: false }
347
+ ],
348
+ outputs: [
349
+ { id: 'exec', name: 'Выполнено', type: 'Exec' }
350
+ ]
351
+ }
352
+ });
353
+
354
+ this.registerNodeType({
355
+ type: 'data:get_argument',
356
+ label: '📥 Получить аргумент',
357
+ category: 'Данные',
358
+ description: 'Получает значение аргумента команды по его имени.',
359
+ graphType: command,
360
+ data: {
361
+ argumentName: {
362
+ type: 'argument',
363
+ label: 'Аргумент'
364
+ }
365
+ },
366
+ pins: {
367
+ inputs: [],
368
+ outputs: [
369
+ { id: 'value', name: 'Значение', type: 'Any' },
370
+ { id: 'exists', name: 'Существует', type: 'Boolean' }
371
+ ]
372
+ }
373
+ });
374
+
375
+ this.registerNodeType({
376
+ type: 'data:get_variable',
377
+ label: '📤 Получить переменную',
378
+ category: 'Данные',
379
+ description: 'Получает значение переменной графа.',
380
+ graphType: all,
381
+ pins: {
382
+ inputs: [],
383
+ outputs: [
384
+ { id: 'value', name: 'Значение', type: 'Wildcard' }
385
+ ]
386
+ }
387
+ });
388
+
389
+ this.registerNodeType({
390
+ type: 'data:get_entity_field',
391
+ label: '📦 Получить поле сущности',
392
+ category: 'Данные',
393
+ description: 'Получает определенное поле из объекта сущности (например, "position.x", "username").',
394
+ graphType: all,
395
+ pins: {
396
+ inputs: [
397
+ { id: 'entity', name: 'Сущность', type: 'Object', required: true },
398
+ ],
399
+ outputs: [
400
+ { id: 'username', name: 'Никнейм', type: 'String' },
401
+ { id: 'type', name: 'Тип', type: 'String' },
402
+ { id: 'position', name: 'Позиция', type: 'Object' },
403
+ { id: 'isValid', name: 'Валидна', type: 'Boolean' },
404
+ ]
405
+ }
406
+ });
407
+
408
+ this.registerNodeType({
409
+ type: 'data:string_literal',
410
+ label: '📜 Строка',
411
+ category: 'Данные',
412
+ description: 'Простое текстовое значение.',
413
+ graphType: all,
414
+ pins: {
415
+ inputs: [],
416
+ outputs: [
417
+ { id: 'value', name: 'Значение', type: 'String' }
418
+ ]
419
+ }
420
+ });
421
+
422
+ this.registerNodeType({
423
+ type: 'data:number_literal',
424
+ label: '🔢 Число',
425
+ category: 'Данные',
426
+ description: 'Простое числовое значение.',
427
+ graphType: all,
428
+ pins: {
429
+ inputs: [
430
+ { id: 'value', name: 'Значение', type: 'Number', required: true }
431
+ ],
432
+ outputs: [
433
+ { id: 'value', name: 'Значение', type: 'Number' }
434
+ ]
435
+ }
436
+ });
437
+
438
+ this.registerNodeType({
439
+ type: 'data:boolean_literal',
440
+ label: '✔️ Булево',
441
+ category: 'Данные',
442
+ description: 'Значение Истина/Ложь.',
443
+ graphType: all,
444
+ pins: {
445
+ inputs: [
446
+ { id: 'value', name: 'Значение', type: 'Boolean', required: true }
447
+ ],
448
+ outputs: [
449
+ { id: 'value', name: 'Значение', type: 'Boolean' }
450
+ ]
451
+ }
452
+ });
453
+
454
+ this.registerNodeType({
455
+ type: 'data:array_literal',
456
+ label: '📋 Массив',
457
+ category: 'Данные',
458
+ description: 'Создает массив из элементов.',
459
+ graphType: all,
460
+ dynamicPins: true,
461
+ pins: {
462
+ inputs: [],
463
+ outputs: [
464
+ { id: 'value', name: 'Массив', type: 'Array' }
465
+ ]
466
+ }
467
+ });
468
+
469
+ this.registerNodeType({
470
+ type: 'data:make_object',
471
+ label: '🏗️ Собрать объект',
472
+ category: 'Данные',
473
+ description: 'Создает JSON-объект из пар ключ-значение.',
474
+ graphType: all,
475
+ dynamicPins: true,
476
+ pins: {
477
+ inputs: [],
478
+ outputs: [
479
+ { id: 'value', name: 'Объект', type: 'Object' }
480
+ ]
481
+ }
482
+ });
483
+
484
+ this.registerNodeType({
485
+ type: 'data:cast',
486
+ label: '✨ Приведение типов',
487
+ category: 'Данные',
488
+ description: 'Приводит входящее значение к указанному целевому типу.',
489
+ graphType: all,
490
+ pins: {
491
+ inputs: [
492
+ { id: 'value', name: 'Значение', type: 'Wildcard', required: true }
493
+ ],
494
+ outputs: [
495
+ { id: 'value', name: 'Значение', type: 'Wildcard' }
496
+ ]
497
+ }
498
+ });
499
+
500
+ this.registerNodeType({
501
+ type: 'data:length',
502
+ label: '📏 Размер (длина)',
503
+ category: 'Данные',
504
+ graphType: 'all',
505
+ description: 'Возвращает количество элементов в массиве или длину строки.',
506
+ pins: {
507
+ inputs: [
508
+ { id: 'data', name: 'Массив или Строка', type: 'Any', required: true }
509
+ ],
510
+ outputs: [
511
+ { id: 'length', name: 'Длина', type: 'Number' }
512
+ ]
513
+ }
514
+ });
515
+
516
+ this.registerNodeType({
517
+ type: 'string:contains',
518
+ label: '🔍 Строка: Содержит',
519
+ category: 'Строки',
520
+ description: 'Проверяет, содержит ли одна строка другую.',
521
+ graphType: all,
522
+ pins: {
523
+ inputs: [
524
+ { id: 'exec', name: 'Exec', type: 'Exec', required: true },
525
+ { id: 'haystack', name: 'Строка', type: 'String', required: true },
526
+ { id: 'needle', name: 'Подстрока', type: 'String', required: true },
527
+ { id: 'case_sensitive', name: 'Учет регистра', type: 'Boolean', required: false }
528
+ ],
529
+ outputs: [
530
+ { id: 'exec', name: 'Exec', type: 'Exec' },
531
+ { id: 'result', name: 'Результат', type: 'Boolean' }
532
+ ]
533
+ }
534
+ });
535
+
536
+ this.registerNodeType({
537
+ type: 'string:equals',
538
+ label: 'Строка: Равно',
539
+ category: 'Строки',
540
+ description: 'Проверяет, равны ли строки (с учетом/без учета регистра).',
541
+ graphType: all,
542
+ pins: {
543
+ inputs: [
544
+ { id: 'exec', name: 'Exec', type: 'Exec', required: true },
545
+ { id: 'a', name: 'A', type: 'String', required: true },
546
+ { id: 'b', name: 'B', type: 'String', required: true },
547
+ { id: 'case_sensitive', name: 'Учет регистра', type: 'Boolean', required: false }
548
+ ],
549
+ outputs: [
550
+ { id: 'exec', name: 'Exec', type: 'Exec' },
551
+ { id: 'result', name: 'Результат', type: 'Boolean' }
552
+ ]
553
+ }
554
+ });
555
+
556
+ this.registerNodeType({
557
+ type: 'string:starts_with',
558
+ label: 'Строка: Начинается с',
559
+ category: 'Строки',
560
+ description: 'Проверяет, начинается ли строка с указанной подстроки.',
561
+ graphType: all,
562
+ pins: {
563
+ inputs: [
564
+ { id: 'exec', name: 'Exec', type: 'Exec', required: true },
565
+ { id: 'string', name: 'Строка', type: 'String', required: true },
566
+ { id: 'prefix', name: 'Префикс', type: 'String', required: true },
567
+ { id: 'case_sensitive', name: 'Учет регистра', type: 'Boolean', required: false }
568
+ ],
569
+ outputs: [
570
+ { id: 'exec', name: 'Exec', type: 'Exec' },
571
+ { id: 'result', name: 'Результат', type: 'Boolean' }
572
+ ]
573
+ }
574
+ });
575
+
576
+ this.registerNodeType({
577
+ type: 'string:ends_with',
578
+ label: 'Строка: Заканчивается на',
579
+ category: 'Строки',
580
+ description: 'Проверяет, заканчивается ли строка указанной подстрокой.',
581
+ graphType: all,
582
+ pins: {
583
+ inputs: [
584
+ { id: 'exec', name: 'Exec', type: 'Exec', required: true },
585
+ { id: 'string', name: 'Строка', type: 'String', required: true },
586
+ { id: 'suffix', name: 'Суффикс', type: 'String', required: true },
587
+ { id: 'case_sensitive', name: 'Учет регистра', type: 'Boolean', required: false }
588
+ ],
589
+ outputs: [
590
+ { id: 'exec', name: 'Exec', type: 'Exec' },
591
+ { id: 'result', name: 'Результат', type: 'Boolean' }
592
+ ]
593
+ }
594
+ });
595
+
596
+ this.registerNodeType({
597
+ type: 'string:length',
598
+ label: 'Строка: Длина',
599
+ category: 'Строки',
600
+ description: 'Возвращает количество символов в строке.',
601
+ graphType: all,
602
+ pins: {
603
+ inputs: [
604
+ { id: 'exec', name: 'Exec', type: 'Exec', required: true },
605
+ { id: 'string', name: 'Строка', type: 'String', required: true }
606
+ ],
607
+ outputs: [
608
+ { id: 'exec', name: 'Exec', type: 'Exec' },
609
+ { id: 'length', name: 'Длина', type: 'Number' }
610
+ ]
611
+ }
612
+ });
613
+
614
+ this.registerNodeType({
615
+ type: 'string:split',
616
+ label: 'Строка: Разделить',
617
+ category: 'Строки',
618
+ description: 'Разделяет строку на массив подстрок по разделителю.',
619
+ graphType: all,
620
+ pins: {
621
+ inputs: [
622
+ { id: 'exec', name: 'Exec', type: 'Exec', required: true },
623
+ { id: 'string', name: 'Строка', type: 'String', required: true },
624
+ { id: 'separator', name: 'Разделитель', type: 'String', required: true }
625
+ ],
626
+ outputs: [
627
+ { id: 'exec', name: 'Exec', type: 'Exec' },
628
+ { id: 'array', name: 'Массив', type: 'Array' }
629
+ ]
630
+ }
631
+ });
632
+
633
+ this.registerNodeType({
634
+ type: 'string:concat',
635
+ label: 'Строка: Объединить',
636
+ category: 'Строки',
637
+ description: 'Объединяет две или более строки в одну.',
638
+ graphType: all,
639
+ dynamicPins: true,
640
+ pins: {
641
+ inputs: [],
642
+ outputs: [
643
+ { id: 'result', name: 'Результат', type: 'String' }
644
+ ]
645
+ }
646
+ });
647
+
648
+ this.registerNodeType({
649
+ type: 'math:operation',
650
+ label: '🔢 Математика',
651
+ category: 'Математика',
652
+ description: 'Выполняет математическую операцию над двумя числами.',
653
+ graphType: all,
654
+ pins: {
655
+ inputs: [
656
+ { id: 'a', name: 'A', type: 'Number', required: true },
657
+ { id: 'b', name: 'B', type: 'Number', required: true }
658
+ ],
659
+ outputs: [
660
+ { id: 'result', name: 'Результат', type: 'Number' }
661
+ ]
662
+ }
663
+ });
664
+
665
+ this.registerNodeType({
666
+ type: 'logic:operation',
667
+ label: '💡 Логика',
668
+ category: 'Логика',
669
+ description: 'Выполняет логическую операцию. Для НЕ (NOT) используется только вход А.',
670
+ graphType: all,
671
+ dynamicPins: true,
672
+ pins: {
673
+ inputs: [
674
+ { id: 'a', name: 'A', type: 'Boolean', required: true },
675
+ { id: 'b', name: 'B', type: 'Boolean', required: true }
676
+ ],
677
+ outputs: [
678
+ { id: 'result', name: 'Результат', type: 'Boolean' }
679
+ ]
680
+ }
681
+ });
682
+
683
+ this.registerNodeType({
684
+ type: 'debug:log',
685
+ label: '🐞 Отладка (консоль)',
686
+ category: 'Отладка',
687
+ description: 'Выводит значение в консоль терминала, где запущен бот.',
688
+ graphType: all,
689
+ pins: {
690
+ inputs: [
691
+ { id: 'exec', name: 'Exec', type: 'Exec' },
692
+ { id: 'value', name: 'Значение', type: 'Wildcard', required: true }
693
+ ],
694
+ outputs: [
695
+ { id: 'exec', name: 'Exec', type: 'Exec' }
696
+ ]
697
+ }
698
+ });
699
+
700
+ this.registerNodeType({
701
+ type: 'math:random_number',
702
+ label: '🎲 Случайное число',
703
+ category: 'Математика',
704
+ graphType: 'all',
705
+ description: 'Генерирует случайное число в заданном диапазоне.',
706
+ pins: {
707
+ inputs: [
708
+ { id: 'min', name: 'Мин', type: 'Number' },
709
+ { id: 'max', name: 'Макс', type: 'Number' }
710
+ ],
711
+ outputs: [{ id: 'result', name: 'Результат', type: 'Number' }]
712
+ }
713
+ });
714
+
715
+ this.registerNodeType({
716
+ type: 'array:get_random_element',
717
+ label: '🎲 Случайный элемент',
718
+ category: 'Данные',
719
+ graphType: 'all',
720
+ description: 'Возвращает случайный элемент из массива и его индекс.',
721
+ pins: {
722
+ inputs: [
723
+ { id: 'array', name: 'Массив', type: 'Array', required: true }
724
+ ],
725
+ outputs: [
726
+ { id: 'element', name: 'Элемент', type: 'Any' },
727
+ { id: 'index', name: 'Индекс', type: 'Number' }
728
+ ]
729
+ }
730
+ });
731
+
732
+ this.registerNodeType({
733
+ type: 'data:get_server_players',
734
+ label: '👥 Список игроков',
735
+ category: 'Данные',
736
+ graphType: 'all',
737
+ description: 'Возвращает массив с именами всех игроков на сервере.',
738
+ pins: {
739
+ inputs: [],
740
+ outputs: [
741
+ { id: 'players', name: 'Игроки', type: 'Array' }
742
+ ]
743
+ }
744
+ });
745
+
746
+ this.registerNodeType({
747
+ type: 'logic:compare',
748
+ label: '⎗ Сравнение',
749
+ category: 'Логика',
750
+ description: 'Сравнивает два значения.',
751
+ graphType: all,
752
+ pins: {
753
+ inputs: [
754
+ { id: 'a', name: 'A', type: 'Wildcard' },
755
+ { id: 'b', name: 'B', type: 'Wildcard' }
756
+ ],
757
+ outputs: [
758
+ { id: 'result', name: 'Результат', type: 'Boolean' }
759
+ ]
760
+ }
761
+ });
762
+
763
+ this.registerNodeType({
764
+ type: 'bot:get_position',
765
+ label: '🤖 Позиция бота',
766
+ category: 'Бот',
767
+ description: 'Возвращает текущую позицию бота в мире.',
768
+ graphType: all,
769
+ pins: {
770
+ inputs: [],
771
+ outputs: [
772
+ { id: 'position', name: 'Позиция', type: 'Object' }
773
+ ]
774
+ }
775
+ });
776
+
777
+ // Пользователи
778
+ this.registerNodeType({
779
+ type: 'user:check_blacklist',
780
+ label: '❓ В черном списке?',
781
+ category: 'Пользователи',
782
+ description: 'Проверяет, находится ли пользователь в черном списке.',
783
+ graphType: all,
784
+ pins: {
785
+ inputs: [
786
+ { id: 'user', name: 'Пользователь', type: 'User', required: true }
787
+ ],
788
+ outputs: [
789
+ { id: 'is_blacklisted', name: 'В ЧС', type: 'Boolean' }
790
+ ]
791
+ }
792
+ });
793
+
794
+ this.registerNodeType({
795
+ type: 'user:set_blacklist',
796
+ label: '🚫 Установить ЧС',
797
+ category: 'Пользователи',
798
+ description: 'Добавляет или убирает пользователя из черного списка.',
799
+ graphType: all,
800
+ pins: {
801
+ inputs: [
802
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
803
+ { id: 'user', name: 'Пользователь', type: 'User', required: true },
804
+ { id: 'blacklist_status', name: 'Статус ЧС', type: 'Boolean', required: true }
805
+ ],
806
+ outputs: [
807
+ { id: 'exec', name: 'Далее', type: 'Exec' },
808
+ { id: 'updated_user', name: 'Обновленный пользователь', type: 'User' }
809
+ ]
810
+ }
811
+ });
812
+
813
+ // Переменные
814
+ this.registerNodeType({
815
+ type: 'variable:get',
816
+ label: '📤 Получить переменную',
817
+ category: 'Переменные',
818
+ description: 'Получает значение переменной графа.',
819
+ graphType: event,
820
+ pins: {
821
+ inputs: [],
822
+ outputs: [
823
+ { id: 'value', name: 'Значение', type: 'Wildcard' }
824
+ ]
825
+ }
826
+ });
827
+
828
+ this.registerNodeType({
829
+ type: 'user:get_groups',
830
+ label: '👥 Получить группы',
831
+ category: 'Пользователь',
832
+ description: 'Возвращает массив названий групп, в которых состоит пользователь.',
833
+ graphType: all,
834
+ pins: {
835
+ inputs: [
836
+ { id: 'user', name: 'Пользователь', type: 'User', required: true }
837
+ ],
838
+ outputs: [
839
+ { id: 'groups', name: 'Группы', type: 'Array' }
840
+ ]
841
+ }
842
+ });
843
+
844
+ this.registerNodeType({
845
+ type: 'user:get_permissions',
846
+ label: '🔑 Получить права',
847
+ category: 'Пользователь',
848
+ description: 'Возвращает массив прав пользователя.',
849
+ graphType: all,
850
+ pins: {
851
+ inputs: [
852
+ { id: 'user', name: 'Пользователь', type: 'User', required: true }
853
+ ],
854
+ outputs: [
855
+ { id: 'permissions', name: 'Права', type: 'Array' }
856
+ ]
857
+ }
858
+ });
859
+
860
+ this.registerNodeType({
861
+ type: 'data:get_user_field',
862
+ label: '👤 Данные пользователя',
863
+ category: 'Данные',
864
+ description: 'Получает различные данные из объекта пользователя.',
865
+ graphType: all,
866
+ pins: {
867
+ inputs: [
868
+ { id: 'user', name: 'Пользователь', type: 'User', required: true }
869
+ ],
870
+ outputs: [
871
+ { id: 'username', name: 'Никнейм', type: 'String' },
872
+ { id: 'groups', name: 'Группы', type: 'Array' },
873
+ { id: 'permissions', name: 'Права', type: 'Array' },
874
+ { id: 'isBlacklisted', name: 'В черном списке', type: 'Boolean' },
875
+ ]
876
+ }
877
+ });
878
+
879
+ console.log(`NodeRegistry: Registered ${this.nodes.size} base nodes`);
880
+ }
881
+
882
+ getNodesByTypes(types) {
883
+ return types.map(type => this.nodes.get(type)).filter(Boolean);
884
+ }
885
+ }
886
+
887
+ const nodeRegistryInstance = new NodeRegistry();
888
+ module.exports = nodeRegistryInstance;