blockmine 1.25.0 → 1.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -3
- package/backend/cli.js +1 -1
- package/backend/package.json +2 -2
- package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +2 -0
- package/backend/src/api/routes/apiKeys.js +8 -0
- package/backend/src/api/routes/bots.js +258 -9
- package/backend/src/api/routes/eventGraphs.js +151 -1
- package/backend/src/api/routes/health.js +38 -0
- package/backend/src/api/routes/nodeRegistry.js +63 -0
- package/backend/src/api/routes/plugins.js +254 -29
- package/backend/src/container.js +11 -8
- package/backend/src/core/BotCommandLoader.js +161 -0
- package/backend/src/core/BotConnection.js +125 -0
- package/backend/src/core/BotEventHandlers.js +234 -0
- package/backend/src/core/BotIPCHandler.js +445 -0
- package/backend/src/core/BotManager.js +15 -7
- package/backend/src/core/BotProcess.js +75 -142
- package/backend/src/core/EventGraphManager.js +7 -3
- package/backend/src/core/GraphDebugHandler.js +229 -0
- package/backend/src/core/GraphDebugIPC.js +117 -0
- package/backend/src/core/GraphExecutionEngine.js +545 -978
- package/backend/src/core/GraphTraversal.js +80 -0
- package/backend/src/core/GraphValidation.js +73 -0
- package/backend/src/core/NodeDefinition.js +138 -0
- package/backend/src/core/NodeRegistry.js +153 -141
- package/backend/src/core/PluginManager.js +272 -31
- package/backend/src/core/RewindSignal.js +9 -0
- package/backend/src/core/config/ConfigValidator.js +72 -0
- package/backend/src/core/config/FeatureFlags.js +52 -0
- package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
- package/backend/src/core/domain/entities/Bot.js +39 -0
- package/backend/src/core/domain/entities/Command.js +41 -0
- package/backend/src/core/domain/entities/EventGraph.js +39 -0
- package/backend/src/core/domain/entities/Plugin.js +45 -0
- package/backend/src/core/domain/entities/User.js +40 -0
- package/backend/src/core/domain/services/DependencyResolver.js +168 -0
- package/backend/src/core/domain/services/GraphValidator.js +117 -0
- package/backend/src/core/domain/services/PermissionChecker.js +34 -0
- package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
- package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
- package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
- package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
- package/backend/src/core/errors/BaseError.js +29 -0
- package/backend/src/core/errors/ErrorHandler.js +81 -0
- package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
- package/backend/src/core/errors/index.js +68 -0
- package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
- package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
- package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
- package/backend/src/core/infrastructure/RateLimiter.js +64 -0
- package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
- package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
- package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
- package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
- package/backend/src/core/ipc/botApiFactory.js +72 -0
- package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
- package/backend/src/core/logging/AuditLogger.js +61 -0
- package/backend/src/core/logging/StructuredLogger.js +80 -0
- package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
- package/backend/src/core/logging/index.js +7 -0
- package/backend/src/core/metrics/MetricsCollector.js +104 -0
- package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
- package/backend/src/core/node-registries/actionsNodes.js +191 -0
- package/backend/src/core/node-registries/arraysNodes.js +152 -0
- package/backend/src/core/node-registries/botNodes.js +48 -0
- package/backend/src/core/node-registries/containerNodes.js +141 -0
- package/backend/src/core/node-registries/dataNodes.js +284 -0
- package/backend/src/core/node-registries/debugNodes.js +23 -0
- package/backend/src/core/node-registries/eventsNodes.js +223 -0
- package/backend/src/core/node-registries/flowNodes.js +151 -0
- package/backend/src/core/node-registries/furnaceNodes.js +123 -0
- package/backend/src/core/node-registries/index.js +108 -0
- package/backend/src/core/node-registries/inventory.js +102 -106
- package/backend/src/core/node-registries/logicNodes.js +54 -0
- package/backend/src/core/node-registries/mathNodes.js +38 -0
- package/backend/src/core/node-registries/navigationNodes.js +109 -0
- package/backend/src/core/node-registries/objectsNodes.js +90 -0
- package/backend/src/core/node-registries/stringsNodes.js +165 -0
- package/backend/src/core/node-registries/timeNodes.js +105 -0
- package/backend/src/core/node-registries/typeNodes.js +22 -0
- package/backend/src/core/node-registries/usersNodes.js +126 -0
- package/backend/src/core/nodes/arrays/shuffle.js +14 -0
- package/backend/src/core/nodes/bot/get_name.js +8 -0
- package/backend/src/core/nodes/bot/stop_bot.js +5 -0
- package/backend/src/core/nodes/container/open.js +101 -111
- package/backend/src/core/nodes/data/store_read.js +26 -0
- package/backend/src/core/nodes/data/store_write.js +23 -0
- package/backend/src/core/nodes/event/call_event.js +31 -0
- package/backend/src/core/nodes/event/custom_event.js +8 -0
- package/backend/src/core/nodes/flow/timer.js +35 -0
- package/backend/src/core/nodes/inventory/drop.js +73 -65
- package/backend/src/core/nodes/inventory/equip.js +54 -45
- package/backend/src/core/nodes/inventory/select_slot.js +48 -46
- package/backend/src/core/nodes/navigation/follow.js +54 -51
- package/backend/src/core/nodes/navigation/go_to.js +41 -53
- package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
- package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
- package/backend/src/core/nodes/navigation/stop.js +17 -26
- package/backend/src/core/nodes/users/add_to_group.js +24 -0
- package/backend/src/core/nodes/users/check_permission.js +26 -0
- package/backend/src/core/nodes/users/remove_from_group.js +24 -0
- package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
- package/backend/src/core/services/BotLifecycleService.js +41 -632
- package/backend/src/core/services/CacheManager.js +83 -23
- package/backend/src/core/services/CrashRestartManager.js +42 -0
- package/backend/src/core/services/DebugSessionManager.js +114 -12
- package/backend/src/core/services/EventGraphService.js +69 -0
- package/backend/src/core/services/MinecraftBotManager.js +9 -1
- package/backend/src/core/services/PluginManagementService.js +84 -0
- package/backend/src/core/services/TestModeContext.js +65 -0
- package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
- package/backend/src/core/services.js +1 -11
- package/backend/src/core/validation/InputValidator.js +167 -0
- package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
- package/backend/src/real-time/botApi/index.js +1 -1
- package/backend/src/real-time/socketHandler.js +26 -0
- package/backend/src/server.js +10 -5
- package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
- package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
- package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/locales/en/bots.json +4 -1
- package/frontend/dist/locales/en/common.json +7 -1
- package/frontend/dist/locales/en/login.json +2 -0
- package/frontend/dist/locales/en/management.json +79 -1
- package/frontend/dist/locales/en/nodes.json +59 -4
- package/frontend/dist/locales/en/plugin-detail.json +24 -4
- package/frontend/dist/locales/en/plugins.json +226 -7
- package/frontend/dist/locales/en/setup.json +2 -0
- package/frontend/dist/locales/en/sidebar.json +171 -3
- package/frontend/dist/locales/en/visual-editor.json +230 -31
- package/frontend/dist/locales/ru/bots.json +4 -1
- package/frontend/dist/locales/ru/login.json +2 -0
- package/frontend/dist/locales/ru/management.json +79 -1
- package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
- package/frontend/dist/locales/ru/nodes.json +105 -51
- package/frontend/dist/locales/ru/plugins.json +103 -4
- package/frontend/dist/locales/ru/setup.json +2 -0
- package/frontend/dist/locales/ru/sidebar.json +171 -3
- package/frontend/dist/locales/ru/visual-editor.json +232 -33
- package/frontend/package.json +2 -0
- package/nul +12 -0
- package/package.json +3 -3
- package/backend/package-lock.json +0 -6801
- package/backend/src/core/node-registries/actions.js +0 -202
- package/backend/src/core/node-registries/arrays.js +0 -155
- package/backend/src/core/node-registries/bot.js +0 -23
- package/backend/src/core/node-registries/container.js +0 -162
- package/backend/src/core/node-registries/data.js +0 -290
- package/backend/src/core/node-registries/debug.js +0 -26
- package/backend/src/core/node-registries/events.js +0 -201
- package/backend/src/core/node-registries/flow.js +0 -139
- package/backend/src/core/node-registries/furnace.js +0 -143
- package/backend/src/core/node-registries/logic.js +0 -62
- package/backend/src/core/node-registries/math.js +0 -42
- package/backend/src/core/node-registries/navigation.js +0 -111
- package/backend/src/core/node-registries/objects.js +0 -98
- package/backend/src/core/node-registries/strings.js +0 -187
- package/backend/src/core/node-registries/time.js +0 -113
- package/backend/src/core/node-registries/type.js +0 -25
- package/backend/src/core/node-registries/users.js +0 -79
- package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const nodeRegistry = require('../../core/NodeRegistry');
|
|
4
|
+
|
|
5
|
+
router.get('/nodes', (req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const graphType = req.query.graphType || null;
|
|
8
|
+
const flat = nodeRegistry.getAllNodes().map(node => {
|
|
9
|
+
const json = node.toJSON ? node.toJSON() : node;
|
|
10
|
+
return json;
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (graphType) {
|
|
14
|
+
const filtered = flat.filter(node =>
|
|
15
|
+
node.graphType === graphType || node.graphType === 'ALL'
|
|
16
|
+
);
|
|
17
|
+
return res.json({ nodes: filtered, count: filtered.length });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
res.json({ nodes: flat, count: flat.length });
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('[API] Error getting nodes:', error);
|
|
23
|
+
res.status(500).json({ error: 'Failed to get nodes', message: error.message });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
router.get('/nodes/categories', (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const graphType = req.query.graphType || null;
|
|
30
|
+
const byCategory = nodeRegistry.getNodesByCategory(graphType);
|
|
31
|
+
|
|
32
|
+
const result = {};
|
|
33
|
+
for (const [category, nodes] of Object.entries(byCategory)) {
|
|
34
|
+
result[category] = nodes.map(node => {
|
|
35
|
+
return node.toJSON ? node.toJSON() : node;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
res.json({ categories: result, count: Object.keys(result).length });
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('[API] Error getting nodes by category:', error);
|
|
42
|
+
res.status(500).json({ error: 'Failed to get nodes', message: error.message });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
router.get('/nodes/:type', (req, res) => {
|
|
47
|
+
try {
|
|
48
|
+
const { type } = req.params;
|
|
49
|
+
const node = nodeRegistry.getNodeConfig(type);
|
|
50
|
+
|
|
51
|
+
if (!node) {
|
|
52
|
+
return res.status(404).json({ error: 'Node type not found', type });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const json = node.toJSON ? node.toJSON() : node;
|
|
56
|
+
res.json({ node: json });
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error('[API] Error getting node:', error);
|
|
59
|
+
res.status(500).json({ error: 'Failed to get node', message: error.message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
module.exports = router;
|
|
@@ -6,23 +6,224 @@ const { pluginManager } = require('../../core/services');
|
|
|
6
6
|
|
|
7
7
|
const prisma = new PrismaClient();
|
|
8
8
|
const OFFICIAL_CATALOG_URL = "https://raw.githubusercontent.com/blockmineJS/official-plugins-list/main/index.json";
|
|
9
|
+
const CATALOG_TTL_MS = 5 * 60 * 1000;
|
|
10
|
+
const PLUGIN_DETAIL_TTL_MS = 10 * 60 * 1000;
|
|
11
|
+
const PLUGIN_CHANGELOG_TTL_MS = 10 * 60 * 1000;
|
|
12
|
+
|
|
13
|
+
let catalogCache = {
|
|
14
|
+
data: null,
|
|
15
|
+
expiresAt: 0,
|
|
16
|
+
pending: null,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const pluginDetailCache = new Map();
|
|
20
|
+
const pluginChangelogCache = new Map();
|
|
21
|
+
const GITHUB_REQUEST_TIMEOUT_MS = 10000;
|
|
22
|
+
|
|
23
|
+
function getGithubHeaders(extra = {}) {
|
|
24
|
+
const headers = {
|
|
25
|
+
'Accept': 'application/vnd.github+json',
|
|
26
|
+
'User-Agent': 'BlockMine',
|
|
27
|
+
...extra
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (process.env.GITHUB_TOKEN) {
|
|
31
|
+
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
32
|
+
}
|
|
9
33
|
|
|
10
|
-
|
|
11
|
-
|
|
34
|
+
return headers;
|
|
35
|
+
}
|
|
12
36
|
|
|
13
|
-
|
|
37
|
+
async function fetchWithTimeout(url, options = {}) {
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeoutId = setTimeout(() => controller.abort(), GITHUB_REQUEST_TIMEOUT_MS);
|
|
14
40
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
42
|
+
} finally {
|
|
43
|
+
clearTimeout(timeoutId);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function fetchOfficialCatalog(force = false) {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
|
|
50
|
+
if (!force && catalogCache.data && catalogCache.expiresAt > now) {
|
|
51
|
+
return catalogCache.data;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (catalogCache.pending) {
|
|
55
|
+
return catalogCache.pending;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
catalogCache.pending = (async () => {
|
|
59
|
+
const response = await fetchWithTimeout(OFFICIAL_CATALOG_URL);
|
|
60
|
+
|
|
17
61
|
if (!response.ok) {
|
|
18
62
|
const errorText = await response.text();
|
|
19
63
|
console.error(`[API Error] Failed to fetch catalog from GitHub. Status: ${response.status}, Response: ${errorText}`);
|
|
20
64
|
throw new Error(`GitHub returned status ${response.status}`);
|
|
21
65
|
}
|
|
22
|
-
|
|
23
|
-
|
|
66
|
+
|
|
67
|
+
const data = await response.json();
|
|
68
|
+
catalogCache.data = data;
|
|
69
|
+
catalogCache.expiresAt = Date.now() + CATALOG_TTL_MS;
|
|
70
|
+
return data;
|
|
71
|
+
})();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
return await catalogCache.pending;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
catalogCache.data = null;
|
|
77
|
+
catalogCache.expiresAt = 0;
|
|
78
|
+
throw error;
|
|
79
|
+
} finally {
|
|
80
|
+
catalogCache.pending = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getCachedTimedValue(cache, key) {
|
|
85
|
+
const cached = cache.get(key);
|
|
86
|
+
if (!cached) return null;
|
|
87
|
+
if (cached.expiresAt <= Date.now()) {
|
|
88
|
+
cache.delete(key);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return cached.data;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function setCachedTimedValue(cache, key, data, ttlMs) {
|
|
95
|
+
cache.set(key, {
|
|
96
|
+
data,
|
|
97
|
+
expiresAt: Date.now() + ttlMs,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getCachedPluginDetail(pluginName) {
|
|
102
|
+
return getCachedTimedValue(pluginDetailCache, pluginName);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function setCachedPluginDetail(pluginName, data) {
|
|
106
|
+
setCachedTimedValue(pluginDetailCache, pluginName, data, PLUGIN_DETAIL_TTL_MS);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getCachedPluginChangelog(pluginName) {
|
|
110
|
+
return getCachedTimedValue(pluginChangelogCache, pluginName);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function setCachedPluginChangelog(pluginName, data) {
|
|
114
|
+
setCachedTimedValue(pluginChangelogCache, pluginName, data, PLUGIN_CHANGELOG_TTL_MS);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseGithubRepoInfo(repoUrl) {
|
|
118
|
+
if (typeof repoUrl !== 'string') {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const parsedUrl = new URL(repoUrl);
|
|
124
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
125
|
+
if (!['github.com', 'www.github.com'].includes(hostname)) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const pathParts = parsedUrl.pathname.split('/').filter(Boolean);
|
|
130
|
+
if (pathParts.length < 2) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
owner: pathParts[0],
|
|
136
|
+
repo: pathParts[1].replace(/\.git$/i, '')
|
|
137
|
+
};
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function fetchGithubReadme(owner, repo) {
|
|
144
|
+
const response = await fetchWithTimeout(
|
|
145
|
+
`https://api.github.com/repos/${owner}/${repo}/readme`,
|
|
146
|
+
{ headers: getGithubHeaders({ 'Accept': 'application/vnd.github.raw+json' }) }
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
if (response.status === 403) {
|
|
151
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
152
|
+
if (remaining === '0') {
|
|
153
|
+
console.warn(`[GitHub README] Rate limit reached while loading README for ${owner}/${repo}.`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return response.text();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function renderGithubMarkdown(markdown, owner, repo) {
|
|
163
|
+
if (!markdown) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const response = await fetchWithTimeout('https://api.github.com/markdown', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: getGithubHeaders({
|
|
170
|
+
'Accept': 'text/html',
|
|
171
|
+
'Content-Type': 'application/json'
|
|
172
|
+
}),
|
|
173
|
+
body: JSON.stringify({
|
|
174
|
+
text: markdown,
|
|
175
|
+
mode: 'gfm',
|
|
176
|
+
context: `${owner}/${repo}`
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
if (response.status === 403) {
|
|
182
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
183
|
+
if (remaining === '0') {
|
|
184
|
+
console.warn(`[GitHub Markdown] Rate limit reached while rendering ${owner}/${repo}.`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return response.text();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function fetchLatestGithubReleaseBody(repoUrl) {
|
|
194
|
+
const repoInfo = parseGithubRepoInfo(repoUrl);
|
|
195
|
+
if (!repoInfo) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const response = await fetchWithTimeout(
|
|
200
|
+
`https://api.github.com/repos/${repoInfo.owner}/${repoInfo.repo}/releases/latest`,
|
|
201
|
+
{ headers: getGithubHeaders() }
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
if (response.status === 403) {
|
|
206
|
+
const remaining = response.headers.get('x-ratelimit-remaining');
|
|
207
|
+
if (remaining === '0') {
|
|
208
|
+
console.warn(`[GitHub Releases] Rate limit reached while loading changelog for ${repoInfo.owner}/${repoInfo.repo}.`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const release = await response.json();
|
|
215
|
+
return typeof release?.body === 'string' ? release.body : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
router.get('/catalog', async (req, res) => {
|
|
220
|
+
try {
|
|
221
|
+
res.json(await fetchOfficialCatalog());
|
|
24
222
|
} catch (error) {
|
|
25
223
|
console.error(`[API Error] Could not fetch catalog URL. Reason: ${error.message}`);
|
|
224
|
+
if (error.name === 'AbortError' || error.message.includes('aborted') || error.message.includes('timed out')) {
|
|
225
|
+
return res.json([]);
|
|
226
|
+
}
|
|
26
227
|
res.status(500).json({ error: 'Не удалось загрузить каталог плагинов.' });
|
|
27
228
|
}
|
|
28
229
|
});
|
|
@@ -31,9 +232,7 @@ router.post('/check-updates/:botId', authenticateUniversal, authorize('plugin:up
|
|
|
31
232
|
try {
|
|
32
233
|
const botId = parseInt(req.params.botId);
|
|
33
234
|
|
|
34
|
-
const
|
|
35
|
-
if (!catalogResponse.ok) throw new Error('Не удалось загрузить каталог для проверки обновлений.');
|
|
36
|
-
const catalog = await catalogResponse.json();
|
|
235
|
+
const catalog = await fetchOfficialCatalog();
|
|
37
236
|
|
|
38
237
|
const updates = await pluginManager.checkForUpdates(botId, catalog);
|
|
39
238
|
res.json(updates);
|
|
@@ -47,7 +246,7 @@ router.post('/update/:pluginId', authenticateUniversal, authorize('plugin:update
|
|
|
47
246
|
try {
|
|
48
247
|
const pluginId = parseInt(req.params.pluginId);
|
|
49
248
|
const { targetTag } = req.body; // Получаем тег из тела запроса (если указан)
|
|
50
|
-
const updatedPlugin = await pluginManager.updatePlugin(pluginId, targetTag);
|
|
249
|
+
const updatedPlugin = await pluginManager.updatePlugin(pluginId, targetTag, req.body?.targetRepoUrl);
|
|
51
250
|
res.json(updatedPlugin);
|
|
52
251
|
} catch (error) {
|
|
53
252
|
res.status(500).json({ error: error.message });
|
|
@@ -92,6 +291,8 @@ router.get('/:id/info', authenticateUniversal, authorize('plugin:list'), async (
|
|
|
92
291
|
description: true,
|
|
93
292
|
sourceType: true,
|
|
94
293
|
sourceUri: true,
|
|
294
|
+
sourceRefType: true,
|
|
295
|
+
sourceRef: true,
|
|
95
296
|
isEnabled: true,
|
|
96
297
|
manifest: true,
|
|
97
298
|
settings: true,
|
|
@@ -142,6 +343,8 @@ router.get('/bot/:botId', authenticateUniversal, authorize('plugin:list'), async
|
|
|
142
343
|
description: true,
|
|
143
344
|
sourceType: true,
|
|
144
345
|
sourceUri: true,
|
|
346
|
+
sourceRefType: true,
|
|
347
|
+
sourceRef: true,
|
|
145
348
|
isEnabled: true,
|
|
146
349
|
manifest: true,
|
|
147
350
|
settings: true,
|
|
@@ -176,14 +379,42 @@ router.get('/bot/:botId', authenticateUniversal, authorize('plugin:list'), async
|
|
|
176
379
|
}
|
|
177
380
|
});
|
|
178
381
|
|
|
382
|
+
router.get('/catalog/:name/changelog', async (req, res) => {
|
|
383
|
+
try {
|
|
384
|
+
const pluginName = req.params.name;
|
|
385
|
+
const cachedChangelog = getCachedPluginChangelog(pluginName);
|
|
386
|
+
|
|
387
|
+
if (cachedChangelog !== null) {
|
|
388
|
+
return res.json({ body: cachedChangelog });
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const catalog = await fetchOfficialCatalog();
|
|
392
|
+
const pluginInfo = catalog.find((plugin) => plugin.name === pluginName);
|
|
393
|
+
|
|
394
|
+
if (!pluginInfo) {
|
|
395
|
+
return res.status(404).json({ error: 'Plugin not found in catalog.' });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const changelogBody = (await fetchLatestGithubReleaseBody(pluginInfo.repoUrl)) || '';
|
|
399
|
+
setCachedPluginChangelog(pluginName, changelogBody);
|
|
400
|
+
|
|
401
|
+
return res.json({ body: changelogBody });
|
|
402
|
+
} catch (error) {
|
|
403
|
+
console.error(`[API Error] /catalog/:name/changelog:`, error);
|
|
404
|
+
return res.status(500).json({ error: 'Failed to load plugin changelog.' });
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
179
408
|
router.get('/catalog/:name', async (req, res) => {
|
|
180
409
|
try {
|
|
181
410
|
const pluginName = req.params.name;
|
|
411
|
+
const cachedDetail = getCachedPluginDetail(pluginName);
|
|
182
412
|
|
|
183
|
-
|
|
184
|
-
|
|
413
|
+
if (cachedDetail) {
|
|
414
|
+
return res.json(cachedDetail);
|
|
415
|
+
}
|
|
185
416
|
|
|
186
|
-
const catalog = await
|
|
417
|
+
const catalog = await fetchOfficialCatalog();
|
|
187
418
|
const pluginInfo = catalog.find(p => p.name === pluginName);
|
|
188
419
|
|
|
189
420
|
if (!pluginInfo) {
|
|
@@ -191,6 +422,7 @@ router.get('/catalog/:name', async (req, res) => {
|
|
|
191
422
|
}
|
|
192
423
|
|
|
193
424
|
let readmeContent = pluginInfo.description || 'Описание для этого плагина не предоставлено.';
|
|
425
|
+
let readmeHtml = null;
|
|
194
426
|
|
|
195
427
|
try {
|
|
196
428
|
const urlParts = new URL(pluginInfo.repoUrl);
|
|
@@ -200,19 +432,10 @@ router.get('/catalog/:name', async (req, res) => {
|
|
|
200
432
|
const owner = pathParts[0];
|
|
201
433
|
const repo = pathParts[1].replace('.git', '');
|
|
202
434
|
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
`https://raw.githubusercontent.com/${owner}/${repo}/master/readme.md`,
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
for (const url of readmeUrls) {
|
|
211
|
-
const readmeResponse = await fetch(getCacheBustedUrl(url));
|
|
212
|
-
if (readmeResponse.ok) {
|
|
213
|
-
readmeContent = await readmeResponse.text();
|
|
214
|
-
break;
|
|
215
|
-
}
|
|
435
|
+
const fetchedReadme = await fetchGithubReadme(owner, repo);
|
|
436
|
+
if (fetchedReadme) {
|
|
437
|
+
readmeContent = fetchedReadme;
|
|
438
|
+
readmeHtml = await renderGithubMarkdown(fetchedReadme, owner, repo);
|
|
216
439
|
}
|
|
217
440
|
}
|
|
218
441
|
} catch (readmeError) {
|
|
@@ -221,9 +444,11 @@ router.get('/catalog/:name', async (req, res) => {
|
|
|
221
444
|
|
|
222
445
|
const finalPluginData = {
|
|
223
446
|
...pluginInfo,
|
|
224
|
-
fullDescription: readmeContent
|
|
447
|
+
fullDescription: readmeContent,
|
|
448
|
+
readmeHtml
|
|
225
449
|
};
|
|
226
450
|
|
|
451
|
+
setCachedPluginDetail(pluginName, finalPluginData);
|
|
227
452
|
res.json(finalPluginData);
|
|
228
453
|
} catch (error) {
|
|
229
454
|
console.error(`[API Error] /catalog/:name :`, error);
|
|
@@ -374,4 +599,4 @@ router.delete('/bot/:botId/:pluginName/store/:key', authenticateUniversal, autho
|
|
|
374
599
|
}
|
|
375
600
|
});
|
|
376
601
|
|
|
377
|
-
module.exports = router;
|
|
602
|
+
module.exports = router;
|
package/backend/src/container.js
CHANGED
|
@@ -19,10 +19,13 @@ const ResourceMonitorService = require('./core/services/ResourceMonitorService')
|
|
|
19
19
|
const TelemetryService = require('./core/services/TelemetryService');
|
|
20
20
|
const BotLifecycleService = require('./core/services/BotLifecycleService');
|
|
21
21
|
const CommandExecutionService = require('./core/services/CommandExecutionService');
|
|
22
|
+
const PluginManagementService = require('./core/services/PluginManagementService');
|
|
23
|
+
const EventGraphService = require('./core/services/EventGraphService');
|
|
22
24
|
|
|
23
25
|
// Core
|
|
24
26
|
const EventGraphManager = require('./core/EventGraphManager');
|
|
25
27
|
const PluginManager = require('./core/PluginManager');
|
|
28
|
+
const BotManager = require('./core/BotManager');
|
|
26
29
|
|
|
27
30
|
function createLogger() {
|
|
28
31
|
return {
|
|
@@ -36,14 +39,12 @@ function createLogger() {
|
|
|
36
39
|
function configureContainer() {
|
|
37
40
|
const container = createContainer();
|
|
38
41
|
|
|
39
|
-
// Infrastructure
|
|
40
42
|
container.register({
|
|
41
43
|
prisma: asValue(prisma),
|
|
42
44
|
config: asValue(config),
|
|
43
45
|
logger: asFunction(createLogger).singleton(),
|
|
44
46
|
});
|
|
45
47
|
|
|
46
|
-
// Repositories
|
|
47
48
|
container.register({
|
|
48
49
|
botRepository: asClass(BotRepository).singleton(),
|
|
49
50
|
commandRepository: asClass(CommandRepository).singleton(),
|
|
@@ -55,7 +56,6 @@ function configureContainer() {
|
|
|
55
56
|
groupRepository: asClass(GroupRepository).singleton(),
|
|
56
57
|
});
|
|
57
58
|
|
|
58
|
-
// Core Services
|
|
59
59
|
container.register({
|
|
60
60
|
cacheManager: asClass(CacheManager).singleton(),
|
|
61
61
|
botProcessManager: asClass(BotProcessManager).singleton(),
|
|
@@ -63,17 +63,20 @@ function configureContainer() {
|
|
|
63
63
|
telemetryService: asClass(TelemetryService).singleton(),
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
container.register({
|
|
67
|
+
botLifecycleService: asClass(BotLifecycleService).singleton(),
|
|
68
|
+
commandExecutionService: asClass(CommandExecutionService).singleton(),
|
|
69
|
+
pluginManagementService: asClass(PluginManagementService).singleton(),
|
|
70
|
+
eventGraphService: asClass(EventGraphService).singleton(),
|
|
71
|
+
});
|
|
72
|
+
|
|
67
73
|
container.register({
|
|
68
74
|
pluginManager: asClass(PluginManager).singleton(),
|
|
69
|
-
// EventGraphManager создаётся без зависимости - botManager передаётся через setter
|
|
70
75
|
eventGraphManager: asClass(EventGraphManager).singleton(),
|
|
71
76
|
});
|
|
72
77
|
|
|
73
|
-
// High-level Services (зависят от managers)
|
|
74
78
|
container.register({
|
|
75
|
-
|
|
76
|
-
commandExecutionService: asClass(CommandExecutionService).singleton(),
|
|
79
|
+
botManager: asClass(BotManager).singleton(),
|
|
77
80
|
});
|
|
78
81
|
|
|
79
82
|
return container;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const Command = require('./system/Command');
|
|
2
|
+
const { loadCommands } = require('./system/CommandRegistry');
|
|
3
|
+
const { MessageTypes } = require('./ipc/ipcMessageTypes');
|
|
4
|
+
|
|
5
|
+
async function loadBotCommands(bot, config, prisma) {
|
|
6
|
+
bot.commands = await loadCommands();
|
|
7
|
+
|
|
8
|
+
const dbCommands = await prisma.command.findMany({ where: { botId: config.id } });
|
|
9
|
+
|
|
10
|
+
for (const dbCommand of dbCommands) {
|
|
11
|
+
const existingCommand = bot.commands.get(dbCommand.name);
|
|
12
|
+
|
|
13
|
+
if (existingCommand) {
|
|
14
|
+
existingCommand.isEnabled = dbCommand.isEnabled;
|
|
15
|
+
existingCommand.description = dbCommand.description;
|
|
16
|
+
existingCommand.cooldown = dbCommand.cooldown;
|
|
17
|
+
existingCommand.aliases = JSON.parse(dbCommand.aliases || '[]');
|
|
18
|
+
existingCommand.permissionId = dbCommand.permissionId;
|
|
19
|
+
existingCommand.allowedChatTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
|
|
20
|
+
|
|
21
|
+
const aliases = JSON.parse(dbCommand.aliases || '[]');
|
|
22
|
+
for (const alias of aliases) {
|
|
23
|
+
bot.commands.set(alias, existingCommand);
|
|
24
|
+
}
|
|
25
|
+
} else if (dbCommand.isVisual) {
|
|
26
|
+
const visualCommand = createVisualCommand(bot, dbCommand);
|
|
27
|
+
bot.commands.set(visualCommand.name, visualCommand);
|
|
28
|
+
|
|
29
|
+
const visualAliases = JSON.parse(dbCommand.aliases || '[]');
|
|
30
|
+
for (const alias of visualAliases) {
|
|
31
|
+
bot.commands.set(alias, visualCommand);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const cmd of bot.commands.values()) {
|
|
37
|
+
if (cmd.aliases && Array.isArray(cmd.aliases)) {
|
|
38
|
+
for (const alias of cmd.aliases) {
|
|
39
|
+
if (!bot.commands.has(alias)) {
|
|
40
|
+
bot.commands.set(alias, cmd);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (process.send) {
|
|
47
|
+
for (const cmd of bot.commands.values()) {
|
|
48
|
+
process.send({
|
|
49
|
+
type: MessageTypes.COMMAND.REGISTER,
|
|
50
|
+
commandConfig: {
|
|
51
|
+
name: cmd.name,
|
|
52
|
+
description: cmd.description,
|
|
53
|
+
aliases: cmd.aliases,
|
|
54
|
+
owner: cmd.owner,
|
|
55
|
+
permissions: cmd.permissions,
|
|
56
|
+
cooldown: cmd.cooldown,
|
|
57
|
+
allowedChatTypes: cmd.allowedChatTypes,
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return bot.commands;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function createVisualCommand(bot, dbCommand) {
|
|
67
|
+
const visualCommand = new Command({
|
|
68
|
+
name: dbCommand.name,
|
|
69
|
+
description: dbCommand.description,
|
|
70
|
+
aliases: JSON.parse(dbCommand.aliases || '[]'),
|
|
71
|
+
cooldown: dbCommand.cooldown,
|
|
72
|
+
allowedChatTypes: JSON.parse(dbCommand.allowedChatTypes || '[]'),
|
|
73
|
+
args: JSON.parse(dbCommand.argumentsJson || '[]'),
|
|
74
|
+
owner: 'visual_editor',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
visualCommand.permissionId = dbCommand.permissionId;
|
|
78
|
+
visualCommand.graphJson = dbCommand.graphJson;
|
|
79
|
+
visualCommand.owner = 'visual_editor';
|
|
80
|
+
|
|
81
|
+
visualCommand.handler = (botInstance, typeChat, user, args) => {
|
|
82
|
+
const playerList = botInstance ? Object.keys(botInstance.players) : [];
|
|
83
|
+
const botState = botInstance ? { yaw: botInstance.entity.yaw, pitch: botInstance.entity.pitch } : {};
|
|
84
|
+
const botEntity = botInstance && botInstance.entity ? {
|
|
85
|
+
position: botInstance.entity.position,
|
|
86
|
+
yaw: botInstance.entity.yaw,
|
|
87
|
+
pitch: botInstance.entity.pitch
|
|
88
|
+
} : null;
|
|
89
|
+
|
|
90
|
+
const context = {
|
|
91
|
+
bot: botInstance,
|
|
92
|
+
botApi: botInstance.api,
|
|
93
|
+
user,
|
|
94
|
+
args,
|
|
95
|
+
typeChat,
|
|
96
|
+
players: playerList,
|
|
97
|
+
botState,
|
|
98
|
+
botEntity,
|
|
99
|
+
botId: botInstance.config.id,
|
|
100
|
+
graphId: dbCommand.id,
|
|
101
|
+
eventType: 'command',
|
|
102
|
+
eventArgs: {
|
|
103
|
+
commandName: dbCommand.name,
|
|
104
|
+
user: { username: user?.username },
|
|
105
|
+
args,
|
|
106
|
+
typeChat
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return botInstance.graphExecutionEngine.execute(visualCommand.graphJson, context);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return visualCommand;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function registerTemporaryCommand(bot, commandData) {
|
|
117
|
+
const tempCommand = new Command({
|
|
118
|
+
name: commandData.name,
|
|
119
|
+
description: commandData.description || '',
|
|
120
|
+
aliases: commandData.aliases || [],
|
|
121
|
+
cooldown: commandData.cooldown || 0,
|
|
122
|
+
allowedChatTypes: commandData.allowedChatTypes || ['chat', 'private'],
|
|
123
|
+
args: [],
|
|
124
|
+
owner: 'runtime',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
tempCommand.permissionId = commandData.permissionId || null;
|
|
128
|
+
tempCommand.isTemporary = true;
|
|
129
|
+
tempCommand.tempId = commandData.tempId;
|
|
130
|
+
tempCommand.isVisual = false;
|
|
131
|
+
tempCommand.handler = () => {};
|
|
132
|
+
|
|
133
|
+
bot.commands.set(commandData.name, tempCommand);
|
|
134
|
+
|
|
135
|
+
if (Array.isArray(commandData.aliases)) {
|
|
136
|
+
for (const alias of commandData.aliases) {
|
|
137
|
+
bot.commands.set(alias, tempCommand);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function unregisterTemporaryCommand(bot, commandName, aliases) {
|
|
143
|
+
if (bot.commands.has(commandName)) {
|
|
144
|
+
bot.commands.delete(commandName);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(aliases)) {
|
|
148
|
+
for (const alias of aliases) {
|
|
149
|
+
if (bot.commands.has(alias)) {
|
|
150
|
+
bot.commands.delete(alias);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
loadBotCommands,
|
|
158
|
+
createVisualCommand,
|
|
159
|
+
registerTemporaryCommand,
|
|
160
|
+
unregisterTemporaryCommand
|
|
161
|
+
};
|