blockmine 1.25.0 → 1.27.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 (164) hide show
  1. package/CHANGELOG.md +46 -3
  2. package/backend/cli.js +1 -1
  3. package/backend/package.json +2 -2
  4. package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
  5. package/backend/prisma/migrations/migration_lock.toml +2 -2
  6. package/backend/prisma/schema.prisma +2 -0
  7. package/backend/src/api/routes/apiKeys.js +8 -0
  8. package/backend/src/api/routes/bots.js +258 -9
  9. package/backend/src/api/routes/eventGraphs.js +151 -1
  10. package/backend/src/api/routes/health.js +38 -0
  11. package/backend/src/api/routes/nodeRegistry.js +63 -0
  12. package/backend/src/api/routes/plugins.js +254 -29
  13. package/backend/src/container.js +11 -8
  14. package/backend/src/core/BotCommandLoader.js +161 -0
  15. package/backend/src/core/BotConnection.js +125 -0
  16. package/backend/src/core/BotEventHandlers.js +234 -0
  17. package/backend/src/core/BotIPCHandler.js +445 -0
  18. package/backend/src/core/BotManager.js +15 -7
  19. package/backend/src/core/BotProcess.js +75 -142
  20. package/backend/src/core/EventGraphManager.js +7 -3
  21. package/backend/src/core/GraphDebugHandler.js +229 -0
  22. package/backend/src/core/GraphDebugIPC.js +117 -0
  23. package/backend/src/core/GraphExecutionEngine.js +545 -978
  24. package/backend/src/core/GraphTraversal.js +80 -0
  25. package/backend/src/core/GraphValidation.js +73 -0
  26. package/backend/src/core/NodeDefinition.js +138 -0
  27. package/backend/src/core/NodeRegistry.js +153 -141
  28. package/backend/src/core/PluginManager.js +272 -31
  29. package/backend/src/core/RewindSignal.js +9 -0
  30. package/backend/src/core/config/ConfigValidator.js +72 -0
  31. package/backend/src/core/config/FeatureFlags.js +52 -0
  32. package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
  33. package/backend/src/core/domain/entities/Bot.js +39 -0
  34. package/backend/src/core/domain/entities/Command.js +41 -0
  35. package/backend/src/core/domain/entities/EventGraph.js +39 -0
  36. package/backend/src/core/domain/entities/Plugin.js +45 -0
  37. package/backend/src/core/domain/entities/User.js +40 -0
  38. package/backend/src/core/domain/services/DependencyResolver.js +168 -0
  39. package/backend/src/core/domain/services/GraphValidator.js +117 -0
  40. package/backend/src/core/domain/services/PermissionChecker.js +34 -0
  41. package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
  42. package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
  43. package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
  44. package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
  45. package/backend/src/core/errors/BaseError.js +29 -0
  46. package/backend/src/core/errors/ErrorHandler.js +81 -0
  47. package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
  48. package/backend/src/core/errors/index.js +68 -0
  49. package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
  50. package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
  51. package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
  52. package/backend/src/core/infrastructure/RateLimiter.js +64 -0
  53. package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
  54. package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
  55. package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
  56. package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
  57. package/backend/src/core/ipc/botApiFactory.js +72 -0
  58. package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
  59. package/backend/src/core/logging/AuditLogger.js +61 -0
  60. package/backend/src/core/logging/StructuredLogger.js +80 -0
  61. package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
  62. package/backend/src/core/logging/index.js +7 -0
  63. package/backend/src/core/metrics/MetricsCollector.js +104 -0
  64. package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
  65. package/backend/src/core/node-registries/actionsNodes.js +191 -0
  66. package/backend/src/core/node-registries/arraysNodes.js +152 -0
  67. package/backend/src/core/node-registries/botNodes.js +48 -0
  68. package/backend/src/core/node-registries/containerNodes.js +141 -0
  69. package/backend/src/core/node-registries/dataNodes.js +284 -0
  70. package/backend/src/core/node-registries/debugNodes.js +23 -0
  71. package/backend/src/core/node-registries/eventsNodes.js +223 -0
  72. package/backend/src/core/node-registries/flowNodes.js +151 -0
  73. package/backend/src/core/node-registries/furnaceNodes.js +123 -0
  74. package/backend/src/core/node-registries/index.js +108 -0
  75. package/backend/src/core/node-registries/inventory.js +102 -106
  76. package/backend/src/core/node-registries/logicNodes.js +54 -0
  77. package/backend/src/core/node-registries/mathNodes.js +38 -0
  78. package/backend/src/core/node-registries/navigationNodes.js +109 -0
  79. package/backend/src/core/node-registries/objectsNodes.js +90 -0
  80. package/backend/src/core/node-registries/stringsNodes.js +165 -0
  81. package/backend/src/core/node-registries/timeNodes.js +105 -0
  82. package/backend/src/core/node-registries/typeNodes.js +22 -0
  83. package/backend/src/core/node-registries/usersNodes.js +126 -0
  84. package/backend/src/core/nodes/arrays/shuffle.js +14 -0
  85. package/backend/src/core/nodes/bot/get_name.js +8 -0
  86. package/backend/src/core/nodes/bot/stop_bot.js +5 -0
  87. package/backend/src/core/nodes/container/open.js +101 -111
  88. package/backend/src/core/nodes/data/store_read.js +26 -0
  89. package/backend/src/core/nodes/data/store_write.js +23 -0
  90. package/backend/src/core/nodes/event/call_event.js +31 -0
  91. package/backend/src/core/nodes/event/custom_event.js +8 -0
  92. package/backend/src/core/nodes/flow/timer.js +35 -0
  93. package/backend/src/core/nodes/inventory/drop.js +73 -65
  94. package/backend/src/core/nodes/inventory/equip.js +54 -45
  95. package/backend/src/core/nodes/inventory/select_slot.js +48 -46
  96. package/backend/src/core/nodes/navigation/follow.js +54 -51
  97. package/backend/src/core/nodes/navigation/go_to.js +41 -53
  98. package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
  99. package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
  100. package/backend/src/core/nodes/navigation/stop.js +17 -26
  101. package/backend/src/core/nodes/users/add_to_group.js +24 -0
  102. package/backend/src/core/nodes/users/check_permission.js +26 -0
  103. package/backend/src/core/nodes/users/remove_from_group.js +24 -0
  104. package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
  105. package/backend/src/core/services/BotLifecycleService.js +41 -632
  106. package/backend/src/core/services/CacheManager.js +83 -23
  107. package/backend/src/core/services/CrashRestartManager.js +42 -0
  108. package/backend/src/core/services/DebugSessionManager.js +114 -12
  109. package/backend/src/core/services/EventGraphService.js +69 -0
  110. package/backend/src/core/services/MinecraftBotManager.js +9 -1
  111. package/backend/src/core/services/PluginManagementService.js +84 -0
  112. package/backend/src/core/services/TestModeContext.js +65 -0
  113. package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
  114. package/backend/src/core/services.js +1 -11
  115. package/backend/src/core/validation/InputValidator.js +167 -0
  116. package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
  117. package/backend/src/real-time/botApi/index.js +1 -1
  118. package/backend/src/real-time/socketHandler.js +26 -0
  119. package/backend/src/server.js +10 -5
  120. package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
  121. package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
  122. package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
  123. package/frontend/dist/index.html +2 -2
  124. package/frontend/dist/locales/en/bots.json +4 -1
  125. package/frontend/dist/locales/en/common.json +7 -1
  126. package/frontend/dist/locales/en/login.json +2 -0
  127. package/frontend/dist/locales/en/management.json +79 -1
  128. package/frontend/dist/locales/en/nodes.json +59 -4
  129. package/frontend/dist/locales/en/plugin-detail.json +24 -4
  130. package/frontend/dist/locales/en/plugins.json +226 -7
  131. package/frontend/dist/locales/en/setup.json +2 -0
  132. package/frontend/dist/locales/en/sidebar.json +171 -3
  133. package/frontend/dist/locales/en/visual-editor.json +230 -31
  134. package/frontend/dist/locales/ru/bots.json +4 -1
  135. package/frontend/dist/locales/ru/login.json +2 -0
  136. package/frontend/dist/locales/ru/management.json +79 -1
  137. package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
  138. package/frontend/dist/locales/ru/nodes.json +105 -51
  139. package/frontend/dist/locales/ru/plugins.json +103 -4
  140. package/frontend/dist/locales/ru/setup.json +2 -0
  141. package/frontend/dist/locales/ru/sidebar.json +171 -3
  142. package/frontend/dist/locales/ru/visual-editor.json +232 -33
  143. package/frontend/package.json +2 -0
  144. package/nul +12 -0
  145. package/package.json +3 -3
  146. package/backend/package-lock.json +0 -6801
  147. package/backend/src/core/node-registries/actions.js +0 -202
  148. package/backend/src/core/node-registries/arrays.js +0 -155
  149. package/backend/src/core/node-registries/bot.js +0 -23
  150. package/backend/src/core/node-registries/container.js +0 -162
  151. package/backend/src/core/node-registries/data.js +0 -290
  152. package/backend/src/core/node-registries/debug.js +0 -26
  153. package/backend/src/core/node-registries/events.js +0 -201
  154. package/backend/src/core/node-registries/flow.js +0 -139
  155. package/backend/src/core/node-registries/furnace.js +0 -143
  156. package/backend/src/core/node-registries/logic.js +0 -62
  157. package/backend/src/core/node-registries/math.js +0 -42
  158. package/backend/src/core/node-registries/navigation.js +0 -111
  159. package/backend/src/core/node-registries/objects.js +0 -98
  160. package/backend/src/core/node-registries/strings.js +0 -187
  161. package/backend/src/core/node-registries/time.js +0 -113
  162. package/backend/src/core/node-registries/type.js +0 -25
  163. package/backend/src/core/node-registries/users.js +0 -79
  164. package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
@@ -0,0 +1,445 @@
1
+ const { MessageTypes } = require('./ipc/ipcMessageTypes');
2
+ const { Vec3 } = require('vec3');
3
+ const { v4: uuidv4 } = require('uuid');
4
+ const { parseArguments } = require('./system/parseArguments');
5
+ const { getRuntimeCommandRegistry } = require('./system/RuntimeCommandRegistry');
6
+ const Transport = require('./system/Transport');
7
+ const CommandContext = require('./system/CommandContext');
8
+ const UserService = require('./UserService');
9
+ const GraphExecutionEngine = require('./GraphExecutionEngine');
10
+ const NodeRegistry = require('./NodeRegistry');
11
+ const Command = require('./system/Command');
12
+
13
+ let viewerRenderDistance = 24;
14
+
15
+ function createSendLog() {
16
+ return process.send
17
+ ? (content) => process.send({ type: MessageTypes.BOT.LOG, content })
18
+ : (content) => console.log(`[ChildProcess Log] ${content}`);
19
+ }
20
+
21
+ function handleIncomingCommand(bot, type, username, message, sendLog) {
22
+ const log = sendLog || createSendLog();
23
+
24
+ if (!message.startsWith(bot.config.prefix || '@')) return;
25
+
26
+ const rawMessage = message.slice((bot.config.prefix || '@').length).trim();
27
+ const commandParts = rawMessage.split(/ +/);
28
+ const commandName = commandParts.shift().toLowerCase();
29
+ const restOfMessage = commandParts.join(' ');
30
+
31
+ let commandInstance = bot.commands.get(commandName) ||
32
+ Array.from(bot.commands.values()).find(cmd => cmd.aliases.includes(commandName));
33
+
34
+ if (!commandInstance) {
35
+ const runtimeRegistry = getRuntimeCommandRegistry();
36
+ commandInstance = runtimeRegistry.get(bot.config.id, commandName);
37
+ }
38
+
39
+ if (!commandInstance) return;
40
+
41
+ try {
42
+ const processedArgs = {};
43
+ const parsedArgs = parseArguments(restOfMessage);
44
+ let currentArgIndex = 0;
45
+
46
+ const argsDef = commandInstance.isVisual && commandInstance.args ? commandInstance.args : (commandInstance.args || []);
47
+ for (const argDef of argsDef) {
48
+ if (argDef.type === 'greedy_string') {
49
+ if (currentArgIndex < parsedArgs.length) {
50
+ processedArgs[argDef.name] = parsedArgs.slice(currentArgIndex).join(' ');
51
+ currentArgIndex = parsedArgs.length;
52
+ }
53
+ } else if (currentArgIndex < parsedArgs.length) {
54
+ let value = parsedArgs[currentArgIndex];
55
+ if (argDef.type === 'number') {
56
+ const numValue = parseFloat(value);
57
+ if (isNaN(numValue)) {
58
+ bot.api.sendMessage(type, `Ошибка: Аргумент "${argDef.description}" должен быть числом.`, username);
59
+ return;
60
+ }
61
+ value = numValue;
62
+ }
63
+ processedArgs[argDef.name] = value;
64
+ currentArgIndex++;
65
+ }
66
+
67
+ if (processedArgs[argDef.name] === undefined) {
68
+ if (argDef.default !== undefined) {
69
+ processedArgs[argDef.name] = argDef.default;
70
+ }
71
+ }
72
+ }
73
+
74
+ if (process.send) {
75
+ process.send({
76
+ type: MessageTypes.COMMAND.VALIDATE_AND_RUN,
77
+ commandName: commandInstance.name,
78
+ username,
79
+ args: processedArgs,
80
+ typeChat: type,
81
+ commandArgs: argsDef
82
+ });
83
+ }
84
+ } catch (e) {
85
+ log(`[BotProcess] Ошибка парсинга аргументов: ${e.message}`);
86
+ }
87
+ }
88
+
89
+ function createBotIPCHandler(bot, prisma, pluginUiState, pendingRequests, sendLog, sendEvent, serializeEntity) {
90
+ const handlers = {};
91
+
92
+ handlers[MessageTypes.BOT.START] = async (message) => {
93
+ const config = message.config;
94
+ sendLog(`[System] Получена команда на запуск бота ${config.username}...`);
95
+ return { config };
96
+ };
97
+
98
+ handlers[MessageTypes.SYSTEM.GET_PLAYER_LIST] = (message) => {
99
+ const playerList = bot ? Object.keys(bot.players) : [];
100
+ return { requestId: message.requestId, players: playerList };
101
+ };
102
+
103
+ handlers[MessageTypes.SYSTEM.GET_NEARBY_ENTITIES] = (message) => {
104
+ const entities = [];
105
+ if (bot && bot.entities) {
106
+ const centerPos = message.payload?.position || bot.entity?.position;
107
+ const radius = message.payload?.radius || 32;
108
+
109
+ if (centerPos) {
110
+ for (const entity of Object.values(bot.entities)) {
111
+ if (entity && entity.position && entity.isValid) {
112
+ const dx = entity.position.x - centerPos.x;
113
+ const dy = entity.position.y - centerPos.y;
114
+ const dz = entity.position.z - centerPos.z;
115
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
116
+
117
+ if (distance <= radius) {
118
+ entities.push(serializeEntity(entity));
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ return { requestId: message.requestId, entities };
125
+ };
126
+
127
+ handlers[MessageTypes.VIEWER.GET_STATE] = (message) => {
128
+ if (!bot || !process.send) return null;
129
+
130
+ let blocks = undefined;
131
+ if (message.includeBlocks && bot.entity?.position) {
132
+ blocks = [];
133
+ const pos = bot.entity.position;
134
+
135
+ for (let x = Math.floor(pos.x - viewerRenderDistance); x <= Math.floor(pos.x + viewerRenderDistance); x++) {
136
+ for (let y = Math.floor(pos.y - viewerRenderDistance / 2); y <= Math.floor(pos.y + viewerRenderDistance); y++) {
137
+ for (let z = Math.floor(pos.z - viewerRenderDistance); z <= Math.floor(pos.z + viewerRenderDistance); z++) {
138
+ const block = bot.blockAt(new Vec3(x, y, z));
139
+ if (block && block.type !== 0) {
140
+ blocks.push({ x, y, z, type: block.type, name: block.name });
141
+ }
142
+ }
143
+ }
144
+ }
145
+ }
146
+
147
+ return {
148
+ requestId: message.requestId,
149
+ state: {
150
+ status: bot._client ? 'online' : 'offline',
151
+ health: bot.health || 20,
152
+ food: bot.food || 20,
153
+ position: bot.entity?.position ? { x: bot.entity.position.x, y: bot.entity.position.y, z: bot.entity.position.z } : null,
154
+ yaw: bot.entity?.yaw || 0,
155
+ pitch: bot.entity?.pitch || 0,
156
+ gameMode: bot.game?.gameMode,
157
+ dimension: bot.game?.dimension,
158
+ blocks,
159
+ inventory: bot.inventory ? bot.inventory.items().map(item => ({ name: item.name, displayName: item.displayName, count: item.count, slot: item.slot })) : [],
160
+ nearbyPlayers: bot.entities ? Object.values(bot.entities).filter(e => e.type === 'player' && e.username !== bot.username).map(e => ({ username: e.username, position: { x: e.position.x, y: e.position.y, z: e.position.z }, yaw: e.yaw || 0, pitch: e.pitch || 0, distance: bot.entity ? bot.entity.position.distanceTo(e.position) : 0 })) : [],
161
+ nearbyMobs: bot.entities ? Object.values(bot.entities).filter(e => e.type === 'mob').map(e => ({ name: e.name || e.displayName, mobType: e.mobType, position: { x: e.position.x, y: e.position.y, z: e.position.z }, distance: bot.entity ? bot.entity.position.distanceTo(e.position) : 0 })) : []
162
+ }
163
+ };
164
+ };
165
+
166
+ handlers[MessageTypes.VIEWER.CONTROL] = (message) => {
167
+ if (!bot) return null;
168
+ const { command } = message;
169
+
170
+ switch (command.type) {
171
+ case 'move':
172
+ bot.setControlState(command.direction, command.active);
173
+ break;
174
+ case 'look':
175
+ if (command.yaw !== undefined) bot.entity.yaw = command.yaw;
176
+ if (command.pitch !== undefined) bot.entity.pitch = command.pitch;
177
+ break;
178
+ case 'chat':
179
+ bot.chat(command.message);
180
+ break;
181
+ case 'dig':
182
+ if (command.position) {
183
+ const block = bot.blockAt(new Vec3(command.position.x, command.position.y, command.position.z));
184
+ if (block) bot.dig(block).catch(err => sendLog(`[Viewer] Dig error: ${err.message}`));
185
+ }
186
+ break;
187
+ case 'hotbar_slot':
188
+ if (command.slot !== undefined && command.slot >= 0 && command.slot <= 8) {
189
+ bot.setQuickBarSlot(command.slot);
190
+ sendLog(`[Viewer] Hotbar slot changed to ${command.slot}`);
191
+ }
192
+ break;
193
+ case 'place':
194
+ if (command.position && command.blockType) {
195
+ const referenceBlock = bot.blockAt(new Vec3(command.position.x, command.position.y, command.position.z));
196
+ if (referenceBlock) {
197
+ const itemToPlace = bot.inventory.items().find(item => item.name === command.blockType);
198
+ if (itemToPlace) {
199
+ bot.equip(itemToPlace, 'hand').then(() => bot.placeBlock(referenceBlock, new Vec3(0, 1, 0))).catch(err => sendLog(`[Viewer] Place error: ${err.message}`));
200
+ }
201
+ }
202
+ }
203
+ break;
204
+ case 'set_render_distance':
205
+ if (command.distance && command.distance >= 8 && command.distance <= 64) {
206
+ viewerRenderDistance = command.distance;
207
+ sendLog(`[Viewer] Render distance set to ${viewerRenderDistance}`);
208
+ }
209
+ break;
210
+ }
211
+ return null;
212
+ };
213
+
214
+ handlers[MessageTypes.GRAPH.EXECUTE_EVENT_GRAPH] = async (message) => {
215
+ const { graph, eventType, eventArgs } = message;
216
+ if (!graph || !graph.nodes || graph.nodes.length === 0) return null;
217
+
218
+ const config = bot?.config;
219
+ if (!config) {
220
+ sendLog('[ERROR] Bot config not available for event graph execution');
221
+ return null;
222
+ }
223
+
224
+ const players = bot ? Object.keys(bot.players) : [];
225
+
226
+ const context = {
227
+ bot,
228
+ players,
229
+ botState: { health: bot?.health, food: bot?.food, position: bot?.entity?.position },
230
+ botEntity: bot && bot.entity ? serializeEntity(bot.entity) : null,
231
+ eventArgs,
232
+ botId: config.id,
233
+ graphId: graph.id,
234
+ eventType
235
+ };
236
+
237
+ const engine = new GraphExecutionEngine(NodeRegistry, bot);
238
+ await engine.execute(graph, context, eventType);
239
+
240
+ return null;
241
+ };
242
+
243
+ handlers[MessageTypes.USER.ACTION_RESPONSE] = (message) => {
244
+ if (pendingRequests.has(message.requestId)) {
245
+ const { resolve, reject } = pendingRequests.get(message.requestId);
246
+ if (message.error) reject(new Error(message.error));
247
+ else resolve(message.payload);
248
+ pendingRequests.delete(message.requestId);
249
+ }
250
+ return null;
251
+ };
252
+
253
+ handlers[MessageTypes.USER.CREDENTIALS_OPERATION_RESPONSE] = (message) => {
254
+ if (pendingRequests.has(message.requestId)) {
255
+ const { resolve, reject } = pendingRequests.get(message.requestId);
256
+ if (message.error) reject(new Error(message.error));
257
+ else resolve(message.payload);
258
+ pendingRequests.delete(message.requestId);
259
+ }
260
+ return null;
261
+ };
262
+
263
+ handlers[MessageTypes.COMMAND.EXECUTE_HANDLER] = (message) => {
264
+ const { commandName, username, args, typeChat } = message;
265
+ const commandInstance = bot.commands.get(commandName);
266
+ if (commandInstance) {
267
+ (async () => {
268
+ try {
269
+ const user = await UserService.getUser(username, bot.config.id, bot.config);
270
+ const handlerParamCount = commandInstance.handler.length;
271
+
272
+ if (handlerParamCount === 1) {
273
+ const transport = new Transport(typeChat, bot);
274
+ const context = new CommandContext(bot, user, args, transport);
275
+ await commandInstance.handler(context);
276
+ } else {
277
+ await commandInstance.handler(bot, typeChat, user, args);
278
+ }
279
+ } catch (e) {
280
+ sendLog(`[Handler Error] ${commandName}: ${e.message}`);
281
+ }
282
+ })();
283
+ }
284
+ return null;
285
+ };
286
+
287
+ handlers[MessageTypes.COMMAND.EXECUTE_COMMAND_REQUEST] = (message) => {
288
+ const { requestId, payload } = message;
289
+ const { commandName, args, username, typeChat } = payload;
290
+
291
+ (async () => {
292
+ try {
293
+ const commandInstance = bot.commands.get(commandName);
294
+ if (!commandInstance) throw new Error(`Command '${commandName}' not found.`);
295
+
296
+ const user = await UserService.getUser(username, bot.config.id, bot.config);
297
+ const handlerParamCount = commandInstance.handler.length;
298
+
299
+ let result;
300
+ if (handlerParamCount === 1) {
301
+ const transport = new Transport(typeChat, bot);
302
+ const context = new CommandContext(bot, user, args, transport);
303
+
304
+ if (typeChat === 'websocket') {
305
+ result = await commandInstance.handler(context);
306
+ if (process.send) process.send({ type: MessageTypes.GRAPH.EXECUTE_COMMAND_RESPONSE, requestId, result });
307
+ } else {
308
+ commandInstance.handler(context).catch(e => sendLog(`[Handler Error] ${commandName}: ${e.message}`));
309
+ }
310
+ } else {
311
+ if (typeChat === 'websocket') {
312
+ const originalSendMessage = bot.sendMessage;
313
+ let resultFromSendMessage = null;
314
+ let sendMessageCalled = false;
315
+
316
+ bot.sendMessage = (type, msg, user) => {
317
+ if (type === 'websocket') {
318
+ resultFromSendMessage = msg;
319
+ sendMessageCalled = true;
320
+ } else {
321
+ originalSendMessage.call(bot, type, msg, user);
322
+ }
323
+ };
324
+
325
+ try {
326
+ const returnValue = await commandInstance.handler(bot, typeChat, user, args);
327
+ result = sendMessageCalled ? resultFromSendMessage : returnValue;
328
+ if (process.send) process.send({ type: MessageTypes.GRAPH.EXECUTE_COMMAND_RESPONSE, requestId, result });
329
+ } finally {
330
+ bot.sendMessage = originalSendMessage;
331
+ }
332
+ } else {
333
+ commandInstance.handler(bot, typeChat, user, args).catch(e => sendLog(`[Handler Error] ${commandName}: ${e.message}`));
334
+ }
335
+ }
336
+ } catch (error) {
337
+ if (process.send) process.send({ type: MessageTypes.GRAPH.EXECUTE_COMMAND_RESPONSE, requestId, error: error.message });
338
+ }
339
+ })();
340
+ return null;
341
+ };
342
+
343
+ handlers[MessageTypes.COMMAND.HANDLE_PERMISSION_ERROR] = (message) => {
344
+ const { commandName, username, typeChat } = message;
345
+ const commandInstance = bot.commands.get(commandName);
346
+ if (commandInstance) {
347
+ if (commandInstance.onInsufficientPermissions !== Command.prototype.onInsufficientPermissions) {
348
+ commandInstance.onInsufficientPermissions(bot, typeChat, { username });
349
+ } else {
350
+ bot.api.sendMessage(typeChat, `У вас нет прав для выполнения команды ${commandName}.`, username);
351
+ }
352
+ }
353
+ return null;
354
+ };
355
+
356
+ handlers[MessageTypes.COMMAND.HANDLE_WRONG_CHAT] = (message) => {
357
+ const { commandName, username, typeChat } = message;
358
+ const commandInstance = bot.commands.get(commandName);
359
+ if (commandInstance) {
360
+ if (commandInstance.onWrongChatType !== Command.prototype.onWrongChatType) {
361
+ commandInstance.onWrongChatType(bot, typeChat, { username });
362
+ } else {
363
+ bot.api.sendMessage('private', `Команду ${commandName} нельзя использовать в этом типе чата - ${typeChat}.`, username);
364
+ }
365
+ }
366
+ return null;
367
+ };
368
+
369
+ handlers[MessageTypes.COMMAND.HANDLE_COOLDOWN] = (message) => {
370
+ const { commandName, username, typeChat, timeLeft } = message;
371
+ const commandInstance = bot.commands.get(commandName);
372
+ if (commandInstance) {
373
+ if (commandInstance.onCooldown !== Command.prototype.onCooldown) {
374
+ commandInstance.onCooldown(bot, typeChat, { username }, timeLeft);
375
+ } else {
376
+ bot.api.sendMessage(typeChat, `Команду ${commandName} можно будет использовать через ${timeLeft} сек.`, username);
377
+ }
378
+ }
379
+ return null;
380
+ };
381
+
382
+ handlers[MessageTypes.COMMAND.HANDLE_BLACKLIST] = (message) => {
383
+ const { commandName, username, typeChat } = message;
384
+ const commandInstance = bot.commands.get(commandName);
385
+ if (commandInstance && commandInstance.onBlacklisted !== Command.prototype.onBlacklisted) {
386
+ commandInstance.onBlacklisted(bot, typeChat, { username });
387
+ }
388
+ return null;
389
+ };
390
+
391
+ handlers[MessageTypes.CHAT.SEND_MESSAGE] = (message) => {
392
+ const { typeChat, message: msg, username } = message;
393
+ if (bot && bot.api) {
394
+ bot.api.sendMessage(typeChat, msg, username);
395
+ }
396
+ return null;
397
+ };
398
+
399
+ handlers[MessageTypes.PLUGINS.RELOAD] = async () => {
400
+ sendLog('[System] Получена команда на перезагрузку плагинов...');
401
+ return { shouldReload: true };
402
+ };
403
+
404
+ handlers[MessageTypes.SERVER.COMMAND] = (message) => {
405
+ if (bot && bot.messageQueue && message.payload?.command) {
406
+ bot.messageQueue.enqueue('command', message.payload.command);
407
+ }
408
+ return null;
409
+ };
410
+
411
+ handlers[MessageTypes.USER.INVALIDATE_USER_CACHE] = (message) => {
412
+ if (message.username && bot && bot.config) {
413
+ UserService.clearCache(message.username, bot.config.id);
414
+ }
415
+ return null;
416
+ };
417
+
418
+ handlers[MessageTypes.USER.INVALIDATE_ALL_USER_CACHE] = () => {
419
+ if (bot && bot.config) {
420
+ for (const [cacheKey] of UserService.cache.entries()) {
421
+ if (cacheKey.startsWith(`${bot.config.id}:`)) {
422
+ UserService.cache.delete(cacheKey);
423
+ }
424
+ }
425
+ sendLog(`[BotProcess] Кэш пользователей очищен для бота ${bot.config.id}`);
426
+ }
427
+ return null;
428
+ };
429
+
430
+ return handlers;
431
+ }
432
+
433
+ function sendLog(content) {
434
+ if (process.send) {
435
+ process.send({ type: MessageTypes.BOT.LOG, content });
436
+ } else {
437
+ console.log(`[ChildProcess Log] ${content}`);
438
+ }
439
+ }
440
+
441
+ module.exports = {
442
+ handleIncomingCommand,
443
+ createBotIPCHandler,
444
+ sendLog
445
+ };
@@ -22,11 +22,10 @@ class BotManager {
22
22
  this.eventGraphManager = eventGraphManager;
23
23
  this.logger = logger;
24
24
 
25
- // Геттеры для обратной совместимости
26
25
  this.bots = this.processManager.getAllProcesses();
27
26
  this.nodeRegistry = require('./NodeRegistry');
28
27
 
29
- // Массив для хранения ссылок на интервалы
28
+ this._cleanupRegistry = new Map();
30
29
  this.intervals = [];
31
30
 
32
31
  this._startBackgroundTasks();
@@ -36,18 +35,27 @@ class BotManager {
36
35
  this.resourceMonitor.startMonitoring(5000);
37
36
  this.telemetry.startHeartbeat(5 * 60 * 1000);
38
37
 
39
- this.intervals.push(setInterval(() => this.updateAllResourceUsage(), 5000));
40
- this.intervals.push(setInterval(() => this.syncBotStatuses(), 10000));
38
+ const usageInterval = setInterval(() => this.updateAllResourceUsage(), 5000);
39
+ const syncInterval = setInterval(() => this.syncBotStatuses(), 10000);
40
+
41
+ this.intervals.push(usageInterval, syncInterval);
42
+ this._cleanupRegistry.set('usageInterval', () => clearInterval(usageInterval));
43
+ this._cleanupRegistry.set('syncInterval', () => clearInterval(syncInterval));
41
44
  }
42
45
 
43
- /**
44
- * Очистка интервалов при завершении работы
45
- */
46
46
  cleanup() {
47
+ for (const fn of this._cleanupRegistry.values()) {
48
+ try { fn(); } catch {}
49
+ }
50
+ this._cleanupRegistry.clear();
47
51
  this.intervals.forEach(interval => clearInterval(interval));
48
52
  this.intervals = [];
49
53
  }
50
54
 
55
+ registerCleanup(key, fn) {
56
+ this._cleanupRegistry.set(key, fn);
57
+ }
58
+
51
59
  initialize() {
52
60
  if (!this.lifecycleService.eventGraphManager) {
53
61
  this.lifecycleService.eventGraphManager = this.eventGraphManager;