blockmine 1.6.2 → 1.12.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.
@@ -1,13 +1,14 @@
1
1
  const { PrismaClient } = require('@prisma/client');
2
+ const GraphExecutionEngine = require('./GraphExecutionEngine');
3
+ const nodeRegistry = require('./NodeRegistry');
2
4
 
3
5
  const prisma = new PrismaClient();
4
6
 
5
7
  class EventGraphManager {
6
8
  constructor(botManager) {
7
9
  this.botManager = botManager;
8
- this.graphEngine = botManager.graphEngine;
10
+ this.graphEngine = new GraphExecutionEngine(nodeRegistry, botManager);
9
11
  this.activeGraphs = new Map();
10
- // Хранилище для состояний (переменных) каждого графа
11
12
  this.graphStates = new Map();
12
13
  }
13
14
 
@@ -26,7 +27,6 @@ class EventGraphManager {
26
27
  const parsedGraph = JSON.parse(graph.graphJson);
27
28
  if (!parsedGraph.nodes) continue;
28
29
 
29
- // Инициализация начального состояния переменных для этого графа
30
30
  const initialState = {};
31
31
  if (graph.variables) {
32
32
  try {
@@ -56,6 +56,7 @@ class EventGraphManager {
56
56
  name: graph.name,
57
57
  nodes: parsedGraph.nodes,
58
58
  connections: parsedGraph.connections,
59
+ variables: parsedGraph.variables || [],
59
60
  });
60
61
  }
61
62
  } catch (e) {
@@ -69,7 +70,6 @@ class EventGraphManager {
69
70
 
70
71
  unloadGraphsForBot(botId) {
71
72
  this.activeGraphs.delete(botId);
72
- // Также очищаем состояния при выгрузке
73
73
  for (const key of this.graphStates.keys()) {
74
74
  if (key.startsWith(`${botId}-`)) {
75
75
  this.graphStates.delete(key);
@@ -120,7 +120,25 @@ class EventGraphManager {
120
120
  initialContext.botId = botId;
121
121
  initialContext.players = players;
122
122
  initialContext.botState = eventArgs.botState || {};
123
- initialContext.variables = { ...(this.graphStates.get(stateKey) || {}) };
123
+
124
+ const savedVariables = { ...(this.graphStates.get(stateKey) || {}) };
125
+
126
+ if (graph.variables && Array.isArray(graph.variables)) {
127
+ for (const v of graph.variables) {
128
+ if (!savedVariables.hasOwnProperty(v.name)) {
129
+ let val;
130
+ switch (v.type) {
131
+ case 'number': val = Number(v.value) || 0; break;
132
+ case 'boolean': val = v.value === 'true'; break;
133
+ case 'array': try { val = Array.isArray(JSON.parse(v.value)) ? JSON.parse(v.value) : []; } catch { val = []; } break;
134
+ default: val = v.value;
135
+ }
136
+ savedVariables[v.name] = val;
137
+ }
138
+ }
139
+ }
140
+
141
+ initialContext.variables = savedVariables;
124
142
 
125
143
  try {
126
144
  const finalContext = await this.graphEngine.execute(graph, initialContext, eventType);
@@ -162,6 +180,12 @@ class EventGraphManager {
162
180
  case 'entityGone':
163
181
  context.entity = args.entity;
164
182
  break;
183
+ case 'command':
184
+ context.command_name = args.commandName;
185
+ context.user = args.user;
186
+ context.args = args.args;
187
+ context.chat_type = args.typeChat;
188
+ break;
165
189
  }
166
190
  return context;
167
191
  }
@@ -219,6 +219,34 @@ class GraphExecutionEngine {
219
219
  await this.traverse(node, 'completed');
220
220
  break;
221
221
  }
222
+ case 'flow:while': {
223
+ let iteration = 0;
224
+ const maxIterations = 1000;
225
+
226
+ try {
227
+ while (iteration < maxIterations) {
228
+ const condition = await this.resolvePinValue(node, 'condition', false);
229
+ if (!condition) break;
230
+
231
+ this.memo.set(`${node.id}:iteration`, iteration);
232
+ this.clearLoopBodyMemo(node);
233
+ await this.traverse(node, 'loop_body');
234
+ iteration++;
235
+ }
236
+
237
+ if (iteration >= maxIterations) {
238
+ console.warn(`[GraphExecutionEngine] Цикл while достиг максимального количества итераций (${maxIterations})`);
239
+ }
240
+ } catch (e) {
241
+ if (e instanceof BreakLoopSignal) {
242
+ } else {
243
+ throw e;
244
+ }
245
+ }
246
+
247
+ await this.traverse(node, 'completed');
248
+ break;
249
+ }
222
250
  case 'flow:sequence': {
223
251
  const pinCount = node.data?.pinCount || 2;
224
252
  for (let i = 0; i < pinCount; i++) {
@@ -375,7 +403,7 @@ class GraphExecutionEngine {
375
403
  case 'event:command':
376
404
  if (pinId === 'args') result = this.context.args || {};
377
405
  else if (pinId === 'user') result = this.context.user || {};
378
- else if (pinId === 'chat_type') result = this.context.typeChat || 'local';
406
+ else if (pinId === 'chat_type') result = this.context.typeChat || 'chat';
379
407
  else result = this.context[pinId];
380
408
  break;
381
409
  case 'event:chat':
@@ -399,8 +427,13 @@ class GraphExecutionEngine {
399
427
  break;
400
428
 
401
429
  case 'data:get_variable':
402
- const varName = node.data?.variableName || '';
403
- result = this.context.variables.hasOwnProperty(varName) ? this.context.variables[varName] : null;
430
+ const varName = node.data?.variableName || node.data?.selectedVariable || '';
431
+ if (!varName) {
432
+ console.warn('[GraphExecutionEngine] data:get_variable: не указано имя переменной', node.data);
433
+ result = null;
434
+ } else {
435
+ result = this.context.variables.hasOwnProperty(varName) ? this.context.variables[varName] : null;
436
+ }
404
437
  break;
405
438
 
406
439
 
@@ -471,17 +504,13 @@ class GraphExecutionEngine {
471
504
  case 'logic:operation': {
472
505
  const op = node.data?.operation || 'AND';
473
506
  const inputs = [];
474
- for (const key in node.data) {
475
- if (key.startsWith('pin_')) {
476
- inputs.push(await this.resolvePinValue(node, key, false));
477
- }
478
- }
479
- if (inputs.length === 0) {
480
- inputs.push(await this.resolvePinValue(node, 'a', false));
481
- inputs.push(await this.resolvePinValue(node, 'b', false));
507
+ const pinCount = node.data?.pinCount || 2;
508
+
509
+ for (let i = 0; i < pinCount; i++) {
510
+ const value = await this.resolvePinValue(node, `pin_${i}`, false);
511
+ inputs.push(value);
482
512
  }
483
513
 
484
-
485
514
  switch (op) {
486
515
  case 'AND': result = inputs.every(Boolean); break;
487
516
  case 'OR': result = inputs.some(Boolean); break;
@@ -738,6 +767,12 @@ class GraphExecutionEngine {
738
767
  }
739
768
  break;
740
769
  }
770
+ case 'flow:while': {
771
+ if (pinId === 'iteration') {
772
+ result = this.memo.get(`${node.id}:iteration`);
773
+ }
774
+ break;
775
+ }
741
776
 
742
777
  case 'string:equals': {
743
778
  const strA = String(await this.resolvePinValue(node, 'a', ''));
@@ -798,6 +833,13 @@ class GraphExecutionEngine {
798
833
 
799
834
  return false;
800
835
  }
836
+
837
+ hasConnection(node, pinId) {
838
+ if (!this.activeGraph || !this.activeGraph.connections) return false;
839
+ return this.activeGraph.connections.some(conn =>
840
+ conn.targetNodeId === node.id && conn.targetPinId === pinId
841
+ );
842
+ }
801
843
  }
802
844
 
803
845
  module.exports = GraphExecutionEngine;
@@ -33,7 +33,16 @@ class MessageQueue {
33
33
  enqueue(chatType, message, username = null) {
34
34
  const typeConfig = this.chatTypes[chatType];
35
35
  if (!typeConfig) return;
36
- this._enqueue({ type: 'simple', chatType, ...typeConfig, message, username });
36
+
37
+ if (Array.isArray(message)) {
38
+ for (const msg of message) {
39
+ if (typeof msg === 'string' && msg.trim().length > 0) {
40
+ this._enqueue({ type: 'simple', chatType, ...typeConfig, message: msg, username });
41
+ }
42
+ }
43
+ } else if (typeof message === 'string') {
44
+ this._enqueue({ type: 'simple', chatType, ...typeConfig, message, username });
45
+ }
37
46
  }
38
47
 
39
48
  enqueueAndWait(command, patterns, timeout) {
@@ -102,11 +102,12 @@ class NodeRegistry {
102
102
  label: '▶️ При выполнении команды',
103
103
  category: 'События',
104
104
  description: 'Стартовая точка для графа команды.',
105
- graphType: command,
105
+ graphType: 'all',
106
106
  pins: {
107
107
  inputs: [],
108
108
  outputs: [
109
109
  { id: 'exec', name: 'Выполнить', type: 'Exec' },
110
+ { id: 'command_name', name: 'Имя команды', type: 'String' },
110
111
  { id: 'user', name: 'Пользователь', type: 'User' },
111
112
  { id: 'args', name: 'Аргументы', type: 'Object' },
112
113
  { id: 'chat_type', name: 'Тип чата', type: 'String' }
@@ -1,8 +1,36 @@
1
1
 
2
2
  const path = require('path');
3
3
  const fs = require('fs/promises');
4
+ const { execSync } = require('child_process');
5
+ const fssync = require('fs');
6
+ const PluginStore = require('../plugins/PluginStore');
4
7
 
5
- async function initializePlugins(bot, installedPlugins = []) {
8
+ const projectRoot = path.resolve(__dirname, '..');
9
+
10
+ async function ensurePluginDependencies(pluginPath, pluginName) {
11
+ const packageJsonPath = path.join(pluginPath, 'package.json');
12
+ try {
13
+ if (fssync.existsSync(packageJsonPath)) {
14
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
15
+ if (packageJson.dependencies && Object.keys(packageJson.dependencies).length > 0) {
16
+ console.log(`[PluginLoader] У плагина ${pluginName} есть зависимости, устанавливаем их...`);
17
+ try {
18
+ execSync('npm install', {
19
+ cwd: pluginPath,
20
+ stdio: 'pipe'
21
+ });
22
+ console.log(`[PluginLoader] Зависимости для плагина ${pluginName} установлены`);
23
+ } catch (installError) {
24
+ console.error(`[PluginLoader] Ошибка установки зависимостей для ${pluginName}:`, installError.message);
25
+ }
26
+ }
27
+ }
28
+ } catch (error) {
29
+ console.error(`[PluginLoader] Ошибка чтения package.json для ${pluginName}:`, error.message);
30
+ }
31
+ }
32
+
33
+ async function initializePlugins(bot, installedPlugins = [], prisma) {
6
34
  if (!installedPlugins || installedPlugins.length === 0) return;
7
35
 
8
36
  const sendLog = bot.sendLog || console.log;
@@ -11,6 +39,8 @@ async function initializePlugins(bot, installedPlugins = []) {
11
39
  for (const plugin of installedPlugins) {
12
40
  if (plugin && plugin.path) {
13
41
  try {
42
+ await ensurePluginDependencies(plugin.path, plugin.name);
43
+
14
44
  const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
15
45
  const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
16
46
  const defaultSettings = {};
@@ -38,20 +68,54 @@ async function initializePlugins(bot, installedPlugins = []) {
38
68
  }
39
69
 
40
70
  const finalSettings = { ...defaultSettings, ...savedSettings };
71
+ const store = new PluginStore(prisma, bot.config.id, plugin.name);
41
72
 
42
73
  const mainFile = manifest.main || 'index.js';
43
74
  const entryPointPath = path.join(plugin.path, mainFile);
44
75
  const normalizedPath = entryPointPath.replace(/\\/g, '/');
45
76
 
46
77
  sendLog(`[PluginLoader] Загрузка: ${plugin.name} (v${plugin.version}) из ${normalizedPath}`);
47
- const pluginModule = require(normalizedPath);
48
78
 
49
- if (typeof pluginModule === 'function') {
50
- pluginModule(bot, { settings: finalSettings });
51
- } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
52
- pluginModule.onLoad(bot, { settings: finalSettings });
53
- } else {
54
- sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
79
+ try {
80
+ const pluginModule = require(normalizedPath);
81
+
82
+ if (typeof pluginModule === 'function') {
83
+ pluginModule(bot, { settings: finalSettings, store });
84
+ } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
85
+ pluginModule.onLoad(bot, { settings: finalSettings, store });
86
+ } else {
87
+ sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
88
+ }
89
+ } catch (error) {
90
+ if (error.message.includes('Cannot find module')) {
91
+ const moduleMatch = error.message.match(/Cannot find module '([^']+)'/);
92
+ if (moduleMatch) {
93
+ const missingModule = moduleMatch[1];
94
+ sendLog(`[PluginLoader] Попытка установки недостающего модуля ${missingModule} в папку плагина ${plugin.name}`);
95
+ try {
96
+ execSync(`npm install ${missingModule}`, {
97
+ cwd: plugin.path,
98
+ stdio: 'pipe'
99
+ });
100
+ sendLog(`[PluginLoader] Модуль ${missingModule} успешно установлен в папку плагина ${plugin.name}, повторная попытка загрузки`);
101
+
102
+ // Повторная попытка загрузки
103
+ const pluginModule = require(normalizedPath);
104
+ if (typeof pluginModule === 'function') {
105
+ pluginModule(bot, { settings: finalSettings, store });
106
+ } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
107
+ pluginModule.onLoad(bot, { settings: finalSettings, store });
108
+ }
109
+ } catch (installError) {
110
+ sendLog(`[PluginLoader] Не удалось установить модуль ${missingModule} в папку плагина ${plugin.name}: ${installError.message}`);
111
+ throw error; // Пробрасываем оригинальную ошибку
112
+ }
113
+ } else {
114
+ throw error;
115
+ }
116
+ } else {
117
+ throw error;
118
+ }
55
119
  }
56
120
 
57
121
  } catch (error) {
@@ -277,6 +277,25 @@ class PluginManager {
277
277
 
278
278
  return await this.installFromGithub(botId, repoUrl, prisma, true);
279
279
  }
280
+
281
+ async clearPluginData(pluginId) {
282
+ const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
283
+ if (!plugin) {
284
+ throw new Error('Плагин не найден.');
285
+ }
286
+
287
+ console.log(`[PluginManager] Очистка данных для плагина ${plugin.name} (Bot ID: ${plugin.botId})`);
288
+
289
+ const { count } = await prisma.pluginDataStore.deleteMany({
290
+ where: {
291
+ pluginName: plugin.name,
292
+ botId: plugin.botId,
293
+ },
294
+ });
295
+
296
+ console.log(`[PluginManager] Удалено ${count} записей из хранилища.`);
297
+ return { count };
298
+ }
280
299
  }
281
300
 
282
301
  module.exports = PluginManager;
@@ -0,0 +1,87 @@
1
+ class PluginStore {
2
+ constructor(prisma, botId, pluginName) {
3
+ this.prisma = prisma;
4
+ this.botId = botId;
5
+ this.pluginName = pluginName;
6
+ }
7
+
8
+ async set(key, value) {
9
+ const jsonValue = JSON.stringify(value);
10
+ await this.prisma.pluginDataStore.upsert({
11
+ where: {
12
+ pluginName_botId_key: {
13
+ pluginName: this.pluginName,
14
+ botId: this.botId,
15
+ key: key
16
+ }
17
+ },
18
+ update: {
19
+ value: jsonValue
20
+ },
21
+ create: {
22
+ pluginName: this.pluginName,
23
+ botId: this.botId,
24
+ key: key,
25
+ value: jsonValue
26
+ }
27
+ });
28
+ }
29
+
30
+ async get(key) {
31
+ const data = await this.prisma.pluginDataStore.findUnique({
32
+ where: {
33
+ pluginName_botId_key: {
34
+ pluginName: this.pluginName,
35
+ botId: this.botId,
36
+ key: key
37
+ }
38
+ }
39
+ });
40
+ return data ? JSON.parse(data.value) : null;
41
+ }
42
+
43
+ async delete(key) {
44
+ try {
45
+ await this.prisma.pluginDataStore.delete({
46
+ where: {
47
+ pluginName_botId_key: {
48
+ pluginName: this.pluginName,
49
+ botId: this.botId,
50
+ key: key
51
+ }
52
+ }
53
+ });
54
+ return true;
55
+ } catch (error) {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ async has(key) {
61
+ const count = await this.prisma.pluginDataStore.count({
62
+ where: {
63
+ pluginName: this.pluginName,
64
+ botId: this.botId,
65
+ key: key
66
+ }
67
+ });
68
+ return count > 0;
69
+ }
70
+
71
+ async getAll() {
72
+ const allData = await this.prisma.pluginDataStore.findMany({
73
+ where: {
74
+ pluginName: this.pluginName,
75
+ botId: this.botId
76
+ }
77
+ });
78
+ const map = new Map();
79
+ for (const item of allData) {
80
+ map.set(item.key, JSON.parse(item.value));
81
+ }
82
+ return map;
83
+ }
84
+ }
85
+
86
+ module.exports = PluginStore;
87
+
@@ -1,6 +1,8 @@
1
1
  const { Server } = require('socket.io');
2
2
  const config = require('../config');
3
3
 
4
+ const { botManager } = require('../core/services');
5
+
4
6
  let io;
5
7
 
6
8
  function initializeSocket(httpServer) {
@@ -16,14 +18,20 @@ function initializeSocket(httpServer) {
16
18
  });
17
19
 
18
20
  io.on('connection', (socket) => {
19
- // console.log(`[Socket.IO] Пользователь подключен: ${socket.id}. Всего клиентов: ${io.engine.clientsCount}`);
20
21
 
21
22
  socket.on('disconnect', () => {
22
- // console.log(`[Socket.IO] Пользователь отключен: ${socket.id}. Всего клиентов: ${io.engine.clientsCount}`);
23
+ botManager.handleSocketDisconnect(socket);
24
+ });
25
+
26
+ socket.on('plugin:ui:subscribe', ({ botId, pluginName }) => {
27
+ botManager.subscribeToPluginUi(botId, pluginName, socket);
28
+ });
29
+
30
+ socket.on('plugin:ui:unsubscribe', ({ botId, pluginName }) => {
31
+ botManager.unsubscribeFromPluginUi(botId, pluginName, socket);
23
32
  });
24
33
  });
25
34
 
26
- // console.log('Socket.IO инициализирован с динамическим CORS.');
27
35
  return io;
28
36
  }
29
37
 
@@ -19,6 +19,7 @@ const searchRoutes = require('./api/routes/search');
19
19
  const eventGraphsRouter = require('./api/routes/eventGraphs');
20
20
  const TaskScheduler = require('./core/TaskScheduler');
21
21
  const panelRoutes = require('./api/routes/panel');
22
+ const changelogRoutes = require('./api/routes/changelog');
22
23
 
23
24
  const app = express();
24
25
  const server = http.createServer(app);
@@ -59,6 +60,7 @@ app.use('/api/servers', serverRoutes);
59
60
  app.use('/api/permissions', permissionsRoutes);
60
61
  app.use('/api/search', searchRoutes);
61
62
  app.use('/api/panel', panelRoutes);
63
+ app.use('/api/changelog', changelogRoutes);
62
64
 
63
65
  app.use(express.static(frontendPath));
64
66
 
Binary file
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ extends: ['@commitlint/config-conventional']
3
+ };