blockmine 1.1.0 → 1.1.9

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.
@@ -1,12 +1,8 @@
1
1
  {
2
- "watch": [
3
- "src/"
4
- ],
5
- "ext": "js,mjs,cjs,json",
6
- "ignore": [
7
- "storage/",
8
- "prisma/dev.db",
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
+ }
@@ -4,10 +4,10 @@
4
4
  "description": "",
5
5
  "main": "src/server.js",
6
6
  "scripts": {
7
- "start": "node src/server.js",
8
- "dev": "nodemon src/server.js",
9
- "test": "echo \"Error: no test specified\" && exit 1"
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,136 @@ 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
+ setInterval(() => this.sendHeartbeat(), 5 * 60 * 1000);
49
+ }
50
+ }
51
+
52
+
53
+ triggerHeartbeat() {
54
+ if (this.heartbeatDebounceTimer) {
55
+ clearTimeout(this.heartbeatDebounceTimer);
56
+ }
57
+ this.heartbeatDebounceTimer = setTimeout(() => {
58
+ this.sendHeartbeat();
59
+ }, 3000);
60
+ }
61
+
62
+
63
+ _sendThrottledWarning(botId, username, warningType, message, typeChat = 'private') {
64
+ const cacheKey = `${botId}:${username}:${warningType}`;
65
+ const now = Date.now();
66
+ const lastWarning = warningCache.get(cacheKey);
67
+
68
+ if (!lastWarning || (now - lastWarning > WARNING_COOLDOWN)) {
69
+ this.sendMessageToBot(botId, message, typeChat, username);
70
+ warningCache.set(cacheKey, now);
71
+ }
72
+ }
73
+
74
+
75
+ async sendHeartbeat() {
76
+ if (!instanceId) return;
77
+
78
+ try {
79
+ const runningBots = [];
80
+ for (const botProcess of this.bots.values()) {
81
+ if (botProcess.botConfig) {
82
+ runningBots.push({
83
+ username: botProcess.botConfig.username,
84
+ serverHost: botProcess.botConfig.server.host,
85
+ serverPort: botProcess.botConfig.server.port
86
+ });
87
+ }
88
+ }
89
+
90
+ if (runningBots.length === 0) {
91
+ return;
92
+ }
93
+
94
+
95
+ const challengeRes = await fetch(`${STATS_SERVER_URL}/api/challenge?uuid=${instanceId}`);
96
+ if (!challengeRes.ok) {
97
+ console.error(`[Telemetry] Сервер статистики вернул ошибку при получении challenge: ${challengeRes.statusText}`);
98
+ return;
99
+ }
100
+ const { challenge, difficulty, prefix } = await challengeRes.json();
101
+
102
+ let nonce = 0;
103
+ let hash = '';
104
+ do {
105
+ nonce++;
106
+ hash = crypto.createHash('sha256').update(prefix + challenge + nonce).digest('hex');
107
+ } while (!hash.startsWith('0'.repeat(difficulty)));
108
+
109
+ const packageJson = require('../../../package.json');
110
+ await fetch(`${STATS_SERVER_URL}/api/heartbeat`, {
111
+ method: 'POST',
112
+ headers: { 'Content-Type': 'application/json' },
113
+ body: JSON.stringify({
114
+ instanceUuid: instanceId,
115
+ appVersion: packageJson.version,
116
+ bots: runningBots,
117
+ nonce: nonce
118
+ })
119
+ });
120
+
121
+ } catch (error) {
122
+ console.error(`[Telemetry] Не удалось отправить heartbeat: ${error.message}`);
123
+ }
124
+ }
125
+
126
+
127
+ async _syncSystemPermissions(botId) {
128
+ const systemPermissions = [
129
+ { name: "admin.*", description: "Все права администратора" },
130
+ { name: "admin.cooldown.bypass", description: "Обход кулдауна для админ-команд" },
131
+ { name: "user.*", description: "Все права обычного пользователя" },
132
+ { name: "user.say", description: "Доступ к простым командам" },
133
+ { name: "user.cooldown.bypass", description: "Обход кулдауна для юзер-команд" },
134
+ ];
135
+ const systemGroups = ["User", "Admin"];
136
+ const systemGroupPermissions = {
137
+ "User": ["user.say"],
138
+ "Admin": ["admin.*", "admin.cooldown.bypass", "user.cooldown.bypass", "user.*"]
139
+ };
140
+
141
+ console.log(`[Permission Sync] Синхронизация системных прав для бота ID ${botId}...`);
142
+
143
+ for (const perm of systemPermissions) {
144
+ await prisma.permission.upsert({
145
+ where: { botId_name: { botId, name: perm.name } },
146
+ update: { description: perm.description },
147
+ create: { ...perm, botId, owner: 'system' }
148
+ });
149
+ }
150
+
151
+ for (const groupName of systemGroups) {
152
+ await prisma.group.upsert({
153
+ where: { botId_name: { botId, name: groupName } },
154
+ update: {},
155
+ create: { name: groupName, botId, owner: 'system' }
156
+ });
157
+ }
158
+
159
+ for (const [groupName, permNames] of Object.entries(systemGroupPermissions)) {
160
+ const group = await prisma.group.findUnique({ where: { botId_name: { botId, name: groupName } } });
161
+ if (group) {
162
+ for (const permName of permNames) {
163
+ const permission = await prisma.permission.findUnique({ where: { botId_name: { botId, name: permName } } });
164
+ if (permission) {
165
+ await prisma.groupPermission.upsert({
166
+ where: { groupId_permissionId: { groupId: group.id, permissionId: permission.id } },
167
+ update: {},
168
+ create: { groupId: group.id, permissionId: permission.id }
169
+ });
170
+ }
171
+ }
172
+ }
173
+ }
174
+ console.log(`[Permission Sync] Синхронизация для бота ID ${botId} завершена.`);
22
175
  }
23
176
 
24
177
  async updateAllResourceUsage() {
@@ -102,6 +255,9 @@ class BotManager {
102
255
  return { success: false, message: 'Бот уже запущен или запускается.' };
103
256
  }
104
257
  }
258
+
259
+ await this._syncSystemPermissions(botConfig.id);
260
+
105
261
  this.logCache.set(botConfig.id, []);
106
262
  this.emitStatusUpdate(botConfig.id, 'starting', '');
107
263
 
@@ -134,6 +290,8 @@ class BotManager {
134
290
  const botProcessPath = path.resolve(__dirname, 'BotProcess.js');
135
291
  const child = fork(botProcessPath, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
136
292
 
293
+ child.botConfig = botConfig;
294
+
137
295
  child.on('error', (err) => {
138
296
  this.appendLog(botConfig.id, `[PROCESS FATAL] КРИТИЧЕСКАЯ ОШИБКА ПРОЦЕССА: ${err.stack}`);
139
297
  });
@@ -164,6 +322,8 @@ class BotManager {
164
322
 
165
323
  this.bots.set(botConfig.id, child);
166
324
  child.send({ type: 'start', config: fullBotConfig });
325
+
326
+ this.triggerHeartbeat();
167
327
 
168
328
  this.appendLog(botConfig.id, '[SYSTEM] Проверка зависимостей пройдена. Запускаем процесс бота...');
169
329
  return { success: true, message: 'Бот запускается' };
@@ -188,13 +348,25 @@ class BotManager {
188
348
 
189
349
  const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
190
350
  if (!allowedTypes.includes(typeChat) && !user.isOwner) {
191
- this.sendMessageToBot(botConfig.id, `Команду ${dbCommand.name} нельзя использовать в этом типе чата.`, 'private', username);
351
+ this._sendThrottledWarning(
352
+ botConfig.id,
353
+ username,
354
+ `wrong_chat:${dbCommand.name}:${typeChat}`,
355
+ `Команду ${dbCommand.name} нельзя использовать в этом типе чата.`,
356
+ typeChat
357
+ );
192
358
  return;
193
359
  }
194
360
 
195
361
  const permission = dbCommand.permissionId ? await prisma.permission.findUnique({ where: { id: dbCommand.permissionId } }) : null;
196
362
  if (permission && !user.hasPermission(permission.name)) {
197
- this.sendMessageToBot(botConfig.id, `У вас нет прав для выполнения команды ${dbCommand.name}.`, 'private', username);
363
+ this._sendThrottledWarning(
364
+ botConfig.id,
365
+ username,
366
+ `no_permission:${dbCommand.name}`,
367
+ `У вас нет прав для выполнения команды ${dbCommand.name}.`,
368
+ typeChat
369
+ );
198
370
  return;
199
371
  }
200
372
 
@@ -207,7 +379,14 @@ class BotManager {
207
379
 
208
380
  if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
209
381
  const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
210
- this.sendMessageToBot(botConfig.id, `Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`, typeChat, username);
382
+
383
+ this._sendThrottledWarning(
384
+ botConfig.id,
385
+ username,
386
+ `cooldown:${dbCommand.name}`,
387
+ `Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`,
388
+ typeChat
389
+ );
211
390
  return;
212
391
  }
213
392
  cooldowns.set(cooldownKey, now);
@@ -236,33 +415,34 @@ class BotManager {
236
415
  data: {
237
416
  botId,
238
417
  name: commandConfig.permissions,
239
- description: `Авто-создано для команды ${commandConfig.name}`,
418
+ description: `Автоматически создано для команды ${commandConfig.name}`,
240
419
  owner: commandConfig.owner,
241
420
  }
242
421
  });
243
422
  }
244
423
  permissionId = permission.id;
245
424
  }
246
-
425
+
426
+ const data = {
427
+ description: commandConfig.description,
428
+ aliases: JSON.stringify(commandConfig.aliases || []),
429
+ owner: commandConfig.owner,
430
+ permissionId: permissionId,
431
+ allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || []),
432
+ cooldown: commandConfig.cooldown || 0,
433
+ };
434
+
247
435
  await prisma.command.upsert({
248
436
  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
- },
437
+ update: data,
256
438
  create: {
257
- ...commandConfig,
258
439
  botId,
259
- aliases: JSON.stringify(commandConfig.aliases || []),
260
- allowedChatTypes: JSON.stringify(commandConfig.allowedChatTypes || ['chat', 'private']),
261
- permissionId: permissionId,
440
+ name: commandConfig.name,
441
+ ...data
262
442
  }
263
443
  });
264
444
  } catch (error) {
265
- console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}' от плагина:`, error);
445
+ console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
266
446
  }
267
447
  }
268
448
 
@@ -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
- return this.registerPlugin(botId, directoryPath, 'LOCAL', directoryPath);
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
- return await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient);
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 = ["merka", "akrem"];
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;
@@ -9,7 +9,7 @@ class PingCommand extends Command {
9
9
  cooldown: 5,
10
10
  permissions: 'user.ping',
11
11
  owner: 'system',
12
- allowedChatTypes: ['local', 'clan', 'private', 'global'],
12
+ allowedChatTypes: ['local', 'clan', 'private'],
13
13
 
14
14
  args: [
15
15
  {
@@ -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
+