From 23a28e6540bc3b8f42b3899f5ed3a7a4728c2b26 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Wed, 11 Jan 2017 11:45:13 -0600 Subject: [PATCH 01/24] General - Added ability to register Discord with Minecraft Minecraft - Added getDiscordIds command - Added registeralias command - Added config file to store aliases Discord - Added listserver command - Added Oikos --- .../obsidian/discordbridge/CommandHandler.kt | 47 ++++++++++- .../obsidian/discordbridge/ConfigAccessor.kt | 79 +++++++++++++++++++ .../discordbridge/DiscordConnection.kt | 23 +++++- .../obsidian/discordbridge/DiscordListener.kt | 48 ++++++++++- .../obsidian/discordbridge/EventListener.kt | 16 ++-- .../gg/obsidian/discordbridge/Plugin.kt | 64 +++++++++++++-- .../gg/obsidian/discordbridge/UserAlias.kt | 4 + 7 files changed, 258 insertions(+), 23 deletions(-) create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt diff --git a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt index 695e4ad..9bbb5ab 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt @@ -9,13 +9,18 @@ import org.bukkit.entity.Player class CommandHandler(val plugin: Plugin): CommandExecutor { override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (cmd.name == "discord") return handleDiscord(player, args) + else if (cmd.name == "registeralias") return handleRegisterAlias(player, args) + else if (cmd.name == "getdiscordids") return handleGetDiscordIds(player) + return false + } + + private fun handleDiscord(player: CommandSender, 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")) { + if (args == null || args.size != 1 || args[0] != "reload") { sendMessage("&eUsage: /discord reload", player, isConsole) return true } @@ -25,6 +30,42 @@ class CommandHandler(val plugin: Plugin): CommandExecutor { return true } + private fun handleRegisterAlias(player: CommandSender, args: Array?): Boolean { + if (player !is Player) return true + + if (args == null || args.size != 1) { + sendMessage("&eUsage: /registeralias ", player, false) + return true + } + + val users = plugin.getDiscordUsers() + //val found: Boolean = users.any { it.second == args[0] } + val found: Pair? = users.find { it.second == args[0] } + if (found == null) { + sendMessage("&eCould not find Discord user with that ID.", player, false) + return true + } + + val ua: UserAlias = UserAlias(player.name, player.uniqueId.toString(), found.first, + found.second) + + plugin.registerUserRequest(ua) + return true + } + + private fun handleGetDiscordIds(player: CommandSender): Boolean { + val isConsole = (player !is Player) + val users = plugin.getDiscordUsers() + if (users.isEmpty()) { + sendMessage("&eNo Discord members could be found. Either server is empty or an error " + + "has occurred.", player, isConsole) + return true + } + val response = users.joinToString("\n- ", "&eDiscord users:\n- ") + sendMessage(response, player, isConsole) + return true + } + private fun sendMessage(message: String, player: CommandSender, isConsole: Boolean) { val formattedMessage = ChatColor.translateAlternateColorCodes('&', message) if (isConsole) { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt new file mode 100644 index 0000000..75527d2 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt @@ -0,0 +1,79 @@ +package gg.obsidian.discordbridge + +/* +* Copyright (C) 2012 +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +import org.bukkit.configuration.file.FileConfiguration +import org.bukkit.configuration.file.YamlConfiguration +import org.bukkit.plugin.java.JavaPlugin +import java.io.File +import java.io.IOException +import java.io.InputStreamReader +import java.util.logging.Level + +class ConfigAccessor(private val plugin: JavaPlugin, private val fileName: String) { + + private val configFile: File? + private var fileConfiguration: FileConfiguration? = null + + init { + plugin.dataFolder ?: throw IllegalStateException() + this.configFile = File(plugin.dataFolder, fileName) + } + + fun reloadConfig() { + fileConfiguration = YamlConfiguration.loadConfiguration(configFile!!) + + // Look for defaults in the jar + val defConfigReader = InputStreamReader(plugin.getResource(fileName)) + val defConfig = YamlConfiguration.loadConfiguration(defConfigReader) + fileConfiguration!!.defaults = defConfig + } + + val config: FileConfiguration + get() { + if (fileConfiguration == null) { + this.reloadConfig() + } + return fileConfiguration!! + } + + fun saveConfig() { + if (fileConfiguration == null || configFile == null) { + return + } else { + try { + config.save(configFile) + } catch (ex: IOException) { + plugin.logger.log(Level.SEVERE, "Could not save config to " + configFile, ex) + } + + } + } + + fun saveDefaultConfig() { + if (!configFile!!.exists()) { + this.plugin.saveResource(fileName, false) + } + } + +} diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt index d509d36..c9e2e8a 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt @@ -4,6 +4,7 @@ import net.dv8tion.jda.JDA import net.dv8tion.jda.JDABuilder import net.dv8tion.jda.entities.Guild import net.dv8tion.jda.entities.TextChannel +import net.dv8tion.jda.events.message.MessageReceivedEvent class DiscordConnection(val plugin: Plugin) : Runnable { var api: JDA? = null @@ -20,7 +21,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } - fun send(message: String) { + fun relay(message: String) { server = if (server == null) getServerById(plugin.configuration.SERVER_ID) else server if (server == null) return @@ -30,11 +31,29 @@ class DiscordConnection(val plugin: Plugin) : Runnable { channel!!.sendMessage(message) } + fun respond(message: String, event: MessageReceivedEvent) { + if (event.isPrivate) event.privateChannel.sendMessage(message) + else event.channel.sendMessage(message) + } + + fun tell(message: String, id: String) { + api!!.getUserById(id).privateChannel.sendMessage(message) + } + fun reconnect() { disconnect() connect() } + fun listUsers(): List> { + channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel + if (channel == null) return mutableListOf() + + val usernames: MutableList> = mutableListOf() + channel!!.users.mapTo(usernames) { Pair(it.username, it.id) } + return usernames + } + private fun disconnect() { api?.removeEventListener(listener) api?.shutdown(false) @@ -61,7 +80,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { private fun getGroupByName(server: Guild, name: String): TextChannel? { for (group in server.textChannels) - if (group.name.equals(name)) + if (group.name == name) return group return null } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index 9b2ae72..0cda0bd 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -12,7 +12,48 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC override fun onMessageReceived(event: MessageReceivedEvent) { plugin.logDebug("Received message ${event.message.id} from Discord") - if (!event.guild.id.equals(plugin.configuration.SERVER_ID)) { + val rawmsg: String = event.message.rawContent + val msg: String = event.message.content + val username: String = event.author.username + + if (rawmsg.startsWith("<@267902537074606082> confirm", true) && event.isPrivate) { + plugin.logDebug("user $username wants to confirm an alias") + val ua = plugin.requests.find {it.discordId == event.author.id} + if (ua == null) { + plugin.sendToDiscordRespond("You have not requested an alias, or your request has expired!", event) + return + } + plugin.updateAlias(ua) + plugin.requests.remove(ua) + plugin.sendToDiscordRespond("Successfully linked aliases!", event) + return + } + + if (rawmsg.startsWith("<@267902537074606082> serverlist", true)) { + plugin.logDebug("user $username has requested a listing of online players") + val players = plugin.getOnlinePlayers() + if (players.isEmpty()) { + plugin.sendToDiscordRespond("Nobody is currently online.", event) + return + } + val response = players.joinToString("\n", "The following players are currently online:\n```\n", "\n```") + plugin.sendToDiscordRespond(response, event) + return + } + + if (rawmsg.startsWith("<@267902537074606082> oikos", true)) { + plugin.logDebug("user $username has initiated oikos!") + plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) + return + } + + if (rawmsg == "<@267902537074606082> :D") { + plugin.logDebug("user $username has initiated Oikos Part 2!") + plugin.sendToDiscordRespond(":D", event) + return + } + + if (event.guild.id != plugin.configuration.SERVER_ID) { plugin.logDebug("Ignoring message ${event.message.id} from Discord: server does not match") return } @@ -22,15 +63,14 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC return } - val username: String = event.author.username - if (username.equals(plugin.configuration.USERNAME, true)) { plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches the server's username") return } plugin.logDebug("Broadcasting message ${event.message.id} from Discord as user $username") - plugin.sendToMinecraft(username, event.message.content) + plugin.logDebug(msg) + plugin.sendToMinecraft(username, event.author.id, event.message.content) } fun onUnexpectedError(ws: WebSocket, wse: WebSocketException) { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt index 37078d8..42a2074 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt @@ -20,7 +20,7 @@ class EventListener(val plugin: Plugin): Listener { if (event.isCancelled && !plugin.configuration.RELAY_CANCELLED_MESSAGES) return // Check for vanished - val player = event.player; + val player = event.player if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && !plugin.configuration.IF_VANISHED_CHAT) return @@ -36,7 +36,7 @@ class EventListener(val plugin: Plugin): Listener { ) ) - plugin.sendToDiscord(formattedMessage) + plugin.sendToDiscordRelay(formattedMessage, event.player.uniqueId.toString()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -44,7 +44,7 @@ class EventListener(val plugin: Plugin): Listener { if (!plugin.configuration.MESSAGES_JOIN) return // Check for vanished - val player = event.player; + val player = event.player if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && !plugin.configuration.IF_VANISHED_JOIN) return @@ -60,7 +60,7 @@ class EventListener(val plugin: Plugin): Listener { ) ) - plugin.sendToDiscord(formattedMessage) + plugin.sendToDiscordRelay(formattedMessage, player.uniqueId.toString()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -68,7 +68,7 @@ class EventListener(val plugin: Plugin): Listener { if (!plugin.configuration.MESSAGES_LEAVE) return // Check for vanished - val player = event.player; + val player = event.player if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && !plugin.configuration.IF_VANISHED_LEAVE) return @@ -84,7 +84,7 @@ class EventListener(val plugin: Plugin): Listener { ) ) - plugin.sendToDiscord(formattedMessage) + plugin.sendToDiscordRelay(formattedMessage, player.uniqueId.toString()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -92,7 +92,7 @@ class EventListener(val plugin: Plugin): Listener { if (!plugin.configuration.MESSAGES_DEATH) return // Check for vanished - val player = event.entity; + val player = event.entity if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && !plugin.configuration.IF_VANISHED_DEATH) return @@ -110,6 +110,6 @@ class EventListener(val plugin: Plugin): Listener { ) ) - plugin.sendToDiscord(formattedMessage) + plugin.sendToDiscordRelay(formattedMessage, player.uniqueId.toString()) } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index bd96815..ff3b51f 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,11 +1,14 @@ package gg.obsidian.discordbridge +import net.dv8tion.jda.events.message.MessageReceivedEvent import org.bukkit.plugin.java.JavaPlugin class Plugin : JavaPlugin() { val configuration = Configuration(this) var connection: DiscordConnection? = null + var users: ConfigAccessor = ConfigAccessor(this, "usernames.yml") + var requests: MutableList = mutableListOf() override fun onEnable() { updateConfig(description.version) @@ -15,26 +18,44 @@ class Plugin : JavaPlugin() { server.scheduler.runTaskAsynchronously(this, connection) server.pluginManager.registerEvents(EventListener(this), this) getCommand("discord").executor = CommandHandler(this) + getCommand("registeralias").executor = CommandHandler(this) + getCommand("getdiscordids").executor = CommandHandler(this) + } + + override fun onDisable() { + server.scheduler.cancelTasks(this) } fun reload() { reloadConfig() + users.reloadConfig() configuration.load() connection?.reconnect() } // Message senders - fun sendToDiscord(message: String) { - logDebug("Sending message to Discord - $message") - connection!!.send(message) + fun sendToDiscordRelay(message: String, uuid: String) { + val alias = users.config.getString("mcaliases.$uuid.discordusername") + var newMessage = message + if (alias != null) newMessage = message.replaceFirst(users.config.getString("mcaliases.$uuid.mcusername"), alias) + logDebug("Sending message to Discord - $newMessage") + connection!!.relay(newMessage) + } + + fun sendToDiscordRespond(message: String, event: MessageReceivedEvent) { + if (event.isPrivate) logDebug("Sending message to ${event.author.username} - $message") + else logDebug("Sending message to Discord - $message") + connection!!.respond(message, event) } - fun sendToMinecraft(username: String, message: String) { + fun sendToMinecraft(username: String, id: String, message: String) { + var alias = users.config.getString("discordAliases.$id.mcusername") + if (alias == null) alias = username val formattedMessage = Util.formatMessage( configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, mapOf( - "%u" to username, + "%u" to alias, "%m" to message ), colors = true @@ -54,7 +75,38 @@ class Plugin : JavaPlugin() { } fun logDebug(msg: String) { - if (!configuration.DEBUG) return; + if (!configuration.DEBUG) return logger.info(msg) } + + // Stuff + + fun getOnlinePlayers(): List { + val names: MutableList = mutableListOf() + val players = server.onlinePlayers.toTypedArray() + players.mapTo(names) { it.name } + return names.toList() + } + + fun registerUserRequest(ua: UserAlias) { + requests.add(ua) + val msg = "Minecraft user '${ua.mcUsername}' has requested to become associated with your Discord" + + " account. If this is you, respond '<@267902537074606082> confirm'. If this is not" + + " you, respond '<@267902537074606082> deny'." + connection!!.tell(msg, ua.discordId) + } + + fun getDiscordUsers(): List> { + return connection!!.listUsers() + } + + fun updateAlias(ua: UserAlias) { + users.config.set("mcaliases.${ua.mcUuid}.mcusername", ua.mcUsername) + users.config.set("mcaliases.${ua.mcUuid}.discordusername", ua.discordUsername) + users.config.set("mcaliases.${ua.mcUuid}.discordid", ua.discordId) + users.config.set("discordaliases.${ua.discordId}.mcuuid", ua.mcUuid) + users.config.set("discordaliases.${ua.discordId}.mcusername", ua.mcUsername) + users.config.set("discordaliases.${ua.discordId}.discordusername", ua.discordUsername) + users.saveConfig() + } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt b/src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt new file mode 100644 index 0000000..ed1ef2e --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt @@ -0,0 +1,4 @@ +package gg.obsidian.discordbridge + +data class UserAlias(var mcUsername: String, var mcUuid: String, + var discordUsername: String, var discordId: String) \ No newline at end of file From 5070e317c952c6fb6ddefcb092a87b4715d62ca0 Mon Sep 17 00:00:00 2001 From: Joel Howard Date: Thu, 12 Jan 2017 04:11:43 +1000 Subject: [PATCH 02/24] Fixed part 2 of Oikos --- src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index 0cda0bd..422bbd9 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -47,9 +47,9 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC return } - if (rawmsg == "<@267902537074606082> :D") { + if (rawmsg == "<@267902537074606082> :smile:") { plugin.logDebug("user $username has initiated Oikos Part 2!") - plugin.sendToDiscordRespond(":D", event) + plugin.sendToDiscordRespond(":smile:", event) return } From e19cedff0533976f9447d34495336ec7753f1df9 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Wed, 11 Jan 2017 14:12:34 -0600 Subject: [PATCH 03/24] Updated to JDA 3.x Updated license --- LICENSE | 5 ++- build.gradle | 6 +-- .../obsidian/discordbridge/CommandHandler.kt | 1 - .../obsidian/discordbridge/ConfigAccessor.kt | 3 +- .../discordbridge/DiscordConnection.kt | 38 ++++++++----------- .../obsidian/discordbridge/DiscordListener.kt | 12 +++--- .../gg/obsidian/discordbridge/Plugin.kt | 5 ++- src/main/resources/config.yml | 3 +- 8 files changed, 35 insertions(+), 38 deletions(-) diff --git a/LICENSE b/LICENSE index 9bca104..ff45272 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,7 @@ -Copyright (c) 2015 Jacob Gillespie +Copyrights for portions of this project are held by Jacob Gilles- +pie (c) 2015 as part of project Disc- +ordBridge. All other copyright for this project are held by Adam +Hart and Joel Howard (c) 2016 unless otherwise noted. Permission is hereby granted, free of charge, to any person ob- taining a copy of this software and associated documentation diff --git a/build.gradle b/build.gradle index c447c24..f32c3df 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ import org.apache.tools.ant.filters.ReplaceTokens buildscript { - ext.kotlin_version = '1.0.0' + ext.kotlin_version = '1.0.6' repositories { mavenCentral() @@ -23,7 +23,7 @@ plugins { apply plugin: 'kotlin' group = 'gg.obsidian' -version = '1.7.1' +version = '1.7.2' description = """Bridge chat between Minecraft and Discord""" ext.url = 'https://github.com/the-obsidian/DiscordBridge' @@ -43,7 +43,7 @@ repositories { dependencies { compile group: 'org.spigotmc', name: 'spigot-api', version:'1.8.8-R0.1-SNAPSHOT' compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version:'1.0.0-rc-1036' - compile group: 'net.dv8tion', name: 'JDA', version:'1.4.0_238' + compile group: 'net.dv8tion', name: 'JDA', version:'3.0.BETA2_108' } processResources { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt index 9bbb5ab..ffd642d 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt @@ -39,7 +39,6 @@ class CommandHandler(val plugin: Plugin): CommandExecutor { } val users = plugin.getDiscordUsers() - //val found: Boolean = users.any { it.second == args[0] } val found: Pair? = users.find { it.second == args[0] } if (found == null) { sendMessage("&eCould not find Discord user with that ID.", player, false) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt index 75527d2..3f0bc8f 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt @@ -1,7 +1,7 @@ package gg.obsidian.discordbridge /* -* Copyright (C) 2012 +* Copyright (C) 2012 SagaciousZed * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -70,6 +70,7 @@ class ConfigAccessor(private val plugin: JavaPlugin, private val fileName: Strin } } + @Suppress("unused") fun saveDefaultConfig() { if (!configFile!!.exists()) { this.plugin.saveResource(fileName, false) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt index c9e2e8a..b34c985 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt @@ -1,10 +1,12 @@ package gg.obsidian.discordbridge -import net.dv8tion.jda.JDA -import net.dv8tion.jda.JDABuilder -import net.dv8tion.jda.entities.Guild -import net.dv8tion.jda.entities.TextChannel -import net.dv8tion.jda.events.message.MessageReceivedEvent +import net.dv8tion.jda.core.AccountType +import net.dv8tion.jda.core.JDA +import net.dv8tion.jda.core.JDABuilder +import net.dv8tion.jda.core.entities.ChannelType +import net.dv8tion.jda.core.entities.Guild +import net.dv8tion.jda.core.entities.TextChannel +import net.dv8tion.jda.core.events.message.MessageReceivedEvent class DiscordConnection(val plugin: Plugin) : Runnable { var api: JDA? = null @@ -32,7 +34,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } fun respond(message: String, event: MessageReceivedEvent) { - if (event.isPrivate) event.privateChannel.sendMessage(message) + if (event.isFromType(ChannelType.PRIVATE)) event.privateChannel.sendMessage(message) else event.channel.sendMessage(message) } @@ -49,9 +51,9 @@ class DiscordConnection(val plugin: Plugin) : Runnable { channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel if (channel == null) return mutableListOf() - val usernames: MutableList> = mutableListOf() - channel!!.users.mapTo(usernames) { Pair(it.username, it.id) } - return usernames + val listOfUsers: MutableList> = mutableListOf() + channel!!.members.mapTo(listOfUsers) { Pair(it.nickname, it.user.id) } + return listOfUsers } private fun disconnect() { @@ -60,28 +62,18 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } private fun connect() { - var builder = JDABuilder().setAudioEnabled(false) - if (plugin.configuration.TOKEN != "") { - builder = builder.setBotToken(plugin.configuration.TOKEN) - } else { - builder = builder.setEmail(plugin.configuration.EMAIL).setPassword(plugin.configuration.PASSWORD) - } + var builder = JDABuilder(AccountType.BOT).setAudioEnabled(false) + builder = builder.setToken(plugin.configuration.TOKEN) api = builder.buildBlocking() listener = DiscordListener(plugin, api as JDA, this) api!!.addEventListener(listener) } private fun getServerById(id: String): Guild? { - for (server in api!!.guilds) - if (server.id.equals(id, true)) - return server - return null + return api!!.guilds.firstOrNull { it.id.equals(id, true) } } private fun getGroupByName(server: Guild, name: String): TextChannel? { - for (group in server.textChannels) - if (group.name == name) - return group - return null + return server.textChannels.firstOrNull { it.name == name } } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index 422bbd9..c2ec848 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -3,10 +3,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 +import net.dv8tion.jda.core.JDA +import net.dv8tion.jda.core.entities.ChannelType +import net.dv8tion.jda.core.events.message.MessageReceivedEvent +import net.dv8tion.jda.core.hooks.ListenerAdapter +@Suppress("unused", "UNUSED_PARAMETER") class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordConnection) : ListenerAdapter() { override fun onMessageReceived(event: MessageReceivedEvent) { @@ -14,9 +16,9 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC val rawmsg: String = event.message.rawContent val msg: String = event.message.content - val username: String = event.author.username + val username: String = event.member.effectiveName - if (rawmsg.startsWith("<@267902537074606082> confirm", true) && event.isPrivate) { + if (rawmsg.startsWith("<@267902537074606082> confirm", true) && event.isFromType(ChannelType.PRIVATE)) { plugin.logDebug("user $username wants to confirm an alias") val ua = plugin.requests.find {it.discordId == event.author.id} if (ua == null) { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index ff3b51f..be7a02f 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,6 +1,7 @@ package gg.obsidian.discordbridge -import net.dv8tion.jda.events.message.MessageReceivedEvent +import net.dv8tion.jda.core.entities.ChannelType +import net.dv8tion.jda.core.events.message.MessageReceivedEvent import org.bukkit.plugin.java.JavaPlugin class Plugin : JavaPlugin() { @@ -44,7 +45,7 @@ class Plugin : JavaPlugin() { } fun sendToDiscordRespond(message: String, event: MessageReceivedEvent) { - if (event.isPrivate) logDebug("Sending message to ${event.author.username} - $message") + if (event.isFromType(ChannelType.PRIVATE)) logDebug("Sending message to ${event.author.name} - $message") else logDebug("Sending message to Discord - $message") connection!!.respond(message, event) } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index c6297a0..73daff2 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,8 +2,7 @@ settings: server-id: '00000000' channel: 'test' username: 'username' - email: 'email@example.com' - password: 'password' + token: 'token' debug: false relay_cancelled_messages: true messages: From af5fc97c7843458e9aab0a8bc2b0cce7676c0915 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Wed, 11 Jan 2017 22:33:03 -0600 Subject: [PATCH 04/24] Removed hardcoded id to make message generic Fixed oikos emoji(?) Added commands to plugins.yml --- .../gg/obsidian/discordbridge/DiscordListener.kt | 10 +++++----- src/main/resources/plugin.yml | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index c2ec848..ce85b4e 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -18,7 +18,7 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC val msg: String = event.message.content val username: String = event.member.effectiveName - if (rawmsg.startsWith("<@267902537074606082> confirm", true) && event.isFromType(ChannelType.PRIVATE)) { + if (rawmsg.startsWith("${api.selfUser.asMention} confirm", true) && event.isFromType(ChannelType.PRIVATE)) { plugin.logDebug("user $username wants to confirm an alias") val ua = plugin.requests.find {it.discordId == event.author.id} if (ua == null) { @@ -31,7 +31,7 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC return } - if (rawmsg.startsWith("<@267902537074606082> serverlist", true)) { + if (rawmsg.startsWith("${api.selfUser.asMention} serverlist", true)) { plugin.logDebug("user $username has requested a listing of online players") val players = plugin.getOnlinePlayers() if (players.isEmpty()) { @@ -43,15 +43,15 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC return } - if (rawmsg.startsWith("<@267902537074606082> oikos", true)) { + if (rawmsg.startsWith("${api.selfUser.asMention} oikos", true)) { plugin.logDebug("user $username has initiated oikos!") plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) return } - if (rawmsg == "<@267902537074606082> :smile:") { + if (rawmsg == "<${api.selfUser.asMention} \uD83D\uDE04") { plugin.logDebug("user $username has initiated Oikos Part 2!") - plugin.sendToDiscordRespond(":smile:", event) + plugin.sendToDiscordRespond("\uD83D\uDE04", event) return } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 4155085..c489bc8 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -18,3 +18,9 @@ commands: discord: description: Reload the Discord Bridge usage: /discord reload + registeralias: + description: Associate a Minecraft user with a Discord user + usage: /registeralias + getdiscordids: + description: Lists the internal IDs of all Discord users currently in the server + usage: /getdiscordids \ No newline at end of file From 947c32cfbcc802b8850a1ed788944b75738735a2 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Thu, 12 Jan 2017 01:27:42 -0600 Subject: [PATCH 05/24] Marina actually works now Fixed relay function Alias feature now works properly Broke private messaging responses Oikos Part 2 is still broken --- build.gradle | 8 +-- .../obsidian/discordbridge/ConfigAccessor.kt | 2 + .../discordbridge/DiscordConnection.kt | 10 ++-- .../obsidian/discordbridge/DiscordListener.kt | 55 ++++++++++--------- .../gg/obsidian/discordbridge/Plugin.kt | 3 +- src/main/resources/plugin.yml | 10 ++-- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/build.gradle b/build.gradle index f32c3df..224ad5f 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { plugins { id 'java' - id 'com.github.johnrengelman.shadow' version '1.2.3' + id 'com.github.johnrengelman.shadow' version '1.2.4' } apply plugin: 'kotlin' @@ -27,9 +27,6 @@ version = '1.7.2' description = """Bridge chat between Minecraft and Discord""" ext.url = 'https://github.com/the-obsidian/DiscordBridge' -sourceCompatibility = 1.8 -targetCompatibility = 1.8 - repositories { maven { url 'https://maven-central.storage.googleapis.com' } maven { url 'https://hub.spigotmc.org/nexus/content/groups/public/' } @@ -41,7 +38,7 @@ repositories { } dependencies { - compile group: 'org.spigotmc', name: 'spigot-api', version:'1.8.8-R0.1-SNAPSHOT' + compile group: 'org.spigotmc', name: 'spigot-api', version:'1.11.2-R0.1-SNAPSHOT' compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version:'1.0.0-rc-1036' compile group: 'net.dv8tion', name: 'JDA', version:'3.0.BETA2_108' } @@ -57,6 +54,7 @@ processResources { build.finalizedBy(shadowJar) shadowJar { + relocate 'org.apache', 'shadow.apache' classifier 'dist' dependencies { exclude(dependency('org.spigotmc:.*:.*')) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt index 3f0bc8f..bbce2d7 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt @@ -44,6 +44,8 @@ class ConfigAccessor(private val plugin: JavaPlugin, private val fileName: Strin fileConfiguration = YamlConfiguration.loadConfiguration(configFile!!) // Look for defaults in the jar + if (plugin.getResource(fileName) == null) + plugin.logger.log(Level.SEVERE, "usernames.yml cannot be found for some reason") val defConfigReader = InputStreamReader(plugin.getResource(fileName)) val defConfig = YamlConfiguration.loadConfiguration(defConfigReader) fileConfiguration!!.defaults = defConfig diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt index b34c985..6c2cfd7 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt @@ -30,16 +30,16 @@ class DiscordConnection(val plugin: Plugin) : Runnable { channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel if (channel == null) return - channel!!.sendMessage(message) + channel!!.sendMessage(message).queue() } fun respond(message: String, event: MessageReceivedEvent) { - if (event.isFromType(ChannelType.PRIVATE)) event.privateChannel.sendMessage(message) - else event.channel.sendMessage(message) + if (event.isFromType(ChannelType.PRIVATE)) event.privateChannel.sendMessage(message).queue() + else event.channel.sendMessage(message).queue() } fun tell(message: String, id: String) { - api!!.getUserById(id).privateChannel.sendMessage(message) + api!!.getUserById(id).privateChannel.sendMessage(message).queue() } fun reconnect() { @@ -52,7 +52,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { if (channel == null) return mutableListOf() val listOfUsers: MutableList> = mutableListOf() - channel!!.members.mapTo(listOfUsers) { Pair(it.nickname, it.user.id) } + channel!!.members.mapTo(listOfUsers) { Pair(it.effectiveName, it.user.id) } return listOfUsers } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index ce85b4e..751b1e8 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -16,7 +16,7 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC val rawmsg: String = event.message.rawContent val msg: String = event.message.content - val username: String = event.member.effectiveName + val username: String = event.author.name if (rawmsg.startsWith("${api.selfUser.asMention} confirm", true) && event.isFromType(ChannelType.PRIVATE)) { plugin.logDebug("user $username wants to confirm an alias") @@ -43,36 +43,39 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC return } - if (rawmsg.startsWith("${api.selfUser.asMention} oikos", true)) { - plugin.logDebug("user $username has initiated oikos!") - plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) - return - } + // Relay + if (event.isFromType(ChannelType.TEXT)) { + if (rawmsg.startsWith("${api.selfUser.asMention} oikos", true)) { + plugin.logDebug("user $username has initiated oikos!") + plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) + return + } - if (rawmsg == "<${api.selfUser.asMention} \uD83D\uDE04") { - plugin.logDebug("user $username has initiated Oikos Part 2!") - plugin.sendToDiscordRespond("\uD83D\uDE04", event) - return - } + if (rawmsg == "<${api.selfUser.asMention} \uD83D\uDE04") { + plugin.logDebug("user $username has initiated Oikos Part 2!") + plugin.sendToDiscordRespond("\uD83D\uDE04", event) + return + } - if (event.guild.id != plugin.configuration.SERVER_ID) { - plugin.logDebug("Ignoring message ${event.message.id} from Discord: server does not match") - return - } + if (event.guild.id != plugin.configuration.SERVER_ID) { + plugin.logDebug("Ignoring message ${event.message.id} from Discord: server does not match") + return + } - if (!event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) { - plugin.logDebug("Ignoring message ${event.message.id} from Discord: channel does not match") - return - } + if (!event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) { + plugin.logDebug("Ignoring message ${event.message.id} from Discord: channel does not match") + return + } - if (username.equals(plugin.configuration.USERNAME, true)) { - plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches the server's username") - return - } + if (username.equals(plugin.configuration.USERNAME, true)) { + plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches the server's username") + return + } - plugin.logDebug("Broadcasting message ${event.message.id} from Discord as user $username") - plugin.logDebug(msg) - plugin.sendToMinecraft(username, event.author.id, event.message.content) + plugin.logDebug("Broadcasting message ${event.message.id} from Discord as user $username") + plugin.logDebug(msg) + plugin.sendToMinecraft(username, event.author.id, event.message.content) + } } fun onUnexpectedError(ws: WebSocket, wse: WebSocketException) { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index be7a02f..e39bca1 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -13,6 +13,7 @@ class Plugin : JavaPlugin() { override fun onEnable() { updateConfig(description.version) + users.saveDefaultConfig() this.connection = DiscordConnection(this) @@ -51,7 +52,7 @@ class Plugin : JavaPlugin() { } fun sendToMinecraft(username: String, id: String, message: String) { - var alias = users.config.getString("discordAliases.$id.mcusername") + var alias = users.config.getString("discordaliases.$id.mcusername") if (alias == null) alias = username val formattedMessage = Util.formatMessage( configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c489bc8..efb15cc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -19,8 +19,8 @@ commands: description: Reload the Discord Bridge usage: /discord reload registeralias: - description: Associate a Minecraft user with a Discord user - usage: /registeralias - getdiscordids: - description: Lists the internal IDs of all Discord users currently in the server - usage: /getdiscordids \ No newline at end of file + description: Associate a Minecraft user with a Discord user + usage: /registeralias + getdiscordids: + description: Lists the internal IDs of all Discord users currently in the server + usage: /getdiscordids \ No newline at end of file From 91a2d824d09991f1352def21d8f067cbd5330f1d Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Thu, 12 Jan 2017 15:00:36 -0600 Subject: [PATCH 06/24] Fixed private messaging Fixed Oikos Part 2 for reals --- build.gradle | 4 +++ .../discordbridge/DiscordConnection.kt | 3 +- .../obsidian/discordbridge/DiscordListener.kt | 36 +++++++++++-------- .../gg/obsidian/discordbridge/Plugin.kt | 7 ++-- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/build.gradle b/build.gradle index 224ad5f..1e390e8 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ dependencies { compile group: 'org.spigotmc', name: 'spigot-api', version:'1.11.2-R0.1-SNAPSHOT' compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version:'1.0.0-rc-1036' compile group: 'net.dv8tion', name: 'JDA', version:'3.0.BETA2_108' + compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } processResources { @@ -72,3 +73,6 @@ task copyFinalJar(type: Copy) { } shadowJar.finalizedBy(copyFinalJar) +sourceSets { + main.java.srcDirs += 'src/main/kotlin' +} diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt index 6c2cfd7..5d8ab4c 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt @@ -39,7 +39,8 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } fun tell(message: String, id: String) { - api!!.getUserById(id).privateChannel.sendMessage(message).queue() + val modifiedMsg = message.replace("<@me>", api!!.selfUser.asMention) + api!!.getUserById(id).privateChannel.sendMessage(modifiedMsg).queue() } fun reconnect() { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index 751b1e8..1e5bca8 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -7,12 +7,12 @@ import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import net.dv8tion.jda.core.hooks.ListenerAdapter +import java.nio.charset.Charset -@Suppress("unused", "UNUSED_PARAMETER") 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") + plugin.logDebug("Received message ${event.message.id} from Discord - ${event.message.rawContent}") val rawmsg: String = event.message.rawContent val msg: String = event.message.content @@ -43,20 +43,26 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC return } - // Relay - if (event.isFromType(ChannelType.TEXT)) { - if (rawmsg.startsWith("${api.selfUser.asMention} oikos", true)) { - plugin.logDebug("user $username has initiated oikos!") - plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) - return - } + if (rawmsg.startsWith("${api.selfUser.asMention} oikos", true)) { + plugin.logDebug("user $username has initiated oikos!") + plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) + return + } - if (rawmsg == "<${api.selfUser.asMention} \uD83D\uDE04") { - plugin.logDebug("user $username has initiated Oikos Part 2!") - plugin.sendToDiscordRespond("\uD83D\uDE04", event) - return - } + if (rawmsg == "${api.selfUser.asMention} \uD83D\uDE04") { + plugin.logDebug("user $username has initiated Oikos Part 2!") + plugin.sendToDiscordRespond("\uD83D\uDE04", event) + return + } + if (rawmsg.startsWith("${api.selfUser.asMention} smile", true)) { + plugin.logDebug("user $username has initiated Oikos Part 2!") + plugin.sendToDiscordRespond("\uD83D\uDE04", event) + return + } + + // Relay + if (event.isFromType(ChannelType.TEXT)) { if (event.guild.id != plugin.configuration.SERVER_ID) { plugin.logDebug("Ignoring message ${event.message.id} from Discord: server does not match") return @@ -78,10 +84,12 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC } } + @Suppress("unused", "UNUSED_PARAMETER") fun onUnexpectedError(ws: WebSocket, wse: WebSocketException) { plugin.logger.severe("Unexpected error from DiscordBridge: ${wse.message}") } + @Suppress("unused", "UNUSED_PARAMETER") 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/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index e39bca1..67f7cdd 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -3,6 +3,7 @@ package gg.obsidian.discordbridge import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import org.bukkit.plugin.java.JavaPlugin +import java.util.logging.Level class Plugin : JavaPlugin() { @@ -25,10 +26,12 @@ class Plugin : JavaPlugin() { } override fun onDisable() { + logger.log(Level.FINE, "Attempting to cancel tasks") server.scheduler.cancelTasks(this) } fun reload() { + reloadConfig() users.reloadConfig() configuration.load() @@ -93,8 +96,8 @@ class Plugin : JavaPlugin() { fun registerUserRequest(ua: UserAlias) { requests.add(ua) val msg = "Minecraft user '${ua.mcUsername}' has requested to become associated with your Discord" + - " account. If this is you, respond '<@267902537074606082> confirm'. If this is not" + - " you, respond '<@267902537074606082> deny'." + " account. If this is you, respond '<@me> confirm'. If this is not" + + " you, respond '<@me> deny'." connection!!.tell(msg, ua.discordId) } From 2a8c31f4c0f8c7a9d8ab21f759b8c46e7de3a167 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Fri, 13 Jan 2017 10:44:19 -0600 Subject: [PATCH 07/24] 1.7.3 Restructured commands Added /discord get online --- build.gradle | 2 +- .../obsidian/discordbridge/CommandHandler.kt | 126 +++++++++++++++--- .../discordbridge/DiscordConnection.kt | 20 ++- .../obsidian/discordbridge/DiscordListener.kt | 9 +- .../gg/obsidian/discordbridge/Plugin.kt | 7 +- src/main/resources/plugin.yml | 10 +- 6 files changed, 132 insertions(+), 42 deletions(-) diff --git a/build.gradle b/build.gradle index 1e390e8..8c9e870 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ plugins { apply plugin: 'kotlin' group = 'gg.obsidian' -version = '1.7.2' +version = '1.7.3' description = """Bridge chat between Minecraft and Discord""" ext.url = '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 index ffd642d..5983902 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt @@ -1,5 +1,6 @@ package gg.obsidian.discordbridge +import net.dv8tion.jda.core.OnlineStatus import org.bukkit.ChatColor import org.bukkit.command.Command import org.bukkit.command.CommandExecutor @@ -10,38 +11,51 @@ class CommandHandler(val plugin: Plugin): CommandExecutor { override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { if (cmd.name == "discord") return handleDiscord(player, args) - else if (cmd.name == "registeralias") return handleRegisterAlias(player, args) - else if (cmd.name == "getdiscordids") return handleGetDiscordIds(player) return false } private fun handleDiscord(player: CommandSender, args: Array?): Boolean { - if (player is Player && !Permissions.reload.has(player)) return true + if (args == null || args.isEmpty()) { + sendMessage("&eUsage: /discord ", player) + return true + } + + when (args[0].toLowerCase()) { + "reload" -> return handleDiscordReload(player, args) + "alias" -> return handleDiscordAlias(player, args) + "get" -> return handleDiscordGet(player, args) + else -> { + sendMessage("&eUsage: /discord ", player) + return true + } + } + } - val isConsole = (player is Player) + private fun handleDiscordReload(player: CommandSender, args: Array): Boolean { + if (player is Player && !Permissions.reload.has(player)) return true - if (args == null || args.size != 1 || args[0] != "reload") { - sendMessage("&eUsage: /discord reload", player, isConsole) + if (args.size != 1) { + sendMessage("&eUsage: /discord reload", player) return true } - sendMessage("&eReloading Discord Bridge...", player, isConsole) + sendMessage("&eReloading Discord Bridge...", player) plugin.reload() return true } - private fun handleRegisterAlias(player: CommandSender, args: Array?): Boolean { + private fun handleDiscordAlias(player: CommandSender, args: Array): Boolean { if (player !is Player) return true - if (args == null || args.size != 1) { - sendMessage("&eUsage: /registeralias ", player, false) + if (args.size != 2) { + sendMessage("&eUsage: /discord alias ", player) return true } val users = plugin.getDiscordUsers() - val found: Pair? = users.find { it.second == args[0] } + val found: Triple? = users.find { it.second == args[0] } if (found == null) { - sendMessage("&eCould not find Discord user with that ID.", player, false) + sendMessage("&eCould not find Discord user with that ID.", player) return true } @@ -52,25 +66,95 @@ class CommandHandler(val plugin: Plugin): CommandExecutor { return true } - private fun handleGetDiscordIds(player: CommandSender): Boolean { - val isConsole = (player !is Player) + private fun handleDiscordGet(player: CommandSender, args: Array): Boolean { + if (args.size < 2) { + sendMessage("&eUsage: /discord get ", player) + return true + } + + when (args[1].toLowerCase()) { + "ids" -> return handleDiscordGetIds(player, args) + "online" -> return handleDiscordGetOnline(player, args) + else -> { + sendMessage("&eUsage: /discord get ", player) + return true + } + } + } + + private fun handleDiscordGetIds(player: CommandSender, args: Array): Boolean { + if (args.size != 2) { + sendMessage("&eUsage: /discord get ids", player) + return true + } + val users = plugin.getDiscordUsers() + + if (users.isEmpty()) { + sendMessage("&eNo Discord members could be found. Either server is empty or an error " + + "has occurred.", player) + return true + } + + var response = "&eDiscord users:" + for (user in users) { + if (user.third) response += "\n&6- ${user.first} (Bot), ${user.second}&r" + else response += "\n&e- ${user.first}, ${user.second}&r" + } + //val response = users.joinToString("\n- ", "&eDiscord users:\n- ") + sendMessage(response, player) + return true + } + + private fun handleDiscordGetOnline(player: CommandSender, args: Array): Boolean { + if (args.size != 2) { + sendMessage("&eUsage: /discord get online", player) + return true + } + + val users = plugin.getDiscordOnline() + if (users.isEmpty()) { sendMessage("&eNo Discord members could be found. Either server is empty or an error " + - "has occurred.", player, isConsole) + "has occurred.", player) return true } - val response = users.joinToString("\n- ", "&eDiscord users:\n- ") - sendMessage(response, player, isConsole) + + var response = "" + if (users.filter{it.third == OnlineStatus.ONLINE}.isNotEmpty()) { + response += "\n&2Online:&r" + for (user in users.filter{it.third == OnlineStatus.ONLINE}) { + if (user.second) response += "\n&2- ${user.first} (Bot)&r" + else response += "\n&2- ${user.first}&r" + } + } + if (users.filter{it.third == OnlineStatus.IDLE}.isNotEmpty()) { + response += "\n&eIdle:&r" + for (user in users.filter{it.third == OnlineStatus.IDLE}) { + if (user.second) response += "\n&e- ${user.first} (Bot)&r" + else response += "\n&e- ${user.first}&r" + } + } + if (users.filter{it.third == OnlineStatus.DO_NOT_DISTURB}.isNotEmpty()) { + response += "\n&cDo Not Disturb:&r" + for (user in users.filter { it.third == OnlineStatus.DO_NOT_DISTURB }) { + if (user.second) response += "\n&c- ${user.first} (Bot)&r" + else response += "\n&c- ${user.first}&r" + } + } + + response.replaceFirst("\n", "") + + sendMessage(response, player) return true } - private fun sendMessage(message: String, player: CommandSender, isConsole: Boolean) { + private fun sendMessage(message: String, player: CommandSender) { val formattedMessage = ChatColor.translateAlternateColorCodes('&', message) - if (isConsole) { - plugin.server.consoleSender.sendMessage(formattedMessage) - } else { + if (player is Player) { player.sendMessage(formattedMessage) + } else { + plugin.server.consoleSender.sendMessage(formattedMessage) } } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt index 5d8ab4c..1877782 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt @@ -3,6 +3,7 @@ package gg.obsidian.discordbridge import net.dv8tion.jda.core.AccountType import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.JDABuilder +import net.dv8tion.jda.core.OnlineStatus import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.entities.Guild import net.dv8tion.jda.core.entities.TextChannel @@ -48,12 +49,25 @@ class DiscordConnection(val plugin: Plugin) : Runnable { connect() } - fun listUsers(): List> { + fun listUsers(): List> { channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel if (channel == null) return mutableListOf() - val listOfUsers: MutableList> = mutableListOf() - channel!!.members.mapTo(listOfUsers) { Pair(it.effectiveName, it.user.id) } + val listOfUsers: MutableList> = mutableListOf() + channel!!.members.mapTo(listOfUsers) { + Triple(it.effectiveName, it.user.id, it.user.isBot) + } + return listOfUsers + } + + fun listOnline(): List> { + channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel + if (channel == null) return mutableListOf() + + val listOfUsers: MutableList> = mutableListOf() + channel!!.members.mapTo(listOfUsers) { + Triple(it.effectiveName, it.user.isBot, it.onlineStatus) + } return listOfUsers } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index 1e5bca8..bf2a302 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -7,7 +7,6 @@ import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import net.dv8tion.jda.core.hooks.ListenerAdapter -import java.nio.charset.Charset class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordConnection) : ListenerAdapter() { @@ -50,13 +49,7 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC } if (rawmsg == "${api.selfUser.asMention} \uD83D\uDE04") { - plugin.logDebug("user $username has initiated Oikos Part 2!") - plugin.sendToDiscordRespond("\uD83D\uDE04", event) - return - } - - if (rawmsg.startsWith("${api.selfUser.asMention} smile", true)) { - plugin.logDebug("user $username has initiated Oikos Part 2!") + plugin.logDebug("user $username has initiated oikos 2!") plugin.sendToDiscordRespond("\uD83D\uDE04", event) return } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index 67f7cdd..0203b88 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,5 +1,6 @@ package gg.obsidian.discordbridge +import net.dv8tion.jda.core.OnlineStatus import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import org.bukkit.plugin.java.JavaPlugin @@ -101,10 +102,14 @@ class Plugin : JavaPlugin() { connection!!.tell(msg, ua.discordId) } - fun getDiscordUsers(): List> { + fun getDiscordUsers(): List> { return connection!!.listUsers() } + fun getDiscordOnline(): List> { + return connection!!.listOnline() + } + fun updateAlias(ua: UserAlias) { users.config.set("mcaliases.${ua.mcUuid}.mcusername", ua.mcUsername) users.config.set("mcaliases.${ua.mcUuid}.discordusername", ua.discordUsername) diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index efb15cc..b1e846f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -16,11 +16,5 @@ permissions: commands: discord: - description: Reload the Discord Bridge - usage: /discord reload - registeralias: - description: Associate a Minecraft user with a Discord user - usage: /registeralias - getdiscordids: - description: Lists the internal IDs of all Discord users currently in the server - usage: /getdiscordids \ No newline at end of file + description: Issue a command to the bot + usage: /discord \ No newline at end of file From d5388fdf69b69d6a581ec16d75d75f0085bf6b07 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Fri, 13 Jan 2017 11:01:38 -0600 Subject: [PATCH 08/24] Removed commands from plugin.kt --- src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index 0203b88..cfc259e 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -22,8 +22,6 @@ class Plugin : JavaPlugin() { server.scheduler.runTaskAsynchronously(this, connection) server.pluginManager.registerEvents(EventListener(this), this) getCommand("discord").executor = CommandHandler(this) - getCommand("registeralias").executor = CommandHandler(this) - getCommand("getdiscordids").executor = CommandHandler(this) } override fun onDisable() { From cc8d98014926c4be5d53dd7704847b54a8e42a6c Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Sun, 12 Feb 2017 11:21:52 -0600 Subject: [PATCH 09/24] Various fixes --- build.gradle | 5 +- .../obsidian/discordbridge/ConfigAccessor.kt | 21 ++-- .../obsidian/discordbridge/Configuration.kt | 2 + .../discordbridge/DiscordConnection.kt | 1 + .../obsidian/discordbridge/DiscordListener.kt | 111 +++++++++++------- .../obsidian/discordbridge/EventListener.kt | 12 +- .../gg/obsidian/discordbridge/Plugin.kt | 56 ++++++++- src/main/resources/config.yml | 1 + src/main/resources/plugin.yml | 6 +- src/main/resources/usernames.yml | 2 + 10 files changed, 160 insertions(+), 57 deletions(-) create mode 100644 src/main/resources/usernames.yml diff --git a/build.gradle b/build.gradle index 8c9e870..eb664ba 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ plugins { apply plugin: 'kotlin' group = 'gg.obsidian' -version = '1.7.3' +version = '1.7.4' description = """Bridge chat between Minecraft and Discord""" ext.url = 'https://github.com/the-obsidian/DiscordBridge' @@ -41,6 +41,7 @@ dependencies { compile group: 'org.spigotmc', name: 'spigot-api', version:'1.11.2-R0.1-SNAPSHOT' compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version:'1.0.0-rc-1036' compile group: 'net.dv8tion', name: 'JDA', version:'3.0.BETA2_108' + compile group: 'com.michaelwflaherty', name: 'cleverbotapi', version: '1.0.1' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } @@ -52,12 +53,14 @@ processResources { ] } +//noinspection GroovyAssignabilityCheck build.finalizedBy(shadowJar) shadowJar { relocate 'org.apache', 'shadow.apache' classifier 'dist' dependencies { + //noinspection GroovyAssignabilityCheck exclude(dependency('org.spigotmc:.*:.*')) } exclude '.cache' diff --git a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt index bbce2d7..52c80d9 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt @@ -30,25 +30,26 @@ import java.io.IOException import java.io.InputStreamReader import java.util.logging.Level -class ConfigAccessor(private val plugin: JavaPlugin, private val fileName: String) { +class ConfigAccessor(private val plugin: JavaPlugin, filepath: File, private val fileName: String) { private val configFile: File? private var fileConfiguration: FileConfiguration? = null init { plugin.dataFolder ?: throw IllegalStateException() - this.configFile = File(plugin.dataFolder, fileName) + this.configFile = File(filepath, fileName) } fun reloadConfig() { - fileConfiguration = YamlConfiguration.loadConfiguration(configFile!!) - - // Look for defaults in the jar - if (plugin.getResource(fileName) == null) - plugin.logger.log(Level.SEVERE, "usernames.yml cannot be found for some reason") - val defConfigReader = InputStreamReader(plugin.getResource(fileName)) - val defConfig = YamlConfiguration.loadConfiguration(defConfigReader) - fileConfiguration!!.defaults = defConfig + try { fileConfiguration = YamlConfiguration.loadConfiguration(configFile) } + catch (e: IllegalArgumentException) { + // Look for defaults in the jar + if (plugin.getResource(fileName) == null) + plugin.logger.log(Level.SEVERE, "usernames.yml cannot be found for some reason") + val defConfigReader = InputStreamReader(plugin.getResource(fileName)) + val defConfig = YamlConfiguration.loadConfiguration(defConfigReader) + fileConfiguration!!.defaults = defConfig + } } val config: FileConfiguration diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt index 5110715..c566a8e 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt @@ -5,6 +5,7 @@ class Configuration(val plugin: Plugin) { var SERVER_ID: String = "" var CHANNEL: String = "" var USERNAME: String = "" + var USERNAME_COLOR: String = "" var EMAIL: String = "" var PASSWORD: String = "" var TOKEN: String = "" @@ -38,6 +39,7 @@ class Configuration(val plugin: Plugin) { SERVER_ID = plugin.config.getString("settings.server-id") CHANNEL = plugin.config.getString("settings.channel") USERNAME = plugin.config.getString("settings.username") + USERNAME_COLOR = plugin.config.getString("settings.username-color") EMAIL = plugin.config.getString("settings.email", "") PASSWORD = plugin.config.getString("settings.password", "") TOKEN = plugin.config.getString("settings.token", "") diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt index 1877782..6f53cbf 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt @@ -82,6 +82,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { api = builder.buildBlocking() listener = DiscordListener(plugin, api as JDA, this) api!!.addEventListener(listener) + relay("Oikos!") } private fun getServerById(id: String): Guild? { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index bf2a302..90ac7e7 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -14,66 +14,95 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC plugin.logDebug("Received message ${event.message.id} from Discord - ${event.message.rawContent}") val rawmsg: String = event.message.rawContent - val msg: String = event.message.content val username: String = event.author.name - if (rawmsg.startsWith("${api.selfUser.asMention} confirm", true) && event.isFromType(ChannelType.PRIVATE)) { - plugin.logDebug("user $username wants to confirm an alias") - val ua = plugin.requests.find {it.discordId == event.author.id} - if (ua == null) { - plugin.sendToDiscordRespond("You have not requested an alias, or your request has expired!", event) - return - } - plugin.updateAlias(ua) - plugin.requests.remove(ua) - plugin.sendToDiscordRespond("Successfully linked aliases!", event) + // Immediately throw out messages sent from itself or from non-matching servers + if (username.equals(plugin.configuration.USERNAME, true)) { + plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches this bot's username") + return + } + if (event.guild.id != plugin.configuration.SERVER_ID) { + plugin.logDebug("Not relaying message ${event.message.id} from Discord: server does not match") return } - if (rawmsg.startsWith("${api.selfUser.asMention} serverlist", true)) { - plugin.logDebug("user $username has requested a listing of online players") - val players = plugin.getOnlinePlayers() - if (players.isEmpty()) { - plugin.sendToDiscordRespond("Nobody is currently online.", event) + // NON-RELAY COMMANDS - the following commands and their responses are not sent to Minecraft + if(rawmsg.startsWith(api.selfUser.asMention, true)) { + val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").replaceFirst("\\s+", "") + + // CONFIRM - Confirm an alias + if (arg.startsWith("confirm", true) && event.isFromType(ChannelType.PRIVATE)) { + plugin.logDebug("user $username wants to confirm an alias") + val ua = plugin.requests.find { it.discordId == event.author.id } + if (ua == null) { + plugin.sendToDiscordRespond("You have not requested an alias, or your request has expired!", event) + return + } + plugin.updateAlias(ua) + plugin.requests.remove(ua) + plugin.sendToDiscordRespond("Successfully linked aliases!", event) return } - val response = players.joinToString("\n", "The following players are currently online:\n```\n", "\n```") - plugin.sendToDiscordRespond(response, event) - return - } - if (rawmsg.startsWith("${api.selfUser.asMention} oikos", true)) { - plugin.logDebug("user $username has initiated oikos!") - plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) - return + // SERVERLIST - List all players currently online on the server + if (arg.startsWith("serverlist", true)) { + plugin.logDebug("user $username has requested a listing of online players") + val players = plugin.getOnlinePlayers() + if (players.isEmpty()) { + plugin.sendToDiscordRespond("Nobody is currently online.", event) + return + } + val response = players.joinToString("\n", "The following players are currently online:\n```\n", "\n```") + plugin.sendToDiscordRespond(response, event) + return + } } - if (rawmsg == "${api.selfUser.asMention} \uD83D\uDE04") { - plugin.logDebug("user $username has initiated oikos 2!") - plugin.sendToDiscordRespond("\uD83D\uDE04", event) - return + // If it is from the relay channel, relay it immediately + if (event.isFromType(ChannelType.TEXT)) { + + if (!event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) + plugin.logDebug("Not relaying message ${event.message.id} from Discord: channel does not match") + else { + plugin.logDebug("Broadcasting message ${event.message.id} from Discord to Minecraft as user $username") + plugin.sendToMinecraft(username, event.author.id, event.message.content) + } } - // Relay - if (event.isFromType(ChannelType.TEXT)) { - if (event.guild.id != plugin.configuration.SERVER_ID) { - plugin.logDebug("Ignoring message ${event.message.id} from Discord: server does not match") + // RELAY COMMANDS - These commands and their outputs DO get relayed to Minecraft + if(rawmsg.startsWith(api.selfUser.asMention, true)) { + val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").removePrefix(" ") + if (arg.isEmpty()) return + plugin.logDebug("Relay command received. Arg: $arg") + + // OIKOS - Delicious Greek yogurt from Danone! + if (arg.startsWith("oikos", true)) { + plugin.logDebug("user $username has initiated oikos!") + plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) + plugin.sendToMinecraftBroadcast("Delicious Greek yogurt from Danone!") return } - - if (!event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) { - plugin.logDebug("Ignoring message ${event.message.id} from Discord: channel does not match") + if (arg.startsWith("delicious greek yogurt from danone", true)) { + plugin.logDebug("user $username has initiated oikos 2!") + plugin.sendToDiscordRespond("\uD83D\uDE04", event) + plugin.sendToMinecraftBroadcast(":D") return } - - if (username.equals(plugin.configuration.USERNAME, true)) { - plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches the server's username") + if (arg == "\uD83D\uDE04") { + plugin.logDebug("user $username has initiated oikos 3!") + plugin.sendToDiscordRespond("\uD83D\uDE04", event) + plugin.sendToMinecraftBroadcast(":D") return } - plugin.logDebug("Broadcasting message ${event.message.id} from Discord as user $username") - plugin.logDebug(msg) - plugin.sendToMinecraft(username, event.author.id, event.message.content) + // CLEVERBOT - Assume anything else invokes Cleverbot + plugin.logDebug("user $username asks CleverBot something") + val response = Util.askCleverbot(arg) + plugin.sendToDiscordRespond(response, event) + // if this occurs in the relay channel, relay the response + if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) + plugin.sendToMinecraftBroadcast(response) + return } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt index 42a2074..4055255 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt @@ -8,6 +8,7 @@ import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.AsyncPlayerChatEvent import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent +import java.lang.reflect.Method class EventListener(val plugin: Plugin): Listener { @@ -26,13 +27,22 @@ class EventListener(val plugin: Plugin): Listener { !plugin.configuration.IF_VANISHED_CHAT) return val username = ChatColor.stripColor(event.player.name) + var worldname = player.world.name + if (plugin.isMultiverse()) { + val worldProperties = plugin.worlds!!.config.get("worlds.$worldname") + val cls = Class.forName("com.onarandombox.MultiverseCore.WorldProperties") + val meth: Method = cls.getMethod("getAlias") + val alias = meth.invoke(worldProperties) + if (alias is String) worldname = alias + } + val formattedMessage = Util.formatMessage( plugin.configuration.TEMPLATES_DISCORD_CHAT_MESSAGE, mapOf( "%u" to username, "%m" to ChatColor.stripColor(event.message), "%d" to ChatColor.stripColor(player.displayName), - "%w" to player.world.name + "%w" to worldname ) ) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index cfc259e..a56cfc6 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -5,27 +5,34 @@ import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import org.bukkit.plugin.java.JavaPlugin import java.util.logging.Level +import org.bukkit.ChatColor +import java.io.File class Plugin : JavaPlugin() { val configuration = Configuration(this) var connection: DiscordConnection? = null - var users: ConfigAccessor = ConfigAccessor(this, "usernames.yml") + var users: ConfigAccessor = ConfigAccessor(this, dataFolder, "usernames.yml") + var worlds: ConfigAccessor? = null var requests: MutableList = mutableListOf() override fun onEnable() { updateConfig(description.version) users.saveDefaultConfig() + if (isMultiverse()) worlds = ConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") + this.connection = DiscordConnection(this) server.scheduler.runTaskAsynchronously(this, connection) server.pluginManager.registerEvents(EventListener(this), this) getCommand("discord").executor = CommandHandler(this) + getCommand("marina").executor = HandleMarina(this) } override fun onDisable() { - logger.log(Level.FINE, "Attempting to cancel tasks") + connection!!.relay("Shutting down...") + logger.log(Level.INFO, "Attempting to cancel tasks") server.scheduler.cancelTasks(this) } @@ -39,9 +46,36 @@ class Plugin : JavaPlugin() { // Message senders + fun sendToDiscordRelaySelf(message: String) { + logDebug("Sending message to Discord - $message") + connection!!.relay(message) + } + fun sendToDiscordRelay(message: String, uuid: String) { val alias = users.config.getString("mcaliases.$uuid.discordusername") var newMessage = message + + // This section should convert attempted @mentions to real ones wherever possible + val discordusers = getDiscordUsers() + val discordaliases: MutableList> = mutableListOf() + discordusers + .filter { users.config.isSet("discordaliases.${it.second}") } + .mapTo(discordaliases) { + Pair(users.config.getString("discordaliases.${it.second}.mcusername"), + it.second) + } + for (match in Regex("""(?:^| )@(w+)""").findAll(message)) { + val found: Triple? = discordusers.firstOrNull { + it.first.replace("\\s+", "").toLowerCase() == match.value.toLowerCase() + } + if (found != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found.second}>") + + val found2: Pair? = discordaliases.firstOrNull { + it.second.toLowerCase() == match.value.toLowerCase() + } + if (found2 != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found2.first}>") + } + if (alias != null) newMessage = message.replaceFirst(users.config.getString("mcaliases.$uuid.mcusername"), alias) logDebug("Sending message to Discord - $newMessage") connection!!.relay(newMessage) @@ -55,7 +89,7 @@ class Plugin : JavaPlugin() { fun sendToMinecraft(username: String, id: String, message: String) { var alias = users.config.getString("discordaliases.$id.mcusername") - if (alias == null) alias = username + if (alias == null) alias = username.replace("\\s+", "") val formattedMessage = Util.formatMessage( configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, mapOf( @@ -68,6 +102,18 @@ class Plugin : JavaPlugin() { server.broadcastMessage(formattedMessage) } + fun sendToMinecraftBroadcast(message: String) { + val formattedMessage = Util.formatMessage( + configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, + mapOf( + "%u" to configuration.USERNAME_COLOR + configuration.USERNAME.replace("\\s+", "") + "&r", + "%m" to message + ), + colors = true + ) + server.broadcastMessage(formattedMessage) + } + // Utilities fun updateConfig(version: String) { @@ -83,6 +129,10 @@ class Plugin : JavaPlugin() { logger.info(msg) } + fun isMultiverse(): Boolean { + return server.pluginManager.getPlugin("Multiverse-Core") != null + } + // Stuff fun getOnlinePlayers(): List { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 73daff2..6e44b6e 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -2,6 +2,7 @@ settings: server-id: '00000000' channel: 'test' username: 'username' + username-color: '' token: 'token' debug: false relay_cancelled_messages: true diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b1e846f..2bef185 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -6,6 +6,7 @@ author: Jacob Gillespie website: '@URL@' loadbefore: [SpaceBukkit, RemoteToolkitPlugin] +softdepend: [Multiverse-Core] main: gg.obsidian.discordbridge.Plugin @@ -17,4 +18,7 @@ permissions: commands: discord: description: Issue a command to the bot - usage: /discord \ No newline at end of file + usage: /discord + marina: + description: Chat with Marina! + usage: /marina \ No newline at end of file diff --git a/src/main/resources/usernames.yml b/src/main/resources/usernames.yml new file mode 100644 index 0000000..6da9d82 --- /dev/null +++ b/src/main/resources/usernames.yml @@ -0,0 +1,2 @@ +mcaliases: +discordaliases: \ No newline at end of file From 552c5f2a1daee540ae5510166075826a2202bad9 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Sun, 12 Feb 2017 12:16:49 -0600 Subject: [PATCH 10/24] whoops --- src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt | 2 ++ src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt | 2 +- src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt | 1 - src/main/resources/config.yml | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt index c566a8e..db50e1f 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt @@ -9,6 +9,7 @@ class Configuration(val plugin: Plugin) { var EMAIL: String = "" var PASSWORD: String = "" var TOKEN: String = "" + var CLEVERBOT_KEY: String = "" var DEBUG: Boolean = false var RELAY_CANCELLED_MESSAGES = true @@ -43,6 +44,7 @@ class Configuration(val plugin: Plugin) { EMAIL = plugin.config.getString("settings.email", "") PASSWORD = plugin.config.getString("settings.password", "") TOKEN = plugin.config.getString("settings.token", "") + CLEVERBOT_KEY = plugin.config.getString("settings.cleverbot-key", "") DEBUG = plugin.config.getBoolean("settings.debug", false) RELAY_CANCELLED_MESSAGES = plugin.config.getBoolean("settings.relay_cancelled_messages", true) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt index 90ac7e7..31e4634 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt @@ -97,7 +97,7 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC // CLEVERBOT - Assume anything else invokes Cleverbot plugin.logDebug("user $username asks CleverBot something") - val response = Util.askCleverbot(arg) + val response = Util.askCleverbot(plugin.configuration.CLEVERBOT_KEY, arg) plugin.sendToDiscordRespond(response, event) // if this occurs in the relay channel, relay the response if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index a56cfc6..d7bc2ac 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -5,7 +5,6 @@ import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import org.bukkit.plugin.java.JavaPlugin import java.util.logging.Level -import org.bukkit.ChatColor import java.io.File class Plugin : JavaPlugin() { diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 6e44b6e..5f37b58 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -4,6 +4,7 @@ settings: username: 'username' username-color: '' token: 'token' + cleverbot-key: '' debug: false relay_cancelled_messages: true messages: From 94d27885d2fb8e1eec787af0f341869ef76b85ca Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Sun, 12 Feb 2017 12:57:31 -0600 Subject: [PATCH 11/24] added handler for Marina command --- .../gg/obsidian/discordbridge/HandleMarina.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt diff --git a/src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt b/src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt new file mode 100644 index 0000000..64a7e45 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt @@ -0,0 +1,31 @@ +package gg.obsidian.discordbridge + +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class HandleMarina(val plugin: Plugin): CommandExecutor { + + override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (args == null || args.isEmpty()) return false + + if (player is Player) { + val message: String = args.joinToString(" ") + player.chat("@MarinaFriend " + message) + val response: String = Util.askCleverbot(plugin.configuration.CLEVERBOT_KEY, message) + val formattedMessage = Util.formatMessage( + plugin.configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, + mapOf( + "%u" to plugin.configuration.USERNAME_COLOR + plugin.configuration.USERNAME.replace("\\s+", "") + "&r", + "%m" to response + ), + colors = true + ) + plugin.server.broadcastMessage(formattedMessage) + plugin.sendToDiscordRelaySelf(response) + return true + } + return true + } +} \ No newline at end of file From 7a20a36d5b3db1d9098129cef544bee8b7e01e79 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Sun, 12 Feb 2017 12:59:53 -0600 Subject: [PATCH 12/24] Re-added the Util class after nuking it from orbit because I'm a dumbass --- .../kotlin/gg/obsidian/discordbridge/Util.kt | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Util.kt b/src/main/kotlin/gg/obsidian/discordbridge/Util.kt index 788828d..2487eec 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Util.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Util.kt @@ -1,17 +1,38 @@ package gg.obsidian.discordbridge import org.bukkit.ChatColor +import java.io.IOException +import com.michaelwflaherty.cleverbotapi.CleverBotQuery object Util { + fun formatMessage(message: String, replacements: Map, colors: Boolean = false): String { var formattedString = message - if (colors) formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) - for ((token, replacement) in replacements) { formattedString = formattedString.replace(token, replacement) } + if (colors) formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) + return formattedString } + + fun askCleverbot(key: String, message: String): String { + + if (key.isEmpty()) return "You do not have an API key. Go to https://www.cleverbot.com/api/ for more information." + val bot: CleverBotQuery = CleverBotQuery(key, message) + var response: String + try + { + bot.sendRequest() + response = bot.response + } + catch (e: IOException) + { + response = e.message!! + } + + return response + } } From 23f2735e93967a24d46a75421c7f88b9e1964a56 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Thu, 16 Feb 2017 14:25:03 -0600 Subject: [PATCH 13/24] Rearranged a lot of stuff --- README.md | 8 +- build.gradle | 10 +- .../{Util.kt => CommandLogic.kt} | 25 +--- ...onfigAccessor.kt => DataConfigAccessor.kt} | 8 +- .../obsidian/discordbridge/EventListener.kt | 68 +++++----- .../gg/obsidian/discordbridge/HandleMarina.kt | 31 ----- .../gg/obsidian/discordbridge/Plugin.kt | 127 +++++++++++------- .../gg/obsidian/discordbridge/Utils/Utils.kt | 22 +++ .../Connection.kt} | 19 +-- .../Listener.kt} | 46 ++++--- .../commands/Discord.kt} | 22 ++- .../minecraft/commands/Marina.kt | 35 +++++ 12 files changed, 228 insertions(+), 193 deletions(-) rename src/main/kotlin/gg/obsidian/discordbridge/{Util.kt => CommandLogic.kt} (50%) rename src/main/kotlin/gg/obsidian/discordbridge/{ConfigAccessor.kt => DataConfigAccessor.kt} (93%) delete mode 100644 src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt rename src/main/kotlin/gg/obsidian/discordbridge/{DiscordConnection.kt => discord/Connection.kt} (85%) rename src/main/kotlin/gg/obsidian/discordbridge/{DiscordListener.kt => discord/Listener.kt} (72%) rename src/main/kotlin/gg/obsidian/discordbridge/{CommandHandler.kt => minecraft/commands/Discord.kt} (88%) create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt diff --git a/README.md b/README.md index 950f068..61d3982 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,14 @@ Bridges chat between Discord and Minecraft (Bukkit/Spigot). 1. Download the [latest release](https://github.com/the-obsidian/DiscordBridge/releases) from GitHub 1. Add it to your `plugins` folder -1. Either run Bukkit/Spigot once to generate `DiscordBridge/config.yml` or create it using the guide below. +1. Either run Bukkit/Spigot once to generate `DiscordBridge/data.yml` or create it using the guide below. 1. All done! ## Configuration **Note:** To use with the official Discord API and a bot user, add a `token: 'your-bot-token-here'` line alongside `email` and `password` (so it will have two spaces of indentation). If a `token` setting is present, it will ignore `email` and `password`. A more user-friendly version of this will be released shortly. -DiscordBridge has several options that can be configured in the `config.yml` file: +DiscordBridge has several options that can be configured in the `data.yml` file: ```yaml settings: @@ -78,11 +78,11 @@ settings: ## Permissions -- `discordbridge.reload` - ability to reload config and reconnect the Discord connection +- `discordbridge.reload` - ability to reload data and reconnect the Discord connection ## Commands -- `/discord reload` - reloads config and reconnects to Discord +- `/discord reload` - reloads data and reconnects to Discord ## Upcoming Features diff --git a/build.gradle b/build.gradle index eb664ba..8ded0d5 100644 --- a/build.gradle +++ b/build.gradle @@ -38,9 +38,9 @@ repositories { } dependencies { - compile group: 'org.spigotmc', name: 'spigot-api', version:'1.11.2-R0.1-SNAPSHOT' - compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version:'1.0.0-rc-1036' - compile group: 'net.dv8tion', name: 'JDA', version:'3.0.BETA2_108' + compile group: 'org.spigotmc', name: 'spigot-api', version: '1.11.2-R0.1-SNAPSHOT' + compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: '1.0.0-rc-1036' + compile group: 'net.dv8tion', name: 'JDA', version: '3.0.BETA2_108' compile group: 'com.michaelwflaherty', name: 'cleverbotapi', version: '1.0.1' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } @@ -48,8 +48,8 @@ dependencies { processResources { filter ReplaceTokens, tokens: [ 'DESCRIPTION': project.property('description'), - 'URL': project.property('url'), - 'VERSION': project.property('version') + 'URL' : project.property('url'), + 'VERSION' : project.property('version') ] } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Util.kt b/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt similarity index 50% rename from src/main/kotlin/gg/obsidian/discordbridge/Util.kt rename to src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt index 2487eec..1056908 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Util.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt @@ -1,38 +1,23 @@ package gg.obsidian.discordbridge -import org.bukkit.ChatColor -import java.io.IOException import com.michaelwflaherty.cleverbotapi.CleverBotQuery +import java.io.IOException -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 - } +object CommandLogic { fun askCleverbot(key: String, message: String): String { if (key.isEmpty()) return "You do not have an API key. Go to https://www.cleverbot.com/api/ for more information." val bot: CleverBotQuery = CleverBotQuery(key, message) var response: String - try - { + try { bot.sendRequest() response = bot.response - } - catch (e: IOException) - { + } catch (e: IOException) { response = e.message!! } return response } + } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt similarity index 93% rename from src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt rename to src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt index 52c80d9..7a6bcc4 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/ConfigAccessor.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt @@ -30,7 +30,7 @@ import java.io.IOException import java.io.InputStreamReader import java.util.logging.Level -class ConfigAccessor(private val plugin: JavaPlugin, filepath: File, private val fileName: String) { +class DataConfigAccessor(private val plugin: JavaPlugin, filepath: File, private val fileName: String) { private val configFile: File? private var fileConfiguration: FileConfiguration? = null @@ -52,7 +52,7 @@ class ConfigAccessor(private val plugin: JavaPlugin, filepath: File, private val } } - val config: FileConfiguration + val data: FileConfiguration get() { if (fileConfiguration == null) { this.reloadConfig() @@ -65,9 +65,9 @@ class ConfigAccessor(private val plugin: JavaPlugin, filepath: File, private val return } else { try { - config.save(configFile) + data.save(configFile) } catch (ex: IOException) { - plugin.logger.log(Level.SEVERE, "Could not save config to " + configFile, ex) + plugin.logger.log(Level.SEVERE, "Could not save data to " + configFile, ex) } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt index 4055255..fc3ba43 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt @@ -1,6 +1,6 @@ package gg.obsidian.discordbridge -import org.bukkit.ChatColor +import gg.obsidian.discordbridge.Utils.* import org.bukkit.event.EventHandler import org.bukkit.event.EventPriority import org.bukkit.event.Listener @@ -10,116 +10,116 @@ import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent import java.lang.reflect.Method -class EventListener(val plugin: Plugin): Listener { +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 (!plugin.configuration.MESSAGES_CHAT) return - if (event.isCancelled && !plugin.configuration.RELAY_CANCELLED_MESSAGES) return + if (!plugin.cfg.MESSAGES_CHAT) return + if (event.isCancelled && !plugin.cfg.RELAY_CANCELLED_MESSAGES) return // Check for vanished val player = event.player if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && - !plugin.configuration.IF_VANISHED_CHAT) return + !plugin.cfg.IF_VANISHED_CHAT) return - val username = ChatColor.stripColor(event.player.name) + val username = event.player.name.stripColor() var worldname = player.world.name if (plugin.isMultiverse()) { - val worldProperties = plugin.worlds!!.config.get("worlds.$worldname") + val worldProperties = plugin.worlds!!.data.get("worlds.$worldname") val cls = Class.forName("com.onarandombox.MultiverseCore.WorldProperties") val meth: Method = cls.getMethod("getAlias") val alias = meth.invoke(worldProperties) - if (alias is String) worldname = alias + if (alias is String) worldname = alias } - val formattedMessage = Util.formatMessage( - plugin.configuration.TEMPLATES_DISCORD_CHAT_MESSAGE, + val formattedMessage = Utils.formatMessage( + plugin.cfg.TEMPLATES_DISCORD_CHAT_MESSAGE, mapOf( "%u" to username, - "%m" to ChatColor.stripColor(event.message), - "%d" to ChatColor.stripColor(player.displayName), + "%m" to event.message.stripColor(), + "%d" to player.displayName.stripColor(), "%w" to worldname ) ) - plugin.sendToDiscordRelay(formattedMessage, event.player.uniqueId.toString()) + plugin.sendToDiscord(formattedMessage, event.player.uniqueId.toString()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun onPlayerJoin(event: PlayerJoinEvent) { - if (!plugin.configuration.MESSAGES_JOIN) return + if (!plugin.cfg.MESSAGES_JOIN) return // Check for vanished val player = event.player if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && - !plugin.configuration.IF_VANISHED_JOIN) return + !plugin.cfg.IF_VANISHED_JOIN) return - val username = ChatColor.stripColor(player.name) + val username = player.name.stripColor() plugin.logDebug("Received a join event for $username") - val formattedMessage = Util.formatMessage( - plugin.configuration.TEMPLATES_DISCORD_PLAYER_JOIN, + val formattedMessage = Utils.formatMessage( + plugin.cfg.TEMPLATES_DISCORD_PLAYER_JOIN, mapOf( "%u" to username, - "%d" to ChatColor.stripColor(player.displayName) + "%d" to player.displayName.stripColor() ) ) - plugin.sendToDiscordRelay(formattedMessage, player.uniqueId.toString()) + plugin.sendToDiscord(formattedMessage, player.uniqueId.toString()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun onPlayerQuit(event: PlayerQuitEvent) { - if (!plugin.configuration.MESSAGES_LEAVE) return + if (!plugin.cfg.MESSAGES_LEAVE) return // Check for vanished val player = event.player if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && - !plugin.configuration.IF_VANISHED_LEAVE) return + !plugin.cfg.IF_VANISHED_LEAVE) return - val username = ChatColor.stripColor(event.player.name) + val username = event.player.name.stripColor() plugin.logDebug("Received a leave event for $username") - val formattedMessage = Util.formatMessage( - plugin.configuration.TEMPLATES_DISCORD_PLAYER_LEAVE, + val formattedMessage = Utils.formatMessage( + plugin.cfg.TEMPLATES_DISCORD_PLAYER_LEAVE, mapOf( "%u" to username, - "%d" to ChatColor.stripColor(event.player.displayName) + "%d" to event.player.displayName.stripColor() ) ) - plugin.sendToDiscordRelay(formattedMessage, player.uniqueId.toString()) + plugin.sendToDiscord(formattedMessage, player.uniqueId.toString()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun onPlayerDeath(event: PlayerDeathEvent) { - if (!plugin.configuration.MESSAGES_DEATH) return + if (!plugin.cfg.MESSAGES_DEATH) return // Check for vanished val player = event.entity if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && - !plugin.configuration.IF_VANISHED_DEATH) return + !plugin.cfg.IF_VANISHED_DEATH) return - val username = ChatColor.stripColor(event.entity.name) + val username = event.entity.name.stripColor() plugin.logDebug("Received a death event for $username") - val formattedMessage = Util.formatMessage( - plugin.configuration.TEMPLATES_DISCORD_PLAYER_DEATH, + val formattedMessage = Utils.formatMessage( + plugin.cfg.TEMPLATES_DISCORD_PLAYER_DEATH, mapOf( "%u" to username, - "%d" to ChatColor.stripColor(event.entity.displayName), + "%d" to event.entity.displayName.stripColor(), "%r" to event.deathMessage, "%w" to event.entity.world.name ) ) - plugin.sendToDiscordRelay(formattedMessage, player.uniqueId.toString()) + plugin.sendToDiscord(formattedMessage, player.uniqueId.toString()) } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt b/src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt deleted file mode 100644 index 64a7e45..0000000 --- a/src/main/kotlin/gg/obsidian/discordbridge/HandleMarina.kt +++ /dev/null @@ -1,31 +0,0 @@ -package gg.obsidian.discordbridge - -import org.bukkit.command.Command -import org.bukkit.command.CommandExecutor -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player - -class HandleMarina(val plugin: Plugin): CommandExecutor { - - override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { - if (args == null || args.isEmpty()) return false - - if (player is Player) { - val message: String = args.joinToString(" ") - player.chat("@MarinaFriend " + message) - val response: String = Util.askCleverbot(plugin.configuration.CLEVERBOT_KEY, message) - val formattedMessage = Util.formatMessage( - plugin.configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, - mapOf( - "%u" to plugin.configuration.USERNAME_COLOR + plugin.configuration.USERNAME.replace("\\s+", "") + "&r", - "%m" to response - ), - colors = true - ) - plugin.server.broadcastMessage(formattedMessage) - plugin.sendToDiscordRelaySelf(response) - return true - } - return true - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index d7bc2ac..e2ee6f6 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,5 +1,9 @@ package gg.obsidian.discordbridge +import gg.obsidian.discordbridge.Utils.Utils +import gg.obsidian.discordbridge.discord.Connection +import gg.obsidian.discordbridge.minecraft.commands.Discord +import gg.obsidian.discordbridge.minecraft.commands.Marina import net.dv8tion.jda.core.OnlineStatus import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent @@ -9,58 +13,59 @@ import java.io.File class Plugin : JavaPlugin() { - val configuration = Configuration(this) - var connection: DiscordConnection? = null - var users: ConfigAccessor = ConfigAccessor(this, dataFolder, "usernames.yml") - var worlds: ConfigAccessor? = null + val cfg = Configuration(this) + var connection: Connection? = null + var users: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "usernames.yml") + var worlds: DataConfigAccessor? = null var requests: MutableList = mutableListOf() override fun onEnable() { + // Load configs updateConfig(description.version) users.saveDefaultConfig() - if (isMultiverse()) worlds = ConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") - - - this.connection = DiscordConnection(this) + if (isMultiverse()) worlds = DataConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") + // Connect to Discord + this.connection = Connection(this) server.scheduler.runTaskAsynchronously(this, connection) server.pluginManager.registerEvents(EventListener(this), this) - getCommand("discord").executor = CommandHandler(this) - getCommand("marina").executor = HandleMarina(this) + + // Register commands + getCommand("discord").executor = Discord(this) + getCommand("marina").executor = Marina(this) } override fun onDisable() { connection!!.relay("Shutting down...") + + // Pretend like this does anything logger.log(Level.INFO, "Attempting to cancel tasks") server.scheduler.cancelTasks(this) } - fun reload() { + /*====================================== + Messaging Functions + ===================================== */ - reloadConfig() - users.reloadConfig() - configuration.load() - connection?.reconnect() - } - - // Message senders - - fun sendToDiscordRelaySelf(message: String) { + // Send a message to Discord as the bot itself + fun sendToDiscord(message: String) { logDebug("Sending message to Discord - $message") connection!!.relay(message) } - fun sendToDiscordRelay(message: String, uuid: String) { - val alias = users.config.getString("mcaliases.$uuid.discordusername") + // Send a message from a Minecraft player to Discord + fun sendToDiscord(message: String, uuid: String) { + val alias = users.data.getString("mcaliases.$uuid.discordusername") var newMessage = message - // This section should convert attempted @mentions to real ones wherever possible + // This gross section converts attempted @mentions to real ones wherever possible + // Mentionable names MUST not contain spaces! val discordusers = getDiscordUsers() val discordaliases: MutableList> = mutableListOf() discordusers - .filter { users.config.isSet("discordaliases.${it.second}") } + .filter { users.data.isSet("discordaliases.${it.second}") } .mapTo(discordaliases) { - Pair(users.config.getString("discordaliases.${it.second}.mcusername"), + Pair(users.data.getString("discordaliases.${it.second}.mcusername"), it.second) } for (match in Regex("""(?:^| )@(w+)""").findAll(message)) { @@ -75,65 +80,78 @@ class Plugin : JavaPlugin() { if (found2 != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found2.first}>") } - if (alias != null) newMessage = message.replaceFirst(users.config.getString("mcaliases.$uuid.mcusername"), alias) + if (alias != null) newMessage = message.replaceFirst(users.data.getString("mcaliases.$uuid.mcusername"), alias) logDebug("Sending message to Discord - $newMessage") connection!!.relay(newMessage) } - fun sendToDiscordRespond(message: String, event: MessageReceivedEvent) { + // TODO: this function is little different because it isn't a simple relay. Consider refactoring. + // Send a direct message to a Discord user as the bot itself + fun sendToDiscord(message: String, event: MessageReceivedEvent) { if (event.isFromType(ChannelType.PRIVATE)) logDebug("Sending message to ${event.author.name} - $message") else logDebug("Sending message to Discord - $message") connection!!.respond(message, event) } - fun sendToMinecraft(username: String, id: String, message: String) { - var alias = users.config.getString("discordaliases.$id.mcusername") - if (alias == null) alias = username.replace("\\s+", "") - val formattedMessage = Util.formatMessage( - configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, + // Send a message to Minecraft as the bot iself + fun sendToMinecraft(message: String) { + val formattedMessage = Utils.formatMessage( + cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, mapOf( - "%u" to alias, + "%u" to cfg.USERNAME_COLOR + cfg.USERNAME.replace("\\s+", "") + "&r", "%m" to message ), colors = true ) - server.broadcastMessage(formattedMessage) } - fun sendToMinecraftBroadcast(message: String) { - val formattedMessage = Util.formatMessage( - configuration.TEMPLATES_MINECRAFT_CHAT_MESSAGE, - mapOf( - "%u" to configuration.USERNAME_COLOR + configuration.USERNAME.replace("\\s+", "") + "&r", - "%m" to message - ), + // Send a message to Minecraft from a Discord user + fun sendToMinecraft(message: String, username: String, id: String) { + var alias = users.data.getString("discordaliases.$id.mcusername") + if (alias == null) alias = username.replace("\\s+", "") + val formattedMessage = Utils.formatMessage( + cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, + mapOf("%u" to alias, "%m" to message), colors = true ) server.broadcastMessage(formattedMessage) } - // Utilities + /*=========================================== + Util + ===========================================*/ + // Reload configs + fun reload() { + reloadConfig() + users.reloadConfig() + if (isMultiverse()) worlds!!.reloadConfig() + cfg.load() + //connection?.reconnect() + } + + // Save default config fun updateConfig(version: String) { this.saveDefaultConfig() config.options().copyDefaults(true) config.set("version", version) saveConfig() - configuration.load() + cfg.load() } + // Log only if debug config is true fun logDebug(msg: String) { - if (!configuration.DEBUG) return + if (!cfg.DEBUG) return logger.info(msg) } + // Shorthand function to check if Multiverse is installed fun isMultiverse(): Boolean { return server.pluginManager.getPlugin("Multiverse-Core") != null } - // Stuff - + // Get a list of usernames of players who are online fun getOnlinePlayers(): List { val names: MutableList = mutableListOf() val players = server.onlinePlayers.toTypedArray() @@ -141,6 +159,7 @@ class Plugin : JavaPlugin() { return names.toList() } + // Open a request to link a Minecraft user with a Discord user fun registerUserRequest(ua: UserAlias) { requests.add(ua) val msg = "Minecraft user '${ua.mcUsername}' has requested to become associated with your Discord" + @@ -149,21 +168,25 @@ class Plugin : JavaPlugin() { connection!!.tell(msg, ua.discordId) } + // Return a list of all Discord users in the specified server fun getDiscordUsers(): List> { return connection!!.listUsers() } + // Return a list of all Discord users in the specified server who are visibly available fun getDiscordOnline(): List> { return connection!!.listOnline() } + // TODO: Rename function to something more intuitive + // Add an alias to the Users data fun updateAlias(ua: UserAlias) { - users.config.set("mcaliases.${ua.mcUuid}.mcusername", ua.mcUsername) - users.config.set("mcaliases.${ua.mcUuid}.discordusername", ua.discordUsername) - users.config.set("mcaliases.${ua.mcUuid}.discordid", ua.discordId) - users.config.set("discordaliases.${ua.discordId}.mcuuid", ua.mcUuid) - users.config.set("discordaliases.${ua.discordId}.mcusername", ua.mcUsername) - users.config.set("discordaliases.${ua.discordId}.discordusername", ua.discordUsername) + users.data.set("mcaliases.${ua.mcUuid}.mcusername", ua.mcUsername) + users.data.set("mcaliases.${ua.mcUuid}.discordusername", ua.discordUsername) + users.data.set("mcaliases.${ua.mcUuid}.discordid", ua.discordId) + users.data.set("discordaliases.${ua.discordId}.mcuuid", ua.mcUuid) + users.data.set("discordaliases.${ua.discordId}.mcusername", ua.mcUsername) + users.data.set("discordaliases.${ua.discordId}.discordusername", ua.discordUsername) users.saveConfig() } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt new file mode 100644 index 0000000..070b915 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt @@ -0,0 +1,22 @@ +package gg.obsidian.discordbridge.Utils + +import org.bukkit.ChatColor + +fun String.noSpace() = this.replace(Regex("""\s+"""), "") +fun String.stripColor() = ChatColor.stripColor(this) + +object Utils { + + 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/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt similarity index 85% rename from src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt rename to src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt index 6f53cbf..de99154 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordConnection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt @@ -1,5 +1,6 @@ -package gg.obsidian.discordbridge +package gg.obsidian.discordbridge.discord +import gg.obsidian.discordbridge.Plugin import net.dv8tion.jda.core.AccountType import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.JDABuilder @@ -9,9 +10,9 @@ import net.dv8tion.jda.core.entities.Guild import net.dv8tion.jda.core.entities.TextChannel import net.dv8tion.jda.core.events.message.MessageReceivedEvent -class DiscordConnection(val plugin: Plugin) : Runnable { +class Connection(val plugin: Plugin) : Runnable { var api: JDA? = null - var listener: DiscordListener? = null + var listener: Listener? = null var server: Guild? = null var channel: TextChannel? = null @@ -25,10 +26,10 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } fun relay(message: String) { - server = if (server == null) getServerById(plugin.configuration.SERVER_ID) else server + server = if (server == null) getServerById(plugin.cfg.SERVER_ID) else server if (server == null) return - channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel + channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel if (channel == null) return channel!!.sendMessage(message).queue() @@ -50,7 +51,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } fun listUsers(): List> { - channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel + channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel if (channel == null) return mutableListOf() val listOfUsers: MutableList> = mutableListOf() @@ -61,7 +62,7 @@ class DiscordConnection(val plugin: Plugin) : Runnable { } fun listOnline(): List> { - channel = if (channel == null) getGroupByName(server!!, plugin.configuration.CHANNEL) else channel + channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel if (channel == null) return mutableListOf() val listOfUsers: MutableList> = mutableListOf() @@ -78,9 +79,9 @@ class DiscordConnection(val plugin: Plugin) : Runnable { private fun connect() { var builder = JDABuilder(AccountType.BOT).setAudioEnabled(false) - builder = builder.setToken(plugin.configuration.TOKEN) + builder = builder.setToken(plugin.cfg.TOKEN) api = builder.buildBlocking() - listener = DiscordListener(plugin, api as JDA, this) + listener = Listener(plugin, api as JDA, this) api!!.addEventListener(listener) relay("Oikos!") } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt similarity index 72% rename from src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt rename to src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt index 31e4634..d5ef172 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DiscordListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt @@ -1,14 +1,16 @@ -package gg.obsidian.discordbridge +package gg.obsidian.discordbridge.discord import com.neovisionaries.ws.client.WebSocket import com.neovisionaries.ws.client.WebSocketException import com.neovisionaries.ws.client.WebSocketFrame +import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.Plugin import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent import net.dv8tion.jda.core.hooks.ListenerAdapter -class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordConnection) : ListenerAdapter() { +class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : ListenerAdapter() { override fun onMessageReceived(event: MessageReceivedEvent) { plugin.logDebug("Received message ${event.message.id} from Discord - ${event.message.rawContent}") @@ -17,17 +19,17 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC val username: String = event.author.name // Immediately throw out messages sent from itself or from non-matching servers - if (username.equals(plugin.configuration.USERNAME, true)) { + if (username.equals(plugin.cfg.USERNAME, true)) { plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches this bot's username") return } - if (event.guild.id != plugin.configuration.SERVER_ID) { + if (event.guild.id != plugin.cfg.SERVER_ID) { plugin.logDebug("Not relaying message ${event.message.id} from Discord: server does not match") return } // NON-RELAY COMMANDS - the following commands and their responses are not sent to Minecraft - if(rawmsg.startsWith(api.selfUser.asMention, true)) { + if (rawmsg.startsWith(api.selfUser.asMention, true)) { val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").replaceFirst("\\s+", "") // CONFIRM - Confirm an alias @@ -35,12 +37,12 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC plugin.logDebug("user $username wants to confirm an alias") val ua = plugin.requests.find { it.discordId == event.author.id } if (ua == null) { - plugin.sendToDiscordRespond("You have not requested an alias, or your request has expired!", event) + plugin.sendToDiscord("You have not requested an alias, or your request has expired!", event) return } plugin.updateAlias(ua) plugin.requests.remove(ua) - plugin.sendToDiscordRespond("Successfully linked aliases!", event) + plugin.sendToDiscord("Successfully linked aliases!", event) return } @@ -49,11 +51,11 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC plugin.logDebug("user $username has requested a listing of online players") val players = plugin.getOnlinePlayers() if (players.isEmpty()) { - plugin.sendToDiscordRespond("Nobody is currently online.", event) + plugin.sendToDiscord("Nobody is currently online.", event) return } val response = players.joinToString("\n", "The following players are currently online:\n```\n", "\n```") - plugin.sendToDiscordRespond(response, event) + plugin.sendToDiscord(response, event) return } } @@ -61,16 +63,16 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC // If it is from the relay channel, relay it immediately if (event.isFromType(ChannelType.TEXT)) { - if (!event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) + if (!event.textChannel.name.equals(plugin.cfg.CHANNEL, true)) plugin.logDebug("Not relaying message ${event.message.id} from Discord: channel does not match") else { plugin.logDebug("Broadcasting message ${event.message.id} from Discord to Minecraft as user $username") - plugin.sendToMinecraft(username, event.author.id, event.message.content) + plugin.sendToMinecraft(event.message.content, username, event.author.id) } } // RELAY COMMANDS - These commands and their outputs DO get relayed to Minecraft - if(rawmsg.startsWith(api.selfUser.asMention, true)) { + if (rawmsg.startsWith(api.selfUser.asMention, true)) { val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").removePrefix(" ") if (arg.isEmpty()) return plugin.logDebug("Relay command received. Arg: $arg") @@ -78,30 +80,30 @@ class DiscordListener(val plugin: Plugin, val api: JDA, val connection: DiscordC // OIKOS - Delicious Greek yogurt from Danone! if (arg.startsWith("oikos", true)) { plugin.logDebug("user $username has initiated oikos!") - plugin.sendToDiscordRespond("Delicious Greek yogurt from Danone!", event) - plugin.sendToMinecraftBroadcast("Delicious Greek yogurt from Danone!") + plugin.sendToDiscord("Delicious Greek yogurt from Danone!", event) + plugin.sendToMinecraft("Delicious Greek yogurt from Danone!") return } if (arg.startsWith("delicious greek yogurt from danone", true)) { plugin.logDebug("user $username has initiated oikos 2!") - plugin.sendToDiscordRespond("\uD83D\uDE04", event) - plugin.sendToMinecraftBroadcast(":D") + plugin.sendToDiscord("\uD83D\uDE04", event) + plugin.sendToMinecraft(":D") return } if (arg == "\uD83D\uDE04") { plugin.logDebug("user $username has initiated oikos 3!") - plugin.sendToDiscordRespond("\uD83D\uDE04", event) - plugin.sendToMinecraftBroadcast(":D") + plugin.sendToDiscord("\uD83D\uDE04", event) + plugin.sendToMinecraft(":D") return } // CLEVERBOT - Assume anything else invokes Cleverbot plugin.logDebug("user $username asks CleverBot something") - val response = Util.askCleverbot(plugin.configuration.CLEVERBOT_KEY, arg) - plugin.sendToDiscordRespond(response, event) + val response = CommandLogic.askCleverbot(plugin.cfg.CLEVERBOT_KEY, arg) + plugin.sendToDiscord(response, event) // if this occurs in the relay channel, relay the response - if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.configuration.CHANNEL, true)) - plugin.sendToMinecraftBroadcast(response) + if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.cfg.CHANNEL, true)) + plugin.sendToMinecraft(response) return } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt similarity index 88% rename from src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt rename to src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt index 5983902..836e5ac 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/CommandHandler.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt @@ -1,5 +1,8 @@ -package gg.obsidian.discordbridge +package gg.obsidian.discordbridge.minecraft.commands +import gg.obsidian.discordbridge.Permissions +import gg.obsidian.discordbridge.Plugin +import gg.obsidian.discordbridge.UserAlias import net.dv8tion.jda.core.OnlineStatus import org.bukkit.ChatColor import org.bukkit.command.Command @@ -7,14 +10,9 @@ import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.entity.Player -class CommandHandler(val plugin: Plugin): CommandExecutor { +class Discord(val plugin: Plugin) : CommandExecutor { override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { - if (cmd.name == "discord") return handleDiscord(player, args) - return false - } - - private fun handleDiscord(player: CommandSender, args: Array?): Boolean { if (args == null || args.isEmpty()) { sendMessage("&eUsage: /discord ", player) return true @@ -121,21 +119,21 @@ class CommandHandler(val plugin: Plugin): CommandExecutor { } var response = "" - if (users.filter{it.third == OnlineStatus.ONLINE}.isNotEmpty()) { + if (users.filter { it.third == OnlineStatus.ONLINE }.isNotEmpty()) { response += "\n&2Online:&r" - for (user in users.filter{it.third == OnlineStatus.ONLINE}) { + for (user in users.filter { it.third == OnlineStatus.ONLINE }) { if (user.second) response += "\n&2- ${user.first} (Bot)&r" else response += "\n&2- ${user.first}&r" } } - if (users.filter{it.third == OnlineStatus.IDLE}.isNotEmpty()) { + if (users.filter { it.third == OnlineStatus.IDLE }.isNotEmpty()) { response += "\n&eIdle:&r" - for (user in users.filter{it.third == OnlineStatus.IDLE}) { + for (user in users.filter { it.third == OnlineStatus.IDLE }) { if (user.second) response += "\n&e- ${user.first} (Bot)&r" else response += "\n&e- ${user.first}&r" } } - if (users.filter{it.third == OnlineStatus.DO_NOT_DISTURB}.isNotEmpty()) { + if (users.filter { it.third == OnlineStatus.DO_NOT_DISTURB }.isNotEmpty()) { response += "\n&cDo Not Disturb:&r" for (user in users.filter { it.third == OnlineStatus.DO_NOT_DISTURB }) { if (user.second) response += "\n&c- ${user.first} (Bot)&r" diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt new file mode 100644 index 0000000..2af5039 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt @@ -0,0 +1,35 @@ +package gg.obsidian.discordbridge.minecraft.commands + +import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.Plugin +import gg.obsidian.discordbridge.Utils.* +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class Marina(val plugin: Plugin) : CommandExecutor { + + override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (args == null || args.isEmpty()) return false + + if (player is Player) { + val message: String = args.joinToString(" ") + player.chat("@${plugin.cfg.USERNAME.noSpace()} $message") + val response: String = CommandLogic.askCleverbot(plugin.cfg.CLEVERBOT_KEY, message) + val formattedMessage = Utils.formatMessage( + plugin.cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, + mapOf( + "%u" to plugin.cfg.USERNAME_COLOR + + plugin.cfg.USERNAME.noSpace() + "&r", + "%m" to response + ), + colors = true + ) + plugin.server.broadcastMessage(formattedMessage) + plugin.sendToDiscord(response) + return true + } + return true + } +} \ No newline at end of file From a096d3d9155f22e5739658c14807237a95fd0bc1 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Sun, 26 Feb 2017 03:22:48 -0600 Subject: [PATCH 14/24] 1.7.5 Added scripted responses Unified the message sending methods under Plugin Broke up the formatMessage method into specific cases --- README.md | 41 +++-- build.gradle | 2 +- .../obsidian/discordbridge/Configuration.kt | 13 ++ .../obsidian/discordbridge/EventListener.kt | 53 ++---- .../gg/obsidian/discordbridge/Permissions.kt | 3 +- .../gg/obsidian/discordbridge/Plugin.kt | 169 ++++++++++-------- .../gg/obsidian/discordbridge/Utils/Utils.kt | 18 +- .../discordbridge/discord/Connection.kt | 27 +-- .../discordbridge/discord/Listener.kt | 60 ++++--- .../minecraft/commands/Marina.kt | 20 +-- src/main/resources/botmemory.yml | 1 + src/main/resources/config.yml | 3 + src/main/resources/plugin.yml | 3 + src/main/resources/scriptedresponses.yml | 5 + 14 files changed, 217 insertions(+), 201 deletions(-) create mode 100644 src/main/resources/botmemory.yml create mode 100644 src/main/resources/scriptedresponses.yml diff --git a/README.md b/README.md index 61d3982..46e3711 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,14 @@ Bridges chat between Discord and Minecraft (Bukkit/Spigot). ## Requirements * Java 8 +* Spigot 1.11.2 ## Installation -1. Download the [latest release](https://github.com/the-obsidian/DiscordBridge/releases) from GitHub -1. Add it to your `plugins` folder -1. Either run Bukkit/Spigot once to generate `DiscordBridge/data.yml` or create it using the guide below. -1. All done! +<> ## Configuration -**Note:** To use with the official Discord API and a bot user, add a `token: 'your-bot-token-here'` line alongside `email` and `password` (so it will have two spaces of indentation). If a `token` setting is present, it will ignore `email` and `password`. A more user-friendly version of this will be released shortly. - DiscordBridge has several options that can be configured in the `data.yml` file: ```yaml @@ -24,10 +20,12 @@ settings: server-id: '00000000' channel: 'test' username: 'username' - email: 'email@example.com' - password: 'password' + username-color: '' + token: 'token' + cleverbot-key: '' debug: false relay_cancelled_messages: true + announce_server_start_stop: true messages: chat: true join: true @@ -44,6 +42,8 @@ settings: player_join: '%u joined the server' player_leave: '%u left the server' player_death: '%r' + server_start: 'Server started!' + server_stop: 'Shutting down...' minecraft: chat_message: '<%u&b(discord)&r> %m' ``` @@ -51,8 +51,9 @@ settings: * `server-id` is the ID of your Discord server. This can be found under *Server Settings > Widget > Server ID* * `channel` is the Discord channel name you would like to bridge with your Minecraft server * `username` is the Discord username of your bot user -* `email` is the Discord email address of your bot user -* `password` is the Discord password of your bot user +* `username_color` is for adding formatting codes to the name of your bot when it speaks in Minecraft's chat (optional) +* `token` is the access token for the Discord bot +* `cleverbot-key` is the access key necessary to chat with Cleverbot's API (optional) * `debug` enables more verbose logging * `relay_cancelled_messages` will relay chat messages even if they are cancelled * `messages` enables or disables certain kinds of messages @@ -62,7 +63,7 @@ settings: **Templates** - `%u` will be replaced with the username -- '%d' will be replaced with the user's display name +- `%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 @@ -72,9 +73,16 @@ settings: * 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) +* You can link Minecraft accounts to Discord accounts and the bot will translate display names to match where the message appears * Join / leave messages are sent to Discord -* Death messages can optionally be sent to Discord +* Death messages are sent to Discord +* Server start and stop messages are sent to Discord +* All of the above messages can be toggled if you don't want them to appear * Message templates are customized +* Prefixing usernames with `@` in the Minecraft chat will be converted to mentions in the Discord chat if the user exists (you can use their Discord display name with spaces removed, or their Minecraft username if the accounts are linked) +* Image attachments sent in the Discord channel will relay their URLs to Minecraft +* Add scripted responses for the bot to say when it detects a trigger phrase +* Cleverbot integration - chat with the bot on Discord using `@` or chat with the bot in Minecraft using a slash command ## Permissions @@ -83,9 +91,12 @@ settings: ## Commands - `/discord reload` - reloads data and reconnects to Discord +- `/discord get online` - provides a list of all Discord users in the relay channel who are Online, Do Not Disturb, and Idle +- `/discord get ids` - provides a list of the Discord IDs of all users in the relay channel, which is useful for... +- `/discord register ` - this command will send a DM to the corresponding user asking if that user wishes to link their Discord account with the Minecraft user who issued the command +- `/marina ` - Talk to Cleverbot! Anything you say after this command is relayed to Cleverbot's API, and the bot will speak the response. Only works if you specify a Cleverbot API key in the config. ## Upcoming Features -* Deeper integration into Minecraft chat (like supporting chat channels inside Minecraft) -* A "merge accounts" function to allow Minecraft players to associate their Discord accounts with their Minecraft accounts so that usernames are properly translated -* Ability to post messages to Discord on behalf of Discord users, rather than using a bot user (hopefully after the official API is released) +* Add support for a URL shortening service so attachment URLs aren't so flipping long +* Some of the 'fun' commands that literally every Discord bot has (with matching Minecraft commands!) diff --git a/build.gradle b/build.gradle index 8ded0d5..34f674f 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ plugins { apply plugin: 'kotlin' group = 'gg.obsidian' -version = '1.7.4' +version = '1.7.5' description = """Bridge chat between Minecraft and Discord""" ext.url = 'https://github.com/the-obsidian/DiscordBridge' diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt index db50e1f..9c4c212 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt @@ -1,5 +1,7 @@ package gg.obsidian.discordbridge +import gg.obsidian.discordbridge.Utils.noSpace + class Configuration(val plugin: Plugin) { var SERVER_ID: String = "" @@ -12,6 +14,7 @@ class Configuration(val plugin: Plugin) { var CLEVERBOT_KEY: String = "" var DEBUG: Boolean = false var RELAY_CANCELLED_MESSAGES = true + var ANNOUNCE_SERVER_START_STOP = true // Toggle message types var MESSAGES_CHAT = true @@ -30,10 +33,15 @@ class Configuration(val plugin: Plugin) { var TEMPLATES_DISCORD_PLAYER_JOIN = "" var TEMPLATES_DISCORD_PLAYER_LEAVE = "" var TEMPLATES_DISCORD_PLAYER_DEATH = "" + var TEMPLATES_DISCORD_SERVER_START = "" + var TEMPLATES_DISCORD_SERVER_STOP = "" // Minecraft message templates var TEMPLATES_MINECRAFT_CHAT_MESSAGE = "" + // misc + var BOT_MC_USERNAME = "" + fun load() { plugin.reloadConfig() @@ -47,6 +55,7 @@ class Configuration(val plugin: Plugin) { CLEVERBOT_KEY = plugin.config.getString("settings.cleverbot-key", "") DEBUG = plugin.config.getBoolean("settings.debug", false) RELAY_CANCELLED_MESSAGES = plugin.config.getBoolean("settings.relay_cancelled_messages", true) + ANNOUNCE_SERVER_START_STOP = plugin.config.getBoolean("settings.announce_server_start_stop", true) MESSAGES_CHAT = plugin.config.getBoolean("settings.messages.chat", true) MESSAGES_JOIN = plugin.config.getBoolean("settings.messages.join", true) @@ -62,7 +71,11 @@ class Configuration(val plugin: Plugin) { 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_DISCORD_SERVER_START = plugin.config.getString("settings.templates.discord.server_start", "Server started!") + TEMPLATES_DISCORD_SERVER_STOP = plugin.config.getString("settings.templates.discord.server_stop", "Shutting down...") TEMPLATES_MINECRAFT_CHAT_MESSAGE = plugin.config.getString("settings.templates.minecraft.chat_message", "<%u&b(discord)&r> %m") + + BOT_MC_USERNAME = USERNAME_COLOR + USERNAME.noSpace() + "&r" } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt index fc3ba43..2ea613c 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt @@ -36,17 +36,11 @@ class EventListener(val plugin: Plugin) : Listener { if (alias is String) worldname = alias } - val formattedMessage = Utils.formatMessage( - plugin.cfg.TEMPLATES_DISCORD_CHAT_MESSAGE, - mapOf( - "%u" to username, - "%m" to event.message.stripColor(), - "%d" to player.displayName.stripColor(), - "%w" to worldname - ) - ) - - plugin.sendToDiscord(formattedMessage, event.player.uniqueId.toString()) + var formattedMessage = plugin.toDiscordChatMessage(event.message.stripColor(), username, player.displayName.stripColor(), worldname) + formattedMessage = plugin.convertAtMentions(formattedMessage) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, event.player.uniqueId.toString()) + + plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -62,15 +56,10 @@ class EventListener(val plugin: Plugin) : Listener { val username = player.name.stripColor() plugin.logDebug("Received a join event for $username") - val formattedMessage = Utils.formatMessage( - plugin.cfg.TEMPLATES_DISCORD_PLAYER_JOIN, - mapOf( - "%u" to username, - "%d" to player.displayName.stripColor() - ) - ) + var formattedMessage = plugin.toDiscordPlayerJoin(username, player.displayName.stripColor()) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, event.player.uniqueId.toString()) - plugin.sendToDiscord(formattedMessage, player.uniqueId.toString()) + plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -86,15 +75,10 @@ class EventListener(val plugin: Plugin) : Listener { val username = event.player.name.stripColor() plugin.logDebug("Received a leave event for $username") - val formattedMessage = Utils.formatMessage( - plugin.cfg.TEMPLATES_DISCORD_PLAYER_LEAVE, - mapOf( - "%u" to username, - "%d" to event.player.displayName.stripColor() - ) - ) + var formattedMessage = plugin.toDiscordPlayerLeave(username, event.player.displayName.stripColor()) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, event.player.uniqueId.toString()) - plugin.sendToDiscord(formattedMessage, player.uniqueId.toString()) + plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @@ -110,16 +94,9 @@ class EventListener(val plugin: Plugin) : Listener { val username = event.entity.name.stripColor() plugin.logDebug("Received a death event for $username") - val formattedMessage = Utils.formatMessage( - plugin.cfg.TEMPLATES_DISCORD_PLAYER_DEATH, - mapOf( - "%u" to username, - "%d" to event.entity.displayName.stripColor(), - "%r" to event.deathMessage, - "%w" to event.entity.world.name - ) - ) - - plugin.sendToDiscord(formattedMessage, player.uniqueId.toString()) + var formattedMessage = plugin.toDiscordPlayerDeath(event.deathMessage, username, event.entity.displayName.stripColor(), event.entity.world.name) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, player.uniqueId.toString()) + + plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt b/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt index 4fa57d1..e0fee5c 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt @@ -3,7 +3,8 @@ package gg.obsidian.discordbridge import org.bukkit.entity.Player enum class Permissions(val node: String) { - reload("discordbridge.reload"); + reload("discordbridge.reload"), + cleverbot("discordbridge.cleverbot"); 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 e2ee6f6..e5d77d6 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,12 +1,11 @@ package gg.obsidian.discordbridge -import gg.obsidian.discordbridge.Utils.Utils import gg.obsidian.discordbridge.discord.Connection import gg.obsidian.discordbridge.minecraft.commands.Discord import gg.obsidian.discordbridge.minecraft.commands.Marina import net.dv8tion.jda.core.OnlineStatus -import net.dv8tion.jda.core.entities.ChannelType -import net.dv8tion.jda.core.events.message.MessageReceivedEvent +import net.dv8tion.jda.core.entities.MessageChannel +import org.bukkit.ChatColor import org.bukkit.plugin.java.JavaPlugin import java.util.logging.Level import java.io.File @@ -14,8 +13,10 @@ import java.io.File class Plugin : JavaPlugin() { val cfg = Configuration(this) - var connection: Connection? = null + lateinit var connection: Connection var users: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "usernames.yml") + var memory: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "botmemory.yml") + var scripted_responses: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "scriptedresponses.yml") var worlds: DataConfigAccessor? = null var requests: MutableList = mutableListOf() @@ -23,6 +24,8 @@ class Plugin : JavaPlugin() { // Load configs updateConfig(description.version) users.saveDefaultConfig() + memory.saveDefaultConfig() + scripted_responses.saveDefaultConfig() if (isMultiverse()) worlds = DataConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") // Connect to Discord @@ -36,7 +39,8 @@ class Plugin : JavaPlugin() { } override fun onDisable() { - connection!!.relay("Shutting down...") + if (cfg.ANNOUNCE_SERVER_START_STOP) + connection.send(cfg.TEMPLATES_DISCORD_SERVER_STOP, connection.getRelayChannel()) // Pretend like this does anything logger.log(Level.INFO, "Attempting to cancel tasks") @@ -47,75 +51,15 @@ class Plugin : JavaPlugin() { Messaging Functions ===================================== */ - // Send a message to Discord as the bot itself - fun sendToDiscord(message: String) { + // Send a message to Discord + fun sendToDiscord(message: String, channel: MessageChannel?) { logDebug("Sending message to Discord - $message") - connection!!.relay(message) + connection.send(message, channel) } - // Send a message from a Minecraft player to Discord - fun sendToDiscord(message: String, uuid: String) { - val alias = users.data.getString("mcaliases.$uuid.discordusername") - var newMessage = message - - // This gross section converts attempted @mentions to real ones wherever possible - // Mentionable names MUST not contain spaces! - val discordusers = getDiscordUsers() - val discordaliases: MutableList> = mutableListOf() - discordusers - .filter { users.data.isSet("discordaliases.${it.second}") } - .mapTo(discordaliases) { - Pair(users.data.getString("discordaliases.${it.second}.mcusername"), - it.second) - } - for (match in Regex("""(?:^| )@(w+)""").findAll(message)) { - val found: Triple? = discordusers.firstOrNull { - it.first.replace("\\s+", "").toLowerCase() == match.value.toLowerCase() - } - if (found != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found.second}>") - - val found2: Pair? = discordaliases.firstOrNull { - it.second.toLowerCase() == match.value.toLowerCase() - } - if (found2 != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found2.first}>") - } - - if (alias != null) newMessage = message.replaceFirst(users.data.getString("mcaliases.$uuid.mcusername"), alias) - logDebug("Sending message to Discord - $newMessage") - connection!!.relay(newMessage) - } - - // TODO: this function is little different because it isn't a simple relay. Consider refactoring. - // Send a direct message to a Discord user as the bot itself - fun sendToDiscord(message: String, event: MessageReceivedEvent) { - if (event.isFromType(ChannelType.PRIVATE)) logDebug("Sending message to ${event.author.name} - $message") - else logDebug("Sending message to Discord - $message") - connection!!.respond(message, event) - } - - // Send a message to Minecraft as the bot iself + // Send a message to Minecraft fun sendToMinecraft(message: String) { - val formattedMessage = Utils.formatMessage( - cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, - mapOf( - "%u" to cfg.USERNAME_COLOR + cfg.USERNAME.replace("\\s+", "") + "&r", - "%m" to message - ), - colors = true - ) - server.broadcastMessage(formattedMessage) - } - - // Send a message to Minecraft from a Discord user - fun sendToMinecraft(message: String, username: String, id: String) { - var alias = users.data.getString("discordaliases.$id.mcusername") - if (alias == null) alias = username.replace("\\s+", "") - val formattedMessage = Utils.formatMessage( - cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, - mapOf("%u" to alias, "%m" to message), - colors = true - ) - server.broadcastMessage(formattedMessage) + server.broadcastMessage(message) } /*=========================================== @@ -126,6 +70,8 @@ class Plugin : JavaPlugin() { fun reload() { reloadConfig() users.reloadConfig() + memory.reloadConfig() + scripted_responses.reloadConfig() if (isMultiverse()) worlds!!.reloadConfig() cfg.load() //connection?.reconnect() @@ -165,17 +111,17 @@ class Plugin : JavaPlugin() { val msg = "Minecraft user '${ua.mcUsername}' has requested to become associated with your Discord" + " account. If this is you, respond '<@me> confirm'. If this is not" + " you, respond '<@me> deny'." - connection!!.tell(msg, ua.discordId) + connection.tell(msg, ua.discordId) } // Return a list of all Discord users in the specified server fun getDiscordUsers(): List> { - return connection!!.listUsers() + return connection.listUsers() } // Return a list of all Discord users in the specified server who are visibly available fun getDiscordOnline(): List> { - return connection!!.listOnline() + return connection.listOnline() } // TODO: Rename function to something more intuitive @@ -189,4 +135,81 @@ class Plugin : JavaPlugin() { users.data.set("discordaliases.${ua.discordId}.discordusername", ua.discordUsername) users.saveConfig() } + + /*====================================== + Message Formatting Functions + ===================================== */ + + // Converts attempted @mentions to real ones wherever possible + // Replaces the sender's name with their registered alias if it exists + // Mentionable names MUST NOT contain spaces! + fun convertAtMentions(message: String): String { + var newMessage = message + + val discordusers = getDiscordUsers() + val discordaliases: MutableList> = mutableListOf() + discordusers + .filter { users.data.isSet("discordaliases.${it.second}") } + .mapTo(discordaliases) { Pair(users.data.getString("discordaliases.${it.second}.mcusername"), it.second) } + for (match in Regex("""(?:^| )@(w+)""").findAll(message)) { + val found: Triple? = discordusers.firstOrNull { + it.first.replace("\\s+", "").toLowerCase() == match.value.toLowerCase() + } + if (found != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found.second}>") + + val found2: Pair? = discordaliases.firstOrNull { + it.second.toLowerCase() == match.value.toLowerCase() + } + if (found2 != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found2.first}>") + } + + return newMessage + } + + // Scans the string for occurrences of the Minecraft name matching the given UUID and attempts to translate it + // to a registered Discord name, if it exists + fun translateAliasToDiscord(message: String, uuid: String?): String { + var newMessage = message + val alias = users.data.getString("mcaliases.$uuid.discordusername") + if (alias != null) + newMessage = newMessage.replaceFirst(users.data.getString("mcaliases.$uuid.mcusername"), alias) + return newMessage + } + + fun toMinecraftChatMessage(message: String, alias: String): String { + var formattedString = cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE + formattedString = formattedString.replace("%u", alias).replace("%m", message) + formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) + return formattedString + } + + fun toDiscordChatMessage(message: String, username: String, displayName: String, worldName: String): String { + var formattedString = cfg.TEMPLATES_DISCORD_CHAT_MESSAGE + formattedString = formattedString.replace("%u", username).replace("%m", message) + .replace("%d", displayName).replace("%w", worldName) + formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) + return formattedString + } + + fun toDiscordPlayerJoin(username: String, displayName: String): String { + var formattedString = cfg.TEMPLATES_DISCORD_PLAYER_JOIN + formattedString = formattedString.replace("%u", username).replace("%d", displayName) + formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) + return formattedString + } + + fun toDiscordPlayerLeave(username: String, displayName: String): String { + var formattedString = cfg.TEMPLATES_DISCORD_PLAYER_LEAVE + formattedString = formattedString.replace("%u", username).replace("%d", displayName) + formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) + return formattedString + } + + fun toDiscordPlayerDeath(deathMessage: String, username: String, displayName: String, worldName: String): String { + var formattedString = cfg.TEMPLATES_DISCORD_PLAYER_DEATH + formattedString = formattedString.replace("%u", username).replace("%r", deathMessage) + .replace("%d", displayName).replace("%w", worldName) + formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) + return formattedString + } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt index 070b915..073e682 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt @@ -3,20 +3,4 @@ package gg.obsidian.discordbridge.Utils import org.bukkit.ChatColor fun String.noSpace() = this.replace(Regex("""\s+"""), "") -fun String.stripColor() = ChatColor.stripColor(this) - -object Utils { - - 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 - } - -} +fun String.stripColor(): String = ChatColor.stripColor(this) \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt index de99154..2f1c643 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt @@ -5,10 +5,9 @@ import net.dv8tion.jda.core.AccountType import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.JDABuilder import net.dv8tion.jda.core.OnlineStatus -import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.entities.Guild +import net.dv8tion.jda.core.entities.MessageChannel import net.dv8tion.jda.core.entities.TextChannel -import net.dv8tion.jda.core.events.message.MessageReceivedEvent class Connection(val plugin: Plugin) : Runnable { var api: JDA? = null @@ -22,24 +21,23 @@ class Connection(val plugin: Plugin) : Runnable { } catch (e: Exception) { plugin.logger.severe("Error connecting to Discord: " + e) } - } - fun relay(message: String) { + fun getRelayChannel(): TextChannel? { server = if (server == null) getServerById(plugin.cfg.SERVER_ID) else server - if (server == null) return - channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel - if (channel == null) return - - channel!!.sendMessage(message).queue() + return channel } - fun respond(message: String, event: MessageReceivedEvent) { - if (event.isFromType(ChannelType.PRIVATE)) event.privateChannel.sendMessage(message).queue() - else event.channel.sendMessage(message).queue() + fun send(message: String, toChannel: MessageChannel?) { + if (toChannel == null) { + plugin.logger.severe("Could not send message to Discord: Channel is not defined") + return + } + toChannel.sendMessage(message).queue() } + // TODO: Try to merge this into "send" fun tell(message: String, id: String) { val modifiedMsg = message.replace("<@me>", api!!.selfUser.asMention) api!!.getUserById(id).privateChannel.sendMessage(modifiedMsg).queue() @@ -50,6 +48,7 @@ class Connection(val plugin: Plugin) : Runnable { connect() } + // TODO: Unfuck this fun listUsers(): List> { channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel if (channel == null) return mutableListOf() @@ -61,6 +60,7 @@ class Connection(val plugin: Plugin) : Runnable { return listOfUsers } + // TODO: Unfuck this fun listOnline(): List> { channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel if (channel == null) return mutableListOf() @@ -83,7 +83,8 @@ class Connection(val plugin: Plugin) : Runnable { api = builder.buildBlocking() listener = Listener(plugin, api as JDA, this) api!!.addEventListener(listener) - relay("Oikos!") + if(plugin.cfg.ANNOUNCE_SERVER_START_STOP) + send(plugin.cfg.TEMPLATES_DISCORD_SERVER_START, getRelayChannel()) } private fun getServerById(id: String): Guild? { diff --git a/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt index d5ef172..93c3cad 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt @@ -5,6 +5,7 @@ import com.neovisionaries.ws.client.WebSocketException import com.neovisionaries.ws.client.WebSocketFrame import gg.obsidian.discordbridge.CommandLogic import gg.obsidian.discordbridge.Plugin +import gg.obsidian.discordbridge.Utils.noSpace import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.entities.ChannelType import net.dv8tion.jda.core.events.message.MessageReceivedEvent @@ -23,12 +24,12 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches this bot's username") return } - if (event.guild.id != plugin.cfg.SERVER_ID) { - plugin.logDebug("Not relaying message ${event.message.id} from Discord: server does not match") - return - } +// if (event.guild.id != plugin.cfg.SERVER_ID) { +// plugin.logDebug("Not relaying message ${event.message.id} from Discord: server does not match") +// return +// } - // NON-RELAY COMMANDS - the following commands and their responses are not sent to Minecraft + // NON-RELAY COMMANDS - the following commands and their responses are never sent to Minecraft if (rawmsg.startsWith(api.selfUser.asMention, true)) { val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").replaceFirst("\\s+", "") @@ -37,25 +38,26 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L plugin.logDebug("user $username wants to confirm an alias") val ua = plugin.requests.find { it.discordId == event.author.id } if (ua == null) { - plugin.sendToDiscord("You have not requested an alias, or your request has expired!", event) + plugin.sendToDiscord("You have not requested an alias, or your request has expired!", event.privateChannel) return } plugin.updateAlias(ua) plugin.requests.remove(ua) - plugin.sendToDiscord("Successfully linked aliases!", event) + plugin.sendToDiscord("Successfully linked aliases!", event.privateChannel) return } // SERVERLIST - List all players currently online on the server if (arg.startsWith("serverlist", true)) { + val channel = if (event.isFromType(ChannelType.PRIVATE)) event.privateChannel else event.channel plugin.logDebug("user $username has requested a listing of online players") val players = plugin.getOnlinePlayers() if (players.isEmpty()) { - plugin.sendToDiscord("Nobody is currently online.", event) + plugin.sendToDiscord("Nobody is currently online.", channel) return } val response = players.joinToString("\n", "The following players are currently online:\n```\n", "\n```") - plugin.sendToDiscord(response, event) + plugin.sendToDiscord(response, channel) return } } @@ -67,43 +69,43 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L plugin.logDebug("Not relaying message ${event.message.id} from Discord: channel does not match") else { plugin.logDebug("Broadcasting message ${event.message.id} from Discord to Minecraft as user $username") - plugin.sendToMinecraft(event.message.content, username, event.author.id) + var alias = plugin.users.data.getString("discordaliases.${event.author.id}.mcusername") + if (alias == null) alias = username.noSpace() + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(event.message.content, alias)) } } - // RELAY COMMANDS - These commands and their outputs DO get relayed to Minecraft + // RELAY COMMANDS - The outputs of these commands WILL be relayed to Minecraft if sent to the relay channel if (rawmsg.startsWith(api.selfUser.asMention, true)) { + val isPrivate = event.isFromType(ChannelType.PRIVATE) + val channel = if (isPrivate) event.privateChannel else event.channel val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").removePrefix(" ") if (arg.isEmpty()) return plugin.logDebug("Relay command received. Arg: $arg") - // OIKOS - Delicious Greek yogurt from Danone! - if (arg.startsWith("oikos", true)) { - plugin.logDebug("user $username has initiated oikos!") - plugin.sendToDiscord("Delicious Greek yogurt from Danone!", event) - plugin.sendToMinecraft("Delicious Greek yogurt from Danone!") - return - } - if (arg.startsWith("delicious greek yogurt from danone", true)) { - plugin.logDebug("user $username has initiated oikos 2!") - plugin.sendToDiscord("\uD83D\uDE04", event) - plugin.sendToMinecraft(":D") - return + // SCRIPTED RESPONSE - The bot replies with a preprogrammed response if it detects a corresponding trigger string + val responses = plugin.scripted_responses.data.getConfigurationSection("responses").getKeys(false) + var scripted_response: String? = null + for (r in responses) { + val casesensitive = plugin.scripted_responses.data.getBoolean("responses.$r.casesensitive", false) + if (arg.startsWith(plugin.scripted_responses.data.getString("responses.$r.trigger").toLowerCase(), !casesensitive)) + scripted_response = plugin.scripted_responses.data.getString("responses.$r.response") } - if (arg == "\uD83D\uDE04") { - plugin.logDebug("user $username has initiated oikos 3!") - plugin.sendToDiscord("\uD83D\uDE04", event) - plugin.sendToMinecraft(":D") + if (scripted_response != null) { + plugin.logDebug("user $username has triggered the scripted response: $scripted_response") + plugin.sendToDiscord(scripted_response, channel) + if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.cfg.CHANNEL, true)) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(scripted_response, plugin.cfg.BOT_MC_USERNAME)) return } // CLEVERBOT - Assume anything else invokes Cleverbot plugin.logDebug("user $username asks CleverBot something") val response = CommandLogic.askCleverbot(plugin.cfg.CLEVERBOT_KEY, arg) - plugin.sendToDiscord(response, event) + plugin.sendToDiscord(response, channel) // if this occurs in the relay channel, relay the response if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.cfg.CHANNEL, true)) - plugin.sendToMinecraft(response) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(response, plugin.cfg.BOT_MC_USERNAME)) return } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt index 2af5039..9dc8f1f 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt @@ -1,6 +1,7 @@ package gg.obsidian.discordbridge.minecraft.commands -import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.CommandLogic.askCleverbot +import gg.obsidian.discordbridge.Permissions import gg.obsidian.discordbridge.Plugin import gg.obsidian.discordbridge.Utils.* import org.bukkit.command.Command @@ -13,21 +14,12 @@ class Marina(val plugin: Plugin) : CommandExecutor { override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { if (args == null || args.isEmpty()) return false - if (player is Player) { + if (player is Player && Permissions.cleverbot.has(player)) { val message: String = args.joinToString(" ") player.chat("@${plugin.cfg.USERNAME.noSpace()} $message") - val response: String = CommandLogic.askCleverbot(plugin.cfg.CLEVERBOT_KEY, message) - val formattedMessage = Utils.formatMessage( - plugin.cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, - mapOf( - "%u" to plugin.cfg.USERNAME_COLOR - + plugin.cfg.USERNAME.noSpace() + "&r", - "%m" to response - ), - colors = true - ) - plugin.server.broadcastMessage(formattedMessage) - plugin.sendToDiscord(response) + val response = askCleverbot(plugin.cfg.CLEVERBOT_KEY, message) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(response, plugin.cfg.BOT_MC_USERNAME)) + plugin.sendToDiscord(response, plugin.connection.getRelayChannel()) return true } return true diff --git a/src/main/resources/botmemory.yml b/src/main/resources/botmemory.yml new file mode 100644 index 0000000..4399866 --- /dev/null +++ b/src/main/resources/botmemory.yml @@ -0,0 +1 @@ +respects: 0 \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 5f37b58..89e0b8b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -7,6 +7,7 @@ settings: cleverbot-key: '' debug: false relay_cancelled_messages: true + announce_server_start_stop: true messages: chat: true join: true @@ -23,5 +24,7 @@ settings: player_join: '%u joined the server' player_leave: '%u left the server' player_death: '%r' + server_start: 'Server started!' + server_stop: 'Shutting down...' minecraft: chat_message: '<%u&b(discord)&r> %m' diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2bef185..a51bcf1 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -14,6 +14,9 @@ permissions: discordbridge.reload: description: Reload the Discord Bridge default: op + discordbridge.cleverbot: + description: Determines whether this user can talk to Cleverbot + default: true commands: discord: diff --git a/src/main/resources/scriptedresponses.yml b/src/main/resources/scriptedresponses.yml new file mode 100644 index 0000000..d4a9397 --- /dev/null +++ b/src/main/resources/scriptedresponses.yml @@ -0,0 +1,5 @@ +responses: + '1': + casesensitive: false + response: 'Hello World!' + trigger: 'hello world!' \ No newline at end of file From 85b6e3ea666caf3714ebef044ba01b7103e470fc Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Fri, 3 Mar 2017 01:29:50 -0600 Subject: [PATCH 15/24] 1.7.6 Added F Added 8ball Added Insult Added Rate Restructured a lot of the code to embrace more code resuse --- README.md | 123 +++-- build.gradle | 2 +- .../gg/obsidian/discordbridge/CommandLogic.kt | 82 ++- .../obsidian/discordbridge/Configuration.kt | 52 +- .../discordbridge/DataConfigAccessor.kt | 20 +- .../obsidian/discordbridge/EventListener.kt | 103 ++-- .../gg/obsidian/discordbridge/Permissions.kt | 6 +- .../gg/obsidian/discordbridge/Plugin.kt | 193 ++++--- .../gg/obsidian/discordbridge/UserAlias.kt | 4 - .../gg/obsidian/discordbridge/Utils/Utils.kt | 5 +- .../discordbridge/discord/Connection.kt | 54 +- .../discordbridge/discord/Listener.kt | 104 +++- .../minecraft/commands/Discord.kt | 94 +--- .../minecraft/commands/EightBall.kt | 24 + .../discordbridge/minecraft/commands/F.kt | 24 + .../minecraft/commands/Insult.kt | 23 + .../minecraft/commands/Marina.kt | 27 - .../discordbridge/minecraft/commands/Rate.kt | 23 + src/main/resources/botmemory.yml | 30 +- src/main/resources/config.yml | 89 ++-- src/main/resources/insults.yml | 504 ++++++++++++++++++ src/main/resources/plugin.yml | 29 +- src/main/resources/scriptedresponses.yml | 5 - 23 files changed, 1205 insertions(+), 415 deletions(-) delete mode 100644 src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/EightBall.kt create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/F.kt create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Insult.kt delete mode 100644 src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt create mode 100644 src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Rate.kt create mode 100644 src/main/resources/insults.yml delete mode 100644 src/main/resources/scriptedresponses.yml diff --git a/README.md b/README.md index 46e3711..c8e864a 100644 --- a/README.md +++ b/README.md @@ -13,48 +13,77 @@ Bridges chat between Discord and Minecraft (Bukkit/Spigot). ## Configuration -DiscordBridge has several options that can be configured in the `data.yml` file: +DiscordBridge has several options that can be configured in the `config.yml` file: ```yaml -settings: - server-id: '00000000' - channel: 'test' - username: 'username' - username-color: '' - token: 'token' - cleverbot-key: '' - debug: false - relay_cancelled_messages: true - announce_server_start_stop: true - messages: - chat: true - join: true - leave: true - death: false - if_vanished: - chat: false - join: false - leave: false - death: false - templates: - discord: - chat_message: '<%u> %m' - player_join: '%u joined the server' - player_leave: '%u left the server' - player_death: '%r' - server_start: 'Server started!' - server_stop: 'Shutting down...' - minecraft: - chat_message: '<%u&b(discord)&r> %m' +# DiscordBridge Config + +# The bot's Discord API access token +token: '' + +# These values will control which channel the bot will watch to relay messages to and from the server. +server-id: '00000000' +channel: 'channel-name' + +# The bot's Discord username +username: 'DiscordBridge' + +# (Optional) Apply formatting codes to the bot's name in Minecraft chat. +# Use '&' in place of the formatting symbol. +username-color: '' + +# (Optional) Set this value with a valid Cleverbot API key to enable chatting with Cleverbot +# Look at https://www.cleverbot.com/api/ for more info +cleverbot-key: '' + +# If true, prints verbose log messages to the server console for every action +debug: false + +# If true, Minecraft chat events that are cancelled will still be relayed to Discord. +relay-cancelled-messages: true + +# Controls which events in general are relayed to Discord +messages: + player-chat: true + player-join: true + player-leave: true + player-death: false + server-start: true + server-stop: true + +# Controls which events caused by vanished players are relayed to Discord +# NOTE: If set to true, it will only have effect if the corresponding message above is also set to true +if-vanished: + player-chat: false + player-join: false + player-leave: false + player-death: false + +# Set the templates for relayed messages +# %u - The sender's username +# %m - The sender's message +# %w - The name of the world the sender is in (Multiverse alias compatible) +# %r - The death message (Death event only) +# Use '&' in place of the formatting symbol to apply formatting codes. +templates: + discord: + chat-message: '<**%u**> %m' + player-join: '**%u** joined the server' + player-leave: '**%u** left the server' + player-death: '%r' + server-start: 'Server started!' + server-stop: 'Shutting down...' + minecraft: + chat-message: '[&b&lDiscord&r]<%u> %m' ``` -* `server-id` is the ID of your Discord server. This can be found under *Server Settings > Widget > Server ID* +* `token` is the access token for the Discord bot. Without this, the bot will not function at all. +* `server-id` is the ID of the Discord server with the channel you want to bridge. This can be found under *Server Settings > Widget > Server ID* * `channel` is the Discord channel name you would like to bridge with your Minecraft server -* `username` is the Discord username of your bot user +* `username` is the Discord username of your bot * `username_color` is for adding formatting codes to the name of your bot when it speaks in Minecraft's chat (optional) -* `token` is the access token for the Discord bot -* `cleverbot-key` is the access key necessary to chat with Cleverbot's API (optional) -* `debug` enables more verbose logging +* `cleverbot-key` (optional) an access key necessary to chat with Cleverbot's API +* `debug` enables verbose logging * `relay_cancelled_messages` will relay chat messages even if they are cancelled * `messages` enables or disables certain kinds of messages * `if_vanished` enables or disables messages if the user is vanished (applies after `messages`) @@ -66,7 +95,7 @@ settings: - `%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 +- `%r` will be replaced with Minecraft's standard death message - Color codes, prefixed with `&`, will be translated on the Minecraft end ## Features @@ -74,19 +103,26 @@ settings: * 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) * You can link Minecraft accounts to Discord accounts and the bot will translate display names to match where the message appears -* Join / leave messages are sent to Discord -* Death messages are sent to Discord -* Server start and stop messages are sent to Discord +* Join / leave messages can be sent to Discord +* Death messages can be sent to Discord +* Server start and stop messages can be sent to Discord * All of the above messages can be toggled if you don't want them to appear * Message templates are customized * Prefixing usernames with `@` in the Minecraft chat will be converted to mentions in the Discord chat if the user exists (you can use their Discord display name with spaces removed, or their Minecraft username if the accounts are linked) * Image attachments sent in the Discord channel will relay their URLs to Minecraft * Add scripted responses for the bot to say when it detects a trigger phrase -* Cleverbot integration - chat with the bot on Discord using `@` or chat with the bot in Minecraft using a slash command +* A handful of fun and shitposting commands for the full Discord Bot experience both in and out of game +* Cleverbot integration - chat with the bot using `@`. Works in Discord AND Minecraft! +* The bot can use any of its commands in any channel it can read (including DMs!) allowing it to function as a general-purpose Discord bot on the side ## Permissions +- `discordbridge.cleverbot` - ability to talk to Cleverbot +- `discordbridge.f` - ability to pay respects with /f - `discordbridge.reload` - ability to reload data and reconnect the Discord connection +- `discordbridge.eightball` - ability to consult the Magic 8-Ball with /8ball +- `discordbridge.rate` - ability to ask for an out-of-10 rating with /rate +- `discordbridge.insult` - ability to make the bot insult something with /insult ## Commands @@ -94,7 +130,10 @@ settings: - `/discord get online` - provides a list of all Discord users in the relay channel who are Online, Do Not Disturb, and Idle - `/discord get ids` - provides a list of the Discord IDs of all users in the relay channel, which is useful for... - `/discord register ` - this command will send a DM to the corresponding user asking if that user wishes to link their Discord account with the Minecraft user who issued the command -- `/marina ` - Talk to Cleverbot! Anything you say after this command is relayed to Cleverbot's API, and the bot will speak the response. Only works if you specify a Cleverbot API key in the config. +- `/f` - press F to pay respects +- `/8ball ` - consults the Magic 8-Ball to answer your yes/no questions (messages configurable in `botmemory.yml`) +- `/rate ` - asks the bot to rate something on a 0-10 scale +- `/insult ` - makes the bot insult something (messages configurable in `insults.yml`) (*WARNING: The supplied insults are quite offensive! Remove permissions for this command or replace the insults if you intend to use this bot on cleaner servers!*) ## Upcoming Features diff --git a/build.gradle b/build.gradle index 34f674f..f1757af 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ plugins { apply plugin: 'kotlin' group = 'gg.obsidian' -version = '1.7.5' +version = '1.7.6' description = """Bridge chat between Minecraft and Discord""" ext.url = 'https://github.com/the-obsidian/DiscordBridge' diff --git a/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt b/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt index 1056908..8fe7815 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt @@ -2,13 +2,13 @@ package gg.obsidian.discordbridge import com.michaelwflaherty.cleverbotapi.CleverBotQuery import java.io.IOException +import java.util.* object CommandLogic { - fun askCleverbot(key: String, message: String): String { - - if (key.isEmpty()) return "You do not have an API key. Go to https://www.cleverbot.com/api/ for more information." - val bot: CleverBotQuery = CleverBotQuery(key, message) + fun askCleverbot(plugin: Plugin, message: String): String { + if (plugin.cfg.CLEVERBOT_KEY.isEmpty()) return "You do not have an API key. Go to https://www.cleverbot.com/api/ for more information." + val bot: CleverBotQuery = CleverBotQuery(plugin.cfg.CLEVERBOT_KEY, message) var response: String try { bot.sendRequest() @@ -20,4 +20,78 @@ object CommandLogic { return response } + fun eightBall(plugin: Plugin, name: String): String { + val responses = plugin.memory.data.getStringList("8-ball-responses") + val rand = Random().nextInt(responses.count()) + return "$name - ${responses[rand]}" + } + + fun f(plugin: Plugin, name: String): String { + var respects = plugin.memory.data.getInt("respects", 0) + val msg: String + val payed = Random().nextInt(100) + when { + payed == 99 -> { + respects += 5 + msg = "$name breaks down and mournfully cries 5 respects out to the sky! (Total respects paid: $respects)" + } + payed > 95 -> { + respects += 3 + msg = "$name manages to give 3 respects through their heavy sobbing! (Total respects paid: $respects)" + } + payed > 65 -> { + respects += 2 + msg = "$name sheds a single tear and pays 2 respects! (Total respects paid: $respects)" + } + else -> { + respects += 1 + msg = "$name solemnly pays respect! (Total respects paid: $respects)" + } + } + plugin.memory.data.set("respects", respects) + plugin.memory.saveConfig() + + return msg + } + + fun insult(plugin: Plugin, arg: String): String { + val responses = plugin.insults.data.getStringList("insults") + val rand = Random().nextInt(500) + return "$arg - ${responses[rand]}" + } + + fun rate(name: String, arg: String): String { + val argArray = arg.split(" ").toMutableList() + val iterate = argArray.listIterator() + while (iterate.hasNext()) { + val oldValue = iterate.next() + if (oldValue == "me") iterate.set("you") + if (oldValue == "myself") iterate.set("yourself") + if (oldValue == "my") iterate.set("your") + if (oldValue == "your") iterate.set("my") + if (oldValue == "yourself") iterate.set("myself") + } + val thingToBeRated = argArray.joinToString(" ") + val rating = Random().nextInt(101) + var response = "I rate $thingToBeRated " + when (rating) { + 42 -> response += "4/20: 'smoke weed erryday'." + 78 -> response += "7.8/10: 'too much water'." + 69 -> response += "69/69." + 100 -> response += "a perfect 10/10, absolutely flawless in every regard." + in 90..99 -> response += "a very high ${rating/10f}/10." + in 80..89 -> response += "a high ${rating/10f}/10." + in 70..79 -> response += "a decently high ${rating/10f}/10." + in 60..69 -> response += "a good ${rating/10f}/10." + in 50..59 -> response += "a solid ${rating/10f}/10." + in 40..49 -> response += "a meh ${rating/10f}/10." + in 30..39 -> response += "a paltry ${rating/10f}/10." + in 20..29 -> response += "a pretty low ${rating/10f}/10." + in 10..19 -> response += "a low ${rating/10f}/10." + in 1..9 -> response += "a shitty ${rating/10f}/10." + else -> response += "0/0: 'amazing'." + } + return "$name - $response" + } + } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt index 9c4c212..8c100ad 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Configuration.kt @@ -8,8 +8,6 @@ class Configuration(val plugin: Plugin) { var CHANNEL: String = "" var USERNAME: String = "" var USERNAME_COLOR: String = "" - var EMAIL: String = "" - var PASSWORD: String = "" var TOKEN: String = "" var CLEVERBOT_KEY: String = "" var DEBUG: Boolean = false @@ -45,36 +43,34 @@ class Configuration(val plugin: Plugin) { fun load() { plugin.reloadConfig() - SERVER_ID = plugin.config.getString("settings.server-id") - CHANNEL = plugin.config.getString("settings.channel") - USERNAME = plugin.config.getString("settings.username") - USERNAME_COLOR = plugin.config.getString("settings.username-color") - EMAIL = plugin.config.getString("settings.email", "") - PASSWORD = plugin.config.getString("settings.password", "") - TOKEN = plugin.config.getString("settings.token", "") - CLEVERBOT_KEY = plugin.config.getString("settings.cleverbot-key", "") - DEBUG = plugin.config.getBoolean("settings.debug", false) - RELAY_CANCELLED_MESSAGES = plugin.config.getBoolean("settings.relay_cancelled_messages", true) - ANNOUNCE_SERVER_START_STOP = plugin.config.getBoolean("settings.announce_server_start_stop", true) + SERVER_ID = plugin.config.getString("server-id") + CHANNEL = plugin.config.getString("channel") + USERNAME = plugin.config.getString("username") + USERNAME_COLOR = plugin.config.getString("username-color") + TOKEN = plugin.config.getString("token", "") + CLEVERBOT_KEY = plugin.config.getString("cleverbot-key", "") + DEBUG = plugin.config.getBoolean("debug", false) + RELAY_CANCELLED_MESSAGES = plugin.config.getBoolean("relay-cancelled-messages", true) + ANNOUNCE_SERVER_START_STOP = plugin.config.getBoolean("announce-server-start-stop", true) - MESSAGES_CHAT = plugin.config.getBoolean("settings.messages.chat", 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) + MESSAGES_CHAT = plugin.config.getBoolean("messages.chat", true) + MESSAGES_JOIN = plugin.config.getBoolean("messages.join", true) + MESSAGES_LEAVE = plugin.config.getBoolean("messages.leave", true) + MESSAGES_DEATH = plugin.config.getBoolean("messages.death", false) - IF_VANISHED_CHAT = plugin.config.getBoolean("settings.if_vanished.chat", false) - IF_VANISHED_JOIN = plugin.config.getBoolean("settings.if_vanished.join", false) - IF_VANISHED_LEAVE = plugin.config.getBoolean("settings.if_vanished.leave", false) - IF_VANISHED_DEATH = plugin.config.getBoolean("settings.if_vanished.death", false) + IF_VANISHED_CHAT = plugin.config.getBoolean("if-vanished.chat", false) + IF_VANISHED_JOIN = plugin.config.getBoolean("if-vanished.join", false) + IF_VANISHED_LEAVE = plugin.config.getBoolean("if-vanished.leave", false) + IF_VANISHED_DEATH = plugin.config.getBoolean("if-vanished.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_DISCORD_SERVER_START = plugin.config.getString("settings.templates.discord.server_start", "Server started!") - TEMPLATES_DISCORD_SERVER_STOP = plugin.config.getString("settings.templates.discord.server_stop", "Shutting down...") + TEMPLATES_DISCORD_CHAT_MESSAGE = plugin.config.getString("templates.discord.chat-message", "<**%u**> %m") + TEMPLATES_DISCORD_PLAYER_JOIN = plugin.config.getString("templates.discord.player-join", "**%u** joined the server") + TEMPLATES_DISCORD_PLAYER_LEAVE = plugin.config.getString("templates.discord.player-leave", "**%u** left the server") + TEMPLATES_DISCORD_PLAYER_DEATH = plugin.config.getString("templates.discord.player-death", "%r") + TEMPLATES_DISCORD_SERVER_START = plugin.config.getString("templates.discord.server-start", "Server started!") + TEMPLATES_DISCORD_SERVER_STOP = plugin.config.getString("templates.discord.server_stop", "Shutting down...") - TEMPLATES_MINECRAFT_CHAT_MESSAGE = plugin.config.getString("settings.templates.minecraft.chat_message", "<%u&b(discord)&r> %m") + TEMPLATES_MINECRAFT_CHAT_MESSAGE = plugin.config.getString("templates.minecraft.chat-message", "[&b&lDiscord&r]<%u> %m") BOT_MC_USERNAME = USERNAME_COLOR + USERNAME.noSpace() + "&r" } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt index 7a6bcc4..446c835 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt @@ -15,7 +15,7 @@ package gg.obsidian.discordbridge * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +* FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE @@ -41,8 +41,9 @@ class DataConfigAccessor(private val plugin: JavaPlugin, filepath: File, private } fun reloadConfig() { - try { fileConfiguration = YamlConfiguration.loadConfiguration(configFile) } - catch (e: IllegalArgumentException) { + try { + fileConfiguration = YamlConfiguration.loadConfiguration(configFile) + } catch (e: IllegalArgumentException) { // Look for defaults in the jar if (plugin.getResource(fileName) == null) plugin.logger.log(Level.SEVERE, "usernames.yml cannot be found for some reason") @@ -54,30 +55,27 @@ class DataConfigAccessor(private val plugin: JavaPlugin, filepath: File, private val data: FileConfiguration get() { - if (fileConfiguration == null) { + if (fileConfiguration == null) this.reloadConfig() - } return fileConfiguration!! } fun saveConfig() { - if (fileConfiguration == null || configFile == null) { + if (fileConfiguration == null || configFile == null) return - } else { + else { try { data.save(configFile) } catch (ex: IOException) { - plugin.logger.log(Level.SEVERE, "Could not save data to " + configFile, ex) + plugin.logger.log(Level.SEVERE, "Could not save data to $configFile", ex) } - } } @Suppress("unused") fun saveDefaultConfig() { - if (!configFile!!.exists()) { + if (!configFile!!.exists()) this.plugin.saveResource(fileName, false) - } } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt index 2ea613c..7cd1417 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/EventListener.kt @@ -8,27 +8,28 @@ import org.bukkit.event.entity.PlayerDeathEvent import org.bukkit.event.player.AsyncPlayerChatEvent import org.bukkit.event.player.PlayerJoinEvent import org.bukkit.event.player.PlayerQuitEvent +import org.bukkit.ChatColor as CC import java.lang.reflect.Method 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}") - + val player = event.player + val username = event.player.name.stripColor() + val displayName = event.player.displayName.stripColor() + val uuid = event.player.uniqueId.toString() + val message = event.message.stripColor() + var worldname = event.player.world.name + plugin.logDebug("Received a chat event from $username: $message") if (!plugin.cfg.MESSAGES_CHAT) return if (event.isCancelled && !plugin.cfg.RELAY_CANCELLED_MESSAGES) return - - // Check for vanished - val player = event.player - if (player.hasMetadata("vanished") && - player.getMetadata("vanished")[0].asBoolean() && + if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && !plugin.cfg.IF_VANISHED_CHAT) return - val username = event.player.name.stripColor() - var worldname = player.world.name - if (plugin.isMultiverse()) { + // Get world alias if Multiverse is installed + if (plugin.foundMultiverse) { val worldProperties = plugin.worlds!!.data.get("worlds.$worldname") val cls = Class.forName("com.onarandombox.MultiverseCore.WorldProperties") val meth: Method = cls.getMethod("getAlias") @@ -36,67 +37,75 @@ class EventListener(val plugin: Plugin) : Listener { if (alias is String) worldname = alias } - var formattedMessage = plugin.toDiscordChatMessage(event.message.stripColor(), username, player.displayName.stripColor(), worldname) + var formattedMessage = plugin.toDiscordChatMessage(message, username, displayName, worldname) formattedMessage = plugin.convertAtMentions(formattedMessage) - formattedMessage = plugin.translateAliasToDiscord(formattedMessage, event.player.uniqueId.toString()) - - plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, uuid) + plugin.sendToDiscord(formattedMessage, plugin.conn.getRelayChannel()) + + // If it was a @mention to the bot, treat it as a Cleverbot invocation + if (message.startsWith("@" + plugin.cfg.USERNAME.noSpace())) { + val task = Runnable { + if (Permissions.cleverbot.has(player)) { + val arg: String = message.removePrefix("@" + plugin.cfg.USERNAME.noSpace()).trimStart() + val response = CommandLogic.askCleverbot(plugin, arg) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(response, plugin.cfg.BOT_MC_USERNAME)) + plugin.sendToDiscord(response, plugin.conn.getRelayChannel()) + } else + player.sendMessage("${CC.RED}You do not have permission to talk to the bot.") + } + plugin.server.scheduler.runTaskAsynchronously(plugin, task) + } } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun onPlayerJoin(event: PlayerJoinEvent) { - if (!plugin.cfg.MESSAGES_JOIN) return - - // Check for vanished val player = event.player - if (player.hasMetadata("vanished") && - player.getMetadata("vanished")[0].asBoolean() && - !plugin.cfg.IF_VANISHED_JOIN) return + val username = event.player.name.stripColor() + val displayName = event.player.displayName.stripColor() + val uuid = event.player.uniqueId.toString() - val username = player.name.stripColor() plugin.logDebug("Received a join event for $username") + if (!plugin.cfg.MESSAGES_JOIN) return + if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && + !plugin.cfg.IF_VANISHED_JOIN) return - var formattedMessage = plugin.toDiscordPlayerJoin(username, player.displayName.stripColor()) - formattedMessage = plugin.translateAliasToDiscord(formattedMessage, event.player.uniqueId.toString()) - - plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) + var formattedMessage = plugin.toDiscordPlayerJoin(username, displayName) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, uuid) + plugin.sendToDiscord(formattedMessage, plugin.conn.getRelayChannel()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun onPlayerQuit(event: PlayerQuitEvent) { - if (!plugin.cfg.MESSAGES_LEAVE) return - - // Check for vanished val player = event.player - if (player.hasMetadata("vanished") && - player.getMetadata("vanished")[0].asBoolean() && - !plugin.cfg.IF_VANISHED_LEAVE) return - val username = event.player.name.stripColor() - plugin.logDebug("Received a leave event for $username") + val displayName = event.player.displayName.stripColor() + val uuid = event.player.uniqueId.toString() - var formattedMessage = plugin.toDiscordPlayerLeave(username, event.player.displayName.stripColor()) - formattedMessage = plugin.translateAliasToDiscord(formattedMessage, event.player.uniqueId.toString()) + plugin.logDebug("Received a leave event for $username") + if (!plugin.cfg.MESSAGES_LEAVE) return + if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && + !plugin.cfg.IF_VANISHED_LEAVE) return - plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) + var formattedMessage = plugin.toDiscordPlayerLeave(username, displayName) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, uuid) + plugin.sendToDiscord(formattedMessage, plugin.conn.getRelayChannel()) } @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) fun onPlayerDeath(event: PlayerDeathEvent) { - if (!plugin.cfg.MESSAGES_DEATH) return - - // Check for vanished val player = event.entity - if (player.hasMetadata("vanished") && - player.getMetadata("vanished")[0].asBoolean() && - !plugin.cfg.IF_VANISHED_DEATH) return - val username = event.entity.name.stripColor() - plugin.logDebug("Received a death event for $username") + val displayName = event.entity.displayName.stripColor() + val uuid = event.entity.uniqueId.toString() + val deathMessage = event.deathMessage - var formattedMessage = plugin.toDiscordPlayerDeath(event.deathMessage, username, event.entity.displayName.stripColor(), event.entity.world.name) - formattedMessage = plugin.translateAliasToDiscord(formattedMessage, player.uniqueId.toString()) + plugin.logDebug("Received a death event for $username") + if (!plugin.cfg.MESSAGES_DEATH) return + if (player.hasMetadata("vanished") && player.getMetadata("vanished")[0].asBoolean() && + !plugin.cfg.IF_VANISHED_DEATH) return - plugin.sendToDiscord(formattedMessage, plugin.connection.getRelayChannel()) + var formattedMessage = plugin.toDiscordPlayerDeath(deathMessage, username, displayName) + formattedMessage = plugin.translateAliasToDiscord(formattedMessage, uuid) + plugin.sendToDiscord(formattedMessage, plugin.conn.getRelayChannel()) } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt b/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt index e0fee5c..90c98ba 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Permissions.kt @@ -4,7 +4,11 @@ import org.bukkit.entity.Player enum class Permissions(val node: String) { reload("discordbridge.reload"), - cleverbot("discordbridge.cleverbot"); + cleverbot("discordbridge.cleverbot"), + f("discordbridge.f"), + eightball("discordbridge.eightball"), + insult("discordbridge.insult"), + rate("discordbridge.rate"); 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 e5d77d6..2026565 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,46 +1,61 @@ package gg.obsidian.discordbridge +import gg.obsidian.discordbridge.Utils.noSpace +import gg.obsidian.discordbridge.Utils.UserAlias import gg.obsidian.discordbridge.discord.Connection -import gg.obsidian.discordbridge.minecraft.commands.Discord -import gg.obsidian.discordbridge.minecraft.commands.Marina +import gg.obsidian.discordbridge.minecraft.commands.* import net.dv8tion.jda.core.OnlineStatus +import net.dv8tion.jda.core.entities.Member import net.dv8tion.jda.core.entities.MessageChannel -import org.bukkit.ChatColor +import org.bukkit.ChatColor as CC +import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin import java.util.logging.Level import java.io.File class Plugin : JavaPlugin() { + // Connection to Discord + lateinit var conn: Connection + + // Configs val cfg = Configuration(this) - lateinit var connection: Connection var users: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "usernames.yml") var memory: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "botmemory.yml") - var scripted_responses: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "scriptedresponses.yml") + var insults: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "insults.yml") var worlds: DataConfigAccessor? = null + + // Temporary storage for alias linking requests var requests: MutableList = mutableListOf() + // Detects if Multiverse-Core is installed + val foundMultiverse: Boolean + get() = server.pluginManager.getPlugin("Multiverse-Core") != null + override fun onEnable() { // Load configs updateConfig(description.version) users.saveDefaultConfig() memory.saveDefaultConfig() - scripted_responses.saveDefaultConfig() - if (isMultiverse()) worlds = DataConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") + insults.saveDefaultConfig() + if (foundMultiverse) worlds = DataConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") // Connect to Discord - this.connection = Connection(this) - server.scheduler.runTaskAsynchronously(this, connection) + this.conn = Connection(this) + server.scheduler.runTaskAsynchronously(this, conn) server.pluginManager.registerEvents(EventListener(this), this) // Register commands getCommand("discord").executor = Discord(this) - getCommand("marina").executor = Marina(this) + getCommand("f").executor = F(this) + getCommand("rate").executor = Rate(this) + getCommand("8ball").executor = EightBall(this) + getCommand("insult").executor = Insult(this) } override fun onDisable() { if (cfg.ANNOUNCE_SERVER_START_STOP) - connection.send(cfg.TEMPLATES_DISCORD_SERVER_STOP, connection.getRelayChannel()) + conn.send(cfg.TEMPLATES_DISCORD_SERVER_STOP, conn.getRelayChannel()) // Pretend like this does anything logger.log(Level.INFO, "Attempting to cancel tasks") @@ -54,7 +69,7 @@ class Plugin : JavaPlugin() { // Send a message to Discord fun sendToDiscord(message: String, channel: MessageChannel?) { logDebug("Sending message to Discord - $message") - connection.send(message, channel) + conn.send(message, channel) } // Send a message to Minecraft @@ -71,10 +86,10 @@ class Plugin : JavaPlugin() { reloadConfig() users.reloadConfig() memory.reloadConfig() - scripted_responses.reloadConfig() - if (isMultiverse()) worlds!!.reloadConfig() + insults.reloadConfig() + if (foundMultiverse) worlds!!.reloadConfig() cfg.load() - //connection?.reconnect() + //conn?.reconnect() } // Save default config @@ -92,11 +107,6 @@ class Plugin : JavaPlugin() { logger.info(msg) } - // Shorthand function to check if Multiverse is installed - fun isMultiverse(): Boolean { - return server.pluginManager.getPlugin("Multiverse-Core") != null - } - // Get a list of usernames of players who are online fun getOnlinePlayers(): List { val names: MutableList = mutableListOf() @@ -106,27 +116,69 @@ class Plugin : JavaPlugin() { } // Open a request to link a Minecraft user with a Discord user - fun registerUserRequest(ua: UserAlias) { + fun registerUserRequest(player: Player, id: String): Boolean { + val users = conn.listUsers() + val found: Member = users.find { it.user.id == id } ?: return false + + val ua: UserAlias = UserAlias(player.name, player.uniqueId.toString(), found.effectiveName, found.user.id) requests.add(ua) val msg = "Minecraft user '${ua.mcUsername}' has requested to become associated with your Discord" + - " account. If this is you, respond '<@me> confirm'. If this is not" + - " you, respond '<@me> deny'." - connection.tell(msg, ua.discordId) + " account. If this is you, respond '${conn.api!!.selfUser.asMention} confirm'. If this is not" + + " you, respond ${conn.api!!.selfUser.asMention} deny'." + conn.send(msg, conn.api!!.getUserById(ua.discordId).privateChannel) + return true } - // Return a list of all Discord users in the specified server - fun getDiscordUsers(): List> { - return connection.listUsers() + // Return a formatted string listing the Discord IDs of all Discord users in the relay channel + fun getDiscordIds(): String { + val users = conn.listUsers() + + if (users.isEmpty()) + return "${CC.YELLOW}No Discord members could be found. Either server is empty or an error has occurred." + + var response = "${CC.YELLOW}Discord users:" + for (user in users) { + if (user.user.isBot) response += "\n${CC.GOLD}- ${user.effectiveName} (Bot), ${user.user.id}${CC.RESET}" + else response += "\n${CC.YELLOW}- ${user.effectiveName}, ${user.user.id}${CC.RESET}" + } + return response } - // Return a list of all Discord users in the specified server who are visibly available - fun getDiscordOnline(): List> { - return connection.listOnline() + // Return a formatted string listing all Discord users in the relay channel who are visibly available + fun getDiscordOnline(): String { + val onlineUsers = conn.listOnline() + if (onlineUsers.isEmpty()) + return "${CC.YELLOW}No Discord members could be found. Either server is empty or an error has occurred." + + var response = "" + if (onlineUsers.filter { it.onlineStatus == OnlineStatus.ONLINE }.isNotEmpty()) { + response += "\n${CC.DARK_GREEN}Online:${CC.RESET}" + for (user in onlineUsers.filter { it.onlineStatus == OnlineStatus.ONLINE }) { + if (user.user.isBot) response += "\n${CC.DARK_GREEN}- ${user.effectiveName} (Bot)${CC.RESET}" + else response += "\n${CC.DARK_GREEN}- ${user.effectiveName}${CC.RESET}" + } + } + if (onlineUsers.filter { it.onlineStatus == OnlineStatus.IDLE }.isNotEmpty()) { + response += "\n${CC.YELLOW}Idle:${CC.RESET}" + for (user in onlineUsers.filter { it.onlineStatus == OnlineStatus.IDLE }) { + if (user.user.isBot) response += "\n${CC.YELLOW}- ${user.effectiveName} (Bot)${CC.RESET}" + else response += "\n${CC.YELLOW}- ${user.effectiveName}${CC.RESET}" + } + } + if (onlineUsers.filter { it.onlineStatus == OnlineStatus.DO_NOT_DISTURB }.isNotEmpty()) { + response += "\n${CC.RED}Do Not Disturb:${CC.RESET}" + for (user in onlineUsers.filter { it.onlineStatus == OnlineStatus.DO_NOT_DISTURB }) { + if (user.user.isBot) response += "\n${CC.RED}- ${user.effectiveName} (Bot)${CC.RESET}" + else response += "\n&${CC.RED} ${user.effectiveName}${CC.RESET}" + } + } + + response.replaceFirst("\n", "") + return response } - // TODO: Rename function to something more intuitive // Add an alias to the Users data - fun updateAlias(ua: UserAlias) { + fun saveAlias(ua: UserAlias) { users.data.set("mcaliases.${ua.mcUuid}.mcusername", ua.mcUsername) users.data.set("mcaliases.${ua.mcUuid}.discordusername", ua.discordUsername) users.data.set("mcaliases.${ua.mcUuid}.discordid", ua.discordId) @@ -141,75 +193,76 @@ class Plugin : JavaPlugin() { ===================================== */ // Converts attempted @mentions to real ones wherever possible - // Replaces the sender's name with their registered alias if it exists // Mentionable names MUST NOT contain spaces! fun convertAtMentions(message: String): String { var newMessage = message - val discordusers = getDiscordUsers() - val discordaliases: MutableList> = mutableListOf() + val discordusers = conn.listUsers() + val discordaliases: MutableList> = mutableListOf() discordusers - .filter { users.data.isSet("discordaliases.${it.second}") } - .mapTo(discordaliases) { Pair(users.data.getString("discordaliases.${it.second}.mcusername"), it.second) } - for (match in Regex("""(?:^| )@(w+)""").findAll(message)) { - val found: Triple? = discordusers.firstOrNull { - it.first.replace("\\s+", "").toLowerCase() == match.value.toLowerCase() + .filter { users.data.isSet("discordaliases.${it.user.id}") } + .mapTo(discordaliases) { Pair(users.data.getString("discordaliases.${it.user.id}.mcusername"), it) } + for (match in Regex("""(?:^| )@(\w+)""").findAll(message)) { + val found: Member? = discordusers.firstOrNull { + it.effectiveName.noSpace().toLowerCase() == match.groupValues[1].toLowerCase() } - if (found != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found.second}>") + if (found != null) newMessage = newMessage.replaceFirst("@${match.groupValues[1]}", found.asMention) - val found2: Pair? = discordaliases.firstOrNull { - it.second.toLowerCase() == match.value.toLowerCase() + val found2: Pair? = discordaliases.firstOrNull { + it.first.toLowerCase() == match.groupValues[1].toLowerCase() } - if (found2 != null) newMessage = newMessage.replaceFirst("@${match.value}", "<@${found2.first}>") + if (found2 != null) newMessage = newMessage.replaceFirst("@${match.groupValues[1]}", found2.second.asMention) } return newMessage } + //TODO: De-convert @mentions + // Scans the string for occurrences of the Minecraft name matching the given UUID and attempts to translate it // to a registered Discord name, if it exists fun translateAliasToDiscord(message: String, uuid: String?): String { var newMessage = message val alias = users.data.getString("mcaliases.$uuid.discordusername") if (alias != null) - newMessage = newMessage.replaceFirst(users.data.getString("mcaliases.$uuid.mcusername"), alias) + newMessage = newMessage.replace(users.data.getString("mcaliases.$uuid.mcusername"), alias) + return newMessage + } + + // Scans the string for occurrences of the Minecraft name matching the given UUID and attempts to translate it + // to a registered Discord name, if it exists + fun translateAliasToMinecraft(message: String, discordId: String?): String { + var newMessage = message + val alias = users.data.getString("discordaliases.$discordId.mcusername") + if (alias != null) + newMessage = newMessage.replace(users.data.getString("discordaliases.$discordId.mcusername"), alias) return newMessage } fun toMinecraftChatMessage(message: String, alias: String): String { - var formattedString = cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE - formattedString = formattedString.replace("%u", alias).replace("%m", message) - formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) - return formattedString + return formatMessage(cfg.TEMPLATES_MINECRAFT_CHAT_MESSAGE, msg=message, u=alias) } fun toDiscordChatMessage(message: String, username: String, displayName: String, worldName: String): String { - var formattedString = cfg.TEMPLATES_DISCORD_CHAT_MESSAGE - formattedString = formattedString.replace("%u", username).replace("%m", message) - .replace("%d", displayName).replace("%w", worldName) - formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) - return formattedString + return formatMessage(cfg.TEMPLATES_DISCORD_CHAT_MESSAGE, msg=message, u=username, d=displayName, w=worldName) } fun toDiscordPlayerJoin(username: String, displayName: String): String { - var formattedString = cfg.TEMPLATES_DISCORD_PLAYER_JOIN - formattedString = formattedString.replace("%u", username).replace("%d", displayName) - formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) - return formattedString + return formatMessage(cfg.TEMPLATES_DISCORD_PLAYER_JOIN, u=username, d=displayName) } fun toDiscordPlayerLeave(username: String, displayName: String): String { - var formattedString = cfg.TEMPLATES_DISCORD_PLAYER_LEAVE - formattedString = formattedString.replace("%u", username).replace("%d", displayName) - formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) - return formattedString - } - - fun toDiscordPlayerDeath(deathMessage: String, username: String, displayName: String, worldName: String): String { - var formattedString = cfg.TEMPLATES_DISCORD_PLAYER_DEATH - formattedString = formattedString.replace("%u", username).replace("%r", deathMessage) - .replace("%d", displayName).replace("%w", worldName) - formattedString = ChatColor.translateAlternateColorCodes('&', formattedString) - return formattedString + return formatMessage(cfg.TEMPLATES_DISCORD_PLAYER_LEAVE, u=username, d=displayName) + } + + fun toDiscordPlayerDeath(deathMessage: String, username: String, displayName: String): String { + return formatMessage(cfg.TEMPLATES_DISCORD_CHAT_MESSAGE, r=deathMessage, u=username, d=displayName) + } + + private fun formatMessage(template: String, msg: String = "N/A", u: String = "N/A", d: String = "N/A", + w: String = "N/A", r: String = "N/A"): String { + var out = CC.translateAlternateColorCodes('&', template) + out = out.replace("%u", u).replace("%m", msg).replace("%d", d).replace("%w", w).replace("%r", r) + return out } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt b/src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt deleted file mode 100644 index ed1ef2e..0000000 --- a/src/main/kotlin/gg/obsidian/discordbridge/UserAlias.kt +++ /dev/null @@ -1,4 +0,0 @@ -package gg.obsidian.discordbridge - -data class UserAlias(var mcUsername: String, var mcUuid: String, - var discordUsername: String, var discordId: String) \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt index 073e682..fe9fef1 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt @@ -3,4 +3,7 @@ package gg.obsidian.discordbridge.Utils import org.bukkit.ChatColor fun String.noSpace() = this.replace(Regex("""\s+"""), "") -fun String.stripColor(): String = ChatColor.stripColor(this) \ No newline at end of file +fun String.stripColor(): String = ChatColor.stripColor(this) + +data class UserAlias(var mcUsername: String, var mcUuid: String, + var discordUsername: String, var discordId: String) \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt index 2f1c643..142519a 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Connection.kt @@ -4,10 +4,7 @@ import gg.obsidian.discordbridge.Plugin import net.dv8tion.jda.core.AccountType import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.JDABuilder -import net.dv8tion.jda.core.OnlineStatus -import net.dv8tion.jda.core.entities.Guild -import net.dv8tion.jda.core.entities.MessageChannel -import net.dv8tion.jda.core.entities.TextChannel +import net.dv8tion.jda.core.entities.* class Connection(val plugin: Plugin) : Runnable { var api: JDA? = null @@ -37,44 +34,21 @@ class Connection(val plugin: Plugin) : Runnable { toChannel.sendMessage(message).queue() } - // TODO: Try to merge this into "send" - fun tell(message: String, id: String) { - val modifiedMsg = message.replace("<@me>", api!!.selfUser.asMention) - api!!.getUserById(id).privateChannel.sendMessage(modifiedMsg).queue() - } - fun reconnect() { disconnect() connect() } - // TODO: Unfuck this - fun listUsers(): List> { - channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel + fun listUsers(): List { + channel = getRelayChannel() if (channel == null) return mutableListOf() - - val listOfUsers: MutableList> = mutableListOf() - channel!!.members.mapTo(listOfUsers) { - Triple(it.effectiveName, it.user.id, it.user.isBot) - } - return listOfUsers + return channel!!.members } - // TODO: Unfuck this - fun listOnline(): List> { - channel = if (channel == null) getGroupByName(server!!, plugin.cfg.CHANNEL) else channel - if (channel == null) return mutableListOf() - - val listOfUsers: MutableList> = mutableListOf() - channel!!.members.mapTo(listOfUsers) { - Triple(it.effectiveName, it.user.isBot, it.onlineStatus) - } - return listOfUsers - } - - private fun disconnect() { - api?.removeEventListener(listener) - api?.shutdown(false) + fun listOnline(): List { + channel = getRelayChannel() + if (channel != null) return channel!!.members + return mutableListOf() } private fun connect() { @@ -83,15 +57,21 @@ class Connection(val plugin: Plugin) : Runnable { api = builder.buildBlocking() listener = Listener(plugin, api as JDA, this) api!!.addEventListener(listener) - if(plugin.cfg.ANNOUNCE_SERVER_START_STOP) + if (plugin.cfg.ANNOUNCE_SERVER_START_STOP) send(plugin.cfg.TEMPLATES_DISCORD_SERVER_START, getRelayChannel()) + api!!.presence.game = Game.of("Minecraft ${plugin.server.bukkitVersion.split("-")[0]}") } - private fun getServerById(id: String): Guild? { - return api!!.guilds.firstOrNull { it.id.equals(id, true) } + private fun disconnect() { + api?.removeEventListener(listener) + api?.shutdown(false) } private fun getGroupByName(server: Guild, name: String): TextChannel? { return server.textChannels.firstOrNull { it.name == name } } + + private fun getServerById(id: String): Guild? { + return api!!.guilds.firstOrNull { it.id.equals(id, true) } + } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt index 93c3cad..7b230d3 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt @@ -18,38 +18,37 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L val rawmsg: String = event.message.rawContent val username: String = event.author.name + val channel = event.channel + val id = event.author.id - // Immediately throw out messages sent from itself or from non-matching servers - if (username.equals(plugin.cfg.USERNAME, true)) { + // Immediately throw out messages sent from itself + if (id == api.selfUser.id) { plugin.logDebug("Ignoring message ${event.message.id} from Discord: it matches this bot's username") return } -// if (event.guild.id != plugin.cfg.SERVER_ID) { -// plugin.logDebug("Not relaying message ${event.message.id} from Discord: server does not match") -// return -// } - // NON-RELAY COMMANDS - the following commands and their responses are never sent to Minecraft + // NON-TRIGGER-RELAY COMMANDS - the triggers of these commands are never relayed to Minecraft if (rawmsg.startsWith(api.selfUser.asMention, true)) { - val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").replaceFirst("\\s+", "") + val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").trimStart() + + // NON-RESPONSE-RELAY COMMANDS - the responses of these commands are also never relayed to Minecraft // CONFIRM - Confirm an alias if (arg.startsWith("confirm", true) && event.isFromType(ChannelType.PRIVATE)) { plugin.logDebug("user $username wants to confirm an alias") - val ua = plugin.requests.find { it.discordId == event.author.id } + val ua = plugin.requests.find { it.discordId == id } if (ua == null) { - plugin.sendToDiscord("You have not requested an alias, or your request has expired!", event.privateChannel) + plugin.sendToDiscord("You have not requested an alias, or your request has expired!", channel) return } - plugin.updateAlias(ua) + plugin.saveAlias(ua) plugin.requests.remove(ua) - plugin.sendToDiscord("Successfully linked aliases!", event.privateChannel) + plugin.sendToDiscord("Successfully linked aliases!", channel) return } // SERVERLIST - List all players currently online on the server if (arg.startsWith("serverlist", true)) { - val channel = if (event.isFromType(ChannelType.PRIVATE)) event.privateChannel else event.channel plugin.logDebug("user $username has requested a listing of online players") val players = plugin.getOnlinePlayers() if (players.isEmpty()) { @@ -60,6 +59,55 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L plugin.sendToDiscord(response, channel) return } + + // RESPONSE-RELAY COMMANDS - only the responses of these commands will be relayed + // to Minecraft if sent to the relay channel + + // 8BALL - consult the Magic 8-Ball to answer your yes or no questions + if (arg.startsWith("8ball", true)) { + plugin.logDebug("user $username consults the Magic 8-Ball") + val response = CommandLogic.eightBall(plugin, username) + plugin.sendToDiscord(response, channel) + if (event.isFromRelayChannel()) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage( + plugin.translateAliasToMinecraft(response, id), plugin.cfg.BOT_MC_USERNAME)) + return + } + + // F - press F to pay respects! + if (arg.equals("f", true)) { + plugin.logDebug("user $username pays respects") + val response = CommandLogic.f(plugin, username) + plugin.sendToDiscord(response, channel) + if (event.isFromRelayChannel()) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage( + plugin.translateAliasToMinecraft(response, id), plugin.cfg.BOT_MC_USERNAME)) + return + } + + // RATE - the bot rates something + if (arg.startsWith("rate", true)) { + val arg2 = arg.replaceFirst("rate", "").trimStart() + plugin.logDebug("user $username requests a rating") + val response = CommandLogic.rate(username, arg2) + plugin.sendToDiscord(response, channel) + if (event.isFromRelayChannel()) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage( + plugin.translateAliasToMinecraft(response, id), plugin.cfg.BOT_MC_USERNAME)) + return + } + + // INSULT - the bot insults something + if (arg.startsWith("insult", true)) { + val arg2 = arg.replaceFirst("insult", "").trimStart() + plugin.logDebug("user $username requests an insult against $arg2") + val response = CommandLogic.insult(plugin, arg2) + plugin.sendToDiscord(response, channel) + if (event.isFromRelayChannel()) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage( + plugin.translateAliasToMinecraft(response, id), plugin.cfg.BOT_MC_USERNAME)) + return + } } // If it is from the relay channel, relay it immediately @@ -69,7 +117,7 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L plugin.logDebug("Not relaying message ${event.message.id} from Discord: channel does not match") else { plugin.logDebug("Broadcasting message ${event.message.id} from Discord to Minecraft as user $username") - var alias = plugin.users.data.getString("discordaliases.${event.author.id}.mcusername") + var alias = plugin.users.data.getString("discordaliases.$id.mcusername") if (alias == null) alias = username.noSpace() plugin.sendToMinecraft(plugin.toMinecraftChatMessage(event.message.content, alias)) } @@ -77,39 +125,43 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L // RELAY COMMANDS - The outputs of these commands WILL be relayed to Minecraft if sent to the relay channel if (rawmsg.startsWith(api.selfUser.asMention, true)) { - val isPrivate = event.isFromType(ChannelType.PRIVATE) - val channel = if (isPrivate) event.privateChannel else event.channel - val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").removePrefix(" ") + val arg = rawmsg.replaceFirst(api.selfUser.asMention, "").trimStart() if (arg.isEmpty()) return plugin.logDebug("Relay command received. Arg: $arg") - // SCRIPTED RESPONSE - The bot replies with a preprogrammed response if it detects a corresponding trigger string - val responses = plugin.scripted_responses.data.getConfigurationSection("responses").getKeys(false) + // SCRIPTED RESPONSE - The bot replies with a pre-programmed response if it detects + // a corresponding trigger string + val responses = plugin.memory.data.getConfigurationSection("scripted-responses").getKeys(false) var scripted_response: String? = null for (r in responses) { - val casesensitive = plugin.scripted_responses.data.getBoolean("responses.$r.casesensitive", false) - if (arg.startsWith(plugin.scripted_responses.data.getString("responses.$r.trigger").toLowerCase(), !casesensitive)) - scripted_response = plugin.scripted_responses.data.getString("responses.$r.response") + val casesensitive = plugin.memory.data.getBoolean("scripted-responses.$r.case-sensitive", false) + if (arg.startsWith(plugin.memory.data.getString("scripted-responses.$r.trigger").toLowerCase(), !casesensitive)) + scripted_response = plugin.memory.data.getString("scripted-responses.$r.response") } if (scripted_response != null) { plugin.logDebug("user $username has triggered the scripted response: $scripted_response") plugin.sendToDiscord(scripted_response, channel) - if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.cfg.CHANNEL, true)) + if (event.isFromRelayChannel()) plugin.sendToMinecraft(plugin.toMinecraftChatMessage(scripted_response, plugin.cfg.BOT_MC_USERNAME)) return } // CLEVERBOT - Assume anything else invokes Cleverbot plugin.logDebug("user $username asks CleverBot something") - val response = CommandLogic.askCleverbot(plugin.cfg.CLEVERBOT_KEY, arg) + val response = CommandLogic.askCleverbot(plugin, arg) plugin.sendToDiscord(response, channel) - // if this occurs in the relay channel, relay the response - if (event.isFromType(ChannelType.TEXT) && event.textChannel.name.equals(plugin.cfg.CHANNEL, true)) + if (event.isFromRelayChannel()) plugin.sendToMinecraft(plugin.toMinecraftChatMessage(response, plugin.cfg.BOT_MC_USERNAME)) return } } + private fun MessageReceivedEvent.isFromRelayChannel(): Boolean { + return this.guild.id == plugin.cfg.SERVER_ID + && this.isFromType(ChannelType.TEXT) + && this.textChannel.name.equals(plugin.cfg.CHANNEL, true) + } + @Suppress("unused", "UNUSED_PARAMETER") fun onUnexpectedError(ws: WebSocket, wse: WebSocketException) { plugin.logger.severe("Unexpected error from DiscordBridge: ${wse.message}") diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt index 836e5ac..6a25083 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Discord.kt @@ -2,9 +2,7 @@ package gg.obsidian.discordbridge.minecraft.commands import gg.obsidian.discordbridge.Permissions import gg.obsidian.discordbridge.Plugin -import gg.obsidian.discordbridge.UserAlias -import net.dv8tion.jda.core.OnlineStatus -import org.bukkit.ChatColor +import org.bukkit.ChatColor as CC import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender @@ -14,7 +12,7 @@ class Discord(val plugin: Plugin) : CommandExecutor { override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { if (args == null || args.isEmpty()) { - sendMessage("&eUsage: /discord ", player) + sendMessage("{${CC.YELLOW}Usage: /discord ", player) return true } @@ -23,7 +21,7 @@ class Discord(val plugin: Plugin) : CommandExecutor { "alias" -> return handleDiscordAlias(player, args) "get" -> return handleDiscordGet(player, args) else -> { - sendMessage("&eUsage: /discord ", player) + sendMessage("${CC.YELLOW}Usage: /discord ", player) return true } } @@ -33,11 +31,11 @@ class Discord(val plugin: Plugin) : CommandExecutor { if (player is Player && !Permissions.reload.has(player)) return true if (args.size != 1) { - sendMessage("&eUsage: /discord reload", player) + sendMessage("${CC.YELLOW}Usage: /discord reload", player) return true } - sendMessage("&eReloading Discord Bridge...", player) + sendMessage("${CC.YELLOW}Reloading Discord Bridge...", player) plugin.reload() return true } @@ -46,27 +44,20 @@ class Discord(val plugin: Plugin) : CommandExecutor { if (player !is Player) return true if (args.size != 2) { - sendMessage("&eUsage: /discord alias ", player) + sendMessage("${CC.YELLOW}Usage: /discord alias ", player) return true } - val users = plugin.getDiscordUsers() - val found: Triple? = users.find { it.second == args[0] } - if (found == null) { - sendMessage("&eCould not find Discord user with that ID.", player) + if (!plugin.registerUserRequest(player, args[1])) { + sendMessage("${CC.YELLOW}Could not find Discord user with that ID.", player) return true } - - val ua: UserAlias = UserAlias(player.name, player.uniqueId.toString(), found.first, - found.second) - - plugin.registerUserRequest(ua) return true } private fun handleDiscordGet(player: CommandSender, args: Array): Boolean { if (args.size < 2) { - sendMessage("&eUsage: /discord get ", player) + sendMessage("${CC.YELLOW}Usage: /discord get ", player) return true } @@ -74,7 +65,7 @@ class Discord(val plugin: Plugin) : CommandExecutor { "ids" -> return handleDiscordGetIds(player, args) "online" -> return handleDiscordGetOnline(player, args) else -> { - sendMessage("&eUsage: /discord get ", player) + sendMessage("${CC.YELLOW}Usage: /discord get ", player) return true } } @@ -82,77 +73,28 @@ class Discord(val plugin: Plugin) : CommandExecutor { private fun handleDiscordGetIds(player: CommandSender, args: Array): Boolean { if (args.size != 2) { - sendMessage("&eUsage: /discord get ids", player) - return true - } - - val users = plugin.getDiscordUsers() - - if (users.isEmpty()) { - sendMessage("&eNo Discord members could be found. Either server is empty or an error " + - "has occurred.", player) + sendMessage("${CC.YELLOW}Usage: /discord get ids", player) return true } - var response = "&eDiscord users:" - for (user in users) { - if (user.third) response += "\n&6- ${user.first} (Bot), ${user.second}&r" - else response += "\n&e- ${user.first}, ${user.second}&r" - } - //val response = users.joinToString("\n- ", "&eDiscord users:\n- ") - sendMessage(response, player) + sendMessage(plugin.getDiscordIds(), player) return true } private fun handleDiscordGetOnline(player: CommandSender, args: Array): Boolean { if (args.size != 2) { - sendMessage("&eUsage: /discord get online", player) - return true - } - - val users = plugin.getDiscordOnline() - - if (users.isEmpty()) { - sendMessage("&eNo Discord members could be found. Either server is empty or an error " + - "has occurred.", player) + sendMessage("${CC.YELLOW}Usage: /discord get online", player) return true } - var response = "" - if (users.filter { it.third == OnlineStatus.ONLINE }.isNotEmpty()) { - response += "\n&2Online:&r" - for (user in users.filter { it.third == OnlineStatus.ONLINE }) { - if (user.second) response += "\n&2- ${user.first} (Bot)&r" - else response += "\n&2- ${user.first}&r" - } - } - if (users.filter { it.third == OnlineStatus.IDLE }.isNotEmpty()) { - response += "\n&eIdle:&r" - for (user in users.filter { it.third == OnlineStatus.IDLE }) { - if (user.second) response += "\n&e- ${user.first} (Bot)&r" - else response += "\n&e- ${user.first}&r" - } - } - if (users.filter { it.third == OnlineStatus.DO_NOT_DISTURB }.isNotEmpty()) { - response += "\n&cDo Not Disturb:&r" - for (user in users.filter { it.third == OnlineStatus.DO_NOT_DISTURB }) { - if (user.second) response += "\n&c- ${user.first} (Bot)&r" - else response += "\n&c- ${user.first}&r" - } - } - - response.replaceFirst("\n", "") - - sendMessage(response, player) + sendMessage(plugin.getDiscordOnline(), player) return true } private fun sendMessage(message: String, player: CommandSender) { - val formattedMessage = ChatColor.translateAlternateColorCodes('&', message) - if (player is Player) { - player.sendMessage(formattedMessage) - } else { - plugin.server.consoleSender.sendMessage(formattedMessage) - } + if (player is Player) + player.sendMessage(message) + else + plugin.server.consoleSender.sendMessage(message) } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/EightBall.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/EightBall.kt new file mode 100644 index 0000000..e5c968e --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/EightBall.kt @@ -0,0 +1,24 @@ +package gg.obsidian.discordbridge.minecraft.commands + +import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.Permissions +import gg.obsidian.discordbridge.Plugin +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class EightBall(val plugin: Plugin) : CommandExecutor { + override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (args == null || args.isEmpty()) { + if (player is Player && Permissions.eightball.has(player)) { + var msg = CommandLogic.eightBall(plugin, player.name) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(msg, plugin.cfg.BOT_MC_USERNAME)) + msg = plugin.translateAliasToDiscord(msg, player.uniqueId.toString()) + plugin.sendToDiscord(msg, plugin.conn.getRelayChannel()) + } + return true + } + return false + } +} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/F.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/F.kt new file mode 100644 index 0000000..73a5de1 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/F.kt @@ -0,0 +1,24 @@ +package gg.obsidian.discordbridge.minecraft.commands + +import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.Permissions +import gg.obsidian.discordbridge.Plugin +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class F(val plugin: Plugin) : CommandExecutor { + override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (args == null || args.isEmpty()) { + if (player is Player && Permissions.f.has(player)) { + var msg = CommandLogic.f(plugin, player.name) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(msg, plugin.cfg.BOT_MC_USERNAME)) + msg = plugin.translateAliasToDiscord(msg, player.uniqueId.toString()) + plugin.sendToDiscord(msg, plugin.conn.getRelayChannel()) + } + return true + } + return false + } +} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Insult.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Insult.kt new file mode 100644 index 0000000..b9c86f3 --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Insult.kt @@ -0,0 +1,23 @@ +package gg.obsidian.discordbridge.minecraft.commands + +import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.Permissions +import gg.obsidian.discordbridge.Plugin +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class Insult(val plugin: Plugin) : CommandExecutor { + override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (args == null || args.isEmpty()) return false + + if (player is Player && Permissions.insult.has(player)) { + var msg = CommandLogic.insult(plugin, args.joinToString(" ")) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(msg, plugin.cfg.BOT_MC_USERNAME)) + msg = plugin.translateAliasToDiscord(msg, player.uniqueId.toString()) + plugin.sendToDiscord(msg, plugin.conn.getRelayChannel()) + } + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt deleted file mode 100644 index 9dc8f1f..0000000 --- a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Marina.kt +++ /dev/null @@ -1,27 +0,0 @@ -package gg.obsidian.discordbridge.minecraft.commands - -import gg.obsidian.discordbridge.CommandLogic.askCleverbot -import gg.obsidian.discordbridge.Permissions -import gg.obsidian.discordbridge.Plugin -import gg.obsidian.discordbridge.Utils.* -import org.bukkit.command.Command -import org.bukkit.command.CommandExecutor -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player - -class Marina(val plugin: Plugin) : CommandExecutor { - - override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { - if (args == null || args.isEmpty()) return false - - if (player is Player && Permissions.cleverbot.has(player)) { - val message: String = args.joinToString(" ") - player.chat("@${plugin.cfg.USERNAME.noSpace()} $message") - val response = askCleverbot(plugin.cfg.CLEVERBOT_KEY, message) - plugin.sendToMinecraft(plugin.toMinecraftChatMessage(response, plugin.cfg.BOT_MC_USERNAME)) - plugin.sendToDiscord(response, plugin.connection.getRelayChannel()) - return true - } - return true - } -} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Rate.kt b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Rate.kt new file mode 100644 index 0000000..5ba397c --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/minecraft/commands/Rate.kt @@ -0,0 +1,23 @@ +package gg.obsidian.discordbridge.minecraft.commands + +import gg.obsidian.discordbridge.CommandLogic +import gg.obsidian.discordbridge.Permissions +import gg.obsidian.discordbridge.Plugin +import org.bukkit.command.Command +import org.bukkit.command.CommandExecutor +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player + +class Rate(val plugin: Plugin) : CommandExecutor { + override fun onCommand(player: CommandSender, cmd: Command, alias: String?, args: Array?): Boolean { + if (args == null || args.isEmpty()) return false + + if (player is Player && Permissions.rate.has(player)) { + var msg = CommandLogic.rate(player.name, args.joinToString(" ")) + plugin.sendToMinecraft(plugin.toMinecraftChatMessage(msg, plugin.cfg.BOT_MC_USERNAME)) + msg = plugin.translateAliasToDiscord(msg, player.uniqueId.toString()) + plugin.sendToDiscord(msg, plugin.conn.getRelayChannel()) + } + return true + } +} diff --git a/src/main/resources/botmemory.yml b/src/main/resources/botmemory.yml index 4399866..9e44592 100644 --- a/src/main/resources/botmemory.yml +++ b/src/main/resources/botmemory.yml @@ -1 +1,29 @@ -respects: 0 \ No newline at end of file +8-ball-responses: + - 'It is certain.' + - 'Seems pretty likely.' + - 'Absolutely.' + - 'Definitely.' + - 'Yes.' + - 'I think so.' + - 'Without a doubt.' + - 'Signs point to yes.' + - 'Probably.' + - 'The opposite is certain.' + - 'Seems pretty unlikely.' + - 'Absolutely not.' + - 'Definitely not.' + - 'No.' + - 'I don''t think so.' + - 'There''s plenty of doubt.' + - 'Signs point to no.' + - 'Probably not.' + - 'I''m not sure.' + - 'That''s hard to say at this time.' + - 'It''s impossible to know. Unless you ask me again.' + - 'Figure it out on your own.' +respects: 0 +scripted-responses: + '1': + case-sensitive: false + response: 'Hello World!' + trigger: 'hello world!' \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 89e0b8b..24ce4c4 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,30 +1,59 @@ -settings: - server-id: '00000000' - channel: 'test' - username: 'username' - username-color: '' - token: 'token' - cleverbot-key: '' - debug: false - relay_cancelled_messages: true - announce_server_start_stop: true - messages: - chat: true - join: true - leave: true - death: false - if_vanished: - chat: false - join: false - leave: false - death: false - templates: - discord: - chat_message: '<%u> %m' - player_join: '%u joined the server' - player_leave: '%u left the server' - player_death: '%r' - server_start: 'Server started!' - server_stop: 'Shutting down...' - minecraft: - chat_message: '<%u&b(discord)&r> %m' +# DiscordBridge Config + +# The bot's Discord API access token +token: '' + +# These values will control which channel the bot will watch to relay messages to and from the server. +server-id: '00000000' +channel: 'channel-name' + +# The bot's Discord username +username: 'DiscordBridge' + +# (Optional) Apply formatting codes to the bot's name in Minecraft chat. +# Use '&' in place of the formatting symbol. +username-color: '' + +# (Optional) Set this value with a valid Cleverbot API key to enable chatting with Cleverbot +# Look at https://www.cleverbot.com/api/ for more info +cleverbot-key: '' + +# If true, prints verbose log messages to the server console for every action +debug: false + +# If true, Minecraft chat events that are cancelled will still be relayed to Discord. +relay-cancelled-messages: true + +# Controls which events in general are relayed to Discord +messages: + player-chat: true + player-join: true + player-leave: true + player-death: false + server-start: true + server-stop: true + +# Controls which events caused by vanished players are relayed to Discord +# NOTE: If set to true, it will only have effect if the corresponding message above is also set to true +if-vanished: + player-chat: false + player-join: false + player-leave: false + player-death: false + +# Set the templates for relayed messages +# %u - The sender's username +# %m - The sender's message +# %w - The name of the world the sender is in (Multiverse alias compatible) +# %r - The death message (Death event only) +# Use '&' in place of the formatting symbol to apply formatting codes. +templates: + discord: + chat-message: '<**%u**> %m' + player-join: '**%u** joined the server' + player-leave: '**%u** left the server' + player-death: '%r' + server-start: 'Server started!' + server-stop: 'Shutting down...' + minecraft: + chat-message: '[&b&lDiscord&r]<%u> %m' diff --git a/src/main/resources/insults.yml b/src/main/resources/insults.yml new file mode 100644 index 0000000..06672e5 --- /dev/null +++ b/src/main/resources/insults.yml @@ -0,0 +1,504 @@ +insults: +- "I hate you so much I can't even breathe." +- "You really have an adorable smile. I'd like to annihilate it with my monstrous penis." +- "For the love of God simply stop being a miserable butt muncher for at least the next 10 minutes." +- "Eat a knob, you Colombian cock farmer." +- "My idea of a romantic Thursday morning is watching hooker obliterate your colon." +- "I just douched a hunk of ham out of my roomy hog funnel and it was the spitting image of you." +- "Hey pork chop, I find your back fat revolting." +- "Suck a bushel of putrid dicks." +- "Die in an ocean of anal pus, you pitiable cocksucker." +- "You have a yeast infection. Don't cry though, it's not the worst thing that could happen. You're also going to die from it." +- "I will bash you square in your stinking cunt, you retarded cock." +- "The point is, you no longer have the ability to socialize normally. It's most likely because you have dendrophilia." +- "Drown in a puddle of your own excrement, you shitty bastard." +- "The truth is, you no longer have the ability to function. It's presumably because you have alcoholism." +- "My idea of a superb Friday evening is seeing a jogger abuse your whore mouth." +- "My girlfriend and I are having an excessively hard time picturing a bleaker destiny than seeing you naked." +- "I just douched a piece of tomato out of my huge cooch and it was the spitting image of you." +- "I'm not saying you're a slut, but if your beaver had a password, it would be qwerty." +- "It seems like every time I think I might try to talk to you for more than a couple minutes, you shout at the homeless and I have to apologize to everyone on your behalf." +- "You disgust me so completely that I burn an effigy of you every Winter Solstice." +- "Wallow in an ocean of smegma, you pitiable fuckhole." +- "The way I understand it, there is no way in hell I would come within a thousand feet of your stinking cooch." +- "I will buttfuck you so vengefully that your bathroom will smell for hours." +- "Hey wide load, I find your quivering back fat terrible to behold." +- "Go fuck yourself, you classless, illiterate, homophobic teabagger." +- "Just so we're clear, there is no fucking way I would come within a kilometer of your wretched spunk dumpster." +- "You are fucking unattractive, grungy, retarded, you reek and you will forever be alone." +- "I will fuck you so vengefully that your living room will stink for weeks." +- "If you have any self respect just shut your overfucked face, you slow piss drinker." +- "Just so we're clear, there is no fucking way I would come within three counties of your bloody tits." +- "Go fuck your dad, you lonely shitfalcon." +- "OK fucklord, here's how it's gonna work. You either loosen the fuck up, or I will literally burn your house down." +- "Every damn time I intend to visit you in prison, you spit at strangers and I forget why I wanted to hang out with you in the first place." +- "Everyone says the only loving you get is from your dad." +- "A pox on thy family, thou horrible common-kissing herb woman." +- "We should hang out sometime, it's legendary to visit with someone with so many dependencies." +- "I hate you so much I can't even go to the bathroom by yourself." +- "Every time I see a hippie take a monsterous liquid shit on television, it reminds me of you." +- "You are not even remotely swell, in fact you're a shit-breathing bitch." +- "I'm not saying you're a ho, but if your messy cleft palate had a password, it would be qwerty." +- "Don't give me an excuse to take a humungous fucking crap on you while you sleep." +- "Why the hell do you reek like your Mom just crapped you out, did you embrace your love of wallowing in your own shit?" +- "Nobody who knows you cares about your aspirations, in fact I honestly think it would be the best for everyone if you just died." +- "I can't wait until you experience tapeworms." +- "Oh man, do you remember when when your best friends went to New York City? You don't? Oh yeah, now I remember, nobody wants to be around you." +- "It is written that you regularly shit yourself." +- "I often like to go for a jog and picture you being publically executed." +- "People don't hate you because you're humpbacked. I mean you are uncommonly humpbacked, that's just not why people hate you." +- "Hey doublewide, I find your back fat appalling." +- "Fuck yourself, you classless, hillbilly, unsophisticated meth baby." +- "I'm going to bend over so you can lick my butthole, you nasty sack of ass." +- "Absolutely nobody cares about your dreams, in fact I honestly think it would save everyone a lot of trouble if you hung yourself in your fucking closet." +- "I don't loathe you because you're senseless or because you're disfigured, I loathe you because you're a Republican." +- "I'd rather masturbate with a jar full of scorpions to Sarah Palin rolling around in gravy than spend another day with you." +- "Oh man, do you remember when when your co-workers went on vacation to Europe? Haha, you don't? Aw shit, I can't believe I forgot, nobody wants to spend time with you." +- "I very much hope you get a urinary tract infection." +- "Sometimes I like to relax in the hot tub and envision you being shot." +- "I have heard that your festering and shriveled penis has been steadily retreating into your fat pelvis." +- "I'm not saying you're a skank, but if your dick holster had a password, it would be 1234." +- "I'd rather masturbate with battery acid to horse surgery than be your friend." +- "Dude we are not going back to that whorehouse, what the fist-fucking christ is the matter with you?" +- "You will never survive to see your children grow up." +- "Why don't you suck ejaculate from my asshole, you grungy prick." +- "You're the first nerd I've ever hungered to slay." +- "I don't dislike you because you're simpleminded or because you're deformed, I dislike you because you're white." +- "I would smother you, but then the title of "Most awesomely disgusting spermatorium on the planet" would be awarded to someone else." +- "I will pummel you square in your fishy flabby meat flap, you hideous fuck." +- "My version of a superb Monday morning is witnessing a Catholic stab your hole." +- "You are not at all hot, in fact you're a butt fister." +- "Wouldn't you like to hear something that would make me excessively elated? You being brutally fucked by an angry mob." +- "Bitch, you best shift yo fat white ass you shit-ass moose knuckle." +- "For the love of God shut your dripping mouth, you foolish druggie." +- "Everyone says that your huge vagina smells like lobsters." +- "Hey, remember that time when your best friends became adults? You don't? Oh yeah, I can't believe I forgot, everyone finds you repulsive." +- "You have a shitty sense of humor, terrible swamp ass, and you're pretty fucking irritating." +- "You are so sexy. And by sexy I actually mean loathsome." +- "Eat a man spear, you Iraqi AIDS patient." +- "Your shitty odor is in fact revolting." +- "Uh, eat shit, you bitch. I don't want your fat ass in my life." +- "I heard the only fucking you get is from your brother." +- "Scholars will write parables of your outrageous slowness for many years after an angry mob kills you." +- "You have a dreadful wife, bad morals, and you're pretty fucking fat." +- "I dream of a time when you get giardia." +- "Piss off, you poor, ignorant, banjo playing hog wrestler." +- "We should meet up, it's absurd to talk to someone with real and crippling issues." +- "You're the first moron I've ever needed to slaughter." +- "I will give you a Chinese steamboat." +- "I can't explain how much I need you to simply stop being an insignificant swamp donkey for at least a few days." +- "I just queefed a hunk of carrot out of my cavernous cunt and it was the spitting image of you." +- "Do chug a can of ejaculate, you son of a bitch." +- "You have a horrible girlfriend, bad ambitions, and you're pretty fucking dumb." +- "Get out of my life, you displeasing cuntass." +- "You are gross, nasty, dumb, you reek and I never want to see you again." +- "Your continued existence makes me sad." +- "Die in a lake of anal pus, you wretched cocksucker." +- "I just farted a piece of silly putty out of my humongous snatch and it looked a lot like you." +- "Why are you bitchy, dicknose, did someone take a big old poop in your cocoa puffs?" +- "Yeah I know that you're a good-for-nothing nerd, but holy shit, throw yourself off the nearest bridge." +- "I will bash you straight in your festering slit, you piece of shit slut." +- "I heard the only loving you get is from your mom." +- "I will fuck you so spitefully that your bed will stink for years." +- "I'm not saying you're a slut, but if your flabby meat flap had a password, it would be 11111." +- "I wake up every morning hoping that you are diagnosed with cancer." +- "I just can't feel sorry for the family member I poisoned." +- "Go suck your Dad's dick, you destitute, hillbilly, inbred crack baby." +- "Go shake your ears, thou dastardly crook-pated cursed drab." +- "I'm fully aware that you're a valueless shitfalcon, but holy titty-fucking christ, throw yourself off the nearest bridge." +- "We should meet up next week, it's overwhelming to see someone with so many dependencies." +- "My idea of a perfect Tuesday afternoon is witnessing an ape forcefuck your throat." +- "Nobody gives one single fuck about your opinions, in fact I really think it would make the world a better place if you were hit by a train." +- "Why are you so aggravated, dickface, did someone take a poop in your fruit loops?" +- "I'd rather masturbate with a weedwhacker to Kim Jong Un than spend another day with you." +- "Every time I see an ape take a gigantic moist poop in the park, it reminds me of you." +- "I hope that someday soon you suffocate on your own dick, you leg humping bastard." +- "Hey, remember that time when your family took a vacation to the Bahamas? Oh, you don't? Ahh right, now I remember, nobody can tolerate your presence." +- "I loathe you so much I can't even have a baby." +- "I find you so detestable that I have night terrors now." +- "Why are you angry, skid row, did someone take a big old shit in your backpack?" +- "In comparison with you, drowning doesn't seem so bad." +- "You're the first prick I've ever desired to sacrifice." +- "My understanding is that the only action you get is from your wet towel." +- "I hate you so much I can't even go to the bathroom by yourself." +- "Sit on a pole and rotate, you wretched fuckdoll." +- "Dude be polite, what the shit slinging fuck is the matter with you?" +- "You are not even remotely sweet, in fact you're a pile of shit." +- "My understanding is that the only action you get is from your brother." +- "Keep talking and I will take a mutant wet shit directly into your mouth." +- "If the tales are true, you no longer have the ability to walk in a straight line. It's presumably because you have rabies." +- "My version of a splendid Friday night is watching a fat chick wreck your chasm." +- "The realization that you're not dead hurts the human race as a species." +- "Just quaff a bucket of cockwater, you cock." +- "You are fucking unattractive, raunchy, senseless, you reek and you're pathetic." +- "Dear Lord just stop being an insignificant whorebag for at least the rest of the evening." +- "You're the first fucker I've ever aspired to smother." +- "Get off my dick, you annoying poopmonster." +- "Your Mom says your turn-ons consist of messy butthole fondling and frogs in your butt." +- "When you least expect it I will take a huge stinking poop in your garage." +- "Why don't you suck ejaculate from my asshole, you filthy twat." +- "Goddamn you are deformed and you smell, let's just hang out sometime so I can put you out of your misery." +- "I really give not a single fuck about your worthless life, in fact I really think things would just be better if you hurled yourself into traffic." +- "In comparison with you, Hitler seems not so scary." +- "You are gross and you smell like muff mustard." +- "Dude, sod off, you dick. I don't need you in my life now or ever." +- "Sometimes I like to relax and visualize you as my slave." +- "You really have a cute ass. It's a shame I have to ravage it with my mutant pants porpoise." +- "Historians will spin yarns of your epic mental deficiency for decades after your murder." +- "I will give you a Cuban waxing." +- "The moral of the story is, there is no motherfucking way I would come within pissing distance of your shriveled nappy dugout." +- "Fuck you with a pitchfork." +- "Bro, you best shut the fuck up you shit-ass porch monkey." +- "Storytellers will be making fun of your outrageous mindlessness for many lifetimes after your death." +- "Every time I see a journalist take a monsterous steaming shit at a comic book convention, it reminds me of you." +- "Listen son, you best shut the fuck up you crack smokin' gaylord." +- "I will bash you straight in your shriveled vag, you Jewish cock." +- "Eat a dyke spike, you Mexican pedophile." +- "Storytellers will write parables of your immense brainlessness for many lifetimes after I kill you." +- "I will kick you straight in your rotting nappy dugout, you heinous fuck." +- "I don't despise you because you're mentally deficient or because you're repelling, I despise you because you have poor taste in nearly everything." +- "Most people say that the reek of your taint is vomit-inducing." +- "You really have a hot ass. I'd like to ravage it with my humungous unit." +- "I don't even feel sorry for the family member I poisoned." +- "Listen up whitebread, you gonna need a new face when I'm through with yo ass you pole smokin' hillbilly." +- "I'd rather masturbate with a riding crop to a pile of shit than wake up next to you." +- "I was thinking about you on Sunday night, and oh my good god I needed to felch you in a beastly manner." +- "Jesus you are foul and you reek, why don't we hang out so I can shoot you in the face." +- "You have anal herpes. Never fear however, the news isn't all bad. You're also going to get hit by a train." +- "I find you so detestable that I told everyone you have AIDS." +- "Why in God's name do you reek like dolphin queef, did you void your bowels?" +- "I will be the man your father was and you will be living behind a dumpster." +- "I saw you on Facebook on Wednesday evening, and oh my sweet christ I needed to finger you in a disgusting manner." +- "I find it hard to feel sorry for you." +- "I will survive to see your children grow up and you will be hanging by your neck." +- "I'm definitely going to take a huge wet shit on your floor." +- "I'm fully aware that you're a trashy twat, but holy shitting fuck, fuck off." +- "Why don't you come here and suck man batter from my butt cave, you scuzzy bitch." +- "Your purposeless existence makes the world seem a little less nice." +- "Go fuck yourself, you repugnant twatbag." +- "Your shitty odor is totally repulsive." +- "Go fuck your stuffed animals, you shitty fuck." +- "Everyone says that your tits are sagging prematurely." +- "I will give you a Chinese mudbath." +- "In a couple weeks, you will almost certainly be giving handjobs to a convict in a deserted brothel for coke, you hateful prick." +- "I will be happy with your life and you will be buried in a shallow grave." +- "Don't you just have a gorgeous smile. It's a shame I have to ruin it with my gigantic veiny bastard." +- "I will kill you if you don't shut your white cake hole, you idiotic republican." +- "Listen up whitebread, you best bounce the fuck outta here you dumb shit fuckwit." +- "Every damn time we attempt to hang out with you, you whisper dark curses to other peoples pets and I have to apologize to everyone on your behalf." +- "I have heard that there are religious groups contemplating ways in which to render you sterile." +- "The fact of the matter is, there is absolutely no way I would come within pissing distance of your festering dick holster." +- "I hope that someday soon you get a yeast infection in your mouth." +- "I'm not saying you're a ho, but if your moose knuckle had a password, it would be hellohello." +- "Fuck you with a snow shovel." +- "I pray that a strange dude kills you in the snow, you aborted cunt." +- "So I know that you're a worthless twat, but holy shitting fuck, eat a bag of dicks." +- "I don't loathe you because you're witless or because you're disfigured, I loathe you because you're a Republican." +- "Oh man, do you remember when when your co-workers went on vacation to Europe? Oh, you don't? Oh right, I completely forgot, nobody can tolerate your presence." +- "I just queefed a piece of baseball bat out of my gaping spunk dumpster and it looked a lot like you." +- "Eat a bushel of bloody dicks." +- "Damn you have a hot ass. It's a shame I have to demolish it with my giant cock." +- "When compared to you, drowning isn't really that bad." +- "Listen up whitebread, you best shut the fuck up you shit-ass fuckwit." +- "I'd rather masturbate with a live piranha to horse surgery than see you again." +- "You have a terrible laptop, dreadful goals, and you're pretty fucking disgusting." +- "Your overpowering stench is in fact nasty." +- "I'm not saying you're a skank, but if your labia had a password, it would be 11111." +- "You are hereby cordially invited to shut your fucking cake hole, you insignificant barrel of ass." +- "Everyone says the only fucking you get is from disease-ridden prostitutes." +- "You have a shitty car, terrible morals, and you're pretty goddamn irritating." +- "My version of a fantastic Saturday night is watching a game show host ram your taint." +- "You are welcome to shut your face, you inconsiderate bag of lard." +- "You're the first bitch I've ever desired to asphyxiate." +- "In comparison with you, drowning in a sea of shit isn't so bad." +- "I have heard that you have a tiny penis." +- "I would exterminate you, but then the title of "Most monstrously hideous fuckass on the planet" would be awarded to someone else." +- "People don't hate you because you're homely. It's true that you are uncommonly homely, that's just not why people hate you." +- "Not even your closest friends care about your pathetic life, in fact I sometimes think it would save everyone a lot of trouble if we killed you." +- "Damn you're sexy. And by sexy I of course mean gross." +- "Boils and plagues plaster you over, thou malevolent horn-mad kitchen trull." +- "I don't despise you because you're senseless or because you're gross, I despise you because you're white." +- "You have a shitty job, shitty swamp ass, and you're pretty fucking useless." +- "We should meet up, it's astonishing to visit with someone with such major problems." +- "Run along and gargle a tub of ejaculate, you twat." +- "Every time I see a weird looking guy take a giant moist poop in the park, it reminds me of you." +- "You are unbeautiful and you smell like pussy pesto." +- "I will screw you so brutally that your garage will smell for months." +- "Why are you so pissed off, cockbreath, did someone poop in your glove box?" +- "Sure you can jump my chocolate starfish, you scuzzy twat." +- "I will pummel you square in your shriveled devil's hallway, you throbbing slut." +- "You are foul and you smell like anus aoli." +- "People don't hate you because you're fucking unattractive. Don't get me wrong you are uncommonly fucking unattractive, that's just not why people hate you." +- "Everyone says that even your friends are reluctant to spend time with you." +- "Bathe in an ocean of anal pus, you pitiable son of a bitch." +- "Listen, sod off, you son of a bitch. I do not need your daddy issues in my life now or ever." +- "If you have any self respect just just quit being a terrible shitass for at least the rest of the evening." +- "Go fuck yourself, you annoying cuntface." +- "You are homely and you smell like nipple scabs." +- "You have downs syndrome. Never fear however, the news isn't all bad. You're also going to die tomorrow." +- "In a year, you will almost certainly be weeping silently while being ridden by a fat guy in a derelict restaurant for cheap liquor, you worthless whore." +- "I heard the only loving you get is from your mom." +- "I will buttfuck you so harshly that your room will stink for weeks." +- "If you have even a single shred of decency you'll just quit being a terrible dog raper for at least the rest of the evening." +- "Fuck you with a post digger." +- "Your Mom says your turn-ons are old man fondling and feces." +- "Squat on a jar, you wretched fuckdoll." +- "Get off my dick, you dreaded cockweasel." +- "It suddenly strikes me that you're nice. And by nice I really mean foul." +- "Hey, remember that time when your co-workers went on vacation to Europe? Wow, you don't? Oh damn, I can't believe I forgot, nobody wants to be around you." +- "You have a horrid wife, terrible pants, and you're pretty goddamn dumb." +- "I am inclined to believe you love goats just a bit too much." +- "People don't loathe you because you're homely. Don't get me wrong you are astonishingly homely, that's just not why people loathe you." +- "I'm fucking begging you to shut your dickless orifice, you senseless cock-fuck." +- "A lot of people think that your turn-ons are old woman fondling and fecal painting." +- "I just can't feel sorry for your hideous face." +- "I'm definitely going to take a huge runny shit inside your chest cavity." +- "I don't even feel sorry for the time we all forgot your birthday." +- "There are rumors that your turn-ons consist of camel fondling and assless chaps." +- "Your stench is utterly lousy." +- "I would slaughter you, but then the title of "Most loathsome shitface in existence" would pass on to someone else." +- "I will strike you straight in your wretched shaggy DA, you rotting loser." +- "Eat a bowl of severed dicks." +- "If what I hear is true, there is no motherfucking way I would come within pissing distance of your festering flabby meat flap." +- "I'm not saying you're a ho, but if your moose knuckle had a password, it would be 1234." +- "Go fuck your tube sock you fucker." +- "You make it very hard to feel sorry for that time in high school." +- "You can go right ahead and screw my cornhole, you foul butthole." +- "I hope that someday soon you get necrotizing fasciitis." +- "Suck a sack of bloody dicks." +- "Why the hell do you reek like you showered in feces, did you use your own feces as shaving cream?" +- "I will buffet you straight in your stinking twat, you heinous cunt." +- "I'm sure it's true that your loose ham cave is starting to rot." +- "Suck a fat dick, you classless, ignorant, narrow-minded republican." +- "I dream of a time when a thug donkey punches you in a tree, you vomit-inducing child molester." +- "Eat a pound of gorilla dicks." +- "I'm not saying you're a ho, but if your cunt had a password, it would be hellohello." +- "I will give you a Russian steamboat." +- "I often like to take a nap and picture you being tortured." +- "Why are you bitchy, shitfalcon, did someone poop in your Captain Crunch?" +- "You are appalling and you smell like rectum relish." +- "I hope that someday soon a football team murders you in a tree, you dripping transvestite." +- "You have fleas. Never fear however, the news isn't all bad. You're also going to die." +- "I will see her again and you will be hanging by your neck in your fucking closet." +- "I'd rather masturbate with a live piranha to Sarah Jessica Parker than see you naked." +- "I can't wait until you break your last wagon tongue miles from chimney rock." +- "I'm going to bend over so you can screw my bleached butthole, you dirty sack of ass." +- "I'm not saying you're a slut, but if your vag had a password, it would be 12345." +- "Leave me the fuck alone, you repugnant pooplord." +- "Yeah I know that you're a terrible shitfalcon, but holy word, go hang yourself." +- "You are fucking motherfucking hideous and you reek, we should hang out sometime so I can strangle you." +- "In comparison with you, cancer sounds pretty good." +- "Seriously why do you smell like a plague victim, did you fingerbang a diarrheal pork lover?" +- "Do quaff a cup of spunk, you loser." +- "Bro, I'ma smack up yo dumb cracka ass you lazy piece of shit fuckass." +- "It suddenly strikes me that you're nice. And by nice I of course mean awesomely disgusting." +- "Minstrels will be making fun of your breathtaking slowness for milennia after your death is met with joy and laughter." +- "Sometimes I like to chill out at home and envision you being disemboweled." +- "The painful reality that is your life makes me hit my wife." +- "Your nauseating stink is utterly heinous." +- "I will buffet you square in your stinking flabby meat flap, you piece of shit nerd." +- "Damn you're sexy. And by sexy I actually mean hideous." +- "You will never grow up." +- "You are gross and you smell like nipple scabs." +- "Drown in a puddle of your own excrement, you wretched cunt." +- "I just farted a piece of potato out of my gaping hatchet wound and it looked just like you." +- "I can't wait until a hooker kills you in central park, you oversexed tranny." +- "Why are you so bitter, Tinkerbell, did someone take a poop in your purse?" +- "Some say that your tits are sagging prematurely." +- "I will skullfuck the shit out of you, you wretched bitch." +- "For the love of god why do you smell like buttrot, did you void your bowels?" +- "You are invited to shut the fuck up, you intolerant bag of boiled dicks." +- "My understanding is that the only loving you get is from your wet towel." +- "You are not even close to good looking, in fact you're a douche spigot." +- "Your astonishing stench is just so horrible." +- "My idea of a flawless Sunday morning is watching a convict wreck your neck." +- "I wake up every morning hoping that you suffocate on a plastic dick, you disgusting cunt." +- "I just queefed a piece of potato out of my roomy hog funnel and it looked kind of like you." +- "You will never find someone to love." +- "It would be fantastic if you shut your fucking mouth, you unthinking bag of shit." +- "I hope that someday soon a southerner humps you in grand union, you overfucked tranny whore." +- "I'm not saying you're a slut, but if your gash had a password, it would be 12345." +- "Do slurp a bucket of man batter, you nerd." +- "You are not at all cool, in fact you're a tard." +- "Just so we're clear, there is absolutely no way I would come within ten feet of your festering cooch." +- "Go fuck yourself, you repellent fucksicle." +- "I fervently hope a Canadian dick-whips you in the woods, you rotting republican." +- "I have heard that the reek of your taint is vomit-inducing." +- "We should meet up, it's overwhelming to see someone with so many future health problems." +- "Every time I see a rapist take a gigantic stinking poop on the subway, it reminds me of you." +- "You're the first bitch I've ever strongly desired to poison." +- "My version of a brilliant Wednesday afternoon is watching a yeti mutilate your taint." +- "Listen up whitebread, you best pull that dick out yo face you pole smokin' tranny." +- "So I know that you're a good-for-nothing skank, but holy shit-slinging fuck, go fuck yourself." +- "People don't despise you because you're repugnant. Don't get me wrong you are unbelievably repugnant, that's just not why people despise you." +- "I don't want to visit your apartment, even your own friends don't want to visit your apartment, perchance it would save everyone a lot of trouble if you threw yourself from the tallest building you could find." +- "Ugh you are just foul and you smell, let's just hang out sometime so I can drown you myself." +- "I just farted a hunk of silly putty out of my gigantic serpent socket and it looked just like you." +- "I would execute you, but then the title of "Most awesomely disgusting douche caboose in the universe" would be awarded to someone else." +- "A pox on thy family, thou spiteful idle-headed king of codpieces." +- "Suck a bushel of putrid dicks." +- "Every time I see a&nbspgorilla take a gigantic moist poop out of a moving vehicle, it reminds me of you." +- "People don't hate you because you're foul. Don't get me wrong you are unbelievably foul, that's just not why people hate you." +- "We should meet for coffee, it's legendary to see someone with so many lice." +- "You are not at all good looking, in fact you're a child molester." +- "People don't dislike you because you're disfigured. Don't get me wrong you are unbelievably disfigured, that's just not why people dislike you." +- "I'm not saying you're a slut, but if your serpent socket had a password, it would be 12345." +- "People don't hate you because you're foul. Don't get me wrong you are astonishingly foul, that's just not why people hate you." +- "The point is, there is no way in hell I would come within spitting distance of your wretched stench trench." +- "We should meet up next week, it's absurd to have a conversation with someone with such major problems." +- "Hey, remember that time when your best friends took a vacation to the Bahamas? Oh, you don't? Oh right, I can't believe I forgot, everyone hates you." +- "Fuck you with a snow shovel." +- "Please be quiet, you discourteous sack of ass." +- "The reality that you're still alive keeps me up at night." +- "I'd rather masturbate with battery acid to Kim Jong Un than be friends with you." +- "You really have a beautiful smile. I'd like to wreck it with my monsterous trouser snake." +- "Go fuck disease-ridden prostitutes, you shitty douche." +- "You have lupus. Never fear however, it's not the end of the world. You're also going to be murdered." +- "In just over a year, I'd be surprised if you weren't porking strange men in a deserted factory for scratch tickets, you despicable nerd." +- "Rub my crab-infested pubes in your eyes, you terrible son of a bitch." +- "I will bang you so thoroughly that your kitchen will smell for days." +- "Your brother says that your turn-ons include messy butthole banging and stomach hair." +- "It feels like every time I intend to talk to you for more than a couple minutes, you start talking about high school and I forget why I wanted to hang out with you in the first place." +- "It's simply an uncommonly laborious time envisioning a more dreadful future than watching you poop." +- "Seriously why do you reek like buttrot, did you move in with a cow?" +- "Sure you can suck cockwater from my rectal opening, you filthy fuck." +- "You're the first shitfalcon I've ever hoped to suffocate." +- "You're the first shitlord I've ever hoped to execute." +- "You are loathsome, dirty, idiotic, you smell and you're pathetic." +- "You're the first loser I've ever desired to drown." +- "Every time I see a fat chick take a humungous poop at a comic book convention, it reminds me of you." +- "We should hang out, it's overwhelming to be in the same room with someone with actual STDs." +- "Die in an ocean of shit, you pathetic son of a bitch." +- "Everyone says the only action you get is from your cousin." +- "I want you to hump my pooper, you foul piece of shit." +- "Goddamn you are disfigured and you smell, let us hang out sometime so I can drown you myself." +- "For fuck's sake fucking stop being a trashy dick chaser for at least the rest of the evening." +- "I will give you a Vietnamese corndog." +- "I will grow old and you will be in prison." +- "Go fist yourself, you insignificant son of a bitch." +- "I dislike you so much I can't even stand without crying." +- "You have cholera. Don't cry though, it's not the worst thing that could happen. You're also going to die." +- "I'm pretty sure I saw you on Friday, and oh my christ I longed to pound you in a horrible manner." +- "I will be attractive and you will be hanging by your neck in your fucking closet." +- "Man, fuck off, you bitch. I don't need to deal with you right now." +- "You are humpbacked and you smell like dick cheese." +- "So I know that you're a miserable moron, but holy shit, hang yourself." +- "I'm not saying you're a skank, but if your hog funnel had a password, it would be hellohello." +- "When compared to you, being killed doesn't seem so bad." +- "It suddenly strikes me that you're sexy. And by sexy I really mean unsightly." +- "I will give you a Iraqi bee sting." +- "Fuck you with a post digger." +- "You're the first loser I've ever hungered to eradicate." +- "Storytellers will still be amazed by your horrifying half-wittedness for centuries after you are hanged." +- "If you have any self respect just just stop being a trashy leg humper for at least a few years." +- "Your unbelievable odor is utterly gruesome." +- "Why are you upset, bitchtits, did someone take a poop in your soup?" +- "I don't want to visit you in prison, even your own family doesn't want to visit you in prison, possibly it would solve a lot of problems if we killed you." +- "I don't dislike you because you're brainless or because you're motherfucking hideous, I dislike you because you're white." +- "I am inclined to believe you adore children a trifle too much." +- "I believe that you are the ugliest human alive." +- "You are not at all sweet, in fact you're a wanker." +- "You're the first fuck I've ever aspired to slaughter." +- "Why are you angry, Nancy Drew, did someone take a big old shit in your soup?" +- "Fuck you with a pitchfork." +- "When compared to you, getting a blowjob from a crocodile doesn't seem so bad." +- "Your continued existence is what causes people to cross the street when they see you." +- "The fact you haven't killed yourself makes me realize there is no justice in the world." +- "I would execute you, but then the title of "Most gross dicknose in the universe" would be awarded to someone else." +- "Why are you so pissy, shitbreath, did someone shit in your yogurt?" +- "We should hang out, it's overwhelming to be in the same room with someone with so much baggage." +- "Hey you ate all my oreos, what the hell is the matter with you?" +- "Why are you in a bad mood, shitbreath, did someone take a big old poop in your frosted flakes?" +- "Nobody who knows you cares about your wishes, in fact we all agreed that it would make the world a better place if you just died." +- "Go fuck your mom you bitch." +- "Yeah I know that you're an insignificant skank, but holy shitsnacks, get fucked." +- "You are not at all swell, in fact you're a cocksucker." +- "I hate you so much I can't even have children." +- "I'd rather masturbate with scissors to a pile of shit than be friends with you." +- "You definitely have a pretty ass. I'd like to wreck it with my mutant dyke spike." +- "Every time I see a drug dealer take a humungous moist poop while giving head, it reminds me of you." +- "Dude get in the car, what the dick is the matter with you?" +- "Wallow in a lake of diarrhea, you sorrowful asshole." +- "Wow you have a gorgeous smile. It will be enjoyable to annihilate it with my giant pants porpoise." +- "I just douched a chunk of potato out of my huge vagina and it looked a little bit like you." +- "The truth is, you lack the ability to go to the bathroom by yourself. It's most likely because you have warts." +- "Thy loins are ripe, thou repugnant crook-pated knave." +- "Just so we're clear, you no longer have the ability to walk in a straight line. It's most likely because you have expleosive diarrhea." +- "I'm not saying you're a ho, but if your serpent socket had a password, it would be qwerty." +- "You're the first fucker I've ever desired to strangle." +- "You repulse me so thoroughly that I sometimes weep at the thought of you." +- "Eat a dick, you Japanese cotton picker." +- "I can't wait until you suffocate on a homeless man's cock, you tree hugging asshole." +- "If you have even a single shred of decency you'll shut your smelly mouth, you witless cum chaser." +- "Why are you cunty, cockbreath, did someone take a poop in your funyuns?" +- "Everyone says the only fucking you get is from your brother." +- "I dream of a time when you get genital warts." +- "Suck a bucket of salted dicks." +- "Listen, eat shit, you shitlord. I do not need your fat ass in my life now or ever." +- "I dream of a time when you suffocate on an unfamiliar cock, you filthy fucking piece of shit." +- "Fuck you with a hoe." +- "People don't despise you because you're repulsive. I mean you are astonishingly repulsive, that's just not why people despise you." +- "Hey, remember that time when your co-workers became productive members of society? You don't? Oh damn, I can't believe I forgot, nobody wants to be around you." +- "Suck a satchel of gorilla dicks." +- "I fervently hope a hideous chick slaps you in a horse stall, you shit-eating porch monkey." +- "Oh my christ why do you reek like you bathed in a swamp, did you make love to a manure pile?" +- "Hey ham planet, I find your quivering back fat difficult to believe." +- "Fuck you, you poverty-stricken, backwoods, moonshine-drinking couch jockey." +- "Why are you so aggravated, dicknose, did someone poop in your Captain Crunch?" +- "In just over a year, I believe strongly that you'll be presenting yourself for anal sex to an ugly ugly man in a godforsaken basement for free, you wretched shitfalcon." +- "I'm encouraging you to shut your face, you intolerant barrel of feces." +- "For your information, you can no longer solve simple problems. It's presumably because you have tapeworms." +- "Every fucking time we try to be your friend, you expose yourself to strangers and I realize you're just not worth it." +- "I suspect you like craigslist a trifle too much." +- "I hate you so much I can't even pee standing up." +- "I will pummel you square in your swampy stench trench, you christian whore." +- "Down to hell and say I send thee thither, thou wretched pottle-deep egregious dog." +- "I'm not saying you're a slut, but if your shaggy DA had a password, it would be qwerty." +- "A lot of people think that your turn-ons are slippery butt licking and NASCAR." +- "We should hang out sometime, it's legendary to hang out with someone with such major problems." +- "Go rate thy minions, thou wretched dread-bolted common dog." +- "Yeah I'm going to need you to go ahead and shut your reasty gob, you mentally deficient dick taster." +- "Hey, remember that time when some of your co-workers became adults? Wait, you don't? Oh damn, I can't believe I forgot, nobody likes you." +- "I'm not saying you're a slut, but if your slit had a password, it would be abc123." +- "Bro, I'ma slap that silly-ass grin off yo face you crack smokin' shit lover." +- "I'm encouraging you to shut your face, you rude barrel of dicks." +- "I hope you drown in a stagnant cum pool, you trashy asshole." +- "I don't hate you because you're unintelligent or because you're fucking unattractive, I hate you because you have poor taste in nearly everything." +- "Mothafucka, I'ma slap that silly-ass grin off yo face you shit-ass queef." +- "Bro, you need to step the fuck back you dumb fuckin' cock jockey." +- "Go fuck your tube sock you douche." +- "Some of us are having a seriously challenging time visualizing a bleaker destiny than being forced to spend time with you." +- "People don't hate you because you're appalling. Don't get me wrong you are unbelievably appalling, that's just not why people hate you." +- "Everyone says that your huge vagina smells like lobsters." +- "Wallow in an ocean of anal pus, you pathetic son of a bitch." +- "You are fucking unattractive and you reek, let us meet up so I can slap the shit out of you." +- "Suck my dick, you classless, illiterate, inbred meth baby." +- "Damn you, thou hateful purple-hued dissembling harlot." +- "Compared with you, getting a blowjob from a crocodile doesn't seem so bad." +- "You are repulsive, raunchy, half-witted, you smell and I never want to see you again." +- "Assuming the stories are true, you no longer have the ability to socialize normally. It's presumably because you have tapeworms." +- "Why don't you shut your fucking noise hole, you unimportant bag of shit." +- "Go fuck your wet towel you moron." +- "You are not even remotely very nice, in fact you're a shit-breathing bitch." +- "In comparison with you, contracting malaria isn't really that bad." +- "If what I hear is true, you no longer have the ability to socialize normally. It's likely because you have hepatitis." +- "You will never be likeable." +- "Hey, remember that time when your co-workers went to New York City? Wait, you don't? Oh right, I can't believe I forgot, nobody likes you." +- "I'm fully aware that you're a worthless son of a bitch, but holy heavenly father, go fuck a meat grinder." +- "I'm just going to go ahead and take a mutant liquid shit on your cat." +- "I saw you on Facebook on Saturday afternoon, and oh my lord in heaven I yearned to pork you in an unpleasant place." +- "I don't even feel sorry for your chronic poverty." +- "I don't want to visit your apartment, even your own friends don't want to visit your apartment, perhaps it would make everybody happier if you were disemboweled." +- "Eat a penis, you Mexican poolboy." +- "Death would be too good for you, you wretched cunt." +- "People don't hate you because you're foul. Don't get me wrong you are uncommonly foul, that's just not why people hate you." +- "Please fuck off, you incommodious cuntface." +- "We should hang out, it's stunning to visit with someone with such a huge potential for failure." +- "I would lynch you, but then the title of "Most awesomely disgusting twatwaffle alive" would be awarded to someone else." +- "Go away, you troublesome cockskank." +- "Everyone says the only loving you get is from your family members." +- "Everyone says the only fucking you get is from your wet towel." +- "Why don't you shut the fuck up, you insignificant bag of feces." + +# Insults from INSULTGENERATOR.ORG +# http://pastebin.com/wjABXq7j \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a51bcf1..be1ba74 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -2,7 +2,7 @@ name: DiscordBridge version: '@VERSION@' description: '@DESCRIPTION@' -author: Jacob Gillespie +authors: [Jacob Gillespie, Adam Hart] website: '@URL@' loadbefore: [SpaceBukkit, RemoteToolkitPlugin] @@ -17,11 +17,32 @@ permissions: discordbridge.cleverbot: description: Determines whether this user can talk to Cleverbot default: true + discordbridge.f: + description: Determines whether this user can use the f command + default: true + discordbridge.rate: + description: Determines whether this user can use the rate command + default: true + discordbridge.eightball: + description: Determines whether this user can use the 8ball command + default: true + discordbridge.insult: + description: Determines whether this user can use the insult command + default: op commands: + 8ball: + description: Consult the Magic 8-Ball to answer your yes/no questions + usage: /8ball discord: description: Issue a command to the bot usage: /discord - marina: - description: Chat with Marina! - usage: /marina \ No newline at end of file + f: + description: Press f to pay respects + usage: /f + rate: + description: Have the bot rate something for you + usage: /rate + insult: + description: Have the bot insult someone for your + usage: /insult \ No newline at end of file diff --git a/src/main/resources/scriptedresponses.yml b/src/main/resources/scriptedresponses.yml deleted file mode 100644 index d4a9397..0000000 --- a/src/main/resources/scriptedresponses.yml +++ /dev/null @@ -1,5 +0,0 @@ -responses: - '1': - casesensitive: false - response: 'Hello World!' - trigger: 'hello world!' \ No newline at end of file From 83d37a55b1f960dab3b013f14bf5cd4f3ad0b714 Mon Sep 17 00:00:00 2001 From: DiamondIceNS Date: Sat, 4 Mar 2017 09:50:38 -0600 Subject: [PATCH 16/24] partway there to having everything config-driven --- .../gg/obsidian/discordbridge/CommandLogic.kt | 127 ++++++++++-------- .../discordbridge/DataConfigAccessor.kt | 4 +- .../gg/obsidian/discordbridge/Plugin.kt | 29 ++-- .../gg/obsidian/discordbridge/Utils/Utils.kt | 56 +++++++- .../discordbridge/discord/Listener.kt | 54 +++++--- .../{ => minecraft}/EventListener.kt | 4 +- .../{ => minecraft}/Permissions.kt | 2 +- .../minecraft/commands/Discord.kt | 2 +- .../minecraft/commands/EightBall.kt | 2 +- .../discordbridge/minecraft/commands/F.kt | 2 +- .../minecraft/commands/Insult.kt | 4 +- .../discordbridge/minecraft/commands/Rate.kt | 4 +- .../resources/{botmemory.yml => 8ball.yml} | 13 +- src/main/resources/f.yaml | 30 +++++ .../resources/{insults.yml => insult.yml} | 61 +++++---- src/main/resources/rate.yml | 78 +++++++++++ src/main/resources/script.yml | 20 +++ 17 files changed, 360 insertions(+), 132 deletions(-) rename src/main/kotlin/gg/obsidian/discordbridge/{ => minecraft}/EventListener.kt (97%) rename src/main/kotlin/gg/obsidian/discordbridge/{ => minecraft}/Permissions.kt (89%) rename src/main/resources/{botmemory.yml => 8ball.yml} (74%) create mode 100644 src/main/resources/f.yaml rename src/main/resources/{insults.yml => insult.yml} (94%) create mode 100644 src/main/resources/rate.yml create mode 100644 src/main/resources/script.yml diff --git a/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt b/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt index 8fe7815..c7a4c24 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/CommandLogic.kt @@ -1,13 +1,16 @@ package gg.obsidian.discordbridge import com.michaelwflaherty.cleverbotapi.CleverBotQuery +import gg.obsidian.discordbridge.Utils.Rating +import gg.obsidian.discordbridge.Utils.Respect import java.io.IOException import java.util.* object CommandLogic { fun askCleverbot(plugin: Plugin, message: String): String { - if (plugin.cfg.CLEVERBOT_KEY.isEmpty()) return "You do not have an API key. Go to https://www.cleverbot.com/api/ for more information." + if (plugin.cfg.CLEVERBOT_KEY.isEmpty()) + return "You do not have an API key. Go to https://www.cleverbot.com/api/ for more information." val bot: CleverBotQuery = CleverBotQuery(plugin.cfg.CLEVERBOT_KEY, message) var response: String try { @@ -21,77 +24,83 @@ object CommandLogic { } fun eightBall(plugin: Plugin, name: String): String { - val responses = plugin.memory.data.getStringList("8-ball-responses") + val responses = plugin.eightball.data.getStringList("responses") val rand = Random().nextInt(responses.count()) return "$name - ${responses[rand]}" } - fun f(plugin: Plugin, name: String): String { - var respects = plugin.memory.data.getInt("respects", 0) - val msg: String - val payed = Random().nextInt(100) - when { - payed == 99 -> { - respects += 5 - msg = "$name breaks down and mournfully cries 5 respects out to the sky! (Total respects paid: $respects)" - } - payed > 95 -> { - respects += 3 - msg = "$name manages to give 3 respects through their heavy sobbing! (Total respects paid: $respects)" - } - payed > 65 -> { - respects += 2 - msg = "$name sheds a single tear and pays 2 respects! (Total respects paid: $respects)" - } - else -> { - respects += 1 - msg = "$name solemnly pays respect! (Total respects paid: $respects)" + fun f(plugin: Plugin, sender_name: String): String { + var totalRespects = plugin.f.data.getInt("total-respects", 0) + val responses = plugin.f.data.getList("responses").checkItemsAre() + ?: return "ERROR: Responses for this command could not be read from the config." + val totalWeight = responses.sumBy { it.weight } + var rand = Random().nextInt(totalWeight) + 1 + var found: Respect? = null + for(r in responses) { + rand -= r.weight + if (rand <= 0) { + found = r + break } } - plugin.memory.data.set("respects", respects) - plugin.memory.saveConfig() + + totalRespects += found!!.count + val msg = found.message.replace("%u", sender_name).replace("%t", totalRespects.toString()) + .replace("%c", found.count.toString()) + + plugin.f.data.set("total-respects", totalRespects) + plugin.f.saveConfig() return msg } - fun insult(plugin: Plugin, arg: String): String { - val responses = plugin.insults.data.getStringList("insults") - val rand = Random().nextInt(500) - return "$arg - ${responses[rand]}" + fun insult(plugin: Plugin, sender_name: String, arg: String): String { + val responses = plugin.insult.data.getStringList("responses") + val rand = Random().nextInt(responses.count()) + return plugin.insult.data.getString("template", "").replace("%u", sender_name) + .replace("%i", responses[rand]).replace("%t", arg) } - fun rate(name: String, arg: String): String { - val argArray = arg.split(" ").toMutableList() - val iterate = argArray.listIterator() - while (iterate.hasNext()) { - val oldValue = iterate.next() - if (oldValue == "me") iterate.set("you") - if (oldValue == "myself") iterate.set("yourself") - if (oldValue == "my") iterate.set("your") - if (oldValue == "your") iterate.set("my") - if (oldValue == "yourself") iterate.set("myself") - } - val thingToBeRated = argArray.joinToString(" ") - val rating = Random().nextInt(101) - var response = "I rate $thingToBeRated " - when (rating) { - 42 -> response += "4/20: 'smoke weed erryday'." - 78 -> response += "7.8/10: 'too much water'." - 69 -> response += "69/69." - 100 -> response += "a perfect 10/10, absolutely flawless in every regard." - in 90..99 -> response += "a very high ${rating/10f}/10." - in 80..89 -> response += "a high ${rating/10f}/10." - in 70..79 -> response += "a decently high ${rating/10f}/10." - in 60..69 -> response += "a good ${rating/10f}/10." - in 50..59 -> response += "a solid ${rating/10f}/10." - in 40..49 -> response += "a meh ${rating/10f}/10." - in 30..39 -> response += "a paltry ${rating/10f}/10." - in 20..29 -> response += "a pretty low ${rating/10f}/10." - in 10..19 -> response += "a low ${rating/10f}/10." - in 1..9 -> response += "a shitty ${rating/10f}/10." - else -> response += "0/0: 'amazing'." + fun rate(plugin: Plugin, sender_name: String, arg: String): String { + val responses = plugin.rate.data.getList("responses").checkItemsAre() + ?: return "ERROR: Responses for this command could not be read from the config." + + var rateOutOf = plugin.rate.data.getInt("rate-out-of", 10) + if (rateOutOf > 1000000) rateOutOf = 1000000 + if (rateOutOf < 0) rateOutOf = 0 + + var granularity = plugin.rate.data.getInt("granularity", 1) + if (granularity > 2) granularity = 2 + if (granularity < 0) granularity = 0 + + val conversionFactor = Math.pow(10.0, granularity.toDouble()) + val rating = Random().nextInt((rateOutOf * conversionFactor.toInt()) + 1) / conversionFactor + + + val found: Rating = responses.firstOrNull { rating <= it.high && rating >= it.low } + ?: return "ERROR: No response set for rating $rating" + + var thingToBeRated = arg + if (plugin.rate.data.getBoolean("translate-first-and-second-person", true)) { + val argArray = arg.split(" ").toMutableList() + val iterate = argArray.listIterator() + while (iterate.hasNext()) { + val oldValue = iterate.next() + if (oldValue == "me") iterate.set("you") + if (oldValue == "myself") iterate.set("yourself") + if (oldValue == "my") iterate.set("your") + if (oldValue == "your") iterate.set("my") + if (oldValue == "yourself") iterate.set("myself") + } + thingToBeRated = argArray.joinToString(" ") } - return "$name - $response" + + return found.message.replace("%u", sender_name).replace("%m", thingToBeRated).replace("%r", "$rating/$rateOutOf") } + // UTIL + + @Suppress("UNCHECKED_CAST") + inline fun List<*>.checkItemsAre() = if (all { it is T }) this as List else null + } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt b/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt index 446c835..965014b 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/DataConfigAccessor.kt @@ -46,7 +46,7 @@ class DataConfigAccessor(private val plugin: JavaPlugin, filepath: File, private } catch (e: IllegalArgumentException) { // Look for defaults in the jar if (plugin.getResource(fileName) == null) - plugin.logger.log(Level.SEVERE, "usernames.yml cannot be found for some reason") + plugin.logger.log(Level.SEVERE, "$fileName cannot be found for some reason") val defConfigReader = InputStreamReader(plugin.getResource(fileName)) val defConfig = YamlConfiguration.loadConfiguration(defConfigReader) fileConfiguration!!.defaults = defConfig @@ -67,7 +67,7 @@ class DataConfigAccessor(private val plugin: JavaPlugin, filepath: File, private try { data.save(configFile) } catch (ex: IOException) { - plugin.logger.log(Level.SEVERE, "Could not save data to $configFile", ex) + plugin.logger.log(Level.SEVERE, "Could not save data to $fileName", ex) } } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index 2026565..43cb165 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -1,8 +1,8 @@ package gg.obsidian.discordbridge -import gg.obsidian.discordbridge.Utils.noSpace -import gg.obsidian.discordbridge.Utils.UserAlias +import gg.obsidian.discordbridge.Utils.* import gg.obsidian.discordbridge.discord.Connection +import gg.obsidian.discordbridge.minecraft.EventListener import gg.obsidian.discordbridge.minecraft.commands.* import net.dv8tion.jda.core.OnlineStatus import net.dv8tion.jda.core.entities.Member @@ -10,6 +10,7 @@ import net.dv8tion.jda.core.entities.MessageChannel import org.bukkit.ChatColor as CC import org.bukkit.entity.Player import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.configuration.serialization.ConfigurationSerialization import java.util.logging.Level import java.io.File @@ -21,8 +22,11 @@ class Plugin : JavaPlugin() { // Configs val cfg = Configuration(this) var users: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "usernames.yml") - var memory: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "botmemory.yml") - var insults: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "insults.yml") + var eightball: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "8ball.yml") + var insult: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "insult.yml") + var f: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "f.yml") + var rate: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "rate.yml") + var script: DataConfigAccessor = DataConfigAccessor(this, dataFolder, "script.yml") var worlds: DataConfigAccessor? = null // Temporary storage for alias linking requests @@ -36,8 +40,11 @@ class Plugin : JavaPlugin() { // Load configs updateConfig(description.version) users.saveDefaultConfig() - memory.saveDefaultConfig() - insults.saveDefaultConfig() + eightball.saveDefaultConfig() + insult.saveDefaultConfig() + f.saveConfig() + rate.saveConfig() + script.saveConfig() if (foundMultiverse) worlds = DataConfigAccessor(this, File("plugins/Multiverse-Core"), "worlds.yml") // Connect to Discord @@ -51,6 +58,9 @@ class Plugin : JavaPlugin() { getCommand("rate").executor = Rate(this) getCommand("8ball").executor = EightBall(this) getCommand("insult").executor = Insult(this) + + ConfigurationSerialization.registerClass(Respect::class.java, "Respect") + ConfigurationSerialization.registerClass(Rating::class.java, "Rating") } override fun onDisable() { @@ -85,8 +95,11 @@ class Plugin : JavaPlugin() { fun reload() { reloadConfig() users.reloadConfig() - memory.reloadConfig() - insults.reloadConfig() + eightball.reloadConfig() + insult.reloadConfig() + f.reloadConfig() + rate.reloadConfig() + script.reloadConfig() if (foundMultiverse) worlds!!.reloadConfig() cfg.load() //conn?.reconnect() diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt index fe9fef1..ccf5a9c 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Utils/Utils.kt @@ -1,9 +1,61 @@ package gg.obsidian.discordbridge.Utils import org.bukkit.ChatColor +import org.bukkit.configuration.serialization.ConfigurationSerializable +import org.bukkit.configuration.serialization.SerializableAs fun String.noSpace() = this.replace(Regex("""\s+"""), "") fun String.stripColor(): String = ChatColor.stripColor(this) -data class UserAlias(var mcUsername: String, var mcUuid: String, - var discordUsername: String, var discordId: String) \ No newline at end of file +data class UserAlias(var mcUsername: String, var mcUuid: String, var discordUsername: String, var discordId: String) + +//TODO: make null safe +@SerializableAs("Respect") +data class Respect(val message: String = "%u solemnly pays respect!", val count: Int = 1, val weight: Int = 1): ConfigurationSerializable{ + @Suppress("unused") + constructor(map: MutableMap): + this(map["message"] as String, map["respects-paid"] as Int, map["weight"] as Int) + + override fun serialize(): MutableMap { + return mutableMapOf("message" to message, "respects-paid" to count, "weight" to weight) + } + +} + +//TODO: Make null safe +@SerializableAs("Rating") +data class Rating(val message: String = "%u - I rate %m %r", val low: Double = 0.0, val high: Double = 0.0): ConfigurationSerializable{ + @Suppress("unused") + constructor(map: MutableMap): + this(map["message"] as String, map["low"] as Double, map["high"] as Double) + + override fun serialize(): MutableMap { + return mutableMapOf("message" to message, "low" to low, "high" to high) + } + +} + +@SerializableAs("Script") +data class Script(val triggerMC: String?, val triggerDis: String?, val responseMC: String?, val responseDis: String?, + val caseSensitive: Boolean?, val requiresMention: Boolean?, + val startsWith: Boolean?): ConfigurationSerializable{ + @Suppress("unused") + constructor(map: MutableMap): + this(map["trigger-minecraft"] as? String, map["trigger-discord"] as? String, map["response-minecraft"] as? String, + map["response-discord"] as String, map["case-sensitive"] as? Boolean, map["requires-mention"] as? Boolean, + map["starts-with"] as? Boolean) + + override fun serialize(): MutableMap { + val map: MutableMap = mutableMapOf() + if (triggerMC != null) map.put("trigger-minecraft", triggerMC) + if (triggerDis != null) map.put("trigger-discord", triggerDis) + if (responseMC != null) map.put("respose-minecraft", responseMC) + if (responseDis != null) map.put("response-discord", responseDis) + if (caseSensitive != null) map.put("case-sensitive", caseSensitive) + if (requiresMention != null) map.put("requires-mention", requiresMention) + if (startsWith != null) map.put("starts-with", startsWith) + + return map + } + +} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt index 7b230d3..4c3935a 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/discord/Listener.kt @@ -5,6 +5,7 @@ import com.neovisionaries.ws.client.WebSocketException import com.neovisionaries.ws.client.WebSocketFrame import gg.obsidian.discordbridge.CommandLogic import gg.obsidian.discordbridge.Plugin +import gg.obsidian.discordbridge.Utils.Script import gg.obsidian.discordbridge.Utils.noSpace import net.dv8tion.jda.core.JDA import net.dv8tion.jda.core.entities.ChannelType @@ -27,6 +28,21 @@ class Listener(val plugin: Plugin, val api: JDA, val connection: Connection) : L return } + // SCRIPTED RESPONSE - The bot replies with a pre-programmed response if it detects + // a corresponding trigger string + val responses = plugin.script.data.getList("responses").checkItemsAre