update javacord and run ktlint

This commit is contained in:
kageru 2023-08-14 10:14:19 +02:00
parent 997284fb54
commit 80111b2bbf
20 changed files with 112 additions and 101 deletions

View File

@ -1,4 +1,6 @@
# kagebot – where the code is better than the name # kagebot
Kinda dead.
This bot is a replacement for [my old one](https://git.kageru.moe/kageru/discord-selphybot) with a very simple premise: This bot is a replacement for [my old one](https://git.kageru.moe/kageru/discord-selphybot) with a very simple premise:
As much as possible should be configurable in a human-readable way. As much as possible should be configurable in a human-readable way.
This will allow anyone to modify the config to host their own instance tailored to their own needs, This will allow anyone to modify the config to host their own instance tailored to their own needs,
@ -14,4 +16,6 @@ The implementation has kind of deteriorated into a playground for me
but it’s been running and moderating a 1000+ user server for over a year but it’s been running and moderating a 1000+ user server for over a year
with relatively little maintenance. with relatively little maintenance.
[1]: While arrow is great, adding it to a project after the fact leads to a very weird combination of FP and non-FP constructs. Would not recommend in production. [1]: While arrow is great, adding it to a project after the fact leads to a very weird combination of FP and non-FP constructs.
Would not recommend in production. This was also built in an early version of arrow that still had `Kind` and other concepts that were scrapped later,
but I don’t plan to update that ever. The bot can keep running as-is until it breaks.

View File

@ -4,22 +4,22 @@ apply {
plugin("kotlin-kapt") plugin("kotlin-kapt")
} }
plugins { plugins {
kotlin("jvm") version "1.4.10" kotlin("jvm") version "1.9.0"
id("com.github.johnrengelman.shadow") version "5.2.0" apply true id("com.github.johnrengelman.shadow") version "8.1.1" apply true
application application
} }
val botMainClass = "moe.kageru.kagebot.KagebotKt" val botMainClass = "moe.kageru.kagebot.KagebotKt"
application { application {
mainClassName = botMainClass mainClass.set(botMainClass)
} }
tasks.withType<Jar> { tasks.withType<Jar> {
manifest { manifest {
attributes( attributes(
mapOf( mapOf(
"Main-Class" to botMainClass "Main-Class" to botMainClass,
) ),
) )
} }
} }
@ -43,10 +43,10 @@ val arrowVersion = "0.11.0"
dependencies { dependencies {
implementation("com.uchuhimo:konf-core:0.23.0") implementation("com.uchuhimo:konf-core:0.23.0")
implementation("com.uchuhimo:konf-toml:0.23.0") implementation("com.uchuhimo:konf-toml:0.23.0")
implementation("org.javacord:javacord:3.1.1") implementation("org.javacord:javacord:3.8.0")
implementation("org.mapdb:mapdb:3.0.8") implementation("org.mapdb:mapdb:3.0.8")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.10") implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.0")
implementation("com.fasterxml.jackson.core:jackson-annotations:2.11.3") implementation("com.fasterxml.jackson.core:jackson-annotations:2.11.3")
implementation("io.arrow-kt:arrow-core:$arrowVersion") implementation("io.arrow-kt:arrow-core:$arrowVersion")
@ -55,10 +55,10 @@ dependencies {
testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2") testImplementation("io.kotlintest:kotlintest-runner-junit5:3.4.2")
testImplementation("io.mockk:mockk:1.10.0") testImplementation("io.mockk:mockk:1.10.0")
// these two are needed to access javacord internals (such as reading from sent embeds during tests) // these two are needed to access javacord internals (such as reading from sent embeds during tests)
testImplementation("org.javacord:javacord-core:3.1.1") testImplementation("org.javacord:javacord-core:3.8.0")
testImplementation("com.fasterxml.jackson.core:jackson-databind:2.11.3") testImplementation("com.fasterxml.jackson.core:jackson-databind:2.11.3")
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {
kotlinOptions.jvmTarget = "14" kotlinOptions.jvmTarget = "20"
} }

View File

@ -42,12 +42,14 @@ object Kagebot {
println("Caused by: ${e.cause}\n${e.cause?.stackTrace?.joinToString("\n")}") println("Caused by: ${e.cause}\n${e.cause?.stackTrace?.joinToString("\n")}")
exitProcess(1) exitProcess(1)
} }
Runtime.getRuntime().addShutdownHook(Thread { Runtime.getRuntime().addShutdownHook(
Log.info("Bot has been interrupted. Shutting down.") Thread {
Dao.setCommandCounter(Globals.commandCounter.get()) Log.info("Bot has been interrupted. Shutting down.")
Dao.close() Dao.setCommandCounter(Globals.commandCounter.get())
api.disconnect() Dao.close()
}) api.disconnect()
},
)
Log.info("kagebot Mk II running") Log.info("kagebot Mk II running")
api.addMessageCreateListener { checked { it.process() } } api.addMessageCreateListener { checked { it.process() } }
Config.features.eventFeatures().forEach { it.register(api) } Config.features.eventFeatures().forEach { it.register(api) }

View File

@ -13,7 +13,7 @@ object Log {
addHandler( addHandler(
FileHandler("kagebot.log", true).apply { FileHandler("kagebot.log", true).apply {
formatter = LogFormatter() formatter = LogFormatter()
} },
) )
} }
} }

View File

@ -80,17 +80,6 @@ object Util {
} catch (e: Exception) { } catch (e: Exception) {
Log.warn("An uncaught exception occurred.\n$e") Log.warn("An uncaught exception occurred.\n$e")
Log.warn(e.stackTrace.joinToString("\n")) Log.warn(e.stackTrace.joinToString("\n"))
MessageUtil.sendEmbed(
Globals.api.owner.get(),
EmbedBuilder()
.setColor(Color.RED)
.addField("Error", "kagebot has encountered an error")
.addField(
"$e", """```
${e.stackTrace.joinToString("\n\t")}
```""".trimIndent().run { applyIf(length > 1800) { substring(1..1800) } }
)
)
} }
} }
} }

View File

@ -21,7 +21,7 @@ class Command(
private val actions: MessageActions?, private val actions: MessageActions?,
embed: List<String>?, embed: List<String>?,
feature: String?, feature: String?,
matchType: String? matchType: String?,
) { ) {
val matchType: MatchType = matchType?.let { type -> val matchType: MatchType = matchType?.let { type ->
MatchType.values().find { it.name.equals(type, ignoreCase = true) } MatchType.values().find { it.name.equals(type, ignoreCase = true) }
@ -56,5 +56,5 @@ class Command(
enum class MatchType(val matches: (String, Command) -> Boolean) { enum class MatchType(val matches: (String, Command) -> Boolean) {
PREFIX({ message, command -> message.startsWith(command.trigger, ignoreCase = true) }), PREFIX({ message, command -> message.startsWith(command.trigger, ignoreCase = true) }),
CONTAINS({ message, command -> message.contains(command.trigger, ignoreCase = true) }), CONTAINS({ message, command -> message.contains(command.trigger, ignoreCase = true) }),
REGEX({ message, command -> command.regex!!.matches(message) }); REGEX({ message, command -> command.regex!!.matches(message) }),
} }

View File

@ -11,7 +11,7 @@ class MessageActions(
private val delete: Boolean = false, private val delete: Boolean = false,
private val redirect: MessageRedirect?, private val redirect: MessageRedirect?,
@JsonProperty("assign") @JsonProperty("assign")
private val assignment: RoleAssignment? private val assignment: RoleAssignment?,
) { ) {
fun run(message: MessageCreateEvent, command: Command) { fun run(message: MessageCreateEvent, command: Command) {

View File

@ -3,14 +3,12 @@ package moe.kageru.kagebot.command
import arrow.core.Option import arrow.core.Option
import arrow.core.toOption import arrow.core.toOption
import moe.kageru.kagebot.Util import moe.kageru.kagebot.Util
import moe.kageru.kagebot.extensions.unwrap
import org.javacord.api.entity.permission.Role
import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.message.MessageCreateEvent
class Permissions( class Permissions(
hasOneOf: List<String>?, hasOneOf: List<String>?,
hasNoneOf: List<String>?, hasNoneOf: List<String>?,
private val onlyDM: Boolean = false private val onlyDM: Boolean = false,
) { ) {
private val hasOneOf: Option<Set<String>> = hasOneOf?.toSet().toOption() private val hasOneOf: Option<Set<String>> = hasOneOf?.toSet().toOption()
private val hasNoneOf: Option<Set<String>> = hasNoneOf?.toSet().toOption() private val hasNoneOf: Option<Set<String>> = hasNoneOf?.toSet().toOption()

View File

@ -12,6 +12,6 @@ class RoleAssignment(@JsonProperty("role") role: String) {
fun assign(message: MessageCreateEvent) = message.getUser().fold( fun assign(message: MessageCreateEvent) = message.getUser().fold(
{ Log.warn("Could not find user ${message.messageAuthor.name} for role assign") }, { Log.warn("Could not find user ${message.messageAuthor.name} for role assign") },
{ it.addRole(role, "Requested via command.") } { it.addRole(role, "Requested via command.") },
) )
} }

View File

@ -22,11 +22,15 @@ class DebugFeature : MessageFeature {
val runtime = Runtime.getRuntime() val runtime = Runtime.getRuntime()
return MessageUtil.listToEmbed( return MessageUtil.listToEmbed(
listOf( listOf(
"Bot:", getBotStats(), "Bot:",
"Memory:", getMemoryInfo(runtime, osBean), getBotStats(),
"CPU:", getCpuInfo(osBean), "Memory:",
"System:", getOsInfo() getMemoryInfo(runtime, osBean),
) "CPU:",
getCpuInfo(osBean),
"System:",
getOsInfo(),
),
) )
} }
@ -40,7 +44,7 @@ class DebugFeature : MessageFeature {
uptime.toDaysPart(), uptime.toDaysPart(),
uptime.toHoursPart(), uptime.toHoursPart(),
uptime.toMinutesPart(), uptime.toMinutesPart(),
uptime.toSecondsPart() uptime.toSecondsPart(),
) )
} }

View File

@ -3,7 +3,7 @@ package moe.kageru.kagebot.features
class Features( class Features(
val welcome: WelcomeFeature? = null, val welcome: WelcomeFeature? = null,
val timeout: TimeoutFeature? = null, val timeout: TimeoutFeature? = null,
vc: TempVCFeature = TempVCFeature(null) vc: TempVCFeature = TempVCFeature(null),
) { ) {
private val debug = DebugFeature() private val debug = DebugFeature()
private val help = HelpFeature() private val help = HelpFeature()
@ -18,7 +18,7 @@ class Features(
"getConfig" to getConfig, "getConfig" to getConfig,
"setConfig" to setConfig, "setConfig" to setConfig,
"timeout" to timeout, "timeout" to timeout,
"vc" to vc "vc" to vc,
) )
fun findByString(feature: String) = featureMap[feature] fun findByString(feature: String) = featureMap[feature]

View File

@ -19,17 +19,21 @@ class TempVCFeature(@JsonProperty("category") category: String? = null) : EventF
private val category: ChannelCategory? = category?.let { Config.server.categoriesByName(it).first() } private val category: ChannelCategory? = category?.let { Config.server.categoriesByName(it).first() }
override fun handle(message: MessageCreateEvent): Unit = with(message) { override fun handle(message: MessageCreateEvent): Unit = with(message) {
Either.cond(' ' in readableMessageContent, Either.cond(
' ' in readableMessageContent,
{ readableMessageContent.split(' ', limit = 2).last() }, { readableMessageContent.split(' ', limit = 2).last() },
{ "Invalid syntax, expected `<command> <userlimit>`" }) { "Invalid syntax, expected `<command> <userlimit>`" },
)
.flatMap { limit -> .flatMap { limit ->
limit.toIntOrNull().rightIfNotNull { "Invalid syntax, expected a number as limit, got $limit" } limit.toIntOrNull().rightIfNotNull { "Invalid syntax, expected a number as limit, got $limit" }
}.filterOrElse({ it < 99 }, { "Error: can’t create a channel with that many users." }) }.filterOrElse({ it < 99 }, { "Error: can’t create a channel with that many users." })
.fold({ err -> channel.sendMessage(err) }, .fold(
{ err -> channel.sendMessage(err) },
{ limit -> { limit ->
createChannel(message, limit) createChannel(message, limit)
channel.sendMessage("Done") channel.sendMessage("Done")
}) },
)
} }
override fun register(api: DiscordApi) { override fun register(api: DiscordApi) {
@ -43,7 +47,7 @@ class TempVCFeature(@JsonProperty("category") category: String? = null) : EventF
private fun deleteChannel(channel: ServerVoiceChannel) = private fun deleteChannel(channel: ServerVoiceChannel) =
channel.delete("Empty temporary channel").asOption().fold( channel.delete("Empty temporary channel").asOption().fold(
{ Log.warn("Attempted to delete temporary VC without the necessary permissions") }, { Log.warn("Attempted to delete temporary VC without the necessary permissions") },
{ Dao.removeTemporaryVC(channel.idAsString) } { Dao.removeTemporaryVC(channel.idAsString) },
) )
private fun createChannel(message: MessageCreateEvent, limit: Int): Unit = private fun createChannel(message: MessageCreateEvent, limit: Int): Unit =
@ -54,7 +58,8 @@ class TempVCFeature(@JsonProperty("category") category: String? = null) : EventF
setCategory(category) setCategory(category)
}.create().asOption().fold( }.create().asOption().fold(
{ Log.warn("Attempted to create temporary VC without the necessary permissions") }, { Log.warn("Attempted to create temporary VC without the necessary permissions") },
{ channel -> Dao.addTemporaryVC(channel.idAsString) }) { channel -> Dao.addTemporaryVC(channel.idAsString) },
)
private fun generateChannelName(message: MessageCreateEvent): String = private fun generateChannelName(message: MessageCreateEvent): String =
"${message.messageAuthor.name}’s volatile corner" "${message.messageAuthor.name}’s volatile corner"

View File

@ -30,7 +30,7 @@ class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
Either.cond( Either.cond(
args.size >= 3, args.size >= 3,
{ Tuple3(args[1], args[2], args.getOrNull(3)) }, { Tuple3(args[1], args[2], args.getOrNull(3)) },
{ "Error: expected “<command> <user> <time> [<reason>]”. If the name contains spaces, please use the user ID instead." } { "Error: expected “<command> <user> <time> [<reason>]”. If the name contains spaces, please use the user ID instead." },
).flatMap { ).flatMap {
it.mapFirst(Option.applicative(), ::findUser).fix() it.mapFirst(Option.applicative(), ::findUser).fix()
.toEither { "Error: User ${it.a} not found, consider using the user ID" } .toEither { "Error: User ${it.a} not found, consider using the user ID" }
@ -47,7 +47,7 @@ class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
addField("Timeout", Config.localization[LocalizationSpec.timeout].replace("@@", "$time")) addField("Timeout", Config.localization[LocalizationSpec.timeout].replace("@@", "$time"))
reason?.let { addField("Reason", it) } reason?.let { addField("Reason", it) }
} }
} },
) )
} }
} }
@ -74,7 +74,7 @@ class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
roleIds.forEach { findRole("$it").map(user::addRole) } roleIds.forEach { findRole("$it").map(user::addRole) }
user.removeRole(timeoutRole) user.removeRole(timeoutRole)
Log.info("Lifted timeout from user ${user.discriminatedName}. Stored roles ${roleIds.joinToString()}") Log.info("Lifted timeout from user ${user.discriminatedName}. Stored roles ${roleIds.joinToString()}")
} },
) )
} }
} }

View File

@ -15,7 +15,7 @@ import org.javacord.api.event.server.member.ServerMemberJoinEvent
class WelcomeFeature( class WelcomeFeature(
content: List<String>?, content: List<String>?,
fallbackChannel: String?, fallbackChannel: String?,
private val fallbackMessage: String? private val fallbackMessage: String?,
) : MessageFeature, EventFeature { ) : MessageFeature, EventFeature {
val embed: EmbedBuilder? by lazy { content?.let(MessageUtil::listToEmbed) } val embed: EmbedBuilder? by lazy { content?.let(MessageUtil::listToEmbed) }
@ -31,7 +31,7 @@ class WelcomeFeature(
// If the user disabled direct messages, try the fallback (if defined) // If the user disabled direct messages, try the fallback (if defined)
if (message.asOption().isEmpty() && hasFallback()) { if (message.asOption().isEmpty() && hasFallback()) {
fallbackChannel!!.sendMessage( fallbackChannel!!.sendMessage(
fallbackMessage!!.replace("@@", event.user.mentionTag) fallbackMessage!!.replace("@@", event.user.mentionTag),
) )
} }
} }

View File

@ -31,9 +31,11 @@ class ConfigTest : StringSpec() {
timeout = "timeout" timeout = "timeout"
""".trimIndent() """.trimIndent()
val message = TestUtil.mockMessage("anything") val message = TestUtil.mockMessage("anything")
every { message.messageAttachments } returns listOf(mockk { every { message.messageAttachments } returns listOf(
every { url.openStream().readAllBytes() } returns testConfig.toByteArray() mockk {
}) every { url.openStream().readAllBytes() } returns testConfig.toByteArray()
},
)
SetConfigFeature().handle(message) SetConfigFeature().handle(message)
Config.localization[LocalizationSpec.redirectedMessage] shouldBe redir Config.localization[LocalizationSpec.redirectedMessage] shouldBe redir
} }

View File

@ -37,7 +37,7 @@ object TestUtil {
replies: MutableList<String> = mutableListOf(), replies: MutableList<String> = mutableListOf(),
replyEmbeds: MutableList<EmbedBuilder> = mutableListOf(), replyEmbeds: MutableList<EmbedBuilder> = mutableListOf(),
files: MutableList<File> = mutableListOf(), files: MutableList<File> = mutableListOf(),
isBot: Boolean = false isBot: Boolean = false,
): MessageCreateEvent { ): MessageCreateEvent {
return mockk { return mockk {
every { messageContent } returns content every { messageContent } returns content
@ -75,7 +75,7 @@ object TestUtil {
fun prepareTestEnvironment( fun prepareTestEnvironment(
sentEmbeds: MutableList<EmbedBuilder> = mutableListOf(), sentEmbeds: MutableList<EmbedBuilder> = mutableListOf(),
sentMessages: MutableList<String> = mutableListOf(), sentMessages: MutableList<String> = mutableListOf(),
dmEmbeds: MutableList<EmbedBuilder> = mutableListOf() dmEmbeds: MutableList<EmbedBuilder> = mutableListOf(),
) { ) {
val channel = mockk<ServerTextChannel>(relaxed = true) { val channel = mockk<ServerTextChannel>(relaxed = true) {
every { sendMessage(capture(sentEmbeds)) } returns mockk(relaxed = true) { every { sendMessage(capture(sentEmbeds)) } returns mockk(relaxed = true) {
@ -99,14 +99,16 @@ object TestUtil {
every { isCompletedExceptionally } returns false every { isCompletedExceptionally } returns false
every { join().idAsString } returns "12345" every { join().idAsString } returns "12345"
} }
every { getMembersByName(any()) } returns ListK.just(mockk(relaxed = true) { every { getMembersByName(any()) } returns setOf(
every { id } returns 123 mockk(relaxed = true) {
every { roles() } returns ListK.just(TEST_ROLE) every { id } returns 123
every { getRoles(any()) } returns ListK.just(TEST_ROLE) every { roles() } returns ListK.just(TEST_ROLE)
every { sendMessage(capture(dmEmbeds)) } returns mockk(relaxed = true) { every { getRoles(any()) } returns ListK.just(TEST_ROLE)
every { isCompletedExceptionally } returns false every { sendMessage(capture(dmEmbeds)) } returns mockk(relaxed = true) {
} every { isCompletedExceptionally } returns false
}) }
},
)
} }
Globals.api = mockk(relaxed = true) { Globals.api = mockk(relaxed = true) {
every { getServerById(any<String>()) } returns Optional.of(Config.server) every { getServerById(any<String>()) } returns Optional.of(Config.server)
@ -134,7 +136,7 @@ object TestUtil {
fun withReplyContents( fun withReplyContents(
expected: List<String> = emptyList(), expected: List<String> = emptyList(),
unexpected: List<String> = emptyList(), unexpected: List<String> = emptyList(),
op: (MutableList<EmbedBuilder>) -> Unit op: (MutableList<EmbedBuilder>) -> Unit,
) { ) {
val replies = mutableListOf<EmbedBuilder>() val replies = mutableListOf<EmbedBuilder>()
op(replies) op(replies)

View File

@ -34,7 +34,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "!ping" trigger = "!ping"
response = "pong" response = "pong"
""".trimIndent() """.trimIndent(),
) { ) {
val before = Globals.commandCounter.get() val before = Globals.commandCounter.get()
testMessageSuccess("!ping", "pong") testMessageSuccess("!ping", "pong")
@ -47,7 +47,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "!ping" trigger = "!ping"
response = "pong" response = "pong"
""".trimIndent() """.trimIndent(),
) { ) {
testMessageSuccess("!ping", "pong") testMessageSuccess("!ping", "pong")
} }
@ -62,7 +62,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "!embed" trigger = "!embed"
embed = [ "$heading", "$content" ] embed = [ "$heading", "$content" ]
""".trimIndent() """.trimIndent(),
) { ) {
TestUtil.withReplyContents(expected = listOf(heading, content)) { TestUtil.withReplyContents(expected = listOf(heading, content)) {
mockMessage("!embed", replyEmbeds = it).process() mockMessage("!embed", replyEmbeds = it).process()
@ -76,7 +76,7 @@ class CommandTest : StringSpec({
trigger = "somewhere" trigger = "somewhere"
response = "found it" response = "found it"
matchType = "CONTAINS" matchType = "CONTAINS"
""".trimIndent() """.trimIndent(),
) { ) {
testMessageSuccess("the trigger is somewhere in this message", "found it") testMessageSuccess("the trigger is somewhere in this message", "found it")
} }
@ -88,7 +88,7 @@ class CommandTest : StringSpec({
trigger = "A.+B" trigger = "A.+B"
response = "regex matched" response = "regex matched"
matchType = "REGEX" matchType = "REGEX"
""".trimIndent() """.trimIndent(),
) { ) {
testMessageSuccess("AcsdB", "regex matched") testMessageSuccess("AcsdB", "regex matched")
} }
@ -99,7 +99,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "answer me" trigger = "answer me"
response = "@@ there you go" response = "@@ there you go"
""".trimIndent() """.trimIndent(),
) { ) {
testMessageSuccess("answer me", "<@1> there you go") testMessageSuccess("answer me", "<@1> there you go")
} }
@ -110,7 +110,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "!ping" trigger = "!ping"
response = "pong" response = "pong"
""".trimIndent() """.trimIndent(),
) { ) {
val calls = mutableListOf<String>() val calls = mutableListOf<String>()
mockMessage("!ping", replies = calls, isBot = true).process() mockMessage("!ping", replies = calls, isBot = true).process()
@ -124,7 +124,7 @@ class CommandTest : StringSpec({
trigger = "delet this" trigger = "delet this"
[command.action] [command.action]
delete = true delete = true
""".trimIndent() """.trimIndent(),
) { ) {
val messageContent = "delet this" val messageContent = "delet this"
TestUtil.withReplyContents(expected = listOf(messageContent)) { TestUtil.withReplyContents(expected = listOf(messageContent)) {
@ -145,7 +145,7 @@ class CommandTest : StringSpec({
hasOneOf = [ hasOneOf = [
"testrole", "testrole",
] ]
""".trimIndent() """.trimIndent(),
) { ) {
val replies = mutableListOf<String>() val replies = mutableListOf<String>()
val mockMessage = mockMessage("!restricted", replies = replies) val mockMessage = mockMessage("!restricted", replies = replies)
@ -163,7 +163,7 @@ class CommandTest : StringSpec({
hasOneOf = [ hasOneOf = [
"testrole" "testrole"
] ]
""".trimIndent() """.trimIndent(),
) { ) {
val calls = mutableListOf<String>() val calls = mutableListOf<String>()
val mockMessage = mockMessage("!restricted", replies = calls) val mockMessage = mockMessage("!restricted", replies = calls)
@ -182,15 +182,17 @@ class CommandTest : StringSpec({
hasOneOf = [ hasOneOf = [
"testrole" "testrole"
] ]
""".trimIndent() """.trimIndent(),
) { ) {
val calls = mutableListOf<String>() val calls = mutableListOf<String>()
val mockMessage = mockMessage("!restricted", replies = calls) val mockMessage = mockMessage("!restricted", replies = calls)
every { mockMessage.messageAuthor.asUser() } returns Optional.of(mockk { every { mockMessage.messageAuthor.asUser() } returns Optional.of(
every { roles() } returns ListK.just( mockk {
Config.server.rolesByName("testrole").first() every { roles() } returns ListK.just(
) Config.server.rolesByName("testrole").first(),
}) )
},
)
mockMessage.process() mockMessage.process()
calls shouldBe mutableListOf("access granted") calls shouldBe mutableListOf("access granted")
} }
@ -203,7 +205,7 @@ class CommandTest : StringSpec({
response = "access granted" response = "access granted"
[command.permissions] [command.permissions]
hasNoneOf = ["testrole"] hasNoneOf = ["testrole"]
""".trimIndent() """.trimIndent(),
) { ) {
val calls = mutableListOf<String>() val calls = mutableListOf<String>()
val mockMessage = mockMessage("!almostUnrestricted", replies = calls) val mockMessage = mockMessage("!almostUnrestricted", replies = calls)
@ -211,7 +213,7 @@ class CommandTest : StringSpec({
every { mockMessage.messageAuthor.asUser() } returns mockk { every { mockMessage.messageAuthor.asUser() } returns mockk {
every { isPresent } returns true every { isPresent } returns true
every { get().getRoles(any()) } returns listOf( every { get().getRoles(any()) } returns listOf(
Config.server.rolesByName("testrole").first() Config.server.rolesByName("testrole").first(),
) )
} }
mockMessage.process() mockMessage.process()
@ -234,7 +236,7 @@ class CommandTest : StringSpec({
response = "access granted" response = "access granted"
[command.permissions] [command.permissions]
onlyDM = true onlyDM = true
""".trimIndent() """.trimIndent(),
) { ) {
val calls = mutableListOf<String>() val calls = mutableListOf<String>()
mockMessage("!dm", replies = calls).process() mockMessage("!dm", replies = calls).process()
@ -256,7 +258,7 @@ class CommandTest : StringSpec({
[command.action.redirect] [command.action.redirect]
target = "testchannel" target = "testchannel"
anonymous = true anonymous = true
""".trimIndent() """.trimIndent(),
) { ) {
val message = "this is a message" val message = "this is a message"
mockMessage("!redirect $message").process() mockMessage("!redirect $message").process()
@ -271,7 +273,7 @@ class CommandTest : StringSpec({
trigger = "!assign" trigger = "!assign"
[command.action.assign] [command.action.assign]
role = "testrole" role = "testrole"
""".trimIndent() """.trimIndent(),
) { ) {
val roles = mutableListOf<Role>() val roles = mutableListOf<Role>()
val user = mockk<User> { val user = mockk<User> {
@ -288,7 +290,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "!vc" trigger = "!vc"
feature = "vc" feature = "vc"
""".trimIndent() """.trimIndent(),
) { ) {
testMessageSuccess("!vc 2", "Done") testMessageSuccess("!vc 2", "Done")
Dao.isTemporaryVC("12345") shouldBe true Dao.isTemporaryVC("12345") shouldBe true
@ -301,7 +303,7 @@ class CommandTest : StringSpec({
[[command]] [[command]]
trigger = "!vc" trigger = "!vc"
feature = "vc" feature = "vc"
""".trimIndent() """.trimIndent(),
) { ) {
testMessageSuccess("!vc asd", "Invalid syntax, expected a number as limit, got asd") testMessageSuccess("!vc asd", "Invalid syntax, expected a number as limit, got asd")
Dao.isTemporaryVC("12345") shouldBe false Dao.isTemporaryVC("12345") shouldBe false

View File

@ -16,7 +16,8 @@ class ConfigFeatureTest : ShouldSpec({
[[command]] [[command]]
trigger = "!getConfig" trigger = "!getConfig"
feature = "getConfig" feature = "getConfig"
""".trimIndent()) { """.trimIndent(),
) {
val calls = mutableListOf<File>() val calls = mutableListOf<File>()
mockMessage("!getConfig", files = calls).process() mockMessage("!getConfig", files = calls).process()
calls.size shouldBe 1 calls.size shouldBe 1

View File

@ -31,7 +31,7 @@ class HelpFeatureTest : StringSpec({
trigger = "!prison" trigger = "!prison"
[command.permissions] [command.permissions]
hasOneOf = ["testrole"] hasOneOf = ["testrole"]
""".trimIndent() """.trimIndent()
"should show prefix command" { "should show prefix command" {
withCommands(commandConfig) { withCommands(commandConfig) {
val expected = listOf("!ping", "!something") val expected = listOf("!ping", "!something")
@ -47,11 +47,13 @@ class HelpFeatureTest : StringSpec({
val unexpected = listOf("not a prefix") val unexpected = listOf("not a prefix")
withReplyContents(expected = expected, unexpected = unexpected) { replies -> withReplyContents(expected = expected, unexpected = unexpected) { replies ->
val message = mockMessage("!help", replyEmbeds = replies) val message = mockMessage("!help", replyEmbeds = replies)
every { message.messageAuthor.asUser() } returns Optional.of(mockk { every { message.messageAuthor.asUser() } returns Optional.of(
every { getRoles(any()) } returns listOf( mockk {
Config.server.rolesByName("testrole").first() every { getRoles(any()) } returns listOf(
) Config.server.rolesByName("testrole").first(),
}) )
},
)
message.process() message.process()
} }
} }

View File

@ -24,7 +24,7 @@ class WelcomeFeatureTest : StringSpec({
every { isCompletedExceptionally } returns false every { isCompletedExceptionally } returns false
} }
} }
} },
) )
sentMessages shouldBe mutableListOf(Config.features.welcome!!.embed) sentMessages shouldBe mutableListOf(Config.features.welcome!!.embed)
} }
@ -40,7 +40,7 @@ class WelcomeFeatureTest : StringSpec({
} }
every { mentionTag } returns "<@123>" every { mentionTag } returns "<@123>"
} }
} },
) )
val channel = Config.server.channelsByName("").first() val channel = Config.server.channelsByName("").first()
verify(exactly = 1) { channel.sendMessage("<@123> welcome") } verify(exactly = 1) { channel.sendMessage("<@123> welcome") }