blockmine 1.2.0 → 1.3.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.
Files changed (42) hide show
  1. package/backend/cli.js +17 -15
  2. package/backend/nodemon.json +1 -1
  3. package/backend/package-lock.json +2539 -2539
  4. package/backend/package.json +36 -33
  5. package/backend/prisma/generated/client/default.d.ts +1 -0
  6. package/backend/prisma/generated/client/default.js +1 -0
  7. package/backend/prisma/generated/client/edge.d.ts +1 -0
  8. package/backend/prisma/generated/client/edge.js +280 -0
  9. package/backend/prisma/generated/client/index-browser.js +272 -0
  10. package/backend/prisma/generated/client/index.d.ts +16892 -0
  11. package/backend/prisma/generated/client/index.js +301 -0
  12. package/backend/prisma/generated/client/package.json +97 -0
  13. package/backend/prisma/generated/client/query_engine-windows.dll.node +0 -0
  14. package/backend/prisma/generated/client/runtime/edge-esm.js +31 -0
  15. package/backend/prisma/generated/client/runtime/edge.js +31 -0
  16. package/backend/prisma/generated/client/runtime/index-browser.d.ts +365 -0
  17. package/backend/prisma/generated/client/runtime/index-browser.js +13 -0
  18. package/backend/prisma/generated/client/runtime/library.d.ts +3403 -0
  19. package/backend/prisma/generated/client/runtime/library.js +143 -0
  20. package/backend/prisma/generated/client/runtime/react-native.js +80 -0
  21. package/backend/prisma/generated/client/runtime/wasm.js +32 -0
  22. package/backend/prisma/generated/client/schema.prisma +148 -0
  23. package/backend/prisma/generated/client/wasm.d.ts +1 -0
  24. package/backend/prisma/generated/client/wasm.js +272 -0
  25. package/backend/prisma/migrations/20250615232848_add_scheduled_tasks/migration.sql +13 -0
  26. package/backend/prisma/schema.prisma +150 -137
  27. package/backend/src/api/routes/tasks.js +95 -0
  28. package/backend/src/core/BotManager.js +8 -0
  29. package/backend/src/core/PluginManager.js +63 -17
  30. package/backend/src/core/TaskScheduler.js +117 -0
  31. package/backend/src/real-time/socketHandler.js +30 -30
  32. package/backend/src/server.js +22 -20
  33. package/frontend/dist/assets/index-4S5VJ11r.js +8179 -0
  34. package/frontend/dist/assets/index-B5_cke-P.css +1 -0
  35. package/frontend/dist/favicon.svg +2 -2
  36. package/frontend/dist/index.html +20 -20
  37. package/frontend/dist/logo.svg +178 -178
  38. package/frontend/dist/site.webmanifest +20 -20
  39. package/frontend/package.json +66 -65
  40. package/package.json +38 -38
  41. package/frontend/dist/assets/index-8hxjI7oG.css +0 -1
  42. package/frontend/dist/assets/index-Brxc-96r.js +0 -8179
@@ -28,6 +28,9 @@ const DATA_DIR = path.join(os.homedir(), '.blockmine');
28
28
  if (TELEMETRY_ENABLED && STATS_SERVER_URL) {
29
29
  const idPath = path.join(DATA_DIR, '.instance_id');
30
30
  try {
31
+ if (!fs.existsSync(DATA_DIR)) {
32
+ fs.mkdirSync(DATA_DIR, { recursive: true });
33
+ }
31
34
  instanceId = fs.readFileSync(idPath, 'utf-8');
32
35
  } catch (e) {
33
36
  instanceId = uuidv4();
@@ -348,6 +351,11 @@ class BotManager {
348
351
 
349
352
  const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
350
353
  if (!allowedTypes.includes(typeChat) && !user.isOwner) {
354
+
355
+ if (typeChat === 'global') {
356
+ return;
357
+ }
358
+
351
359
  this._sendThrottledWarning(
352
360
  botConfig.id,
353
361
  username,
@@ -1,5 +1,5 @@
1
1
  const path = require('path');
2
- const fs = require('fs/promises');
2
+ const fse = require('fs-extra');
3
3
  const os = require('os');
4
4
  const { PrismaClient } = require('@prisma/client');
5
5
  const AdmZip = require('adm-zip');
@@ -42,13 +42,13 @@ class PluginManager {
42
42
  }
43
43
 
44
44
  async ensureBaseDirExists() {
45
- await fs.mkdir(PLUGINS_BASE_DIR, { recursive: true }).catch(console.error);
45
+ await fse.mkdir(PLUGINS_BASE_DIR, { recursive: true }).catch(console.error);
46
46
  }
47
47
 
48
48
  async installFromLocalPath(botId, directoryPath) {
49
49
  const newPlugin = await this.registerPlugin(botId, directoryPath, 'LOCAL', directoryPath);
50
50
  try {
51
- const packageJson = JSON.parse(await fs.readFile(path.join(directoryPath, 'package.json'), 'utf-8'));
51
+ const packageJson = JSON.parse(await fse.readFile(path.join(directoryPath, 'package.json'), 'utf-8'));
52
52
  reportPluginDownload(packageJson.name);
53
53
  } catch(e) {
54
54
  console.error('Не удалось прочитать package.json для отправки статистики локального плагина');
@@ -58,7 +58,7 @@ class PluginManager {
58
58
 
59
59
  async installFromGithub(botId, repoUrl, prismaClient = prisma) {
60
60
  const botPluginsDir = path.join(PLUGINS_BASE_DIR, `bot_${botId}`);
61
- await fs.mkdir(botPluginsDir, { recursive: true });
61
+ await fse.mkdir(botPluginsDir, { recursive: true });
62
62
 
63
63
  const existing = await prismaClient.installedPlugin.findFirst({ where: { botId, sourceUri: repoUrl } });
64
64
  if (existing) throw new Error(`Плагин из ${repoUrl} уже установлен.`);
@@ -88,13 +88,21 @@ class PluginManager {
88
88
  const repoName = path.basename(repoPath);
89
89
  const localPath = path.join(botPluginsDir, repoName);
90
90
 
91
+ if (await fse.pathExists(localPath)) {
92
+ await fse.remove(localPath);
93
+ }
94
+ const tempExtractPath = path.join(botPluginsDir, rootFolderName);
95
+ if (await fse.pathExists(tempExtractPath)) {
96
+ await fse.remove(tempExtractPath);
97
+ }
98
+
91
99
  zip.extractAllTo(botPluginsDir, true);
92
100
 
93
- await fs.rename(path.join(botPluginsDir, rootFolderName), localPath);
101
+ await fse.move(tempExtractPath, localPath, { overwrite: true });
94
102
 
95
103
  const newPlugin = await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient);
96
104
 
97
- const packageJson = JSON.parse(await fs.readFile(path.join(localPath, 'package.json'), 'utf-8'));
105
+ const packageJson = JSON.parse(await fse.readFile(path.join(localPath, 'package.json'), 'utf-8'));
98
106
  reportPluginDownload(packageJson.name);
99
107
 
100
108
  return newPlugin;
@@ -112,7 +120,7 @@ class PluginManager {
112
120
  const packageJsonPath = path.join(directoryPath, 'package.json');
113
121
  let packageJson;
114
122
  try {
115
- packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
123
+ packageJson = JSON.parse(await fse.readFile(packageJsonPath, 'utf-8'));
116
124
  } catch (e) {
117
125
  throw new Error(`Не удалось прочитать или распарсить package.json в плагине по пути: ${directoryPath}`);
118
126
  }
@@ -138,29 +146,67 @@ class PluginManager {
138
146
  const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
139
147
  if (!plugin) throw new Error('Плагин не найден');
140
148
 
149
+ const pluginOwnerId = `plugin:${plugin.name}`;
150
+ console.log(`[PluginManager] Начало удаления плагина ${plugin.name} (ID: ${plugin.id}) и его ресурсов.`);
151
+ console.log(`[PluginManager] Идентификатор владельца для очистки: ${pluginOwnerId}`);
152
+
141
153
  try {
142
154
  const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
143
155
  const mainFile = manifest.main || 'index.js';
144
156
  const entryPointPath = path.join(plugin.path, mainFile);
145
157
 
146
- await fs.access(entryPointPath);
147
- const pluginModule = require(entryPointPath);
158
+ if (await fse.pathExists(entryPointPath)) {
159
+ if (require.cache[require.resolve(entryPointPath)]) {
160
+ delete require.cache[require.resolve(entryPointPath)];
161
+ }
162
+
163
+ const pluginModule = require(entryPointPath);
148
164
 
149
- if (pluginModule && typeof pluginModule.onUnload === 'function') {
150
- console.log(`[PluginManager] Вызов хука onUnload для плагина ${plugin.name}...`);
151
- await pluginModule.onUnload({ botId: plugin.botId, prisma });
152
- console.log(`[PluginManager] Хук onUnload для ${plugin.name} успешно выполнен.`);
165
+ if (pluginModule && typeof pluginModule.onUnload === 'function') {
166
+ console.log(`[PluginManager] Вызов хука onUnload для плагина ${plugin.name}...`);
167
+ await pluginModule.onUnload({ botId: plugin.botId, prisma });
168
+ console.log(`[PluginManager] Хук onUnload для ${plugin.name} успешно выполнен.`);
169
+ }
170
+ } else {
171
+ console.warn(`[PluginManager] Главный файл плагина ${entryPointPath} не найден. Хук onUnload пропущен.`);
153
172
  }
154
173
  } catch (error) {
155
174
  console.error(`[PluginManager] Ошибка при выполнении хука onUnload для плагина ${plugin.name}:`, error);
156
175
  }
157
176
 
158
- if (plugin.sourceType === 'GITHUB' || plugin.sourceType === 'IMPORTED') {
159
- await fs.rm(plugin.path, { recursive: true, force: true }).catch(err => {
160
- console.error(`Не удалось удалить папку плагина ${plugin.path}:`, err);
177
+ try {
178
+ await prisma.$transaction(async (tx) => {
179
+ const deletedCommands = await tx.command.deleteMany({
180
+ where: { botId: plugin.botId, owner: pluginOwnerId },
181
+ });
182
+ if (deletedCommands.count > 0) console.log(`[DB Cleanup] Удалено команд: ${deletedCommands.count}`);
183
+
184
+ const deletedPermissions = await tx.permission.deleteMany({
185
+ where: { botId: plugin.botId, owner: pluginOwnerId },
186
+ });
187
+ if (deletedPermissions.count > 0) console.log(`[DB Cleanup] Удалено прав: ${deletedPermissions.count}`);
188
+
189
+ const deletedGroups = await tx.group.deleteMany({
190
+ where: { botId: plugin.botId, owner: pluginOwnerId },
191
+ });
192
+ if (deletedGroups.count > 0) console.log(`[DB Cleanup] Удалено групп: ${deletedGroups.count}`);
193
+
194
+ await tx.installedPlugin.delete({ where: { id: pluginId } });
195
+ console.log(`[DB Cleanup] Запись о плагине ${plugin.name} удалена.`);
161
196
  });
197
+ } catch (dbError) {
198
+ console.error(`[PluginManager] Ошибка при очистке БД для плагина ${plugin.name}:`, dbError);
199
+ throw new Error('Ошибка при удалении данных плагина из БД. Файлы не были удалены.');
200
+ }
201
+
202
+ if (plugin.sourceType === 'GITHUB' || plugin.sourceType === 'IMPORTED') {
203
+ try {
204
+ await fse.remove(plugin.path);
205
+ console.log(`[PluginManager] Папка плагина ${plugin.path} успешно удалена.`);
206
+ } catch (fileError) {
207
+ console.error(`Не удалось удалить папку плагина ${plugin.path}:`, fileError);
208
+ }
162
209
  }
163
- await prisma.installedPlugin.delete({ where: { id: pluginId } });
164
210
  }
165
211
 
166
212
  async checkForUpdates(botId, catalog) {
@@ -0,0 +1,117 @@
1
+ const cron = require('node-cron');
2
+ const { PrismaClient } = require('@prisma/client');
3
+ const BotManager = require('./BotManager');
4
+
5
+ const prisma = new PrismaClient();
6
+
7
+ class TaskScheduler {
8
+ constructor() {
9
+ this.scheduledJobs = new Map();
10
+ console.log('[TaskScheduler] Сервис планировщика инициализирован.');
11
+ }
12
+
13
+ async initialize() {
14
+ console.log('[TaskScheduler] Загрузка и планирование активных задач из БД...');
15
+ const tasks = await prisma.scheduledTask.findMany({ where: { isEnabled: true } });
16
+
17
+ tasks.forEach(task => {
18
+ this.scheduleTask(task);
19
+ });
20
+
21
+ console.log(`[TaskScheduler] Запланировано ${this.scheduledJobs.size} задач.`);
22
+ }
23
+
24
+ async executeTask(task) {
25
+ console.log(`[TaskScheduler] Выполнение задачи: "${task.name}" (ID: ${task.id})`);
26
+ let botIds = [];
27
+
28
+ try {
29
+ const targetIds = JSON.parse(task.targetBotIds);
30
+ if (Array.isArray(targetIds) && targetIds[0] === 'ALL') {
31
+ const allBots = await prisma.bot.findMany({ select: { id: true } });
32
+ botIds = allBots.map(b => b.id);
33
+ } else {
34
+ botIds = targetIds.map(id => parseInt(id, 10));
35
+ }
36
+ } catch (e) {
37
+ console.error(`[TaskScheduler] Ошибка парсинга targetBotIds для задачи ${task.id}:`, e.message);
38
+ return;
39
+ }
40
+
41
+ for (const botId of botIds) {
42
+ try {
43
+ const botConfig = await prisma.bot.findUnique({ where: { id: botId }, include: { server: true } });
44
+ if (!botConfig) continue;
45
+
46
+ switch (task.action) {
47
+ case 'START_BOT':
48
+ console.log(` -> Запуск бота ${botConfig.username}`);
49
+ if (!BotManager.bots.has(botId)) await BotManager.startBot(botConfig);
50
+ break;
51
+ case 'STOP_BOT':
52
+ console.log(` -> Остановка бота ${botConfig.username}`);
53
+ if (BotManager.bots.has(botId)) BotManager.stopBot(botId);
54
+ break;
55
+ case 'RESTART_BOT':
56
+ console.log(` -> Перезапуск бота ${botConfig.username}`);
57
+ if (BotManager.bots.has(botId)) {
58
+ BotManager.stopBot(botId);
59
+ setTimeout(() => BotManager.startBot(botConfig), 5000);
60
+ } else {
61
+ await BotManager.startBot(botConfig);
62
+ }
63
+ break;
64
+ case 'SEND_COMMAND':
65
+ if (BotManager.bots.has(botId)) {
66
+ const payload = JSON.parse(task.payload || '{}');
67
+ if (payload.command) {
68
+ console.log(` -> Отправка команды "${payload.command}" боту ${botConfig.username}`);
69
+ BotManager.sendMessageToBot(botId, payload.command);
70
+ }
71
+ }
72
+ break;
73
+ }
74
+ } catch (error) {
75
+ console.error(`[TaskScheduler] Ошибка выполнения действия для бота ID ${botId}:`, error);
76
+ }
77
+ }
78
+ await prisma.scheduledTask.update({ where: { id: task.id }, data: { lastRun: new Date() } });
79
+ }
80
+
81
+ scheduleTask(task) {
82
+ if (this.scheduledJobs.has(task.id)) {
83
+ this.unscheduleTask(task.id);
84
+ }
85
+
86
+ if (!cron.validate(task.cronPattern)) {
87
+ console.error(`[TaskScheduler] Неверный cron-паттерн для задачи ID ${task.id}: ${task.cronPattern}`);
88
+ return;
89
+ }
90
+
91
+ const job = cron.schedule(task.cronPattern, () => this.executeTask(task), {
92
+ scheduled: true,
93
+ timezone: "Europe/Moscow"
94
+ });
95
+
96
+ this.scheduledJobs.set(task.id, job);
97
+ console.log(`[TaskScheduler] Задача "${task.name}" запланирована с паттерном: ${task.cronPattern}`);
98
+ }
99
+
100
+ unscheduleTask(taskId) {
101
+ const job = this.scheduledJobs.get(taskId);
102
+ if (job) {
103
+ job.stop();
104
+ this.scheduledJobs.delete(taskId);
105
+ console.log(`[TaskScheduler] Задача ID ${taskId} снята с планирования.`);
106
+ }
107
+ }
108
+
109
+ async updateTask(updatedTask) {
110
+ this.unscheduleTask(updatedTask.id);
111
+ if (updatedTask.isEnabled) {
112
+ this.scheduleTask(updatedTask);
113
+ }
114
+ }
115
+ }
116
+
117
+ module.exports = new TaskScheduler();
@@ -1,31 +1,31 @@
1
- const { Server } = require('socket.io');
2
-
3
- let io;
4
-
5
- function initializeSocket(httpServer) {
6
- io = new Server(httpServer, {
7
- cors: {
8
- origin: "http://localhost:5173",
9
- methods: ["GET", "POST"]
10
- }
11
- });
12
-
13
- io.on('connection', (socket) => {
14
- console.log('A user connected to Socket.IO');
15
- socket.on('disconnect', () => {
16
- console.log('User disconnected');
17
- });
18
- });
19
-
20
- console.log('Socket.IO initialized');
21
- return io;
22
- }
23
-
24
- function getIO() {
25
- if (!io) {
26
- throw new Error("Socket.IO not initialized!");
27
- }
28
- return io;
29
- }
30
-
1
+ const { Server } = require('socket.io');
2
+
3
+ let io;
4
+
5
+ function initializeSocket(httpServer) {
6
+ io = new Server(httpServer, {
7
+ cors: {
8
+ origin: "http://localhost:5173",
9
+ methods: ["GET", "POST"]
10
+ }
11
+ });
12
+
13
+ io.on('connection', (socket) => {
14
+ console.log('A user connected to Socket.IO');
15
+ socket.on('disconnect', () => {
16
+ console.log('User disconnected');
17
+ });
18
+ });
19
+
20
+ console.log('Socket.IO initialized');
21
+ return io;
22
+ }
23
+
24
+ function getIO() {
25
+ if (!io) {
26
+ throw new Error("Socket.IO not initialized!");
27
+ }
28
+ return io;
29
+ }
30
+
31
31
  module.exports = { initializeSocket, getIO };
@@ -2,22 +2,15 @@ const express = require('express');
2
2
  const http = require('http');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
- const os = require('os');
6
-
7
- const DATA_DIR = path.join(os.homedir(), '.blockmine');
8
- if (!fs.existsSync(DATA_DIR)) {
9
- console.log(`[Server] Создание папки для данных: ${DATA_DIR}`);
10
- fs.mkdirSync(DATA_DIR, { recursive: true });
11
- }
12
-
13
- process.env.DATABASE_URL = `file:${path.join(DATA_DIR, 'blockmine.db')}`;
14
5
 
15
6
  const { initializeSocket } = require('./real-time/socketHandler');
16
7
  const botRoutes = require('./api/routes/bots');
17
8
  const pluginRoutes = require('./api/routes/plugins');
18
9
  const serverRoutes = require('./api/routes/servers');
19
10
  const permissionsRoutes = require('./api/routes/permissions');
11
+ const taskRoutes = require('./api/routes/tasks');
20
12
  const BotManager = require('./core/BotManager');
13
+ const TaskScheduler = require('./core/TaskScheduler');
21
14
 
22
15
  const app = express();
23
16
  const server = http.createServer(app);
@@ -28,8 +21,10 @@ const PORT = process.env.PORT || 3001;
28
21
 
29
22
  app.use(express.json());
30
23
 
31
- const frontendPath = path.join(__dirname, '..', '..', 'frontend', 'dist');
32
- const rootPath = path.join(__dirname, '..', '..');
24
+
25
+ const frontendPath = path.resolve(__dirname, '..', '..', 'frontend', 'dist');
26
+ const rootPath = path.resolve(__dirname, '..', '..');
27
+
33
28
 
34
29
  app.get('/api/version', async (req, res) => {
35
30
  try {
@@ -43,27 +38,35 @@ app.get('/api/version', async (req, res) => {
43
38
  }
44
39
  });
45
40
 
41
+ app.use('/api/tasks', taskRoutes);
46
42
  app.use('/api/bots', botRoutes);
47
43
  app.use('/api/plugins', pluginRoutes);
48
44
  app.use('/api/servers', serverRoutes);
49
45
  app.use('/api/permissions', permissionsRoutes);
50
46
 
47
+
48
+
51
49
  app.use(express.static(frontendPath));
52
50
 
53
51
  app.get(/^(?!\/api).*/, (req, res) => {
54
- res.sendFile(path.join(frontendPath, 'index.html'), (err) => {
55
- if (err && !res.headersSent) {
56
- console.error(`Ошибка при отправке index.html для пути ${req.path}:`, err);
57
- res.status(500).send("Не удалось загрузить приложение.");
58
- }
59
- });
52
+ const indexPath = path.join(frontendPath, 'index.html');
53
+
54
+ if (fs.existsSync(indexPath)) {
55
+ res.sendFile(indexPath);
56
+ } else {
57
+ console.error(`Критическая ошибка: файл index.html не найден по пути ${indexPath}`);
58
+ res.status(404).send(
59
+ '<h1>Файлы фронтенда не найдены!1!!!!111</h1>'
60
+ );
61
+ }
60
62
  });
61
63
 
62
64
  async function startServer() {
63
65
  return new Promise((resolve) => {
64
- server.listen(PORT, () => {
66
+ server.listen(PORT, async () => {
65
67
  console.log(`Backend сервер успешно запущен на http://localhost:${PORT}`);
66
68
  console.log(`Панель управления доступна по адресу: http://localhost:${PORT}`);
69
+ await TaskScheduler.initialize();
67
70
  resolve(server);
68
71
  });
69
72
  });
@@ -101,5 +104,4 @@ module.exports = { startServer, app, server };
101
104
 
102
105
  if (require.main === module) {
103
106
  startServer();
104
- }
105
-
107
+ }