diff --git a/build.gradle.kts b/build.gradle.kts index e929430..ba2327b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation("org.mapdb:mapdb:3.0.7") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2") implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.50") + implementation("com.fasterxml.jackson.core:jackson-annotations:2.10.0.pr3") testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2") testImplementation("io.mockk:mockk:1.9.3") diff --git a/src/main/kotlin/moe/kageru/kagebot/command/Command.kt b/src/main/kotlin/moe/kageru/kagebot/command/Command.kt index 6d7f930..87eb446 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/Command.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/Command.kt @@ -1,12 +1,12 @@ package moe.kageru.kagebot.command +import com.fasterxml.jackson.annotation.JsonProperty import moe.kageru.kagebot.Globals import moe.kageru.kagebot.Log import moe.kageru.kagebot.MessageUtil import moe.kageru.kagebot.Util.applyIf import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.LocalizationSpec -import moe.kageru.kagebot.config.RawCommand import moe.kageru.kagebot.features.MessageFeature import org.javacord.api.entity.message.MessageAuthor import org.javacord.api.entity.message.embed.EmbedBuilder @@ -14,29 +14,23 @@ import org.javacord.api.event.message.MessageCreateEvent private const val AUTHOR_PLACEHOLDER = "@@" -class Command(cmd: RawCommand) { - val trigger: String - private val response: String? - val matchType: MatchType - private val permissions: Permissions? - private val actions: MessageActions? - val regex: Regex? - val embed: EmbedBuilder? - val feature: MessageFeature? - - init { - trigger = cmd.trigger ?: throw IllegalArgumentException("Every command must have a trigger.") - response = cmd.response - matchType = cmd.matchType?.let { type -> - MatchType.values().find { it.name.equals(type, ignoreCase = true) } - ?: throw IllegalArgumentException("Invalid [command.matchType]: “${cmd.matchType}”") - } ?: MatchType.PREFIX - permissions = cmd.permissions?.let { Permissions(it) } - actions = cmd.actions?.let { MessageActions(it) } - regex = if (matchType == MatchType.REGEX) Regex(trigger) else null - embed = cmd.embed?.let(MessageUtil::listToEmbed) - feature = cmd.feature?.let { Config.features.findByString(it) } - } +class Command( + val trigger: String, + private val response: String? = null, + private val permissions: Permissions?, + @JsonProperty("action") + private val actions: MessageActions?, + embed: List?, + feature: String?, + matchType: String? +) { + val matchType: MatchType = matchType?.let { type -> + MatchType.values().find { it.name.equals(type, ignoreCase = true) } + ?: throw IllegalArgumentException("Invalid [command.matchType]: “$matchType”") + } ?: MatchType.PREFIX + val regex: Regex? = if (this.matchType == MatchType.REGEX) Regex(trigger) else null + val embed: EmbedBuilder? = embed?.let(MessageUtil::listToEmbed) + private val feature: MessageFeature? = feature?.let { Config.features.findByString(it) } fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true @@ -69,6 +63,7 @@ class Command(cmd: RawCommand) { } } +@Suppress("unused") enum class MatchType { PREFIX { override fun matches(message: String, command: Command) = message.startsWith(command.trigger, ignoreCase = true) diff --git a/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt b/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt index 103cce9..1627812 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/MessageActions.kt @@ -1,16 +1,18 @@ package moe.kageru.kagebot.command +import com.fasterxml.jackson.annotation.JsonProperty import moe.kageru.kagebot.Log import moe.kageru.kagebot.MessageUtil.sendEmbed import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.LocalizationSpec -import moe.kageru.kagebot.config.RawMessageActions import org.javacord.api.event.message.MessageCreateEvent -class MessageActions(rawActions: RawMessageActions) { - private val delete: Boolean = rawActions.delete - private val redirect: MessageRedirect? = rawActions.redirect?.let(::MessageRedirect) - private val assignment: RoleAssignment? = rawActions.assign?.let(::RoleAssignment) +class MessageActions( + private val delete: Boolean = false, + private val redirect: MessageRedirect?, + @JsonProperty("assign") + private val assignment: RoleAssignment? +) { fun run(message: MessageCreateEvent, command: Command) { if (delete) { diff --git a/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt b/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt index d1e01ad..2da83fd 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/MessageRedirect.kt @@ -7,14 +7,11 @@ import moe.kageru.kagebot.Util.applyIf import moe.kageru.kagebot.Util.failed import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.LocalizationSpec -import moe.kageru.kagebot.config.RawRedirect import org.javacord.api.entity.channel.TextChannel import org.javacord.api.event.message.MessageCreateEvent -internal class MessageRedirect(rawRedirect: RawRedirect) { - private val target: TextChannel = rawRedirect.target?.let(Util::findChannel) - ?: throw IllegalArgumentException("Every redirect needs to have a target.") - private val anonymous: Boolean = rawRedirect.anonymous +class MessageRedirect(target: String, private val anonymous: Boolean = false) { + private val targetChannel: TextChannel = Util.findChannel(target) fun execute(message: MessageCreateEvent, command: Command) { val embed = MessageUtil.withEmbed { @@ -35,9 +32,9 @@ internal class MessageRedirect(rawRedirect: RawRedirect) { } } - if (MessageUtil.sendEmbed(target, embed).failed()) { - target.sendMessage("Error: could not redirect message.") - Log.warn("Could not redirect message to channel $target") + if (MessageUtil.sendEmbed(targetChannel, embed).failed()) { + targetChannel.sendMessage("Error: could not redirect message.") + Log.warn("Could not redirect message to channel $targetChannel") } } } diff --git a/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt b/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt index 7052b9c..fbc1087 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/Permissions.kt @@ -1,20 +1,16 @@ package moe.kageru.kagebot.command import moe.kageru.kagebot.Util -import moe.kageru.kagebot.config.RawPermissions import org.javacord.api.entity.permission.Role import org.javacord.api.event.message.MessageCreateEvent -class Permissions(perms: RawPermissions) { - private val hasOneOf: Set? - private val hasNoneOf: Set? - private val onlyDM: Boolean - - init { - hasOneOf = perms.hasOneOf?.mapTo(mutableSetOf(), Util::findRole) - hasNoneOf = perms.hasNoneOf?.mapTo(mutableSetOf(), Util::findRole) - onlyDM = perms.onlyDM - } +class Permissions( + hasOneOf: List?, + hasNoneOf: List?, + private val onlyDM: Boolean = false +) { + private val hasOneOf: Set? = hasOneOf?.mapTo(mutableSetOf(), Util::findRole) + private val hasNoneOf: Set? = hasNoneOf?.mapTo(mutableSetOf(), Util::findRole) fun isAllowed(message: MessageCreateEvent): Boolean { if (message.messageAuthor.isBotOwner) { diff --git a/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt b/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt index 3120287..c3d28a7 100644 --- a/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt +++ b/src/main/kotlin/moe/kageru/kagebot/command/RoleAssignment.kt @@ -1,15 +1,13 @@ package moe.kageru.kagebot.command +import com.fasterxml.jackson.annotation.JsonProperty import moe.kageru.kagebot.Log import moe.kageru.kagebot.Util import moe.kageru.kagebot.Util.getUser -import moe.kageru.kagebot.config.RawAssignment import org.javacord.api.event.message.MessageCreateEvent -internal class RoleAssignment(rawAssignment: RawAssignment) { - private val role = rawAssignment.role?.let { idOrName -> - Util.findRole(idOrName) - } ?: throw IllegalArgumentException("Can’t find role “${rawAssignment.role}”") +class RoleAssignment(@JsonProperty("role") role: String) { + private val role = Util.findRole(role) fun assign(message: MessageCreateEvent) = message.getUser()?.addRole(role, "Requested via command.") diff --git a/src/main/kotlin/moe/kageru/kagebot/config/Config.kt b/src/main/kotlin/moe/kageru/kagebot/config/Config.kt index 766de74..fae3055 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/Config.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/Config.kt @@ -9,9 +9,12 @@ import org.javacord.api.entity.server.Server object Config { val systemSpec = Config { addSpec(SystemSpec) }.from.toml val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml + val commandSpec = Config { addSpec(CommandSpec) }.from.toml lateinit var system: Config lateinit var localization: Config lateinit var server: Server - lateinit var commands: List + lateinit var commandConfig: Config lateinit var features: Features + // for easier access + val commands: List get() = commandConfig[CommandSpec.command] } diff --git a/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt b/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt index b469eab..cce3bc9 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt @@ -1,7 +1,6 @@ package moe.kageru.kagebot.config import moe.kageru.kagebot.Globals -import moe.kageru.kagebot.command.Command import moe.kageru.kagebot.config.SystemSpec.serverId import moe.kageru.kagebot.features.Features import java.io.File @@ -18,12 +17,7 @@ object ConfigParser { .orElseThrow { IllegalArgumentException("Invalid server configured.") } Config.localization = Config.localeSpec.file(configFile) reloadFeatures(rawConfig) - reloadCommands(rawConfig) - } - - fun reloadCommands(rawConfig: RawConfig) { - Config.commands = rawConfig.commands?.map(::Command)?.toMutableList() - ?: throw IllegalArgumentException("No commands found in config.") + Config.commandConfig = Config.commandSpec.file(configFile) } fun reloadFeatures(rawConfig: RawConfig) { diff --git a/src/main/kotlin/moe/kageru/kagebot/config/RawCommands.kt b/src/main/kotlin/moe/kageru/kagebot/config/RawCommands.kt deleted file mode 100644 index 062c7d0..0000000 --- a/src/main/kotlin/moe/kageru/kagebot/config/RawCommands.kt +++ /dev/null @@ -1,19 +0,0 @@ -package moe.kageru.kagebot.config - -import com.google.gson.annotations.SerializedName - -class RawCommand( - val trigger: String?, - val response: String?, - val matchType: String?, - val permissions: RawPermissions?, - @SerializedName("action") - val actions: RawMessageActions?, - val embed: List?, - val feature: String? -) - -class RawPermissions(val hasOneOf: List?, val hasNoneOf: List?, val onlyDM: Boolean) -class RawMessageActions(val delete: Boolean, val redirect: RawRedirect?, val assign: RawAssignment?) -class RawRedirect(val target: String?, val anonymous: Boolean) -class RawAssignment(var role: String?) diff --git a/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt b/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt index 3ab1795..f2b882f 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt @@ -3,16 +3,12 @@ package moe.kageru.kagebot.config import com.google.gson.annotations.SerializedName import com.moandjiezana.toml.Toml import com.uchuhimo.konf.ConfigSpec +import moe.kageru.kagebot.command.Command import moe.kageru.kagebot.config.Config.system import java.awt.Color import java.io.File -class RawConfig( - @SerializedName("command") - val commands: List?, - @SerializedName("feature") - val features: RawFeatures? -) { +class RawConfig(@SerializedName("feature") val features: RawFeatures?) { companion object { const val DEFAULT_CONFIG_PATH = "config.toml" @@ -46,3 +42,7 @@ object LocalizationSpec : ConfigSpec() { val messageDeleted by optional("Your message was deleted.") val timeout by optional("You have been timed out for @@ minutes.") } + +object CommandSpec : ConfigSpec(prefix = "") { + val command by optional(emptyList()) +} diff --git a/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt index 1c432ad..82e0e2e 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/HelpFeature.kt @@ -15,5 +15,4 @@ class HelpFeature : MessageFeature { private fun listCommands(message: MessageCreateEvent) = Config.commands .filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) } - .map { it.trigger } - .joinToString("\n") + .joinToString("\n") { it.trigger } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt index 17c8235..128d43c 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt @@ -25,7 +25,7 @@ class SetConfigFeature : MessageFeature { try { Config.localization = Config.localeSpec.string(newConfig) ConfigParser.reloadFeatures(rawConfig) - ConfigParser.reloadCommands(rawConfig) + Config.commandConfig = Config.commandSpec.string(newConfig) ConfigParser.configFile.writeText(newConfig) message.channel.sendMessage("Config reloaded.") } catch (e: IllegalArgumentException) { diff --git a/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt index f87704f..3a9e38f 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt @@ -44,8 +44,8 @@ class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeatu rawWelcome.content?.let(MessageUtil::listToEmbed) } private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let { - if (rawWelcome.fallbackMessage == null) { - throw IllegalArgumentException("[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined") + requireNotNull(rawWelcome.fallbackMessage) { + "[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined" } Util.findChannel(it) } diff --git a/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt b/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt index 527820c..def7346 100644 --- a/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt +++ b/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt @@ -29,9 +29,6 @@ class ConfigTest : ShouldSpec({ redirectedMessage = "says" messageDeleted = "dongered" timeout = "timeout" - - [[command]] - response = "this command is broken" """.trimIndent() val message = TestUtil.mockMessage("anything") every { message.messageAttachments } returns listOf(mockk { diff --git a/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt b/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt index 14cf93f..cea6808 100644 --- a/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt +++ b/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt @@ -10,7 +10,6 @@ import io.mockk.mockk import moe.kageru.kagebot.Kagebot.process import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.ConfigParser -import moe.kageru.kagebot.config.RawConfig import org.javacord.api.DiscordApi import org.javacord.api.entity.channel.ServerTextChannel import org.javacord.api.entity.message.embed.EmbedBuilder @@ -22,7 +21,7 @@ import java.io.File import java.util.* object TestUtil { - val TIMEOUT_ROLE = mockk { + private val TIMEOUT_ROLE = mockk { every { id } returns 123 } val TEST_ROLE = mockk { @@ -117,11 +116,10 @@ object TestUtil { } fun withCommands(config: String, test: (() -> Unit)) { - val oldCmds = Config.commands - val rawConfig = RawConfig.readFromString(config) - ConfigParser.reloadCommands(rawConfig) + val oldCmds = Config.commandConfig + Config.commandConfig = Config.commandSpec.string(config) test() - Config.commands = oldCmds + Config.commandConfig = oldCmds } fun withLocalization(config: String, test: (() -> Unit)) {