summary refs log tree commit diff homepage
path: root/src/Discord.ts
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Discord.ts180
1 files changed, 180 insertions, 0 deletions
diff --git a/src/Discord.ts b/src/Discord.ts
new file mode 100644
index 0000000..bc15fac
--- /dev/null
+++ b/src/Discord.ts
@@ -0,0 +1,180 @@
+import {Client, Message, Snowflake, 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
+
+  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 || ''
+  }
+
+  public async init () {
+    try {
+      await this.client.login(this.config.DISCORD_TOKEN)
+      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)
+    }
+  }
+
+  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.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
+    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))) {
+        // send the 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()
+  }
+
+  private 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)
+  }
+
+  private 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) {
+              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
+  }
+
+  private 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,
+    }
+  }
+
+  private makeDiscordMessage(username: string, message: string) {
+    message = this.replaceDiscordMentions(message)
+
+    return this.config.DISCORD_MESSAGE_TEMPLATE
+      .replace('%username%', username)
+      .replace('%message%', message)
+  }
+
+  public 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