diff --git a/README.md b/README.md
index fcaee9c..a58e3f1 100644
--- a/README.md
+++ b/README.md
@@ -26,11 +26,16 @@ settings:
password: 'password'
debug: false
relay_cancelled_messages: true
+ messages:
+ join: true
+ leave: true
+ death: false
templates:
discord:
chat_message: '<%u> %m'
player_join: '%u joined the server'
player_leave: '%u left the server'
+ player_death: '%r'
minecraft:
chat_message: '<%u&b(discord)&r> %m'
```
@@ -42,15 +47,34 @@ settings:
* `password` is the Discord password of your bot user
* `debug` enables more verbose logging
* `relay_cancelled_messages` will relay chat messages even if they are cancelled
-* `templates` - customize the message text - `%u` will be replaced with the username and `%m` will be replaced with the message. Color codes, prefixed with `&`, will be translated on the Minecraft end.
+* `messages` enables or disables certain kinds of messages
+* `templates` - customize the message text
+
+**Templates**
+
+- `%u` will be replaced with the username
+- '%d' will be replaced with the user's display name
+- `%m` will be replaced with the message
+- `%w` will be replaced with the world name
+- `%r` will be replaced with the death reason
+- Color codes, prefixed with `&`, will be translated on the Minecraft end
## Features
* Anything said in Minecraft chat will be sent to your chosen Discord channel
* Anything said in your chosen Discord channel will be sent to your Minecraft chat (with a `(discord)` suffix added to usernames)
* Join / leave messages are sent to Discord
+* Death messages can optionally be sent to Discord
* Message templates are customized
+## Permissions
+
+- `discordbridge.reload` - ability to reload config and reconnect the Discord connection
+
+## Commands
+
+- `/discord reload` - reloads config and reconnects to Discord
+
## Upcoming Features
* Deeper integration into Minecraft chat (like supporting chat channels inside Minecraft)
diff --git a/pom.xml b/pom.xml
index 93cdcae..527de9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
gg.obsidian
DiscordBridge
- 1.3.0
+ 1.4.0
Bridge chat between Discord and Minecraft
https://github.com/the-obsidian/DiscordBridge
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt
new file mode 100644
index 0000000..695e4ad
--- /dev/null
+++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt
@@ -0,0 +1,36 @@
+package gg.obsidian.discordbridge
+
+import org.bukkit.ChatColor
+import org.bukkit.command.Command
+import org.bukkit.command.CommandExecutor
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+class CommandHandler(val plugin: Plugin): CommandExecutor {
+
+ override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean {
+ if (player is Player && !Permissions.reload.has(player)) return true
+
+ val isConsole = (player is Player)
+
+ if (cmd.name != "discord") return true
+
+ if (args == null || args.size != 1 || !args[0].equals("reload")) {
+ sendMessage("&eUsage: /discord reload", player, isConsole)
+ return true
+ }
+
+ sendMessage("&eReloading Discord Bridge...", player, isConsole)
+ plugin.reload()
+ return true
+ }
+
+ private fun sendMessage(message: String, player: CommandSender, isConsole: Boolean) {
+ val formattedMessage = ChatColor.translateAlternateColorCodes('&', message)
+ if (isConsole) {
+ plugin.server.consoleSender.sendMessage(formattedMessage)
+ } else {
+ player.sendMessage(formattedMessage)
+ }
+ }
+}
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt
index d1557a2..7609f8e 100644
--- a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt
+++ b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt
@@ -9,9 +9,19 @@ class Configuration(val plugin: Plugin) {
var PASSWORD: String = ""
var DEBUG: Boolean = false
var RELAY_CANCELLED_MESSAGES = true
+
+ // Toggle message types
+ var MESSAGES_JOIN = true
+ var MESSAGES_LEAVE = true
+ var MESSAGES_DEATH = false
+
+ // Discord message templates
var TEMPLATES_DISCORD_CHAT_MESSAGE = ""
var TEMPLATES_DISCORD_PLAYER_JOIN = ""
var TEMPLATES_DISCORD_PLAYER_LEAVE = ""
+ var TEMPLATES_DISCORD_PLAYER_DEATH = ""
+
+ // Minecraft message templates
var TEMPLATES_MINECRAFT_CHAT_MESSAGE = ""
fun load() {
@@ -23,11 +33,17 @@ class Configuration(val plugin: Plugin) {
EMAIL = plugin.config.getString("settings.email")
PASSWORD = plugin.config.getString("settings.password")
DEBUG = plugin.config.getBoolean("settings.debug", false)
- RELAY_CANCELLED_MESSAGES = plugin.config.getBoolean("settings.relay_cancelled_messages", true);
+ RELAY_CANCELLED_MESSAGES = plugin.config.getBoolean("settings.relay_cancelled_messages", true)
+
+ MESSAGES_JOIN = plugin.config.getBoolean("settings.messages.join", true)
+ MESSAGES_LEAVE = plugin.config.getBoolean("settings.messages.leave", true)
+ MESSAGES_DEATH = plugin.config.getBoolean("settings.messages.death", false)
TEMPLATES_DISCORD_CHAT_MESSAGE = plugin.config.getString("settings.templates.discord.chat_message", "<%u> %m")
TEMPLATES_DISCORD_PLAYER_JOIN = plugin.config.getString("settings.templates.discord.player_join", "%u joined the server")
TEMPLATES_DISCORD_PLAYER_LEAVE = plugin.config.getString("settings.templates.discord.player_leave", "%u left the server")
+ TEMPLATES_DISCORD_PLAYER_DEATH = plugin.config.getString("settings.templates.discord.player_death", "%r")
+
TEMPLATES_MINECRAFT_CHAT_MESSAGE = plugin.config.getString("settings.templates.minecraft.chat_message", "<%u&b(discord)&r> %m")
}
}
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt
index 48f36d9..dd4decd 100644
--- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt
+++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt
@@ -6,12 +6,13 @@ import net.dv8tion.jda.entities.TextChannel
class DiscordConnection(val plugin: Plugin) : Runnable {
var api = JDABuilder(plugin.configuration.EMAIL, plugin.configuration.PASSWORD).build()
+ var listener = DiscordListener(plugin, api, this)
var server: Guild? = null
var channel: TextChannel? = null
override fun run() {
try {
- api.addEventListener(DiscordListener(plugin, api))
+ api.addEventListener(listener)
} catch (e: Exception) {
plugin.logger.severe("Error connecting to Discord: " + e)
}
@@ -28,6 +29,14 @@ class DiscordConnection(val plugin: Plugin) : Runnable {
channel!!.sendMessage(message)
}
+ fun reconnect() {
+ api.removeEventListener(listener)
+ api.shutdown(false)
+ api = JDABuilder(plugin.configuration.EMAIL, plugin.configuration.PASSWORD).build()
+ listener = DiscordListener(plugin, api, this)
+ api.addEventListener(listener)
+ }
+
private fun getServerById(id: String): Guild? {
for (server in api.guilds)
if (server.id.equals(id, true))
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt
index 32b341d..9b2ae72 100644
--- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt
+++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt
@@ -2,11 +2,12 @@ package gg.obsidian.discordbridge
import com.neovisionaries.ws.client.WebSocket
import com.neovisionaries.ws.client.WebSocketException
+import com.neovisionaries.ws.client.WebSocketFrame
import net.dv8tion.jda.JDA
import net.dv8tion.jda.events.message.MessageReceivedEvent
import net.dv8tion.jda.hooks.ListenerAdapter
-class DiscordListener(val plugin: Plugin, val api: JDA) : ListenerAdapter() {
+class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordConnection) : ListenerAdapter() {
override fun onMessageReceived(event: MessageReceivedEvent) {
plugin.logDebug("Received message ${event.message.id} from Discord")
@@ -35,4 +36,9 @@ class DiscordListener(val plugin: Plugin, val api: JDA) : ListenerAdapter() {
fun onUnexpectedError(ws: WebSocket, wse: WebSocketException) {
plugin.logger.severe("Unexpected error from DiscordBridge: ${wse.message}")
}
+
+ fun onDisconnected(webSocket: WebSocket, serverCloseFrame: WebSocketFrame, clientCloseFrame: WebSocketFrame, closedByServer: Boolean) {
+ plugin.logDebug("Discord disconnected - attempting to reconnect")
+ connection.reconnect()
+ }
}
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt
new file mode 100644
index 0000000..797c681
--- /dev/null
+++ b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt
@@ -0,0 +1,88 @@
+package gg.obsidian.discordbridge
+
+import org.bukkit.ChatColor
+import org.bukkit.event.EventHandler
+import org.bukkit.event.EventPriority
+import org.bukkit.event.Listener
+import org.bukkit.event.entity.PlayerDeathEvent
+import org.bukkit.event.player.AsyncPlayerChatEvent
+import org.bukkit.event.player.PlayerJoinEvent
+import org.bukkit.event.player.PlayerQuitEvent
+
+class EventListener(val plugin: Plugin): Listener {
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ fun onChat(event: AsyncPlayerChatEvent) {
+ plugin.logDebug("Received a chat event from ${event.player.name}: ${event.message}")
+ if (!event.isCancelled || plugin.configuration.RELAY_CANCELLED_MESSAGES) {
+ val username = ChatColor.stripColor(event.player.name)
+ val formattedMessage = Util.formatMessage(
+ plugin.configuration.TEMPLATES_DISCORD_CHAT_MESSAGE,
+ mapOf(
+ "%u" to username,
+ "%m" to ChatColor.stripColor(event.message),
+ "%d" to ChatColor.stripColor(event.player.displayName),
+ "%w" to event.player.world.name
+ )
+ )
+
+ plugin.sendToDiscord(formattedMessage)
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ fun onPlayerJoin(event: PlayerJoinEvent) {
+ if (!plugin.configuration.MESSAGES_JOIN) return
+
+ val username = ChatColor.stripColor(event.player.name)
+ plugin.logDebug("Received a join event for $username")
+
+ val formattedMessage = Util.formatMessage(
+ plugin.configuration.TEMPLATES_DISCORD_PLAYER_JOIN,
+ mapOf(
+ "%u" to username,
+ "%d" to ChatColor.stripColor(event.player.displayName)
+ )
+ )
+
+ plugin.sendToDiscord(formattedMessage)
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ fun onPlayerQuit(event: PlayerQuitEvent) {
+ if (!plugin.configuration.MESSAGES_LEAVE) return
+
+ val username = ChatColor.stripColor(event.player.name)
+ plugin.logDebug("Received a leave event for $username")
+
+ val formattedMessage = Util.formatMessage(
+ plugin.configuration.TEMPLATES_DISCORD_PLAYER_LEAVE,
+ mapOf(
+ "%u" to username,
+ "%d" to ChatColor.stripColor(event.player.displayName)
+ )
+ )
+
+ plugin.sendToDiscord(formattedMessage)
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ fun onPlayerDeath(event: PlayerDeathEvent) {
+ if (!plugin.configuration.MESSAGES_DEATH) return
+
+ val username = ChatColor.stripColor(event.entity.name)
+ plugin.logDebug("Received a death event for $username")
+
+ val formattedMessage = Util.formatMessage(
+ plugin.configuration.TEMPLATES_DISCORD_PLAYER_DEATH,
+ mapOf(
+ "%u" to username,
+ "%d" to ChatColor.stripColor(event.entity.displayName),
+ "%r" to event.deathMessage,
+ "%w" to event.entity.world.name
+ )
+ )
+
+ plugin.sendToDiscord(formattedMessage)
+ }
+}
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt b/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt
new file mode 100644
index 0000000..4fa57d1
--- /dev/null
+++ b/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt
@@ -0,0 +1,11 @@
+package gg.obsidian.discordbridge
+
+import org.bukkit.entity.Player
+
+enum class Permissions(val node: String) {
+ reload("discordbridge.reload");
+
+ fun has(player: Player): Boolean {
+ return player.hasPermission(node)
+ }
+}
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt
index 1a1c8d6..bd96815 100644
--- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt
+++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt
@@ -1,15 +1,8 @@
package gg.obsidian.discordbridge
-import org.bukkit.ChatColor
-import org.bukkit.event.EventHandler
-import org.bukkit.event.EventPriority
-import org.bukkit.event.Listener
-import org.bukkit.event.player.AsyncPlayerChatEvent
-import org.bukkit.event.player.PlayerJoinEvent
-import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.plugin.java.JavaPlugin
-class Plugin : JavaPlugin(), Listener {
+class Plugin : JavaPlugin() {
val configuration = Configuration(this)
var connection: DiscordConnection? = null
@@ -20,39 +13,14 @@ class Plugin : JavaPlugin(), Listener {
this.connection = DiscordConnection(this)
server.scheduler.runTaskAsynchronously(this, connection)
- server.pluginManager.registerEvents(this, this)
+ server.pluginManager.registerEvents(EventListener(this), this)
+ getCommand("discord").executor = CommandHandler(this)
}
- // Event Handlers
-
- @EventHandler(priority = EventPriority.MONITOR)
- fun onChat(event: AsyncPlayerChatEvent) {
- logDebug("Received a chat event from ${event.player.name}: ${event.message}")
- if (!event.isCancelled || configuration.RELAY_CANCELLED_MESSAGES) {
- val username = ChatColor.stripColor(event.player.name)
- val formattedMessage = configuration.TEMPLATES_DISCORD_CHAT_MESSAGE
- .replace("%u", username)
- .replace("%m", event.message)
- sendToDiscord(formattedMessage)
- }
- }
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- fun onPlayerJoin(event: PlayerJoinEvent) {
- val username = ChatColor.stripColor(event.player.name)
- logDebug("Received a join event for $username")
- val formattedMessage = configuration.TEMPLATES_DISCORD_PLAYER_JOIN
- .replace("%u", username)
- sendToDiscord(formattedMessage)
- }
-
- @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
- fun onPlayerQuit(event: PlayerQuitEvent) {
- val username = ChatColor.stripColor(event.player.name)
- logDebug("Received a leave event for $username")
- val formattedMessage = configuration.TEMPLATES_DISCORD_PLAYER_LEAVE
- .replace("%u", username)
- sendToDiscord(formattedMessage)
+ fun reload() {
+ reloadConfig()
+ configuration.load()
+ connection?.reconnect()
}
// Message senders
@@ -63,10 +31,15 @@ class Plugin : JavaPlugin(), Listener {
}
fun sendToMinecraft(username: String, message: String) {
- val formattedMessage = ChatColor.translateAlternateColorCodes('&',
- configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE
- .replace("%u", username)
- .replace("%m", message))
+ val formattedMessage = Util.formatMessage(
+ configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE,
+ mapOf(
+ "%u" to username,
+ "%m" to message
+ ),
+ colors = true
+ )
+
server.broadcastMessage(formattedMessage)
}
diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Util.kt b/src/main/kotlin/gg/obsidian/discordbridge/Util.kt
new file mode 100644
index 0000000..62871c7
--- /dev/null
+++ b/src/main/kotlin/gg/obsidian/discordbridge/Util.kt
@@ -0,0 +1,16 @@
+package gg.obsidian.discordbridge
+
+import org.bukkit.ChatColor
+
+object Util {
+ fun formatMessage(message: String, replacements: Map, colors: Boolean = false): String {
+ var formattedString = message
+ for ((token, replacement) in replacements) {
+ formattedString = formattedString.replace(token, replacement)
+ }
+
+ if (colors) formattedString = ChatColor.translateAlternateColorCodes('&', formattedString)
+
+ return formattedString
+ }
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index e6e5cfb..851d4aa 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -6,10 +6,15 @@ settings:
password: 'password'
debug: false
relay_cancelled_messages: true
+ messages:
+ join: true
+ leave: true
+ death: false
templates:
discord:
chat_message: '<%u> %m'
player_join: '%u joined the server'
player_leave: '%u left the server'
+ player_death: '%r'
minecraft:
chat_message: '<%u&b(discord)&r> %m'
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 26ae9b8..6735681 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -7,3 +7,12 @@ website: ${project.url}
main: gg.obsidian.discordbridge.Plugin
+permissions:
+ discordbridge.reload:
+ description: Reload the Discord Bridge
+ default: op
+
+commands:
+ discord:
+ description: Reload the Discord Bridge
+ usage: /discord reload