GitHub Actions
Sync from GitHub (excluding README)
7a64905
const express = require('express');
const { KEY_TYPE_ADMIN } = require('../database');
const { EVENT_TYPES } = require('../constants/eventTypes');
const router = express.Router();
const DEFAULT_ADMIN_BLOCKED_COMMANDS = new Set([
'stop',
'deop',
'op',
'ban',
'ban-ip',
'banlist',
'pardon',
'pardon-ip',
'whitelist',
'kick',
'restart',
'reload',
'save-all',
'save-on',
'save-off'
]);
function parseCommandList(value) {
if (!value) {
return [];
}
return value
.split(',')
.map((entry) => entry.trim().toLowerCase())
.filter(Boolean);
}
const adminAllowedCommands = new Set(parseCommandList(process.env.ADMIN_ALLOWED_COMMANDS));
const adminBlockedCommandsFromEnv = parseCommandList(process.env.ADMIN_BLOCKED_COMMANDS);
const adminBlockedCommands = adminBlockedCommandsFromEnv.length
? new Set(adminBlockedCommandsFromEnv)
: DEFAULT_ADMIN_BLOCKED_COMMANDS;
function normalizeCommand(command) {
const trimmed = command.trim();
if (!trimmed) {
return null;
}
const token = trimmed.split(/\s+/)[0].toLowerCase();
const base = token.includes(':') ? token.split(':').pop() : token;
return { token, base };
}
function isAdminCommandAllowed(command) {
const normalized = normalizeCommand(command);
if (!normalized) {
return false;
}
const { token, base } = normalized;
if (adminAllowedCommands.size) {
return adminAllowedCommands.has(token) || adminAllowedCommands.has(base);
}
return !(adminBlockedCommands.has(token) || adminBlockedCommands.has(base));
}
function createServerRouter(db, manager, requireCommandKey) {
router.get('/info', requireCommandKey, (req, res) => {
try {
const requestedServerId = req.apiKey.keyType === KEY_TYPE_ADMIN
? (req.query.server_id || req.apiKey.serverId)
: req.apiKey.serverId;
if (!requestedServerId) {
return res.status(400).json({ detail: 'server_id is required for this key' });
}
const serverInfo = {
server_id: requestedServerId,
status: 'running',
online_players: 5,
max_players: 20,
version: '1.20.1',
uptime: '2h 30m',
tps: 19.8
};
res.json(serverInfo);
} catch (error) {
res.status(500).json({ detail: error.message });
}
});
router.post('/command', requireCommandKey, (req, res) => {
try {
const { command, server_id: requestedServerId } = req.body;
if (!command || typeof command !== 'string') {
return res.status(400).json({ detail: 'Command is required' });
}
const isAdmin = req.apiKey.keyType === KEY_TYPE_ADMIN;
if (isAdmin && !isAdminCommandAllowed(command)) {
return res.status(403).json({ detail: 'Command is not allowed for Admin Key' });
}
const targetServerId = isAdmin ? (requestedServerId || req.apiKey.serverId) : req.apiKey.serverId;
if (!targetServerId) {
return res.status(400).json({ detail: 'server_id is required for this key' });
}
console.log(`[Server Command] ${targetServerId}: ${command}`);
const commandEvent = {
event_type: EVENT_TYPES.SERVER_COMMAND,
server_name: targetServerId,
timestamp: new Date().toISOString(),
data: {
command: command,
executed_by: req.apiKey.id,
server_id: targetServerId
}
};
manager.broadcastToAll({
type: 'minecraft_event',
event: commandEvent,
source_key_id_prefix: req.apiKey.id.substring(0, 8)
});
res.json({
message: 'Command sent successfully',
command: command,
server_id: targetServerId
});
} catch (error) {
res.status(500).json({ detail: error.message });
}
});
return router;
}
module.exports = createServerRouter;