blockmine 1.17.0 → 1.18.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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,33 @@
1
1
  # История версий
2
2
 
3
3
 
4
+ ### [1.18.1](https://github.com/blockmineJS/blockmine/compare/v1.18.0...v1.18.1) (2025-08-03)
5
+
6
+
7
+ ### 🐛 Исправления
8
+
9
+ * добавлен механизм перезапуска ботов при некоторых противных ошибках ([2eb34e3](https://github.com/blockmineJS/blockmine/commit/2eb34e3e63aaa72e5fb641d7ab155baa92dbde63))
10
+ * копирование кода/графов и всё что дает возможность скопировать в буфер обмена, починено ([dbe4ee6](https://github.com/blockmineJS/blockmine/commit/dbe4ee6bb2e19d99cfaaef33130c27803d5988a8))
11
+ * мелкие фиксы крон паттерна у планировщика. больше логов для наблюдений ([c33e7c8](https://github.com/blockmineJS/blockmine/commit/c33e7c895b3daf60bcde187cf1334ab38bb71592))
12
+
13
+ ## [1.18.0](https://github.com/blockmineJS/blockmine/compare/v1.17.1...v1.18.0) (2025-07-26)
14
+
15
+
16
+ ### ✨ Новые возможности
17
+
18
+ * добавлена новая кнопка - "Предложить улучшение". Поможет адептам составить свой запрос ([d741881](https://github.com/blockmineJS/blockmine/commit/d7418813e53d15fcd16c0517cea033d019ed355b))
19
+ * добавлено глубокое объединение настроек для плагинов и улучшена установка зависимостей ([452af4b](https://github.com/blockmineJS/blockmine/commit/452af4b67325f3faebe12c136a40f77515e805c0))
20
+
21
+ ### [1.17.1](https://github.com/blockmineJS/blockmine/compare/v1.17.0...v1.17.1) (2025-07-24)
22
+
23
+
24
+ ### 🐛 Исправления
25
+
26
+ * добавлена синхронизация статусов ботов каждые 10 секунд и обработка события 'bot_ready' ([c9e8bcc](https://github.com/blockmineJS/blockmine/commit/c9e8bcc5f1b31a122aa05b2bb65f3b4127dfb79a))
27
+ * исправление ошибок со скинами. вроде ([459d65b](https://github.com/blockmineJS/blockmine/commit/459d65ba40ec18da5d9a7402992c2cd6aa73a7d2))
28
+ * исправление ошибок со скинами. by сахарок ([433e5c6](https://github.com/blockmineJS/blockmine/commit/433e5c6222e385cfd0ba656c78eecd67f094b0ef))
29
+ * уменьшены лимиты логов для ботов. так много явно не надо ([d690dcd](https://github.com/blockmineJS/blockmine/commit/d690dcd5701603d455d105a2032f9262923cf5f0))
30
+
4
31
  ## [1.17.0](https://github.com/blockmineJS/blockmine/compare/v1.16.3...v1.17.0) (2025-07-24)
5
32
 
6
33
 
@@ -1,27 +1,27 @@
1
- {
2
- "name": "backend",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "src/server.js",
6
- "scripts": {
7
- "start": "node ../backend/cli.js",
8
- "dev": "cross-env NODE_ENV=development nodemon",
9
- "test": "echo \"Error: no test specified\" && exit 1"
10
- },
11
- "prisma": {
12
- "seed": "node prisma/seed.js"
13
- },
14
- "keywords": [],
15
- "author": "",
16
- "license": "ISC",
17
- "devDependencies": {
18
- "nodemon": "^3.1.2",
19
- "cross-env": "^7.0.3"
20
- },
21
- "dependencies": {
22
- "express-validator": "^7.2.1",
23
-
24
- "pino": "^9.7.0",
25
- "pino-pretty": "^13.0.0"
26
- }
27
- }
1
+ {
2
+ "name": "backend",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "src/server.js",
6
+ "scripts": {
7
+ "start": "node ../backend/cli.js",
8
+ "dev": "cross-env NODE_ENV=development nodemon",
9
+ "test": "echo \"Error: no test specified\" && exit 1"
10
+ },
11
+ "prisma": {
12
+ "seed": "node prisma/seed.js"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "devDependencies": {
18
+ "nodemon": "^3.1.2",
19
+ "cross-env": "^7.0.3"
20
+ },
21
+ "dependencies": {
22
+ "express-validator": "^7.2.1",
23
+
24
+ "pino": "^9.7.0",
25
+ "pino-pretty": "^13.0.0"
26
+ }
27
+ }
@@ -12,6 +12,7 @@ const { encrypt } = require('../../core/utils/crypto');
12
12
  const { randomUUID } = require('crypto');
13
13
  const eventGraphsRouter = require('./eventGraphs');
14
14
  const pluginIdeRouter = require('./pluginIde');
15
+ const { deepMergeSettings } = require('../../core/utils/settingsMerger');
15
16
 
16
17
  const multer = require('multer');
17
18
  const archiver = require('archiver');
@@ -163,7 +164,7 @@ router.get('/state', conditionalListAuth, (req, res) => {
163
164
  router.get('/:id/logs', conditionalListAuth, (req, res) => {
164
165
  try {
165
166
  const botId = parseInt(req.params.id, 10);
166
- const { limit = 100, offset = 0 } = req.query;
167
+ const { limit = 50, offset = 0 } = req.query;
167
168
 
168
169
  const logs = botManager.getBotLogs(botId);
169
170
 
@@ -522,7 +523,7 @@ router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view
522
523
  }
523
524
  }
524
525
 
525
- const finalSettings = { ...defaultSettings, ...savedSettings };
526
+ const finalSettings = deepMergeSettings(defaultSettings, savedSettings);
526
527
  res.json(finalSettings);
527
528
  } catch (error) {
528
529
  console.error("[API Error] /settings GET:", error);
@@ -969,7 +970,7 @@ router.get('/:id/settings/all', authorize('bot:update'), async (req, res) => {
969
970
  description: plugin.description,
970
971
  isEnabled: plugin.isEnabled,
971
972
  manifest: manifest,
972
- settings: { ...defaultSettings, ...savedSettings }
973
+ settings: deepMergeSettings(defaultSettings, savedSettings)
973
974
  };
974
975
  });
975
976
 
@@ -12,7 +12,8 @@ router.use(authenticate);
12
12
 
13
13
  const normalizeCronPattern = (pattern) => {
14
14
  if (typeof pattern !== 'string') return '* * * * *';
15
- return pattern.replace(/\*\/1/g, '*').trim();
15
+ // Убираем лишние пробелы и нормализуем паттерн
16
+ return pattern.replace(/\*\/1/g, '*').replace(/\s+/g, ' ').trim();
16
17
  };
17
18
 
18
19
  router.get('/', authorize('task:list'), async (req, res) => {
@@ -55,6 +55,7 @@ class BotManager {
55
55
 
56
56
  getInstanceId();
57
57
  setInterval(() => this.updateAllResourceUsage(), 5000);
58
+ setInterval(() => this.syncBotStatuses(), 10000);
58
59
  if (config.telemetry?.enabled) {
59
60
  setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
60
61
  }
@@ -322,6 +323,14 @@ class BotManager {
322
323
  if (message) this.appendLog(botId, `[SYSTEM] ${message}`);
323
324
  getIO().emit('bot:status', { botId, status, message });
324
325
  }
326
+
327
+ syncBotStatuses() {
328
+ for (const [botId, child] of this.bots.entries()) {
329
+ const actualStatus = child.killed ? 'stopped' : 'running';
330
+ const { getIO } = require('../real-time/socketHandler');
331
+ getIO().emit('bot:status', { botId, status: actualStatus });
332
+ }
333
+ }
325
334
 
326
335
  appendLog(botId, logContent) {
327
336
  const { getIO } = require('../real-time/socketHandler');
@@ -330,7 +339,7 @@ class BotManager {
330
339
  content: logContent,
331
340
  };
332
341
  const currentLogs = this.logCache.get(botId) || [];
333
- const newLogs = [...currentLogs.slice(-499), logEntry];
342
+ const newLogs = [...currentLogs.slice(-199), logEntry];
334
343
  this.logCache.set(botId, newLogs);
335
344
  getIO().emit('bot:log', { botId, log: logEntry });
336
345
  }
@@ -380,7 +389,6 @@ class BotManager {
380
389
  if (decryptedConfig.password) decryptedConfig.password = decrypt(decryptedConfig.password);
381
390
  if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
382
391
 
383
- // Очищаем данные прокси от лишних символов
384
392
  if (decryptedConfig.proxyUsername) decryptedConfig.proxyUsername = decryptedConfig.proxyUsername.trim();
385
393
  if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decryptedConfig.proxyPassword.trim();
386
394
 
@@ -431,6 +439,9 @@ class BotManager {
431
439
  case 'status':
432
440
  this.emitStatusUpdate(botId, message.status);
433
441
  break;
442
+ case 'bot_ready':
443
+ this.emitStatusUpdate(botId, 'running', 'Бот успешно подключился к серверу.');
444
+ break;
434
445
  case 'validate_and_run_command':
435
446
  await this.handleCommandValidation(botConfig, message);
436
447
  break;
@@ -517,8 +528,18 @@ class BotManager {
517
528
  this.bots.delete(botId);
518
529
  this.resourceUsage.delete(botId);
519
530
  this.botConfigs.delete(botId);
531
+
520
532
  this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
521
533
  this.updateAllResourceUsage();
534
+
535
+ if (code === 1) {
536
+ console.log(`[BotManager] Обнаружена ошибка с кодом 1 для бота ${botId}. Попытка перезапуска через 5 секунд...`);
537
+ this.appendLog(botId, `[SYSTEM] Обнаружена критическая ошибка, перезапуск через 5 секунд...`);
538
+ setTimeout(() => {
539
+ console.log(`[BotManager] Перезапуск бота ${botId}...`);
540
+ this.startBot(botConfig);
541
+ }, 5000);
542
+ }
522
543
  });
523
544
 
524
545
  this.bots.set(botConfig.id, child);
@@ -22,6 +22,17 @@ const pendingRequests = new Map();
22
22
  const entityMoveThrottles = new Map();
23
23
  let connectionTimeout = null;
24
24
 
25
+ const originalJSONParse = JSON.parse
26
+ JSON.parse = function(text, reviver) {
27
+ if (typeof text !== 'string') return originalJSONParse(text, reviver)
28
+ try {
29
+ return originalJSONParse(text, reviver)
30
+ } catch (e) {
31
+ const fixed = text.replace(/([{,])\s*([a-zA-Z0-9_]+)\s*:/g, '$1"$2":')
32
+ return originalJSONParse(fixed, reviver)
33
+ }
34
+ }
35
+
25
36
  function sendLog(content) {
26
37
  if (process.send) {
27
38
  process.send({ type: 'log', content });
@@ -616,8 +627,11 @@ process.on('message', async (message) => {
616
627
  clearTimeout(connectionTimeout);
617
628
  connectionTimeout = null;
618
629
  }
630
+ const restartableReasons = ['socketClosed', 'keepAliveError'];
631
+ const exitCode = restartableReasons.includes(reason) ? 1 : 0;
632
+
619
633
  sendLog(`[Event: end] Отключен от сервера. Причина: ${reason}`);
620
- process.exit(0);
634
+ process.exit(exitCode);
621
635
  });
622
636
 
623
637
  bot.on('playerJoined', (player) => {
@@ -1,132 +1,87 @@
1
-
2
- const path = require('path');
3
- const fs = require('fs/promises');
4
- const { execSync } = require('child_process');
5
- const fssync = require('fs');
6
- const PluginStore = require('../plugins/PluginStore');
7
-
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
- const shell = process.platform === 'win32' ? process.env.ComSpec : '/bin/sh';
18
- try {
19
- execSync('npm install', {
20
- cwd: pluginPath,
21
- stdio: 'pipe',
22
- shell: shell
23
- });
24
- console.log(`[PluginLoader] Зависимости для плагина ${pluginName} установлены`);
25
- } catch (installError) {
26
- console.error(`[PluginLoader] Ошибка установки зависимостей для ${pluginName}:`, installError.message);
27
- }
28
- }
29
- }
30
- } catch (error) {
31
- console.error(`[PluginLoader] Ошибка чтения package.json для ${pluginName}:`, error.message);
32
- }
33
- }
34
-
35
- async function initializePlugins(bot, installedPlugins = [], prisma) {
36
- if (!installedPlugins || installedPlugins.length === 0) return;
37
-
38
- const sendLog = bot.sendLog || console.log;
39
- sendLog(`[PluginLoader] Загрузка ${installedPlugins.length} плагинов...`);
40
-
41
- for (const plugin of installedPlugins) {
42
- if (plugin && plugin.path) {
43
- try {
44
- await ensurePluginDependencies(plugin.path, plugin.name);
45
-
46
- const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
47
- const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
48
- const defaultSettings = {};
49
-
50
- if (manifest.settings) {
51
- for (const key in manifest.settings) {
52
- const config = manifest.settings[key];
53
- if (config.type === 'json_file' && config.defaultPath) {
54
- const configFilePath = path.join(plugin.path, config.defaultPath);
55
- try {
56
- const fileContent = await fs.readFile(configFilePath, 'utf-8');
57
- defaultSettings[key] = JSON.parse(fileContent);
58
- } catch (e) {
59
- sendLog(`[PluginLoader] WARN: Не удалось прочитать defaultPath '${config.defaultPath}' для плагина ${plugin.name}.`);
60
- defaultSettings[key] = (config.type === 'string[]' || config.type === 'json_file') ? [] : {};
61
- }
62
- } else {
63
- try {
64
- defaultSettings[key] = JSON.parse(config.default || 'null');
65
- } catch {
66
- defaultSettings[key] = config.default;
67
- }
68
- }
69
- }
70
- }
71
-
72
- const finalSettings = { ...defaultSettings, ...savedSettings };
73
- const store = new PluginStore(prisma, bot.config.id, plugin.name);
74
-
75
- const mainFile = manifest.main || 'index.js';
76
- const entryPointPath = path.join(plugin.path, mainFile);
77
- const normalizedPath = entryPointPath.replace(/\\/g, '/');
78
-
79
- sendLog(`[PluginLoader] Загрузка: ${plugin.name} (v${plugin.version}) из ${normalizedPath}`);
80
-
81
- try {
82
- const pluginModule = require(normalizedPath);
83
-
84
- if (typeof pluginModule === 'function') {
85
- pluginModule(bot, { settings: finalSettings, store });
86
- } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
87
- pluginModule.onLoad(bot, { settings: finalSettings, store });
88
- } else {
89
- sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
90
- }
91
- } catch (error) {
92
- if (error.message.includes('Cannot find module')) {
93
- const moduleMatch = error.message.match(/Cannot find module '([^']+)'/);
94
- if (moduleMatch) {
95
- const missingModule = moduleMatch[1];
96
- sendLog(`[PluginLoader] Попытка установки недостающего модуля ${missingModule} в папку плагина ${plugin.name}`);
97
- const shell = process.platform === 'win32' ? process.env.ComSpec : '/bin/sh';
98
- try {
99
- execSync(`npm install ${missingModule}`, {
100
- cwd: plugin.path,
101
- stdio: 'pipe',
102
- shell: shell
103
- });
104
- sendLog(`[PluginLoader] Модуль ${missingModule} успешно установлен в папку плагина ${plugin.name}, повторная попытка загрузки`);
105
-
106
- // Повторная попытка загрузки
107
- const pluginModule = require(normalizedPath);
108
- if (typeof pluginModule === 'function') {
109
- pluginModule(bot, { settings: finalSettings, store });
110
- } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
111
- pluginModule.onLoad(bot, { settings: finalSettings, store });
112
- }
113
- } catch (installError) {
114
- sendLog(`[PluginLoader] Не удалось установить модуль ${missingModule} в папку плагина ${plugin.name}: ${installError.message}`);
115
- throw error; // Пробрасываем оригинальную ошибку
116
- }
117
- } else {
118
- throw error;
119
- }
120
- } else {
121
- throw error;
122
- }
123
- }
124
-
125
- } catch (error) {
126
- sendLog(`[PluginLoader] [FATAL] Не удалось загрузить плагин ${plugin.name}: ${error.stack}`);
127
- }
128
- }
129
- }
130
- }
131
-
1
+
2
+ const path = require('path');
3
+ const fs = require('fs/promises');
4
+ const { execSync } = require('child_process');
5
+ const fssync = require('fs');
6
+ const PluginStore = require('../plugins/PluginStore');
7
+ const { deepMergeSettings } = require('./utils/settingsMerger');
8
+
9
+ const projectRoot = path.resolve(__dirname, '..');
10
+
11
+
12
+
13
+ async function initializePlugins(bot, installedPlugins = [], prisma) {
14
+ if (!installedPlugins || installedPlugins.length === 0) return;
15
+
16
+ const sendLog = bot.sendLog || console.log;
17
+ sendLog(`[PluginLoader] Загрузка ${installedPlugins.length} плагинов...`);
18
+
19
+ for (const plugin of installedPlugins) {
20
+ if (plugin && plugin.path) {
21
+ try {
22
+ const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
23
+ const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
24
+ const defaultSettings = {};
25
+
26
+ if (manifest.settings) {
27
+ for (const key in manifest.settings) {
28
+ const config = manifest.settings[key];
29
+ if (config.type === 'json_file' && config.defaultPath) {
30
+ const configFilePath = path.join(plugin.path, config.defaultPath);
31
+ try {
32
+ const fileContent = await fs.readFile(configFilePath, 'utf-8');
33
+ defaultSettings[key] = JSON.parse(fileContent);
34
+ } catch (e) {
35
+ sendLog(`[PluginLoader] WARN: Не удалось прочитать defaultPath '${config.defaultPath}' для плагина ${plugin.name}.`);
36
+ defaultSettings[key] = (config.type === 'string[]' || config.type === 'json_file') ? [] : {};
37
+ }
38
+ } else {
39
+ try {
40
+ defaultSettings[key] = JSON.parse(config.default || 'null');
41
+ } catch {
42
+ defaultSettings[key] = config.default;
43
+ }
44
+ }
45
+ }
46
+ }
47
+
48
+ const finalSettings = deepMergeSettings(defaultSettings, savedSettings);
49
+ const store = new PluginStore(prisma, bot.config.id, plugin.name);
50
+
51
+ const mainFile = manifest.main || 'index.js';
52
+ const entryPointPath = path.join(plugin.path, mainFile);
53
+ const normalizedPath = entryPointPath.replace(/\\/g, '/');
54
+
55
+ sendLog(`[PluginLoader] Загрузка: ${plugin.name} (v${plugin.version}) из ${normalizedPath}`);
56
+
57
+ try {
58
+ const pluginModule = require(normalizedPath);
59
+
60
+ if (typeof pluginModule === 'function') {
61
+ pluginModule(bot, { settings: finalSettings, store });
62
+ } else if (pluginModule && typeof pluginModule.onLoad === 'function') {
63
+ pluginModule.onLoad(bot, { settings: finalSettings, store });
64
+ } else {
65
+ sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
66
+ }
67
+ } catch (error) {
68
+ // Зависимости должны быть установлены заранее в PluginManager
69
+ // Если модуль не найден, это означает, что установка зависимостей не была выполнена корректно
70
+ if (error.message.includes('Cannot find module')) {
71
+ const moduleMatch = error.message.match(/Cannot find module '([^']+)'/);
72
+ if (moduleMatch) {
73
+ const missingModule = moduleMatch[1];
74
+ sendLog(`[PluginLoader] [ERROR] Модуль ${missingModule} не найден для плагина ${plugin.name}. Зависимости должны быть установлены заранее.`);
75
+ }
76
+ }
77
+ throw error;
78
+ }
79
+
80
+ } catch (error) {
81
+ sendLog(`[PluginLoader] [FATAL] Не удалось загрузить плагин ${plugin.name}: ${error.stack}`);
82
+ }
83
+ }
84
+ }
85
+ }
86
+
132
87
  module.exports = { initializePlugins };