blockmine 1.1.0 → 1.1.8
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/backend/nodemon.json +7 -11
- package/backend/package.json +4 -4
- package/backend/src/api/routes/bots.js +3 -2
- package/backend/src/core/BotManager.js +188 -17
- package/backend/src/core/BotProcess.js +18 -0
- package/backend/src/core/PluginManager.js +41 -5
- package/backend/src/core/UserService.js +1 -1
- package/backend/src/core/commands/dev.js +41 -0
- package/backend/src/core/commands/ping.js +1 -1
- package/backend/src/server.js +33 -5
- package/frontend/dist/assets/index-B6uPFKxW.css +1 -0
- package/frontend/dist/assets/{index-DjU4Tk7J.js → index-BO23cI3z.js} +339 -339
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-B83SHIXE.css +0 -1
package/backend/nodemon.json
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"prisma/dev.db-journal"
|
|
10
|
-
],
|
|
11
|
-
"exec": "node src/server.js"
|
|
12
|
-
}
|
|
2
|
+
"watch": ["src/"],
|
|
3
|
+
"ext": "js,mjs,cjs,json",
|
|
4
|
+
"ignore": ["storage/", "prisma/dev.db", "prisma/dev.db-journal"],
|
|
5
|
+
"exec": "node src/server.js",
|
|
6
|
+
"signal": "SIGINT",
|
|
7
|
+
"delay": 1500
|
|
8
|
+
}
|
package/backend/package.json
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
"start": "node src/server.js",
|
|
8
|
+
"dev": "nodemon",
|
|
9
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
|
+
},
|
|
11
11
|
"prisma": {
|
|
12
12
|
"seed": "node prisma/seed.js"
|
|
13
13
|
},
|
|
@@ -22,11 +22,12 @@ async function setupDefaultPermissionsForBot(botId, prismaClient = prisma) {
|
|
|
22
22
|
{ name: "admin.*", description: "Все права администратора" },
|
|
23
23
|
{ name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
|
|
24
24
|
{ name: "user.*", description: "Все права обычного пользователя" },
|
|
25
|
+
{ name: "user.say", description: "Доступ к простым командам" },
|
|
25
26
|
{ name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
|
|
26
27
|
],
|
|
27
28
|
groupPermissions: {
|
|
28
|
-
"User": ["user
|
|
29
|
-
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass"]
|
|
29
|
+
"User": ["user.say"],
|
|
30
|
+
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
|
|
30
31
|
},
|
|
31
32
|
};
|
|
32
33
|
|
|
@@ -6,11 +6,34 @@ const { PrismaClient } = require('@prisma/client');
|
|
|
6
6
|
const pidusage = require('pidusage');
|
|
7
7
|
const DependencyService = require('./DependencyService');
|
|
8
8
|
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const { v4: uuidv4 } = require('uuid');
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
|
|
9
14
|
const RealPermissionManager = require('./PermissionManager');
|
|
10
15
|
const RealUserService = require('./UserService');
|
|
11
16
|
|
|
12
17
|
const prisma = new PrismaClient();
|
|
13
18
|
const cooldowns = new Map();
|
|
19
|
+
const warningCache = new Map();
|
|
20
|
+
const WARNING_COOLDOWN = 10 * 1000;
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
const TELEMETRY_ENABLED = 'true';
|
|
24
|
+
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
25
|
+
let instanceId = null;
|
|
26
|
+
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
27
|
+
|
|
28
|
+
if (TELEMETRY_ENABLED && STATS_SERVER_URL) {
|
|
29
|
+
const idPath = path.join(DATA_DIR, '.instance_id');
|
|
30
|
+
try {
|
|
31
|
+
instanceId = fs.readFileSync(idPath, 'utf-8');
|
|
32
|
+
} catch (e) {
|
|
33
|
+
instanceId = uuidv4();
|
|
34
|
+
fs.writeFileSync(idPath, instanceId, 'utf-8');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
14
37
|
|
|
15
38
|
class BotManager {
|
|
16
39
|
constructor() {
|
|
@@ -19,6 +42,129 @@ class BotManager {
|
|
|
19
42
|
this.resourceUsage = new Map();
|
|
20
43
|
|
|
21
44
|
setInterval(() => this.updateAllResourceUsage(), 5000);
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if (TELEMETRY_ENABLED && STATS_SERVER_URL) {
|
|
48
|
+
setTimeout(() => this.sendHeartbeat(), 10000);
|
|
49
|
+
setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
_sendThrottledWarning(botId, username, warningType, message, typeChat = 'private') {
|
|
55
|
+
const cacheKey = `${botId}:${username}:${warningType}`;
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const lastWarning = warningCache.get(cacheKey);
|
|
58
|
+
|
|
59
|
+
if (!lastWarning || (now - lastWarning > WARNING_COOLDOWN)) {
|
|
60
|
+
this.sendMessageToBot(botId, message, typeChat, username);
|
|
61
|
+
warningCache.set(cacheKey, now);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
async sendHeartbeat() {
|
|
67
|
+
if (!instanceId) return;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const runningBots = [];
|
|
71
|
+
for (const botProcess of this.bots.values()) {
|
|
72
|
+
if (botProcess.botConfig) {
|
|
73
|
+
runningBots.push({
|
|
74
|
+
username: botProcess.botConfig.username,
|
|
75
|
+
serverHost: botProcess.botConfig.server.host,
|
|
76
|
+
serverPort: botProcess.botConfig.server.port
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (runningBots.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(`[Telemetry] Подготовка к отправке heartbeat для ${runningBots.length} ботов...`);
|
|
86
|
+
|
|
87
|
+
const challengeRes = await fetch(`${STATS_SERVER_URL}/api/challenge?uuid=${instanceId}`);
|
|
88
|
+
if (!challengeRes.ok) {
|
|
89
|
+
console.error(`[Telemetry] Сервер статистики вернул ошибку при получении challenge: ${challengeRes.statusText}`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { challenge, difficulty, prefix } = await challengeRes.json();
|
|
93
|
+
|
|
94
|
+
let nonce = 0;
|
|
95
|
+
let hash = '';
|
|
96
|
+
do {
|
|
97
|
+
nonce++;
|
|
98
|
+
hash = crypto.createHash('sha256').update(prefix + challenge + nonce).digest('hex');
|
|
99
|
+
} while (!hash.startsWith('0'.repeat(difficulty)));
|
|
100
|
+
|
|
101
|
+
const packageJson = require('../../../package.json');
|
|
102
|
+
await fetch(`${STATS_SERVER_URL}/api/heartbeat`, {
|
|
103
|
+
method: 'POST',
|
|
104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
instanceUuid: instanceId,
|
|
107
|
+
appVersion: packageJson.version,
|
|
108
|
+
bots: runningBots,
|
|
109
|
+
nonce: nonce
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
console.log(`[Telemetry] Heartbeat успешно отправлен.`);
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error(`[Telemetry] Не удалось отправить heartbeat: ${error.message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
async _syncSystemPermissions(botId) {
|
|
121
|
+
const systemPermissions = [
|
|
122
|
+
{ name: "admin.*", description: "Все права администратора" },
|
|
123
|
+
{ name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
|
|
124
|
+
{ name: "user.*", description: "Все права обычного пользователя" },
|
|
125
|
+
{ name: "user.say", description: "Доступ к простым командам" },
|
|
126
|
+
{ name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
|
|
127
|
+
];
|
|
128
|
+
const systemGroups = ["User", "Admin"];
|
|
129
|
+
const systemGroupPermissions = {
|
|
130
|
+
"User": ["user.say"],
|
|
131
|
+
"Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
console.log(`[Permission Sync] Синхронизация системных прав для бота ID ${botId}...`);
|
|
135
|
+
|
|
136
|
+
for (const perm of systemPermissions) {
|
|
137
|
+
await prisma.permission.upsert({
|
|
138
|
+
where: { botId_name: { botId, name: perm.name } },
|
|
139
|
+
update: { description: perm.description },
|
|
140
|
+
create: { ...perm, botId, owner: 'system' }
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
for (const groupName of systemGroups) {
|
|
145
|
+
await prisma.group.upsert({
|
|
146
|
+
where: { botId_name: { botId, name: groupName } },
|
|
147
|
+
update: {},
|
|
148
|
+
create: { name: groupName, botId, owner: 'system' }
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const [groupName, permNames] of Object.entries(systemGroupPermissions)) {
|
|
153
|
+
const group = await prisma.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
|
|
154
|
+
if (group) {
|
|
155
|
+
for (const permName of permNames) {
|
|
156
|
+
const permission = await prisma.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
|
|
157
|
+
if (permission) {
|
|
158
|
+
await prisma.groupPermission.upsert({
|
|
159
|
+
where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
|
|
160
|
+
update: {},
|
|
161
|
+
create: { groupId: group.id, permissionId: permission.id }
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
console.log(`[Permission Sync] Синхронизация для бота ID ${botId} завершена.`);
|
|
22
168
|
}
|
|
23
169
|
|
|
24
170
|
async updateAllResourceUsage() {
|
|
@@ -102,6 +248,9 @@ class BotManager {
|
|
|
102
248
|
return { success: false, message: 'Бот уже запущен или запускается.' };
|
|
103
249
|
}
|
|
104
250
|
}
|
|
251
|
+
|
|
252
|
+
await this._syncSystemPermissions(botConfig.id);
|
|
253
|
+
|
|
105
254
|
this.logCache.set(botConfig.id, []);
|
|
106
255
|
this.emitStatusUpdate(botConfig.id, 'starting', '');
|
|
107
256
|
|
|
@@ -134,6 +283,8 @@ class BotManager {
|
|
|
134
283
|
const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
|
|
135
284
|
const child = fork(botProcessPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
|
|
136
285
|
|
|
286
|
+
child.botConfig = botConfig;
|
|
287
|
+
|
|
137
288
|
child.on('error', (err) => {
|
|
138
289
|
this.appendLog(botConfig.id, `[PROCESS FATAL] КРИТИЧЕСКАЯ ОШИБКА ПРОЦЕССА: ${err.stack}`);
|
|
139
290
|
});
|
|
@@ -188,13 +339,25 @@ class BotManager {
|
|
|
188
339
|
|
|
189
340
|
const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
|
|
190
341
|
if (!allowedTypes.includes(typeChat) && !user.isOwner) {
|
|
191
|
-
this.
|
|
342
|
+
this._sendThrottledWarning(
|
|
343
|
+
botConfig.id,
|
|
344
|
+
username,
|
|
345
|
+
`wrong_chat:${dbCommand.name}:${typeChat}`,
|
|
346
|
+
`Команду ${dbCommand.name} нельзя использовать в этом типе чата.`,
|
|
347
|
+
typeChat
|
|
348
|
+
);
|
|
192
349
|
return;
|
|
193
350
|
}
|
|
194
351
|
|
|
195
352
|
const permission = dbCommand.permissionId ? await prisma.permission.findUnique({ where: { id: dbCommand.permissionId } }) : null;
|
|
196
353
|
if (permission && !user.hasPermission(permission.name)) {
|
|
197
|
-
this.
|
|
354
|
+
this._sendThrottledWarning(
|
|
355
|
+
botConfig.id,
|
|
356
|
+
username,
|
|
357
|
+
`no_permission:${dbCommand.name}`,
|
|
358
|
+
`У вас нет прав для выполнения команды ${dbCommand.name}.`,
|
|
359
|
+
typeChat
|
|
360
|
+
);
|
|
198
361
|
return;
|
|
199
362
|
}
|
|
200
363
|
|
|
@@ -207,7 +370,14 @@ class BotManager {
|
|
|
207
370
|
|
|
208
371
|
if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
|
|
209
372
|
const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
|
|
210
|
-
|
|
373
|
+
|
|
374
|
+
this._sendThrottledWarning(
|
|
375
|
+
botConfig.id,
|
|
376
|
+
username,
|
|
377
|
+
`cooldown:${dbCommand.name}`,
|
|
378
|
+
`Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`,
|
|
379
|
+
typeChat
|
|
380
|
+
);
|
|
211
381
|
return;
|
|
212
382
|
}
|
|
213
383
|
cooldowns.set(cooldownKey, now);
|
|
@@ -236,33 +406,34 @@ class BotManager {
|
|
|
236
406
|
data: {
|
|
237
407
|
botId,
|
|
238
408
|
name: commandConfig.permissions,
|
|
239
|
-
description:
|
|
409
|
+
description: `Автоматически создано для команды ${commandConfig.name}`,
|
|
240
410
|
owner: commandConfig.owner,
|
|
241
411
|
}
|
|
242
412
|
});
|
|
243
413
|
}
|
|
244
414
|
permissionId = permission.id;
|
|
245
415
|
}
|
|
246
|
-
|
|
416
|
+
|
|
417
|
+
const data = {
|
|
418
|
+
description: commandConfig.description,
|
|
419
|
+
aliases: JSON.stringify(commandConfig.aliases || []),
|
|
420
|
+
owner: commandConfig.owner,
|
|
421
|
+
permissionId: permissionId,
|
|
422
|
+
allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
|
|
423
|
+
cooldown: commandConfig.cooldown || 0,
|
|
424
|
+
};
|
|
425
|
+
|
|
247
426
|
await prisma.command.upsert({
|
|
248
427
|
where: { botId_name: { botId, name: commandConfig.name } },
|
|
249
|
-
update:
|
|
250
|
-
description: commandConfig.description,
|
|
251
|
-
aliases: JSON.stringify(commandConfig.aliases || []),
|
|
252
|
-
owner: commandConfig.owner,
|
|
253
|
-
permissionId: permissionId,
|
|
254
|
-
allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || ['chat', 'private']),
|
|
255
|
-
},
|
|
428
|
+
update: data,
|
|
256
429
|
create: {
|
|
257
|
-
...commandConfig,
|
|
258
430
|
botId,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
permissionId: permissionId,
|
|
431
|
+
name: commandConfig.name,
|
|
432
|
+
...data
|
|
262
433
|
}
|
|
263
434
|
});
|
|
264
435
|
} catch (error) {
|
|
265
|
-
console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}'
|
|
436
|
+
console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
|
|
266
437
|
}
|
|
267
438
|
}
|
|
268
439
|
|
|
@@ -171,6 +171,24 @@ process.on('message', async (message) => {
|
|
|
171
171
|
};
|
|
172
172
|
|
|
173
173
|
bot.commands = await loadCommands();
|
|
174
|
+
|
|
175
|
+
if (process.send) {
|
|
176
|
+
for (const cmd of bot.commands.values()) {
|
|
177
|
+
process.send({
|
|
178
|
+
type: 'register_command',
|
|
179
|
+
commandConfig: {
|
|
180
|
+
name: cmd.name,
|
|
181
|
+
description: cmd.description,
|
|
182
|
+
aliases: cmd.aliases,
|
|
183
|
+
owner: cmd.owner,
|
|
184
|
+
permissions: cmd.permissions,
|
|
185
|
+
cooldown: cmd.cooldown,
|
|
186
|
+
allowedChatTypes: cmd.allowedChatTypes,
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
174
192
|
await initializePlugins(bot, config.plugins);
|
|
175
193
|
sendLog('[System] Все системы инициализированы.');
|
|
176
194
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
const path = require('path');
|
|
3
2
|
const fs = require('fs/promises');
|
|
4
3
|
const os = require('os');
|
|
@@ -11,6 +10,32 @@ const prisma = new PrismaClient();
|
|
|
11
10
|
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
12
11
|
const PLUGINS_BASE_DIR = path.join(DATA_DIR, 'storage', 'plugins');
|
|
13
12
|
|
|
13
|
+
const TELEMETRY_ENABLED = true;
|
|
14
|
+
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
15
|
+
|
|
16
|
+
function reportPluginDownload(pluginName) {
|
|
17
|
+
if (!TELEMETRY_ENABLED) return;
|
|
18
|
+
|
|
19
|
+
console.log(`[Telemetry] Попытка отправить статистику по плагину: ${pluginName}`);
|
|
20
|
+
|
|
21
|
+
fetch(`${STATS_SERVER_URL}/api/plugins/download`, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ plugin_name: pluginName })
|
|
25
|
+
})
|
|
26
|
+
.then(res => {
|
|
27
|
+
if (res.ok) {
|
|
28
|
+
console.log(`[Telemetry] Статистика для плагина ${pluginName} успешно отправлена.`);
|
|
29
|
+
} else {
|
|
30
|
+
console.error(`[Telemetry] Сервер статистики вернул ошибку для плагина ${pluginName}: ${res.statusText}`);
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
.catch(error => {
|
|
34
|
+
console.error(`[Telemetry] Не удалось отправить статистику по плагину ${pluginName}: ${error.message}`);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
14
39
|
class PluginManager {
|
|
15
40
|
constructor() {
|
|
16
41
|
this.ensureBaseDirExists();
|
|
@@ -21,7 +46,14 @@ class PluginManager {
|
|
|
21
46
|
}
|
|
22
47
|
|
|
23
48
|
async installFromLocalPath(botId, directoryPath) {
|
|
24
|
-
|
|
49
|
+
const newPlugin = await this.registerPlugin(botId, directoryPath, 'LOCAL', directoryPath);
|
|
50
|
+
try {
|
|
51
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(directoryPath, 'package.json'), 'utf-8'));
|
|
52
|
+
reportPluginDownload(packageJson.name);
|
|
53
|
+
} catch(e) {
|
|
54
|
+
console.error('Не удалось прочитать package.json для отправки статистики локального плагина');
|
|
55
|
+
}
|
|
56
|
+
return newPlugin;
|
|
25
57
|
}
|
|
26
58
|
|
|
27
59
|
async installFromGithub(botId, repoUrl, prismaClient = prisma) {
|
|
@@ -60,7 +92,12 @@ class PluginManager {
|
|
|
60
92
|
|
|
61
93
|
await fs.rename(path.join(botPluginsDir, rootFolderName), localPath);
|
|
62
94
|
|
|
63
|
-
|
|
95
|
+
const newPlugin = await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient);
|
|
96
|
+
|
|
97
|
+
const packageJson = JSON.parse(await fs.readFile(path.join(localPath, 'package.json'), 'utf-8'));
|
|
98
|
+
reportPluginDownload(packageJson.name);
|
|
99
|
+
|
|
100
|
+
return newPlugin;
|
|
64
101
|
|
|
65
102
|
} catch (error) {
|
|
66
103
|
console.error(`[PluginManager] Ошибка установки с GitHub: ${error.message}`);
|
|
@@ -106,8 +143,7 @@ class PluginManager {
|
|
|
106
143
|
const mainFile = manifest.main || 'index.js';
|
|
107
144
|
const entryPointPath = path.join(plugin.path, mainFile);
|
|
108
145
|
|
|
109
|
-
await fs.access(entryPointPath);
|
|
110
|
-
|
|
146
|
+
await fs.access(entryPointPath);
|
|
111
147
|
const pluginModule = require(entryPointPath);
|
|
112
148
|
|
|
113
149
|
if (pluginModule && typeof pluginModule.onUnload === 'function') {
|
|
@@ -11,7 +11,7 @@ class User {
|
|
|
11
11
|
this.user = userInstance;
|
|
12
12
|
this.botConfig = botConfig;
|
|
13
13
|
|
|
14
|
-
const globalOwners = ["
|
|
14
|
+
const globalOwners = ["d", "akrem"];
|
|
15
15
|
const keksikServers = ["mc.mineblaze.net", "mc.masedworld.net", "mc.cheatmine.net", "mc.dexland.org"];
|
|
16
16
|
|
|
17
17
|
const botOwners = (this.botConfig.owners || '').toLowerCase().split(',').map(s => s.trim()).filter(Boolean);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
const Command = require('../system/Command');
|
|
3
|
+
const fs = require('fs/promises');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
let appVersion = null;
|
|
7
|
+
|
|
8
|
+
class DevCommand extends Command {
|
|
9
|
+
constructor() {
|
|
10
|
+
super({
|
|
11
|
+
name: 'dev',
|
|
12
|
+
description: 'Показывает информацию о панели управления и статистику бота.',
|
|
13
|
+
aliases: ['about', 'version', 'ver'],
|
|
14
|
+
cooldown: 10,
|
|
15
|
+
permissions: 'user.say',
|
|
16
|
+
owner: 'system',
|
|
17
|
+
allowedChatTypes: ['chat', 'local', 'clan', 'private'],
|
|
18
|
+
args: []
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async handler(bot, typeChat, user) {
|
|
23
|
+
if (!appVersion) {
|
|
24
|
+
try {
|
|
25
|
+
const packageJsonPath = path.join(__dirname, '..', '..', '..', '..', 'package.json');
|
|
26
|
+
const packageJsonData = await fs.readFile(packageJsonPath, 'utf-8');
|
|
27
|
+
appVersion = JSON.parse(packageJsonData).version;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
bot.sendLog(`[DevCommand] Не удалось прочитать версию: ${error.message}`);
|
|
30
|
+
appVersion = 'неизвестно';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const enabledPluginsCount = bot.config.plugins.length;
|
|
35
|
+
const totalCommandsCount = bot.commands.size;
|
|
36
|
+
|
|
37
|
+
bot.api.sendMessage(typeChat, `Бот создан с помощью - BlockMine. Версия: v${appVersion}. Активных плагинов: ${enabledPluginsCount}. Всего команд: ${totalCommandsCount}`, user.username);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = DevCommand;
|
package/backend/src/server.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
const express = require('express');
|
|
3
2
|
const http = require('http');
|
|
4
3
|
const path = require('path');
|
|
@@ -13,12 +12,12 @@ if (!fs.existsSync(DATA_DIR)) {
|
|
|
13
12
|
|
|
14
13
|
process.env.DATABASE_URL = `file:${path.join(DATA_DIR, 'blockmine.db')}`;
|
|
15
14
|
|
|
16
|
-
|
|
17
15
|
const { initializeSocket } = require('./real-time/socketHandler');
|
|
18
16
|
const botRoutes = require('./api/routes/bots');
|
|
19
17
|
const pluginRoutes = require('./api/routes/plugins');
|
|
20
18
|
const serverRoutes = require('./api/routes/servers');
|
|
21
19
|
const permissionsRoutes = require('./api/routes/permissions');
|
|
20
|
+
const BotManager = require('./core/BotManager');
|
|
22
21
|
|
|
23
22
|
const app = express();
|
|
24
23
|
const server = http.createServer(app);
|
|
@@ -53,8 +52,8 @@ app.use(express.static(frontendPath));
|
|
|
53
52
|
|
|
54
53
|
app.get(/^(?!\/api).*/, (req, res) => {
|
|
55
54
|
res.sendFile(path.join(frontendPath, 'index.html'), (err) => {
|
|
56
|
-
if (err) {
|
|
57
|
-
console.error(`Ошибка при отправке index.html:`, err);
|
|
55
|
+
if (err && !res.headersSent) {
|
|
56
|
+
console.error(`Ошибка при отправке index.html для пути ${req.path}:`, err);
|
|
58
57
|
res.status(500).send("Не удалось загрузить приложение.");
|
|
59
58
|
}
|
|
60
59
|
});
|
|
@@ -70,8 +69,37 @@ async function startServer() {
|
|
|
70
69
|
});
|
|
71
70
|
}
|
|
72
71
|
|
|
72
|
+
|
|
73
|
+
const gracefulShutdown = (signal) => {
|
|
74
|
+
console.log(`[Shutdown] Получен сигнал ${signal}. Начинаем завершение...`);
|
|
75
|
+
|
|
76
|
+
const botIds = Array.from(BotManager.bots.keys());
|
|
77
|
+
if (botIds.length > 0) {
|
|
78
|
+
console.log(`[Shutdown] Остановка ${botIds.length} активных ботов...`);
|
|
79
|
+
for (const botId of botIds) {
|
|
80
|
+
BotManager.stopBot(botId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
server.close(() => {
|
|
85
|
+
console.log('[Shutdown] HTTP сервер закрыт. Завершение процесса.');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
console.error('[Shutdown] Не удалось закрыть соединения вовремя, принудительное завершение.');
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}, 5000);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
process.on('SIGUSR2', () => gracefulShutdown('SIGUSR2 (nodemon)'));
|
|
96
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT (Ctrl+C)'));
|
|
97
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
98
|
+
|
|
99
|
+
|
|
73
100
|
module.exports = { startServer, app, server };
|
|
74
101
|
|
|
75
102
|
if (require.main === module) {
|
|
76
103
|
startServer();
|
|
77
|
-
|
|
104
|
+
}
|
|
105
|
+
|