diff --git a/build.gradle b/build.gradle index b7c8c60..ce9bd56 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ import org.apache.tools.ant.filters.ReplaceTokens buildscript { - ext.kotlin_version = '1.1.51' + ext.kotlin_version = '1.2.0' repositories { mavenCentral() @@ -22,7 +22,7 @@ plugins { apply plugin: 'kotlin' group = 'gg.obsidian' -version = '3.0.1' +version = '3.1.0' description = """Bridge chat between Minecraft and Discord""" ext.url = 'https://github.com/the-obsidian/DiscordBridge' diff --git a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt index adf5411..b84c691 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/Plugin.kt @@ -136,7 +136,7 @@ class Plugin : JavaPlugin() { /** * Saves all default configs where configs do not exist and reloads data from file into memory */ - fun updateConfig(version: String) { + private fun updateConfig(version: String) { this.saveDefaultConfig() config.options().copyDefaults(true) config.set("version", version) @@ -182,7 +182,7 @@ class Plugin : JavaPlugin() { val users = Connection.listUsers() val found: Member = users.find { it.user.name + "#" + it.user.discriminator == discriminator } ?: return null - val ua: UserAlias = UserAlias(player.uniqueId, found.user.id) + val ua = UserAlias(player.uniqueId, found.user.id) requests.add(ua) val msg = "Minecraft user '${server.getOfflinePlayer(ua.mcUuid).name}' has requested to become associated with your Discord" + " account. If this is you, respond '${Connection.JDA.selfUser.asMention} confirm'. If this is not" + @@ -203,8 +203,8 @@ class Plugin : JavaPlugin() { var response = "${CC.YELLOW}Discord users:" for (user in users) { - if (user.user.isBot) response += "\n${CC.GOLD}- ${user.effectiveName} (Bot) | ${user.user.name}#${user.user.discriminator}${CC.RESET}" - else response += "\n${CC.YELLOW}- ${user.effectiveName} | ${user.user.name}#${user.user.discriminator}${CC.RESET}" + response += if (user.user.isBot) "\n${CC.GOLD}- ${user.effectiveName} (Bot) | ${user.user.name}#${user.user.discriminator}${CC.RESET}" + else "\n${CC.YELLOW}- ${user.effectiveName} | ${user.user.name}#${user.user.discriminator}${CC.RESET}" } return response.trim() } @@ -218,25 +218,25 @@ class Plugin : JavaPlugin() { 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()) { + if (onlineUsers.any { it.onlineStatus == OnlineStatus.ONLINE }) { 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}" + response += if (user.user.isBot) "\n${CC.DARK_GREEN}- ${user.effectiveName} (Bot)${CC.RESET}" + else "\n${CC.DARK_GREEN}- ${user.effectiveName}${CC.RESET}" } } - if (onlineUsers.filter { it.onlineStatus == OnlineStatus.IDLE }.isNotEmpty()) { + if (onlineUsers.any { it.onlineStatus == OnlineStatus.IDLE }) { 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}" + response += if (user.user.isBot) "\n${CC.YELLOW}- ${user.effectiveName} (Bot)${CC.RESET}" + else "\n${CC.YELLOW}- ${user.effectiveName}${CC.RESET}" } } - if (onlineUsers.filter { it.onlineStatus == OnlineStatus.DO_NOT_DISTURB }.isNotEmpty()) { + if (onlineUsers.any { it.onlineStatus == OnlineStatus.DO_NOT_DISTURB }) { 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 += if (user.user.isBot) "\n${CC.RED}- ${user.effectiveName} (Bot)${CC.RESET}" + else "\n${CC.RED}- ${user.effectiveName}${CC.RESET}" } } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/UserAliasConfig.kt b/src/main/kotlin/gg/obsidian/discordbridge/UserAliasConfig.kt index 2398afa..05e8b41 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/UserAliasConfig.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/UserAliasConfig.kt @@ -13,7 +13,7 @@ object UserAliasConfig { */ fun load(plugin: Plugin) { val list = plugin.users.data.getList("aliases") - if (list != null) aliases = list.checkItemsAre() ?: + if (list != null) aliases = list.checkItemsAre() ?: throw IllegalStateException("usernames.yml could not be read - list items are not properly formatted") else mutableListOf() } @@ -42,5 +42,5 @@ object UserAliasConfig { * A function to assert that all the items in a given list are of a specific type */ @Suppress("UNCHECKED_CAST") - inline fun List<*>.checkItemsAre() = if (all { it is T }) this as List else null + private inline fun List<*>.checkItemsAre() = if (all { it is T }) this as List else null } diff --git a/src/main/kotlin/gg/obsidian/discordbridge/commands/DiscordCommandSender.kt b/src/main/kotlin/gg/obsidian/discordbridge/commands/DiscordCommandSender.kt new file mode 100644 index 0000000..8658bcf --- /dev/null +++ b/src/main/kotlin/gg/obsidian/discordbridge/commands/DiscordCommandSender.kt @@ -0,0 +1,96 @@ +package gg.obsidian.discordbridge.commands + +import net.dv8tion.jda.core.entities.MessageChannel +import org.bukkit.Bukkit +import org.bukkit.Server +import org.bukkit.command.CommandSender +import org.bukkit.command.ConsoleCommandSender +import org.bukkit.command.RemoteConsoleCommandSender +import org.bukkit.permissions.Permission +import org.bukkit.permissions.PermissionAttachment +import org.bukkit.permissions.PermissionAttachmentInfo +import org.bukkit.plugin.Plugin + +class DiscordCommandSender(val channel: MessageChannel) : RemoteConsoleCommandSender { + + private val sender:ConsoleCommandSender = Bukkit.getServer().consoleSender + + init { + + } + + override fun sendMessage(message: String?) { + channel.sendMessage(message).queue() + } + + override fun sendMessage(messages: Array?) { + if (messages != null) + for (m in messages) channel.sendMessage(m) + } + + override fun spigot(): CommandSender.Spigot { + return sender.spigot() + } + + override fun addAttachment(plugin: Plugin?): PermissionAttachment { + return sender.addAttachment(plugin) + } + + override fun addAttachment(plugin: Plugin?, ticks: Int): PermissionAttachment { + return sender.addAttachment(plugin, ticks) + } + + override fun addAttachment(plugin: Plugin?, name: String?, value: Boolean): PermissionAttachment { + return sender.addAttachment(plugin, name, value) + } + + override fun addAttachment(plugin: Plugin?, name: String?, value: Boolean, ticks: Int): PermissionAttachment { + return sender.addAttachment(plugin, name, value, ticks) + } + + override fun getEffectivePermissions(): MutableSet { + return sender.effectivePermissions + } + + override fun getName(): String { + return sender.name + } + + override fun getServer(): Server { + return sender.server + } + + override fun hasPermission(name: String?): Boolean { + return sender.hasPermission(name) + } + + override fun hasPermission(perm: Permission?): Boolean { + return sender.hasPermission(perm) + } + + override fun isOp(): Boolean { + return sender.isOp + } + + override fun isPermissionSet(name: String?): Boolean { + return sender.isPermissionSet(name) + } + + override fun isPermissionSet(perm: Permission?): Boolean { + return sender.isPermissionSet(perm) + } + + override fun recalculatePermissions() { + return sender.recalculatePermissions() + } + + override fun removeAttachment(attachment: PermissionAttachment?) { + return sender.removeAttachment(attachment) + } + + override fun setOp(value: Boolean) { + return sender.setOp(value) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/gg/obsidian/discordbridge/commands/annotations/BotCommand.kt b/src/main/kotlin/gg/obsidian/discordbridge/commands/annotations/BotCommand.kt index ed1de34..766751f 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/commands/annotations/BotCommand.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/commands/annotations/BotCommand.kt @@ -7,9 +7,9 @@ package gg.obsidian.discordbridge.commands.annotations * @param description a short string that describes the command's function * @param name an optional field to override the command's access name if it is not the same as the method name * @param relayTriggerMessage whether the message used to trigger this command should be relayed - * @param ignoreExcessArguments if false, this command will fail if the invoker provides too many arguments + * @param squishExcessArgs if true, this command will put all extra args passed to it into a single string */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) annotation class BotCommand(val usage: String, val description: String, val name: String = "", - val relayTriggerMessage: Boolean = true, val ignoreExcessArguments: Boolean = true) + val relayTriggerMessage: Boolean = true, val squishExcessArgs: Boolean = false) diff --git a/src/main/kotlin/gg/obsidian/discordbridge/commands/controllers/BotControllerManager.kt b/src/main/kotlin/gg/obsidian/discordbridge/commands/controllers/BotControllerManager.kt index 2a40453..8f09c83 100644 --- a/src/main/kotlin/gg/obsidian/discordbridge/commands/controllers/BotControllerManager.kt +++ b/src/main/kotlin/gg/obsidian/discordbridge/commands/controllers/BotControllerManager.kt @@ -11,6 +11,8 @@ import gg.obsidian.discordbridge.utils.UtilFunctions.noSpace import gg.obsidian.discordbridge.utils.UtilFunctions.stripColor import gg.obsidian.discordbridge.utils.UtilFunctions.toDiscordChatMessage import gg.obsidian.discordbridge.utils.UtilFunctions.toMinecraftChatMessage +import net.dv8tion.jda.core.Permission +import org.bukkit.Bukkit import java.lang.reflect.Method import java.util.* import java.util.logging.Level @@ -29,6 +31,9 @@ class BotControllerManager(val plugin: Plugin) { private val commands: MutableMap = mutableMapOf() private val controllers: MutableMap, IBotController> = mutableMapOf() + // ============================================= + // =============== SETUP METHODS =============== + /** * Adds an IBotController to the manager. * @@ -71,14 +76,17 @@ class BotControllerManager(val plugin: Plugin) { if (methodParameters.isEmpty() || !methodParameters[0].type.isAssignableFrom(IEventWrapper::class.java)) return method.isAccessible = true - val parameters = (1..methodParameters.size - 1).mapTo(ArrayList>()) { methodParameters[it].type } + val parameters = (1 until methodParameters.size).mapTo(ArrayList>()) { methodParameters[it].type } val isTagged: Boolean = method.getAnnotation(TaggedResponse::class.java) != null val isPrivate: Boolean = method.getAnnotation(PrivateResponse::class.java) != null val command = Command(commandName, usage, annotation.description, parameters, annotation.relayTriggerMessage, - annotation.ignoreExcessArguments, isTagged, isPrivate, controllerClass, method) + annotation.squishExcessArgs, isTagged, isPrivate, controllerClass, method) commands.put(command.name, command) } + // ============================================= + // ============== MESSAGE HANDLING ============= + /** * Reads an incoming message and attempts to parse and execute a command. * @@ -94,58 +102,31 @@ class BotControllerManager(val plugin: Plugin) { commandNotFound(event, event.command.name) return true } - val inputArguments = event.rawMessage.split("\\s+".toRegex(), command.parameters.size).toTypedArray() - return invokeCommand(command, controllers, event, inputArguments) + return invokeBotCommand(command, controllers, event, event.args.asList().toTypedArray()) } // Short circuit scripted responses - if (scriptedResponse(event)) return true - - val args = event.rawMessage.trim().split("\\s+".toRegex(), 2).toTypedArray() + if (sendScriptedResponse(event)) return true // command - if (Config.COMMAND_PREFIX.isNotBlank() && args[0].startsWith(Config.COMMAND_PREFIX)) { - val commandName = args[0].substring(Config.COMMAND_PREFIX.length).toLowerCase() - if (commandName == "") return true - val command = commands[commandName] - - if (command == null) { - commandNotFound(event, commandName) - return false - } - - val inputArguments = if (args.size == 1) arrayOf() - else args[1].split("\\s+".toRegex(), command.parameters.size).toTypedArray() - - return invokeCommand(command, controllers, event, inputArguments) + if (Config.COMMAND_PREFIX.isNotBlank() && event.rawMessage.startsWith(Config.COMMAND_PREFIX)) { + val split = event.rawMessage.replaceFirst(Config.COMMAND_PREFIX, "").trim().split("\\s+".toRegex()).toTypedArray() + return parseCommand(event, split, false) } - // @ command - if ((event is AsyncPlayerChatEventWrapper && args[0] == "@"+Config.USERNAME.noSpace() || - args[0] == Connection.JDA.selfUser.asMention) && args.count() == 2) { - val args2 = args[1].split("\\s+".toRegex(), 2).toTypedArray() - val commandName = args2[0].toLowerCase() - if (commandName == "") return true - var params = if (args2.size > 1) args2[1] else "" - var command = commands[commandName] - - if (command == null) { - // Assume user wants to talk to Cleverbot - command = commands["talk"] - if (command == null) { - commandNotFound(event, commandName) - return false - } - params = args[1] - } - - val inputArguments = if (params == "") arrayOf() - else params.split("\\s+".toRegex(), command.parameters.size).toTypedArray() + // @ command from Minecraft + if (event is AsyncPlayerChatEventWrapper && event.rawMessage.startsWith("@${Config.USERNAME.noSpace()} ")) { + val split = event.rawMessage.replaceFirst("@${Config.USERNAME.noSpace()} ", "").trim().split("\\s+".toRegex()).toTypedArray() + return parseCommand(event, split, true) + } - return invokeCommand(command, controllers, event, inputArguments) + // @ command from Discord + if (event is MessageWrapper && event.rawMessage.startsWith(Connection.JDA.selfUser.asMention + " ")) { + val split = event.rawMessage.replaceFirst(Connection.JDA.selfUser.asMention + " ", "").trim().split("\\s+".toRegex()).toTypedArray() + return parseCommand(event, split, true) } - // Just relay the message if it is neither + // Just relay the message if no command is found relay(event, true) return true } @@ -156,7 +137,7 @@ class BotControllerManager(val plugin: Plugin) { * @param event the incoming event object * @return true if a trigger was found and successfully responded to, false otherwise */ - private fun scriptedResponse(event: IEventWrapper): Boolean { + private fun sendScriptedResponse(event: IEventWrapper): Boolean { val responses = plugin.script.data.getList("responses").checkItemsAre