Add features as configurable command output

This commit is contained in:
kageru 2019-07-11 21:05:35 +02:00
parent 731867f59e
commit 344148cd03
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
16 changed files with 73 additions and 108 deletions

@ -3,6 +3,7 @@ package moe.kageru.kagebot
import moe.kageru.kagebot.command.Command import moe.kageru.kagebot.command.Command
import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.SystemConfig import moe.kageru.kagebot.config.SystemConfig
import moe.kageru.kagebot.features.Features
import org.javacord.api.DiscordApi import org.javacord.api.DiscordApi
import org.javacord.api.entity.server.Server import org.javacord.api.entity.server.Server
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -13,5 +14,6 @@ object Globals {
lateinit var config: Config lateinit var config: Config
lateinit var commands: List<Command> lateinit var commands: List<Command>
lateinit var systemConfig: SystemConfig lateinit var systemConfig: SystemConfig
lateinit var features: Features
var commandCounter: AtomicInteger = AtomicInteger(0) var commandCounter: AtomicInteger = AtomicInteger(0)
} }

@ -4,7 +4,6 @@ import moe.kageru.kagebot.Log.log
import moe.kageru.kagebot.Util.checked import moe.kageru.kagebot.Util.checked
import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.RawConfig import moe.kageru.kagebot.config.RawConfig
import moe.kageru.kagebot.features.MessageFeature
import org.javacord.api.DiscordApiBuilder import org.javacord.api.DiscordApiBuilder
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
import org.javacord.api.event.server.member.ServerMemberJoinEvent import org.javacord.api.event.server.member.ServerMemberJoinEvent
@ -65,19 +64,10 @@ object Kagebot {
}) })
log.info("kagebot Mk II running") log.info("kagebot Mk II running")
Globals.api.addMessageCreateListener { checked { processMessage(it) } } Globals.api.addMessageCreateListener { checked { processMessage(it) } }
Globals.config.features.welcome?.let { welcome -> Globals.config.features.welcome?.let {
if (welcome.enabled) {
Globals.api.addServerMemberJoinListener { Globals.api.addServerMemberJoinListener {
checked { welcomeUser(it) } checked { welcomeUser(it) }
} }
} }
} }
for (feature in Globals.config.features.allWithMessage()) {
if (feature.commandEnabled) {
Globals.api.addMessageCreateListener {
checked { feature.handle(it) }
}
}
}
}
} }

@ -6,6 +6,7 @@ import moe.kageru.kagebot.Log.log
import moe.kageru.kagebot.MessageUtil import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util.doIf import moe.kageru.kagebot.Util.doIf
import moe.kageru.kagebot.config.RawCommand 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.MessageAuthor
import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.entity.message.embed.EmbedBuilder
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
@ -20,6 +21,7 @@ class Command(cmd: RawCommand) {
private val actions: MessageActions? private val actions: MessageActions?
val regex: Regex? val regex: Regex?
val embed: EmbedBuilder? val embed: EmbedBuilder?
val feature: MessageFeature?
init { init {
trigger = cmd.trigger ?: throw IllegalArgumentException("Every command must have a trigger.") trigger = cmd.trigger ?: throw IllegalArgumentException("Every command must have a trigger.")
@ -32,6 +34,7 @@ class Command(cmd: RawCommand) {
actions = cmd.actions?.let { MessageActions(it) } actions = cmd.actions?.let { MessageActions(it) }
regex = if (matchType == MatchType.REGEX) Regex(trigger) else null regex = if (matchType == MatchType.REGEX) Regex(trigger) else null
embed = cmd.embed?.let(MessageUtil::mapToEmbed) embed = cmd.embed?.let(MessageUtil::mapToEmbed)
feature = cmd.feature?.let { Globals.features.findByString(it) }
} }
fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true fun isAllowed(message: MessageCreateEvent) = permissions?.isAllowed(message) ?: true
@ -53,6 +56,7 @@ class Command(cmd: RawCommand) {
this.embed?.let { this.embed?.let {
message.channel.sendMessage(embed) message.channel.sendMessage(embed)
} }
this.feature?.handle(message)
} }
fun matches(msg: String) = this.matchType.matches(msg, this) fun matches(msg: String) = this.matchType.matches(msg, this)

@ -16,9 +16,11 @@ class Config(rawConfig: RawConfig) {
init { init {
Globals.systemConfig = system Globals.systemConfig = system
Globals.server = api.getServerById(system.serverId).orElseThrow() Globals.server = api.getServerById(system.serverId).orElseThrow()
Globals.features = rawConfig.features?.let(::Features) ?: Features(RawFeatures(null))
// TODO: remove this
this.features = Globals.features
Globals.commands = rawConfig.commands?.map(::Command) ?: emptyList() Globals.commands = rawConfig.commands?.map(::Command) ?: emptyList()
Globals.config = this Globals.config = this
this.features = rawConfig.features?.let(::Features) ?: Features.NONE
} }
fun reloadLocalization(rawLocalization: RawLocalization) { fun reloadLocalization(rawLocalization: RawLocalization) {

@ -8,7 +8,8 @@ class RawCommand(
val matchType: String?, val matchType: String?,
val permissions: RawPermissions?, val permissions: RawPermissions?,
@SerializedName("action") val actions: RawMessageActions?, @SerializedName("action") val actions: RawMessageActions?,
val embed: Map<String, String>? val embed: Map<String, String>?,
val feature: String?
) )
class RawPermissions(val hasOneOf: List<String>?, val hasNoneOf: List<String>?, val onlyDM: Boolean) class RawPermissions(val hasOneOf: List<String>?, val hasNoneOf: List<String>?, val onlyDM: Boolean)

@ -1,16 +1,4 @@
package moe.kageru.kagebot.config package moe.kageru.kagebot.config
import com.google.gson.annotations.SerializedName class RawFeatures(val welcome: RawWelcomeFeature?)
class RawWelcomeFeature(val content: Map<String, String>?, val fallbackChannel: String?, val fallbackMessage: String?)
class RawFeatures(val welcome: RawWelcomeFeature?, val debug: RawDebugFeature?, val help: RawHelpFeature?)
class RawWelcomeFeature(
val enable: Boolean,
val content: Map<String, String>?,
val fallbackChannel: String?,
val fallbackMessage: String?,
@SerializedName("command") val commandEnabled: Boolean
)
class RawDebugFeature(val enable: Boolean)
class RawHelpFeature(val enable: Boolean)

@ -3,23 +3,19 @@ package moe.kageru.kagebot.features
import com.sun.management.OperatingSystemMXBean import com.sun.management.OperatingSystemMXBean
import moe.kageru.kagebot.Globals import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.MessageUtil import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.config.RawDebugFeature
import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.entity.message.embed.EmbedBuilder
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.time.Duration import java.time.Duration
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
class DebugFeature(rawDebugFeatures: RawDebugFeature): MessageFeature() { class DebugFeature : MessageFeature() {
override val commandEnabled = rawDebugFeatures.enable
override fun handleInternal(message: MessageCreateEvent) { override fun handleInternal(message: MessageCreateEvent) {
if (message.messageAuthor.isBotOwner) { if (message.messageAuthor.isBotOwner) {
if (message.readableMessageContent.startsWith("!debugstats")) {
message.channel.sendMessage(getPerformanceStats()) message.channel.sendMessage(getPerformanceStats())
} }
} }
}
private fun getPerformanceStats(): EmbedBuilder { private fun getPerformanceStats(): EmbedBuilder {
val osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean::class.java) val osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean::class.java)

@ -2,19 +2,14 @@ package moe.kageru.kagebot.features
import moe.kageru.kagebot.config.RawFeatures import moe.kageru.kagebot.config.RawFeatures
class Features(val welcome: WelcomeFeature?, val debug: DebugFeature?, val help: HelpFeature?) { class Features(val welcome: WelcomeFeature?, debug: DebugFeature, help: HelpFeature) {
constructor(rawFeatures: RawFeatures) : this( constructor(rawFeatures: RawFeatures) : this(
rawFeatures.welcome?.let(::WelcomeFeature), rawFeatures.welcome?.let(::WelcomeFeature),
rawFeatures.debug?.let(::DebugFeature), DebugFeature(),
rawFeatures.help?.let(::HelpFeature) HelpFeature()
) )
fun all() = listOfNotNull(this.welcome, this.debug, this.help) private val featureMap = mapOf("help" to help, "debug" to debug, "welcome" to welcome)
fun allWithMessage() = all().filterIsInstance<MessageFeature>()
companion object { fun findByString(feature: String) = featureMap[feature]
val NONE = Features(null, null, null)
}
} }
interface Feature

@ -3,24 +3,18 @@ package moe.kageru.kagebot.features
import moe.kageru.kagebot.Globals import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.MessageUtil import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.command.MatchType import moe.kageru.kagebot.command.MatchType
import moe.kageru.kagebot.config.RawHelpFeature
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
class HelpFeature(rawFeature: RawHelpFeature) : MessageFeature() { class HelpFeature : MessageFeature() {
override val commandEnabled = rawFeature.enable
override fun handleInternal(message: MessageCreateEvent) { override fun handleInternal(message: MessageCreateEvent) {
if (message.readableMessageContent.startsWith("!help")) {
message.channel.sendMessage( message.channel.sendMessage(
MessageUtil.getEmbedBuilder() MessageUtil.getEmbedBuilder()
.addField("Commands:", listCommands(message)) .addField("Commands:", listCommands(message))
) )
} }
} }
private fun listCommands(message: MessageCreateEvent) = private fun listCommands(message: MessageCreateEvent) = Globals.commands
Globals.commands
.filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) } .filter { it.matchType == MatchType.PREFIX && it.isAllowed(message) }
.map { it.trigger } .map { it.trigger }
.joinToString("\n") .joinToString("\n")
}

@ -1,17 +1,11 @@
package moe.kageru.kagebot.features package moe.kageru.kagebot.features
import moe.kageru.kagebot.Globals
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
abstract class MessageFeature : Feature { abstract class MessageFeature {
abstract val commandEnabled: Boolean
fun handle(message: MessageCreateEvent) { fun handle(message: MessageCreateEvent) {
if (commandEnabled) {
Globals.commandCounter.incrementAndGet()
handleInternal(message) handleInternal(message)
} }
}
internal abstract fun handleInternal(message: MessageCreateEvent) internal abstract fun handleInternal(message: MessageCreateEvent)
} }

@ -8,15 +8,10 @@ import org.javacord.api.entity.message.embed.EmbedBuilder
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature() { class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature() {
override val commandEnabled = rawWelcome.commandEnabled
override fun handleInternal(message: MessageCreateEvent) { override fun handleInternal(message: MessageCreateEvent) {
if (message.readableMessageContent == "!welcome") {
message.channel.sendMessage(embed) message.channel.sendMessage(embed)
} }
}
val enabled: Boolean = rawWelcome.enable
val embed: EmbedBuilder? by lazy { val embed: EmbedBuilder? by lazy {
rawWelcome.content?.let(MessageUtil::mapToEmbed) rawWelcome.content?.let(MessageUtil::mapToEmbed)
} }

@ -13,9 +13,6 @@ messageDeleted = "Your message was deleted because it contained a banned word or
# If the user has disabled their DMs, the fallbackMessage will be sent in the fallbackChannel instead. # If the user has disabled their DMs, the fallbackMessage will be sent in the fallbackChannel instead.
# If no fallback channel or message is specified, no fallback will be sent. # If no fallback channel or message is specified, no fallback will be sent.
[feature.welcome] [feature.welcome]
enable = true
# enable the !welcome command which will print the welcome embed
command = true
fallbackChannel = "555097559023222825" fallbackChannel = "555097559023222825"
fallbackMessage = "@@ I would like to greet you, but I can’t. :(" fallbackMessage = "@@ I would like to greet you, but I can’t. :("
# This is a list of pairs where the key is the title and the value the content of the paragraph. # This is a list of pairs where the key is the title and the value the content of the paragraph.
@ -24,13 +21,6 @@ fallbackMessage = "@@ I would like to greet you, but I can’t. :("
"Welcome to the Server" = "This is the content of the first paragraph" "Welcome to the Server" = "This is the content of the first paragraph"
"Second paragraph heading" = "Second paragraph content" "Second paragraph heading" = "Second paragraph content"
# allow the bot owner to get debug stats
[feature.debug]
enable = true
[feature.help]
enable = true
[[command]] [[command]]
trigger = "!ping" trigger = "!ping"
response = "pong" response = "pong"
@ -95,3 +85,15 @@ response = "redirected"
[command.action.redirect] [command.action.redirect]
target = "555097559023222825" target = "555097559023222825"
anonymous = true anonymous = true
[[command]]
trigger = "!debug"
feature = "debug"
[[command]]
trigger = "!welcome"
feature = "welcome"
[[command]]
trigger = "!help"
feature = "help"

@ -9,6 +9,6 @@ class ConfigTest : StringSpec({
"should properly parse test config" { "should properly parse test config" {
Globals.config shouldNotBe null Globals.config shouldNotBe null
Globals.systemConfig shouldNotBe null Globals.systemConfig shouldNotBe null
Globals.commands shouldBe emptyList() Globals.commands.size shouldBe 2
} }
}) })

@ -4,8 +4,8 @@ import io.kotlintest.specs.StringSpec
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import moe.kageru.kagebot.Kagebot
import moe.kageru.kagebot.TestUtil import moe.kageru.kagebot.TestUtil
import moe.kageru.kagebot.config.RawDebugFeature
import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.entity.message.embed.EmbedBuilder
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
@ -14,19 +14,19 @@ class DebugFeatureTest : StringSpec({
// this will fail if the bot tries to execute more than it should // this will fail if the bot tries to execute more than it should
// because the mock does not provide the necessary methods // because the mock does not provide the necessary methods
"should ignore regular users" { "should ignore regular users" {
val message = mockk<MessageCreateEvent> { val message = TestUtil.mockMessage("!debug")
every { messageAuthor.isBotOwner } returns false every { message.messageAuthor.isBotOwner } returns false
} Kagebot.processMessage(message)
DebugFeature(RawDebugFeature(true)).handle(message) DebugFeature().handle(message)
verify(exactly = 0) { message.channel.sendMessage(any<EmbedBuilder>()) } verify(exactly = 0) { message.channel.sendMessage(any<EmbedBuilder>()) }
} }
"should return something" { "should return something" {
val message = mockk<MessageCreateEvent> { val message = mockk<MessageCreateEvent> {
every { messageAuthor.isBotOwner } returns true every { messageAuthor.isBotOwner } returns true
every { readableMessageContent } returns "!debugstats something" every { readableMessageContent } returns "!debug"
every { channel.sendMessage(any<EmbedBuilder>()) } returns mockk() every { channel.sendMessage(any<EmbedBuilder>()) } returns mockk()
} }
DebugFeature(RawDebugFeature(true)).handle(message) DebugFeature().handle(message)
verify(exactly = 1) { message.channel.sendMessage(any<EmbedBuilder>()) } verify(exactly = 1) { message.channel.sendMessage(any<EmbedBuilder>()) }
} }
}) })

@ -4,18 +4,21 @@ import io.kotlintest.specs.StringSpec
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
import moe.kageru.kagebot.Globals import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.Kagebot
import moe.kageru.kagebot.TestUtil import moe.kageru.kagebot.TestUtil
import moe.kageru.kagebot.TestUtil.withReplyContents
import moe.kageru.kagebot.TestUtil.mockMessage import moe.kageru.kagebot.TestUtil.mockMessage
import moe.kageru.kagebot.TestUtil.withCommands import moe.kageru.kagebot.TestUtil.withCommands
import moe.kageru.kagebot.config.RawHelpFeature import moe.kageru.kagebot.TestUtil.withReplyContents
import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.entity.message.embed.EmbedBuilder
import java.util.Optional import java.util.*
class HelpFeatureTest : StringSpec({ class HelpFeatureTest : StringSpec({
val sentEmbeds = mutableListOf<EmbedBuilder>() val sentEmbeds = mutableListOf<EmbedBuilder>()
TestUtil.prepareTestEnvironment(sentEmbeds = sentEmbeds) TestUtil.prepareTestEnvironment(sentEmbeds = sentEmbeds)
val commandConfig = """ val commandConfig = """
[[command]]
trigger = "!help"
feature = "help"
[[command]] [[command]]
trigger = "!ping" trigger = "!ping"
[[command]] [[command]]
@ -33,9 +36,7 @@ class HelpFeatureTest : StringSpec({
val expected = listOf("!ping", "!something") val expected = listOf("!ping", "!something")
val unexpected = listOf("not a prefix", "!prison") val unexpected = listOf("not a prefix", "!prison")
withReplyContents(expected = expected, unexpected = unexpected) { replies -> withReplyContents(expected = expected, unexpected = unexpected) { replies ->
HelpFeature(RawHelpFeature(true)) Kagebot.processMessage(mockMessage("!help", replyEmbeds = replies))
.handle(message = mockMessage("!help", replyEmbeds = replies))
//Kagebot.processMessage(TestUtil.mockMessage("!help", replyEmbeds = replies))
} }
} }
} }
@ -50,11 +51,8 @@ class HelpFeatureTest : StringSpec({
Globals.server.getRolesByNameIgnoreCase("testrole")[0] Globals.server.getRolesByNameIgnoreCase("testrole")[0]
) )
}) })
HelpFeature(RawHelpFeature(true)) Kagebot.processMessage(message)
.handle(message = message)
//Kagebot.processMessage(TestUtil.mockMessage("!help", replyEmbeds = replies))
} }
} }
} }
}) })

@ -8,7 +8,6 @@ redirectedMessage = "says"
messageDeleted = "message dongered" messageDeleted = "message dongered"
[feature.welcome] [feature.welcome]
enable = true
fallbackChannel = "123" fallbackChannel = "123"
fallbackMessage = "@@ welcome" fallbackMessage = "@@ welcome"
# This is a list of pairs where the key is the title and the value the content of the paragraph. # This is a list of pairs where the key is the title and the value the content of the paragraph.
@ -17,5 +16,10 @@ fallbackMessage = "@@ welcome"
"Welcome to the Server" = "This is the content of the first paragraph" "Welcome to the Server" = "This is the content of the first paragraph"
"Second paragraph heading" = "Second paragraph content" "Second paragraph heading" = "Second paragraph content"
[feature.help] [[command]]
enable = true trigger = "!debug"
feature = "debug"
[[command]]
trigger = "!welcome"
feature = "welcome"