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

View File

@ -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")

View File

@ -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<String>?,
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)

View File

@ -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) {

View File

@ -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")
}
}
}

View File

@ -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<Role>?
private val hasNoneOf: Set<Role>?
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<String>?,
hasNoneOf: List<String>?,
private val onlyDM: Boolean = false
) {
private val hasOneOf: Set<Role>? = hasOneOf?.mapTo(mutableSetOf(), Util::findRole)
private val hasNoneOf: Set<Role>? = hasNoneOf?.mapTo(mutableSetOf(), Util::findRole)
fun isAllowed(message: MessageCreateEvent): Boolean {
if (message.messageAuthor.isBotOwner) {

View File

@ -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.")

View File

@ -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<Command>
lateinit var commandConfig: Config
lateinit var features: Features
// for easier access
val commands: List<Command> get() = commandConfig[CommandSpec.command]
}

View File

@ -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) {

View File

@ -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?)

View File

@ -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<RawCommand>?,
@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<Command>())
}

View File

@ -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 }

View File

@ -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) {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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<Role> {
private val TIMEOUT_ROLE = mockk<Role> {
every { id } returns 123
}
val TEST_ROLE = mockk<Role> {
@ -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)) {