blockmine 1.23.0 → 1.23.2

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 (29) 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 +1 -1
  6. package/.claude/settings.local.json +3 -1
  7. package/.claude/skills/frontend-design/SKILL.md +42 -0
  8. package/CHANGELOG.md +31 -16
  9. package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -22
  10. package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -21
  11. package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -45
  12. package/backend/prisma/migrations/migration_lock.toml +2 -2
  13. package/backend/src/api/routes/auth.js +669 -669
  14. package/backend/src/api/routes/bots.js +2451 -2451
  15. package/backend/src/api/routes/panel.js +66 -66
  16. package/backend/src/api/routes/panelApiKeys.js +179 -179
  17. package/backend/src/api/routes/plugins.js +376 -376
  18. package/backend/src/api/routes/system.js +174 -174
  19. package/backend/src/core/EventGraphManager.js +194 -194
  20. package/backend/src/core/GraphExecutionEngine.js +28 -1
  21. package/backend/src/core/node-registries/actions.js +2 -2
  22. package/backend/src/core/nodes/actions/http_request.js +23 -4
  23. package/backend/src/core/nodes/actions/send_message.js +2 -12
  24. package/backend/src/core/nodes/data/string_literal.js +2 -13
  25. package/backend/src/core/services/BotLifecycleService.js +835 -835
  26. package/frontend/dist/assets/{index-B1serztM.js → index-DqzDkFsP.js} +185 -185
  27. package/frontend/dist/index.html +1 -1
  28. package/package.json +2 -1
  29. package/CLAUDE.md +0 -284
@@ -1,195 +1,195 @@
1
- const prismaService = require('./PrismaService');
2
- const { safeJsonParse } = require('./utils/jsonParser');
3
- const { parseVariables } = require('./utils/variableParser');
4
- const validationService = require('./services/ValidationService');
5
- const botHistoryStore = require('./BotHistoryStore');
6
-
7
- const prisma = prismaService.getClient();
8
-
9
- class EventGraphManager {
10
- constructor(botManager = null) {
11
- this.botManager = botManager;
12
- this.activeGraphs = new Map();
13
- this.graphStates = new Map();
14
- }
15
-
16
- setBotManager(botManager) {
17
- this.botManager = botManager;
18
- }
19
-
20
- async loadGraphsForBot(botId) {
21
- console.log(`[EventGraphs] Загрузка графов для бота ${botId}...`);
22
- const botGraphs = await prisma.eventGraph.findMany({
23
- where: { botId, isEnabled: true },
24
- include: { triggers: true },
25
- });
26
-
27
- const graphsByEvent = new Map();
28
- for (const graph of botGraphs) {
29
- if (!graph.triggers || graph.triggers.length === 0 || !graph.graphJson) continue;
30
-
31
- try {
32
- const parsedGraph = validationService.parseGraph(graph.graphJson, `EventGraph ID ${graph.id}`);
33
- if (!validationService.hasValidBasicStructure(parsedGraph)) continue;
34
-
35
- const initialState = {};
36
- if (graph.variables) {
37
- const parsedVars = safeJsonParse(graph.variables, [], `EventGraph ID ${graph.id} variables`);
38
- Object.assign(initialState, parseVariables(parsedVars, `EventGraph ID ${graph.id}`));
39
- }
40
- this.graphStates.set(`${botId}-${graph.id}`, initialState);
41
-
42
- for (const trigger of graph.triggers) {
43
- if (!graphsByEvent.has(trigger.eventType)) {
44
- graphsByEvent.set(trigger.eventType, []);
45
- }
46
- graphsByEvent.get(trigger.eventType).push({
47
- id: graph.id,
48
- name: graph.name,
49
- nodes: parsedGraph.nodes,
50
- connections: parsedGraph.connections,
51
- variables: parsedGraph.variables || [],
52
- });
53
- }
54
- } catch (e) {
55
- console.error(`[EventGraphs] Ошибка парсинга JSON для графа ID ${graph.id}:`, e);
56
- }
57
- }
58
-
59
- this.activeGraphs.set(botId, graphsByEvent);
60
- console.log(`[EventGraphs] Загружено ${botGraphs.length} графов, сгруппировано по ${graphsByEvent.size} событиям для бота ${botId}.`);
61
- }
62
-
63
- unloadGraphsForBot(botId) {
64
- this.activeGraphs.delete(botId);
65
- for (const key of this.graphStates.keys()) {
66
- if (key.startsWith(`${botId}-`)) {
67
- this.graphStates.delete(key);
68
- }
69
- }
70
- console.log(`[EventGraphs] Графы и их состояния для бота ${botId} выгружены.`);
71
- }
72
-
73
- async handleEvent(botId, eventType, args) {
74
- this.broadcastEventToApi(botId, eventType, args);
75
-
76
- const graphsForBot = this.activeGraphs.get(botId);
77
- if (!graphsForBot) return;
78
-
79
- const graphsToRun = graphsForBot.get(eventType);
80
- if (!graphsToRun || graphsToRun.length === 0) return;
81
-
82
- for (const graph of graphsToRun) {
83
- try {
84
- await this.executeGraphInChildProcess(botId, eventType, graph, args);
85
- } catch (error) {
86
- console.error(`[EventGraphManager] Error sending event to child process for '${eventType}':`, error);
87
- this.botManager.appendLog(botId, `[ERROR] Error in event graph: ${error.message}`);
88
- }
89
- }
90
- }
91
-
92
- /**
93
- * Отправляет граф в child process для выполнения
94
- */
95
- async executeGraphInChildProcess(botId, eventType, graph, eventArgs) {
96
- if (!graph || !graph.nodes || graph.nodes.length === 0) return;
97
-
98
- const childProcess = this.botManager.getChildProcess(botId);
99
- if (!childProcess || !childProcess.send) {
100
- console.error(`[EventGraphManager] No child process found for bot ${botId}`);
101
- return;
102
- }
103
-
104
- childProcess.send({
105
- type: 'execute_event_graph',
106
- botId: botId,
107
- graph: graph,
108
- eventType: eventType,
109
- eventArgs: eventArgs
110
- });
111
- }
112
-
113
- /**
114
- * Отправляет события в WebSocket API
115
- */
116
- broadcastEventToApi(botId, eventType, args) {
117
- try {
118
- // Динамический импорт для избежания циклической зависимости
119
- const { getIOSafe } = require('../real-time/socketHandler');
120
- const { broadcastToApiClients } = require('../real-time/botApi');
121
-
122
- const io = getIOSafe();
123
- if (!io) return;
124
-
125
- switch (eventType) {
126
- case 'chat':
127
- case 'private':
128
- case 'global':
129
- case 'clan':
130
- const chatData = {
131
- type: eventType,
132
- username: args.username,
133
- message: args.message,
134
- raw_message: args.rawText || args.raw_message,
135
- };
136
-
137
- broadcastToApiClients(io, botId, 'chat:message', chatData);
138
-
139
- botHistoryStore.addChatMessage(botId, {
140
- type: eventType,
141
- username: args.username,
142
- message: args.message
143
- });
144
- break;
145
-
146
- case 'playerJoined':
147
- broadcastToApiClients(io, botId, 'player:join', {
148
- username: args.user?.username || args.username,
149
- });
150
- break;
151
-
152
- case 'playerLeft':
153
- broadcastToApiClients(io, botId, 'player:leave', {
154
- username: args.user?.username || args.username,
155
- });
156
- break;
157
-
158
- case 'health':
159
- broadcastToApiClients(io, botId, 'bot:health', {
160
- health: args.health,
161
- food: args.food,
162
- });
163
- break;
164
-
165
- case 'death':
166
- broadcastToApiClients(io, botId, 'bot:death', {});
167
- break;
168
- }
169
- } catch (error) {
170
- }
171
- }
172
-
173
- /**
174
- * Отправляет кастомное событие от плагина в WebSocket API
175
- */
176
- emitCustomApiEvent(botId, eventName, payload = {}) {
177
- try {
178
- const { getIOSafe } = require('../real-time/socketHandler');
179
- const { broadcastToApiClients } = require('../real-time/botApi');
180
-
181
- const io = getIOSafe();
182
- if (!io) return;
183
-
184
- broadcastToApiClients(io, botId, 'plugin:custom_event', {
185
- eventName,
186
- payload,
187
- timestamp: new Date().toISOString(),
188
- });
189
- } catch (error) {
190
- // Игнорируем другие ошибки
191
- }
192
- }
193
- }
194
-
1
+ const prismaService = require('./PrismaService');
2
+ const { safeJsonParse } = require('./utils/jsonParser');
3
+ const { parseVariables } = require('./utils/variableParser');
4
+ const validationService = require('./services/ValidationService');
5
+ const botHistoryStore = require('./BotHistoryStore');
6
+
7
+ const prisma = prismaService.getClient();
8
+
9
+ class EventGraphManager {
10
+ constructor(botManager = null) {
11
+ this.botManager = botManager;
12
+ this.activeGraphs = new Map();
13
+ this.graphStates = new Map();
14
+ }
15
+
16
+ setBotManager(botManager) {
17
+ this.botManager = botManager;
18
+ }
19
+
20
+ async loadGraphsForBot(botId) {
21
+ console.log(`[EventGraphs] Загрузка графов для бота ${botId}...`);
22
+ const botGraphs = await prisma.eventGraph.findMany({
23
+ where: { botId, isEnabled: true },
24
+ include: { triggers: true },
25
+ });
26
+
27
+ const graphsByEvent = new Map();
28
+ for (const graph of botGraphs) {
29
+ if (!graph.triggers || graph.triggers.length === 0 || !graph.graphJson) continue;
30
+
31
+ try {
32
+ const parsedGraph = validationService.parseGraph(graph.graphJson, `EventGraph ID ${graph.id}`);
33
+ if (!validationService.hasValidBasicStructure(parsedGraph)) continue;
34
+
35
+ const initialState = {};
36
+ if (graph.variables) {
37
+ const parsedVars = safeJsonParse(graph.variables, [], `EventGraph ID ${graph.id} variables`);
38
+ Object.assign(initialState, parseVariables(parsedVars, `EventGraph ID ${graph.id}`));
39
+ }
40
+ this.graphStates.set(`${botId}-${graph.id}`, initialState);
41
+
42
+ for (const trigger of graph.triggers) {
43
+ if (!graphsByEvent.has(trigger.eventType)) {
44
+ graphsByEvent.set(trigger.eventType, []);
45
+ }
46
+ graphsByEvent.get(trigger.eventType).push({
47
+ id: graph.id,
48
+ name: graph.name,
49
+ nodes: parsedGraph.nodes,
50
+ connections: parsedGraph.connections,
51
+ variables: parsedGraph.variables || [],
52
+ });
53
+ }
54
+ } catch (e) {
55
+ console.error(`[EventGraphs] Ошибка парсинга JSON для графа ID ${graph.id}:`, e);
56
+ }
57
+ }
58
+
59
+ this.activeGraphs.set(botId, graphsByEvent);
60
+ console.log(`[EventGraphs] Загружено ${botGraphs.length} графов, сгруппировано по ${graphsByEvent.size} событиям для бота ${botId}.`);
61
+ }
62
+
63
+ unloadGraphsForBot(botId) {
64
+ this.activeGraphs.delete(botId);
65
+ for (const key of this.graphStates.keys()) {
66
+ if (key.startsWith(`${botId}-`)) {
67
+ this.graphStates.delete(key);
68
+ }
69
+ }
70
+ console.log(`[EventGraphs] Графы и их состояния для бота ${botId} выгружены.`);
71
+ }
72
+
73
+ async handleEvent(botId, eventType, args) {
74
+ this.broadcastEventToApi(botId, eventType, args);
75
+
76
+ const graphsForBot = this.activeGraphs.get(botId);
77
+ if (!graphsForBot) return;
78
+
79
+ const graphsToRun = graphsForBot.get(eventType);
80
+ if (!graphsToRun || graphsToRun.length === 0) return;
81
+
82
+ for (const graph of graphsToRun) {
83
+ try {
84
+ await this.executeGraphInChildProcess(botId, eventType, graph, args);
85
+ } catch (error) {
86
+ console.error(`[EventGraphManager] Error sending event to child process for '${eventType}':`, error);
87
+ this.botManager.appendLog(botId, `[ERROR] Error in event graph: ${error.message}`);
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Отправляет граф в child process для выполнения
94
+ */
95
+ async executeGraphInChildProcess(botId, eventType, graph, eventArgs) {
96
+ if (!graph || !graph.nodes || graph.nodes.length === 0) return;
97
+
98
+ const childProcess = this.botManager.getChildProcess(botId);
99
+ if (!childProcess || !childProcess.send) {
100
+ console.error(`[EventGraphManager] No child process found for bot ${botId}`);
101
+ return;
102
+ }
103
+
104
+ childProcess.send({
105
+ type: 'execute_event_graph',
106
+ botId: botId,
107
+ graph: graph,
108
+ eventType: eventType,
109
+ eventArgs: eventArgs
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Отправляет события в WebSocket API
115
+ */
116
+ broadcastEventToApi(botId, eventType, args) {
117
+ try {
118
+ // Динамический импорт для избежания циклической зависимости
119
+ const { getIOSafe } = require('../real-time/socketHandler');
120
+ const { broadcastToApiClients } = require('../real-time/botApi');
121
+
122
+ const io = getIOSafe();
123
+ if (!io) return;
124
+
125
+ switch (eventType) {
126
+ case 'chat':
127
+ case 'private':
128
+ case 'global':
129
+ case 'clan':
130
+ const chatData = {
131
+ type: eventType,
132
+ username: args.username,
133
+ message: args.message,
134
+ raw_message: args.rawText || args.raw_message,
135
+ };
136
+
137
+ broadcastToApiClients(io, botId, 'chat:message', chatData);
138
+
139
+ botHistoryStore.addChatMessage(botId, {
140
+ type: eventType,
141
+ username: args.username,
142
+ message: args.message
143
+ });
144
+ break;
145
+
146
+ case 'playerJoined':
147
+ broadcastToApiClients(io, botId, 'player:join', {
148
+ username: args.user?.username || args.username,
149
+ });
150
+ break;
151
+
152
+ case 'playerLeft':
153
+ broadcastToApiClients(io, botId, 'player:leave', {
154
+ username: args.user?.username || args.username,
155
+ });
156
+ break;
157
+
158
+ case 'health':
159
+ broadcastToApiClients(io, botId, 'bot:health', {
160
+ health: args.health,
161
+ food: args.food,
162
+ });
163
+ break;
164
+
165
+ case 'death':
166
+ broadcastToApiClients(io, botId, 'bot:death', {});
167
+ break;
168
+ }
169
+ } catch (error) {
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Отправляет кастомное событие от плагина в WebSocket API
175
+ */
176
+ emitCustomApiEvent(botId, eventName, payload = {}) {
177
+ try {
178
+ const { getIOSafe } = require('../real-time/socketHandler');
179
+ const { broadcastToApiClients } = require('../real-time/botApi');
180
+
181
+ const io = getIOSafe();
182
+ if (!io) return;
183
+
184
+ broadcastToApiClients(io, botId, 'plugin:custom_event', {
185
+ eventName,
186
+ payload,
187
+ timestamp: new Date().toISOString(),
188
+ });
189
+ } catch (error) {
190
+ // Игнорируем другие ошибки
191
+ }
192
+ }
193
+ }
194
+
195
195
  module.exports = EventGraphManager;
@@ -462,7 +462,34 @@ class GraphExecutionEngine {
462
462
  const sourceNode = this.activeGraph.nodes.find(n => n.id === connection.sourceNodeId);
463
463
  return await this.evaluateOutputPin(sourceNode, connection.sourcePinId, defaultValue);
464
464
  }
465
- return node.data && node.data[pinId] !== undefined ? node.data[pinId] : defaultValue;
465
+
466
+ let value = node.data && node.data[pinId] !== undefined ? node.data[pinId] : defaultValue;
467
+
468
+ // Автоматически заменяем переменные {varName} в строковых значениях
469
+ if (typeof value === 'string' && value.includes('{')) {
470
+ value = await this._replaceVariablesInString(value, node);
471
+ }
472
+
473
+ return value;
474
+ }
475
+
476
+ /**
477
+ * Заменяет переменные {varName} на значения из пинов
478
+ * @private
479
+ */
480
+ async _replaceVariablesInString(text, node) {
481
+ const variablePattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
482
+ const matches = [...text.matchAll(variablePattern)];
483
+
484
+ let result = text;
485
+
486
+ for (const match of matches) {
487
+ const varName = match[1];
488
+ const varValue = await this.resolvePinValue(node, `var_${varName}`, '');
489
+ result = result.replace(match[0], String(varValue));
490
+ }
491
+
492
+ return result;
466
493
  }
467
494
 
468
495
  async evaluateOutputPin(node, pinId, defaultValue = null) {
@@ -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
  ],
@@ -9,16 +9,35 @@
9
9
  async function execute(node, context, helpers) {
10
10
  const { resolvePinValue, traverse, memo } = helpers;
11
11
 
12
- const url = await resolvePinValue(node, 'url', '');
12
+ let url = await resolvePinValue(node, 'url', '');
13
13
  const method = await resolvePinValue(node, 'method', node.data?.method || 'GET');
14
- const headersStr = await resolvePinValue(node, 'headers', '');
14
+ const headersInput = await resolvePinValue(node, 'headers', null);
15
+ const queryParamsInput = await resolvePinValue(node, 'queryParams', null);
15
16
  const body = await resolvePinValue(node, 'body', '');
16
17
  const timeout = await resolvePinValue(node, 'timeout', 5000);
17
18
 
19
+ if (queryParamsInput) {
20
+ try {
21
+ const params = typeof queryParamsInput === 'string'
22
+ ? JSON.parse(queryParamsInput)
23
+ : queryParamsInput;
24
+
25
+ const urlObj = new URL(url);
26
+ Object.entries(params).forEach(([key, value]) => {
27
+ urlObj.searchParams.append(key, value);
28
+ });
29
+ url = urlObj.toString();
30
+ } catch (e) {
31
+ console.error('[HTTP Request] Ошибка обработки query params:', e);
32
+ }
33
+ }
34
+
18
35
  let headers = {};
19
- if (headersStr) {
36
+ if (headersInput) {
20
37
  try {
21
- headers = JSON.parse(headersStr);
38
+ headers = typeof headersInput === 'string'
39
+ ? JSON.parse(headersInput)
40
+ : headersInput;
22
41
  } catch (e) {
23
42
  console.error('[HTTP Request] Ошибка парсинга headers:', e);
24
43
  headers = {};
@@ -8,21 +8,11 @@
8
8
  async function execute(node, context, helpers) {
9
9
  const { resolvePinValue, traverse } = helpers;
10
10
 
11
- let message = String(await resolvePinValue(node, 'message', ''));
11
+ // resolvePinValue теперь автоматически заменяет {varName} на значения
12
+ const message = String(await resolvePinValue(node, 'message', ''));
12
13
  const chatType = await resolvePinValue(node, 'chat_type', context.typeChat);
13
14
  const recipient = await resolvePinValue(node, 'recipient', context.user?.username);
14
15
 
15
- // Парсим и заменяем переменные в формате {varName}
16
- const variablePattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
17
- const matches = [...message.matchAll(variablePattern)];
18
-
19
- for (const match of matches) {
20
- const varName = match[1];
21
- // Для динамических пинов, созданных на фронтенде, значение нужно будет получить, используя resolvePinValue
22
- const varValue = await resolvePinValue(node, `var_${varName}`, '');
23
- message = message.replace(match[0], String(varValue));
24
- }
25
-
26
16
  context.bot.sendMessage(chatType, message, recipient);
27
17
  await traverse(node, 'exec');
28
18
  }
@@ -10,19 +10,8 @@ async function evaluate(node, pinId, context, helpers) {
10
10
  const { resolvePinValue } = helpers;
11
11
 
12
12
  if (pinId === 'value') {
13
- let text = String(node.data?.value || '');
14
-
15
- // Парсим и заменяем переменные в формате {varName}
16
- const variablePattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
17
- const matches = [...text.matchAll(variablePattern)];
18
-
19
- for (const match of matches) {
20
- const varName = match[1];
21
- // Получаем значение из динамического пина
22
- const varValue = await resolvePinValue(node, `var_${varName}`, '');
23
- text = text.replace(match[0], String(varValue));
24
- }
25
-
13
+ // resolvePinValue автоматически заменит переменные {varName}
14
+ const text = String(await resolvePinValue(node, 'value', ''));
26
15
  return text;
27
16
  }
28
17