This commit is contained in:
BuildTools 2019-10-08 01:11:54 +02:00
commit 20a8ccc6dd
4 changed files with 257 additions and 0 deletions

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
node_modules
.env

187
manager.js Normal file

@ -0,0 +1,187 @@
require("dotenv").config();
var child_process = require("child_process");
var EventEmitter = require("events").EventEmitter;
var colors = require("colors");
var Discord = require("discord.js");
process.chdir("/srv/jpland");
const MAX_IDLE_MINUTES = 60;
class MinecraftServer extends EventEmitter {
constructor (cwd, jar) {
super();
this.cwd = cwd;
this.jar = jar;
this.idleMinutes = 0;
//this.thisIdleCheckTime = 0;
//this.lastIdleCheckTime = 0;
this.listCommand = "minecraft:list";
}
start() {
this._log("Starting server".green);
this.process = child_process.spawn("java", ["-Xmx4G", "-jar", this.jar], {cwd: this.cwd});
this.process.on("error", error => {
this.process.emit("exit");
this._log(error.stack.red, true);
});
this.process.stdout.on("data", data => {
data = data.toString().trim().split("\n");
data.forEach(data => {
this._log(data);
this._handleOutput(data);
});
});
this.process.stderr.on("data", data => {
data = data.toString().trim().split("\n");
this._log(data, true);
});
this.process.once("exit", () => {
this.process = undefined;
clearInterval(this.idleCheckInterval);
this._log("Server has exited".red);
});
this.idleMinutes = 0;
this.idleCheckInterval = setInterval(()=>{
//this.lastIdleCheckTime = this.thisIdleCheckTime;
//this.thisIdleCheckTime = Date.now();
this.process.stdin.write(this.listCommand + "\n");
}, 60000);
return this;
}
stop() {
return this.process && this.process.stdin.write("stop\n");
}
restart() {
if (!this.process) return;
this.process.on("exit", () => this.start());
return this.stop();
}
_log(msg, isError) {
return console[isError ? "error" : "log"](`[${this.cwd}]`[isError ? "red" : "green"], msg);
}
_handleOutput(line) {
if (this._testIfConsoleLineIndicatesNoPlayersOnline(line)) {
// no players are online
// tried to make it ignore arbitrary listings but I can't seem to figure it out and the issue is not a big deal.
//if (Date.now() - this.lastIdleCheckTime < IDLE_CHECK_INTERVAL) return; // this list must've been called by someone else, ignore it*/
this._log("Detected no players online; incrementing idleMinutes".yellow);
this.idleMinutes++;
if (this.idleMinutes >= MAX_IDLE_MINUTES) {
this.idleMinutes = 0;
this.stop();
this._log("Shutting down due to inactivity".red);
this.emit("idle timeout");
}
} else if (this._testIfConsoleLineIndicatesPlayersOnline(line)) {
// players are online
//if (Date.now() - this.lastIdleCheckTime < IDLE_CHECK_INTERVAL) return; // ditto*/
this._log("Detected players online; resetting idle minutes".yellow);
this.idleMinutes = 0;
}
}
_testIfConsoleLineIndicatesPlayersOnline(line) {
let foqat = line.substr(line.indexOf(" INFO]: ")+8);
let fojat = foqat.substr(0, foqat.indexOf(": "));
return (fojat.startsWith("There are ") && fojat.endsWith(" of a max 20 players online"));
}
_testIfConsoleLineIndicatesNoPlayersOnline(line) {
return line.endsWith(" INFO]: There are 0 of a max 20 players online:");
}
}
var servers = {
creative: new MinecraftServer("creative", "paper.jar"),
survival: new MinecraftServer("survival", "paper.jar"),
modded: new MinecraftServer("forge", "forge-1.12.2-14.23.5.2768-universal.jar"),
};
servers.modded._testIfConsoleLineIndicatesPlayersOnline = function(line) {
var fojat = line.substr(line.indexOf("] [Server thread/INFO] [minecraft/DedicatedServer]: There are "));
return (fojat.startsWith("] [Server thread/INFO] [minecraft/DedicatedServer]: There are ") && fojat.endsWith(" players online:"));
}
servers.modded._testIfConsoleLineIndicatesNoPlayersOnline = function(line) {
return line.endsWith("] [Server thread/INFO] [minecraft/DedicatedServer]: There are 0/20 players online:");
}
servers.modded.listCommand = "list";
function commandHandler(input, priviledged) {
var args = input.split(' ');
var cmd = args[0];
var unauthorized = "You are not permitted to perform this command.";
var serverName = args[1] && args[1].toLowerCase();
var server = servers[serverName];
if (cmd == "start") {
if (!server) return `Unknown server ${serverName}`
if (server.process) return `${serverName} server is already running.`;
server.start();
return `Starting ${serverName} server.`;
} else if (cmd == "stop") {
if (!priviledged) return unauthorized;
if (!server) return `Unknown server ${serverName}`
if (!server.process) return `${serverName} server is not running.`;
server.stop();
return `Stopping ${serverName} server.`;
} else if (cmd == "input") {
if (!priviledged) return unauthorized;
if (!server) return `Unknown server ${serverName}`
if (!server.process) return `${serverName} is not running.`;
if (args[2] == "list" || args[2] == "minecraft:list") return "`list` command is prohibited from running in console because it would interfere with idle minute counting.";
server.process.stdin.write(args.slice(2).join(" ") + '\n');
return;
} else if (cmd == "list") {
return `Servers: ${Object.keys(servers).map(x => `${x} (${servers[x].process ? 'running' : 'stopped'})`).join(', ')}`;
} else if (cmd == "eval") {
if (!priviledged) return unauthorized;
try {
return String(eval(args.slice(1).join(' ')));
} catch (error) {
return String(error);
}
} else {
return `Unknown command \`${cmd}\`; commands are \`start\`, \`stop\`, \`input\`, \`list\`, and \`eval\`.`;
}
}
process.openStdin();
process.stdin.on("data", data => {
data = data.toString().trim();
var response = commandHandler(data, true);
if (response) console.log(response.blue);
});
if (process.env.DISCORD_TOKEN) {
var dClient = new Discord.Client();
dClient.login(process.env.DISCORD_TOKEN);
dClient.on("error", error => console.error(colors.red("Discord client error: " + error.message)));
dClient.on("ready", () => console.log("Discord client is ready.".green));
dClient.on("message", message => {
if (message.channel.id != "452025433328975872") return;
if (message.content.startsWith('%')) {
let response = commandHandler(message.content.substr('%'.length), message.member && message.member.roles.map(x => x.name).includes("Minecraft admin"));
if (response) message.channel.send(response);
}
});
for (let serverName in servers) {
let server = servers[serverName];
server.on("idle timeout", () => {
let channel = dClient.channels.get("452025433328975872");
if (channel) channel.send(`${serverName} server has been shut down due to 1 hour of inactivity. Run \`%start ${serverName}\` when you want to play on it again.`);
});
}
}
console.log("JPLand Manager is now running.".green);

61
package-lock.json generated Normal file

@ -0,0 +1,61 @@
{
"requires": true,
"lockfileVersion": 1,
"dependencies": {
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"colors": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
},
"discord.js": {
"version": "11.5.1",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.5.1.tgz",
"integrity": "sha512-tGhV5xaZXE3Z+4uXJb3hYM6gQ1NmnSxp9PClcsSAYFVRzH6AJH74040mO3afPDMWEAlj8XsoPXXTJHTxesqcGw==",
"requires": {
"long": "^4.0.0",
"prism-media": "^0.0.3",
"snekfetch": "^3.6.4",
"tweetnacl": "^1.0.0",
"ws": "^6.0.0"
}
},
"dotenv": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.1.0.tgz",
"integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA=="
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"prism-media": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.3.tgz",
"integrity": "sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ=="
},
"snekfetch": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
"integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw=="
},
"tweetnacl": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz",
"integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A=="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
}

7
package.json Normal file

@ -0,0 +1,7 @@
{
"dependencies": {
"colors": "^1.4.0",
"discord.js": "^11.5.1",
"dotenv": "^8.1.0"
}
}