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:
parent
1836844950
commit
7ebafc9958
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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", "")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 Kotlin’s ?: 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
|
@ -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?
|
||||
)
|
|
@ -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
|
||||
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
|
@ -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)) }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
*/
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user