diff --git a/src/main/kotlin/moe/kageru/kagebot/Command.kt b/src/main/kotlin/moe/kageru/kagebot/Command.kt index a81be1d..e041601 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Command.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Command.kt @@ -1,5 +1,6 @@ package moe.kageru.kagebot +import moe.kageru.kagebot.Config.Companion.config import moe.kageru.kagebot.Util.doIf import org.javacord.api.entity.message.MessageAuthor import org.javacord.api.event.message.MessageCreateEvent @@ -10,15 +11,29 @@ class Command( trigger: String?, private val response: String?, matchType: MatchType?, - private val deleteMessage: Boolean + private val deleteMessage: Boolean, + neededPermissions: Iterable? ) { val trigger: String = trigger!! val regex: Regex? = if (matchType == MatchType.REGEX) Regex(trigger!!) else null private val matchType: MatchType = matchType ?: MatchType.PREFIX + private val neededRoles = neededPermissions?.toSet() - constructor(cmd: Command) : this(cmd.trigger, cmd.response, cmd.matchType, cmd.deleteMessage) + constructor(cmd: Command) : this( + cmd.trigger, + cmd.response, + cmd.matchType, + cmd.deleteMessage, + cmd.neededRoles + ) fun execute(message: MessageCreateEvent) { + neededRoles?.let { roles -> + if (!(message.messageAuthor.isBotOwner || hasOneOf(message.messageAuthor, roles))) { + message.channel.sendMessage(config.localization.permissionDenied) + return + } + } if (this.deleteMessage && message.message.canYouDelete()) { message.deleteMessage() } @@ -27,6 +42,14 @@ class Command( } } + private fun hasOneOf(messageAuthor: MessageAuthor, roles: Set): Boolean { + val optional = messageAuthor.asUser() + return when { + optional.isEmpty -> false + else -> optional.get().getRoles(Config.server).map { it.id }.toSet().intersect(roles).isNotEmpty() + } + } + fun matches(msg: String) = this.matchType.matches(msg, this) private fun respond(author: MessageAuthor) = this.response!!.doIf({ it.contains(AUTHOR_PLACEHOLDER) }) { it.replace(AUTHOR_PLACEHOLDER, MessageUtil.mention(author)) diff --git a/src/main/kotlin/moe/kageru/kagebot/Config.kt b/src/main/kotlin/moe/kageru/kagebot/Config.kt index e222bac..6a81705 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Config.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Config.kt @@ -1,12 +1,15 @@ package moe.kageru.kagebot import com.moandjiezana.toml.Toml +import org.javacord.api.entity.server.Server import java.io.File -class Config(val system: System, val commands: List) { +class Config(val system: System, val localization: Localization, val commands: List) { companion object { val config: Config by lazy { read("config.toml") } val secret = File("secret").readText().replace("\n", "") + var server: Server? = null + get() = field!! private fun read(path: String): Config { val rawConfig: Toml = Toml().read(run { @@ -20,6 +23,7 @@ class Config(val system: System, val commands: List) { val parsed = rawConfig.to(Config::class.java) return Config( parsed.system, + parsed.localization, parsed.commands.map { Command(it) } ) } @@ -28,4 +32,4 @@ class Config(val system: System, val commands: List) { } data class System(val serverId: String) - +data class Localization(val permissionDenied: String) \ No newline at end of file diff --git a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt index 7e8eb97..e6d6b35 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt @@ -22,6 +22,7 @@ class Kagebot { init { val api = DiscordApiBuilder().setToken(Config.secret).login().join() + Config.server = api.getServerById(config.system.serverId).orElseThrow() Runtime.getRuntime().addShutdownHook(Thread { log.info("Bot has been interrupted. Shutting down.") api.disconnect() diff --git a/src/main/resources/config.toml b/src/main/resources/config.toml index 9103a9a..66bbf11 100644 --- a/src/main/resources/config.toml +++ b/src/main/resources/config.toml @@ -1,6 +1,9 @@ [system] serverId = "356414885292277771" +[localization] +permissionDenied = "You do not have permission to use this command." + [[commands]] trigger = "!ping" response = "pong" @@ -25,6 +28,15 @@ response = "@@ there you go" trigger = "delet this" deleteMessage = true +[[commands]] +trigger = "!restricted" +response = "access granted" +# a user needs *one of* these roles to trigger the command +neededRoles = [ + 452034011393425409, + 446668543816106004 +] + [[commands]] trigger = "^[^`]*([()|DoOvVcC][-=^']?;|;[-=^']?[()|DoOpPvVcC3]|:wink:|😉)[^`]*$" response = "@@ Oboe!" diff --git a/src/test/kotlin/moe/kageru/kagebot/CommandTest.kt b/src/test/kotlin/moe/kageru/kagebot/CommandTest.kt index 2b30941..b4be168 100644 --- a/src/test/kotlin/moe/kageru/kagebot/CommandTest.kt +++ b/src/test/kotlin/moe/kageru/kagebot/CommandTest.kt @@ -5,8 +5,13 @@ import io.kotlintest.specs.StringSpec import io.mockk.every import io.mockk.mockk import io.mockk.verify +import moe.kageru.kagebot.Config.Companion.config +import org.javacord.api.entity.permission.Role +import org.javacord.api.entity.user.User +import java.util.* class CommandTest : StringSpec({ + Config.server = mockk() "should match prefix command" { testMessageSuccess("!ping", "pong") } @@ -30,6 +35,38 @@ class CommandTest : StringSpec({ Kagebot.processMessage(mockMessage) verify(exactly = 1) { mockMessage.deleteMessage() } } + "should refuse command without permissions" { + val calls = mutableListOf() + val mockOptional = mockk>() + every { mockOptional.isEmpty } returns false + every { mockOptional.get().getRoles(any()) } returns emptyList() + val mockMessage = TestUtil.mockMessage("!restricted", capturedCalls = calls) + every { mockMessage.messageAuthor.asUser() } returns mockOptional + Kagebot.processMessage(mockMessage) + calls.size shouldBe 1 + calls[0] shouldBe config.localization.permissionDenied + } + "should accept restricted command for owner" { + val calls = mutableListOf() + val mockMessage = TestUtil.mockMessage("!restricted", capturedCalls = calls) + every { mockMessage.messageAuthor.isBotOwner } returns true + Kagebot.processMessage(mockMessage) + calls.size shouldBe 1 + calls[0] shouldBe "access granted" + } + "should accept restricted command with permissions" { + val calls = mutableListOf() + val mockRole = mockk() + every { mockRole.id } returns 452034011393425409 + val mockOptional = mockk>() + every { mockOptional.isEmpty } returns false + every { mockOptional.get().getRoles(any()) } returns listOf(mockRole) + val mockMessage = TestUtil.mockMessage("!restricted", capturedCalls = calls) + every { mockMessage.messageAuthor.asUser() } returns mockOptional + Kagebot.processMessage(mockMessage) + calls.size shouldBe 1 + calls[0] shouldBe "access granted" + } }) { companion object { fun testMessageSuccess(content: String, result: String) { diff --git a/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt b/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt index 2f85823..d1efb4f 100644 --- a/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt +++ b/src/test/kotlin/moe/kageru/kagebot/TestUtil.kt @@ -17,6 +17,7 @@ object TestUtil { every { message.channel.sendMessage(capture(capturedCalls)) } returns mockk() every { message.messageAuthor.isYourself } returns isSelf every { message.message.canYouDelete() } returns true + every { message.messageAuthor.isBotOwner } returns false return message } } \ No newline at end of file