init
This commit is contained in:
commit
20a8ccc6dd
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.env
|
187
manager.js
Normal file
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
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
7
package.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"colors": "^1.4.0",
|
||||
"discord.js": "^11.5.1",
|
||||
"dotenv": "^8.1.0"
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user