Rewrite config to use Konf (3): Commands

This commit is contained in:
kageru 2019-10-18 20:48:43 +02:00
parent 17c7120796
commit bb03474bf5
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
15 changed files with 61 additions and 100 deletions

@ -42,6 +42,7 @@ dependencies {
implementation("org.mapdb:mapdb:3.0.7") implementation("org.mapdb:mapdb:3.0.7")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.3.50") 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.kotlintest:kotlintest-runner-junit5:3.4.2")
testImplementation("io.mockk:mockk:1.9.3") testImplementation("io.mockk:mockk:1.9.3")

@ -1,12 +1,12 @@
package moe.kageru.kagebot.command package moe.kageru.kagebot.command
import com.fasterxml.jackson.annotation.JsonProperty
import moe.kageru.kagebot.Globals import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.Log import moe.kageru.kagebot.Log
import moe.kageru.kagebot.MessageUtil import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util.applyIf import moe.kageru.kagebot.Util.applyIf
import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.LocalizationSpec import moe.kageru.kagebot.config.LocalizationSpec
import moe.kageru.kagebot.config.RawCommand
import moe.kageru.kagebot.features.MessageFeature import moe.kageru.kagebot.features.MessageFeature
import org.javacord.api.entity.message.MessageAuthor import org.javacord.api.entity.message.MessageAuthor
import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.entity.message.embed.EmbedBuilder
@ -14,29 +14,23 @@ import org.javacord.api.event.message.MessageCreateEvent
private const val AUTHOR_PLACEHOLDER = "@@" private const val AUTHOR_PLACEHOLDER = "@@"
class Command(cmd: RawCommand) { class Command(
val trigger: String val trigger: String,
private val response: String? private val response: String? = null,
val matchType: MatchType private val permissions: Permissions?,
private val permissions: Permissions? @JsonProperty("action")
private val actions: MessageActions? private val actions: MessageActions?,
val regex: Regex? embed: List<String>?,
val embed: EmbedBuilder? feature: String?,
val feature: MessageFeature? matchType: String?
) {
init { val matchType: MatchType = matchType?.let { type ->
trigger = cmd.trigger ?: throw IllegalArgumentException("Every command must have a trigger.") MatchType.values().find { it.name.equals(type, ignoreCase = true) }
response = cmd.response ?: throw IllegalArgumentException("Invalid [command.matchType]: “$matchType")
matchType = cmd.matchType?.let { type -> } ?: MatchType.PREFIX
MatchType.values().find { it.name.equals(type, ignoreCase = true) } val regex: Regex? = if (this.matchType == MatchType.REGEX) Regex(trigger) else null
?: throw IllegalArgumentException("Invalid [command.matchType]: “${cmd.matchType}") val embed: EmbedBuilder? = embed?.let(MessageUtil::listToEmbed)
} ?: MatchType.PREFIX private val feature: MessageFeature? = feature?.let { Config.features.findByString(it) }
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) }
}
fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true
@ -69,6 +63,7 @@ class Command(cmd: RawCommand) {
} }
} }
@Suppress("unused")
enum class MatchType { enum class MatchType {
PREFIX { PREFIX {
override fun matches(message: String, command: Command) = message.startsWith(command.trigger, ignoreCase = true) override fun matches(message: String, command: Command) = message.startsWith(command.trigger, ignoreCase = true)

@ -1,16 +1,18 @@
package moe.kageru.kagebot.command package moe.kageru.kagebot.command
import com.fasterxml.jackson.annotation.JsonProperty
import moe.kageru.kagebot.Log import moe.kageru.kagebot.Log
import moe.kageru.kagebot.MessageUtil.sendEmbed import moe.kageru.kagebot.MessageUtil.sendEmbed
import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.LocalizationSpec import moe.kageru.kagebot.config.LocalizationSpec
import moe.kageru.kagebot.config.RawMessageActions
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
class MessageActions(rawActions: RawMessageActions) { class MessageActions(
private val delete: Boolean = rawActions.delete private val delete: Boolean = false,
private val redirect: MessageRedirect? = rawActions.redirect?.let(::MessageRedirect) private val redirect: MessageRedirect?,
private val assignment: RoleAssignment? = rawActions.assign?.let(::RoleAssignment) @JsonProperty("assign")
private val assignment: RoleAssignment?
) {
fun run(message: MessageCreateEvent, command: Command) { fun run(message: MessageCreateEvent, command: Command) {
if (delete) { if (delete) {

@ -7,14 +7,11 @@ import moe.kageru.kagebot.Util.applyIf
import moe.kageru.kagebot.Util.failed import moe.kageru.kagebot.Util.failed
import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.LocalizationSpec import moe.kageru.kagebot.config.LocalizationSpec
import moe.kageru.kagebot.config.RawRedirect
import org.javacord.api.entity.channel.TextChannel import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
internal class MessageRedirect(rawRedirect: RawRedirect) { class MessageRedirect(target: String, private val anonymous: Boolean = false) {
private val target: TextChannel = rawRedirect.target?.let(Util::findChannel) private val targetChannel: TextChannel = Util.findChannel(target)
?: throw IllegalArgumentException("Every redirect needs to have a target.")
private val anonymous: Boolean = rawRedirect.anonymous
fun execute(message: MessageCreateEvent, command: Command) { fun execute(message: MessageCreateEvent, command: Command) {
val embed = MessageUtil.withEmbed { val embed = MessageUtil.withEmbed {
@ -35,9 +32,9 @@ internal class MessageRedirect(rawRedirect: RawRedirect) {
} }
} }
if (MessageUtil.sendEmbed(target, embed).failed()) { if (MessageUtil.sendEmbed(targetChannel, embed).failed()) {
target.sendMessage("Error: could not redirect message.") targetChannel.sendMessage("Error: could not redirect message.")
Log.warn("Could not redirect message to channel $target") Log.warn("Could not redirect message to channel $targetChannel")
} }
} }
} }

@ -1,20 +1,16 @@
package moe.kageru.kagebot.command package moe.kageru.kagebot.command
import moe.kageru.kagebot.Util import moe.kageru.kagebot.Util
import moe.kageru.kagebot.config.RawPermissions
import org.javacord.api.entity.permission.Role import org.javacord.api.entity.permission.Role
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
class Permissions(perms: RawPermissions) { class Permissions(
private val hasOneOf: Set<Role>? hasOneOf: List<String>?,
private val hasNoneOf: Set<Role>? hasNoneOf: List<String>?,
private val onlyDM: Boolean private val onlyDM: Boolean = false
) {
init { private val hasOneOf: Set<Role>? = hasOneOf?.mapTo(mutableSetOf(), Util::findRole)
hasOneOf = perms.hasOneOf?.mapTo(mutableSetOf(), Util::findRole) private val hasNoneOf: Set<Role>? = hasNoneOf?.mapTo(mutableSetOf(), Util::findRole)
hasNoneOf = perms.hasNoneOf?.mapTo(mutableSetOf(), Util::findRole)
onlyDM = perms.onlyDM
}
fun isAllowed(message: MessageCreateEvent): Boolean { fun isAllowed(message: MessageCreateEvent): Boolean {
if (message.messageAuthor.isBotOwner) { if (message.messageAuthor.isBotOwner) {

@ -1,15 +1,13 @@
package moe.kageru.kagebot.command package moe.kageru.kagebot.command
import com.fasterxml.jackson.annotation.JsonProperty
import moe.kageru.kagebot.Log import moe.kageru.kagebot.Log
import moe.kageru.kagebot.Util import moe.kageru.kagebot.Util
import moe.kageru.kagebot.Util.getUser import moe.kageru.kagebot.Util.getUser
import moe.kageru.kagebot.config.RawAssignment
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
internal class RoleAssignment(rawAssignment: RawAssignment) { class RoleAssignment(@JsonProperty("role") role: String) {
private val role = rawAssignment.role?.let { idOrName -> private val role = Util.findRole(role)
Util.findRole(idOrName)
} ?: throw IllegalArgumentException("Can’t find role “${rawAssignment.role}")
fun assign(message: MessageCreateEvent) = fun assign(message: MessageCreateEvent) =
message.getUser()?.addRole(role, "Requested via command.") message.getUser()?.addRole(role, "Requested via command.")

@ -9,9 +9,12 @@ import org.javacord.api.entity.server.Server
object Config { object Config {
val systemSpec = Config { addSpec(SystemSpec) }.from.toml val systemSpec = Config { addSpec(SystemSpec) }.from.toml
val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml
val commandSpec = Config { addSpec(CommandSpec) }.from.toml
lateinit var system: Config lateinit var system: Config
lateinit var localization: Config lateinit var localization: Config
lateinit var server: Server lateinit var server: Server
lateinit var commands: List<Command> lateinit var commandConfig: Config
lateinit var features: Features lateinit var features: Features
// for easier access
val commands: List<Command> get() = commandConfig[CommandSpec.command]
} }

@ -1,7 +1,6 @@
package moe.kageru.kagebot.config package moe.kageru.kagebot.config
import moe.kageru.kagebot.Globals import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.command.Command
import moe.kageru.kagebot.config.SystemSpec.serverId import moe.kageru.kagebot.config.SystemSpec.serverId
import moe.kageru.kagebot.features.Features import moe.kageru.kagebot.features.Features
import java.io.File import java.io.File
@ -18,12 +17,7 @@ object ConfigParser {
.orElseThrow { IllegalArgumentException("Invalid server configured.") } .orElseThrow { IllegalArgumentException("Invalid server configured.") }
Config.localization = Config.localeSpec.file(configFile) Config.localization = Config.localeSpec.file(configFile)
reloadFeatures(rawConfig) reloadFeatures(rawConfig)
reloadCommands(rawConfig) Config.commandConfig = Config.commandSpec.file(configFile)
}
fun reloadCommands(rawConfig: RawConfig) {
Config.commands = rawConfig.commands?.map(::Command)?.toMutableList()
?: throw IllegalArgumentException("No commands found in config.")
} }
fun reloadFeatures(rawConfig: RawConfig) { fun reloadFeatures(rawConfig: RawConfig) {

@ -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<String>?,
val feature: String?
)
class RawPermissions(val hasOneOf: List<String>?, val hasNoneOf: List<String>?, 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?)

@ -3,16 +3,12 @@ package moe.kageru.kagebot.config
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import com.moandjiezana.toml.Toml import com.moandjiezana.toml.Toml
import com.uchuhimo.konf.ConfigSpec import com.uchuhimo.konf.ConfigSpec
import moe.kageru.kagebot.command.Command
import moe.kageru.kagebot.config.Config.system import moe.kageru.kagebot.config.Config.system
import java.awt.Color import java.awt.Color
import java.io.File import java.io.File
class RawConfig( class RawConfig(@SerializedName("feature") val features: RawFeatures?) {
@SerializedName("command")
val commands: List<RawCommand>?,
@SerializedName("feature")
val features: RawFeatures?
) {
companion object { companion object {
const val DEFAULT_CONFIG_PATH = "config.toml" const val DEFAULT_CONFIG_PATH = "config.toml"
@ -46,3 +42,7 @@ object LocalizationSpec : ConfigSpec() {
val messageDeleted by optional("Your message was deleted.") val messageDeleted by optional("Your message was deleted.")
val timeout by optional("You have been timed out for @@ minutes.") val timeout by optional("You have been timed out for @@ minutes.")
} }
object CommandSpec : ConfigSpec(prefix = "") {
val command by optional(emptyList<Command>())
}

@ -15,5 +15,4 @@ class HelpFeature : MessageFeature {
private fun listCommands(message: MessageCreateEvent) = Config.commands private fun listCommands(message: MessageCreateEvent) = Config.commands
.filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) } .filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) }
.map { it.trigger } .joinToString("\n") { it.trigger }
.joinToString("\n")

@ -25,7 +25,7 @@ class SetConfigFeature : MessageFeature {
try { try {
Config.localization = Config.localeSpec.string(newConfig) Config.localization = Config.localeSpec.string(newConfig)
ConfigParser.reloadFeatures(rawConfig) ConfigParser.reloadFeatures(rawConfig)
ConfigParser.reloadCommands(rawConfig) Config.commandConfig = Config.commandSpec.string(newConfig)
ConfigParser.configFile.writeText(newConfig) ConfigParser.configFile.writeText(newConfig)
message.channel.sendMessage("Config reloaded.") message.channel.sendMessage("Config reloaded.")
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {

@ -44,8 +44,8 @@ class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeatu
rawWelcome.content?.let(MessageUtil::listToEmbed) rawWelcome.content?.let(MessageUtil::listToEmbed)
} }
private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let { private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let {
if (rawWelcome.fallbackMessage == null) { requireNotNull(rawWelcome.fallbackMessage) {
throw IllegalArgumentException("[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined") "[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
} }
Util.findChannel(it) Util.findChannel(it)
} }

@ -29,9 +29,6 @@ class ConfigTest : ShouldSpec({
redirectedMessage = "says" redirectedMessage = "says"
messageDeleted = "dongered" messageDeleted = "dongered"
timeout = "timeout" timeout = "timeout"
[[command]]
response = "this command is broken"
""".trimIndent() """.trimIndent()
val message = TestUtil.mockMessage("anything") val message = TestUtil.mockMessage("anything")
every { message.messageAttachments } returns listOf(mockk { every { message.messageAttachments } returns listOf(mockk {

@ -10,7 +10,6 @@ import io.mockk.mockk
import moe.kageru.kagebot.Kagebot.process import moe.kageru.kagebot.Kagebot.process
import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.ConfigParser import moe.kageru.kagebot.config.ConfigParser
import moe.kageru.kagebot.config.RawConfig
import org.javacord.api.DiscordApi import org.javacord.api.DiscordApi
import org.javacord.api.entity.channel.ServerTextChannel import org.javacord.api.entity.channel.ServerTextChannel
import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.entity.message.embed.EmbedBuilder
@ -22,7 +21,7 @@ import java.io.File
import java.util.* import java.util.*
object TestUtil { object TestUtil {
val TIMEOUT_ROLE = mockk<Role> { private val TIMEOUT_ROLE = mockk<Role> {
every { id } returns 123 every { id } returns 123
} }
val TEST_ROLE = mockk<Role> { val TEST_ROLE = mockk<Role> {
@ -117,11 +116,10 @@ object TestUtil {
} }
fun withCommands(config: String, test: (() -> Unit)) { fun withCommands(config: String, test: (() -> Unit)) {
val oldCmds = Config.commands val oldCmds = Config.commandConfig
val rawConfig = RawConfig.readFromString(config) Config.commandConfig = Config.commandSpec.string(config)
ConfigParser.reloadCommands(rawConfig)
test() test()
Config.commands = oldCmds Config.commandConfig = oldCmds
} }
fun withLocalization(config: String, test: (() -> Unit)) { fun withLocalization(config: String, test: (() -> Unit)) {