Completely rewrote config

Added a rawconfig as an intermediate step when parsing the TOML.
This will make nullability much more reliable and also gives me lots of
options in the future. It also broke all tests because reading the
config (not the raw config) now depends on the discord API. :tehe:
This commit is contained in:
kageru 2019-06-12 23:43:36 +02:00
parent 1836844950
commit 7ebafc9958
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
18 changed files with 442 additions and 158 deletions

View File

@ -1,79 +0,0 @@
package moe.kageru.kagebot
import com.google.gson.annotations.SerializedName
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
private const val AUTHOR_PLACEHOLDER = "@@"
class Command(
trigger: String?,
private val response: String?,
matchType: MatchType?,
private val permissions: Permissions?,
@SerializedName("action") private val actions: MessageActions?
) {
val trigger: String = trigger!!
val regex: Regex? = if (matchType == MatchType.REGEX) Regex(trigger!!) else null
val matchType: MatchType = matchType ?: MatchType.PREFIX
constructor(cmd: Command) : this(
cmd.trigger,
cmd.response,
cmd.matchType,
cmd.permissions?.let { Permissions(it) },
cmd.actions
)
fun execute(message: MessageCreateEvent) {
if (!(message.messageAuthor.isBotOwner || permissions?.isAllowed(message) != false)) {
message.channel.sendMessage(config.localization.permissionDenied)
return
}
this.actions?.run(message, this)
this.response?.let {
message.channel.sendMessage(respond(message.messageAuthor))
}
}
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))
}
}
enum class MatchType {
PREFIX {
override fun matches(message: String, command: Command) = message.startsWith(command.trigger)
},
CONTAINS {
override fun matches(message: String, command: Command) = message.contains(command.trigger)
},
REGEX {
override fun matches(message: String, command: Command) = command.regex!!.matches(message)
};
abstract fun matches(message: String, command: Command): Boolean
}
class Permissions(hasOneOf: Iterable<Long>?, hasNoneOf: Iterable<Long>?, private val onlyDM: Boolean) {
private val hasNoneOf = hasNoneOf?.toSet()
private val hasOneOf = hasOneOf?.toSet()
constructor(perms: Permissions) : this(perms.hasOneOf, perms.hasNoneOf, perms.onlyDM)
fun isAllowed(message: MessageCreateEvent): Boolean {
if (onlyDM && !message.isPrivateMessage) {
return false
}
hasOneOf?.let {
if (!Util.hasOneOf(message.messageAuthor, hasOneOf)) return false
}
hasNoneOf?.let {
if (Util.hasOneOf(message.messageAuthor, hasNoneOf)) return false
}
return true
}
}

View File

@ -1,43 +0,0 @@
package moe.kageru.kagebot
import com.google.gson.annotations.SerializedName
import com.moandjiezana.toml.Toml
import org.javacord.api.DiscordApi
import org.javacord.api.entity.server.Server
import java.io.File
class Config(
val system: System,
val localization: Localization,
@SerializedName("command") val commands: List<Command>
) {
companion object {
val config: Config by lazy { read("config.toml") }
val secret = File("secret").readText().replace("\n", "")
var server: Server? = null
get() = field!!
var api: DiscordApi? = null
get() = field!!
private fun read(path: String): Config {
val rawConfig: Toml = Toml().read(run {
val file = File(path)
if (file.isFile) {
return@run file
}
println("Config not found, falling back to defaults...")
File(this::class.java.classLoader.getResource(path)!!.toURI())
})
val parsed = rawConfig.to(Config::class.java)
return Config(
parsed.system,
parsed.localization,
parsed.commands.map { Command(it) }
)
}
}
}
data class System(val serverId: String, val color: String)
data class Localization(val permissionDenied: String, val redirectedMessage: String, val messageDeleted: String)

View File

@ -0,0 +1,11 @@
package moe.kageru.kagebot
import moe.kageru.kagebot.config.Config
import org.javacord.api.DiscordApi
import org.javacord.api.entity.server.Server
object Globals {
lateinit var server: Server
lateinit var api: DiscordApi
lateinit var config: Config
}

View File

@ -1,9 +1,12 @@
package moe.kageru.kagebot
import moe.kageru.kagebot.Config.Companion.config
import moe.kageru.kagebot.Log.log
import moe.kageru.kagebot.config.Config
import moe.kageru.kagebot.config.RawConfig
import org.javacord.api.DiscordApiBuilder
import org.javacord.api.event.message.MessageCreateEvent
import org.javacord.api.event.server.member.ServerMemberJoinEvent
import java.io.File
class Kagebot {
companion object {
@ -11,24 +14,53 @@ class Kagebot {
if (event.messageAuthor.isYourself) {
return
}
for (command in config.commands) {
for (command in Globals.config.commands) {
if (command.matches(event.messageContent)) {
command.execute(event)
break
}
}
}
fun welcomeUser(event: ServerMemberJoinEvent) {
Globals.config.features.welcome!!.let { welcome ->
val message = event.user.sendMessage(welcome.embed)
// If the user disabled direct messages, try the fallback (if defined)
if (message.isCompletedExceptionally
&& welcome.fallbackChannel != null
&& welcome.fallbackMessage != null
) {
welcome.fallbackChannel.sendMessage(
welcome.fallbackMessage.replace(
"@@",
MessageUtil.mention(event.user)
)
)
}
}
}
}
init {
val api = DiscordApiBuilder().setToken(Config.secret).login().join()
Config.server = api.getServerById(config.system.serverId).orElseThrow()
Config.api = api
Globals.api = DiscordApiBuilder().setToken(getSecret()).login().join()
try {
Globals.config = Config(RawConfig.read())
} catch (e: IllegalArgumentException) {
println("Config error:\n$e,\n${e.message},\n${e.stackTrace}")
System.exit(1)
}
Runtime.getRuntime().addShutdownHook(Thread {
log.info("Bot has been interrupted. Shutting down.")
api.disconnect()
Globals.api.disconnect()
})
log.info("kagebot Mk II running")
api.addMessageCreateListener { processMessage(it) }
Globals.api.addMessageCreateListener { processMessage(it) }
Globals.config.features.welcome?.let { welcome ->
if (welcome.enabled) {
Globals.api.addServerMemberJoinListener { welcomeUser(it) }
}
}
}
}
private fun getSecret() = File("secret").readText().replace("\n", "")
}

View File

@ -1,18 +1,30 @@
package moe.kageru.kagebot
import moe.kageru.kagebot.Config.Companion.config
import org.javacord.api.entity.message.MessageAuthor
import org.javacord.api.entity.message.embed.EmbedBuilder
import java.awt.Color
import org.javacord.api.entity.user.User
object MessageUtil {
fun mention(user: MessageAuthor): String {
return "<@${user.id}>"
}
fun mention(user: User): String {
return "<@${user.id}>"
}
fun getEmbedBuilder(): EmbedBuilder {
val builder = EmbedBuilder()
Config.server!!.icon.ifPresent { builder.setThumbnail(it) }
return builder.setColor(Color.decode(config.system.color)).setTimestampToNow()
Globals.server.icon.ifPresent { builder.setThumbnail(it) }
return builder.setColor(Globals.config.system.color).setTimestampToNow()
}
fun mapToEmbed(contents: Map<String, String>): EmbedBuilder {
val builder = getEmbedBuilder()
for ((heading, content) in contents) {
builder.addField(heading, content)
}
return builder
}
}

View File

@ -1,6 +1,10 @@
package moe.kageru.kagebot
import moe.kageru.kagebot.Globals.api
import moe.kageru.kagebot.Globals.server
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.entity.message.MessageAuthor
import org.javacord.api.entity.permission.Role
import java.util.*
object Util {
@ -8,6 +12,10 @@ object Util {
return if (condition(this)) op(this) else this
}
/**
* Mimics the behavior of [Optional.ifPresent], but returns null if the optional is empty,
* allowing easier fallback behavior via Kotlins ?: operator.
*/
inline fun <T, R> Optional<T>.ifNotEmpty(op: (T) -> R): R? {
if (this.isPresent) {
return op(this.get())
@ -15,9 +23,50 @@ object Util {
return null
}
fun hasOneOf(messageAuthor: MessageAuthor, roles: Set<Long>): Boolean {
fun hasOneOf(messageAuthor: MessageAuthor, roles: Set<Role>): Boolean {
return messageAuthor.asUser().ifNotEmpty { user ->
user.getRoles(Config.server).map { it.id }.toSet().intersect(roles).isNotEmpty()
user.getRoles(server).map { it }.toSet().intersect(roles).isNotEmpty()
} ?: false
}
private val channelIdRegex = Regex("\\d{18}")
fun String.isEntityId() = channelIdRegex.matches(this)
@Throws(IllegalArgumentException::class)
fun findRole(idOrName: String): Role {
return when {
idOrName.isEntityId() -> server.getRoleById(idOrName).ifNotEmpty { it }
?: throw IllegalArgumentException("Role $idOrName not found.")
else -> server.getRolesByNameIgnoreCase(idOrName).let {
when (it.size) {
0 -> throw IllegalArgumentException("Role $idOrName not found.")
1 -> it[0]
else -> throw IllegalArgumentException("More than one role found with name $idOrName. Please specify the role ID instead")
}
}
}
}
@Throws(IllegalArgumentException::class)
fun findChannel(idOrName: String): TextChannel {
return when {
idOrName.isEntityId() -> server.getTextChannelById(idOrName).ifNotEmpty { it }
?: throw IllegalArgumentException("Channel ID $idOrName not found.")
else -> if (idOrName.startsWith('@')) {
api.getCachedUserByDiscriminatedName(idOrName.removePrefix("@")).ifNotEmpty { user ->
user.privateChannel.ifNotEmpty { it }
?: throw IllegalArgumentException("Could not open private channel with user $idOrName for redirection.")
}
?: throw IllegalArgumentException("Can’t find user $idOrName for redirection.")
} else {
server.getTextChannelsByName(idOrName).let {
when (it.size) {
0 -> throw IllegalArgumentException("Channel $idOrName not found.")
1 -> it[0]
else -> throw IllegalArgumentException("More than one channel found with name $idOrName. Please specify the channel ID instead")
}
}
}
}
}
}

View File

@ -0,0 +1,62 @@
package moe.kageru.kagebot.command
import moe.kageru.kagebot.Globals.config
import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util.doIf
import moe.kageru.kagebot.config.RawCommand
import org.javacord.api.entity.message.MessageAuthor
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?
init {
trigger = cmd.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
}
fun execute(message: MessageCreateEvent) {
if (permissions?.isAllowed(message) == false) {
message.channel.sendMessage(config.localization.permissionDenied)
return
}
this.actions?.run(message, this)
this.response?.let {
message.channel.sendMessage(respond(message.messageAuthor))
}
}
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))
}
}
enum class MatchType {
PREFIX {
override fun matches(message: String, command: Command) = message.startsWith(command.trigger)
},
CONTAINS {
override fun matches(message: String, command: Command) = message.contains(command.trigger)
},
REGEX {
override fun matches(message: String, command: Command) = command.regex!!.matches(message)
};
abstract fun matches(message: String, command: Command): Boolean
}

View File

@ -1,27 +1,45 @@
package moe.kageru.kagebot
package moe.kageru.kagebot.command
import moe.kageru.kagebot.Config.Companion.config
import moe.kageru.kagebot.Globals.config
import moe.kageru.kagebot.Log.log
import moe.kageru.kagebot.Util.ifNotEmpty
import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.config.RawMessageActions
import moe.kageru.kagebot.config.RawRedirect
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.event.message.MessageCreateEvent
class MessageActions(private val delete: Boolean, private val redirect: Redirect?) {
class MessageActions(rawActions: RawMessageActions) {
private val delete: Boolean = rawActions.delete
private val redirect: Redirect? = rawActions.redirect?.let { Redirect(it) }
fun run(message: MessageCreateEvent, command: Command) {
if (delete && message.message.canYouDelete()) {
if (delete) {
deleteMessage(message)
}
redirect?.execute(message, command)
}
private fun deleteMessage(message: MessageCreateEvent) {
if (message.message.canYouDelete()) {
message.deleteMessage()
message.messageAuthor.asUser().ifNotEmpty { user ->
message.messageAuthor.asUser().ifPresent { user ->
user.sendMessage(
MessageUtil.getEmbedBuilder()
.addField("Blacklisted", config.localization.messageDeleted)
.addField("Original:", "${message.readableMessageContent}")
)
}
} else {
log.info("Tried to delete a message without the necessary permissions. Channel: ${message.channel.id}")
}
redirect?.execute(message, command)
}
}
class Redirect(private val target: Long, private val anonymous: Boolean) {
class Redirect(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
fun execute(message: MessageCreateEvent, command: Command) {
val embed = MessageUtil.getEmbedBuilder()
@ -43,7 +61,9 @@ class Redirect(private val target: Long, private val anonymous: Boolean) {
setAuthor(message.messageAuthor)
}
}
Config.server!!.getTextChannelById(target).ifNotEmpty { it.sendMessage(embed) }
?: log.warning("Could not redirect message to channel $target")
if (target.sendMessage(embed).isCompletedExceptionally) {
log.warning("Could not redirect message to channel $target")
}
}
}

View File

@ -0,0 +1,34 @@
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
}
fun isAllowed(message: MessageCreateEvent): Boolean {
if (message.messageAuthor.isBotOwner) {
return true
}
if (onlyDM && !message.isPrivateMessage) {
return false
}
hasOneOf?.let { roles ->
if (!Util.hasOneOf(message.messageAuthor, roles)) return false
}
hasNoneOf?.let { roles ->
if (Util.hasOneOf(message.messageAuthor, roles)) return false
}
return true
}
}

View File

@ -0,0 +1,47 @@
package moe.kageru.kagebot.config
import moe.kageru.kagebot.Globals
import moe.kageru.kagebot.Globals.api
import moe.kageru.kagebot.command.Command
import moe.kageru.kagebot.features.Features
import moe.kageru.kagebot.features.WelcomeFeature
import java.awt.Color
import kotlin.IllegalArgumentException
class Config(rawConfig: RawConfig) {
val system: SystemConfig
val localization: Localization
val commands: List<Command>
val features: Features
init {
system = rawConfig.system?.let {
SystemConfig(
it.serverId ?: throw IllegalArgumentException("No [system.server] defined."),
Color.decode(it.color ?: "#1793d0")
)
} ?: throw IllegalArgumentException("No [system] block in config.")
Globals.server = api.getServerById(system.serverId).orElseThrow()
localization = rawConfig.localization?.let {
Localization(
permissionDenied = it.permissionDenied
?: throw IllegalArgumentException("No [localization.permissionDenied] defined"),
redirectedMessage = it.redirectedMessage
?: throw IllegalArgumentException("No [localization.permissionDenied] defined"),
messageDeleted = it.messageDeleted
?: throw IllegalArgumentException("No [localization.permissionDenied] defined")
)
} ?: throw IllegalArgumentException("No [localization] block in config.")
commands = rawConfig.commands?.let { rawCommands ->
rawCommands.map { Command(it) }
} ?: emptyList()
features = rawConfig.features?.let { Features(it) }
?: throw IllegalArgumentException("No [feature] block in config.")
}
}
class SystemConfig(val serverId: String, val color: Color)
class Localization(val permissionDenied: String, val redirectedMessage: String, val messageDeleted: String)

View File

@ -0,0 +1,50 @@
package moe.kageru.kagebot.config
import com.google.gson.annotations.SerializedName
import com.moandjiezana.toml.Toml
import java.io.File
class RawConfig(
val system: RawSystemConfig?,
val localization: RawLocalization?,
@SerializedName("command") val commands: List<RawCommand>?,
@SerializedName("feature") val features: RawFeatures?
) {
companion object {
const val DEFAULT_CONFIG_PATH = "config.toml"
fun read(path: String = DEFAULT_CONFIG_PATH): RawConfig {
val toml: Toml = Toml().read(run {
val file = File(path)
if (file.isFile) {
return@run file
}
println("Config not found, falling back to defaults...")
File(this::class.java.classLoader.getResource(path)!!.toURI())
})
return toml.to(RawConfig::class.java)
}
}
}
class RawSystemConfig(val serverId: String?, val color: String?)
class RawLocalization(val permissionDenied: String?, val redirectedMessage: String?, val messageDeleted: String?)
class RawCommand(
val trigger: String?,
val response: String?,
val matchType: String?,
val permissions: RawPermissions?,
@SerializedName("action") val actions: RawMessageActions?
)
class RawPermissions(val hasOneOf: List<String>?, val hasNoneOf: List<String>?, val onlyDM: Boolean)
class RawMessageActions(val delete: Boolean, val redirect: RawRedirect?)
class RawRedirect(val target: String?, val anonymous: Boolean)
class RawFeatures(val welcome: RawWelcomeFeature?)
class RawWelcomeFeature(
val enabled: Boolean,
val content: Map<String, String>?,
val fallbackChannel: String?,
val fallbackMessage: String?
)

View File

@ -0,0 +1,27 @@
package moe.kageru.kagebot.features
import moe.kageru.kagebot.MessageUtil
import moe.kageru.kagebot.Util
import moe.kageru.kagebot.config.RawFeatures
import moe.kageru.kagebot.config.RawWelcomeFeature
import org.javacord.api.entity.channel.TextChannel
import org.javacord.api.entity.message.embed.EmbedBuilder
class Features(rawFeatures: RawFeatures) {
val welcome: WelcomeFeature? = rawFeatures.welcome?.let { WelcomeFeature(it) }
}
class WelcomeFeature(rawWelcome: RawWelcomeFeature) {
val enabled: Boolean = rawWelcome.enabled
val embed: EmbedBuilder? by lazy {
rawWelcome.content?.let(MessageUtil::mapToEmbed)
}
val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let {
if (rawWelcome.fallbackMessage == null) {
throw IllegalArgumentException("[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined")
}
Util.findChannel(it)
}
val fallbackMessage: String? = rawWelcome.fallbackMessage
}

View File

@ -4,10 +4,10 @@ import moe.kageru.kagebot.Log.log
import java.lang.System
fun main() {
try {
//try {
Kagebot()
} catch (e: Exception) {
/*} catch (e: Exception) {
log.warning("An exception occurred in the main thread, exiting.\n${e.stackTrace.joinToString("\n")}")
System.exit(1)
}
}*/
}

View File

@ -8,6 +8,19 @@ permissionDenied = "You do not have permission to use this command."
redirectedMessage = "says"
messageDeleted = "Your message was deleted because it contained a banned word or phrase."
# If this is enabled, every new user will receive a welcome message.
# 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.
[feature.welcome]
enabled = true
fallbackChannel = 555097559023222825
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.
# Do not use empty strings to get empty headings or paragraphs. The discord API rejects those.
[feature.welcome.content]
"Welcome to the Server" = "This is the content of the first paragraph"
"Second paragraph heading" = "Second paragraph content"
[[command]]
trigger = "!ping"
response = "pong"
@ -38,29 +51,27 @@ trigger = "!restricted"
response = "access granted"
[command.permissions]
hasOneOf = [
452034011393425409,
446668543816106004
"452034011393425409",
"446668543816106004"
]
[[command]]
trigger = "!almostUnrestricted"
response = "access granted"
[command.permissions]
hasNoneOf = [452034011393425409]
hasNoneOf = ["452034011393425409"]
# redirect every message that starts with !redirect to channel 555097559023222825
[[command]]
trigger = "!redirect"
response = "redirected"
[command.action.redirect]
target = 555097559023222825
target = "testchannel"
# the same, but without the original username
[[command]]
trigger = "!anonRedirect"
response = "redirected"
[command.action.redirect]
target = 555097559023222825
target = "555097559023222825"
anonymous = true

View File

@ -6,11 +6,11 @@ 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 moe.kageru.kagebot.TestUtil.embedToString
import moe.kageru.kagebot.TestUtil.messageableAuthor
import moe.kageru.kagebot.TestUtil.mockMessage
import moe.kageru.kagebot.TestUtil.testMessageSuccess
import moe.kageru.kagebot.config.RawConfig.Companion.config
import org.javacord.api.entity.message.embed.EmbedBuilder
class CommandTest : StringSpec({

View File

@ -2,10 +2,16 @@ package moe.kageru.kagebot
import io.kotlintest.shouldNotBe
import io.kotlintest.specs.StringSpec
import moe.kageru.kagebot.config.RawConfig
class ConfigTest : StringSpec({
/*
"should properly parse default config" {
Config.config shouldNotBe null
Config.config.commands shouldNotBe null
}
*/
"should convert to raw config" {
RawConfig.config shouldNotBe null
}
})

View File

@ -0,0 +1,45 @@
package moe.kageru.kagebot
import io.kotlintest.matchers.string.shouldContain
import io.kotlintest.shouldBe
import io.kotlintest.specs.StringSpec
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import org.javacord.api.entity.message.embed.EmbedBuilder
class FeatureTest
/*
class FeatureTest : StringSpec({
"should send welcome" {
val sentMessages = mutableListOf<EmbedBuilder>()
Kagebot.welcomeUser(
mockk {
every { user } returns mockk {
every { id } returns 123
every { sendMessage(capture(sentMessages)) }
}
}
)
sentMessages.size shouldBe 1
TestUtil.embedToString(sentMessages[0]).let { embed ->
Config.config.features!!.welcome!!.content!!.entries.forEach { (title, content) ->
embed shouldContain title
embed shouldContain content
}
}
}
"should send welcome fallback if DMs are disabled" {
val dm = slot<String>()
val sentMessages = mutableListOf<EmbedBuilder>()
TestUtil.prepareServerConfig(sentMessages)
Kagebot.welcomeUser(
mockk {
every { user } returns mockk {
every { id } returns 123
every { sendMessage(capture(dm)) }
}
}
)
}
})
*/

View File

@ -50,7 +50,7 @@ object TestUtil {
val server = mockk<Server>()
every { server.icon.ifPresent(any()) } just Runs
every { server.getTextChannelById(any<Long>()) } returns resultMock
Config.server = server
//Config.server = server
}
fun testMessageSuccess(content: String, result: String) {