blockmine 1.19.0 → 1.20.0
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/.claude/settings.local.json +11 -0
- package/CHANGELOG.md +16 -0
- package/backend/src/api/routes/bots.js +44 -53
- package/backend/src/api/routes/pluginIde.js +6 -2
- package/backend/src/core/BotManager.js +34 -5
- package/backend/src/core/BotProcess.js +1 -2
- package/backend/src/core/GraphExecutionEngine.js +12 -1
- package/backend/src/core/NodeRegistry.js +2 -1
- package/frontend/dist/assets/{index-5m_JZxJ-.js → index-CMMutadc.js} +33 -33
- package/frontend/dist/index.html +1 -1
- package/nul +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
# История версий
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## [1.20.0](https://github.com/blockmineJS/blockmine/compare/v1.19.1...v1.20.0) (2025-10-23)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
### ✨ Новые возможности
|
|
8
|
+
|
|
9
|
+
* если бот падает 5 раз с критической ошибкой, то перезапуск отменяется ([2ba022f](https://github.com/blockmineJS/blockmine/commit/2ba022fff038cc83bb1edfa04d3edf97fc76c255))
|
|
10
|
+
* action:send_message to support dynamic variables. example. Сообщение: привет, {username} ([457639d](https://github.com/blockmineJS/blockmine/commit/457639d70e7a8933f0508d93196a2be6951f1a0a))
|
|
11
|
+
|
|
12
|
+
### [1.19.1](https://github.com/blockmineJS/blockmine/compare/v1.19.0...v1.19.1) (2025-10-01)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🐛 Исправления
|
|
16
|
+
|
|
17
|
+
* изменение порядка ботов теперь корректное ([9663737](https://github.com/blockmineJS/blockmine/commit/9663737505201468dd2233bf5658be5b2442f583))
|
|
18
|
+
* фиксим новой вкладки прокси ([7ebdb7d](https://github.com/blockmineJS/blockmine/commit/7ebdb7d0df6c4e5cd6a9b15576cc1907ab7fcd74))
|
|
19
|
+
|
|
4
20
|
## [1.19.0](https://github.com/blockmineJS/blockmine/compare/v1.18.5...v1.19.0) (2025-08-23)
|
|
5
21
|
|
|
6
22
|
|
|
@@ -136,11 +136,20 @@ router.get('/', conditionalListAuth, async (req, res) => {
|
|
|
136
136
|
|
|
137
137
|
if (botsWithoutSortOrder.length > 0) {
|
|
138
138
|
console.log(`[API] Обновляем sortOrder для ${botsWithoutSortOrder.length} ботов`);
|
|
139
|
+
|
|
140
|
+
const maxSortOrder = await prisma.bot.aggregate({
|
|
141
|
+
_max: { sortOrder: true }
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
let nextSortOrder = (maxSortOrder._max.sortOrder || 0) + 1;
|
|
145
|
+
|
|
139
146
|
for (const bot of botsWithoutSortOrder) {
|
|
140
147
|
await prisma.bot.update({
|
|
141
148
|
where: { id: bot.id },
|
|
142
|
-
data: { sortOrder:
|
|
149
|
+
data: { sortOrder: nextSortOrder }
|
|
143
150
|
});
|
|
151
|
+
console.log(`[API] Установлен sortOrder ${nextSortOrder} для бота ${bot.id}`);
|
|
152
|
+
nextSortOrder++;
|
|
144
153
|
}
|
|
145
154
|
}
|
|
146
155
|
|
|
@@ -236,7 +245,7 @@ router.put('/bulk-proxy-update', authenticate, authorize('bot:update'), async (r
|
|
|
236
245
|
const encryptedSettings = {
|
|
237
246
|
proxyHost: proxySettings.proxyHost.trim(),
|
|
238
247
|
proxyPort: parseInt(proxySettings.proxyPort),
|
|
239
|
-
proxyUsername: proxySettings.proxyUsername ?
|
|
248
|
+
proxyUsername: proxySettings.proxyUsername ? proxySettings.proxyUsername.trim() : null,
|
|
240
249
|
proxyPassword: proxySettings.proxyPassword ? encrypt(proxySettings.proxyPassword) : null
|
|
241
250
|
};
|
|
242
251
|
|
|
@@ -465,74 +474,56 @@ router.put('/:id', authenticate, checkBotAccess, authorize('bot:update'), async
|
|
|
465
474
|
|
|
466
475
|
router.put('/:id/sort-order', authenticate, checkBotAccess, authorize('bot:update'), async (req, res) => {
|
|
467
476
|
try {
|
|
468
|
-
const { newPosition } = req.body;
|
|
477
|
+
const { newPosition, oldIndex, newIndex } = req.body;
|
|
469
478
|
const botId = parseInt(req.params.id, 10);
|
|
470
479
|
|
|
471
|
-
console.log(`[API] Запрос на изменение порядка бота ${botId}
|
|
480
|
+
console.log(`[API] Запрос на изменение порядка бота ${botId}: oldIndex=${oldIndex}, newIndex=${newIndex}, newPosition=${newPosition}`);
|
|
472
481
|
|
|
473
|
-
if (isNaN(botId)
|
|
474
|
-
console.log(`[API]
|
|
475
|
-
return res.status(400).json({ error: '
|
|
482
|
+
if (isNaN(botId)) {
|
|
483
|
+
console.log(`[API] Неверный botId: ${botId}`);
|
|
484
|
+
return res.status(400).json({ error: 'Неверный ID бота' });
|
|
476
485
|
}
|
|
477
486
|
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
select: { sortOrder: true }
|
|
487
|
+
const allBots = await prisma.bot.findMany({
|
|
488
|
+
orderBy: { sortOrder: 'asc' },
|
|
489
|
+
select: { id: true, sortOrder: true }
|
|
481
490
|
});
|
|
482
491
|
|
|
483
|
-
|
|
492
|
+
console.log(`[API] Всего ботов: ${allBots.length}`);
|
|
493
|
+
const currentBotIndex = allBots.findIndex(bot => bot.id === botId);
|
|
494
|
+
if (currentBotIndex === -1) {
|
|
484
495
|
console.log(`[API] Бот ${botId} не найден`);
|
|
485
496
|
return res.status(404).json({ error: 'Бот не найден' });
|
|
486
497
|
}
|
|
487
498
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
499
|
+
if (newIndex < 0 || newIndex >= allBots.length) {
|
|
500
|
+
console.log(`[API] Неверная новая позиция: ${newIndex}`);
|
|
501
|
+
return res.status(400).json({ error: 'Неверная позиция' });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (currentBotIndex === newIndex) {
|
|
492
505
|
console.log(`[API] Позиция не изменилась для бота ${botId}`);
|
|
493
506
|
return res.json({ success: true, message: 'Позиция не изменилась' });
|
|
494
507
|
}
|
|
508
|
+
const reorderedBots = [...allBots];
|
|
509
|
+
const [movedBot] = reorderedBots.splice(currentBotIndex, 1);
|
|
510
|
+
reorderedBots.splice(newIndex, 0, movedBot);
|
|
495
511
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
});
|
|
511
|
-
console.log(`[API] Обновлено ${updateResult.count} ботов при перемещении вниз`);
|
|
512
|
-
} else {
|
|
513
|
-
console.log(`[API] Перемещаем бота ${botId} вверх с позиции ${currentPosition} на ${newPosition}`);
|
|
514
|
-
const updateResult = await prisma.bot.updateMany({
|
|
515
|
-
where: {
|
|
516
|
-
sortOrder: {
|
|
517
|
-
gte: newPosition,
|
|
518
|
-
lt: currentPosition
|
|
519
|
-
}
|
|
520
|
-
},
|
|
521
|
-
data: {
|
|
522
|
-
sortOrder: {
|
|
523
|
-
increment: 1
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
console.log(`[API] Обновлено ${updateResult.count} ботов при перемещении вверх`);
|
|
512
|
+
console.log(`[API] Обновляем порядок для всех ботов`);
|
|
513
|
+
for (let i = 0; i < reorderedBots.length; i++) {
|
|
514
|
+
const bot = reorderedBots[i];
|
|
515
|
+
const newSortOrder = i + 1; // 1-based позиции
|
|
516
|
+
|
|
517
|
+
if (bot.sortOrder !== newSortOrder) {
|
|
518
|
+
await prisma.bot.update({
|
|
519
|
+
where: { id: bot.id },
|
|
520
|
+
data: { sortOrder: newSortOrder }
|
|
521
|
+
});
|
|
522
|
+
console.log(`[API] Обновлен бот ${bot.id}: sortOrder ${bot.sortOrder} -> ${newSortOrder}`);
|
|
523
|
+
}
|
|
528
524
|
}
|
|
529
525
|
|
|
530
|
-
|
|
531
|
-
where: { id: botId },
|
|
532
|
-
data: { sortOrder: newPosition }
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
console.log(`[API] Успешно обновлен порядок бота ${botId} на позицию ${newPosition}`);
|
|
526
|
+
console.log(`[API] Успешно обновлен порядок бота ${botId}`);
|
|
536
527
|
res.json({ success: true, message: 'Порядок ботов обновлен' });
|
|
537
528
|
} catch (error) {
|
|
538
529
|
console.error("[API Error] /bots sort-order PUT:", error);
|
|
@@ -588,6 +588,10 @@ router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
|
|
|
588
588
|
if (!branch) {
|
|
589
589
|
return res.status(400).json({ error: 'Название ветки обязательно.' });
|
|
590
590
|
}
|
|
591
|
+
// Validate branch name: only allow letters, numbers, dashes, underscores, dots, slashes
|
|
592
|
+
if (!branch.match(/^[\w\-.\/]+$/)) {
|
|
593
|
+
return res.status(400).json({ error: 'Некорректное имя ветки.' });
|
|
594
|
+
}
|
|
591
595
|
|
|
592
596
|
try {
|
|
593
597
|
cp.execSync('git --version');
|
|
@@ -675,10 +679,10 @@ router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
|
|
|
675
679
|
|
|
676
680
|
|
|
677
681
|
if (branchExists) {
|
|
678
|
-
cp.
|
|
682
|
+
cp.execFileSync('git', ['push', 'origin', branch, '--force']);
|
|
679
683
|
console.log(`[Plugin IDE] Ветка ${branch} обновлена`);
|
|
680
684
|
} else {
|
|
681
|
-
cp.
|
|
685
|
+
cp.execFileSync('git', ['push', '-u', 'origin', branch]);
|
|
682
686
|
console.log(`[Plugin IDE] Новая ветка ${branch} создана`);
|
|
683
687
|
}
|
|
684
688
|
|
|
@@ -52,6 +52,7 @@ class BotManager {
|
|
|
52
52
|
this.playerListCache = new Map();
|
|
53
53
|
this.eventGraphManager = null;
|
|
54
54
|
this.uiSubscriptions = new Map();
|
|
55
|
+
this.crashCounters = new Map();
|
|
55
56
|
|
|
56
57
|
getInstanceId();
|
|
57
58
|
setInterval(() => this.updateAllResourceUsage(), 5000);
|
|
@@ -390,11 +391,16 @@ class BotManager {
|
|
|
390
391
|
if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
|
|
391
392
|
|
|
392
393
|
if (decryptedConfig.proxyUsername) decryptedConfig.proxyUsername = decryptedConfig.proxyUsername.trim();
|
|
393
|
-
if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decryptedConfig.proxyPassword.trim();
|
|
394
394
|
|
|
395
395
|
const fullBotConfig = { ...decryptedConfig, plugins: sortedPlugins };
|
|
396
396
|
const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
|
|
397
|
-
const child = fork(botProcessPath, [], {
|
|
397
|
+
const child = fork(botProcessPath, [], {
|
|
398
|
+
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
399
|
+
env: {
|
|
400
|
+
...process.env,
|
|
401
|
+
NODE_PATH: path.resolve(__dirname, '../../../node_modules')
|
|
402
|
+
}
|
|
403
|
+
});
|
|
398
404
|
|
|
399
405
|
child.botConfig = botConfig;
|
|
400
406
|
|
|
@@ -441,6 +447,7 @@ class BotManager {
|
|
|
441
447
|
break;
|
|
442
448
|
case 'bot_ready':
|
|
443
449
|
this.emitStatusUpdate(botId, 'running', 'Бот успешно подключился к серверу.');
|
|
450
|
+
this.crashCounters.delete(botId);
|
|
444
451
|
break;
|
|
445
452
|
case 'validate_and_run_command':
|
|
446
453
|
await this.handleCommandValidation(botConfig, message);
|
|
@@ -528,13 +535,35 @@ class BotManager {
|
|
|
528
535
|
this.bots.delete(botId);
|
|
529
536
|
this.resourceUsage.delete(botId);
|
|
530
537
|
this.botConfigs.delete(botId);
|
|
531
|
-
|
|
538
|
+
|
|
532
539
|
this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
|
|
533
540
|
this.updateAllResourceUsage();
|
|
534
541
|
|
|
535
542
|
if (code === 1) {
|
|
536
|
-
|
|
537
|
-
|
|
543
|
+
const MAX_RESTART_ATTEMPTS = 5;
|
|
544
|
+
const RESTART_COOLDOWN = 60000;
|
|
545
|
+
|
|
546
|
+
const counter = this.crashCounters.get(botId) || { count: 0, firstCrash: Date.now() };
|
|
547
|
+
const timeSinceFirstCrash = Date.now() - counter.firstCrash;
|
|
548
|
+
|
|
549
|
+
if (timeSinceFirstCrash > RESTART_COOLDOWN) {
|
|
550
|
+
counter.count = 0;
|
|
551
|
+
counter.firstCrash = Date.now();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
counter.count++;
|
|
555
|
+
this.crashCounters.set(botId, counter);
|
|
556
|
+
|
|
557
|
+
if (counter.count >= MAX_RESTART_ATTEMPTS) {
|
|
558
|
+
console.log(`[BotManager] Бот ${botId} упал ${counter.count} раз подряд. Автоперезапуск остановлен.`);
|
|
559
|
+
this.appendLog(botId, `[SYSTEM] ❌ Обнаружено ${counter.count} критических ошибок подряд.`);
|
|
560
|
+
this.appendLog(botId, `[SYSTEM] 💡 Исправьте проблему и запустите бота вручную.`);
|
|
561
|
+
this.crashCounters.delete(botId);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.log(`[BotManager] Обнаружена ошибка с кодом 1 для бота ${botId}. Попытка ${counter.count}/${MAX_RESTART_ATTEMPTS}. Перезапуск через 5 секунд...`);
|
|
566
|
+
this.appendLog(botId, `[SYSTEM] Обнаружена критическая ошибка, перезапуск через 5 секунд... (попытка ${counter.count}/${MAX_RESTART_ATTEMPTS})`);
|
|
538
567
|
setTimeout(() => {
|
|
539
568
|
console.log(`[BotManager] Перезапуск бота ${botId}...`);
|
|
540
569
|
this.startBot(botConfig);
|
|
@@ -187,9 +187,8 @@ process.on('message', async (message) => {
|
|
|
187
187
|
if (config.proxyHost && config.proxyPort) {
|
|
188
188
|
sendLog(`[System] Используется прокси: ${config.proxyHost}:${config.proxyPort}`);
|
|
189
189
|
|
|
190
|
-
// Очищаем пароль от лишних символов
|
|
191
|
-
const cleanProxyPassword = config.proxyPassword ? config.proxyPassword.trim() : null;
|
|
192
190
|
const cleanProxyUsername = config.proxyUsername ? config.proxyUsername.trim() : null;
|
|
191
|
+
const cleanProxyPassword = config.proxyPassword || null;
|
|
193
192
|
|
|
194
193
|
botOptions.connect = (client) => {
|
|
195
194
|
SocksClient.createConnection({
|
|
@@ -129,9 +129,20 @@ class GraphExecutionEngine {
|
|
|
129
129
|
break;
|
|
130
130
|
}
|
|
131
131
|
case 'action:send_message': {
|
|
132
|
-
|
|
132
|
+
let message = String(await this.resolvePinValue(node, 'message', ''));
|
|
133
133
|
const chatType = await this.resolvePinValue(node, 'chat_type', this.context.typeChat);
|
|
134
134
|
const recipient = await this.resolvePinValue(node, 'recipient', this.context.user?.username);
|
|
135
|
+
|
|
136
|
+
// Парсим и заменяем переменные в формате {varName}
|
|
137
|
+
const variablePattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
138
|
+
const matches = [...message.matchAll(variablePattern)];
|
|
139
|
+
|
|
140
|
+
for (const match of matches) {
|
|
141
|
+
const varName = match[1];
|
|
142
|
+
const varValue = await this.resolvePinValue(node, `var_${varName}`, '');
|
|
143
|
+
message = message.replace(match[0], String(varValue));
|
|
144
|
+
}
|
|
145
|
+
|
|
135
146
|
this.context.bot.sendMessage(chatType, message, recipient);
|
|
136
147
|
await this.traverse(node, 'exec');
|
|
137
148
|
break;
|
|
@@ -299,8 +299,9 @@ class NodeRegistry {
|
|
|
299
299
|
type: 'action:send_message',
|
|
300
300
|
label: '🗣️ Отправить сообщение',
|
|
301
301
|
category: 'Действия',
|
|
302
|
-
description: 'Отправляет сообщение в
|
|
302
|
+
description: 'Отправляет сообщение в чат. Поддерживает переменные в формате {varName}',
|
|
303
303
|
graphType: all,
|
|
304
|
+
dynamicPins: true,
|
|
304
305
|
pins: {
|
|
305
306
|
inputs: [
|
|
306
307
|
{ id: 'exec', name: 'Выполнить', type: 'Exec', required: true },
|