blockmine 1.22.0 → 1.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/.claude/agents/code-architect.md +34 -0
  2. package/.claude/agents/code-explorer.md +51 -0
  3. package/.claude/agents/code-reviewer.md +46 -0
  4. package/.claude/commands/feature-dev.md +125 -0
  5. package/.claude/settings.json +5 -1
  6. package/.claude/settings.local.json +12 -1
  7. package/.claude/skills/frontend-design/SKILL.md +42 -0
  8. package/CHANGELOG.md +32 -1
  9. package/README.md +302 -152
  10. package/backend/package-lock.json +681 -9
  11. package/backend/package.json +8 -0
  12. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
  13. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
  14. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
  15. package/backend/prisma/schema.prisma +70 -1
  16. package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
  17. package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
  18. package/backend/src/api/middleware/auth.js +27 -0
  19. package/backend/src/api/middleware/botAccess.js +7 -3
  20. package/backend/src/api/middleware/panelApiAuth.js +135 -0
  21. package/backend/src/api/routes/aiAssistant.js +995 -0
  22. package/backend/src/api/routes/auth.js +90 -54
  23. package/backend/src/api/routes/botCommands.js +107 -0
  24. package/backend/src/api/routes/botGroups.js +165 -0
  25. package/backend/src/api/routes/botHistory.js +108 -0
  26. package/backend/src/api/routes/botPermissions.js +99 -0
  27. package/backend/src/api/routes/botStatus.js +36 -0
  28. package/backend/src/api/routes/botUsers.js +162 -0
  29. package/backend/src/api/routes/bots.js +108 -59
  30. package/backend/src/api/routes/eventGraphs.js +4 -1
  31. package/backend/src/api/routes/logs.js +13 -3
  32. package/backend/src/api/routes/panel.js +3 -3
  33. package/backend/src/api/routes/panelApiKeys.js +179 -0
  34. package/backend/src/api/routes/pluginIde.js +1715 -135
  35. package/backend/src/api/routes/plugins.js +170 -13
  36. package/backend/src/api/routes/proxies.js +130 -0
  37. package/backend/src/api/routes/search.js +4 -0
  38. package/backend/src/api/routes/servers.js +20 -3
  39. package/backend/src/api/routes/settings.js +5 -0
  40. package/backend/src/api/routes/system.js +3 -3
  41. package/backend/src/api/routes/traces.js +131 -0
  42. package/backend/src/config/debug.config.js +36 -0
  43. package/backend/src/core/BotHistoryStore.js +180 -0
  44. package/backend/src/core/BotManager.js +14 -4
  45. package/backend/src/core/BotProcess.js +1517 -1092
  46. package/backend/src/core/EventGraphManager.js +194 -280
  47. package/backend/src/core/GraphExecutionEngine.js +1004 -321
  48. package/backend/src/core/MessageQueue.js +12 -6
  49. package/backend/src/core/PluginLoader.js +99 -5
  50. package/backend/src/core/PluginManager.js +74 -13
  51. package/backend/src/core/TaskScheduler.js +1 -1
  52. package/backend/src/core/commands/whois.js +1 -1
  53. package/backend/src/core/node-registries/actions.js +72 -2
  54. package/backend/src/core/node-registries/arrays.js +18 -0
  55. package/backend/src/core/node-registries/data.js +1 -1
  56. package/backend/src/core/node-registries/events.js +14 -0
  57. package/backend/src/core/node-registries/logic.js +17 -0
  58. package/backend/src/core/node-registries/strings.js +34 -0
  59. package/backend/src/core/node-registries/type.js +25 -0
  60. package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
  61. package/backend/src/core/nodes/actions/create_command.js +189 -0
  62. package/backend/src/core/nodes/actions/delete_command.js +92 -0
  63. package/backend/src/core/nodes/actions/http_request.js +23 -4
  64. package/backend/src/core/nodes/actions/send_message.js +2 -12
  65. package/backend/src/core/nodes/actions/update_command.js +133 -0
  66. package/backend/src/core/nodes/arrays/join.js +28 -0
  67. package/backend/src/core/nodes/data/cast.js +2 -1
  68. package/backend/src/core/nodes/data/string_literal.js +2 -13
  69. package/backend/src/core/nodes/logic/not.js +22 -0
  70. package/backend/src/core/nodes/strings/starts_with.js +1 -1
  71. package/backend/src/core/nodes/strings/to_lower.js +22 -0
  72. package/backend/src/core/nodes/strings/to_upper.js +22 -0
  73. package/backend/src/core/nodes/type/to_string.js +32 -0
  74. package/backend/src/core/services/BotLifecycleService.js +835 -596
  75. package/backend/src/core/services/CommandExecutionService.js +430 -351
  76. package/backend/src/core/services/DebugSessionManager.js +347 -0
  77. package/backend/src/core/services/GraphCollaborationManager.js +501 -0
  78. package/backend/src/core/services/MinecraftBotManager.js +259 -0
  79. package/backend/src/core/services/MinecraftViewerService.js +216 -0
  80. package/backend/src/core/services/TraceCollectorService.js +545 -0
  81. package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
  82. package/backend/src/core/system/Transport.js +0 -4
  83. package/backend/src/core/validation/nodeSchemas.js +6 -6
  84. package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
  85. package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
  86. package/backend/src/real-time/botApi/utils.js +11 -0
  87. package/backend/src/real-time/panelNamespace.js +387 -0
  88. package/backend/src/real-time/presence.js +7 -2
  89. package/backend/src/real-time/socketHandler.js +395 -4
  90. package/backend/src/server.js +18 -0
  91. package/frontend/dist/assets/index-DqzDkFsP.js +11210 -0
  92. package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
  93. package/frontend/dist/index.html +2 -2
  94. package/frontend/package-lock.json +9437 -0
  95. package/frontend/package.json +8 -0
  96. package/package.json +2 -2
  97. package/screen/console.png +0 -0
  98. package/screen/dashboard.png +0 -0
  99. package/screen/graph_collabe.png +0 -0
  100. package/screen/graph_live_debug.png +0 -0
  101. package/screen/management_command.png +0 -0
  102. package/screen/node_debug_trace.png +0 -0
  103. package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
  104. package/screen/websocket.png +0 -0
  105. 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
  106. 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
  107. package/frontend/dist/assets/index-CfTo92bP.css +0 -1
  108. package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
@@ -109,15 +109,21 @@ class MessageQueue {
109
109
  await this._handleWaitableTask(task);
110
110
  } else {
111
111
  let fullMessage;
112
- if (task.chatType === 'private' && task.username) {
112
+ const typeConfig = this.chatTypes[task.chatType];
113
+
114
+ // Для private/whisper добавляем username
115
+ if ((task.chatType === 'private' || task.chatType === 'whisper') && task.username) {
113
116
  fullMessage = `/msg ${task.username} ${task.message}`;
114
- } else if (task.chatType === 'clan') {
115
- fullMessage = `/cc ${task.message}`;
116
- } else if (task.chatType === 'global') {
117
- fullMessage = `!${task.message}`;
118
- } else {
117
+ }
118
+ // Для остальных типов используем prefix из конфига
119
+ else if (typeConfig && typeConfig.prefix) {
120
+ fullMessage = `${typeConfig.prefix}${task.message}`;
121
+ }
122
+ // Если нет prefix (command, chat) - отправляем как есть
123
+ else {
119
124
  fullMessage = task.message;
120
125
  }
126
+
121
127
  this.bot.chat(fullMessage);
122
128
  }
123
129
  } catch (error) {
@@ -10,7 +10,72 @@ const { execSync: execSyncRaw } = require('child_process');
10
10
 
11
11
  const projectRoot = path.resolve(__dirname, '..');
12
12
 
13
+ // Создаёт обёрнутый console для перехвата логов плагина
14
+ function createPluginConsole(botId, pluginName, originalConsole) {
15
+ const emitLog = (level, args) => {
16
+ try {
17
+ const message = args.map(arg =>
18
+ typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
19
+ ).join(' ');
13
20
 
21
+ // Фильтруем системные логи (не отправляем в IDE)
22
+ const systemLogPatterns = [
23
+ /^\[Config\]/, // [Config] сообщения
24
+ /^\[System\]/, // [System] сообщения
25
+ /^\[Internal\]/, // [Internal] сообщения
26
+ /^\[Graph\]/, // [Graph] логи от визуальных графов
27
+ /\[Graph Log\]/, // [Graph Log] логи от нод графов
28
+ ];
29
+
30
+ const isSystemLog = systemLogPatterns.some(pattern => pattern.test(message));
31
+ if (isSystemLog) {
32
+ return; // Не отправляем системные логи в IDE
33
+ }
34
+
35
+ const logData = {
36
+ botId,
37
+ pluginName,
38
+ level,
39
+ message,
40
+ source: 'plugin',
41
+ timestamp: Date.now()
42
+ };
43
+
44
+ // Отправляем лог в parent process через IPC
45
+ if (process.send) {
46
+ process.send({
47
+ type: 'plugin-log',
48
+ log: logData
49
+ });
50
+ }
51
+ } catch (error) {
52
+ originalConsole.error(`[PluginLog] Error emitting log:`, error);
53
+ }
54
+ };
55
+
56
+ return {
57
+ log: (...args) => {
58
+ originalConsole.log(`[${pluginName}]`, ...args);
59
+ emitLog('info', args);
60
+ },
61
+ info: (...args) => {
62
+ originalConsole.info(`[${pluginName}]`, ...args);
63
+ emitLog('info', args);
64
+ },
65
+ warn: (...args) => {
66
+ originalConsole.warn(`[${pluginName}]`, ...args);
67
+ emitLog('warn', args);
68
+ },
69
+ error: (...args) => {
70
+ originalConsole.error(`[${pluginName}]`, ...args);
71
+ emitLog('error', args);
72
+ },
73
+ debug: (...args) => {
74
+ originalConsole.debug(`[${pluginName}]`, ...args);
75
+ emitLog('debug', args);
76
+ }
77
+ };
78
+ }
14
79
 
15
80
  async function initializePlugins(bot, installedPlugins = [], prisma) {
16
81
  if (!installedPlugins || installedPlugins.length === 0) return;
@@ -70,23 +135,52 @@ async function initializePlugins(bot, installedPlugins = [], prisma) {
70
135
  }
71
136
  }
72
137
 
138
+ // Создаём обёрнутый console для перехвата логов плагина
139
+ const originalConsole = global.console;
140
+ const pluginConsole = createPluginConsole(bot.config.id, plugin.name, originalConsole);
141
+
142
+ // Добавляем кастомный console в bot объект для использования плагинами
143
+ bot.console = pluginConsole;
144
+
145
+ // Опции плагина с кастомным console
146
+ const pluginOptions = {
147
+ settings: finalSettings,
148
+ store,
149
+ console: pluginConsole // Передаём обёрнутый console в опциях
150
+ };
151
+
73
152
  if (typeof pluginModule === 'function') {
74
- pluginModule(bot, { settings: finalSettings, store });
153
+ pluginModule(bot, pluginOptions);
75
154
  } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
76
- pluginModule.onLoad(bot, { settings: finalSettings, store });
155
+ pluginModule.onLoad(bot, pluginOptions);
77
156
  } else if (pluginModule && pluginModule.default && typeof pluginModule.default === 'function') {
78
- pluginModule.default(bot, { settings: finalSettings, store });
157
+ pluginModule.default(bot, pluginOptions);
79
158
  } else if (pluginModule && pluginModule.default && typeof pluginModule.default.onLoad === 'function') {
80
- pluginModule.default.onLoad(bot, { settings: finalSettings, store });
159
+ pluginModule.default.onLoad(bot, pluginOptions);
81
160
  } else {
82
161
  sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
83
162
  }
84
163
  };
85
164
 
86
165
  sendLog(`[PluginLoader] Загрузка: ${plugin.name} (v${plugin.version}) из ${normalizedPath}`);
87
-
166
+
88
167
  try {
89
168
  await loadAndInit();
169
+
170
+ // Отправляем сообщение об успешной загрузке в IDE console
171
+ const { getIOSafe } = require('../real-time/socketHandler');
172
+ const io = getIOSafe();
173
+ if (io) {
174
+ const room = `plugin-logs:${bot.config.id}:${plugin.name}`;
175
+ io.to(room).emit('plugin-log', {
176
+ botId: bot.config.id,
177
+ pluginName: plugin.name,
178
+ level: 'success',
179
+ message: `✓ Плагин ${plugin.name} v${plugin.version} успешно загружен`,
180
+ source: 'system',
181
+ timestamp: Date.now()
182
+ });
183
+ }
90
184
  } catch (error) {
91
185
  let handled = false;
92
186
  let lastError = error;
@@ -360,28 +360,34 @@ class PluginManager {
360
360
  }
361
361
 
362
362
  console.log(`[PluginManager] Начало обновления плагина ${plugin.name}${targetTag ? ` до версии ${targetTag}` : ''}...`);
363
-
363
+
364
364
  const repoUrl = plugin.sourceUri;
365
365
  const botId = plugin.botId;
366
-
367
- // Сохраняем настройки и важные данные перед удалением
366
+ const oldVersion = plugin.version;
367
+
368
368
  const backupData = {
369
369
  name: plugin.name,
370
370
  settings: plugin.settings,
371
371
  isEnabled: plugin.isEnabled,
372
- // PluginDataStore сохранятся автоматически (привязаны к pluginName + botId)
373
372
  };
374
373
  console.log(`[PluginManager] Настройки плагина ${plugin.name} сохранены для миграции.`);
375
-
376
- // Удаляем старую версию
374
+
377
375
  await this.deletePlugin(pluginId);
378
376
  console.log(`[PluginManager] Старая версия ${plugin.name} удалена, устанавливаем новую...`);
379
-
380
- // Устанавливаем новую версию с конкретным тегом (если указан)
377
+
381
378
  const newPlugin = await this.installFromGithub(botId, repoUrl, prisma, true, targetTag);
382
-
383
- // Восстанавливаем настройки
384
- if (backupData.settings && newPlugin) {
379
+
380
+ const oldMajor = semver.major(semver.coerce(oldVersion) || '0.0.0');
381
+ const newMajor = semver.major(semver.coerce(newPlugin.version) || '0.0.0');
382
+ const isMajorUpdate = newMajor > oldMajor;
383
+
384
+ if (isMajorUpdate) {
385
+ console.log(`[PluginManager] Мажорное обновление ${oldVersion} → ${newPlugin.version}. Настройки сброшены к дефолтным.`);
386
+ await prisma.installedPlugin.update({
387
+ where: { id: newPlugin.id },
388
+ data: { isEnabled: backupData.isEnabled },
389
+ });
390
+ } else if (backupData.settings && newPlugin) {
385
391
  try {
386
392
  await prisma.installedPlugin.update({
387
393
  where: { id: newPlugin.id },
@@ -393,10 +399,9 @@ class PluginManager {
393
399
  console.log(`[PluginManager] Настройки успешно восстановлены для ${plugin.name}`);
394
400
  } catch (settingsError) {
395
401
  console.error(`[PluginManager] Не удалось восстановить настройки для ${plugin.name}:`, settingsError);
396
- // Не бросаем ошибку, т.к. плагин уже установлен
397
402
  }
398
403
  }
399
-
404
+
400
405
  return newPlugin;
401
406
  }
402
407
 
@@ -511,6 +516,62 @@ class PluginManager {
511
516
  console.log(`[PluginManager] Удалено ${count} записей из хранилища.`);
512
517
  return { count };
513
518
  }
519
+
520
+ async reloadLocalPlugin(pluginId) {
521
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
522
+ if (!plugin) {
523
+ throw new Error('Плагин не найден.');
524
+ }
525
+
526
+ if (plugin.sourceType !== 'LOCAL' && plugin.sourceType !== 'LOCAL_IDE') {
527
+ throw new Error('Перезагрузка доступна только для локальных плагинов.');
528
+ }
529
+
530
+ const pluginPath = plugin.path;
531
+ const packageJsonPath = path.join(pluginPath, 'package.json');
532
+
533
+ if (!await fse.pathExists(packageJsonPath)) {
534
+ throw new Error(`package.json не найден: ${packageJsonPath}`);
535
+ }
536
+
537
+ console.log(`[PluginManager] Перезагрузка локального плагина ${plugin.name} из ${pluginPath}`);
538
+
539
+ let packageJson;
540
+ try {
541
+ packageJson = JSON.parse(await fse.readFile(packageJsonPath, 'utf-8'));
542
+ } catch (e) {
543
+ throw new Error(`Не удалось прочитать package.json: ${e.message}`);
544
+ }
545
+
546
+ const manifest = packageJson.botpanel || {};
547
+ const defaultSettings = {};
548
+
549
+ if (manifest.settings) {
550
+ for (const [key, config] of Object.entries(manifest.settings)) {
551
+ if (config.default !== undefined) {
552
+ defaultSettings[key] = config.default;
553
+ }
554
+ }
555
+ }
556
+
557
+ const updatedPlugin = await prisma.installedPlugin.update({
558
+ where: { id: pluginId },
559
+ data: {
560
+ version: packageJson.version,
561
+ description: packageJson.description || '',
562
+ manifest: JSON.stringify(manifest),
563
+ settings: JSON.stringify(defaultSettings),
564
+ },
565
+ });
566
+
567
+ console.log(`[PluginManager] Плагин ${plugin.name} перезагружен. Версия: ${packageJson.version}, настройки сброшены.`);
568
+
569
+ if (this.botManager) {
570
+ await this.botManager.reloadPlugins(plugin.botId);
571
+ }
572
+
573
+ return updatedPlugin;
574
+ }
514
575
  }
515
576
 
516
577
  module.exports = PluginManager;
@@ -51,7 +51,7 @@ class TaskScheduler {
51
51
 
52
52
  for (const botId of botIds) {
53
53
  try {
54
- const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
54
+ const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true, proxy: true } });
55
55
  if (!botConfig) continue;
56
56
 
57
57
  switch (task.action) {
@@ -33,7 +33,7 @@ class WhoisCommand extends Command {
33
33
  .join(', ') || 'Нет групп';
34
34
 
35
35
  // Статус
36
- const status = targetUser.isBlacklisted ? '⛔ В черном списке' : '✅ Активен';
36
+ const status = targetUser.isBlacklisted ? '⛔ В черном списке' : '✅ Не в чс';
37
37
 
38
38
  // Форматируем вывод в зависимости от транспорта
39
39
  let message;
@@ -10,7 +10,6 @@ function registerNodes(registry) {
10
10
  category: 'Действия',
11
11
  description: 'Отправляет сообщение в чат. Поддерживает переменные в формате {varName}',
12
12
  graphType: GRAPH_TYPES.ALL,
13
- dynamicPins: true,
14
13
  executor: require('../nodes/actions/send_message').execute,
15
14
  pins: {
16
15
  inputs: [
@@ -112,7 +111,8 @@ function registerNodes(registry) {
112
111
  { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
113
112
  { id: 'url', name: 'URL', type: 'String', required: true },
114
113
  { id: 'method', name: 'Метод', type: 'String', required: false },
115
- { id: 'headers', name: 'Заголовки (JSON)', type: 'String', required: false },
114
+ { id: 'queryParams', name: 'Query Params', type: 'Object', required: false },
115
+ { id: 'headers', name: 'Headers', type: 'Object', required: false },
116
116
  { id: 'body', name: 'Тело (JSON)', type: 'Wildcard', required: false },
117
117
  { id: 'timeout', name: 'Таймаут (мс)', type: 'Number', required: false }
118
118
  ],
@@ -127,6 +127,76 @@ function registerNodes(registry) {
127
127
  ]
128
128
  }
129
129
  });
130
+
131
+ registry.registerNodeType({
132
+ type: 'action:create_command',
133
+ label: '➕ Создать команду',
134
+ category: 'Действия',
135
+ description: 'Создает новую команду (временную или постоянную)',
136
+ graphType: GRAPH_TYPES.ALL,
137
+ executor: require('../nodes/actions/create_command').execute,
138
+ pins: {
139
+ inputs: [
140
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
141
+ { id: 'name', name: 'Имя команды', type: 'String', required: true },
142
+ { id: 'description', name: 'Описание', type: 'String', required: false },
143
+ { id: 'aliases', name: 'Алиасы', type: 'Array', required: false },
144
+ { id: 'cooldown', name: 'Кулдаун (сек)', type: 'Number', required: false },
145
+ { id: 'allowedChatTypes', name: 'Типы чата', type: 'Array', required: false },
146
+ { id: 'permissionName', name: 'Название права', type: 'String', required: false },
147
+ { id: 'temporary', name: 'Временная?', type: 'Boolean', required: false }
148
+ ],
149
+ outputs: [
150
+ { id: 'exec', name: 'Выполнено', type: 'Exec' },
151
+ { id: 'commandId', name: 'ID команды', type: 'Number' },
152
+ { id: 'success', name: 'Успешно', type: 'Boolean' }
153
+ ]
154
+ }
155
+ });
156
+
157
+ registry.registerNodeType({
158
+ type: 'action:update_command',
159
+ label: '✏️ Редактировать команду',
160
+ category: 'Действия',
161
+ description: 'Изменяет параметры существующей команды',
162
+ graphType: GRAPH_TYPES.ALL,
163
+ executor: require('../nodes/actions/update_command').execute,
164
+ pins: {
165
+ inputs: [
166
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
167
+ { id: 'commandName', name: 'Имя команды', type: 'String', required: true },
168
+ { id: 'newName', name: 'Новое имя', type: 'String', required: false },
169
+ { id: 'description', name: 'Описание', type: 'String', required: false },
170
+ { id: 'aliases', name: 'Алиасы', type: 'Array', required: false },
171
+ { id: 'cooldown', name: 'Кулдаун (сек)', type: 'Number', required: false },
172
+ { id: 'allowedChatTypes', name: 'Типы чата', type: 'Array', required: false },
173
+ { id: 'permissionName', name: 'Название права', type: 'String', required: false }
174
+ ],
175
+ outputs: [
176
+ { id: 'exec', name: 'Выполнено', type: 'Exec' },
177
+ { id: 'success', name: 'Успешно', type: 'Boolean' }
178
+ ]
179
+ }
180
+ });
181
+
182
+ registry.registerNodeType({
183
+ type: 'action:delete_command',
184
+ label: '🗑️ Удалить команду',
185
+ category: 'Действия',
186
+ description: 'Удаляет существующую команду бота',
187
+ graphType: GRAPH_TYPES.ALL,
188
+ executor: require('../nodes/actions/delete_command').execute,
189
+ pins: {
190
+ inputs: [
191
+ { id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
192
+ { id: 'commandName', name: 'Имя команды', type: 'String', required: true }
193
+ ],
194
+ outputs: [
195
+ { id: 'exec', name: 'Выполнено', type: 'Exec' },
196
+ { id: 'success', name: 'Успешно', type: 'Boolean' }
197
+ ]
198
+ }
199
+ });
130
200
  }
131
201
 
132
202
  module.exports = { registerNodes };
@@ -132,6 +132,24 @@ function registerNodes(registry) {
132
132
  ]
133
133
  }
134
134
  });
135
+
136
+ registry.registerNodeType({
137
+ type: 'array:join',
138
+ label: '🔗 Объединить в строку',
139
+ category: 'Массив',
140
+ description: 'Объединяет элементы массива в строку с разделителем.',
141
+ graphType: GRAPH_TYPES.ALL,
142
+ evaluator: require('../nodes/arrays/join').evaluate,
143
+ pins: {
144
+ inputs: [
145
+ { id: 'array', name: 'Массив', type: 'Array', required: false },
146
+ { id: 'separator', name: 'Разделитель', type: 'String', required: false }
147
+ ],
148
+ outputs: [
149
+ { id: 'result', name: 'Result', type: 'String' }
150
+ ]
151
+ }
152
+ });
135
153
  }
136
154
 
137
155
  module.exports = { registerNodes };
@@ -139,7 +139,7 @@ function registerNodes(registry) {
139
139
  { id: 'value', name: 'Значение', type: 'Wildcard', required: true }
140
140
  ],
141
141
  outputs: [
142
- { id: 'value', name: 'Значение', type: 'Wildcard' }
142
+ { id: 'result', name: 'Результат', type: 'Wildcard' }
143
143
  ]
144
144
  }
145
145
  });
@@ -148,6 +148,20 @@ function registerNodes(registry) {
148
148
  }
149
149
  });
150
150
 
151
+ registry.registerNodeType({
152
+ type: 'event:botStartup',
153
+ label: '🚀 При запуске бота',
154
+ category: 'События',
155
+ description: 'Срабатывает один раз при запуске бота.',
156
+ graphType: GRAPH_TYPES.EVENT,
157
+ pins: {
158
+ inputs: [],
159
+ outputs: [
160
+ { id: 'exec', name: 'Выполнить', type: 'Exec' },
161
+ ]
162
+ }
163
+ });
164
+
151
165
  registry.registerNodeType({
152
166
  type: 'event:health',
153
167
  label: '❤️ Здоровье/Голод изменилось',
@@ -40,6 +40,23 @@ function registerNodes(registry) {
40
40
  ]
41
41
  }
42
42
  });
43
+
44
+ registry.registerNodeType({
45
+ type: 'logic:not',
46
+ label: '! НЕ',
47
+ category: 'Логика',
48
+ description: 'Инвертирует boolean значение (NOT).',
49
+ graphType: GRAPH_TYPES.ALL,
50
+ evaluator: require('../nodes/logic/not').evaluate,
51
+ pins: {
52
+ inputs: [
53
+ { id: 'value', name: 'Значение', type: 'Boolean', required: false }
54
+ ],
55
+ outputs: [
56
+ { id: 'result', name: 'Результат', type: 'Boolean' }
57
+ ]
58
+ }
59
+ });
43
60
  }
44
61
 
45
62
  module.exports = { registerNodes };
@@ -148,6 +148,40 @@ function registerNodes(registry) {
148
148
  ]
149
149
  }
150
150
  });
151
+
152
+ registry.registerNodeType({
153
+ type: 'string:to_upper',
154
+ label: '⬆️ В верхний регистр',
155
+ category: 'Строки',
156
+ description: 'Преобразует строку в верхний регистр (UPPERCASE).',
157
+ graphType: GRAPH_TYPES.ALL,
158
+ evaluator: require('../nodes/strings/to_upper').evaluate,
159
+ pins: {
160
+ inputs: [
161
+ { id: 'text', name: 'Текст', type: 'String', required: false }
162
+ ],
163
+ outputs: [
164
+ { id: 'result', name: 'Result', type: 'String' }
165
+ ]
166
+ }
167
+ });
168
+
169
+ registry.registerNodeType({
170
+ type: 'string:to_lower',
171
+ label: '⬇️ В нижний регистр',
172
+ category: 'Строки',
173
+ description: 'Преобразует строку в нижний регистр (lowercase).',
174
+ graphType: GRAPH_TYPES.ALL,
175
+ evaluator: require('../nodes/strings/to_lower').evaluate,
176
+ pins: {
177
+ inputs: [
178
+ { id: 'text', name: 'Текст', type: 'String', required: false }
179
+ ],
180
+ outputs: [
181
+ { id: 'result', name: 'Result', type: 'String' }
182
+ ]
183
+ }
184
+ });
151
185
  }
152
186
 
153
187
  module.exports = { registerNodes };
@@ -0,0 +1,25 @@
1
+ const { GRAPH_TYPES } = require('../constants/graphTypes');
2
+
3
+ /**
4
+ * Регистрация нод категории "Преобразование типов"
5
+ */
6
+ function registerNodes(registry) {
7
+ registry.registerNodeType({
8
+ type: 'type:to_string',
9
+ label: '📝 В строку',
10
+ category: 'Типы',
11
+ description: 'Преобразует любое значение в строку (toString).',
12
+ graphType: GRAPH_TYPES.ALL,
13
+ evaluator: require('../nodes/type/to_string').evaluate,
14
+ pins: {
15
+ inputs: [
16
+ { id: 'value', name: 'Значение', type: 'Wildcard', required: false }
17
+ ],
18
+ outputs: [
19
+ { id: 'result', name: 'Result', type: 'String' }
20
+ ]
21
+ }
22
+ });
23
+ }
24
+
25
+ module.exports = { registerNodes };
@@ -24,7 +24,7 @@ async function execute(node, context, helpers) {
24
24
 
25
25
  if (finalPosition) {
26
26
  finalPosition.y += Number(yOffset || 0);
27
- context.bot.lookAt(finalPosition);
27
+ await context.bot.lookAt(finalPosition.x, finalPosition.y, finalPosition.z);
28
28
  }
29
29
  }
30
30