From bc29d4f83a2bc4e63dfec0f4318e1b34860aab2a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 13:32:09 +0600 Subject: [PATCH 1/8] start 9.3.1 --- CHANGELOG.md | 2 ++ gradle.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3fbbe7..6cfa9d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 9.3.1 + ## 9.3.0 * `Bot`: diff --git a/gradle.properties b/gradle.properties index 68d24b4..388cc67 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,4 @@ kotlin.js.generate.externals=true kotlin.incremental=true group=dev.inmo -version=9.3.0 +version=9.3.1 From 47f5086ebd5da695a3f175fd3564be98e91b638a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 13:32:59 +0600 Subject: [PATCH 2/8] Plugin#setupBotPlugin will call startPlugin by default --- CHANGELOG.md | 3 +++ plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cfa9d2..9631420 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 9.3.1 +* `Plugin`: + * `Plugin#setupBotPlugin` now will call start plugin by default + ## 9.3.0 * `Bot`: diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt index 621e151..0797d8f 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt @@ -48,7 +48,9 @@ interface Plugin : StartPlugin { */ suspend fun BehaviourContext.setupBotPlugin( koin: Koin - ) {} + ) { + startPlugin(koin) + } /** * Override this method in cases when you want to declare full behaviour of the plugin. It is recommended to declare * common logic of plugin in the [setupBotPlugin] with [BehaviourContext] receiver and use override this one From f9b7d444a682fbedc6976f23e6d5f293983452d8 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 13:33:53 +0600 Subject: [PATCH 3/8] add note about startPlugin in setupBotPlugin --- plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt index 0797d8f..7a8ed97 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt @@ -45,6 +45,8 @@ interface Plugin : StartPlugin { /** * Override this method in cases when you want to declare common bot behaviour. In case you wish to use FSM, you * should override the method with receiver [BehaviourContextWithFSM] + * + * Besides, this method by default will call [startPlugin] */ suspend fun BehaviourContext.setupBotPlugin( koin: Koin From 893f3a95a1a1315dbc31a7dd63596a7190079cd0 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 14:46:20 +0600 Subject: [PATCH 4/8] migration onto 10.0.0 --- CHANGELOG.md | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9631420..09199a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 9.3.1 +## 10.0.0 * `Plugin`: * `Plugin#setupBotPlugin` now will call start plugin by default diff --git a/gradle.properties b/gradle.properties index 388cc67..df50981 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,4 +5,4 @@ kotlin.js.generate.externals=true kotlin.incremental=true group=dev.inmo -version=9.3.1 +version=10.0.0 From d928db7e748f65ba13d2d81ff7436eb54d74e2ae Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 15:29:17 +0600 Subject: [PATCH 5/8] rework of plagubot system --- CHANGELOG.md | 20 +- bot/src/main/kotlin/dev/inmo/plagubot/App.kt | 16 +- .../dev/inmo/plagubot/KoinExtensions.kt | 28 +++ .../main/kotlin/dev/inmo/plagubot/PlaguBot.kt | 198 +++++++++--------- .../kotlin/dev/inmo/plagubot/config/Config.kt | 5 +- .../dev/inmo/plagubot/config/JsonFormat.kt | 2 + .../kotlin/dev/inmo/plagubot/HelloPlugin.kt | 8 +- .../dev/inmo/plagubot/KoinExtensions.kt | 11 + .../main/kotlin/dev/inmo/plagubot/Plugin.kt | 18 +- 9 files changed, 161 insertions(+), 145 deletions(-) create mode 100644 bot/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt rename {bot => plugin}/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt (85%) create mode 100644 plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 09199a9..0f9f86d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,26 @@ ## 10.0.0 +**OVERALL LOGIC OF PLAGUBOT INITIALIZATION AND WORK HAS BEEN CHANGED** + +First of all, since this update `PlaguBot` will use default `StartPlugin` logic and will be built on top of it. +All special methods of `Plugin` will be called from one of `PlaguBot` initialization phases: + +* `setupBotClient` will be called from `single` initialization of `telegramBot` (in `setupDI` phase) +* `setupBotPlugin` will be called from `startPlugin` method in time of `buildBehaviourWithFSM` initialization + * `Plugin`: - * `Plugin#setupBotPlugin` now will call start plugin by default + * Extension `Module.setupDI(Database,JsonObject)` has been dropped. Use `database` extension in `Module.setupDI(JsonObject)` +* `Bot`: + * `dev.inmo.plagubot.config.Config` lost its `plugins` section. Now you may retrieve plugins from `Koin` only + * `defaultJsonFormat` became `Warning` feature due to the fact of its fully default nature + * `PlaguBot` lost old `start` method and took two new: with `args` as `Array` and `initialConfig` as `JsonObject` + +**Migration:** + +* If you are running bot and doing it using `StartPlugin` launcher, add `dev.inmo.plagubot.PlaguBot` explicitly +* In plugins: replace your `setupDI` overrides with `Database` as argument by the same one, but `database` will be +available as extension in `single` or `factory` calls (as extension to `Scope` and `Koin`) ## 9.3.0 diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/App.kt b/bot/src/main/kotlin/dev/inmo/plagubot/App.kt index 6f28c13..bb246a0 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/App.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/App.kt @@ -1,25 +1,11 @@ package dev.inmo.plagubot -import dev.inmo.kslog.common.KSLog -import dev.inmo.kslog.common.i -import dev.inmo.plagubot.config.Config -import dev.inmo.plagubot.config.defaultJsonFormat import kotlinx.serialization.InternalSerializationApi -import kotlinx.serialization.json.jsonObject -import java.io.File /** * This method by default expects one argument in [args] field: path to config */ @InternalSerializationApi suspend fun main(args: Array) { - KSLog.default = KSLog("PlaguBot") - val (configPath) = args - val file = File(configPath) - KSLog.i("Start read config from ${file.absolutePath}") - val json = defaultJsonFormat.parseToJsonElement(file.readText()).jsonObject - val config = defaultJsonFormat.decodeFromJsonElement(Config.serializer(), json) - KSLog.i("Config has been read") - - PlaguBot(json, config).start().join() + PlaguBot.start(args).join() } diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt b/bot/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt new file mode 100644 index 0000000..47004e8 --- /dev/null +++ b/bot/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt @@ -0,0 +1,28 @@ +package dev.inmo.plagubot + +import org.koin.core.Koin +import org.koin.core.definition.Definition +import org.koin.core.module.Module +import org.koin.core.qualifier.Qualifier +import org.koin.core.qualifier.StringQualifier +import org.koin.core.scope.Scope + + +val Scope.plagubot: PlaguBot + get() = get() + +val Koin.plagubot: PlaguBot + get() = get() + +private val pluginsQualifier = StringQualifier("plagubotPlugins") +internal fun Module.singlePlugins( + createdAtStart: Boolean = false, + definition: Definition> +) = single(pluginsQualifier, createdAtStart, definition) +val Scope.plugins: List + get() = get(pluginsQualifier) + +val Koin.plugins: List + get() = get(pluginsQualifier) + + diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt b/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt index 1258264..2d99565 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt @@ -7,6 +7,7 @@ import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.StatesManager import dev.inmo.micro_utils.fsm.common.managers.* import dev.inmo.micro_utils.koin.getAllDistinct +import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin import dev.inmo.plagubot.config.* import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.ktor.KtorRequestsExecutorBuilder @@ -16,49 +17,30 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling import kotlinx.coroutines.* import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonObject -import org.jetbrains.exposed.sql.Database +import kotlinx.serialization.json.* import org.koin.core.Koin -import org.koin.core.KoinApplication -import org.koin.core.context.GlobalContext import org.koin.core.module.Module import org.koin.core.scope.Scope -import org.koin.dsl.module - -val Scope.plagubot: PlaguBot - get() = get() - -val Koin.plagubot: PlaguBot - get() = get() +import java.io.File @OptIn(Warning::class) @Serializable -data class PlaguBot( - private val json: JsonObject, - private val config: Config -) : Plugin { +object PlaguBot : Plugin { override fun KtorRequestsExecutorBuilder.setupBotClient(scope: Scope, params: JsonObject) { - config.botPlugins.forEach { + scope.plugins.filter { it !== this@PlaguBot }.forEach { with(it) { setupBotClient(scope, params) } } } - override fun KtorRequestsExecutorBuilder.setupBotClient() { - config.botPlugins.forEach { - with(it) { - setupBotClient() - } - } - } override fun Module.setupDI(config: JsonObject) { - single { this@PlaguBot.config } - single { this@PlaguBot.config.plugins } - single { this@PlaguBot.config.databaseConfig } - single { this@PlaguBot.config.databaseConfig.database } - single { defaultJsonFormat } + single { get().decodeFromJsonElement(Config.serializer(), config) } + single { config } + single { get().databaseConfig } + single { get().databaseConfig.database } single { this@PlaguBot } + singlePlugins { get().plugins.filterIsInstance() } single { val config = get() telegramBot( @@ -66,48 +48,63 @@ data class PlaguBot( testServer = config.testServer, apiUrl = config.botApiServer ) { - setupBotClient(this@single, json) + setupBotClient(this@single, get()) } } } - override fun Module.setupDI(database: Database, params: JsonObject) { - setupDI(params) - - includes( - config.botPlugins.mapNotNull { - runCatching { - module { - with(it) { - setupDI(database, params) - } - } - }.onFailure { e -> - logger.w(e) { "Unable to load DI part of $it" } - }.getOrNull() - } - ) - } - + /** + * Getting all [OnStartContextsConflictResolver], [OnUpdateContextsConflictResolver], [StatesManager] and [DefaultStatesManagerRepo] + * and pass them into [buildBehaviourWithFSM] on top of [TelegramBot] took from [koin]. In time of + * [buildBehaviourWithFSM] configuration will call [setupBotPlugin] and [deleteWebhook]. + * + * After all preparation, the result of [buildBehaviourWithFSM] will be passed to [startGettingOfUpdatesByLongPolling] + * as [CoroutineScope] and [UpdatesFilter]. + * + * The [Job] took from [startGettingOfUpdatesByLongPolling] will be used to prevent app stopping by calling [Job.join] + * on it + */ override suspend fun startPlugin(koin: Koin) { super.startPlugin(koin) + val scope = koin.get() - config.plugins.forEach { plugin -> - runCatchingSafely { - logger.i { "Starting of $plugin common logic" } - with(plugin) { - startPlugin(koin) - } - }.onFailure { e -> - logger.w(e) { "Unable to load common logic of $plugin" } - }.onSuccess { - logger.i { "Complete loading of $plugin common logic" } + lateinit var behaviourContext: BehaviourContext + val onStartContextsConflictResolver by lazy { koin.getAllDistinct() } + val onUpdateContextsConflictResolver by lazy { koin.getAllDistinct() } + val bot = koin.get() + bot.buildBehaviourWithFSM( + scope = scope, + defaultExceptionsHandler = { + logger.e("Something went wrong", it) + }, + statesManager = koin.getOrNull>() ?: DefaultStatesManager( + koin.getOrNull>() ?: InMemoryDefaultStatesManagerRepo(), + onStartContextsConflictResolver = { old, new -> onStartContextsConflictResolver.firstNotNullOfOrNull { it(old, new) } ?: false }, + onUpdateContextsConflictResolver = { old, new, currentNew -> onUpdateContextsConflictResolver.firstNotNullOfOrNull { it(old, new, currentNew) } ?: false } + ), + onStateHandlingErrorHandler = koin.getOrNull>() ?: { state, e -> + logger.eS(e) { "Unable to handle state $state" } + null } - } + ) { + logger.i("Start setup of bot part") + behaviourContext = this + setupBotPlugin(koin) + deleteWebhook() + }.start() + logger.i("Behaviour builder has been setup") + + bot.startGettingOfUpdatesByLongPolling(scope = behaviourContext, updatesFilter = behaviourContext).also { + logger.i("Long polling has been started") + }.join() } + /** + * Initializing [Plugin]s from [koin] took by [plugins] extension. [PlaguBot] itself will be filtered out from + * list of plugins to be inited + */ override suspend fun BehaviourContextWithFSM.setupBotPlugin(koin: Koin) { - config.botPlugins.forEach { plugin -> + koin.plugins.filter { it !== this@PlaguBot }.forEach { plugin -> runCatchingSafely { logger.i("Start loading of $plugin") with(plugin) { @@ -122,50 +119,47 @@ data class PlaguBot( } /** - * This method will create an [Job] which will be the main [Job] of ran instance + * Starting plugins system using [StartLauncherPlugin.start]. In time of parsing [initialJson] [PlaguBot] may + * add itself in its `plugins` section in case of its absence there. So, by launching this [start] it is guaranteed + * that [PlaguBot] will be in list of plugins to be loaded by [StartLauncherPlugin] */ - suspend fun start( - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) - ): Job { - logger.i("Start initialization") - val koinApp = KoinApplication.init() - koinApp.modules( - module { - setupDI(config.databaseConfig.database, json) - } - ) - logger.i("Modules loaded. Starting koin") - GlobalContext.startKoin(koinApp) - logger.i("Koin started. Starting plugins common logic") - startPlugin(koinApp.koin) - logger.i("Plugins common logic started. Starting setup of bot logic part") - lateinit var behaviourContext: BehaviourContext - val onStartContextsConflictResolver by lazy { koinApp.koin.getAllDistinct() } - val onUpdateContextsConflictResolver by lazy { koinApp.koin.getAllDistinct() } - val bot = koinApp.koin.get() - bot.buildBehaviourWithFSM( - scope = scope, - defaultExceptionsHandler = { - logger.e("Something went wrong", it) - }, - statesManager = koinApp.koin.getOrNull>() ?: DefaultStatesManager( - koinApp.koin.getOrNull>() ?: InMemoryDefaultStatesManagerRepo(), - onStartContextsConflictResolver = { old, new -> onStartContextsConflictResolver.firstNotNullOfOrNull { it(old, new) } ?: false }, - onUpdateContextsConflictResolver = { old, new, currentNew -> onUpdateContextsConflictResolver.firstNotNullOfOrNull { it(old, new, currentNew) } ?: false } - ), - onStateHandlingErrorHandler = koinApp.koin.getOrNull>() ?: { state, e -> - logger.eS(e) { "Unable to handle state $state" } - null - } - ) { - logger.i("Start setup of bot part") - behaviourContext = this - setupBotPlugin(koinApp.koin) - deleteWebhook() - }.start() - logger.i("Behaviour builder has been setup") - return bot.startGettingOfUpdatesByLongPolling(scope = behaviourContext, updatesFilter = behaviourContext).also { - logger.i("Long polling has been started") + suspend fun start(initialJson: JsonObject): Job { + val initialConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), initialJson) + + KSLog.i("Config has been read") + + // Adding of PlaguBot when it absent in config + val (resultJson, resultConfig) = if (PlaguBot in initialConfig.plugins) { + KSLog.i("Initial config contains PlaguBot, pass config as is to StartLauncherPlugin") + initialJson to initialConfig + } else { + KSLog.i("Start fixing of PlaguBot absence. If PlaguBot has been skipped by some reason, use dev.inmo.micro_utils.startup.launcher.main as startup point or StartLauncherPlugin directly") + val resultJson = JsonObject( + initialJson + Pair("plugins", JsonArray(initialJson["plugins"]!!.jsonArray + JsonPrimitive(PlaguBot::class.qualifiedName!!))) + ) + val resultConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), resultJson) + resultJson to resultConfig } + + KSLog.i("Config initialization done. Passing config to StartLauncherPlugin") + return StartLauncherPlugin.start( + resultConfig, + resultJson + ).koin.get().coroutineContext.job + } + + /** + * Accepts single argument in [args] which will be interpreted as [File] path with [StartLauncherPlugin] + * configuration content. After reading of that file as [JsonObject] will pass it in [start] with [JsonObject] as + * argument + */ + suspend fun start(args: Array): Job { + KSLog.default = KSLog("PlaguBot") + val (configPath) = args + val file = File(configPath) + KSLog.i("Start read config from ${file.absolutePath}") + + val initialJson = defaultJsonFormat.parseToJsonElement(file.readText()).jsonObject + return start(initialJson) } } 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 2344e20..8b82cd1 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt @@ -11,11 +11,8 @@ import kotlinx.serialization.Serializable @Serializable data class Config( val botToken: String, - val plugins: List, @SerialName("database") val databaseConfig: DatabaseConfig = DatabaseConfig(), val botApiServer: String = telegramBotAPIDefaultUrl, val testServer: Boolean = false -) { - val botPlugins = plugins.filterIsInstance() -} +) diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt index 4202346..10879e1 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt @@ -1,7 +1,9 @@ package dev.inmo.plagubot.config +import dev.inmo.micro_utils.common.Warning import kotlinx.serialization.json.Json +@Warning("This format will not be configured throw StartPlugin system. Use it will caution, it has no any configured things") val defaultJsonFormat = Json { ignoreUnknownKeys = true } diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt similarity index 85% rename from bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt rename to plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt index a2bf9d4..ecb53f1 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt @@ -2,22 +2,18 @@ package dev.inmo.plagubot import dev.inmo.kslog.common.* import dev.inmo.micro_utils.fsm.common.State -import dev.inmo.plagubot.HelloPlugin.setupBotPlugin import dev.inmo.tgbotapi.extensions.api.bot.getMe import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.sendMessage import dev.inmo.tgbotapi.extensions.behaviour_builder.* -import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitText import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitTextMessage import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand -import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onUnhandledCommand import dev.inmo.tgbotapi.types.IdChatIdentifier import kotlinx.coroutines.flow.first import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject -import org.jetbrains.exposed.sql.Database import org.koin.core.Koin import org.koin.core.module.Module @@ -29,9 +25,9 @@ object HelloPlugin : Plugin { val print: String ) - override fun Module.setupDI(database: Database, params: JsonObject) { + override fun Module.setupDI(config: JsonObject) { single { - get().decodeFromJsonElement(HelloPluginConfig.serializer(), params["helloPlugin"] ?: return@single null) + get().decodeFromJsonElement(HelloPluginConfig.serializer(), config["helloPlugin"] ?: return@single null) } } diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt new file mode 100644 index 0000000..d3ed49d --- /dev/null +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt @@ -0,0 +1,11 @@ +package dev.inmo.plagubot + +import org.jetbrains.exposed.sql.Database +import org.koin.core.Koin +import org.koin.core.scope.Scope + +val Scope.database: Database + get() = get() + +val Koin.database: Database + get() = get() diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt index 7a8ed97..769f184 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt @@ -7,9 +7,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonObject -import org.jetbrains.exposed.sql.Database import org.koin.core.Koin -import org.koin.core.module.Module import org.koin.core.scope.Scope /** @@ -32,27 +30,13 @@ interface Plugin : StartPlugin { */ fun KtorRequestsExecutorBuilder.setupBotClient(scope: Scope, params: JsonObject) = setupBotClient() - /** - * This method will be called when this plugin should configure di module based on the incoming params - */ - fun Module.setupDI( - database: Database, - params: JsonObject - ) { - setupDI(params) - } - /** * Override this method in cases when you want to declare common bot behaviour. In case you wish to use FSM, you * should override the method with receiver [BehaviourContextWithFSM] - * - * Besides, this method by default will call [startPlugin] */ suspend fun BehaviourContext.setupBotPlugin( koin: Koin - ) { - startPlugin(koin) - } + ) {} /** * Override this method in cases when you want to declare full behaviour of the plugin. It is recommended to declare * common logic of plugin in the [setupBotPlugin] with [BehaviourContext] receiver and use override this one From c2d6afccc2f004e18da64e98eef5037968054355 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 18:12:46 +0600 Subject: [PATCH 6/8] add koin extensions for plugins --- .../kotlin/dev/inmo/plagubot/HelloPlugin.kt | 6 +-- .../dev/inmo/plagubot/KoinExtensions.kt | 48 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt index ecb53f1..c6a388b 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt @@ -26,9 +26,7 @@ object HelloPlugin : Plugin { ) override fun Module.setupDI(config: JsonObject) { - single { - get().decodeFromJsonElement(HelloPluginConfig.serializer(), config["helloPlugin"] ?: return@single null) - } + registerConfig("helloPlugin") } private sealed interface InternalFSMState : State { @@ -43,7 +41,7 @@ object HelloPlugin : Plugin { } override suspend fun BehaviourContextWithFSM.setupBotPlugin(koin: Koin) { - val toPrint = koin.getOrNull() ?.print ?: "Hello :)" + val toPrint = koin.configOrNull() ?.print ?: "Hello :)" logger.d { toPrint } logger.dS { getMe().toString() } onCommand("hello_world") { diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt index d3ed49d..c230e65 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt @@ -1,11 +1,59 @@ package dev.inmo.plagubot +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.serializer import org.jetbrains.exposed.sql.Database import org.koin.core.Koin +import org.koin.core.module.Module import org.koin.core.scope.Scope +import kotlin.reflect.KClass val Scope.database: Database get() = get() val Koin.database: Database get() = get() + +/** + * Using [single] to register `T` with serializer [configSerializer] + * + * @param optional If passed, absence of [field] in config will lead to null config value in koin. If passed as false + * (default), absence of [field] in config will lead to an error + */ +inline fun Module.registerConfig(configSerializer: KSerializer, field: String?, optional: Boolean = false) { + single { + val fieldValue = get().let { + if (field == null) { + it + } else { + it[field] ?: if (optional) return@single null else error("Unable to take field $field from config") + } + } + get().decodeFromJsonElement(configSerializer, fieldValue) + } +} + +/** + * Using [single] to register config with getting of [serializer] from [kClass] + * + * @param optional If passed, absence of [field] in config will lead to null config value in koin. If passed as false + * (default), absence of [field] in config will lead to an error + */ +@OptIn(InternalSerializationApi::class) +inline fun Module.registerConfig(kClass: KClass, field: String?, optional: Boolean = false) = registerConfig(kClass.serializer(), field, optional) + +/** + * Using [single] to register config with getting of [serializer] from [kClass] + * + * @param optional If passed, absence of [field] in config will lead to null config value in koin. If passed as false + * (default), absence of [field] in config will lead to an error + */ +inline fun Module.registerConfig(field: String?, optional: Boolean = false) = registerConfig(T::class, field, optional) + +inline fun Scope.config() = get() +inline fun Koin.config() = get() +inline fun Scope.configOrNull() = getOrNull() +inline fun Koin.configOrNull() = getOrNull() From a7fe1e3d82ed7d19e467111ca22c0eea53caac05 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 18:33:22 +0600 Subject: [PATCH 7/8] improve registerConfig --- .../kotlin/dev/inmo/plagubot/HelloPlugin.kt | 2 +- .../dev/inmo/plagubot/KoinExtensions.kt | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt index c6a388b..7929336 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt @@ -26,7 +26,7 @@ object HelloPlugin : Plugin { ) override fun Module.setupDI(config: JsonObject) { - registerConfig("helloPlugin") + registerConfig("helloPlugin") { null } } private sealed interface InternalFSMState : State { diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt index c230e65..33a4cae 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt @@ -20,16 +20,18 @@ val Koin.database: Database /** * Using [single] to register `T` with serializer [configSerializer] * - * @param optional If passed, absence of [field] in config will lead to null config value in koin. If passed as false - * (default), absence of [field] in config will lead to an error + * @param default Will be used if [field] is absent as an alternative way of config allocation. If null passed, error + * will be thrown */ -inline fun Module.registerConfig(configSerializer: KSerializer, field: String?, optional: Boolean = false) { +inline fun Module.registerConfig(configSerializer: KSerializer, field: String?, noinline default: (Scope.(JsonObject) -> T?)? = null) { single { val fieldValue = get().let { if (field == null) { it } else { - it[field] ?: if (optional) return@single null else error("Unable to take field $field from config") + it[field] ?: default ?.let { _ -> + return@single default(it) + } ?: error("Unable to take field $field from config") } } get().decodeFromJsonElement(configSerializer, fieldValue) @@ -39,19 +41,19 @@ inline fun Module.registerConfig(configSerializer: KSerializer, f /** * Using [single] to register config with getting of [serializer] from [kClass] * - * @param optional If passed, absence of [field] in config will lead to null config value in koin. If passed as false - * (default), absence of [field] in config will lead to an error + * @param default Will be used if [field] is absent as an alternative way of config allocation. If null passed, error + * will be thrown */ @OptIn(InternalSerializationApi::class) -inline fun Module.registerConfig(kClass: KClass, field: String?, optional: Boolean = false) = registerConfig(kClass.serializer(), field, optional) +inline fun Module.registerConfig(kClass: KClass, field: String?, noinline default: (Scope.(JsonObject) -> T?)? = null) = registerConfig(kClass.serializer(), field, default) /** * Using [single] to register config with getting of [serializer] from [kClass] * - * @param optional If passed, absence of [field] in config will lead to null config value in koin. If passed as false - * (default), absence of [field] in config will lead to an error + * @param default Will be used if [field] is absent as an alternative way of config allocation. If null passed, error + * will be thrown */ -inline fun Module.registerConfig(field: String?, optional: Boolean = false) = registerConfig(T::class, field, optional) +inline fun Module.registerConfig(field: String?, noinline default: (Scope.(JsonObject) -> T?)? = null) = registerConfig(T::class, field, default) inline fun Scope.config() = get() inline fun Koin.config() = get() From 4342e04e3f7d01601800aafa3cdf3ea7c3664af5 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 22 Sep 2024 19:39:45 +0600 Subject: [PATCH 8/8] replace database extension into externded file --- .../dev/inmo/plagubot/KoinDatabaseExtensions.kt | 11 +++++++++++ .../main/kotlin/dev/inmo/plagubot/KoinExtensions.kt | 6 ------ 2 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 plugin/src/main/kotlin/dev/inmo/plagubot/KoinDatabaseExtensions.kt diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinDatabaseExtensions.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinDatabaseExtensions.kt new file mode 100644 index 0000000..d3ed49d --- /dev/null +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinDatabaseExtensions.kt @@ -0,0 +1,11 @@ +package dev.inmo.plagubot + +import org.jetbrains.exposed.sql.Database +import org.koin.core.Koin +import org.koin.core.scope.Scope + +val Scope.database: Database + get() = get() + +val Koin.database: Database + get() = get() diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt index 33a4cae..18585df 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/KoinExtensions.kt @@ -11,12 +11,6 @@ import org.koin.core.module.Module import org.koin.core.scope.Scope import kotlin.reflect.KClass -val Scope.database: Database - get() = get() - -val Koin.database: Database - get() = get() - /** * Using [single] to register `T` with serializer [configSerializer] *