blockmine 1.4.8 → 1.5.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/README.md +26 -19
- package/backend/package.json +5 -0
- package/backend/prisma/migrations/20250627144030_add_visual_editor_fields/migration.sql +26 -0
- package/backend/prisma/migrations/20250628113254_add_event_graphs/migration.sql +14 -0
- package/backend/prisma/migrations/20250628122517_added_eventgraph_name/migration.sql +26 -0
- package/backend/prisma/migrations/20250628122710_complex_events/migration.sql +36 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +45 -14
- package/backend/src/api/routes/bots.js +530 -286
- package/backend/src/api/routes/eventGraphs.js +375 -0
- package/backend/src/api/routes/plugins.js +5 -3
- package/backend/src/core/BotManager.js +297 -170
- package/backend/src/core/BotProcess.js +312 -44
- package/backend/src/core/EventGraphManager.js +164 -0
- package/backend/src/core/GraphExecutionEngine.js +706 -0
- package/backend/src/core/NodeRegistry.js +888 -0
- package/backend/src/core/PluginManager.js +12 -2
- package/backend/src/core/UserService.js +15 -2
- package/backend/src/core/services.js +12 -0
- package/backend/src/core/system/CommandManager.js +3 -1
- package/backend/src/lib/logger.js +15 -0
- package/backend/src/server.js +12 -4
- package/ecosystem.config.js +11 -0
- package/frontend/dist/assets/index-CY4JKfFL.js +8203 -0
- package/frontend/dist/assets/index-DC4RjP6E.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package.json +4 -0
- package/image/visualcommand.png +0 -0
- package/package.json +8 -2
- package/test_visual_command.json +9 -0
- package/frontend/dist/assets/index-CLCxr_rh.js +0 -8179
- package/frontend/dist/assets/index-Dk9CeSuD.css +0 -1
|
@@ -10,17 +10,17 @@ const os = require('os');
|
|
|
10
10
|
const { v4: uuidv4 } = require('uuid');
|
|
11
11
|
const crypto = require('crypto');
|
|
12
12
|
const { decrypt } = require('./utils/crypto');
|
|
13
|
+
const EventGraphManager = require('./EventGraphManager');
|
|
14
|
+
const nodeRegistry = require('./NodeRegistry');
|
|
15
|
+
const GraphExecutionEngine = require('./GraphExecutionEngine');
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
const RealPermissionManager = require('./PermissionManager');
|
|
16
|
-
const RealUserService = require('./UserService');
|
|
17
|
+
const UserService = require('./UserService');
|
|
17
18
|
|
|
18
19
|
const prisma = new PrismaClient();
|
|
19
20
|
const cooldowns = new Map();
|
|
20
21
|
const warningCache = new Map();
|
|
21
22
|
const WARNING_COOLDOWN = 10 * 1000;
|
|
22
23
|
|
|
23
|
-
|
|
24
24
|
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
25
25
|
let instanceId = null;
|
|
26
26
|
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
@@ -45,22 +45,31 @@ function getInstanceId() {
|
|
|
45
45
|
return instanceId;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
|
|
49
48
|
class BotManager {
|
|
50
49
|
constructor() {
|
|
51
50
|
this.bots = new Map();
|
|
52
51
|
this.logCache = new Map();
|
|
53
52
|
this.resourceUsage = new Map();
|
|
54
53
|
this.botConfigs = new Map();
|
|
54
|
+
this.nodeRegistry = nodeRegistry;
|
|
55
|
+
this.pendingPlayerListRequests = new Map();
|
|
56
|
+
this.playerListCache = new Map();
|
|
57
|
+
this.graphEngine = new GraphExecutionEngine(this.nodeRegistry, this);
|
|
58
|
+
this.eventGraphManager = null;
|
|
55
59
|
|
|
56
60
|
getInstanceId();
|
|
57
|
-
|
|
58
61
|
setInterval(() => this.updateAllResourceUsage(), 5000);
|
|
59
62
|
if (config.telemetry?.enabled) {
|
|
60
63
|
setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
|
|
67
|
+
initialize() {
|
|
68
|
+
if (!this.eventGraphManager) {
|
|
69
|
+
this.eventGraphManager = new EventGraphManager(this);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
async loadConfigForBot(botId) {
|
|
65
74
|
console.log(`[BotManager] Caching configuration for bot ID ${botId}...`);
|
|
66
75
|
try {
|
|
@@ -79,7 +88,6 @@ class BotManager {
|
|
|
79
88
|
config.commandAliases.set(alias, cmd.name);
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
|
-
|
|
83
91
|
this.botConfigs.set(botId, config);
|
|
84
92
|
console.log(`[BotManager] Configuration for bot ID ${botId} cached successfully.`);
|
|
85
93
|
return config;
|
|
@@ -88,6 +96,10 @@ class BotManager {
|
|
|
88
96
|
throw new Error(`Failed to load/cache bot configuration for botId ${botId}: ${error.message}`);
|
|
89
97
|
}
|
|
90
98
|
}
|
|
99
|
+
|
|
100
|
+
async _ensureDefaultEventGraphs(botId) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
91
103
|
|
|
92
104
|
invalidateConfigCache(botId) {
|
|
93
105
|
if (this.botConfigs.has(botId)) {
|
|
@@ -102,7 +114,6 @@ class BotManager {
|
|
|
102
114
|
if (child && !child.killed) {
|
|
103
115
|
child.send({ type: 'config:reload' });
|
|
104
116
|
console.log(`[BotManager] Sent config:reload to bot process ${botId}`);
|
|
105
|
-
|
|
106
117
|
getIO().emit('bot:config_reloaded', { botId });
|
|
107
118
|
}
|
|
108
119
|
}
|
|
@@ -117,45 +128,22 @@ class BotManager {
|
|
|
117
128
|
}, 3000);
|
|
118
129
|
}
|
|
119
130
|
|
|
120
|
-
|
|
121
|
-
_sendThrottledWarning(botId, username, warningType, message, typeChat = 'private') {
|
|
122
|
-
const cacheKey = `${botId}:${username}:${warningType}`;
|
|
123
|
-
const now = Date.now();
|
|
124
|
-
const lastWarning = warningCache.get(cacheKey);
|
|
125
|
-
|
|
126
|
-
if (!lastWarning || (now - lastWarning > WARNING_COOLDOWN)) {
|
|
127
|
-
this.sendMessageToBot(botId, message, typeChat, username);
|
|
128
|
-
warningCache.set(cacheKey, now);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
131
|
async sendHeartbeat() {
|
|
134
|
-
if (!config.telemetry?.enabled) return;
|
|
135
|
-
if (!instanceId) return;
|
|
136
|
-
|
|
132
|
+
if (!config.telemetry?.enabled || !instanceId) return;
|
|
137
133
|
try {
|
|
138
|
-
const runningBots =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
134
|
+
const runningBots = Array.from(this.bots.values())
|
|
135
|
+
.filter(p => p.botConfig)
|
|
136
|
+
.map(p => ({
|
|
137
|
+
username: p.botConfig.username,
|
|
138
|
+
serverHost: p.botConfig.server.host,
|
|
139
|
+
serverPort: p.botConfig.server.port
|
|
140
|
+
}));
|
|
148
141
|
|
|
149
|
-
if (runningBots.length === 0)
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
142
|
+
if (runningBots.length === 0) return;
|
|
153
143
|
|
|
154
144
|
const challengeRes = await fetch(`${STATS_SERVER_URL}/api/challenge?uuid=${instanceId}`);
|
|
155
|
-
if (!challengeRes.ok) {
|
|
156
|
-
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
145
|
+
if (!challengeRes.ok) throw new Error(`Challenge server error: ${challengeRes.statusText}`);
|
|
146
|
+
|
|
159
147
|
const { challenge, difficulty, prefix } = await challengeRes.json();
|
|
160
148
|
let nonce = 0;
|
|
161
149
|
let hash = '';
|
|
@@ -180,7 +168,6 @@ class BotManager {
|
|
|
180
168
|
}
|
|
181
169
|
}
|
|
182
170
|
|
|
183
|
-
|
|
184
171
|
async _syncSystemPermissions(botId) {
|
|
185
172
|
const systemPermissions = [
|
|
186
173
|
{ name: "admin.*", description: "Все права администратора" },
|
|
@@ -195,7 +182,6 @@ class BotManager {
|
|
|
195
182
|
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
|
|
196
183
|
};
|
|
197
184
|
console.log(`[Permission Sync] Синхронизация системных прав для бота ID ${botId}...`);
|
|
198
|
-
|
|
199
185
|
for (const perm of systemPermissions) {
|
|
200
186
|
await prisma.permission.upsert({
|
|
201
187
|
where: { botId_name: { botId, name: perm.name } },
|
|
@@ -203,7 +189,6 @@ class BotManager {
|
|
|
203
189
|
create: { ...perm, botId, owner: 'system' }
|
|
204
190
|
});
|
|
205
191
|
}
|
|
206
|
-
|
|
207
192
|
for (const groupName of systemGroups) {
|
|
208
193
|
await prisma.group.upsert({
|
|
209
194
|
where: { botId_name: { botId, name: groupName } },
|
|
@@ -211,7 +196,6 @@ class BotManager {
|
|
|
211
196
|
create: { name: groupName, botId, owner: 'system' }
|
|
212
197
|
});
|
|
213
198
|
}
|
|
214
|
-
|
|
215
199
|
for (const [groupName, permNames] of Object.entries(systemGroupPermissions)) {
|
|
216
200
|
const group = await prisma.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
|
|
217
201
|
if (group) {
|
|
@@ -238,13 +222,11 @@ class BotManager {
|
|
|
238
222
|
}
|
|
239
223
|
return;
|
|
240
224
|
}
|
|
241
|
-
|
|
242
225
|
const pids = Array.from(this.bots.values()).map(child => child.pid).filter(Boolean);
|
|
243
226
|
if (pids.length === 0) return;
|
|
244
227
|
try {
|
|
245
228
|
const stats = await pidusage(pids);
|
|
246
229
|
const usageData = [];
|
|
247
|
-
|
|
248
230
|
for (const pid in stats) {
|
|
249
231
|
if (!stats[pid]) continue;
|
|
250
232
|
const botId = this.getBotIdByPid(parseInt(pid, 10));
|
|
@@ -259,8 +241,7 @@ class BotManager {
|
|
|
259
241
|
}
|
|
260
242
|
}
|
|
261
243
|
getIO().emit('bots:usage', usageData);
|
|
262
|
-
} catch (error) {
|
|
263
|
-
}
|
|
244
|
+
} catch (error) {}
|
|
264
245
|
}
|
|
265
246
|
|
|
266
247
|
getBotIdByPid(pid) {
|
|
@@ -287,9 +268,7 @@ class BotManager {
|
|
|
287
268
|
}
|
|
288
269
|
|
|
289
270
|
emitStatusUpdate(botId, status, message = null) {
|
|
290
|
-
if (message) {
|
|
291
|
-
this.appendLog(botId, `[SYSTEM] ${message}`);
|
|
292
|
-
}
|
|
271
|
+
if (message) this.appendLog(botId, `[SYSTEM] ${message}`);
|
|
293
272
|
getIO().emit('bot:status', { botId, status, message });
|
|
294
273
|
}
|
|
295
274
|
|
|
@@ -301,13 +280,9 @@ class BotManager {
|
|
|
301
280
|
}
|
|
302
281
|
|
|
303
282
|
async startBot(botConfig) {
|
|
304
|
-
if (this.bots.has(botConfig.id)) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
console.error(`[BotManager] Попытка повторного запуска уже работающего бота ID: ${botConfig.id}. Запуск отменен.`);
|
|
308
|
-
this.appendLog(botConfig.id, `[SYSTEM-ERROR] Попытка повторного запуска. Запуск отменен.`);
|
|
309
|
-
return { success: false, message: 'Бот уже запущен или запускается.' };
|
|
310
|
-
}
|
|
283
|
+
if (this.bots.has(botConfig.id) && !this.bots.get(botConfig.id).killed) {
|
|
284
|
+
this.appendLog(botConfig.id, `[SYSTEM-ERROR] Попытка повторного запуска. Запуск отменен.`);
|
|
285
|
+
return { success: false, message: 'Бот уже запущен или запускается.' };
|
|
311
286
|
}
|
|
312
287
|
|
|
313
288
|
await this._syncSystemPermissions(botConfig.id);
|
|
@@ -315,98 +290,131 @@ class BotManager {
|
|
|
315
290
|
this.logCache.set(botConfig.id, []);
|
|
316
291
|
this.emitStatusUpdate(botConfig.id, 'starting', '');
|
|
317
292
|
|
|
318
|
-
const allPluginsForBot = await prisma.installedPlugin.findMany({
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const enabledPlugins = allPluginsForBot.filter(p => p.isEnabled);
|
|
322
|
-
|
|
323
|
-
const { sortedPlugins, pluginInfo, hasCriticalIssues } = DependencyService.resolveDependencies(enabledPlugins, allPluginsForBot);
|
|
293
|
+
const allPluginsForBot = await prisma.installedPlugin.findMany({ where: { botId: botConfig.id, isEnabled: true } });
|
|
294
|
+
const { sortedPlugins, hasCriticalIssues, pluginInfo } = DependencyService.resolveDependencies(allPluginsForBot, allPluginsForBot);
|
|
295
|
+
|
|
324
296
|
if (hasCriticalIssues) {
|
|
325
|
-
this.appendLog(botConfig.id, '[DependencyManager] Обнаружены критические проблемы с
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
297
|
+
this.appendLog(botConfig.id, '[DependencyManager] Обнаружены критические проблемы с зависимостями, запуск отменен.');
|
|
298
|
+
|
|
299
|
+
const criticalIssueTypes = new Set(['missing_dependency', 'version_mismatch', 'circular_dependency']);
|
|
300
|
+
|
|
301
|
+
for (const pluginId in pluginInfo) {
|
|
302
|
+
const info = pluginInfo[pluginId];
|
|
303
|
+
if (info.issues.length === 0) continue;
|
|
304
|
+
|
|
305
|
+
const criticalIssues = info.issues.filter(issue => criticalIssueTypes.has(issue.type));
|
|
306
|
+
|
|
307
|
+
if (criticalIssues.length > 0) {
|
|
308
|
+
this.appendLog(botConfig.id, `* Плагин "${info.name}":`);
|
|
309
|
+
for (const issue of criticalIssues) {
|
|
310
|
+
this.appendLog(botConfig.id, ` - ${issue.message}`);
|
|
332
311
|
}
|
|
333
312
|
}
|
|
334
313
|
}
|
|
335
|
-
|
|
314
|
+
|
|
336
315
|
this.emitStatusUpdate(botConfig.id, 'stopped', 'Ошибка зависимостей плагинов.');
|
|
337
316
|
return { success: false, message: 'Критические ошибки в зависимостях плагинов.' };
|
|
338
317
|
}
|
|
339
318
|
|
|
340
319
|
const decryptedConfig = { ...botConfig };
|
|
341
|
-
if (decryptedConfig.password)
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
if (decryptedConfig.proxyPassword) {
|
|
345
|
-
decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
|
|
346
|
-
}
|
|
320
|
+
if (decryptedConfig.password) decryptedConfig.password = decrypt(decryptedConfig.password);
|
|
321
|
+
if (decryptedConfig.proxyPassword) decryptedConfig.proxyPassword = decrypt(decryptedConfig.proxyPassword);
|
|
347
322
|
|
|
348
323
|
const fullBotConfig = { ...decryptedConfig, plugins: sortedPlugins };
|
|
349
324
|
const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
|
|
350
325
|
const child = fork(botProcessPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
|
|
351
326
|
|
|
352
327
|
child.botConfig = botConfig;
|
|
353
|
-
|
|
354
|
-
this.appendLog(botConfig.id, `[PROCESS FATAL] КРИТИЧЕСКАЯ ОШИБКА ПРОЦЕССА: ${err.stack}`);
|
|
355
|
-
});
|
|
356
|
-
child.stderr.on('data', (data) => {
|
|
357
|
-
this.appendLog(botConfig.id, `[STDERR] ${data.toString()}`);
|
|
358
|
-
});
|
|
328
|
+
|
|
359
329
|
child.on('message', async (message) => {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
} else if (message.type === 'register_command') {
|
|
367
|
-
await this.handleCommandRegistration(botConfig.id, message.commandConfig);
|
|
368
|
-
} else if (message.type === 'request_user_action') {
|
|
369
|
-
const { requestId, payload } = message;
|
|
370
|
-
const { targetUsername, action, data } = payload;
|
|
371
|
-
const botId = botConfig.id;
|
|
372
|
-
|
|
373
|
-
try {
|
|
374
|
-
const targetUser = await RealUserService.getUser(targetUsername, botId);
|
|
375
|
-
let replyPayload = {};
|
|
376
|
-
|
|
377
|
-
switch (action) {
|
|
378
|
-
case 'toggle_blacklist': {
|
|
379
|
-
const isCurrentlyBlacklisted = targetUser.isBlacklisted;
|
|
380
|
-
await targetUser.setBlacklist(!isCurrentlyBlacklisted);
|
|
381
|
-
replyPayload.newStatus = !isCurrentlyBlacklisted;
|
|
382
|
-
break;
|
|
330
|
+
const botId = botConfig.id;
|
|
331
|
+
try {
|
|
332
|
+
switch (message.type) {
|
|
333
|
+
case 'event':
|
|
334
|
+
if (this.eventGraphManager) {
|
|
335
|
+
this.eventGraphManager.handleEvent(botId, message.eventType, message.args);
|
|
383
336
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
337
|
+
break;
|
|
338
|
+
case 'log':
|
|
339
|
+
this.appendLog(botId, message.content);
|
|
340
|
+
break;
|
|
341
|
+
case 'status':
|
|
342
|
+
this.emitStatusUpdate(botId, message.status);
|
|
343
|
+
break;
|
|
344
|
+
case 'validate_and_run_command':
|
|
345
|
+
await this.handleCommandValidation(botConfig, message);
|
|
346
|
+
break;
|
|
347
|
+
case 'register_command':
|
|
348
|
+
await this.handleCommandRegistration(botId, message.commandConfig);
|
|
349
|
+
break;
|
|
350
|
+
case 'register_group':
|
|
351
|
+
await this.handleGroupRegistration(botId, message.groupConfig);
|
|
352
|
+
break;
|
|
353
|
+
case 'request_user_action':
|
|
354
|
+
const { requestId, payload } = message;
|
|
355
|
+
const { targetUsername, action, data } = payload;
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
const user = await UserService.getUser(targetUsername, botConfig.id);
|
|
359
|
+
if (!user) throw new Error(`Пользователь ${targetUsername} не найден.`);
|
|
360
|
+
|
|
361
|
+
let result;
|
|
362
|
+
|
|
363
|
+
switch (action) {
|
|
364
|
+
case 'addGroup':
|
|
365
|
+
result = await user.addGroup(data.group);
|
|
366
|
+
break;
|
|
367
|
+
case 'removeGroup':
|
|
368
|
+
result = await user.removeGroup(data.group);
|
|
369
|
+
break;
|
|
370
|
+
case 'addPermission':
|
|
371
|
+
break;
|
|
372
|
+
case 'removePermission':
|
|
373
|
+
break;
|
|
374
|
+
case 'getGroups':
|
|
375
|
+
result = user.groups;
|
|
376
|
+
break;
|
|
377
|
+
case 'getPermissions':
|
|
378
|
+
result = Array.from(user.permissionsSet);
|
|
379
|
+
break;
|
|
380
|
+
case 'isBlacklisted':
|
|
381
|
+
result = user.isBlacklisted;
|
|
382
|
+
break;
|
|
383
|
+
case 'setBlacklisted':
|
|
384
|
+
result = await user.setBlacklist(data.value);
|
|
385
|
+
break;
|
|
386
|
+
default:
|
|
387
|
+
throw new Error(`Неизвестное действие: ${action}`);
|
|
394
388
|
}
|
|
395
|
-
|
|
389
|
+
|
|
390
|
+
child.send({ type: 'user_action_response', requestId, payload: result });
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error(`[BotManager] Ошибка выполнения действия '${action}' для пользователя '${targetUsername}':`, error);
|
|
393
|
+
child.send({ type: 'user_action_response', requestId, error: error.message });
|
|
396
394
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
395
|
+
break;
|
|
396
|
+
case 'playerListUpdate':
|
|
397
|
+
break;
|
|
398
|
+
case 'get_player_list_response': {
|
|
399
|
+
const { requestId, payload } = message;
|
|
400
|
+
const request = this.pendingPlayerListRequests.get(requestId);
|
|
401
|
+
if (request) {
|
|
402
|
+
clearTimeout(request.timeout);
|
|
403
|
+
request.resolve(payload.players);
|
|
404
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
405
|
+
}
|
|
406
|
+
break;
|
|
400
407
|
}
|
|
401
|
-
|
|
402
|
-
child.send({ type: 'user_action_response', requestId, payload: replyPayload });
|
|
403
|
-
|
|
404
|
-
} catch (error) {
|
|
405
|
-
console.error(`[BotManager] Ошибка выполнения действия '${action}' для пользователя '${targetUsername}':`, error);
|
|
406
|
-
child.send({ type: 'user_action_response', requestId, error: error.message });
|
|
407
408
|
}
|
|
409
|
+
} catch (error) {
|
|
410
|
+
this.appendLog(botId, `[SYSTEM-ERROR] Критическая ошибка в обработчике сообщений от бота: ${error.stack}`);
|
|
411
|
+
console.error(`[BotManager] Критическая ошибка в обработчике сообщений от бота ${botId}:`, error);
|
|
408
412
|
}
|
|
409
413
|
});
|
|
414
|
+
|
|
415
|
+
child.on('error', (err) => this.appendLog(botConfig.id, `[PROCESS FATAL] ${err.stack}`));
|
|
416
|
+
child.stderr.on('data', (data) => this.appendLog(botConfig.id, `[STDERR] ${data.toString()}`));
|
|
417
|
+
|
|
410
418
|
child.on('exit', (code, signal) => {
|
|
411
419
|
const botId = botConfig.id;
|
|
412
420
|
this.bots.delete(botId);
|
|
@@ -415,13 +423,15 @@ class BotManager {
|
|
|
415
423
|
this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
|
|
416
424
|
this.updateAllResourceUsage();
|
|
417
425
|
});
|
|
426
|
+
|
|
418
427
|
this.bots.set(botConfig.id, child);
|
|
419
428
|
child.send({ type: 'start', config: fullBotConfig });
|
|
420
429
|
|
|
421
|
-
this.
|
|
430
|
+
await this.eventGraphManager.loadGraphsForBot(botConfig.id);
|
|
422
431
|
|
|
423
|
-
this.
|
|
424
|
-
|
|
432
|
+
this.triggerHeartbeat();
|
|
433
|
+
getIO().emit('bot:status', { botId: botConfig.id, status: 'starting' });
|
|
434
|
+
return child;
|
|
425
435
|
}
|
|
426
436
|
|
|
427
437
|
async handleCommandValidation(botConfig, message) {
|
|
@@ -431,72 +441,99 @@ class BotManager {
|
|
|
431
441
|
try {
|
|
432
442
|
let botConfigCache = this.botConfigs.get(botId);
|
|
433
443
|
if (!botConfigCache) {
|
|
444
|
+
console.log(`[BotManager] No cache for ${botId}, loading...`);
|
|
434
445
|
botConfigCache = await this.loadConfigForBot(botId);
|
|
435
446
|
}
|
|
436
|
-
|
|
437
|
-
const user = await
|
|
447
|
+
|
|
448
|
+
const user = await UserService.getUser(username, botId, botConfig);
|
|
449
|
+
|
|
438
450
|
const child = this.bots.get(botId);
|
|
439
|
-
|
|
440
|
-
if (!child) {
|
|
441
|
-
console.warn(`[BotManager] No running bot process found for botId: ${botId} during command validation. Aborting.`);
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
451
|
+
if (!child) return;
|
|
444
452
|
|
|
445
453
|
if (user.isBlacklisted) {
|
|
446
|
-
|
|
454
|
+
this.sendMessageToBot(botId, 'Вы находитесь в черном списке и не можете использовать команды.', 'private', username);
|
|
447
455
|
return;
|
|
448
456
|
}
|
|
449
|
-
|
|
457
|
+
|
|
450
458
|
const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
|
|
451
459
|
const dbCommand = botConfigCache.commands.get(mainCommandName);
|
|
452
|
-
|
|
460
|
+
|
|
453
461
|
if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
|
|
454
462
|
return;
|
|
455
463
|
}
|
|
456
|
-
|
|
464
|
+
|
|
457
465
|
const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
|
|
458
466
|
if (!allowedTypes.includes(typeChat) && !user.isOwner) {
|
|
459
|
-
if (typeChat === 'global')
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
child.send({ type: 'handle_wrong_chat', commandName: dbCommand.name, username, typeChat });
|
|
467
|
+
if (typeChat === 'global') return;
|
|
468
|
+
this.sendMessageToBot(botId, 'Эту команду нельзя использовать в этом чате.', 'private', username);
|
|
463
469
|
return;
|
|
464
470
|
}
|
|
465
|
-
|
|
471
|
+
|
|
466
472
|
const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
|
|
467
473
|
if (permission && !user.hasPermission(permission.name)) {
|
|
468
|
-
|
|
474
|
+
this.sendMessageToBot(botId, 'У вас нет прав на использование этой команды.', 'private', username);
|
|
469
475
|
return;
|
|
470
476
|
}
|
|
471
|
-
|
|
477
|
+
|
|
472
478
|
const domain = (permission?.name || '').split('.')[0] || 'user';
|
|
473
479
|
const bypassCooldownPermission = `${domain}.cooldown.bypass`;
|
|
480
|
+
|
|
474
481
|
if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
|
|
475
482
|
const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
|
|
476
483
|
const now = Date.now();
|
|
477
484
|
const lastUsed = cooldowns.get(cooldownKey);
|
|
478
|
-
|
|
485
|
+
|
|
479
486
|
if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
|
|
480
487
|
const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
|
|
481
|
-
|
|
488
|
+
this.sendMessageToBot(botId, `Подождите еще ${timeLeft} сек. перед использованием команды.`, 'private', username);
|
|
482
489
|
return;
|
|
483
490
|
}
|
|
484
491
|
cooldowns.set(cooldownKey, now);
|
|
485
492
|
}
|
|
486
493
|
|
|
487
|
-
|
|
494
|
+
if (dbCommand.isVisual) {
|
|
495
|
+
const graph = dbCommand.graphJson;
|
|
496
|
+
if (graph) {
|
|
497
|
+
const players = await this.getPlayerList(botId);
|
|
498
|
+
console.log('[BotManager] Received player list for graph:', players);
|
|
499
|
+
|
|
500
|
+
const botAPI = {
|
|
501
|
+
sendMessage: (type, message, recipient) => {
|
|
502
|
+
this.sendMessageToBot(botId, message, type, recipient);
|
|
503
|
+
},
|
|
504
|
+
executeCommand: (command) => {
|
|
505
|
+
this.sendServerCommandToBot(botId, command);
|
|
506
|
+
},
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const graphContext = {
|
|
510
|
+
bot: botAPI,
|
|
511
|
+
botId: botId,
|
|
512
|
+
user,
|
|
513
|
+
args,
|
|
514
|
+
typeChat,
|
|
515
|
+
players,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
try {
|
|
519
|
+
const resultContext = await this.graphEngine.execute(graph, graphContext, 'command');
|
|
520
|
+
} catch (e) {
|
|
521
|
+
console.error(`[BotManager] Ошибка выполнения визуальной команды '${commandName}':`, e);
|
|
522
|
+
this.sendMessageToBot(botId, 'Произошла внутренняя ошибка при выполнении команды.', 'private', username);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
|
|
527
|
+
}
|
|
528
|
+
|
|
488
529
|
} catch (error) {
|
|
489
530
|
console.error(`[BotManager] Command validation error for botId: ${botId}`, {
|
|
490
|
-
command: commandName,
|
|
491
|
-
user: username,
|
|
492
|
-
error: error.message,
|
|
493
|
-
stack: error.stack
|
|
531
|
+
command: commandName, user: username, error: error.message, stack: error.stack
|
|
494
532
|
});
|
|
495
533
|
this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
|
|
496
534
|
}
|
|
497
535
|
}
|
|
498
536
|
|
|
499
|
-
|
|
500
537
|
async handleCommandRegistration(botId, commandConfig) {
|
|
501
538
|
try {
|
|
502
539
|
let permissionId = null;
|
|
@@ -516,8 +553,6 @@ class BotManager {
|
|
|
516
553
|
}
|
|
517
554
|
permissionId = permission.id;
|
|
518
555
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
556
|
const createData = {
|
|
522
557
|
botId,
|
|
523
558
|
name: commandConfig.name,
|
|
@@ -538,15 +573,34 @@ class BotManager {
|
|
|
538
573
|
create: createData,
|
|
539
574
|
});
|
|
540
575
|
this.invalidateConfigCache(botId);
|
|
541
|
-
|
|
542
576
|
} catch (error) {
|
|
543
577
|
console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
|
|
544
578
|
}
|
|
545
579
|
}
|
|
546
580
|
|
|
581
|
+
async handleGroupRegistration(botId, groupConfig) {
|
|
582
|
+
try {
|
|
583
|
+
await prisma.group.upsert({
|
|
584
|
+
where: { botId_name: { botId, name: groupConfig.name } },
|
|
585
|
+
update: {
|
|
586
|
+
owner: groupConfig.owner,
|
|
587
|
+
},
|
|
588
|
+
create: {
|
|
589
|
+
botId,
|
|
590
|
+
name: groupConfig.name,
|
|
591
|
+
owner: groupConfig.owner,
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
this.invalidateConfigCache(botId);
|
|
595
|
+
} catch (error) {
|
|
596
|
+
console.error(`[BotManager] Ошибка при регистрации группы '${groupConfig.name}':`, error);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
547
600
|
stopBot(botId) {
|
|
548
601
|
const child = this.bots.get(botId);
|
|
549
602
|
if (child) {
|
|
603
|
+
this.eventGraphManager.unloadGraphsForBot(botId);
|
|
550
604
|
child.send({ type: 'stop' });
|
|
551
605
|
this.botConfigs.delete(botId);
|
|
552
606
|
return { success: true };
|
|
@@ -564,7 +618,7 @@ class BotManager {
|
|
|
564
618
|
}
|
|
565
619
|
|
|
566
620
|
invalidateUserCache(botId, username) {
|
|
567
|
-
|
|
621
|
+
UserService.clearCache(username, botId);
|
|
568
622
|
const child = this.bots.get(botId);
|
|
569
623
|
if (child) {
|
|
570
624
|
child.send({ type: 'invalidate_user_cache', username });
|
|
@@ -572,17 +626,90 @@ class BotManager {
|
|
|
572
626
|
return { success: true };
|
|
573
627
|
}
|
|
574
628
|
|
|
575
|
-
|
|
576
|
-
|
|
629
|
+
async getPlayerList(botId) {
|
|
630
|
+
const PLAYER_LIST_CACHE_TTL = 2000;
|
|
631
|
+
|
|
632
|
+
const child = this.bots.get(botId);
|
|
633
|
+
if (!child || child.killed) {
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const cachedEntry = this.playerListCache.get(botId);
|
|
638
|
+
if (cachedEntry && (Date.now() - cachedEntry.timestamp < PLAYER_LIST_CACHE_TTL)) {
|
|
639
|
+
return cachedEntry.promise;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
const newPromise = new Promise((resolve) => {
|
|
643
|
+
const requestId = uuidv4();
|
|
644
|
+
const timeout = setTimeout(() => {
|
|
645
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
646
|
+
if (this.playerListCache.get(botId)?.promise === newPromise) {
|
|
647
|
+
this.playerListCache.delete(botId);
|
|
648
|
+
}
|
|
649
|
+
resolve([]);
|
|
650
|
+
}, 5000);
|
|
651
|
+
|
|
652
|
+
this.pendingPlayerListRequests.set(requestId, {
|
|
653
|
+
resolve: (playerList) => {
|
|
654
|
+
clearTimeout(timeout);
|
|
655
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
656
|
+
this.playerListCache.set(botId, {
|
|
657
|
+
promise: Promise.resolve(playerList),
|
|
658
|
+
timestamp: Date.now()
|
|
659
|
+
});
|
|
660
|
+
resolve(playerList);
|
|
661
|
+
},
|
|
662
|
+
reject: (error) => {
|
|
663
|
+
clearTimeout(timeout);
|
|
664
|
+
this.pendingPlayerListRequests.delete(requestId);
|
|
665
|
+
if (this.playerListCache.get(botId)?.promise === newPromise) {
|
|
666
|
+
this.playerListCache.delete(botId);
|
|
667
|
+
}
|
|
668
|
+
resolve([]);
|
|
669
|
+
},
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
child.send({ type: 'system:get_player_list', requestId });
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
this.playerListCache.set(botId, {
|
|
676
|
+
promise: newPromise,
|
|
677
|
+
timestamp: Date.now()
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
return newPromise;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
setEventGraphManager(manager) {
|
|
684
|
+
this.eventGraphManager = manager;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
lookAt(botId, position) {
|
|
688
|
+
const botProcess = this.bots.get(botId);
|
|
689
|
+
if (botProcess && !botProcess.killed) {
|
|
690
|
+
botProcess.send({ type: 'action', name: 'lookAt', payload: { position } });
|
|
691
|
+
} else {
|
|
692
|
+
console.error(`[BotManager] Не удалось найти запущенный процесс для бота ${botId}, чтобы выполнить lookAt.`);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async reloadPlugins(botId) {
|
|
577
697
|
const child = this.bots.get(botId);
|
|
578
698
|
if (child && !child.killed) {
|
|
579
|
-
child.send({ type: '
|
|
580
|
-
console.log(`[BotManager] Sent
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
699
|
+
child.send({ type: 'plugins:reload' });
|
|
700
|
+
console.log(`[BotManager] Sent plugins:reload to bot process ${botId}`);
|
|
701
|
+
getIO().emit('bot:plugins_reloaded', { botId });
|
|
702
|
+
return { success: true, message: 'Команда на перезагрузку плагинов отправлена.' };
|
|
703
|
+
}
|
|
704
|
+
return { success: false, message: 'Бот не запущен.' };
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
sendServerCommandToBot(botId, command) {
|
|
708
|
+
const child = this.bots.get(botId);
|
|
709
|
+
if (child) {
|
|
710
|
+
child.send({ type: 'server_command', payload: { command } });
|
|
584
711
|
}
|
|
585
712
|
}
|
|
586
713
|
}
|
|
587
714
|
|
|
588
|
-
module.exports =
|
|
715
|
+
module.exports = BotManager;
|