Skip to content

Commit

Permalink
feat: added a bunch of cool features'
Browse files Browse the repository at this point in the history
  • Loading branch information
Cedric Poottaren committed Feb 28, 2021
1 parent 2aff6bd commit be3a98b
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 46 deletions.
97 changes: 73 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@


## Introduction
This is a Node.js script for a Discord bot. Based on [MineStat](https://github.com/ldilley/minestat), the bot checks the status of user-specified Minecraft servers and returns the number of players online.
This is a NodeJS Minecraft Bot that let you manage or do in game operation via Discord. It's based off the work of the orginal author Amal Bansode [Discord-Minecraft-Bot](https://github.com/amalbansode/Discord-Minecraft-Bot) and uses the [MineStat](https://github.com/ldilley/minestat), the bot checks the status of user-specified Minecraft servers, teleport users to a specified coordinates, list the names of players in game, broadcast messages and a lot more to come.

## Installation
This requires Node.js to be installed on your system, along with primary dependencies discord.js and minestat.js.

### Setting up for use with Node.js
1. Install Node.js and npm on your system. (You can use [nvm](https://github.com/nvm-sh/nvm) or the [Node.js](https://nodejs.org/en/download/) binary.)
2. Clone this repository and place it in a folder. (Use `git clone https://github.com/amalbansode/Minecraft-Discord-Bot`)
2. In terminal, navigate to this folder.
2. Install [mcrcon](https://github.com/Tiiffi/mcrcon)
3. Clone this repository and place it in a folder. (Use `git clone https://github.com/cedroid09/homer-mc-discord`)
4. In terminal, navigate to this folder.
```bash
$ cd location/of/your/Minecraft-Discord-Bot
$ cd location/of/your/homer-mc-discord
```
3. Install the latest `discord.js` using npm.
5. Install the latest `discord.js` using npm.
```bash
$ npm i -S discord.js # You can remove the -S because npm now automatically saves it to package.json
```
Expand All @@ -23,8 +24,8 @@ This requires Node.js to be installed on your system, along with primary depende
1. [Create a Discord Application](https://discordapp.com/developers/applications/) for the purpose of this bot. Setup a Discord account if you do not have one yet.
2. Give the application/bot a name and description.
3. Navigate to the ‘Bot’ section. Reveal your bot’s token, and copy it.
4. Open `auth.json`, which is contained in the same folder as `bot.js`.
5. Paste your token within the double quotes corresponding to the "token" key, and save `auth.json`.
4. Open `config.json`, which is contained in the same folder as `bot.js`.
5. Paste your token within the double quotes corresponding to the "authtoken" key, and save.
6. Now, with Terminal open in the same directory as before, run
```bash
$ node bot.js
Expand Down Expand Up @@ -64,36 +65,86 @@ The IP details of servers can be displayed in Discord chat by typing `!ip` into

![Discord Bot replies with list of IPs to servers](/images/discord_reply.png)

### Returns the list of players connected
The list of users in-game can be obtained by typing the keyword `!playing` into any Discord chat. A list of users and server capacity will be printed.

![Bot replies with the name of users connected](/images/discord_print_users.png)

### Reports the status of the user
Quick way to check if the server ready to accept connections.

![Bot checks server connectivity](/images/discord_print_status.png)

### Broadcast a massage to all in-game players
Quickly sent out alert or message using the bot to all players in game.

![Bot sends in game messages](/images/discord_send_message.png)

### Gives the ability to anyone to teleport
The bot can be used to allow users on a vanilla server to teleport without any OP level permissions.

![Bot teleport users](/images/discord_teleport_users.png)

## Configuration

### Changing the servers to be checked
1. Stop the bot if it is running. Open `bot.js` in a text-editing program.
2. Navigate to line _33_, which contains the dictionary type variable `servIP`.
3. By default, the dictionary contains the IP Address for the MInecraft Hypixel server.
```javascript
const servIP = {
hypixel: 'mc.hypixel.net:25565',
// 'your title': 'your.ip:port',
};
1. Stop the bot if it is running. Open `config.json` in a text-editing program.
2. Change the parameters listed as follows:
```json
{
"discord": {
"authtoken": "<your_discord_authtoken_here"
},
"mcrcon": {
"path": "path/to/mcrcon/binary",
"host": "host_ip", #ip for mcrcon to connect to the server
"password": "mcrcon_password" #defined in server.properties
},
"minecraft": {
"host": "public.minecraft.server.dns",
"hostname": "server_name",
"port": "25565"
}
}
```
4. Modify this dictionary to follow the template, keeping the title(s) and IP Address(es) within single quotation (') marks, separated by a colon (:) mark. The order of servers in this dictionary list will be preserved in the bot’s output.

### Changing the frequency of checking servers' status
1. By default, the bot checks the status of servers every four minutes.
2. To modify this, change the variable `refreshEvery` on line _29_ to the number of minutes between every refresh. Use a floating point numeral if required.
2. To modify this, change the variable `refreshEvery` to the number of minutes between every refresh. Use a floating point numeral if required.

Note: The bot being asynchronous, however, will require at least `the number of servers times 2.5` seconds to fetch the statuses of all servers at once. This time interval can be reduced, but it is suggested that this remain as it is to prevent any malfunctioning.

### Install the Bot service
Note, this applies only if you are running the bot a Linux machine. Here we are using CentOS.
1. On your server, create a unit file under `/etc/systemd/system/homer-mc-bot.service`
2. Paste in the following conetents:

```
Description=Homer's Minecraft stats bot
After=network.target
[Service]
Type=simple
WorkingDirectory=/minecraft/bot/installation/directory
User=minecraft
Group=minecraft
ExecStart=/bin/node minecraft/bot/installation/directory
ExecStop=/bin/pkill -9 node
Restart=always
SyslogIdentifier=minecraft
[Install]
WantedBy=multi-user.target
```

## FAQs
**There's no port specified for my Minecraft server**

The standard port for Minecraft servers is typically _25565_.

**Does the bot run forever?**

No, the bot goes offline when you close Terminal or end the process on your system. You could host the bot on AWS or Heroku, following the same steps as above, instead. The bot can be kept online using the _forever_ module. Check out [this guide](https://shiffman.net/a2z/bot-ec2/) on the usage of AWS.

## Contributors
## Other Contributors, besides orignal author

Contributions and improvements to the project are welcome! The following users helped by reviewing code and fixing my silly errors:

Expand All @@ -106,5 +157,3 @@ GPL v3 is not a very permissive license. You may be better off with
MIT or ISC license. Check it out! :)
-->
GNU GPL v3 or later

© Amal Bansode, 2019
3 changes: 0 additions & 3 deletions auth.json

This file was deleted.

200 changes: 186 additions & 14 deletions bot.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/*
* MinecraftDiscordBot.js — A script to power a Discord bot that checks
* the status of specified Minecraft servers.
* Copyright (C) 2019 Amal Bansode
* http://amalbansode.com
*
* Original author: Amal Bansode
* Fork by: Cedric Poottaren
* https://cedric.poottaren.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
Expand All @@ -18,18 +19,29 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

// Load required modules
const Discord = require('discord.js');
const auth = require('./auth.json');
const config = require('./config.json');
const serv = require('./minestat');

const client = new Discord.Client();

// Number of minutes to refresh server count after (recommended >= 1 min; default = 4)
const REFRESH_INTERVAL = 4 * 60 * 1000; // 4 minutes.
// Load mcron variables
const mcron_loc = config["mcrcon"].path;
const mcron_host = config["mcrcon"].host;
const mcron_pwd = config["mcrcon"].password;

// Load minecraft server variables
const mc_server_addr = config["minecraft"].host;
const mc_server_name = config["minecraft"].hostname;
const mc_server_port = config["minecraft"].port;

// Number of minutes to refresh server count after (recommended >= 1 min; default = 2)
const REFRESH_INTERVAL = 2 * 60 * 1000; // 2 minutes.

// IP and port of Minecraft servers
const servIP = {
hypixel: 'mc.hypixel.net:25565',
server_name: `${mc_server_addr}:${mc_server_port}`
};

let intervalId;
Expand All @@ -49,14 +61,174 @@ client.on('ready', () => {
// States IPs of Minecraft servers respectively if '!ip' is typed into chat.
client.on('message', async msg => {
if (msg.content === '!ip') {
await checkStatus();
await checkStatus();
// Building string output for '!ip' command
for (const name in servIP) {
msg.reply(`${name}: ${servIP[name]}`);
msg.reply(`Hello, my name is ${mc_server_name}, here's my address: ${servIP[name]}` +
`\nI hope to see in-world soon!`);
}
}
});

// Teleports user to the specified coordinates
client.on('message', async msg => {
if (msg.content.startsWith('!tp')) {
if (await checkServerStatus()) {
// Building string output for '!tp' command
data = msg.content.split(' ');
let user = data[1];
let x = Number(data[2]);
let y = Number(data[3]);
let z = Number(data[4]);
if (Number.isNaN(x) || Number.isNaN(y) || Number.isNaN(z)) {
msg.reply("Nope, won't teleport you! Please check the coordinates again.");
} else {
msg.reply(`Hi, trying to teleport user: ${user}.` +
`\nLocation is set to x: ${x}, y: ${y} and z: ${z}`);
var tp = await teleportUser(user, x, y, z);
if (tp == 0) {
msg.reply(`User ${user} teleported successfully ;)`);
} else if (tp == 1) {
msg.reply(`Oops... I can't find user ${user}`);
} else {
msg.reply(`I'm so sorry.. Failed to teleport user ${user}`);
}
}
} else {
msg.reply(`Oops.. Looks like the server is offline!`);
}
}
});

// Returns the status of the server
client.on('message', async msg => {
if (msg.content === '!status') {
if (await checkServerStatus()) {
// Building string output for '!status' command
msg.reply(`I'm online and ready for you to connect!`);
} else {
msg.reply(`Server offline, please contact the Wizards!`);
}
}
});

// Broadcast msg to all in-game players
client.on('message', async msg => {
if (msg.content.startsWith('!broadcast')) {
if (await checkServerStatus()) {
// Building string output for '!broadcast' command
let _msg = "";
_msg = msg.content.split(' ')[1];
console.log(_msg);
if (_msg == null) {
msg.reply(`Nope, I won't broadcast empty messages!`);
} else {
bd = await broadcast(`[${mc_server_name}]: ${_msg}`);
if (bd == 0) {
msg.reply(`Message broadcasted successfully ;)`);
} else {
msg.reply(`Oops.. I couldn't send that out for you`);
}
}
} else {
msg.reply(`Server offline, please contact the Wizards!`);
}
}
});

// Returns the number & name of in-game players
client.on('message', async msg => {
if (msg.content === '!playing') {
if (await checkServerStatus()) {
// Building string output for '!playing' command
result = await mcrcon_cmd(`list`);
if (result != 2) {
msg.reply(result.substring(0, result.length - 5));
} else {
msg.reply(`Oops.. Can't list the players for now!`);
}
} else {
msg.reply(`Server offline, please contact the Wizards!`);
}
}
});

// The teleport user function
async function teleportUser(user, x, y, z) {
const { exec } = require('child_process');
return new Promise(resolve => {
exec(`${mcron_loc} -H ${mcron_host} -p ${mcron_pwd} 'teleport ${user} ${x} ${y} ${z}'`, (err, stdout, stderr) => {
console.log(`[info]: ${stdout}`);
console.log(`[error]: ${stderr}`);
if (err) {
// node couldn't execute the command
resolve(2);
} else {
var out = stdout.toLowerCase().concat(stderr.toLowerCase());
if (out.includes(`no entity`)) {
resolve(1);
} else if (out.includes(`teleported`)) {
resolve(0);
}
}
});
});
}

// Broadcast message to all in-game players
async function broadcast(msg) {
const { exec } = require('child_process');
return new Promise(resolve => {
exec(`${mcron_loc} -H ${mcron_host} -p ${mcron_pwd} 'say ${msg}'`, (err, stdout, stderr) => {
console.log(`[info]: ${stdout}`);
console.log(`[error]: ${stderr}`);
if (err) {
// node couldn't execute the command
resolve(2);
} else {
var out = stdout.toLowerCase().concat(stderr.toLowerCase());
if (out == "") {
resolve(0);
} else {
resolve(1);
}
}
});
});
}

// Generic mcrcon command parser
async function mcrcon_cmd(command) {
const { exec } = require('child_process');
return new Promise(resolve => {
exec(`${mcron_loc} -H ${mcron_host} -p ${mcron_pwd} '${command}'`, (err, stdout, stderr) => {
console.log(`[info]: ${stdout}`);
console.log(`[error]: ${stderr}`);
if (err) {
// node couldn't execute the command
resolve(2);
} else {
resolve(stdout);
}
});
});
}

// Query mc server for availability
async function checkServerStatus() {
serv.init(mc_server_addr, parseInt(mc_server_port), () => {
if (serv.online) {
return true;
}
else {
return false;
}
});

return serv;
}

// Updates the in-discord status of the server and number of players in-world
async function checkStatus() {
let statusStr = '';

Expand All @@ -66,18 +238,18 @@ async function checkStatus() {
serv.init(host, parseInt(port), () => {
if (serv.online) {
// Setting 'Activity' attribute of Discord bot with player counts of servers respectively.
statusStr = (`${statusStr}${name}: ${serv.current_players} running ${serv.version} | `);
statusStr = (`${mc_server_name} | Players online: ${serv.current_players} | running ${serv.version}`);
}
else {
statusStr = (`${statusStr}${name}: nope | `);
statusStr = (`${mc_server_name}: Server unreachable`);
}
client.user.setActivity(statusStr);
});

await new Promise(done => setTimeout(done, 5000));
}
}

// Set this in 'auth.json'
// Set this in 'config.json'
// Token can be found on your Discord developer portal.
client.login(auth.token);

client.login(config["discord"].authtoken);

0 comments on commit be3a98b

Please sign in to comment.