blockmine 1.14.1 → 1.15.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.
@@ -4,6 +4,8 @@ const { PrismaClient } = require('@prisma/client');
4
4
  const { authorize } = require('../middleware/auth');
5
5
  const logger = require('../../lib/logger');
6
6
  const crypto = require('crypto');
7
+ const fse = require('fs-extra');
8
+ const path = require('path');
7
9
 
8
10
  const prisma = new PrismaClient();
9
11
  const router = express.Router({ mergeParams: true });
@@ -14,7 +16,17 @@ router.get('/',
14
16
  const { botId } = req.params;
15
17
  const eventGraphs = await prisma.eventGraph.findMany({
16
18
  where: { botId: parseInt(botId) },
17
- include: { triggers: true },
19
+ include: {
20
+ triggers: true,
21
+ pluginOwner: {
22
+ select: {
23
+ id: true,
24
+ name: true,
25
+ version: true,
26
+ sourceType: true
27
+ }
28
+ }
29
+ },
18
30
  orderBy: { createdAt: 'desc' }
19
31
  });
20
32
 
@@ -46,21 +58,49 @@ router.post('/',
46
58
  }
47
59
 
48
60
  const { botId } = req.params;
49
- const { name } = req.body;
50
-
51
- const initialGraph = {
61
+ const { name, graphJson, variables, isEnabled = true } = req.body;
62
+
63
+ let graphJsonString;
64
+ if (graphJson) {
65
+ if (typeof graphJson === 'string') {
66
+ graphJsonString = graphJson;
67
+ } else {
68
+ graphJsonString = JSON.stringify(graphJson);
69
+ }
70
+ } else {
71
+ graphJsonString = JSON.stringify({
52
72
  nodes: [],
53
73
  connections: []
54
- };
74
+ });
75
+ }
55
76
 
56
77
  try {
78
+ let eventTypes = [];
79
+ try {
80
+ const parsedGraph = JSON.parse(graphJsonString);
81
+ if (parsedGraph.nodes && Array.isArray(parsedGraph.nodes)) {
82
+ const eventNodes = parsedGraph.nodes.filter(node => node.type && node.type.startsWith('event:'));
83
+ eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
84
+ }
85
+ } catch (error) {
86
+ console.warn('[API] Не удалось извлечь типы событий из графа:', error.message);
87
+ }
88
+
57
89
  const newGraph = await prisma.eventGraph.create({
58
90
  data: {
59
91
  botId: parseInt(botId),
60
- name,
61
- graphJson: JSON.stringify(initialGraph)
62
- }
92
+ name: name.trim(),
93
+ isEnabled: isEnabled,
94
+ graphJson: graphJsonString,
95
+ variables: variables || '[]',
96
+ pluginOwnerId: req.body.pluginOwnerId || null,
97
+ triggers: {
98
+ create: eventTypes.map(eventType => ({ eventType }))
99
+ }
100
+ },
101
+ include: { triggers: true }
63
102
  });
103
+
64
104
  res.status(201).json(newGraph);
65
105
  } catch (error) {
66
106
  if (error.code === 'P2002') {
@@ -84,8 +124,20 @@ router.get('/:graphId',
84
124
  const { graphId } = req.params;
85
125
  const eventGraph = await prisma.eventGraph.findUnique({
86
126
  where: { id: parseInt(graphId) },
87
- include: { triggers: true }
127
+ include: {
128
+ triggers: true,
129
+ pluginOwner: {
130
+ select: {
131
+ id: true,
132
+ name: true,
133
+ version: true,
134
+ sourceType: true
135
+ }
136
+ }
137
+ }
88
138
  });
139
+
140
+
89
141
  if (!eventGraph) {
90
142
  return res.status(404).json({ error: 'Event graph not found' });
91
143
  }
@@ -109,12 +161,15 @@ router.put('/:graphId',
109
161
  }
110
162
 
111
163
  const { graphId } = req.params;
112
- const { name, isEnabled, graphJson, variables } = req.body;
164
+ const { name, isEnabled, graphJson, variables, pluginOwnerId } = req.body;
165
+
166
+
113
167
 
114
168
  try {
115
169
  const dataToUpdate = {};
116
170
  if (name !== undefined) dataToUpdate.name = name;
117
171
  if (isEnabled !== undefined) dataToUpdate.isEnabled = isEnabled;
172
+ if (pluginOwnerId !== undefined) dataToUpdate.pluginOwnerId = pluginOwnerId;
118
173
 
119
174
  if (graphJson !== undefined) {
120
175
  dataToUpdate.graphJson = graphJson;
@@ -149,6 +204,25 @@ router.put('/:graphId',
149
204
  include: { triggers: true },
150
205
  });
151
206
 
207
+ if (graphJson && updatedGraph.pluginOwnerId) {
208
+ try {
209
+ const plugin = await prisma.installedPlugin.findUnique({
210
+ where: { id: updatedGraph.pluginOwnerId }
211
+ });
212
+
213
+ if (plugin) {
214
+ const graphDir = path.join(plugin.path, 'graph');
215
+ await fse.mkdir(graphDir, { recursive: true });
216
+
217
+ const graphFile = path.join(graphDir, `${updatedGraph.name}.json`);
218
+ await fse.writeJson(graphFile, JSON.parse(graphJson), { spaces: 2 });
219
+ console.log(`[API] Граф события ${updatedGraph.name} сохранен в ${graphFile}`);
220
+ }
221
+ } catch (error) {
222
+ console.error(`[API] Ошибка сохранения графа события в папку плагина:`, error);
223
+ }
224
+ }
225
+
152
226
  res.json(updatedGraph);
153
227
  } catch (error) {
154
228
  logger.error(error, "--- ERROR CAUGHT IN EVENT GRAPH UPDATE ---");
@@ -64,6 +64,89 @@ router.post('/:id/clear-data', authenticate, authorize('plugin:settings:edit'),
64
64
  }
65
65
  });
66
66
 
67
+ router.get('/:id/info', authenticate, authorize('plugin:list'), async (req, res) => {
68
+ try {
69
+ const pluginId = parseInt(req.params.id);
70
+
71
+ const plugin = await prisma.installedPlugin.findUnique({
72
+ where: { id: pluginId },
73
+ include: {
74
+ commands: {
75
+ select: {
76
+ id: true,
77
+ name: true,
78
+ description: true,
79
+ isEnabled: true,
80
+ isVisual: true,
81
+ owner: true
82
+ }
83
+ },
84
+ eventGraphs: {
85
+ select: {
86
+ id: true,
87
+ name: true,
88
+ isEnabled: true,
89
+ createdAt: true,
90
+ updatedAt: true
91
+ }
92
+ }
93
+ }
94
+ });
95
+
96
+ if (!plugin) {
97
+ return res.status(404).json({ error: 'Плагин не найден.' });
98
+ }
99
+
100
+ res.json(plugin);
101
+ } catch (error) {
102
+ console.error(`[API Error] /plugins/:id/info:`, error);
103
+ res.status(500).json({ error: 'Не удалось получить информацию о плагине.' });
104
+ }
105
+ });
106
+
107
+ router.get('/bot/:botId', authenticate, authorize('plugin:list'), async (req, res) => {
108
+ try {
109
+ const botId = parseInt(req.params.botId);
110
+
111
+ const plugins = await prisma.installedPlugin.findMany({
112
+ where: { botId },
113
+ select: {
114
+ id: true,
115
+ name: true,
116
+ version: true,
117
+ description: true,
118
+ sourceType: true,
119
+ isEnabled: true,
120
+ commands: {
121
+ select: {
122
+ id: true,
123
+ name: true,
124
+ description: true,
125
+ isEnabled: true,
126
+ isVisual: true,
127
+ owner: true
128
+ }
129
+ },
130
+ eventGraphs: {
131
+ select: {
132
+ id: true,
133
+ name: true,
134
+ isEnabled: true,
135
+ createdAt: true,
136
+ updatedAt: true
137
+ }
138
+ }
139
+ },
140
+ orderBy: { name: 'asc' }
141
+ });
142
+
143
+ res.json(plugins);
144
+ } catch (error) {
145
+ console.error(`[API Error] /plugins/bot/:botId:`, error);
146
+ res.status(500).json({ error: 'Не удалось получить список плагинов.' });
147
+ }
148
+ });
149
+
67
150
  router.get('/catalog/:name', async (req, res) => {
68
151
  try {
69
152
  const pluginName = req.params.name;
@@ -253,6 +253,20 @@ process.on('message', async (message) => {
253
253
  }
254
254
  }
255
255
 
256
+ let pluginOwnerId = null;
257
+ if (command.owner && command.owner.startsWith('plugin:')) {
258
+ const pluginName = command.owner.replace('plugin:', '');
259
+ const plugin = await prisma.installedPlugin.findFirst({
260
+ where: {
261
+ botId: bot.config.id,
262
+ name: pluginName
263
+ }
264
+ });
265
+ if (plugin) {
266
+ pluginOwnerId = plugin.id;
267
+ }
268
+ }
269
+
256
270
  const commandData = {
257
271
  botId: bot.config.id,
258
272
  name: command.name,
@@ -264,6 +278,7 @@ process.on('message', async (message) => {
264
278
  aliases: JSON.stringify(command.aliases || []),
265
279
  allowedChatTypes: JSON.stringify(command.allowedChatTypes || ['chat', 'private']),
266
280
  argumentsJson: JSON.stringify(command.args || []),
281
+ pluginOwnerId: pluginOwnerId,
267
282
  };
268
283
 
269
284
  await prisma.command.upsert({
@@ -339,6 +354,69 @@ process.on('message', async (message) => {
339
354
  }, 10000);
340
355
  });
341
356
  },
357
+ registerEventGraph: async (graphData) => {
358
+ try {
359
+ let pluginOwnerId = null;
360
+ if (graphData.owner && graphData.owner.startsWith('plugin:')) {
361
+ const pluginName = graphData.owner.replace('plugin:', '');
362
+ const plugin = await prisma.installedPlugin.findFirst({
363
+ where: {
364
+ botId: bot.config.id,
365
+ name: pluginName
366
+ }
367
+ });
368
+ if (plugin) {
369
+ pluginOwnerId = plugin.id;
370
+ }
371
+ }
372
+
373
+ const graphDataToSave = {
374
+ botId: bot.config.id,
375
+ name: graphData.name,
376
+ isEnabled: graphData.isEnabled !== undefined ? graphData.isEnabled : true,
377
+ graphJson: graphData.graphJson || JSON.stringify({ nodes: [], connections: [] }),
378
+ variables: JSON.stringify(graphData.variables || []),
379
+ pluginOwnerId: pluginOwnerId,
380
+ };
381
+
382
+ const eventGraph = await prisma.eventGraph.upsert({
383
+ where: {
384
+ botId_name: {
385
+ botId: bot.config.id,
386
+ name: graphData.name
387
+ }
388
+ },
389
+ update: {
390
+ isEnabled: graphDataToSave.isEnabled,
391
+ graphJson: graphDataToSave.graphJson,
392
+ variables: graphDataToSave.variables,
393
+ pluginOwnerId: graphDataToSave.pluginOwnerId,
394
+ },
395
+ create: graphDataToSave,
396
+ });
397
+
398
+ if (graphData.triggers && Array.isArray(graphData.triggers)) {
399
+ await prisma.eventTrigger.deleteMany({
400
+ where: { graphId: eventGraph.id }
401
+ });
402
+
403
+ if (graphData.triggers.length > 0) {
404
+ await prisma.eventTrigger.createMany({
405
+ data: graphData.triggers.map(eventType => ({
406
+ graphId: eventGraph.id,
407
+ eventType
408
+ }))
409
+ });
410
+ }
411
+ }
412
+
413
+ sendLog(`[API] Граф события "${graphData.name}" от плагина "${graphData.owner}" зарегистрирован.`);
414
+ return eventGraph;
415
+ } catch (error) {
416
+ sendLog(`[API] Ошибка при регистрации графа события: ${error.message}`);
417
+ throw error;
418
+ }
419
+ },
342
420
  executeCommand: (command) => {
343
421
  sendLog(`[Graph] Выполнение серверной команды: ${command}`);
344
422
  bot.chat(command);
@@ -676,7 +754,13 @@ process.on('message', async (message) => {
676
754
  process.on('unhandledRejection', (reason, promise) => {
677
755
  const errorMsg = `[FATAL] Необработанная ошибка процесса: ${reason?.stack || reason}`;
678
756
  sendLog(errorMsg);
679
- process.exit(1);
757
+ setTimeout(() => process.exit(1), 100);
758
+ });
759
+
760
+ process.on('uncaughtException', (error) => {
761
+ const errorMsg = `[FATAL] Необработанное исключение: ${error.stack || error.message}`;
762
+ sendLog(errorMsg);
763
+ setTimeout(() => process.exit(1), 100);
680
764
  });
681
765
 
682
766
  process.on('SIGTERM', () => {
@@ -688,14 +772,26 @@ process.on('SIGTERM', () => {
688
772
  sendLog(`[System] Ошибка при корректном завершении бота: ${error.message}`);
689
773
  }
690
774
  }
691
- process.exit(0);
775
+ setTimeout(() => process.exit(0), 100);
692
776
  });
693
777
 
694
- process.on('SIGKILL', () => {
695
- sendLog('[System] Получен сигнал SIGKILL. Принудительное завершение...');
696
- process.exit(0);
778
+ process.on('SIGINT', () => {
779
+ sendLog('[System] Получен сигнал SIGINT. Завершение работы...');
780
+ if (bot) {
781
+ try {
782
+ bot.quit();
783
+ } catch (error) {
784
+ sendLog(`[System] Ошибка при корректном завершении бота: ${error.message}`);
785
+ }
786
+ }
787
+ setTimeout(() => process.exit(0), 100);
697
788
  });
698
789
 
790
+ // process.on('SIGKILL', () => {
791
+ // sendLog('[System] Получен сигнал SIGKILL. Принудительное завершение...');
792
+ // process.exit(0);
793
+ // });
794
+
699
795
  function serializeEntity(entity) {
700
796
  if (!entity) return null;
701
797
  return {
@@ -266,6 +266,41 @@ class GraphExecutionEngine {
266
266
  await this.traverse(node, 'exec');
267
267
  break;
268
268
  }
269
+ case 'array:get_random_element': {
270
+ await this.traverse(node, 'element');
271
+ break;
272
+ }
273
+ case 'array:add_element':
274
+ case 'array:remove_by_index':
275
+ case 'array:get_by_index':
276
+ case 'array:find_index':
277
+ case 'array:contains': {
278
+ await this.traverse(node, 'result');
279
+ break;
280
+ }
281
+ case 'data:array_literal':
282
+ case 'data:make_object':
283
+ case 'data:get_variable':
284
+ case 'data:get_argument':
285
+ case 'data:length':
286
+ case 'data:get_entity_field':
287
+ case 'data:cast':
288
+ case 'data:string_literal':
289
+ case 'data:get_user_field':
290
+ case 'data:get_server_players':
291
+ case 'data:get_bot_look':
292
+ case 'math:operation':
293
+ case 'math:random_number':
294
+ case 'logic:operation':
295
+ case 'string:concat':
296
+ case 'object:create':
297
+ case 'object:get':
298
+ case 'object:set':
299
+ case 'object:delete':
300
+ case 'object:has_key': {
301
+ await this.traverse(node, 'value');
302
+ break;
303
+ }
269
304
  }
270
305
  }
271
306
 
@@ -535,7 +570,10 @@ class GraphExecutionEngine {
535
570
  const numPins = node.data?.pinCount || 0;
536
571
  const items = [];
537
572
  for (let i = 0; i < numPins; i++) {
538
- items.push(await this.resolvePinValue(node, `pin_${i}`));
573
+ const value = await this.resolvePinValue(node, `pin_${i}`) ||
574
+ node.data?.[`item_${i}`] ||
575
+ node.data?.[`value_${i}`];
576
+ items.push(value);
539
577
  }
540
578
  result = items;
541
579
  break;