blockmine 1.17.1 → 1.18.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.
- package/CHANGELOG.md +28 -0
- package/backend/package.json +27 -27
- package/backend/src/api/routes/bots.js +3 -2
- package/backend/src/api/routes/tasks.js +2 -1
- package/backend/src/core/BotManager.js +10 -0
- package/backend/src/core/BotProcess.js +4 -1
- package/backend/src/core/PluginLoader.js +86 -131
- package/backend/src/core/PluginManager.js +427 -402
- package/backend/src/core/TaskScheduler.js +82 -52
- package/backend/src/core/utils/settingsMerger.js +25 -0
- package/backend/src/server.js +2 -1
- package/frontend/dist/assets/index-BQxcQe9H.css +1 -0
- package/frontend/dist/assets/{index-UZUhEwz5.js → index-Ddf7Gs0f.js} +1671 -1671
- package/frontend/dist/create_issue.png +0 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-D3DCCCQP.css +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
# История версий
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
### [1.18.2](https://github.com/blockmineJS/blockmine/compare/v1.18.1...v1.18.2) (2025-08-07)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### 🐛 Исправления
|
|
8
|
+
|
|
9
|
+
* добавлена обработка ошибок при подготовке данных для импорта бота ([4e1c67d](https://github.com/blockmineJS/blockmine/commit/4e1c67d1d85ffc792b462b5f5ed0fc7daf2c45b9))
|
|
10
|
+
* добавлено авто заполнение владельцев при импорте бота ([10e8fde](https://github.com/blockmineJS/blockmine/commit/10e8fdec16f4d3d6b3cd42dfceee66eb8576f6ce))
|
|
11
|
+
* добавлено скрытое заголовок и описание для диалога создания нового бота ([1c8f87a](https://github.com/blockmineJS/blockmine/commit/1c8f87a6a8d723fb3ebdfa4abfdf6e32823f853d))
|
|
12
|
+
* увеличен лимит на размер загружаемых файлов. поможет при импорте больших ботов ([a45cf0a](https://github.com/blockmineJS/blockmine/commit/a45cf0ab368de0a021ee708d40a0d64a192ae3c9))
|
|
13
|
+
* улучшен интерфейс для мобильных устройств ([b5684a3](https://github.com/blockmineJS/blockmine/commit/b5684a34b92ce5a819b3e7a82e11c05679d890c7))
|
|
14
|
+
|
|
15
|
+
### [1.18.1](https://github.com/blockmineJS/blockmine/compare/v1.18.0...v1.18.1) (2025-08-03)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### 🐛 Исправления
|
|
19
|
+
|
|
20
|
+
* добавлен механизм перезапуска ботов при некоторых противных ошибках ([2eb34e3](https://github.com/blockmineJS/blockmine/commit/2eb34e3e63aaa72e5fb641d7ab155baa92dbde63))
|
|
21
|
+
* копирование кода/графов и всё что дает возможность скопировать в буфер обмена, починено ([dbe4ee6](https://github.com/blockmineJS/blockmine/commit/dbe4ee6bb2e19d99cfaaef33130c27803d5988a8))
|
|
22
|
+
* мелкие фиксы крон паттерна у планировщика. больше логов для наблюдений ([c33e7c8](https://github.com/blockmineJS/blockmine/commit/c33e7c895b3daf60bcde187cf1334ab38bb71592))
|
|
23
|
+
|
|
24
|
+
## [1.18.0](https://github.com/blockmineJS/blockmine/compare/v1.17.1...v1.18.0) (2025-07-26)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### ✨ Новые возможности
|
|
28
|
+
|
|
29
|
+
* добавлена новая кнопка - "Предложить улучшение". Поможет адептам составить свой запрос ([d741881](https://github.com/blockmineJS/blockmine/commit/d7418813e53d15fcd16c0517cea033d019ed355b))
|
|
30
|
+
* добавлено глубокое объединение настроек для плагинов и улучшена установка зависимостей ([452af4b](https://github.com/blockmineJS/blockmine/commit/452af4b67325f3faebe12c136a40f77515e805c0))
|
|
31
|
+
|
|
4
32
|
### [1.17.1](https://github.com/blockmineJS/blockmine/compare/v1.17.0...v1.17.1) (2025-07-24)
|
|
5
33
|
|
|
6
34
|
|
package/backend/package.json
CHANGED
|
@@ -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');
|
|
@@ -522,7 +523,7 @@ router.get('/:botId/plugins/:pluginId/settings', authorize('plugin:settings:view
|
|
|
522
523
|
}
|
|
523
524
|
}
|
|
524
525
|
|
|
525
|
-
const finalSettings =
|
|
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:
|
|
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
|
-
|
|
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) => {
|
|
@@ -528,8 +528,18 @@ class BotManager {
|
|
|
528
528
|
this.bots.delete(botId);
|
|
529
529
|
this.resourceUsage.delete(botId);
|
|
530
530
|
this.botConfigs.delete(botId);
|
|
531
|
+
|
|
531
532
|
this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
|
|
532
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
|
+
}
|
|
533
543
|
});
|
|
534
544
|
|
|
535
545
|
this.bots.set(botConfig.id, child);
|
|
@@ -627,8 +627,11 @@ process.on('message', async (message) => {
|
|
|
627
627
|
clearTimeout(connectionTimeout);
|
|
628
628
|
connectionTimeout = null;
|
|
629
629
|
}
|
|
630
|
+
const restartableReasons = ['socketClosed', 'keepAliveError'];
|
|
631
|
+
const exitCode = restartableReasons.includes(reason) ? 1 : 0;
|
|
632
|
+
|
|
630
633
|
sendLog(`[Event: end] Отключен от сервера. Причина: ${reason}`);
|
|
631
|
-
process.exit(
|
|
634
|
+
process.exit(exitCode);
|
|
632
635
|
});
|
|
633
636
|
|
|
634
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 };
|