text: 'use strict'
  
  const Discord = require('discord.js')
  const Rcon = require('./lib/rcon.js')
  const express = require('express')
  const axios = require('axios')
  const emojiStrip = require('emoji-strip')
  const { Tail } = require('tail')
  const fs = require('fs')
  
  const configFile = (process.argv.length > 2) ? process.argv[2] : './config.json'
  
  console.log('[INFO] Using configuration file:', configFile)
  
  const c = require(configFile)
  
  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,
      'avatar_url': `https://minotar.net/helm/${username}/256.png`
    }
  }
  
  function makeMinecraftTellraw (message) {
    // same as the discord side but with discord message parameters
    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%', variables.username)
      .replace('%discriminator%', variables.discriminator)
      .replace('%message%', variables.text)
  }
  
  const debug = c.DEBUG
  const shulker = new Discord.Client()
  
  function initApp () {
    // run a server if not local
    if (!c.IS_LOCAL_FILE) {
      app = express()
      const http = require('http').Server(app)
  
      app.use(function (request, response, next) {
        request.rawBody = ''
        request.setEncoding('utf8')
  
        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 () {
    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)
      if (!ignored.test(body)) {
        const bodymatch = body.match(re)
        const username = fixUsername(bodymatch[1])
        const message = bodymatch[2]
        if (debug) {
          console.log('[DEBUG] Username: ' + bodymatch[1])
          console.log('[DEBUG] Text: ' + bodymatch[2])
        }
        if (c.USE_WEBHOOKS) {
          const webhook = makeDiscordWebhook(username, message)
          axios.post(c.WEBHOOK_URL, {
            ...webhook
          }, {
            headers: {
              'Content-Type': 'application/json'
            }
          })
        } else {
          // find the channel
          const channel = shulker.channels.find((ch) => ch.id === c.DISCORD_CHANNEL_ID && ch.type === 'text')
          channel.send(makeDiscordMessage(username, message))
        }
      }
    })
  })
  
  shulker.on('message', function (message) {
    if (message.channel.id === c.DISCORD_CHANNEL_ID && message.channel.type === 'text') {
      if (c.USE_WEBHOOKS && message.webhookID) {
        return // ignore webhooks if using a webhook
      }
      if (message.author.id !== shulker.user.id) {
        if (message.attachments.length) { // skip images/attachments
          return
        }
        const client = new Rcon(c.MINECRAFT_SERVER_RCON_IP, c.MINECRAFT_SERVER_RCON_PORT) // create rcon client
        client.auth(c.MINECRAFT_SERVER_RCON_PASSWORD, function () { // only authenticate when needed
          client.command('tellraw @a ' + makeMinecraftTellraw(message), function (err) {
            if (err) {
              console.log('[ERROR]', err)
            }
            client.close() // close the rcon connection
          })
        })
      }
    }
  })
  
  initApp()
  shulker.login(c.DISCORD_TOKEN)
  