From a5e932adbe930630d68a81d625b9499379eeeb63 Mon Sep 17 00:00:00 2001 From: destruc7i0n Date: Mon, 11 Feb 2019 20:00:25 -0500 Subject: Local file support --- README.md | 12 +++++- config.json | 8 ++++ index.js | 117 +++++++++++++++++++++++++++++++++++++++++++---------------- package.json | 3 +- yarn.lock | 5 +++ 5 files changed, 111 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 43c2141..5917a3c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ > Connects [Discord](https://discordapp.com/) and [Minecraft](https://minecraft.net) Servers by sending messages back and forth without any mods or plugins. ## In Action -![discord-irc](http://i.thedestruc7i0n.ca/I5anbg.gif) +![discord-mc](http://i.thedestruc7i0n.ca/I5anbg.gif) ## Installation and usage @@ -27,7 +27,8 @@ $ yarn $ yarn start ``` -Run the following 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 locally, check 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 @@ -42,15 +43,22 @@ You can also easily Deploy to Heroku and the like, just be sure to edit `YOUR_UR ```js { "PORT": 8000, /* Port you want to run the webserver for the hook on */ + "USE_WEBHOOKS": true, /* If you want to use snazzy webhooks */ "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_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) */ "MINECRAFT_SERVER_RCON_PORT": <1-65535>, /* Minecraft server rcon port */ "MINECRAFT_SERVER_RCON_PASSWORD": "", /* Minecraft server rcon password */ "MINECRAFT_TELLRAW_TEMPLATE": "[{\"color\": \"white\", \"text\": \"<%username%> %message%\"}]", /* Tellraw template to display in Minecraft */ + + "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 */ + "ALLOW_USER_MENTIONS": false, /* should replace @mentions with the mention in discord */ + "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_IGNORED_CHAT": "packets too frequently", /* What to ignore, you can put any regex for swear words for example and it will be ignored */ diff --git a/config.json b/config.json index 6f2dee1..e845cf1 100644 --- a/config.json +++ b/config.json @@ -1,14 +1,22 @@ { "PORT": 8000, + "USE_WEBHOOKS": true, "WEBHOOK_URL": "DISCORD_WEBHOOK_URL_HERE", "DISCORD_TOKEN": "TOKEN_HERE", "DISCORD_CHANNEL_ID": "1234", "DISCORD_MESSAGE_TEMPLATE": "`%username%`: %message%", + "MINECRAFT_SERVER_RCON_IP": "127.0.0.1", "MINECRAFT_SERVER_RCON_PORT": 25575, "MINECRAFT_SERVER_RCON_PASSWORD": "password", "MINECRAFT_TELLRAW_TEMPLATE": "[{\"color\": \"white\", \"text\": \"<%username%> %message%\"}]", + + "IS_LOCAL_FILE": false, + "LOCAL_FILE_PATH": "/usr/home/minecraft_server/logs/latest.log", + + "ALLOW_USER_MENTIONS": false, + "WEBHOOK": "/minecraft/hook", "REGEX_MATCH_CHAT_MC": "\\[Server thread/INFO\\]: <([^>]*)> (.*)", "REGEX_IGNORED_CHAT": "packets too frequently", diff --git a/index.js b/index.js index fae323c..e7b399e 100644 --- a/index.js +++ b/index.js @@ -5,8 +5,8 @@ const Rcon = require('./lib/rcon.js') const express = require('express') const axios = require('axios') const emojiStrip = require('emoji-strip') -const app = express() -const http = require('http').Server(app) +const { Tail } = require('tail') +const fs = require('fs') const configFile = (process.argv.length > 2) ? process.argv[2] : './config.json' @@ -14,16 +14,45 @@ console.log('[INFO] Using configuration file:', configFile) const c = require(configFile) -const fixUsername = (username) => username.replace(/(§[A-Z-a-z0-9])/g, '') +let app = null +let tail = null + +function fixUsername (username) { + return username.replace(/(§[A-Z-a-z0-9])/g, '') +} + +// replace mentions with discriminator with the actual mention +function replaceDiscordMentions (message) { + if (c.ALLOW_USER_MENTIONS) { + 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) { + const user = shulker.users.find(user => user.username === username && user.discriminator === mentionParts[1]) + if (user) { + message = message.replace(mention, '<@' + user.id + '>') + } + } + } + } + } + return message +} function makeDiscordMessage (username, message) { // make a discord message string by formatting the configured template with the given parameters + message = replaceDiscordMentions(message) + return c.DISCORD_MESSAGE_TEMPLATE .replace('%username%', username) .replace('%message%', message) } function makeDiscordWebhook (username, message) { + message = replaceDiscordMentions(message) + return { username: username, content: message, @@ -36,32 +65,70 @@ function makeMinecraftTellraw (message) { 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 c.MINECRAFT_TELLRAW_TEMPLATE - .replace('%username%', username) - .replace('%discriminator%', discriminator) - .replace('%message%', text) + .replace('%username%', variables.username) + .replace('%discriminator%', variables.discriminator) + .replace('%message%', variables.text) } const debug = c.DEBUG const shulker = new Discord.Client() -app.use(function (request, response, next) { - request.rawBody = '' - request.setEncoding('utf8') +function initApp () { + // run a server if not local + if (!c.IS_LOCAL_FILE) { + app = express() + const http = require('http').Server(app) - request.on('data', function (chunk) { - request.rawBody += chunk - }) + app.use(function (request, response, next) { + request.rawBody = '' + request.setEncoding('utf8') - request.on('end', function () { - next() - }) -}) + request.on('data', function (chunk) { + request.rawBody += chunk + }) + + request.on('end', function () { + next() + }) + }) + + const serverport = process.env.PORT || c.PORT + + http.listen(serverport, function () { + console.log('[INFO] Bot listening on *:' + serverport) + }) + } else { + if (fs.existsSync(c.LOCAL_FILE_PATH)) { + console.log('[INFO] Using configuration for local file at "' + c.LOCAL_FILE_PATH + '"') + tail = new Tail(c.LOCAL_FILE_PATH) + } else { + throw new Error('[ERROR] Local file not found at "' + c.LOCAL_FILE_PATH + '"') + } + } +} + +function watch (callback) { + if (c.IS_LOCAL_FILE) { + tail.on('line', function (data) { + // ensure that this is a message + if (data.indexOf(': <') !== -1) { + callback(data) + } + }) + } else { + app.post(c.WEBHOOK, function (request, response) { + callback(request.rawBody) + response.send('') + }) + } +} shulker.on('ready', function () { - app.post(c.WEBHOOK, function (request, response) { - const body = request.rawBody + watch(function (body) { console.log('[INFO] Recieved ' + body) const re = new RegExp(c.REGEX_MATCH_CHAT_MC) const ignored = new RegExp(c.REGEX_IGNORED_CHAT) @@ -88,7 +155,6 @@ shulker.on('ready', function () { channel.send(makeDiscordMessage(username, message)) } } - response.send('') }) }) @@ -114,16 +180,5 @@ shulker.on('message', function (message) { } }) +initApp() shulker.login(c.DISCORD_TOKEN) - -const ipaddress = process.env.OPENSHIFT_NODEJS_IP || process.env.IP || '127.0.0.1' -const serverport = process.env.OPENSHIFT_NODEJS_PORT || process.env.PORT || c.PORT -if (process.env.OPENSHIFT_NODEJS_IP !== undefined) { - http.listen(serverport, ipaddress, function () { - console.log('[INFO] Bot listening on *:' + serverport) - }) -} else { - http.listen(serverport, function () { - console.log('[INFO] Bot listening on *:' + c.PORT) - }) -} diff --git a/package.json b/package.json index 081c541..0b03fca 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "axios": "^0.18.0", "discord.js": "^11.4.2", "emoji-strip": "^1.0.1", - "express": "^4.16.4" + "express": "^4.16.4", + "tail": "^2.0.1" }, "devDependencies": { "standard": "^12.0.1" diff --git a/yarn.lock b/yarn.lock index fda7a7c..fd581ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1703,6 +1703,11 @@ table@^4.0.3: slice-ansi "1.0.0" string-width "^2.1.1" +tail@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tail/-/tail-2.0.1.tgz#f4cd9d514512e77d0eb2aaacd5520d578fe20271" + integrity sha512-9vPPjlv63kuIHLKVR7T65ey3CeukjcWA3f/JnfPyFd1KPSqWMh3qWyz2M5T7osAYQNYlaYtddovJ/X27tL0w1w== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" -- cgit 1.4.1