diff --git a/build.gradle.kts b/build.gradle.kts index ba2327b..094f88a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -36,7 +36,6 @@ val test by tasks.getting(Test::class) { dependencies { implementation("com.uchuhimo:konf-core:0.20.0") implementation("com.uchuhimo:konf-toml:0.20.0") - implementation("com.moandjiezana.toml:toml4j:0.7.2") implementation(kotlin("stdlib-jdk8")) implementation("org.javacord:javacord:3.0.4") implementation("org.mapdb:mapdb:3.0.7") diff --git a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt index fe7e236..a02e25a 100644 --- a/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt +++ b/src/main/kotlin/moe/kageru/kagebot/Kagebot.kt @@ -3,7 +3,6 @@ package moe.kageru.kagebot import moe.kageru.kagebot.Util.checked import moe.kageru.kagebot.config.Config import moe.kageru.kagebot.config.ConfigParser -import moe.kageru.kagebot.config.RawConfig import moe.kageru.kagebot.cron.CronD import moe.kageru.kagebot.persistence.Dao import org.javacord.api.DiscordApiBuilder @@ -44,7 +43,7 @@ object Kagebot { val api = DiscordApiBuilder().setToken(secret).login().join() Globals.api = api try { - ConfigParser.initialLoad(RawConfig.DEFAULT_CONFIG_PATH) + ConfigParser.initialLoad(ConfigParser.DEFAULT_CONFIG_PATH) } catch (e: IllegalArgumentException) { println("Config error:\n$e,\n${e.message},\n${e.stackTrace.joinToString("\n")}") exitProcess(1) diff --git a/src/main/kotlin/moe/kageru/kagebot/config/Config.kt b/src/main/kotlin/moe/kageru/kagebot/config/Config.kt index fae3055..5fc0605 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/Config.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/Config.kt @@ -10,11 +10,15 @@ object Config { val systemSpec = Config { addSpec(SystemSpec) }.from.toml val localeSpec = Config { addSpec(LocalizationSpec) }.from.toml val commandSpec = Config { addSpec(CommandSpec) }.from.toml + val featureSpec = Config { addSpec(FeatureSpec) }.from.toml + lateinit var system: Config lateinit var localization: Config - lateinit var server: Server lateinit var commandConfig: Config - lateinit var features: Features + lateinit var featureConfig: Config + lateinit var server: Server + // for easier access + val features: Features get() = featureConfig[FeatureSpec.features] val commands: List get() = commandConfig[CommandSpec.command] } diff --git a/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt b/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt index cce3bc9..3c1043a 100644 --- a/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt +++ b/src/main/kotlin/moe/kageru/kagebot/config/ConfigParser.kt @@ -2,26 +2,29 @@ package moe.kageru.kagebot.config import moe.kageru.kagebot.Globals import moe.kageru.kagebot.config.SystemSpec.serverId -import moe.kageru.kagebot.features.Features import java.io.File 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) { - val rawConfig = RawConfig.read(file) - val configFile = RawConfig.getFile(file) + val configFile = getFile(file) val config = Config.systemSpec.file(configFile) Config.system = config Config.server = Globals.api.getServerById(config[serverId]) .orElseThrow { IllegalArgumentException("Invalid server configured.") } Config.localization = Config.localeSpec.file(configFile) - reloadFeatures(rawConfig) + Config.featureConfig = Config.featureSpec.file(configFile) Config.commandConfig = Config.commandSpec.file(configFile) } - fun reloadFeatures(rawConfig: RawConfig) { - Config.features = rawConfig.features?.let(::Features) - ?: Features(RawFeatures(null, null)) + private 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()) } } diff --git a/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt b/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt new file mode 100644 index 0000000..71e0ecc --- /dev/null +++ b/src/main/kotlin/moe/kageru/kagebot/config/ConfigSpecs.kt @@ -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() + 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()) +} + +object FeatureSpec : ConfigSpec(prefix = "") { + val features by optional(Features.DEFAULT, name = "feature") +} diff --git a/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt b/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt deleted file mode 100644 index f2b882f..0000000 --- a/src/main/kotlin/moe/kageru/kagebot/config/RawConfig.kt +++ /dev/null @@ -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() - 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()) -} diff --git a/src/main/kotlin/moe/kageru/kagebot/config/RawFeatures.kt b/src/main/kotlin/moe/kageru/kagebot/config/RawFeatures.kt deleted file mode 100644 index 75b25a0..0000000 --- a/src/main/kotlin/moe/kageru/kagebot/config/RawFeatures.kt +++ /dev/null @@ -1,5 +0,0 @@ -package moe.kageru.kagebot.config - -class RawFeatures(val welcome: RawWelcomeFeature?, val timeout: RawTimeoutFeature?) -class RawWelcomeFeature(val content: List?, val fallbackChannel: String?, val fallbackMessage: String?) -class RawTimeoutFeature(val role: String?) diff --git a/src/main/kotlin/moe/kageru/kagebot/features/Features.kt b/src/main/kotlin/moe/kageru/kagebot/features/Features.kt index ca115f4..ce1f25b 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/Features.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/Features.kt @@ -1,23 +1,10 @@ package moe.kageru.kagebot.features -import moe.kageru.kagebot.config.RawFeatures - -class Features( - val welcome: WelcomeFeature?, - debug: DebugFeature, - 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) - ) +class Features(val welcome: WelcomeFeature?, val timeout: TimeoutFeature?) { + private val debug = DebugFeature() + private val help = HelpFeature() + private val getConfig = GetConfigFeature() + private val setConfig = SetConfigFeature() private val all = listOf(welcome, debug, help, getConfig, setConfig, timeout) private val featureMap = mapOf( @@ -31,4 +18,8 @@ class Features( fun findByString(feature: String) = featureMap[feature] fun eventFeatures() = all.filterIsInstance() + + companion object { + val DEFAULT = Features(null, null) + } } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt index 128d43c..0697a1d 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/SetConfigFeature.kt @@ -1,12 +1,10 @@ package moe.kageru.kagebot.features -import moe.kageru.kagebot.Log import moe.kageru.kagebot.MessageUtil.sendEmbed import moe.kageru.kagebot.config.Config 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 java.lang.reflect.InvocationTargetException class SetConfigFeature : MessageFeature { @ExperimentalStdlibApi @@ -16,33 +14,16 @@ class SetConfigFeature : MessageFeature { return } val newConfig = message.messageAttachments[0].url.openStream().readAllBytes().decodeToString() - val rawConfig = try { - RawConfig.readFromString(newConfig) - } catch (e: IllegalStateException) { - reportError(message.channel, e) - return - } try { Config.localization = Config.localeSpec.string(newConfig) - ConfigParser.reloadFeatures(rawConfig) + Config.featureConfig = Config.featureSpec.string(newConfig) Config.commandConfig = Config.commandSpec.string(newConfig) ConfigParser.configFile.writeText(newConfig) message.channel.sendMessage("Config reloaded.") - } catch (e: IllegalArgumentException) { + } catch (e: Exception) { message.channel.sendEmbed { 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 - } } diff --git a/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt index ef01c1d..6e14443 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/TimeoutFeature.kt @@ -1,5 +1,6 @@ package moe.kageru.kagebot.features +import com.fasterxml.jackson.annotation.JsonProperty import moe.kageru.kagebot.Log import moe.kageru.kagebot.MessageUtil.sendEmbed 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.config.Config import moe.kageru.kagebot.config.LocalizationSpec -import moe.kageru.kagebot.config.RawTimeoutFeature import moe.kageru.kagebot.persistence.Dao import org.javacord.api.entity.permission.Role import org.javacord.api.event.message.MessageCreateEvent import java.time.Duration import java.time.Instant -class TimeoutFeature(raw: RawTimeoutFeature) : MessageFeature { - private val timeoutRole: Role = raw.role?.let(::findRole) - ?: throw IllegalArgumentException("No timeout role defined") +class TimeoutFeature(@JsonProperty("role") role: String) : MessageFeature { + private val timeoutRole: Role = findRole(role) override fun handle(message: MessageCreateEvent) { val timeout = message.readableMessageContent.split(' ', limit = 4).let { args -> diff --git a/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt b/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt index 3a9e38f..f51c035 100644 --- a/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt +++ b/src/main/kotlin/moe/kageru/kagebot/features/WelcomeFeature.kt @@ -5,14 +5,19 @@ import moe.kageru.kagebot.MessageUtil import moe.kageru.kagebot.Util import moe.kageru.kagebot.Util.checked import moe.kageru.kagebot.Util.failed -import moe.kageru.kagebot.config.RawWelcomeFeature import org.javacord.api.DiscordApi import org.javacord.api.entity.channel.TextChannel import org.javacord.api.entity.message.embed.EmbedBuilder import org.javacord.api.event.message.MessageCreateEvent import org.javacord.api.event.server.member.ServerMemberJoinEvent -class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeature { +class WelcomeFeature( + content: List?, + fallbackChannel: String?, + private val fallbackMessage: String? +) : MessageFeature, EventFeature { + val embed: EmbedBuilder? by lazy { content?.let(MessageUtil::listToEmbed) } + override fun register(api: DiscordApi) { api.addServerMemberJoinListener { event -> checked { welcomeUser(event) } @@ -40,14 +45,10 @@ class WelcomeFeature(rawWelcome: RawWelcomeFeature) : MessageFeature, EventFeatu private fun hasFallback(): Boolean = fallbackChannel != null && fallbackMessage != null - val embed: EmbedBuilder? by lazy { - rawWelcome.content?.let(MessageUtil::listToEmbed) - } - private val fallbackChannel: TextChannel? = rawWelcome.fallbackChannel?.let { - requireNotNull(rawWelcome.fallbackMessage) { + private val fallbackChannel: TextChannel? = fallbackChannel?.let { + requireNotNull(fallbackMessage) { "[feature.welcome.fallbackMessage] must not be null if fallbackChannel is defined" } Util.findChannel(it) } - private val fallbackMessage: String? = rawWelcome.fallbackMessage } diff --git a/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt b/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt index def7346..0ddb706 100644 --- a/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt +++ b/src/test/kotlin/moe/kageru/kagebot/ConfigTest.kt @@ -17,7 +17,7 @@ class ConfigTest : ShouldSpec({ "should properly parse test config" { Config.system[SystemSpec.serverId] shouldNotBe null SystemSpec.color shouldBe Color.decode("#1793d0") - Config.features shouldNotBe null + Config.features.welcome!!.embed shouldNotBe null Config.commands.size shouldBe 3 }