From 9ac2bbe9781f7ab5a798621cc9dd46b4ff8befda Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Tue, 4 Feb 2020 00:51:15 -0500 Subject: Refactor and rebuild to TypeScript --- src/Config.ts | 39 ++++++++++ src/Discord.ts | 160 +++++++++++++++++++++++++++++++++++++++++ src/MinecraftHandler.ts | 184 ++++++++++++++++++++++++++++++++++++++++++++++++ src/Rcon.ts | 113 +++++++++++++++++++++++++++++ src/Shulker.ts | 56 +++++++++++++++ src/index.ts | 8 +++ 6 files changed, 560 insertions(+) create mode 100644 src/Config.ts create mode 100644 src/Discord.ts create mode 100644 src/MinecraftHandler.ts create mode 100644 src/Rcon.ts create mode 100644 src/Shulker.ts create mode 100644 src/index.ts (limited to 'src') diff --git a/src/Config.ts b/src/Config.ts new file mode 100644 index 0000000..036e057 --- /dev/null +++ b/src/Config.ts @@ -0,0 +1,39 @@ +export interface Config { + PORT: number + + USE_WEBHOOKS: boolean + WEBHOOK_URL: string + DISCORD_TOKEN: string + DISCORD_CHANNEL_ID: string + DISCORD_MESSAGE_TEMPLATE: string + + MINECRAFT_SERVER_RCON_IP: string + MINECRAFT_SERVER_RCON_PORT: number + MINECRAFT_SERVER_RCON_PASSWORD: string + MINECRAFT_TELLRAW_TEMPLATE: string + + IS_LOCAL_FILE: boolean + LOCAL_FILE_PATH: string + + SHOW_INIT_MESSAGE: boolean + + ALLOW_USER_MENTIONS: boolean + ALLOW_HERE_EVERYONE_MENTIONS: boolean + ALLOW_SLASH_COMMANDS: boolean + SLASH_COMMAND_ROLES: string[] + + WEBHOOK: string + REGEX_SERVER_PREFIX: string + REGEX_MATCH_CHAT_MC: string + REGEX_IGNORED_CHAT: string + RCON_RECONNECT_DELAY: number + DEBUG: boolean + + SERVER_NAME: string + SERVER_IMAGE: string + SHOW_PLAYER_CONN_STAT: boolean + SHOW_PLAYER_ADVANCEMENT: boolean + SHOW_PLAYER_DEATH: boolean + SHOW_PLAYER_ME: boolean + DEATH_KEY_WORDS: string[] +} diff --git a/src/Discord.ts b/src/Discord.ts new file mode 100644 index 0000000..7088c0e --- /dev/null +++ b/src/Discord.ts @@ -0,0 +1,160 @@ +import { Client, Message, TextChannel } from 'discord.js' + +import emojiStrip from 'emoji-strip' +import axios from 'axios' + +import { Config } from './Config' + +import Rcon from './Rcon' + +class Discord { + config: Config + client: Client + + constructor (config: Config, onReady?: () => void) { + this.config = config + + this.client = new Client() + if (onReady) this.client.once('ready', () => onReady()) + this.client.on('message', (message: Message) => this.onMessage(message)) + } + + async init () { + try { + await this.client.login(this.config.DISCORD_TOKEN) + } catch (e) { + console.log('[ERROR] Could not authenticate with Discord: ' + e) + if (this.config.DEBUG) console.error(e) + } + } + + async onMessage (message: Message) { + // don't want to check other channels + if (message.channel.id !== this.config.DISCORD_CHANNEL_ID || message.channel.type !== 'text') return + // if using webhooks, ignore this! + if (this.config.USE_WEBHOOKS && message.webhookID) return + // if the same user as the bot, ignore + if (message.author.id === this.client.user.id) return + // ignore any attachments + if (message.attachments.array().length) return + + const rcon = new Rcon(this.config.MINECRAFT_SERVER_RCON_IP, this.config.MINECRAFT_SERVER_RCON_PORT, this.config.DEBUG) + try { + await rcon.auth(this.config.MINECRAFT_SERVER_RCON_PASSWORD) + } catch (e) { + console.log('[ERROR] Could not auth with the server!') + if (this.config.DEBUG) console.error(e) + } + + let command = '' + if (this.config.ALLOW_SLASH_COMMANDS && this.config.SLASH_COMMAND_ROLES && message.cleanContent.startsWith('/')) { + const author = message.member + if (author.roles.find(r => this.config.SLASH_COMMAND_ROLES.includes(r.name))) { + // raw command, can be dangerous... + command = message.cleanContent + } else { + console.log('[INFO] User attempted a slash command without a role') + } + } else { + command = `/tellraw @a ${this.makeMinecraftTellraw(message)}` + } + + if (command) { + await rcon.command(command).catch((e) => { + console.log('[ERROR] Could not send command!') + if (this.config.DEBUG) console.error(e) + }) + } + rcon.close() + } + + makeMinecraftTellraw(message: Message): string { + const username = emojiStrip(message.author.username) + const discriminator = message.author.discriminator + const text = emojiStrip(message.cleanContent) + // hastily use JSON to encode the strings + const variables = JSON.parse(JSON.stringify({ username, discriminator, text })) + + return this.config.MINECRAFT_TELLRAW_TEMPLATE + .replace('%username%', variables.username) + .replace('%discriminator%', variables.discriminator) + .replace('%message%', variables.text) + } + + replaceDiscordMentions(message: string): string { + const possibleMentions = message.match(/@(\S+)/gim) + if (possibleMentions) { + for (let mention of possibleMentions) { + const mentionParts = mention.split('#') + let username = mentionParts[0].replace('@', '') + if (mentionParts.length > 1) { + if (this.config.ALLOW_USER_MENTIONS) { + const user = this.client.users.find(user => user.username === username && user.discriminator === mentionParts[1]) + if (user) { + if (this.config.ALLOW_USER_MENTIONS) { + message = message.replace(mention, '<@' + user.id + '>') + } + } + } + } + + if (['here', 'everyone'].includes(username)) { + // remove these large pings + if (!this.config.ALLOW_HERE_EVERYONE_MENTIONS) { + message = message + .replace('@everyone', '@ everyone') + .replace('@here', '@ here') + } + } + } + } + return message + } + + makeDiscordWebhook (username: string, message: string) { + message = this.replaceDiscordMentions(message) + + let avatarURL + if (username === this.config.SERVER_NAME + ' - Server') { // Use avatar for the server + avatarURL = this.config.SERVER_IMAGE || 'https://minotar.net/helm/Steve/256.png' + } else { // Use avatar for player + avatarURL = `https://minotar.net/helm/${username}/256.png` + } + + return { + username: username, + content: message, + 'avatar_url': avatarURL, + } + } + + makeDiscordMessage(username: string, message: string) { + message = this.replaceDiscordMentions(message) + + return this.config.DISCORD_MESSAGE_TEMPLATE + .replace('%username%', username) + .replace('%message%', message) + } + + async sendMessage (username: string, message: string) { + if (this.config.USE_WEBHOOKS) { + const webhook = this.makeDiscordWebhook(username, message) + try { + await axios.post(this.config.WEBHOOK_URL, webhook, { headers: { 'Content-Type': 'application/json' } }) + } catch (e) { + console.log('[ERROR] Could not send Discord message through WebHook!') + if (this.config.DEBUG) console.log(e) + } + } else { + // find the channel + const channel = this.client.channels.find((ch) => ch.id === this.config.DISCORD_CHANNEL_ID && ch.type === 'text') as TextChannel + if (channel) { + await channel.send(this.makeDiscordMessage(username, message)) + } else { + console.log(`[ERROR] Could not find channel with ID ${this.config.DISCORD_CHANNEL_ID}!`) + } + } + } +} + +export default Discord diff --git a/src/MinecraftHandler.ts b/src/MinecraftHandler.ts new file mode 100644 index 0000000..39ab914 --- /dev/null +++ b/src/MinecraftHandler.ts @@ -0,0 +1,184 @@ +import fs from 'fs' +import { Tail } from 'tail' +import express from 'express' + +import { Config } from './Config' + +export type LogLine = { + username: string + message: string +} | null + +type Callback = (data: LogLine) => void + +class MinecraftHandler { + config: Config + + app: express.Application + tail: Tail + + constructor(config: Config) { + this.config = config + } + + fixMinecraftUsername (username: string) { + return username.replace(/(§[A-Z-a-z0-9])/g, '') + } + + private parseLogLine (data: string): LogLine { + if (this.config.DEBUG) console.log('[DEBUG] Received ' + data) + + const ignored = new RegExp(this.config.REGEX_IGNORED_CHAT) + + if (ignored.test(data)) { + if (this.config.DEBUG) console.log('[DEBUG] Line ignored') + return null + } + + const logLineDataRegex = new RegExp( + `${(this.config.REGEX_SERVER_PREFIX || "\\[Server thread/INFO\\]:")} (.*)` + ) + + const logLineData = data.match(logLineDataRegex) + + if (data.includes('Rcon connection')) return null + if (!logLineDataRegex.test(data) || !logLineData) { + console.log('[ERROR] Regex could not match the string! Please verify it is correct!') + console.log('Received: "' + data + '", Regex matches lines that start with: "' + this.config.REGEX_SERVER_PREFIX + '"') + return null + } + + const logLine = logLineData[1] + + const serverUsername = `${this.config.SERVER_NAME} - Server` + + if (logLine.startsWith('<')) { + if (this.config.DEBUG){ + console.log('[DEBUG]: A player sent a chat message') + } + + const re = new RegExp(this.config.REGEX_MATCH_CHAT_MC) + const matches = logLine.match(re) + + if (!matches) { + console.log('[ERROR] Could not parse message: ' + logLine) + return null + } + + const username = this.fixMinecraftUsername(matches[1]) + const message = matches[2] + if (this.config.DEBUG) { + console.log('[DEBUG] Username: ' + matches[1]) + console.log('[DEBUG] Text: ' + matches[2]) + } + return { username, message } + } else if ( + this.config.SHOW_PLAYER_CONN_STAT && ( + logLine.includes('left the game') || + logLine.includes('joined the game') + ) + ) { + // handle disconnection etc. + if (this.config.DEBUG){ + console.log(`[DEBUG]: A player's connection status changed`) + } + + return { username: serverUsername, message: logLine } + } else if (this.config.SHOW_PLAYER_ADVANCEMENT && logLine.includes('granted the advancement')) { + // handle achievement earning + if (this.config.DEBUG){ + console.log('[DEBUG] A player has earned an achievement') + } + return { username: `${this.config.SERVER_NAME} - Server`, message: logLine } + } else if (this.config.SHOW_PLAYER_ME && logLine.startsWith('* ')) { + const usernameMatch = data.match(/: \* ([a-zA-Z0-9_]{1,16}) (.*)/) + if (usernameMatch) { + const username = usernameMatch[1] + const rest = usernameMatch[2] + return { username: serverUsername, message: `**${username}** ${rest}` } + } + } else if (this.config.SHOW_PLAYER_DEATH) { + for (let word of this.config.DEATH_KEY_WORDS){ + if (data.includes(word)){ + if (this.config.DEBUG) { + console.log( + `[DEBUG] A player died. Matched key word "${word}"` + ) + } + return { username: serverUsername, message: logLine } + } + } + } + + return null + } + + private initWebServer (callback: Callback) { + // init the webserver + this.app = express() + const http = require('http').Server(this.app) + + this.app.use((request: express.Request, response: express.Response, next: express.NextFunction) => { + request.rawBody = '' + request.setEncoding('utf8') + + request.on('data', (chunk: string) => { + request.rawBody += chunk + }) + + request.on('end', function () { + next() + }) + }) + + this.app.post(this.config.WEBHOOK, (req, res) => { + if (req.rawBody) { + const logLine = this.parseLogLine(req.rawBody) + callback(logLine) + } + res.json({ received: true }) + }) + + const port = process.env.PORT || this.config.PORT + + http.listen(port, () => { + console.log('[INFO] Bot listening on *:' + port) + + if (!this.config.IS_LOCAL_FILE && this.config.SHOW_INIT_MESSAGE) { + console.log('[INFO] Please enter the following command on your server to send the logs to the server.') + console.log(' Be sure to replace "PATH_TO_MINECRAFT_SERVER_INSTALL" with the path to your Minecraft install') + console.log(' and replace "YOUR_URL" with the URL/IP of the server running Shulker!') + console.log(` \`tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL:${port}${this.config.WEBHOOK} ; done\``) + } + }) + } + + private initTail (callback: Callback) { + if (fs.existsSync(this.config.LOCAL_FILE_PATH)) { + console.log(`[INFO] Using configuration for local file at "${this.config.LOCAL_FILE_PATH}"`) + this.tail = new Tail(this.config.LOCAL_FILE_PATH) + } else { + throw new Error(`[ERROR] Local file not found at "${this.config.LOCAL_FILE_PATH}"`) + } + this.tail.on('line', (data: string) => { + // Parse the line to see if we care about it + let logLine = this.parseLogLine(data) + if (data) { + callback(logLine) + } + }) + this.tail.on('error', (error: any) => { + console.log('[ERROR] Error tailing file: ' + error) + }) + } + + init (callback: Callback) { + if (this.config.IS_LOCAL_FILE) { + this.initTail(callback) + } else { + this.initWebServer(callback) + } + } +} + +export default MinecraftHandler diff --git a/src/Rcon.ts b/src/Rcon.ts new file mode 100644 index 0000000..81cf3ea --- /dev/null +++ b/src/Rcon.ts @@ -0,0 +1,113 @@ +// Credits to M4GNV5 for this library + +import net from 'net' + +class Rcon { + socket: net.Socket + timeout: number + nextId: number + + connected: boolean + authed: boolean + debug: boolean + + ip: string + port: number + + packages: any + + constructor (ip: string, port: number, debug: boolean) { + this.ip = ip + this.port = port + this.debug = debug + + this.timeout = 5000 + this.nextId = 0 + this.connected = false + this.authed = false + this.packages = [] + + this.socket = net.connect(port, ip, () => { + this.connected = true + console.log('[INFO] Authenticated with ' + ip + ':' + port) + }) + + this.socket.on('data', (data) => { + const id = data.readInt32LE(4) + const type = data.readInt32LE(8) + const response = data.toString('ascii', 12, data.length - 2) + + if (this.packages[id]) { + this.packages[id](type, response) + } else { + console.log('Unexpected rcon response', id, type, response) + } + }).on('end', () => { + if (debug) { + console.log('[DEBUG] Rcon closed!') + } + }) + } + + close () { + this.connected = false + this.socket.end() + } + + async auth (password: string) { + if (this.authed) { throw new Error('Already authed') } + + if (this.connected){ + await this.sendPackage(3, password) + } else { + return new Promise(resolve => { + this.socket.on('connect', async () => { + await this.sendPackage(3, password) + resolve() + }) + }) + } + } + + command (cmd: string) { + return this.sendPackage(2, cmd) + } + + sendPackage (type: number, payload: string) { + const id = this.nextId + this.nextId++ + + if (!this.connected) { throw new Error('Cannot send package while not connected') } + + const length = 14 + payload.length + const buff = Buffer.alloc(length) + buff.writeInt32LE(length - 4, 0) + buff.writeInt32LE(id, 4) + buff.writeInt32LE(type, 8) + + buff.write(payload, 12) + buff.writeInt8(0, length - 2) + buff.writeInt8(0, length - 1) + + this.socket.write(buff) + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + delete this.packages[id] + return reject('Server sent no request in ' + this.timeout / 1000 + ' seconds') + }, this.timeout) + + this.packages[id] = (type: number, response: any) => { + clearTimeout(timeout) + const err = type >= 0 ? false : 'Server sent package code ' + type + if (this.debug) { + console.log('[DEBUG] Received response: ' + response) + } + if (err) return reject(err) + return resolve(response) + } + }) + } +} + +export default Rcon diff --git a/src/Shulker.ts b/src/Shulker.ts new file mode 100644 index 0000000..9de927d --- /dev/null +++ b/src/Shulker.ts @@ -0,0 +1,56 @@ +import DiscordClient from './Discord' +import Handler, { LogLine } from './MinecraftHandler' + +import { Config } from './Config' + +class Shulker { + config: Config + discordClient: DiscordClient + handler: Handler + + constructor() { + } + + loadConfig () { + const configFile = (process.argv.length > 2) ? process.argv[2] : '../config.json' + console.log('[INFO] Using configuration file:', configFile) + this.config = require(configFile) + if (!this.config) { + console.log('[ERROR] Could not load config file!') + return false + } + + if (this.config.USE_WEBHOOKS) { + console.log('[INFO] Using Discord WebHooks to send messages') + } else { + console.log('[INFO] Using Discord bot to send messages') + } + + return true + } + + fixMinecraftUsername (username: string) { + return username.replace(/(§[A-Z-a-z0-9])/g, '') + } + + onDiscordReady () { + this.handler.init(async (data: LogLine) => { + if (data) { + const { username, message } = data + await this.discordClient.sendMessage(username, message) + } + }) + } + + async init () { + const loaded = this.loadConfig() + if (!loaded) return + + this.discordClient = new DiscordClient(this.config, () => this.onDiscordReady()) + this.handler = new Handler(this.config) + + await this.discordClient.init() + } +} + +export default Shulker diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..d69cdb0 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,8 @@ +import Shulker from './Shulker' + +const main = async () => { + const shulker = new Shulker() + await shulker.init() +} + +main() -- cgit 1.4.1 From 4ba376b36d1f1d56c50ef8f8cee591f4cf382b24 Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Tue, 4 Feb 2020 01:06:59 -0500 Subject: Code cleanup --- src/MinecraftHandler.ts | 23 ++++++++++++----------- src/Shulker.ts | 4 ---- 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/MinecraftHandler.ts b/src/MinecraftHandler.ts index 39ab914..79ad81b 100644 --- a/src/MinecraftHandler.ts +++ b/src/MinecraftHandler.ts @@ -26,22 +26,22 @@ class MinecraftHandler { } private parseLogLine (data: string): LogLine { - if (this.config.DEBUG) console.log('[DEBUG] Received ' + data) - const ignored = new RegExp(this.config.REGEX_IGNORED_CHAT) - if (ignored.test(data)) { + if (ignored.test(data) || data.includes('Rcon connection')) { if (this.config.DEBUG) console.log('[DEBUG] Line ignored') return null } + if (this.config.DEBUG) console.log('[DEBUG] Received ' + data) + const logLineDataRegex = new RegExp( `${(this.config.REGEX_SERVER_PREFIX || "\\[Server thread/INFO\\]:")} (.*)` ) + // get the part after the log prefix, so all the actual data is here const logLineData = data.match(logLineDataRegex) - if (data.includes('Rcon connection')) return null if (!logLineDataRegex.test(data) || !logLineData) { console.log('[ERROR] Regex could not match the string! Please verify it is correct!') console.log('Received: "' + data + '", Regex matches lines that start with: "' + this.config.REGEX_SERVER_PREFIX + '"') @@ -84,13 +84,14 @@ class MinecraftHandler { } return { username: serverUsername, message: logLine } - } else if (this.config.SHOW_PLAYER_ADVANCEMENT && logLine.includes('granted the advancement')) { - // handle achievement earning + } else if (this.config.SHOW_PLAYER_ADVANCEMENT && logLine.includes('made the advancement')) { + // handle advancements if (this.config.DEBUG){ - console.log('[DEBUG] A player has earned an achievement') + console.log('[DEBUG] A player has made an advancement') } return { username: `${this.config.SERVER_NAME} - Server`, message: logLine } } else if (this.config.SHOW_PLAYER_ME && logLine.startsWith('* ')) { + // /me commands have the bolded name and the action they did const usernameMatch = data.match(/: \* ([a-zA-Z0-9_]{1,16}) (.*)/) if (usernameMatch) { const username = usernameMatch[1] @@ -145,10 +146,10 @@ class MinecraftHandler { console.log('[INFO] Bot listening on *:' + port) if (!this.config.IS_LOCAL_FILE && this.config.SHOW_INIT_MESSAGE) { - console.log('[INFO] Please enter the following command on your server to send the logs to the server.') - console.log(' Be sure to replace "PATH_TO_MINECRAFT_SERVER_INSTALL" with the path to your Minecraft install') - console.log(' and replace "YOUR_URL" with the URL/IP of the server running Shulker!') - console.log(` \`tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL:${port}${this.config.WEBHOOK} ; done\``) + console.log('[INFO] Please enter the following command on your server running the Minecraft server.') + console.log(' Replace "PATH_TO_MINECRAFT_SERVER_INSTALL" with the path to your Minecraft server install') + console.log(' and "YOUR_URL" with the URL/IP of the server running Shulker!') + console.log(` \`tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered "${this.config.REGEX_SERVER_PREFIX}" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL:${port}${this.config.WEBHOOK} ; done\``) } }) } diff --git a/src/Shulker.ts b/src/Shulker.ts index 9de927d..16921d6 100644 --- a/src/Shulker.ts +++ b/src/Shulker.ts @@ -29,10 +29,6 @@ class Shulker { return true } - fixMinecraftUsername (username: string) { - return username.replace(/(§[A-Z-a-z0-9])/g, '') - } - onDiscordReady () { this.handler.init(async (data: LogLine) => { if (data) { -- cgit 1.4.1 From 85e8c38f51d14a669cc5d7bb24c210d9d72654da Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Tue, 4 Feb 2020 10:21:17 -0500 Subject: Docs updates --- README.md | 41 ++++++++++++++++++++++++++++++++++++----- package.json | 2 +- src/MinecraftHandler.ts | 2 +- 3 files changed, 38 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 5917a3c..68d7555 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,12 @@ ## In Action ![discord-mc](http://i.thedestruc7i0n.ca/I5anbg.gif) +## Features +- Sends message to and from Vanilla Minecraft servers +- Can send messages regarding advancements, when players join and leave, and player deaths +- Allows admins to send commands to Minecraft through Discord + + ## Installation and usage Create a Discord bot here: https://discordapp.com/developers/applications/me @@ -24,15 +30,16 @@ rcon.port=<1-65535> Clone repository onto a server, edit ```config.json``` (see below for more info) and change any options, and then, in the repository folder: ```sh $ yarn -$ yarn start +$ yarn build && yarn start ``` -If you are running this locally, check the `IS_LOCAL_FILE` flag and related options below. Otherwise, perform the following command: +If you are running this locally, enable the `IS_LOCAL_FILE` flag and related options below. Otherwise, perform the following command: On your server hosting (in a screen/tmux session or background process, make sure to replace your `YOUR_URL` with whatever URL you're using (`localhost:8000` if running on the same server and default config) and `PATH_TO_MINECRAFT_SERVER_INSTALL` with the path to the Minecraft server installation, such as `/usr/home/minecraft_server/`): ``` sh tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL/minecraft/hook ; done ``` +(The above command will also be given to you if you are not using a local file when you start up Shulker) You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_URL` in the command to match accordingly. @@ -57,25 +64,49 @@ You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_UR "IS_LOCAL_FILE": false, /* should tail the local file, may be a little buggy. please report any you find */ "LOCAL_FILE_PATH": "/usr/home/minecraft_server/logs/latest.log", /* the path to the local file if specified */ + + "SHOW_INIT_MESSAGE": true, /* Sends the message on boot if not a local file of what command to run */ + "ALLOW_USER_MENTIONS": false, /* should replace @mentions with the mention in discord */ + "ALLOW_HERE_EVERYONE_MENTIONS": false, /* replaces @everyone and @here with "@ everyone" and "@ here" respectively */ + "ALLOW_SLASH_COMMANDS": false, /* whether to allow users to run slash commands from discord */ + "SLASH_COMMAND_ROLES": [], /* if the above is enabled, the names of the roles which can run slash commands */ "WEBHOOK": "/minecraft/hook", /* Web hook, where to send the log to */ - "REGEX_MATCH_CHAT_MC": "\\[Server thread/INFO\\]: <(.*)> (.*)", /* What to match for chat (best to leave as default) */ + "REGEX_SERVER_PREFIX": "\\[Server thread/INFO\\]:", /* What the lines of the log should start with */ + "REGEX_MATCH_CHAT_MC": "^<([^>]*)> (.*)", /* What to match for chat (best to leave as default) */ "REGEX_IGNORED_CHAT": "packets too frequently", /* What to ignore, you can put any regex for swear words for example and it will be ignored */ - "DEBUG": false /* Dev debugging */ + "DEBUG": false, /* Dev debugging */ + + "SERVER_NAME" : "Shulker", /* The username used when displaying any server information in chat, e.g., Server - Shulker : Server message here*/ + "SERVER_IMAGE": "", /* Image for the server when sending such messages (if enabled below). Only for WebHooks. */ + "SHOW_PLAYER_CONN_STAT" : false, /* Shows player connection status in chat, e.g., Server - Shulker : TheMachine joined the game */ + "SHOW_PLAYER_ADVANCEMENT" : false, /* Shows when players earn achievements in chat, e.g., Server - Shulker : TheMachine has earned the achievement [MEME - Machine] */ + "SHOW_PLAYER_DEATH" : false, /* Shows when players die in chat, e.g., Server - Shulker : TheMachine was blown up by creeper */ + "SHOW_PLAYER_ME" : false, /* Shows when players use the /me command, e.g. **destruc7i0n** says hello */ + "DEATH_KEY_WORDS" : ["shot", "fell", "etc".] /* Key words to look for when trying to identify a death message. (As of 3/11/2019 this list is up to date) */ } ``` +## FAQ +* How do I make this work on a modded server? + - Try replacing `REGEX_SERVER_PREFIX` with `"\\[Server thread/INFO\\] \\[.*\\]:"` + +* Why can't I send commands even if I have the option enabled? + - Make sure that you have a role on the server which is put in the array `SLASH_COMMAND_ROLES` case-sensitive. + - e.g. `"SLASH_COMMAND_ROLES": ["Admin"],` + ## Upcoming None ## Suggestions -If you have any suggestions or feature requests, feel free to add an issue and I will take a look and possibly add it to the "Upcoming" section! +If you have any suggestions or feature requests, feel free to add an issue and I will take a look. ## Thanks * [hydrabolt](https://github.com/hydrabolt) for discord.js * [qrush](https://github.com/qrush) for the idea of this ([wither](https://github.com/qrush/wither)) * [SecretOnline](https://github.com/secretonline) for Rcon reconnecting and for making it only send messages in specified channel +* [TheZackCodec](https://github.com/TheZackCodec/) for the updates in server based messages ## License diff --git a/package.json b/package.json index 7da3554..e4b799c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shulker", - "version": "2.1.0", + "version": "3.0.0", "description": "A Discord to Minecraft Chat Gateway", "main": "build/index.js", "scripts": { diff --git a/src/MinecraftHandler.ts b/src/MinecraftHandler.ts index 79ad81b..aa4f2f7 100644 --- a/src/MinecraftHandler.ts +++ b/src/MinecraftHandler.ts @@ -149,7 +149,7 @@ class MinecraftHandler { console.log('[INFO] Please enter the following command on your server running the Minecraft server.') console.log(' Replace "PATH_TO_MINECRAFT_SERVER_INSTALL" with the path to your Minecraft server install') console.log(' and "YOUR_URL" with the URL/IP of the server running Shulker!') - console.log(` \`tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered "${this.config.REGEX_SERVER_PREFIX}" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL:${port}${this.config.WEBHOOK} ; done\``) + console.log(` \`tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL:${port}${this.config.WEBHOOK} ; done\``) } }) } -- cgit 1.4.1 From 7c4dd4a8f126701e2a4a63bd9d860e2068941bd7 Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Tue, 4 Feb 2020 12:57:59 -0500 Subject: Update types --- README.md | 3 +-- src/Discord.ts | 22 ++++++++++------------ src/MinecraftHandler.ts | 18 +++++++++--------- src/Rcon.ts | 33 ++++++++++++++++++++++----------- src/Shulker.ts | 2 +- 5 files changed, 43 insertions(+), 35 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 68d7555..0601c79 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,6 @@ - Can send messages regarding advancements, when players join and leave, and player deaths - Allows admins to send commands to Minecraft through Discord - ## Installation and usage Create a Discord bot here: https://discordapp.com/developers/applications/me @@ -67,7 +66,7 @@ You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_UR "SHOW_INIT_MESSAGE": true, /* Sends the message on boot if not a local file of what command to run */ - "ALLOW_USER_MENTIONS": false, /* should replace @mentions with the mention in discord */ + "ALLOW_USER_MENTIONS": false, /* should replace @mentions with the mention in discord (format: @username#discriminator) */ "ALLOW_HERE_EVERYONE_MENTIONS": false, /* replaces @everyone and @here with "@ everyone" and "@ here" respectively */ "ALLOW_SLASH_COMMANDS": false, /* whether to allow users to run slash commands from discord */ "SLASH_COMMAND_ROLES": [], /* if the above is enabled, the names of the roles which can run slash commands */ diff --git a/src/Discord.ts b/src/Discord.ts index 7088c0e..a0a3498 100644 --- a/src/Discord.ts +++ b/src/Discord.ts @@ -28,7 +28,7 @@ class Discord { } } - async onMessage (message: Message) { + private async onMessage (message: Message) { // don't want to check other channels if (message.channel.id !== this.config.DISCORD_CHANNEL_ID || message.channel.type !== 'text') return // if using webhooks, ignore this! @@ -50,7 +50,7 @@ class Discord { if (this.config.ALLOW_SLASH_COMMANDS && this.config.SLASH_COMMAND_ROLES && message.cleanContent.startsWith('/')) { const author = message.member if (author.roles.find(r => this.config.SLASH_COMMAND_ROLES.includes(r.name))) { - // raw command, can be dangerous... + // send the raw command, can be dangerous... command = message.cleanContent } else { console.log('[INFO] User attempted a slash command without a role') @@ -68,7 +68,7 @@ class Discord { rcon.close() } - makeMinecraftTellraw(message: Message): string { + private makeMinecraftTellraw(message: Message): string { const username = emojiStrip(message.author.username) const discriminator = message.author.discriminator const text = emojiStrip(message.cleanContent) @@ -81,7 +81,7 @@ class Discord { .replace('%message%', variables.text) } - replaceDiscordMentions(message: string): string { + private replaceDiscordMentions(message: string): string { const possibleMentions = message.match(/@(\S+)/gim) if (possibleMentions) { for (let mention of possibleMentions) { @@ -91,9 +91,7 @@ class Discord { if (this.config.ALLOW_USER_MENTIONS) { const user = this.client.users.find(user => user.username === username && user.discriminator === mentionParts[1]) if (user) { - if (this.config.ALLOW_USER_MENTIONS) { - message = message.replace(mention, '<@' + user.id + '>') - } + message = message.replace(mention, '<@' + user.id + '>') } } } @@ -111,13 +109,13 @@ class Discord { return message } - makeDiscordWebhook (username: string, message: string) { + private makeDiscordWebhook (username: string, message: string) { message = this.replaceDiscordMentions(message) let avatarURL - if (username === this.config.SERVER_NAME + ' - Server') { // Use avatar for the server + if (username === this.config.SERVER_NAME + ' - Server') { // use avatar for the server avatarURL = this.config.SERVER_IMAGE || 'https://minotar.net/helm/Steve/256.png' - } else { // Use avatar for player + } else { // use avatar for player avatarURL = `https://minotar.net/helm/${username}/256.png` } @@ -128,7 +126,7 @@ class Discord { } } - makeDiscordMessage(username: string, message: string) { + private makeDiscordMessage(username: string, message: string) { message = this.replaceDiscordMentions(message) return this.config.DISCORD_MESSAGE_TEMPLATE @@ -136,7 +134,7 @@ class Discord { .replace('%message%', message) } - async sendMessage (username: string, message: string) { + public async sendMessage (username: string, message: string) { if (this.config.USE_WEBHOOKS) { const webhook = this.makeDiscordWebhook(username, message) try { diff --git a/src/MinecraftHandler.ts b/src/MinecraftHandler.ts index aa4f2f7..7ed9d86 100644 --- a/src/MinecraftHandler.ts +++ b/src/MinecraftHandler.ts @@ -21,7 +21,7 @@ class MinecraftHandler { this.config = config } - fixMinecraftUsername (username: string) { + private fixMinecraftUsername (username: string) { return username.replace(/(§[A-Z-a-z0-9])/g, '') } @@ -50,6 +50,7 @@ class MinecraftHandler { const logLine = logLineData[1] + // the username used for server messages const serverUsername = `${this.config.SERVER_NAME} - Server` if (logLine.startsWith('<')) { @@ -117,9 +118,8 @@ class MinecraftHandler { private initWebServer (callback: Callback) { // init the webserver this.app = express() - const http = require('http').Server(this.app) - this.app.use((request: express.Request, response: express.Response, next: express.NextFunction) => { + this.app.use((request, response, next) => { request.rawBody = '' request.setEncoding('utf8') @@ -140,9 +140,9 @@ class MinecraftHandler { res.json({ received: true }) }) - const port = process.env.PORT || this.config.PORT + const port: number = Number(process.env.PORT) || this.config.PORT - http.listen(port, () => { + this.app.listen(port, () => { console.log('[INFO] Bot listening on *:' + port) if (!this.config.IS_LOCAL_FILE && this.config.SHOW_INIT_MESSAGE) { @@ -156,10 +156,10 @@ class MinecraftHandler { private initTail (callback: Callback) { if (fs.existsSync(this.config.LOCAL_FILE_PATH)) { - console.log(`[INFO] Using configuration for local file at "${this.config.LOCAL_FILE_PATH}"`) + console.log(`[INFO] Using configuration for local log file at "${this.config.LOCAL_FILE_PATH}"`) this.tail = new Tail(this.config.LOCAL_FILE_PATH) } else { - throw new Error(`[ERROR] Local file not found at "${this.config.LOCAL_FILE_PATH}"`) + throw new Error(`[ERROR] Local log file not found at "${this.config.LOCAL_FILE_PATH}"`) } this.tail.on('line', (data: string) => { // Parse the line to see if we care about it @@ -169,11 +169,11 @@ class MinecraftHandler { } }) this.tail.on('error', (error: any) => { - console.log('[ERROR] Error tailing file: ' + error) + console.log('[ERROR] Error tailing log file: ' + error) }) } - init (callback: Callback) { + public init (callback: Callback) { if (this.config.IS_LOCAL_FILE) { this.initTail(callback) } else { diff --git a/src/Rcon.ts b/src/Rcon.ts index 81cf3ea..180ad38 100644 --- a/src/Rcon.ts +++ b/src/Rcon.ts @@ -14,7 +14,7 @@ class Rcon { ip: string port: number - packages: any + packages: { [key: number]: (type: number, response: string) => void } constructor (ip: string, port: number, debug: boolean) { this.ip = ip @@ -32,7 +32,7 @@ class Rcon { console.log('[INFO] Authenticated with ' + ip + ':' + port) }) - this.socket.on('data', (data) => { + this.socket.on('data', (data: Buffer) => { const id = data.readInt32LE(4) const type = data.readInt32LE(8) const response = data.toString('ascii', 12, data.length - 2) @@ -49,31 +49,42 @@ class Rcon { }) } - close () { + public close () { this.connected = false this.socket.end() } - async auth (password: string) { + public async auth (password: string): Promise { if (this.authed) { throw new Error('Already authed') } if (this.connected){ - await this.sendPackage(3, password) + try { + await this.sendPackage(3, password) + } catch (e) { + console.log('[ERROR] Could not send password to Rcon server!') + if (this.debug) console.error(e) + } } else { - return new Promise(resolve => { + return new Promise((resolve, reject) => { this.socket.on('connect', async () => { - await this.sendPackage(3, password) - resolve() + try { + await this.sendPackage(3, password) + resolve() + } catch (e) { + console.log('[ERROR] Could not send password to Rcon server!') + if (this.debug) console.error(e) + reject(e) + } }) }) } } - command (cmd: string) { + public command (cmd: string): Promise { return this.sendPackage(2, cmd) } - sendPackage (type: number, payload: string) { + public sendPackage (type: number, payload: string): Promise { const id = this.nextId this.nextId++ @@ -97,7 +108,7 @@ class Rcon { return reject('Server sent no request in ' + this.timeout / 1000 + ' seconds') }, this.timeout) - this.packages[id] = (type: number, response: any) => { + this.packages[id] = (type: number, response: string) => { clearTimeout(timeout) const err = type >= 0 ? false : 'Server sent package code ' + type if (this.debug) { diff --git a/src/Shulker.ts b/src/Shulker.ts index 16921d6..4c79c78 100644 --- a/src/Shulker.ts +++ b/src/Shulker.ts @@ -23,7 +23,7 @@ class Shulker { if (this.config.USE_WEBHOOKS) { console.log('[INFO] Using Discord WebHooks to send messages') } else { - console.log('[INFO] Using Discord bot to send messages') + console.log('[INFO] Using the Discord bot to send messages') } return true -- cgit 1.4.1 From ff4a9fa8b148da00bff9dd7cb447e9d5321ef84e Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Wed, 5 Feb 2020 19:32:35 -0500 Subject: More config handling --- src/Config.ts | 3 +++ src/MinecraftHandler.ts | 27 +++++++++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/Config.ts b/src/Config.ts index 036e057..5688f2e 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -15,6 +15,9 @@ export interface Config { IS_LOCAL_FILE: boolean LOCAL_FILE_PATH: string + PATH_TO_MINECRAFT_SERVER_INSTALL?: string + YOUR_URL?: string + SHOW_INIT_MESSAGE: boolean ALLOW_USER_MENTIONS: boolean diff --git a/src/MinecraftHandler.ts b/src/MinecraftHandler.ts index 7ed9d86..20bccf5 100644 --- a/src/MinecraftHandler.ts +++ b/src/MinecraftHandler.ts @@ -1,4 +1,5 @@ import fs from 'fs' +import path from 'path' import { Tail } from 'tail' import express from 'express' @@ -21,7 +22,7 @@ class MinecraftHandler { this.config = config } - private fixMinecraftUsername (username: string) { + private static fixMinecraftUsername (username: string) { return username.replace(/(§[A-Z-a-z0-9])/g, '') } @@ -66,7 +67,7 @@ class MinecraftHandler { return null } - const username = this.fixMinecraftUsername(matches[1]) + const username = MinecraftHandler.fixMinecraftUsername(matches[1]) const message = matches[2] if (this.config.DEBUG) { console.log('[DEBUG] Username: ' + matches[1]) @@ -146,10 +147,24 @@ class MinecraftHandler { console.log('[INFO] Bot listening on *:' + port) if (!this.config.IS_LOCAL_FILE && this.config.SHOW_INIT_MESSAGE) { - console.log('[INFO] Please enter the following command on your server running the Minecraft server.') - console.log(' Replace "PATH_TO_MINECRAFT_SERVER_INSTALL" with the path to your Minecraft server install') - console.log(' and "YOUR_URL" with the URL/IP of the server running Shulker!') - console.log(` \`tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL:${port}${this.config.WEBHOOK} ; done\``) + // in case someone inputs the actual path and url in the config here... + let mcPath: string = this.config.PATH_TO_MINECRAFT_SERVER_INSTALL || 'PATH_TO_MINECRAFT_SERVER_INSTALL' + const url: string = this.config.YOUR_URL || 'YOUR_URL' + + const defaultPath = mcPath === 'PATH_TO_MINECRAFT_SERVER_INSTALL' + const defaultUrl = url === 'YOUR_URL' + + console.log('[INFO] Please enter the following command on your server running the Minecraft server:') + if (defaultPath) { + console.log(' Replace "PATH_TO_MINECRAFT_SERVER_INSTALL" with the path to your Minecraft server install') + if (defaultUrl) console.log(' and "YOUR_URL" with the URL/IP of the server running Shulker!') + } else { + if (defaultUrl) console.log(' Replace "YOUR_URL" with the URL/IP of the server running Shulker') + } + + mcPath = (defaultPath ? '/' : '') + path.join(mcPath, '/logs/latest.log') + + console.log(` \`tail -F ${mcPath} | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://${url}:${port}${this.config.WEBHOOK} ; done\``) } }) } -- cgit 1.4.1 From f99e623f643c0a55dfa27a792fe451ccba64d7ec Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Wed, 5 Feb 2020 21:23:00 -0500 Subject: Some more cleanup --- LICENSE | 2 +- README.md | 14 +++++++------- config.json | 1 - src/Config.ts | 1 - 4 files changed, 8 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/LICENSE b/LICENSE index 3fde45f..cf96254 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2016, destruc7i0n +Copyright (c) 2020, destruc7i0n Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above diff --git a/README.md b/README.md index 0601c79..3cd1a99 100644 --- a/README.md +++ b/README.md @@ -77,13 +77,13 @@ You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_UR "REGEX_IGNORED_CHAT": "packets too frequently", /* What to ignore, you can put any regex for swear words for example and it will be ignored */ "DEBUG": false, /* Dev debugging */ - "SERVER_NAME" : "Shulker", /* The username used when displaying any server information in chat, e.g., Server - Shulker : Server message here*/ + "SERVER_NAME": "Shulker", /* The username used when displaying any server information in chat, e.g., Server - Shulker : Server message here*/ "SERVER_IMAGE": "", /* Image for the server when sending such messages (if enabled below). Only for WebHooks. */ - "SHOW_PLAYER_CONN_STAT" : false, /* Shows player connection status in chat, e.g., Server - Shulker : TheMachine joined the game */ - "SHOW_PLAYER_ADVANCEMENT" : false, /* Shows when players earn achievements in chat, e.g., Server - Shulker : TheMachine has earned the achievement [MEME - Machine] */ - "SHOW_PLAYER_DEATH" : false, /* Shows when players die in chat, e.g., Server - Shulker : TheMachine was blown up by creeper */ - "SHOW_PLAYER_ME" : false, /* Shows when players use the /me command, e.g. **destruc7i0n** says hello */ - "DEATH_KEY_WORDS" : ["shot", "fell", "etc".] /* Key words to look for when trying to identify a death message. (As of 3/11/2019 this list is up to date) */ + "SHOW_PLAYER_CONN_STAT": false, /* Shows player connection status in chat, e.g., Server - Shulker : TheMachine joined the game */ + "SHOW_PLAYER_ADVANCEMENT": false, /* Shows when players earn advancements in chat, e.g., Server - Shulker : TheMachine has made the advacement [MEME - Machine] */ + "SHOW_PLAYER_DEATH": false, /* Shows when players die in chat, e.g., Server - Shulker : TheMachine was blown up by creeper */ + "SHOW_PLAYER_ME": false, /* Shows when players use the /me command, e.g. **destruc7i0n** says hello */ + "DEATH_KEY_WORDS": ["shot", "fell", "etc".] /* Key words to look for when trying to identify a death message. (As of 3/11/2019 this list is up to date) */ } ``` @@ -93,7 +93,7 @@ You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_UR * Why can't I send commands even if I have the option enabled? - Make sure that you have a role on the server which is put in the array `SLASH_COMMAND_ROLES` case-sensitive. - - e.g. `"SLASH_COMMAND_ROLES": ["Admin"],` + - e.g. `"SLASH_COMMAND_ROLES": ["Admin"]` ## Upcoming None diff --git a/config.json b/config.json index 60ab378..2179402 100644 --- a/config.json +++ b/config.json @@ -26,7 +26,6 @@ "REGEX_SERVER_PREFIX": "\\[Server thread/INFO\\]:", "REGEX_MATCH_CHAT_MC": "^<([^>]*)> (.*)", "REGEX_IGNORED_CHAT": "packets too frequently", - "RCON_RECONNECT_DELAY": 10, "DEBUG": false, "SERVER_NAME": "Shulker", diff --git a/src/Config.ts b/src/Config.ts index 5688f2e..77fefd2 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -29,7 +29,6 @@ export interface Config { REGEX_SERVER_PREFIX: string REGEX_MATCH_CHAT_MC: string REGEX_IGNORED_CHAT: string - RCON_RECONNECT_DELAY: number DEBUG: boolean SERVER_NAME: string -- cgit 1.4.1 From 4d08955c59c35bcbe79a8e202fb947551e7470b8 Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Wed, 5 Feb 2020 22:07:14 -0500 Subject: Allow specification of the channel name rather than id --- README.md | 8 +++++--- config.json | 3 ++- src/Config.ts | 1 + src/Discord.ts | 27 ++++++++++++++++++++++++--- src/MinecraftHandler.ts | 15 ++++++++++++--- 5 files changed, 44 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/README.md b/README.md index 3cd1a99..64abf69 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,15 @@ rcon.password= rcon.port=<1-65535> ``` -Clone repository onto a server, edit ```config.json``` (see below for more info) and change any options, and then, in the repository folder: +Clone repository onto a server, edit ```config.json``` (see below for more info) and change any options. +Then, in the repository folder: ```sh $ yarn $ yarn build && yarn start ``` -If you are running this locally, enable the `IS_LOCAL_FILE` flag and related options below. Otherwise, perform the following command: -On your server hosting (in a screen/tmux session or background process, make sure to replace your `YOUR_URL` with whatever URL you're using (`localhost:8000` if running on the same server and default config) and `PATH_TO_MINECRAFT_SERVER_INSTALL` with the path to the Minecraft server installation, such as `/usr/home/minecraft_server/`): +If you are running this on the same server as the MC server, enable the `IS_LOCAL_FILE` flag and update related options below. +Otherwise, perform the following command on the server hosting (in a screen/tmux session or background process, make sure to replace your `YOUR_URL` with whatever URL you're using (`localhost:8000` if running on the same server and default config) and `PATH_TO_MINECRAFT_SERVER_INSTALL` with the path to the Minecraft server installation, such as `/usr/home/minecraft_server/`): ``` sh tail -F /PATH_TO_MINECRAFT_SERVER_INSTALL/logs/latest.log | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://YOUR_URL/minecraft/hook ; done @@ -54,6 +55,7 @@ You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_UR "WEBHOOK_URL": "DISCORD_WEBHOOK_URL_HERE", /* Be sure to create a webhook in the channel settings and place it here! */ "DISCORD_TOKEN": "<12345>", /* Discord bot token. [Click here](https://discordapp.com/developers/applications/me) to create you application and add a bot to it. */ "DISCORD_CHANNEL_ID": "", /* Discord channel ID for for the discord bot. Enable developer mode in your Discord client, then right click channel and select "Copy ID". */ + "DISCORD_CHANNEL_NAME": "#" /* The Discord channel name. It is recommended to use the ID if the bot is in multiple servers. The ID will take precedence. */ "DISCORD_MESSAGE_TEMPLATE": "`%username%`:%message%", /* Message template to display in Discord */ "MINECRAFT_SERVER_RCON_IP": "127.0.0.1", /* Minecraft server IP (make sure you have enabled rcon) */ diff --git a/config.json b/config.json index 2179402..43d7140 100644 --- a/config.json +++ b/config.json @@ -4,7 +4,8 @@ "USE_WEBHOOKS": true, "WEBHOOK_URL": "DISCORD_WEBHOOK_URL_HERE", "DISCORD_TOKEN": "TOKEN_HERE", - "DISCORD_CHANNEL_ID": "1234", + "DISCORD_CHANNEL_ID": "", + "DISCORD_CHANNEL_NAME": "#bot", "DISCORD_MESSAGE_TEMPLATE": "`%username%`: %message%", "MINECRAFT_SERVER_RCON_IP": "127.0.0.1", diff --git a/src/Config.ts b/src/Config.ts index 77fefd2..ce59ed2 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -5,6 +5,7 @@ export interface Config { WEBHOOK_URL: string DISCORD_TOKEN: string DISCORD_CHANNEL_ID: string + DISCORD_CHANNEL_NAME: string DISCORD_MESSAGE_TEMPLATE: string MINECRAFT_SERVER_RCON_IP: string diff --git a/src/Discord.ts b/src/Discord.ts index a0a3498..97722c2 100644 --- a/src/Discord.ts +++ b/src/Discord.ts @@ -1,4 +1,4 @@ -import { Client, Message, TextChannel } from 'discord.js' +import {Client, Message, Snowflake, TextChannel} from 'discord.js' import emojiStrip from 'emoji-strip' import axios from 'axios' @@ -11,26 +11,47 @@ class Discord { config: Config client: Client + channel: Snowflake + constructor (config: Config, onReady?: () => void) { this.config = config this.client = new Client() if (onReady) this.client.once('ready', () => onReady()) this.client.on('message', (message: Message) => this.onMessage(message)) + + this.channel = config.DISCORD_CHANNEL_ID || '' } - async init () { + public async init () { try { await this.client.login(this.config.DISCORD_TOKEN) + if (this.config.DISCORD_CHANNEL_NAME) this.getChannelIdFromName(this.config.DISCORD_CHANNEL_NAME) } catch (e) { console.log('[ERROR] Could not authenticate with Discord: ' + e) if (this.config.DEBUG) console.error(e) } } + private getChannelIdFromName (name: string) { + // remove the # if there is one + if (name.startsWith('#')) name = name.substring(1, name.length) + // @ts-ignore + const channel: TextChannel = this.client.channels.find((c: TextChannel) => c.type === 'text' && c.name === name && !c.deleted) + if (channel) { + this.channel = channel.id + console.log(`[INFO] Found channel #${channel.name} (id: ${channel.id}) in the server "${channel.guild.name}"`) + } else { + console.log(`[INFO] Could not find channel ${name}! Check that the name is correct or use the ID of the channel instead (DISCORD_CHANNEL_ID)!`) + process.exit(1) + } + } + private async onMessage (message: Message) { + // no channel, done + if (!this.channel) return // don't want to check other channels - if (message.channel.id !== this.config.DISCORD_CHANNEL_ID || message.channel.type !== 'text') return + if (message.channel.id !== this.channel || message.channel.type !== 'text') return // if using webhooks, ignore this! if (this.config.USE_WEBHOOKS && message.webhookID) return // if the same user as the bot, ignore diff --git a/src/MinecraftHandler.ts b/src/MinecraftHandler.ts index 20bccf5..0e4e5d7 100644 --- a/src/MinecraftHandler.ts +++ b/src/MinecraftHandler.ts @@ -44,8 +44,10 @@ class MinecraftHandler { const logLineData = data.match(logLineDataRegex) if (!logLineDataRegex.test(data) || !logLineData) { - console.log('[ERROR] Regex could not match the string! Please verify it is correct!') - console.log('Received: "' + data + '", Regex matches lines that start with: "' + this.config.REGEX_SERVER_PREFIX + '"') + if (this.config.DEBUG) { + console.log('[DEBUG] Regex could not match the string:') + console.log('Received: "' + data + '", Regex matches lines that start with: "' + this.config.REGEX_SERVER_PREFIX + '"') + } return null } @@ -164,7 +166,14 @@ class MinecraftHandler { mcPath = (defaultPath ? '/' : '') + path.join(mcPath, '/logs/latest.log') - console.log(` \`tail -F ${mcPath} | grep --line-buffered ": <" | while read x ; do echo -ne $x | curl -X POST -d @- http://${url}:${port}${this.config.WEBHOOK} ; done\``) + let grepMatch = ': <' + if (this.config.SHOW_PLAYER_DEATH || this.config.SHOW_PLAYER_ME || this.config.SHOW_PLAYER_ADVANCEMENT || this.config.SHOW_PLAYER_CONN_STAT) { + grepMatch = this.config.REGEX_SERVER_PREFIX + } + console.log(` \`tail -F ${mcPath} | grep --line-buffered "${grepMatch}" | while read x ; do echo -ne $x | curl -X POST -d @- http://${url}:${port}${this.config.WEBHOOK} ; done\``) + if (grepMatch !== ': <') { + console.log(' Please note that the above command can send a lot of requests to the server. Disable the non-text messages (such as "SHOW_PLAYER_CONN_STAT") to reduce this if necessary.') + } } }) } -- cgit 1.4.1 From b174e696b2667990720b0b427eb9905a350c648f Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Wed, 5 Feb 2020 22:08:52 -0500 Subject: Precedence --- src/Discord.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/Discord.ts b/src/Discord.ts index 97722c2..bc15fac 100644 --- a/src/Discord.ts +++ b/src/Discord.ts @@ -26,7 +26,8 @@ class Discord { public async init () { try { await this.client.login(this.config.DISCORD_TOKEN) - if (this.config.DISCORD_CHANNEL_NAME) this.getChannelIdFromName(this.config.DISCORD_CHANNEL_NAME) + if (this.config.DISCORD_CHANNEL_NAME && !this.config.DISCORD_CHANNEL_ID) + this.getChannelIdFromName(this.config.DISCORD_CHANNEL_NAME) } catch (e) { console.log('[ERROR] Could not authenticate with Discord: ' + e) if (this.config.DEBUG) console.error(e) -- cgit 1.4.1