Compare commits

..

38 Commits

Author SHA1 Message Date
b30aee0109 update dependencies 2025-04-30 13:26:31 +06:00
be45f94070 start 10.5.0 2025-03-27 08:31:24 +06:00
485d0d1fbf Merge pull request #113 from InsanusMokrassar/10.4.0
10.4.0
2025-02-16 09:22:03 +06:00
69e97223ce update dependencies 2025-02-16 09:21:30 +06:00
f067033678 start 10.4.0 2025-02-16 09:19:24 +06:00
37920521a0 Merge pull request #112 from InsanusMokrassar/10.3.1
10.3.1
2025-01-29 14:29:07 +06:00
d78f8adbea Update CHANGELOG.md 2025-01-29 14:28:55 +06:00
487ce413f1 update dependencies 2025-01-29 12:04:12 +06:00
556ded1e84 start 10.3.1 2025-01-29 12:02:34 +06:00
605fb956a4 Merge pull request #111 from InsanusMokrassar/10.3.0
10.3.0
2024-12-08 15:00:54 +06:00
b907f8d53b update dependencies 2024-12-07 10:10:17 +06:00
2a0cd5d6a8 start 10.3.0 2024-12-07 10:09:23 +06:00
00f3485959 Merge pull request #110 from InsanusMokrassar/10.2.1
10.2.1
2024-12-03 11:30:48 +06:00
856ae65fa8 add support of subcontext initial actions 2024-12-03 11:28:07 +06:00
d9488f8ccd start 10.2.1 2024-12-03 09:18:07 +06:00
2f06a65cc6 Merge pull request #109 from InsanusMokrassar/10.2.0
10.2.0
2024-12-01 12:00:01 +06:00
f3e6b55861 update dependencies 2024-12-01 11:59:02 +06:00
7bdb525f01 start 10.2.0 2024-12-01 11:57:22 +06:00
e6a2add7f8 Merge pull request #108 from InsanusMokrassar/10.1.1
10.1.1
2024-11-11 16:05:46 +06:00
3d7cb28c92 update tgbotapi 2024-11-11 16:02:10 +06:00
81a1ed6613 start 10.1.1 2024-11-11 15:59:14 +06:00
b43efb4255 Merge pull request #107 from InsanusMokrassar/10.1.0
10.1.0
2024-11-04 22:11:56 +06:00
8d3d9f8c6a update dependencies 2024-11-04 22:05:24 +06:00
6cb241642b start 10.1.0 2024-11-04 22:00:01 +06:00
f551f471d9 Merge pull request #106 from InsanusMokrassar/10.0.0
10.0.0
2024-09-22 19:40:02 +06:00
4342e04e3f replace database extension into externded file 2024-09-22 19:39:45 +06:00
a7fe1e3d82 improve registerConfig 2024-09-22 18:33:22 +06:00
c2d6afccc2 add koin extensions for plugins 2024-09-22 18:12:46 +06:00
d928db7e74 rework of plagubot system 2024-09-22 15:29:17 +06:00
893f3a95a1 migration onto 10.0.0 2024-09-22 14:46:20 +06:00
f9b7d444a6 add note about startPlugin in setupBotPlugin 2024-09-22 13:33:53 +06:00
47f5086ebd Plugin#setupBotPlugin will call startPlugin by default 2024-09-22 13:32:59 +06:00
bc29d4f83a start 9.3.1 2024-09-22 13:32:09 +06:00
26b34a470b Merge pull request #104 from InsanusMokrassar/9.3.0
9.3.0
2024-09-14 21:21:52 +06:00
3b507a5668 a lot of improvements 2024-09-14 21:06:06 +06:00
bb06ba1ca7 Add opportunity to use config in setup bot client 2024-09-14 20:17:38 +06:00
d772d275b3 start 9.3.0 2024-09-14 20:17:10 +06:00
e1e7c7a547 Merge pull request #103 from InsanusMokrassar/9.2.0
9.2.0
2024-09-06 00:11:20 +06:00
14 changed files with 354 additions and 159 deletions

View File

@@ -1,5 +1,108 @@
# Changelog # Changelog
## 10.5.0
* `Versions`:
* `kotlin`: `2.1.20`
* `serialization`: `1.8.1`
* `coroutines`: `1.10.2`
* `microutils`: `0.25.5`
* `tgbotapi`: `24.0.2`
* `exposed`: `0.61.0`
* `sqlite`: `3.49.1.0`
* `koin`: `4.0.4`
## 10.4.0
* `Versions`:
* `microutils`: `0.24.6`
* `tgbotapi`: `23.2.0`
* `exposed`: `0.59.0`
* `sqlite`: `3.49.0.0`
## 10.3.1
* `Versions`:
* `kotlin`: `2.1.10`
* `serialization`: `1.8.0`
* `coroutines`: `1.10.1`
* `microutils`: `0.24.5`
* `tgbotapi`: `23.1.2`
* `exposed`: `0.58.0`
* `sqlite`: `3.48.0.0`
* `koin`: `4.0.2`
## 10.3.0
* `Versions`:
* `microutils`: `0.23.2`
* `tgbotapi`: `22.0.0`
* `exposed`: `0.57.0`
## 10.2.1
* `Versions`:
* `tgbotapi`: `21.0.1`
* `Bot`:
* Now all `CombinedSubcontextInitialAction.SubItem`s will be taken from `Koin` to setup root `subcontextInitialAction`
## 10.2.0
* `Versions`:
* `kotlin`: `2.1.0`
* `microutils`: `0.23.1`
* `tgbotapi`: `21.0.0`
* `exposed`: `0.56.0`
* `sqlite`: `3.47.1.0`
## 10.1.1
* `Versions`:
* `tgbotapi`: `20.0.1`
## 10.1.0
* `Versions`:
* `kotlin`: `2.0.21`
* `serialization`: `1.7.3`
* `coroutines`: `1.9.0`
* `microutils`: `0.23.0`
* `tgbotapi`: `20.0.0`
* `exposed`: `0.55.0`
* `sqlite`: `3.47.0.0`
* `koin`: `4.0.0`
## 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`:
* 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<String>` 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
* `Bot`:
* Now bot is not built-in into `PlaguBot` and setted up as all other `Koin` dependencies
* Now it is possible to use `testServer` parameter for bots out of the box
* `Plugin`:
* New method `setupBotClient` with arguments to let plugin setup bot more freely
## 9.2.0 ## 9.2.0
* `Versions`: * `Versions`:

View File

@@ -19,6 +19,8 @@ That is a set of libraries for plagubots. Look at the
### Technical help ### Technical help
#### FSM
In this bot has been used variant with FSM. That means that you may use all the [Behaviour Builder with FSM](https://bookstack.inmo.dev/books/telegrambotapi/page/behaviour-builder-with-fsm) functionality. In case you wish to setup states repo, you should use the next code in the `setupDI` of your plugin: In this bot has been used variant with FSM. That means that you may use all the [Behaviour Builder with FSM](https://bookstack.inmo.dev/books/telegrambotapi/page/behaviour-builder-with-fsm) functionality. In case you wish to setup states repo, you should use the next code in the `setupDI` of your plugin:
```kotlin ```kotlin
@@ -41,3 +43,16 @@ single<StateHandlingErrorHandler<State>> {
} }
} }
``` ```
#### Subcontext initial actions
Bot will take all the `CombinedSubcontextInitialAction.SubItem`s from `Koin` to include it in root of
`behaviourBuilder`. To create your own subitem:
```kotlin
singleWithRandomQualifier<CombinedSubcontextInitialAction.SubItem> {
CombinedSubcontextInitialAction.SubItem {
// do some action or throw error to rerun on next round
}
}
```

View File

@@ -1,25 +1,11 @@
package dev.inmo.plagubot 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.InternalSerializationApi
import kotlinx.serialization.json.jsonObject
import java.io.File
/** /**
* This method by default expects one argument in [args] field: path to config * This method by default expects one argument in [args] field: path to config
*/ */
@InternalSerializationApi @InternalSerializationApi
suspend fun main(args: Array<String>) { suspend fun main(args: Array<String>) {
KSLog.default = KSLog("PlaguBot") PlaguBot.start(args).join()
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()
} }

View File

@@ -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<List<Plugin>>
) = single(pluginsQualifier, createdAtStart, definition)
val Scope.plugins: List<Plugin>
get() = get(pluginsQualifier)
val Koin.plugins: List<Plugin>
get() = get(pluginsQualifier)

View File

@@ -7,7 +7,10 @@ import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.fsm.common.StatesManager import dev.inmo.micro_utils.fsm.common.StatesManager
import dev.inmo.micro_utils.fsm.common.managers.* import dev.inmo.micro_utils.fsm.common.managers.*
import dev.inmo.micro_utils.koin.getAllDistinct import dev.inmo.micro_utils.koin.getAllDistinct
import dev.inmo.micro_utils.pagination.utils.getAll
import dev.inmo.micro_utils.startup.launcher.StartLauncherPlugin
import dev.inmo.plagubot.config.* import dev.inmo.plagubot.config.*
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.bot.ktor.KtorRequestsExecutorBuilder import dev.inmo.tgbotapi.bot.ktor.KtorRequestsExecutorBuilder
import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook
@@ -15,91 +18,95 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.json.*
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin 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.module.Module
import org.koin.core.scope.Scope import org.koin.core.scope.Scope
import org.koin.dsl.module import java.io.File
val Scope.plagubot: PlaguBot
get() = get()
val Koin.plagubot: PlaguBot
get() = get()
@OptIn(Warning::class) @OptIn(Warning::class)
@Serializable @Serializable
data class PlaguBot( object PlaguBot : Plugin {
private val json: JsonObject, override fun KtorRequestsExecutorBuilder.setupBotClient(scope: Scope, params: JsonObject) {
private val config: Config scope.plugins.filter { it !== this@PlaguBot }.forEach {
) : Plugin {
@Transient
private val bot = telegramBot(
token = config.botToken,
apiUrl = config.botApiServer
) {
setupBotClient()
}
override fun KtorRequestsExecutorBuilder.setupBotClient() {
config.botPlugins.forEach {
with(it) { with(it) {
setupBotClient() setupBotClient(scope, params)
} }
} }
} }
override fun Module.setupDI(config: JsonObject) { override fun Module.setupDI(config: JsonObject) {
single { this@PlaguBot.config } single { get<Json>().decodeFromJsonElement(Config.serializer(), config) }
single { this@PlaguBot.config.plugins } single { config }
single { this@PlaguBot.config.databaseConfig } single { get<Config>().databaseConfig }
single { this@PlaguBot.config.databaseConfig.database } single { get<Config>().databaseConfig.database }
single { defaultJsonFormat }
single { this@PlaguBot } single { this@PlaguBot }
single { bot } singlePlugins { get<dev.inmo.micro_utils.startup.launcher.Config>().plugins.filterIsInstance<Plugin>() }
} single {
val config = get<Config>()
override fun Module.setupDI(database: Database, params: JsonObject) { telegramBot(
setupDI(params) token = config.botToken,
testServer = config.testServer,
includes( apiUrl = config.botApiServer
config.botPlugins.mapNotNull { ) {
runCatching { setupBotClient(this@single, get<JsonObject>())
module {
with(it) {
setupDI(database, params)
}
}
}.onFailure { e ->
logger.w(e) { "Unable to load DI part of $it" }
}.getOrNull()
}
)
}
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
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" }
} }
} }
} }
/**
* 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<CoroutineScope>()
lateinit var behaviourContext: BehaviourContext
val onStartContextsConflictResolver by lazy { koin.getAllDistinct<OnStartContextsConflictResolver>() }
val onUpdateContextsConflictResolver by lazy { koin.getAllDistinct<OnUpdateContextsConflictResolver>() }
val bot = koin.get<TelegramBot>()
bot.buildBehaviourWithFSM(
scope = scope,
defaultExceptionsHandler = {
logger.e("Something went wrong", it)
},
statesManager = koin.getOrNull<StatesManager<State>>() ?: DefaultStatesManager(
koin.getOrNull<DefaultStatesManagerRepo<State>>() ?: InMemoryDefaultStatesManagerRepo<State>(),
onStartContextsConflictResolver = { old, new -> onStartContextsConflictResolver.firstNotNullOfOrNull { it(old, new) } ?: false },
onUpdateContextsConflictResolver = { old, new, currentNew -> onUpdateContextsConflictResolver.firstNotNullOfOrNull { it(old, new, currentNew) } ?: false }
),
onStateHandlingErrorHandler = koin.getOrNull<StateHandlingErrorHandler<State>>() ?: { state, e ->
logger.eS(e) { "Unable to handle state $state" }
null
},
subcontextInitialAction = CombinedSubcontextInitialAction(koin.getAllDistinct()).subcontextInitialAction
) {
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<State>.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) {
config.botPlugins.forEach { plugin -> koin.plugins.filter { it !== this@PlaguBot }.forEach { plugin ->
runCatchingSafely { runCatchingSafely {
logger.i("Start loading of $plugin") logger.i("Start loading of $plugin")
with(plugin) { with(plugin) {
@@ -114,49 +121,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( suspend fun start(initialJson: JsonObject): Job {
scope: CoroutineScope = CoroutineScope(Dispatchers.IO) val initialConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), initialJson)
): Job {
logger.i("Start initialization") KSLog.i("Config has been read")
val koinApp = KoinApplication.init()
koinApp.modules( // Adding of PlaguBot when it absent in config
module { val (resultJson, resultConfig) = if (PlaguBot in initialConfig.plugins) {
setupDI(config.databaseConfig.database, json) KSLog.i("Initial config contains PlaguBot, pass config as is to StartLauncherPlugin")
} initialJson to initialConfig
) } else {
logger.i("Modules loaded. Starting koin") 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")
GlobalContext.startKoin(koinApp) val resultJson = JsonObject(
logger.i("Koin started. Starting plugins common logic") initialJson + Pair("plugins", JsonArray(initialJson["plugins"]!!.jsonArray + JsonPrimitive(PlaguBot::class.qualifiedName!!)))
startPlugin(koinApp.koin) )
logger.i("Plugins common logic started. Starting setup of bot logic part") val resultConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), resultJson)
lateinit var behaviourContext: BehaviourContext resultJson to resultConfig
val onStartContextsConflictResolver by lazy { koinApp.koin.getAllDistinct<OnStartContextsConflictResolver>() }
val onUpdateContextsConflictResolver by lazy { koinApp.koin.getAllDistinct<OnUpdateContextsConflictResolver>() }
bot.buildBehaviourWithFSM(
scope = scope,
defaultExceptionsHandler = {
logger.e("Something went wrong", it)
},
statesManager = koinApp.koin.getOrNull<StatesManager<State>>() ?: DefaultStatesManager(
koinApp.koin.getOrNull<DefaultStatesManagerRepo<State>>() ?: InMemoryDefaultStatesManagerRepo<State>(),
onStartContextsConflictResolver = { old, new -> onStartContextsConflictResolver.firstNotNullOfOrNull { it(old, new) } ?: false },
onUpdateContextsConflictResolver = { old, new, currentNew -> onUpdateContextsConflictResolver.firstNotNullOfOrNull { it(old, new, currentNew) } ?: false }
),
onStateHandlingErrorHandler = koinApp.koin.getOrNull<StateHandlingErrorHandler<State>>() ?: { 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")
} }
KSLog.i("Config initialization done. Passing config to StartLauncherPlugin")
return StartLauncherPlugin.start(
resultConfig,
resultJson
).koin.get<CoroutineScope>().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<String>): 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)
} }
} }

View File

@@ -11,10 +11,8 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Config( data class Config(
val botToken: String, val botToken: String,
val plugins: List<StartPlugin>,
@SerialName("database") @SerialName("database")
val databaseConfig: DatabaseConfig = DatabaseConfig(), val databaseConfig: DatabaseConfig = DatabaseConfig(),
val botApiServer: String = telegramBotAPIDefaultUrl val botApiServer: String = telegramBotAPIDefaultUrl,
) { val testServer: Boolean = false
val botPlugins = plugins.filterIsInstance<Plugin>() )
}

View File

@@ -1,7 +1,9 @@
package dev.inmo.plagubot.config package dev.inmo.plagubot.config
import dev.inmo.micro_utils.common.Warning
import kotlinx.serialization.json.Json 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 { val defaultJsonFormat = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }

View File

@@ -5,4 +5,4 @@ kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
group=dev.inmo group=dev.inmo
version=9.2.0 version=10.5.0

View File

@@ -1,22 +1,22 @@
[versions] [versions]
kt = "2.0.20" kt = "2.1.20"
kt-serialization = "1.7.2" kt-serialization = "1.8.1"
kt-coroutines = "1.8.1" kt-coroutines = "1.10.2"
microutils = "0.22.2" microutils = "0.25.5"
tgbotapi = "18.1.0" tgbotapi = "24.0.2"
ksp = "2.0.20-1.0.24" ksp = "2.1.20-1.0.31"
jb-exposed = "0.54.0" jb-exposed = "0.61.0"
jb-dokka = "1.9.20" jb-dokka = "2.0.0"
sqlite = "3.46.1.0" sqlite = "3.49.1.0"
gh-release = "2.5.2" gh-release = "2.5.2"
koin = "3.5.6" koin = "4.0.4"
[libraries] [libraries]

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -2,22 +2,18 @@ package dev.inmo.plagubot
import dev.inmo.kslog.common.* import dev.inmo.kslog.common.*
import dev.inmo.micro_utils.fsm.common.State 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.bot.getMe
import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.api.send.sendMessage import dev.inmo.tgbotapi.extensions.api.send.sendMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.* 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.expectations.waitTextMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand 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 dev.inmo.tgbotapi.types.IdChatIdentifier
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.module.Module
@@ -29,10 +25,8 @@ object HelloPlugin : Plugin {
val print: String val print: String
) )
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(config: JsonObject) {
single { registerConfig<HelloPluginConfig>("helloPlugin") { null }
get<Json>().decodeFromJsonElement(HelloPluginConfig.serializer(), params["helloPlugin"] ?: return@single null)
}
} }
private sealed interface InternalFSMState : State { private sealed interface InternalFSMState : State {
@@ -47,7 +41,7 @@ object HelloPlugin : Plugin {
} }
override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) {
val toPrint = koin.getOrNull<HelloPluginConfig>() ?.print ?: "Hello :)" val toPrint = koin.configOrNull<HelloPluginConfig>() ?.print ?: "Hello :)"
logger.d { toPrint } logger.d { toPrint }
logger.dS { getMe().toString() } logger.dS { getMe().toString() }
onCommand("hello_world") { onCommand("hello_world") {

View File

@@ -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()

View File

@@ -0,0 +1,55 @@
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
/**
* Using [single] to register `T` with serializer [configSerializer]
*
* @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 <reified T> Module.registerConfig(configSerializer: KSerializer<T>, field: String?, noinline default: (Scope.(JsonObject) -> T?)? = null) {
single {
val fieldValue = get<JsonObject>().let {
if (field == null) {
it
} else {
it[field] ?: default ?.let { _ ->
return@single default(it)
} ?: error("Unable to take field $field from config")
}
}
get<Json>().decodeFromJsonElement(configSerializer, fieldValue)
}
}
/**
* Using [single] to register config with getting of [serializer] from [kClass]
*
* @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 <reified T : Any> Module.registerConfig(kClass: KClass<T>, 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 default Will be used if [field] is absent as an alternative way of config allocation. If null passed, error
* will be thrown
*/
inline fun <reified T : Any> Module.registerConfig(field: String?, noinline default: (Scope.(JsonObject) -> T?)? = null) = registerConfig(T::class, field, default)
inline fun <reified T : Any> Scope.config() = get<T>()
inline fun <reified T : Any> Koin.config() = get<T>()
inline fun <reified T : Any> Scope.configOrNull() = getOrNull<T>()
inline fun <reified T : Any> Koin.configOrNull() = getOrNull<T>()

View File

@@ -7,9 +7,8 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextWithFSM
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.scope.Scope
/** /**
* **ANY REALIZATION OF [Plugin] MUST HAVE CONSTRUCTOR WITH ABSENCE OF INCOMING PARAMETERS** * **ANY REALIZATION OF [Plugin] MUST HAVE CONSTRUCTOR WITH ABSENCE OF INCOMING PARAMETERS**
@@ -20,17 +19,16 @@ import org.koin.core.module.Module
*/ */
@Serializable(PluginSerializer::class) @Serializable(PluginSerializer::class)
interface Plugin : StartPlugin { interface Plugin : StartPlugin {
@Deprecated("Deprecated in favor to setupBotClient with arguments")
fun KtorRequestsExecutorBuilder.setupBotClient() {} fun KtorRequestsExecutorBuilder.setupBotClient() {}
/** /**
* This method will be called when this plugin should configure di module based on the incoming params * Will be called on stage of bot setup
*
* @param scope The scope of [org.koin.core.module.Module.single] of bot definition
* @param params Params (in fact, the whole bot config)
*/ */
fun Module.setupDI( fun KtorRequestsExecutorBuilder.setupBotClient(scope: Scope, params: JsonObject) = setupBotClient()
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 * Override this method in cases when you want to declare common bot behaviour. In case you wish to use FSM, you