Rewrite config to use Konf (4): Features
The entire config parsing is now rewritten. This entirely removes toml4j in favor of Konf. It also removes all remaining RawConfig logic.
This commit is contained in:
parent
bb03474bf5
commit
d6492bae8f
|
@ -36,7 +36,6 @@ val test by tasks.getting(Test::class) {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.uchuhimo:konf-core:0.20.0")
|
implementation("com.uchuhimo:konf-core:0.20.0")
|
||||||
implementation("com.uchuhimo:konf-toml:0.20.0")
|
implementation("com.uchuhimo:konf-toml:0.20.0")
|
||||||
implementation("com.moandjiezana.toml:toml4j:0.7.2")
|
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation("org.javacord:javacord:3.0.4")
|
implementation("org.javacord:javacord:3.0.4")
|
||||||
implementation("org.mapdb:mapdb:3.0.7")
|
implementation("org.mapdb:mapdb:3.0.7")
|
||||||
|
|
|
@ -3,7 +3,6 @@ package moe.kageru.kagebot
|
||||||
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.ConfigParser
|
import moe.kageru.kagebot.config.ConfigParser
|
||||||
import moe.kageru.kagebot.config.RawConfig
|
|
||||||
import moe.kageru.kagebot.cron.CronD
|
import moe.kageru.kagebot.cron.CronD
|
||||||
import moe.kageru.kagebot.persistence.Dao
|
import moe.kageru.kagebot.persistence.Dao
|
||||||
import org.javacord.api.DiscordApiBuilder
|
import org.javacord.api.DiscordApiBuilder
|
||||||
|
@ -44,7 +43,7 @@ object Kagebot {
|
||||||
val api = DiscordApiBuilder().setToken(secret).login().join()
|
val api = DiscordApiBuilder().setToken(secret).login().join()
|
||||||
Globals.api = api
|
Globals.api = api
|
||||||
try {
|
try {
|
||||||
ConfigParser.initialLoad(RawConfig.DEFAULT_CONFIG_PATH)
|
ConfigParser.initialLoad(ConfigParser.DEFAULT_CONFIG_PATH)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
println("Config error:\n$e,\n${e.message},\n${e.stackTrace.joinToString("\n")}")
|
println("Config error:\n$e,\n${e.message},\n${e.stackTrace.joinToString("\n")}")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
|
|
|
@ -10,11 +10,15 @@ object Config {
|
||||||
val systemSpec = Config { addSpec(SystemSpec) }.from.toml
|
val systemSpec = Config { addSpec(SystemSpec) }.from.toml
|
||||||
val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml
|
val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml
|
||||||
val commandSpec = Config { addSpec(CommandSpec) }.from.toml
|
val commandSpec = Config { addSpec(CommandSpec) }.from.toml
|
||||||
|
val featureSpec = Config { addSpec(FeatureSpec) }.from.toml
|
||||||
|
|
||||||
lateinit var system: Config
|
lateinit var system: Config
|
||||||
lateinit var localization: Config
|
lateinit var localization: Config
|
||||||
lateinit var server: Server
|
|
||||||
lateinit var commandConfig: Config
|
lateinit var commandConfig: Config
|
||||||
lateinit var features: Features
|
lateinit var featureConfig: Config
|
||||||
|
lateinit var server: Server
|
||||||
|
|
||||||
// for easier access
|
// for easier access
|
||||||
|
val features: Features get() = featureConfig[FeatureSpec.features]
|
||||||
val commands: List<Command> get() = commandConfig[CommandSpec.command]
|
val commands: List<Command> get() = commandConfig[CommandSpec.command]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,29 @@ package moe.kageru.kagebot.config
|
||||||
|
|
||||||
import moe.kageru.kagebot.Globals
|
import moe.kageru.kagebot.Globals
|
||||||
import moe.kageru.kagebot.config.SystemSpec.serverId
|
import moe.kageru.kagebot.config.SystemSpec.serverId
|
||||||
import moe.kageru.kagebot.features.Features
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
object ConfigParser {
|
object ConfigParser {
|
||||||
val configFile: File = File(RawConfig.DEFAULT_CONFIG_PATH)
|
internal const val DEFAULT_CONFIG_PATH = "config.toml"
|
||||||
|
val configFile: File = File(DEFAULT_CONFIG_PATH)
|
||||||
|
|
||||||
fun initialLoad(file: String) {
|
fun initialLoad(file: String) {
|
||||||
val rawConfig = RawConfig.read(file)
|
val configFile = getFile(file)
|
||||||
val configFile = RawConfig.getFile(file)
|
|
||||||
val config = Config.systemSpec.file(configFile)
|
val config = Config.systemSpec.file(configFile)
|
||||||
Config.system = config
|
Config.system = config
|
||||||
Config.server = Globals.api.getServerById(config[serverId])
|
Config.server = Globals.api.getServerById(config[serverId])
|
||||||
.orElseThrow { IllegalArgumentException("Invalid server configured.") }
|
.orElseThrow { IllegalArgumentException("Invalid server configured.") }
|
||||||
Config.localization = Config.localeSpec.file(configFile)
|
Config.localization = Config.localeSpec.file(configFile)
|
||||||
reloadFeatures(rawConfig)
|
Config.featureConfig = Config.featureSpec.file(configFile)
|
||||||
Config.commandConfig = Config.commandSpec.file(configFile)
|
Config.commandConfig = Config.commandSpec.file(configFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reloadFeatures(rawConfig: RawConfig) {
|
private fun getFile(path: String): File {
|
||||||
Config.features = rawConfig.features?.let(::Features)
|
val file = File(path)
|
||||||
?: Features(RawFeatures(null, null))
|
if (file.isFile) {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
println("Config not found, falling back to defaults...")
|
||||||
|
return File(this::class.java.classLoader.getResource(path)!!.toURI())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
package moe.kageru.kagebot.config
|
||||||
|
|
||||||
|
import com.uchuhimo.konf.ConfigSpec
|
||||||
|
import moe.kageru.kagebot.command.Command
|
||||||
|
import moe.kageru.kagebot.config.Config.system
|
||||||
|
import moe.kageru.kagebot.features.Features
|
||||||
|
import java.awt.Color
|
||||||
|
|
||||||
|
object SystemSpec : ConfigSpec() {
|
||||||
|
private val rawColor by optional("#1793d0", name = "color")
|
||||||
|
val serverId by required<String>()
|
||||||
|
val color by kotlin.lazy { Color.decode(system[rawColor])!! }
|
||||||
|
}
|
||||||
|
|
||||||
|
object LocalizationSpec : ConfigSpec() {
|
||||||
|
val permissionDenied by optional("You do not have the permission to use this command.")
|
||||||
|
val redirectedMessage by optional("says")
|
||||||
|
val messageDeleted by optional("Your message was deleted.")
|
||||||
|
val timeout by optional("You have been timed out for @@ minutes.")
|
||||||
|
}
|
||||||
|
|
||||||
|
object CommandSpec : ConfigSpec(prefix = "") {
|
||||||
|
val command by optional(emptyList<Command>())
|
||||||
|
}
|
||||||
|
|
||||||
|
object FeatureSpec : ConfigSpec(prefix = "") {
|
||||||
|
val features by optional(Features.DEFAULT, name = "feature")
|
||||||
|
}
|
|
@ -1,48 +0,0 @@
|
||||||
package moe.kageru.kagebot.config
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
import com.moandjiezana.toml.Toml
|
|
||||||
import com.uchuhimo.konf.ConfigSpec
|
|
||||||
import moe.kageru.kagebot.command.Command
|
|
||||||
import moe.kageru.kagebot.config.Config.system
|
|
||||||
import java.awt.Color
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class RawConfig(@SerializedName("feature") val features: RawFeatures?) {
|
|
||||||
companion object {
|
|
||||||
const val DEFAULT_CONFIG_PATH = "config.toml"
|
|
||||||
|
|
||||||
fun readFromString(tomlContent: String): RawConfig = Toml().read(tomlContent).to(RawConfig::class.java)
|
|
||||||
|
|
||||||
fun getFile(path: String): File {
|
|
||||||
val file = File(path)
|
|
||||||
if (file.isFile) {
|
|
||||||
return file
|
|
||||||
}
|
|
||||||
println("Config not found, falling back to defaults...")
|
|
||||||
return File(this::class.java.classLoader.getResource(path)!!.toURI())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun read(path: String = DEFAULT_CONFIG_PATH): RawConfig {
|
|
||||||
val toml: Toml = Toml().read(getFile(path))
|
|
||||||
return toml.to(RawConfig::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object SystemSpec : ConfigSpec() {
|
|
||||||
private val rawColor by optional("#1793d0", name = "color")
|
|
||||||
val serverId by required<String>()
|
|
||||||
val color by kotlin.lazy { Color.decode(system[rawColor])!! }
|
|
||||||
}
|
|
||||||
|
|
||||||
object LocalizationSpec : ConfigSpec() {
|
|
||||||
val permissionDenied by optional("You do not have the permission to use this command.")
|
|
||||||
val redirectedMessage by optional("says")
|
|
||||||
val messageDeleted by optional("Your message was deleted.")
|
|
||||||
val timeout by optional("You have been timed out for @@ minutes.")
|
|
||||||
}
|
|
||||||
|
|
||||||
object CommandSpec : ConfigSpec(prefix = "") {
|
|
||||||
val command by optional(emptyList<Command>())
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
package moe.kageru.kagebot.config
|
|
||||||
|
|
||||||
class RawFeatures(val welcome: RawWelcomeFeature?, val timeout: RawTimeoutFeature?)
|
|
||||||
class RawWelcomeFeature(val content: List<String>?, val fallbackChannel: String?, val fallbackMessage: String?)
|
|
||||||
class RawTimeoutFeature(val role: String?)
|
|
|
@ -1,23 +1,10 @@
|
||||||
package moe.kageru.kagebot.features
|
package moe.kageru.kagebot.features
|
||||||
|
|
||||||
import moe.kageru.kagebot.config.RawFeatures
|
class Features(val welcome: WelcomeFeature?, val timeout: TimeoutFeature?) {
|
||||||
|
private val debug = DebugFeature()
|
||||||
class Features(
|
private val help = HelpFeature()
|
||||||
val welcome: WelcomeFeature?,
|
private val getConfig = GetConfigFeature()
|
||||||
debug: DebugFeature,
|
private val setConfig = SetConfigFeature()
|
||||||
help: HelpFeature,
|
|
||||||
getConfig: GetConfigFeature,
|
|
||||||
setConfig: SetConfigFeature,
|
|
||||||
val timeout: TimeoutFeature?
|
|
||||||
) {
|
|
||||||
constructor(rawFeatures: RawFeatures) : this(
|
|
||||||
rawFeatures.welcome?.let(::WelcomeFeature),
|
|
||||||
DebugFeature(),
|
|
||||||
HelpFeature(),
|
|
||||||
GetConfigFeature(),
|
|
||||||
SetConfigFeature(),
|
|
||||||
rawFeatures.timeout?.let(::TimeoutFeature)
|
|
||||||
)
|
|
||||||
|
|
||||||
private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout)
|
private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout)
|
||||||
private val featureMap = mapOf(
|
private val featureMap = mapOf(
|
||||||
|
@ -31,4 +18,8 @@ class Features(
|
||||||
|
|
||||||
fun findByString(feature: String) = featureMap[feature]
|
fun findByString(feature: String) = featureMap[feature]
|
||||||
fun eventFeatures() = all.filterIsInstance<EventFeature>()
|
fun eventFeatures() = all.filterIsInstance<EventFeature>()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = Features(null, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
package moe.kageru.kagebot.features
|
package moe.kageru.kagebot.features
|
||||||
|
|
||||||
import moe.kageru.kagebot.Log
|
|
||||||
import moe.kageru.kagebot.MessageUtil.sendEmbed
|
import moe.kageru.kagebot.MessageUtil.sendEmbed
|
||||||
import moe.kageru.kagebot.config.Config
|
import moe.kageru.kagebot.config.Config
|
||||||
import moe.kageru.kagebot.config.ConfigParser
|
import moe.kageru.kagebot.config.ConfigParser
|
||||||
import moe.kageru.kagebot.config.RawConfig
|
|
||||||
import org.javacord.api.entity.channel.TextChannel
|
|
||||||
import org.javacord.api.event.message.MessageCreateEvent
|
import org.javacord.api.event.message.MessageCreateEvent
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
class SetConfigFeature : MessageFeature {
|
class SetConfigFeature : MessageFeature {
|
||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
|
@ -16,33 +14,16 @@ class SetConfigFeature : MessageFeature {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val newConfig = message.messageAttachments[0].url.openStream().readAllBytes().decodeToString()
|
val newConfig = message.messageAttachments[0].url.openStream().readAllBytes().decodeToString()
|
||||||
val rawConfig = try {
|
|
||||||
RawConfig.readFromString(newConfig)
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
reportError(message.channel, e)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
Config.localization = Config.localeSpec.string(newConfig)
|
Config.localization = Config.localeSpec.string(newConfig)
|
||||||
ConfigParser.reloadFeatures(rawConfig)
|
Config.featureConfig = Config.featureSpec.string(newConfig)
|
||||||
Config.commandConfig = Config.commandSpec.string(newConfig)
|
Config.commandConfig = Config.commandSpec.string(newConfig)
|
||||||
ConfigParser.configFile.writeText(newConfig)
|
ConfigParser.configFile.writeText(newConfig)
|
||||||
message.channel.sendMessage("Config reloaded.")
|
message.channel.sendMessage("Config reloaded.")
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: Exception) {
|
||||||
message.channel.sendEmbed {
|
message.channel.sendEmbed {
|
||||||
addField("Error", "```${e.message}```")
|
addField("Error", "```${e.message}```")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reportError(message: TextChannel, e: IllegalStateException) {
|
|
||||||
message.sendEmbed {
|
|
||||||
addField(
|
|
||||||
"An unexpected error occured. This is probably caused by a malformed config file. Perhaps this can help:",
|
|
||||||
"```$e: ${e.message}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Log.info("Could not parse new config: $e: ${e.message}")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package moe.kageru.kagebot.features
|
package moe.kageru.kagebot.features
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import moe.kageru.kagebot.Log
|
import moe.kageru.kagebot.Log
|
||||||
import moe.kageru.kagebot.MessageUtil.sendEmbed
|
import moe.kageru.kagebot.MessageUtil.sendEmbed
|
||||||
import moe.kageru.kagebot.Util.findRole
|
import moe.kageru.kagebot.Util.findRole
|
||||||
|
@ -7,16 +8,14 @@ import moe.kageru.kagebot.Util.findUser
|
||||||
import moe.kageru.kagebot.Util.ifNotEmpty
|
import moe.kageru.kagebot.Util.ifNotEmpty
|
||||||
import moe.kageru.kagebot.config.Config
|
import moe.kageru.kagebot.config.Config
|
||||||
import moe.kageru.kagebot.config.LocalizationSpec
|
import moe.kageru.kagebot.config.LocalizationSpec
|
||||||
import moe.kageru.kagebot.config.RawTimeoutFeature
|
|
||||||
import moe.kageru.kagebot.persistence.Dao
|
import moe.kageru.kagebot.persistence.Dao
|
||||||
import org.javacord.api.entity.permission.Role
|
import org.javacord.api.entity.permission.Role
|
||||||
import org.javacord.api.event.message.MessageCreateEvent
|
import org.javacord.api.event.message.MessageCreateEvent
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
class TimeoutFeature(raw: RawTimeoutFeature) : MessageFeature {
|
class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature {
|
||||||
private val timeoutRole: Role = raw.role?.let(::findRole)
|
private val timeoutRole: Role = findRole(role)
|
||||||
?: throw IllegalArgumentException("No timeout role defined")
|
|
||||||
|
|
||||||
override fun handle(message: MessageCreateEvent) {
|
override fun handle(message: MessageCreateEvent) {
|
||||||
val timeout = message.readableMessageContent.split(' ', limit = 4).let { args ->
|
val timeout = message.readableMessageContent.split(' ', limit = 4).let { args ->
|
||||||
|
|
|
@ -5,14 +5,19 @@ import moe.kageru.kagebot.MessageUtil
|
||||||
import moe.kageru.kagebot.Util
|
import moe.kageru.kagebot.Util
|
||||||
import moe.kageru.kagebot.Util.checked
|
import moe.kageru.kagebot.Util.checked
|
||||||
import moe.kageru.kagebot.Util.failed
|
import moe.kageru.kagebot.Util.failed
|
||||||
import moe.kageru.kagebot.config.RawWelcomeFeature
|
|
||||||
import org.javacord.api.DiscordApi
|
import org.javacord.api.DiscordApi
|
||||||
import org.javacord.api.entity.channel.TextChannel
|
import org.javacord.api.entity.channel.TextChannel
|
||||||
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 org.javacord.api.event.server.member.ServerMemberJoinEvent
|
import org.javacord.api.event.server.member.ServerMemberJoinEvent
|
||||||
|
|
||||||
class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeature {
|
class WelcomeFeature(
|
||||||
|
content: List<String>?,
|
||||||
|
fallbackChannel: String?,
|
||||||
|
private val fallbackMessage: String?
|
||||||
|
) : MessageFeature, EventFeature {
|
||||||
|
val embed: EmbedBuilder? by lazy { content?.let(MessageUtil::listToEmbed) }
|
||||||
|
|
||||||
override fun register(api: DiscordApi) {
|
override fun register(api: DiscordApi) {
|
||||||
api.addServerMemberJoinListener { event ->
|
api.addServerMemberJoinListener { event ->
|
||||||
checked { welcomeUser(event) }
|
checked { welcomeUser(event) }
|
||||||
|
@ -40,14 +45,10 @@ class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeatu
|
||||||
|
|
||||||
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
|
private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null
|
||||||
|
|
||||||
val embed: EmbedBuilder? by lazy {
|
private val fallbackChannel: TextChannel? = fallbackChannel?.let {
|
||||||
rawWelcome.content?.let(MessageUtil::listToEmbed)
|
requireNotNull(fallbackMessage) {
|
||||||
}
|
|
||||||
private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let {
|
|
||||||
requireNotNull(rawWelcome.fallbackMessage) {
|
|
||||||
"[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
|
"[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined"
|
||||||
}
|
}
|
||||||
Util.findChannel(it)
|
Util.findChannel(it)
|
||||||
}
|
}
|
||||||
private val fallbackMessage: String? = rawWelcome.fallbackMessage
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ConfigTest : ShouldSpec({
|
||||||
"should properly parse test config" {
|
"should properly parse test config" {
|
||||||
Config.system[SystemSpec.serverId] shouldNotBe null
|
Config.system[SystemSpec.serverId] shouldNotBe null
|
||||||
SystemSpec.color shouldBe Color.decode("#1793d0")
|
SystemSpec.color shouldBe Color.decode("#1793d0")
|
||||||
Config.features shouldNotBe null
|
Config.features.welcome!!.embed shouldNotBe null
|
||||||
Config.commands.size shouldBe 3
|
Config.commands.size shouldBe 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user