diff --git a/includes/class_server.js b/includes/class_server.js new file mode 100644 index 0000000..e913d57 --- /dev/null +++ b/includes/class_server.js @@ -0,0 +1,92 @@ +const Ping = require('net-ping'); +const Discord = require('discord.js'); + +class Server { + + slug = ""; + name = ""; + platformURL = ""; + mcVersion = ""; + iconURL = ""; + description = ""; + addressPrimary = ""; + addressSecondary = ""; + queryPort = 0; + rconAddress = ""; + rconPort = 0; + rconPassword = ""; + rconTPSCommand = ""; + rconOnlineCommand = ""; + active = false; + + constructor(serverObj) { + if ( serverObj.slug ) this.slug = serverObj.slug; + if ( serverObj.name ) this.name = serverObj.name; + if ( serverObj.platformURL ) this.platformURL = serverObj.platformURL; + if ( serverObj.mcVersion ) this.mcVersion = serverObj.mcVersion; + if ( serverObj.iconURL ) this.iconURL = serverObj.iconURL; + if ( serverObj.description ) this.description = serverObj.description; + if ( serverObj.addressPrimary ) this.addressPrimary = serverObj.addressPrimary; + if ( serverObj.addressSecondary ) this.addressSecondary = serverObj.addressSecondary; + if ( serverObj.queryPort ) this.queryPort = serverObj.queryPort; + if ( serverObj.rconAddress ) this.rconAddress = serverObj.rconAddress; + if ( serverObj.rconPort ) this.rconPort = serverObj.rconPort; + if ( serverObj.rconPassword ) this.rconPassword = serverObj.rconPassword; + if ( serverObj.rconTPSCommand ) this.rconTPSCommand = serverObj.rconTPSCommand; + if ( serverObj.rconOnlineCommand ) this.rconOnlineCommand = serverObj.rconOnlineCommand; + if ( serverObj.active ) this.active = serverObj.active; + } + + async hostIsAlive() { + try { + if ( !this.active || (this.rconAddress == "") ) return false; + let session = Ping.createSession({retries: 1, timeout: 250}); + let result = await session.pingHost(this.rconAddress, function(error, target) { + if ( error ) { + return false; + } else { + return true; + } + }); + return result; + } catch (e) { + console.log(e); + return false; + } + } + + static sendOnline(status, channel) { + let playerCount = 0; + let embed = new Discord.MessageEmbed() + .setThumbnail("https://www.circlecraft.info/images/circlecraft_discord.png") + .setColor(0x44ff44); + status.forEach(function (s) { + if ( !s.tested ) return; + if ( s.players.length > 0 ) embed.addField("**" + s.name + "**:", s.players.join(', '), false); + }); + embed.setTitle("Players Currently Online: " + playerCount); + channel.send(embed); + } + + sendDetails(channel, hostStatus, serverStatus, serverTPS, queryStatus = {}) { + let embed = new Discord.MessageEmbed() + .setTitle(this.name + " *(" + this.slug + ")*") + .setDescription(this.description) + .setURL(this.platformURL) + .setThumbnail(this.iconURL) + .setColor(0xffaaaa) + .addField("**MC Version:**", this.mcVersion, true) + .addField("**Server Address:**", this.addressPrimary, true) + .addField("**Server Alternate:**", this.addressSecondary, true) + .addField("**Host Status:**", hostStatus, true) + .addField("**Server Status:**", serverStatus, true) + .addField("**Server TPS:**", serverTPS, true); + if ( queryStatus.players ) { + let players = (queryStatus.players.length > 0) ? queryStatus.players.join(', ') : "None"; + embed.addField("**Online Players (" + queryStatus.online_players + "):**", players, false); + } + channel.send(embed); + } +} + +module.exports = Server; \ No newline at end of file diff --git a/index.js b/index.js index 87820f8..53fc411 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,10 @@ -const Config = require('./config/config.js'); -const Servers = require('./config/servers.js'); +const Config = require('./config/config'); +const Servers = require('./config/servers'); const Discord = require('discord.js'); +const Rcon = require('mbr-rcon'); +const Query = require("minecraft-query"); +const Server = require('./includes/class_server'); + const client = new Discord.Client(); client.on('ready', () => { @@ -9,24 +13,72 @@ client.on('ready', () => { process.on('SIGINT', function() { console.log("Closing down..."); + process.exit(1); }); -let last = ""; +function sendOnline(status, channel) { + let playerCount = 0; + let embed = new Discord.MessageEmbed() + .setThumbnail("https://www.circlecraft.info/images/circlecraft_discord.png") + .setColor(0x44ff44); + for ( const s in status ) { + if ( !status[s].tested ) { + return; + } + if ( status[s].players.length > 0 ) { + playerCount += status[s].players.length; + embed.addField("**" + status[s].name + "**:", status[s].players.join(', '), false); + } + }; + if ( playerCount == 0 ) return; + embed.setTitle("**Players Currently Online:** " + playerCount); + channel.send(embed); +} -client.on('message', msg => { +client.on('message', async msg => { if ( msg.content.startsWith(">>servers") && Config.CHANNELS_ALLOW.includes(msg.channel.id) ) { let embed = new Discord.MessageEmbed() .setTitle("Here's a list of our current servers...") .setColor(0xFFaaaa); Servers.forEach(function (server) { - embed.addField("**" + server.name + "** *(" + server.slug + ")*", server.description); + if ( server.active ) embed.addField("**" + server.name + "** *(" + server.slug + ")*", server.description); }); embed.setFooter("Get more info on a pack with: >>server slug\nThe slugs are in parenthesis above"); msg.channel.send(embed); } - if ( msg.content == ">>server" ) { + if ( msg.content == ">>online" && Config.CHANNELS_ALLOW.includes(msg.channel.id) ) { + let status = []; + Servers.forEach(function (server) { + if ( server.active ) { + status[server.slug] = {tested: false, online: false, players: [], name: ""}; + status[server.slug].name = server.name; + } + }); + Servers.forEach(function (server) { + const q = new Query({host: server.rconAddress, port: server.queryPort, timeout: 250}); + try { + q.fullStat() + .then(response => { + status[server.slug].tested = true; + status[server.slug].online = true; + status[server.slug].players = response.players; + sendOnline(status, msg.channel); + }) + .then(() => { q.close(); }) + .catch(e => { + q.close(); + status[server.slug].tested = true; + sendOnline(status, msg.channel); + }); + } catch (e) { + console.log(e); + } + }); + } + + if ( msg.content == ">>server" && Config.CHANNELS_ALLOW.includes(msg.channel.id) ) { msg.channel.send("**Usage:** >>server *slug*"); } @@ -34,16 +86,78 @@ client.on('message', msg => { let cmdParts = msg.content.split(" "); let server = Servers.find(element => element.slug == cmdParts[1]); if ( server ) { - let embed = new Discord.MessageEmbed() - .setTitle(server.name + " *(" + server.slug + ")*") - .setDescription(server.description) - .setURL(server.platformURL) - .setThumbnail(server.iconURL) - .setColor(0xffaaaa) - .addField("**MC Version:**", server.mcVersion, true) - .addField("**Server Address:**", server.addressPrimary, true) - .addField("**Server Alternate:**", server.addressSecondary, true); - msg.channel.send(embed); + let hostStatus = "Offline"; + try { + if (server.hostIsAlive()) hostStatus = "Online"; + } catch (e) { + console.log(e); + } + //console.log(query); + let serverStatus = "Stopped"; + let serverTPS = "---"; + if ( server.rconPort != 0 ) { + //console.log("Making new rcon connection..."); + const rcon = new Rcon({ + host: server.rconAddress, + port: server.rconPort, + pass: server.rconPassword, + onClose: function () {} + }); + const connection = rcon.connect({ + onSuccess: function() { + //console.log("Connected! " + serverStatus); + serverStatus = "Running"; + }, + onError: function() { + console.log("Could not connect to rcon server: " + server.name + " " + server.rconAddress + " " + server.rconPort); + server.sendDetails(msg.channel, hostStatus, serverStatus, serverTPS); + } + }); + connection.auth({ + onSuccess: function () { + //console.log("Successfully authenticated to: " + server.name); + }, + onError: function (error) { + console.log("Could not authenticate to rcon server: " + server.name + " " + server.rconAddress + " " + server.rconPort); + server.sendDetails(msg.channel, hostStatus, serverStatus, serverTPS); + } + }); + await connection.send(server.rconTPSCommand, { + onSuccess: function (response) { + //console.log(response); + if ( ['1.2.5', '1.4.7'].includes(server.mcVersion) ) { + let tpsParts = response.trim().split("\n"); + serverTPS = tpsParts[0].split(" ").pop().slice(2); + } else { + let tpsParts = response.trim().split(" "); + serverTPS = tpsParts.pop(); + if ( serverTPS.includes("*") ) serverTPS = serverTPS.split("*").pop(); + } + //console.log("Got it all!_" + hostStatus + "_" + serverStatus + "_" + serverTPS + "_"); + const q = new Query({host: server.rconAddress, port: server.queryPort, timeout: 250}); + try { + q.fullStat() + .then(response => { + server.sendDetails(msg.channel, hostStatus, serverStatus, serverTPS, response); + }) + .then(() => { q.close(); }) + .catch(e => { + q.close(); + server.sendDetails(msg.channel, hostStatus, serverStatus, serverTPS); + }); + } catch (e) { + console.log(e); + } + }, + onError: function (error) { + console.log("Could not query rcon server: " + server.name + " " + server.rconAddress + " " + server.rconPort); + server.sendDetails(msg.channel, hostStatus, serverStatus, serverTPS); + } + }); + } else { + console.log("No rcon settings for: " + server.name); + server.sendDetails(msg.channel, hostStatus, serverStatus, serverTPS); + } } else { if ( cmdParts[1] ) { msg.channel.send("No server with the slug \"" + cmdParts[1] + "\" exists"); diff --git a/package-lock.json b/package-lock.json index 5632b35..898af36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -553,6 +553,25 @@ "pify": "^3.0.0" } }, + "mbr-buffer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mbr-buffer/-/mbr-buffer-1.3.0.tgz", + "integrity": "sha512-4CFPovz0jVLf9MVgez9iMz6u8yG8PxJTncr7Y4rzDCeu8y9NQNad+mCrVjKpvYndkKF9pP2DiTnzBKzdqhMZ9g==" + }, + "mbr-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/mbr-queue/-/mbr-queue-1.0.3.tgz", + "integrity": "sha512-u0tJHtLXrE8ImxCMZoZjWocn/9A1rV7m+GxaWh+GjMsHp3KjcVmGVKIb1IEiUH/vkwTJ9AuqwQCbd6h0qqyR+g==" + }, + "mbr-rcon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mbr-rcon/-/mbr-rcon-1.1.0.tgz", + "integrity": "sha512-JPUFGFjQFfXImyOwap4ssvPrqQhO75ZJFGXkAVzK5M1bean2d+SVDlYywENtKThUtRHeFvKJC6e2q6XD97Z4pg==", + "requires": { + "mbr-buffer": "^1.2.2", + "mbr-queue": "^1.0.3" + } + }, "mime-db": { "version": "1.43.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", @@ -566,6 +585,11 @@ "mime-db": "1.43.0" } }, + "minecraft-query": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/minecraft-query/-/minecraft-query-1.0.9.tgz", + "integrity": "sha512-RCVT45MRV5MjNMSOFPKVWD5cj+V1TAREQ0irHPUBDKrnVl4WaQzcqhEyXwWZ2LRaR2GiUI6TtMBklTytFrJ+tA==" + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -592,6 +616,19 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + }, + "net-ping": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/net-ping/-/net-ping-1.2.3.tgz", + "integrity": "sha512-ZKxj/kVPKL2RIsV9nR6I8nMT8Pi3k6ciTBKxD/6gd5lga9qcNmlyqNv+dbXqYGBvHsmG9yIpsfajr8X054x2fQ==", + "requires": { + "raw-socket": "*" + } + }, "node-fetch": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", @@ -704,6 +741,14 @@ "integrity": "sha512-xsMgrUwRpuGskEzBFkH8NmTimbZ5PcPup0LA8JJkHIm2IMUbQcpo3yeLNWVrufEYjh8YwtSVh0xz6UeWc5Oh5A==", "dev": true }, + "raw-socket": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/raw-socket/-/raw-socket-1.7.0.tgz", + "integrity": "sha512-mXqWihgwaFNmV5le0dWk5o+03M3A2zBIkC9BNaE6R0CJN9eYot++j2FIqgNSDq6/Vmu32PPI155SiiWNV2yyFQ==", + "requires": { + "nan": "2.14.*" + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -716,14 +761,6 @@ "strip-json-comments": "~2.0.1" } }, - "rcon-client": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/rcon-client/-/rcon-client-4.2.0.tgz", - "integrity": "sha512-PXoGQiO6ztssnveLkY/AqMnokuzv+Ml8Zij65fYQ98N873KP2eiV+DbN3rijC7vfMgyy2DSKZ2gjoGD9iCPjVg==", - "requires": { - "typed-emitter": "^0.1.0" - } - }, "readdirp": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", @@ -877,11 +914,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" }, - "typed-emitter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-0.1.0.tgz", - "integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg==" - }, "undefsafe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", diff --git a/package.json b/package.json index 076cbef..54f333f 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "tweetnacl": "^1.0.3", "ws": "^7.2.3", "moment": "^2.24.0", - "rcon-client": "^4.2.0" + "net-ping": "^1.2.3", + "mbr-rcon": "^1.1.0", + "minecraft-query": "^1.0.9" }, "devDependencies": { "nodemon": "^2.0.2"