From aa8dc1af57434f0a5cdc7f0ce565d80eed04d85f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 8 May 2021 21:35:53 +0600 Subject: [PATCH 1/8] start 0.2.2 --- CHANGELOG.md | 2 ++ gradle.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 944a783..7b833ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.2.2 + ## 0.2.1 * `Versions` diff --git a/gradle.properties b/gradle.properties index 59c2348..6e7eb10 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,4 @@ sqlite_version=3.34.0 github_release_plugin_version=2.2.12 group=dev.inmo -version=0.2.1 +version=0.2.2 From 3ba965f16212cf1f50c95012b585ebf03e985d8a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 9 May 2021 12:37:28 +0600 Subject: [PATCH 2/8] PluginsHolder, new Config deserialization and PlaguBot as correctly serializable/deserializable --- CHANGELOG.md | 5 + bot/src/main/kotlin/dev/inmo/plagubot/App.kt | 2 +- .../main/kotlin/dev/inmo/plagubot/PlaguBot.kt | 4 +- .../kotlin/dev/inmo/plagubot/PluginsHolder.kt | 25 +++ .../kotlin/dev/inmo/plagubot/config/Config.kt | 163 +-------------- .../plagubot/config/PluginsConfiguration.kt | 190 ++++++++++++++++++ .../dev/inmo/plagubot/config/ConfigTest.kt | 7 +- 7 files changed, 234 insertions(+), 162 deletions(-) create mode 100644 bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt create mode 100644 bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b833ee..9f662b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 0.2.2 +* `Bot` + * Add plugin `PluginsHolder` + * Rewrite mechanism of `Config` working + * `PlaguBot` now is correctly serializable/deserializable + ## 0.2.1 * `Versions` diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/App.kt b/bot/src/main/kotlin/dev/inmo/plagubot/App.kt index cfa0bbe..9c5b12f 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/App.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/App.kt @@ -24,7 +24,7 @@ suspend inline fun initPlaguBot( suspend fun main(args: Array) { val (configPath) = args val file = File(configPath) - val config = configJsonFormat.decodeFromString(ConfigSerializer, file.readText()) + val config = configAndPluginsConfigJsonFormat.decodeFromString(PluginsConfigurationSerializer, file.readText()) as Config PlaguBot(config).start().join() } diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt b/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt index ca7e3b5..98dae72 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt @@ -1,8 +1,7 @@ package dev.inmo.plagubot import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions -import dev.inmo.plagubot.config.Config -import dev.inmo.plagubot.config.database +import dev.inmo.plagubot.config.* import dev.inmo.tgbotapi.bot.Ktor.telegramBot import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext @@ -20,6 +19,7 @@ val Map.plagubot @Serializable data class PlaguBot( + @Serializable(PluginsConfigurationSerializer::class) private val config: Config ) : Plugin { @Transient diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt b/bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt new file mode 100644 index 0000000..a30a362 --- /dev/null +++ b/bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt @@ -0,0 +1,25 @@ +package dev.inmo.plagubot + +import dev.inmo.plagubot.config.PluginsConfigurationSerializer +import dev.inmo.plagubot.config.SimplePluginsConfiguration +import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext +import dev.inmo.tgbotapi.types.BotCommand +import kotlinx.serialization.Serializable +import org.jetbrains.exposed.sql.Database + +@Serializable +data class PluginsHolder( + @Serializable(PluginsConfigurationSerializer::class) + private val pluginsConfiguration: SimplePluginsConfiguration +) : Plugin { + override suspend fun getCommands(): List = pluginsConfiguration.plugins.flatMap { + it.getCommands() + } + + override suspend fun BehaviourContext.invoke(database: Database, params: Map) { + val finalParams = pluginsConfiguration.params ?.plus(params) ?: params + pluginsConfiguration.plugins.forEach { + it.apply { invoke(database, finalParams) } + } + } +} \ No newline at end of file diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt index 063a53c..d4b0238 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt @@ -1,166 +1,15 @@ package dev.inmo.plagubot.config -import com.github.matfax.klassindex.KlassIndex import dev.inmo.plagubot.Plugin -import dev.inmo.plagubot.PluginSerializer -import dev.inmo.sdi.* -import kotlinx.serialization.* -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.* -import kotlinx.serialization.modules.* -import kotlin.reflect.KClass - -@InternalSerializationApi -internal inline fun KClass.includeIn(builder: PolymorphicModuleBuilder) = builder.subclass(this, serializer()) - -@InternalSerializationApi -internal val configJsonFormat: Json - get() = Json { - ignoreUnknownKeys = true - serializersModule = SerializersModule { - polymorphic(Plugin::class) { - KlassIndex.getSubclasses(Plugin::class).flatMap { kclass -> - kclass.includeIn(this) - kclass.annotations.mapNotNull { it as? SerialName }.map { - it.value to kclass.serializer() - } + listOfNotNull( - kclass.simpleName ?.let { - it to kclass.serializer() - } - ) - }.toMap().let { - default { requiredType -> - it[requiredType] - } - } - } - } - } +import dev.inmo.sdi.Module +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable @Serializable data class Config( - val plugins: List<@Contextual Plugin>, + override val plugins: List<@Contextual Plugin>, val database: DatabaseConfig = DatabaseConfig(), val botToken: String, @Contextual - val params: Module? = null -) - -@Serializer(Plugin::class) -private class InternalPluginSerializer( - private val params: Module -) : KSerializer { - override val descriptor: SerialDescriptor = PluginSerializer.descriptor - - @InternalSerializationApi - override fun deserialize(decoder: Decoder): Plugin { - val asJson = JsonElement.serializer().deserialize(decoder) - - return if (asJson is JsonPrimitive) { - params[asJson.jsonPrimitive.content] as Plugin - } else { - val jsonFormat = ((decoder as? JsonDecoder)?.json ?: configJsonFormat) - jsonFormat.decodeFromJsonElement(PluginSerializer, asJson) - } - } - - @InternalSerializationApi - override fun serialize(encoder: Encoder, value: Plugin) { - params.keys.firstOrNull { params[it] === value } ?.also { - encoder.encodeString(it) - } ?: PluginSerializer.serialize(encoder, value) - } -} - -private val DefaultModuleSerializer = ModuleSerializer(emptyList()) { - -} - -@Serializer(Module::class) -private class InternalModuleSerializer( - private val original: JsonElement?, - private val params: Module -) : KSerializer { - override val descriptor: SerialDescriptor = PluginSerializer.descriptor - - @InternalSerializationApi - override fun deserialize(decoder: Decoder): Module { - val asJson = JsonElement.serializer().deserialize(decoder) - - return if (asJson == original) { - params - } else { - configJsonFormat.decodeFromJsonElement(DefaultModuleSerializer, asJson) - } - } - - @InternalSerializationApi - override fun serialize(encoder: Encoder, value: Module) = DefaultModuleSerializer.serialize(encoder, value) -} - -private fun internalPluginSerializerSerializersModule( - internalPluginSerializer: InternalPluginSerializer, - internalModuleSerializer: InternalModuleSerializer? -) = SerializersModule { - contextual(internalPluginSerializer) - contextual(internalModuleSerializer ?: return@SerializersModule) -} - -@Serializer(Config::class) -internal object ConfigSerializer : KSerializer { - private val jsonSerializer = JsonObject.serializer() - private val moduleSerializer = ModuleSerializer() - - @InternalSerializationApi - override fun deserialize(decoder: Decoder): Config { - val json = jsonSerializer.deserialize(decoder) - val jsonFormat = (decoder as? JsonDecoder) ?.json ?: configJsonFormat - val paramsRow = json["params"] - - val resultJsonFormat = if (paramsRow != null && paramsRow != JsonNull) { - val params = jsonFormat.decodeFromJsonElement( - moduleSerializer, - paramsRow - ) - - val pluginsSerializer = InternalPluginSerializer(params) - val moduleSerializer = InternalModuleSerializer(paramsRow, params) - Json(jsonFormat) { - serializersModule = decoder.serializersModule.overwriteWith( - internalPluginSerializerSerializersModule(pluginsSerializer, moduleSerializer) - ) - } - } else { - jsonFormat - } - return resultJsonFormat.decodeFromJsonElement( - Config.serializer(), - json - ) - } - - @InternalSerializationApi - override fun serialize(encoder: Encoder, value: Config) { - if (value.params != null) { - val pluginsSerializer = InternalPluginSerializer(value.params) - - val jsonFormat = Json(configJsonFormat) { - serializersModule = encoder.serializersModule.overwriteWith( - internalPluginSerializerSerializersModule(pluginsSerializer, null) - ) - } - - jsonSerializer.serialize( - encoder, - jsonFormat.encodeToJsonElement( - Config.serializer(), - value - ) as JsonObject - ) - } else { - Config.serializer().serialize(encoder, value) - } - } -} + override val params: Module? = null +) : PluginsConfiguration diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt new file mode 100644 index 0000000..c94ada7 --- /dev/null +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt @@ -0,0 +1,190 @@ +package dev.inmo.plagubot.config + +import com.github.matfax.klassindex.KlassIndex +import dev.inmo.plagubot.Plugin +import dev.inmo.plagubot.PluginSerializer +import dev.inmo.sdi.Module +import dev.inmo.sdi.ModuleSerializer +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.* +import kotlin.reflect.KClass + +@InternalSerializationApi +internal inline fun KClass.includeIn(builder: PolymorphicModuleBuilder) = builder.subclass(this, serializer()) + +@InternalSerializationApi +internal val configAndPluginsConfigJsonFormat: Json + get() = Json { + ignoreUnknownKeys = true + serializersModule = SerializersModule { + polymorphic(Plugin::class) { + KlassIndex.getSubclasses(Plugin::class).flatMap { kclass -> + kclass.includeIn(this) + kclass.annotations.mapNotNull { it as? SerialName }.map { + it.value to kclass.serializer() + } + listOfNotNull( + kclass.simpleName ?.let { + it to kclass.serializer() + } + ) + }.toMap().let { + default { requiredType -> + it[requiredType] + } + } + } + } + } + +internal interface PluginsConfiguration { + val plugins: List + val params: Module? +} + +@Serializable +data class SimplePluginsConfiguration( + override val plugins: List<@Contextual Plugin>, + @Contextual + override val params: Module? = null +) : PluginsConfiguration + + +internal val DefaultModuleSerializer = ModuleSerializer(emptyList()) { + +} + +@Serializer(Plugin::class) +internal class InternalPluginSerializer( + private val params: Module +) : KSerializer { + override val descriptor: SerialDescriptor = PluginSerializer.descriptor + + @InternalSerializationApi + override fun deserialize(decoder: Decoder): Plugin { + val asJson = JsonElement.serializer().deserialize(decoder) + + return if (asJson is JsonPrimitive) { + params[asJson.jsonPrimitive.content] as Plugin + } else { + val jsonFormat = ((decoder as? JsonDecoder)?.json ?: configAndPluginsConfigJsonFormat) + jsonFormat.decodeFromJsonElement(PluginSerializer, asJson) + } + } + + @InternalSerializationApi + override fun serialize(encoder: Encoder, value: Plugin) { + params.keys.firstOrNull { params[it] === value } ?.also { + encoder.encodeString(it) + } ?: PluginSerializer.serialize(encoder, value) + } +} + +@Serializer(Module::class) +internal class InternalModuleSerializer( + private val original: JsonElement?, + private val params: Module +) : KSerializer { + override val descriptor: SerialDescriptor = PluginSerializer.descriptor + + @InternalSerializationApi + override fun deserialize(decoder: Decoder): Module { + val asJson = JsonElement.serializer().deserialize(decoder) + + return if (asJson == original) { + params + } else { + configAndPluginsConfigJsonFormat.decodeFromJsonElement(DefaultModuleSerializer, asJson) + } + } + + @InternalSerializationApi + override fun serialize(encoder: Encoder, value: Module) = DefaultModuleSerializer.serialize(encoder, value) +} + +internal fun internalPluginSerializerSerializersModule( + internalPluginSerializer: InternalPluginSerializer, + internalModuleSerializer: InternalModuleSerializer? +) = SerializersModule { + contextual(internalPluginSerializer) + contextual(internalModuleSerializer ?: return@SerializersModule) +} + +@Serializer(PluginsConfiguration::class) +internal object PluginsConfigurationSerializer : KSerializer { + private val jsonSerializer = JsonObject.serializer() + private val moduleSerializer = ModuleSerializer() + override val descriptor: SerialDescriptor = jsonSerializer.descriptor + + @InternalSerializationApi + override fun deserialize(decoder: Decoder): PluginsConfiguration { + val json = jsonSerializer.deserialize(decoder) + val jsonFormat = (decoder as? JsonDecoder) ?.json ?: configAndPluginsConfigJsonFormat + val paramsRow = json["params"] + + val resultJsonFormat = if (paramsRow != null && paramsRow != JsonNull) { + val params = jsonFormat.decodeFromJsonElement( + moduleSerializer, + paramsRow + ) + + val pluginsSerializer = InternalPluginSerializer(params) + val moduleSerializer = InternalModuleSerializer(paramsRow, params) + Json(jsonFormat) { + serializersModule = decoder.serializersModule.overwriteWith( + internalPluginSerializerSerializersModule(pluginsSerializer, moduleSerializer) + ) + } + } else { + jsonFormat + } + return try { + resultJsonFormat.decodeFromJsonElement( + Config.serializer(), + json + ) + } catch (e: SerializationException) { + resultJsonFormat.decodeFromJsonElement( + SimplePluginsConfiguration.serializer(), + json + ) + } + } + + @InternalSerializationApi + override fun serialize(encoder: Encoder, value: PluginsConfiguration) { + val params = value.params + val serializer = when (value) { + is Config -> Config.serializer() + is SimplePluginsConfiguration -> SimplePluginsConfiguration.serializer() + else -> return + } + if (params != null) { + val pluginsSerializer = InternalPluginSerializer(params) + + val jsonFormat = Json(configAndPluginsConfigJsonFormat) { + serializersModule = encoder.serializersModule.overwriteWith( + internalPluginSerializerSerializersModule(pluginsSerializer, null) + ) + } + + jsonSerializer.serialize( + encoder, + when (value) { + is Config -> jsonFormat.encodeToJsonElement(Config.serializer(), value) + is SimplePluginsConfiguration -> jsonFormat.encodeToJsonElement(SimplePluginsConfiguration.serializer(), value) + else -> return + } as JsonObject + ) + } else { + when (value) { + is Config -> Config.serializer().serialize(encoder, value) + is SimplePluginsConfiguration -> SimplePluginsConfiguration.serializer().serialize(encoder, value) + else -> return + } + } + } +} diff --git a/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt b/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt index 48b6fa9..3cd4fe9 100644 --- a/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt +++ b/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt @@ -22,13 +22,16 @@ class ConfigTest { } } """.trimIndent() - val config = configJsonFormat.decodeFromString(ConfigSerializer, rawConfig) + val config = configAndPluginsConfigJsonFormat.decodeFromString(PluginsConfigurationSerializer, rawConfig) as Config assert(config.plugins.size == 1) assert(config.plugins.first() is HelloPlugin) assert((config.plugins.first() as HelloPlugin).parameter == "Example") - val redecoded = configJsonFormat.decodeFromString(ConfigSerializer, configJsonFormat.encodeToString(ConfigSerializer, config)) + val redecoded = configAndPluginsConfigJsonFormat.decodeFromString( + PluginsConfigurationSerializer, + configAndPluginsConfigJsonFormat.encodeToString(PluginsConfigurationSerializer, config) + ) as Config assertEquals(config.database, redecoded.database) assertEquals(config.plugins, redecoded.plugins) assertEquals(config.botToken, redecoded.botToken) From 5a116d512a575c7b6a29bdf2bfa5278c9c790f3f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 4 Jun 2021 12:09:06 +0600 Subject: [PATCH 3/8] migration onto 0.3.0 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f662b0..7630fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 0.2.2 +## 0.3.0 * `Bot` * Add plugin `PluginsHolder` diff --git a/gradle.properties b/gradle.properties index 6e7eb10..d7bd836 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,4 @@ sqlite_version=3.34.0 github_release_plugin_version=2.2.12 group=dev.inmo -version=0.2.2 +version=0.3.0 From 14e6c0c67d5ed548181aa895527f3f15c535fbd4 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 4 Jun 2021 12:12:58 +0600 Subject: [PATCH 4/8] update versions --- gradle.properties | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index d7bd836..665e7c3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,14 +4,14 @@ org.gradle.parallel=true kotlin.js.generate.externals=true kotlin.incremental=true -kotlin_version=1.4.32 -kotlin_coroutines_version=1.4.3 -kotlin_serialisation_runtime_version=1.1.0 +kotlin_version=1.5.10 +kotlin_coroutines_version=1.5.0 +kotlin_serialisation_runtime_version=1.2.1 kotlin_exposed_version=0.31.1 sdi_version=0.4.1 -tgbotapi_version=0.34.1 -microutils_version=0.4.36 +tgbotapi_version=0.35.0 +microutils_version=0.5.5 klassindex_version=4.1.0-rc.1 sqlite_version=3.34.0 From a0d1bd2d54b6dcb10ae794c62cac379d2e1ec597 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 5 Jun 2021 15:25:17 +0600 Subject: [PATCH 5/8] update mircoutils and exposed --- gradle.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 665e7c3..a68bd4e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,11 +7,11 @@ kotlin.incremental=true kotlin_version=1.5.10 kotlin_coroutines_version=1.5.0 kotlin_serialisation_runtime_version=1.2.1 -kotlin_exposed_version=0.31.1 +kotlin_exposed_version=0.32.1 -sdi_version=0.4.1 +sdi_version=0.5.0 tgbotapi_version=0.35.0 -microutils_version=0.5.5 +microutils_version=0.5.6 klassindex_version=4.1.0-rc.1 sqlite_version=3.34.0 From fae2dca3d3fe0e8fc210275216968f2964ee74db Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sat, 5 Jun 2021 16:16:40 +0600 Subject: [PATCH 6/8] update changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7630fb3..1c43680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## 0.3.0 + +* `Versions` + * `kotlin`: `1.4.32` -> `1.5.10` + * `coroutines`: `1.4.3` -> `1.5.0` + * `serialization`: `1.1.0` -> `1.2.1` + * `exposed`: `0.31.1` -> `0.32.1` + * `sdi`: `0.4.1` -> `0.5.0` + * `tgbotapi`: `0.34.1` -> `0.35.0` + * `microutils`: `0.4.36` -> `0.5.6` * `Bot` * Add plugin `PluginsHolder` * Rewrite mechanism of `Config` working From a42bb9112134e8fd650a1b52608de144607827ed Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 7 Jun 2021 19:35:29 +0600 Subject: [PATCH 7/8] Update gradle.properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a68bd4e..1b046fe 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ kotlin_exposed_version=0.32.1 sdi_version=0.5.0 tgbotapi_version=0.35.0 -microutils_version=0.5.6 +microutils_version=0.5.7 klassindex_version=4.1.0-rc.1 sqlite_version=3.34.0 From 8bf42e6fcab22cf0c9e906ac80d32c95dd24d26e Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 7 Jun 2021 19:35:46 +0600 Subject: [PATCH 8/8] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c43680..fee1795 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ * `exposed`: `0.31.1` -> `0.32.1` * `sdi`: `0.4.1` -> `0.5.0` * `tgbotapi`: `0.34.1` -> `0.35.0` - * `microutils`: `0.4.36` -> `0.5.6` + * `microutils`: `0.4.36` -> `0.5.7` * `Bot` * Add plugin `PluginsHolder` * Rewrite mechanism of `Config` working