Compare commits

...

60 Commits

Author SHA1 Message Date
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
d8403dd3b7 update dependencies 2024-09-06 00:09:41 +06:00
17ac66f7fe start 9.2.0 2024-09-06 00:05:26 +06:00
b23c9b755a
Merge pull request #102 from InsanusMokrassar/9.1.0
9.1.0
2024-08-15 21:32:02 +06:00
629ff7ee4e update dependencies 2024-08-15 21:30:04 +06:00
666ba9926e start 9.1.0 2024-08-15 21:29:17 +06:00
995bce3885
Merge pull request #101 from InsanusMokrassar/9.0.0
9.0.0
2024-08-12 16:35:14 +06:00
d597539b9e update dependencies 2024-08-12 16:24:34 +06:00
c80a7a1bda start 9.0.0 2024-08-12 16:22:42 +06:00
bd20c2d60f
Merge pull request #100 from InsanusMokrassar/8.5.1
8.5.1
2024-07-24 16:10:12 +06:00
cfd42af6bc update dependencies 2024-07-24 16:08:27 +06:00
5c39fd2491 start 8.5.1 2024-07-24 16:06:56 +06:00
745b9cad72
Merge pull request #99 from InsanusMokrassar/8.5.0
8.5.0
2024-07-11 23:03:40 +06:00
f39947edd6 update dependencies 2024-07-11 23:01:37 +06:00
cacc1ef828 start 8.5.0 2024-07-11 23:00:11 +06:00
136d106b4c
Merge pull request #98 from InsanusMokrassar/8.4.0
8.4.0
2024-06-27 23:20:42 +06:00
ddd3854d78 update dependencies 2024-06-27 22:31:45 +06:00
1c2bcb6907 start 8.4.0 2024-06-27 22:29:07 +06:00
8bd80c1c0c
Merge pull request #97 from InsanusMokrassar/8.3.0
8.3.0
2024-04-23 18:50:57 +06:00
2234ffd55d update gradle wrapper 2024-04-23 18:44:51 +06:00
1a9d8b0d5e update dependencies 2024-04-23 18:44:26 +06:00
e00f40d06a start 8.3.0 2024-04-23 18:37:33 +06:00
9883d2b3b1
Merge pull request #96 from InsanusMokrassar/8.2.0
8.2.0
2024-02-18 21:34:04 +06:00
bdfd3b816c update dependencies 2024-02-18 21:31:45 +06:00
dd37ae0f2f start 8.2.0 2024-02-18 21:27:22 +06:00
8efe977e76
Merge pull request #95 from InsanusMokrassar/8.1.1
8.1.1
2024-02-10 23:45:26 +06:00
8290a68654 update dependencies 2024-02-10 23:44:52 +06:00
493a18a0f7 start 8.1.1 2024-02-10 23:41:36 +06:00
36608937af
Merge pull request #94 from InsanusMokrassar/8.1.0
8.1.0
2024-01-14 18:42:36 +06:00
1b1b069d93 integrate startup launcher 2024-01-14 18:33:41 +06:00
37b5b8e867 start 8.1.0 2024-01-14 18:21:26 +06:00
468310c819
Merge pull request #93 from InsanusMokrassar/8.0.0
8.0.0
2024-01-12 20:28:24 +06:00
0d27a4f6f7 update dependencies 2024-01-12 20:12:58 +06:00
f9d131866f start 8.0.0 2024-01-12 20:11:29 +06:00
451b2bbc39
Merge pull request #92 from InsanusMokrassar/7.4.2
7.4.2
2023-12-24 23:25:42 +06:00
d51e2d8e7c fix of #90 and #91 2023-12-24 23:24:04 +06:00
6c45a8c848 update dependencies 2023-12-24 23:17:29 +06:00
8a7e05580d start 7.4.2 2023-12-24 23:14:24 +06:00
8b78f2901f
Merge pull request #89 from InsanusMokrassar/7.4.1
7.4.1
2023-12-10 14:47:39 +06:00
cad8743ced update dependencies and fill changelog 2023-12-10 14:46:13 +06:00
8f827e9f79 start 7.4.1 2023-12-10 14:34:52 +06:00
28039df992
Merge pull request #88 from InsanusMokrassar/7.4.0
7.4.0
2023-11-26 20:56:04 +06:00
18019c37ea remove expicit KSLog dependency 2023-11-26 19:29:29 +06:00
8a84f65a12
Update CHANGELOG.md 2023-11-26 19:02:00 +06:00
49f30ab939
Update libs.versions.toml 2023-11-26 18:52:26 +06:00
8a15deceb8
Update gradle.properties 2023-11-26 18:49:43 +06:00
ea3273c4cf
Merge pull request #87 from InsanusMokrassar/7.3.0
7.3.0
2023-11-06 15:17:37 +06:00
21 changed files with 418 additions and 167 deletions

View File

@ -1,7 +1,144 @@
# Changelog # Changelog
## 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
* `Versions`:
* `kotlin`: `2.0.20`
* `serialization`: `1.7.2`
* `microutils`: `0.22.2`
* `tgbotapi`: `18.1.0`
* `exposed`: `0.54.0`
* `sqlite`: `3.46.1.0`
## 9.1.0
* `Versions`:
* `tgbotapi`: `17.0.0`
## 9.0.0
* `Versions`:
* `Kotlin`: `2.0.10`
* `Serialization`: `1.7.1`
* `MicroUtils`: `0.22.0`
* `tgbotapi`: `16.0.0`
* `Exposed`: `0.53.0`
## 8.5.1
* `Versions`:
* `MicroUtils`: `0.21.4`
* `tgbotapi`: `15.2.0`
## 8.5.0
* `Versions`:
* `MicroUtils`: `0.21.2`
* `tgbotapi`: `15.1.0`
## 8.4.0
* `Versions`:
* `Coroutines`: `1.8.1`
* `MicroUtils`: `0.21.1`
* `tgbotapi`: `15.0.0`
* `Exposed`: `0.51.1`
## 8.3.0
* `Versions`:
* `Serialization`: `1.6.3`
* `MicroUtils`: `0.20.45`
* `tgbotapi`: `12.0.1`
* `Exposed`: `0.49.0`
* `SQLite`: `3.45.3.0`
* `Koin`: `3.5.6`
## 8.2.0
* `Versions`:
* `Coroutines`: `1.8.0`
* `tgbotapi`: `10.1.0`
* `MicroUtils`: `0.20.35`
## 8.1.1
* `Versions`:
* `tgbotapi`: `10.0.1`
* `MicroUtils`: `0.20.32`
* `Exposed`: `0.47.0`
## 8.1.0
* Integrate `dev.inmo:micro_utils.startup` into project
## 8.0.0
* `Versions`:
* `tgbotapi`: `10.0.0`
* `MicroUtils`: `0.20.26`
* `Exposed`: `0.46.0`
## 7.4.2
* `Versions`:
* `Kotlin`: `1.9.22`
* `tgbotapi`: `9.4.3`
* `MicroUtils`: `0.20.23`
* `Koin`: `3.5.7`
## 7.4.1
* `Versions`:
* `Serialization`: `1.6.2`
* `tgbotapi`: `9.4.2`
* `Exposed`: `0.45.0`
* `SQLite`: `3.44.1.0`
* `MicroUtils`: `0.20.19`
* `uuid`: `0.8.2`
* `ktor`: `2.3.7`
## 7.3.0 ## 7.3.0
* `Versions`:
* `Kotlin`: `1.9.21`
* `Serialization`: `1.6.1`
* `tgbotapi`: `9.4.1`
* `ktor`: `2.3.6`
* `KSLog`: Removed explicit dependency, now it is declared in tgbotapi
* `MicroUtils`: `0.20.15`
## 7.2.3 ## 7.2.3
* `Versions`: * `Versions`:

View File

@ -20,7 +20,7 @@ dependencies {
api libs.tgbotapi.behaviourBuilder.fsm api libs.tgbotapi.behaviourBuilder.fsm
api libs.microutils.repos.exposed api libs.microutils.repos.exposed
api libs.microutils.koin api libs.microutils.koin
api libs.kslog api libs.microutils.startup.launcher
api libs.sqlite api libs.sqlite

View File

@ -49,18 +49,14 @@ publishing {
} }
} }
repositories { repositories {
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven { maven {
name = "Gitea" name = "InmoNexus"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) { credentials {
name = "Authorization" username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }

View File

@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/PlaguBot/LICENSE"}],"mavenConfig":{"name":"PlaguBot Bot","description":"Base PlaguBot project","url":"https://github.com/InsanusMokrassar/PlaguBot","vcsUrl":"ssh://git@github.com/InsanusMokrassar/PlaguBot.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/PlaguBot/LICENSE"}],"mavenConfig":{"name":"PlaguBot Bot","description":"Base PlaguBot project","url":"https://github.com/InsanusMokrassar/PlaguBot","vcsUrl":"ssh://git@github.com/InsanusMokrassar/PlaguBot.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

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,64 +7,104 @@ 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.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.telegramBot import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook
import dev.inmo.tgbotapi.extensions.behaviour_builder.* 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 { with(it) {
@Transient setupBotClient(scope, params)
private val bot = telegramBot(config.botToken)
override fun Module.setupDI(database: Database, params: JsonObject) {
single { config }
single { config.plugins }
single { config.databaseConfig }
single { config.databaseConfig.database }
single { defaultJsonFormat }
single { this@PlaguBot }
single { bot }
includes(
config.plugins.mapNotNull {
runCatching {
module {
with(it) {
setupDI(database, params)
}
}
}.onFailure { e ->
logger.w("Unable to load DI part of $it", e)
}.getOrNull()
} }
) }
} }
override fun Module.setupDI(config: JsonObject) {
single { get<Json>().decodeFromJsonElement(Config.serializer(), config) }
single { config }
single { get<Config>().databaseConfig }
single { get<Config>().databaseConfig.database }
single { this@PlaguBot }
singlePlugins { get<dev.inmo.micro_utils.startup.launcher.Config>().plugins.filterIsInstance<Plugin>() }
single {
val config = get<Config>()
telegramBot(
token = config.botToken,
testServer = config.testServer,
apiUrl = config.botApiServer
) {
setupBotClient(this@single, get<JsonObject>())
}
}
}
/**
* 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
}
) {
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.plugins.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) {
@ -79,47 +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( 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") 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") initialJson + Pair("plugins", JsonArray(initialJson["plugins"]!!.jsonArray + JsonPrimitive(PlaguBot::class.qualifiedName!!)))
lateinit var behaviourContext: BehaviourContext )
val onStartContextsConflictResolver by lazy { koinApp.koin.getAllDistinct<OnStartContextsConflictResolver>() } val resultConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), resultJson)
val onUpdateContextsConflictResolver by lazy { koinApp.koin.getAllDistinct<OnUpdateContextsConflictResolver>() } resultJson to resultConfig
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

@ -1,15 +1,18 @@
package dev.inmo.plagubot.config package dev.inmo.plagubot.config
import dev.inmo.micro_utils.common.Warning import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@Warning("This API is internal and can be changed without notifications of mentions of changes") @Warning("This API is internal and can be changed without notifications or mentions of changes")
@Serializable @Serializable
data class Config( data class Config(
val botToken: String, val botToken: String,
val plugins: List<Plugin>,
@SerialName("database") @SerialName("database")
val databaseConfig: DatabaseConfig = DatabaseConfig(), val databaseConfig: DatabaseConfig = DatabaseConfig(),
val botApiServer: String = telegramBotAPIDefaultUrl,
val testServer: Boolean = false
) )

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

@ -17,7 +17,7 @@ allprojects {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
maven { url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven" } maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
} }
} }

View File

@ -20,13 +20,13 @@ if (new File(projectDir, "secret.gradle").exists()) {
releaseAssets.from('bot/build/distributions') releaseAssets.from('bot/build/distributions')
owner "InsanusMokrassar" owner = "InsanusMokrassar"
repo "PlaguBot" repo = "PlaguBot"
tagName "v${project.version}" tagName = "v${project.version}"
releaseName "${project.version}" releaseName = "${project.version}"
targetCommitish "${project.version}" targetCommitish = "${project.version}"
body getCurrentVersionChangelog() body = getCurrentVersionChangelog()
} }
} }

View File

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

View File

@ -1,28 +1,22 @@
[versions] [versions]
kt = "1.9.20" kt = "2.0.20"
kt-serialization = "1.6.0" kt-serialization = "1.7.2"
kt-coroutines = "1.7.3" kt-coroutines = "1.8.1"
microutils = "0.20.12" microutils = "0.22.2"
tgbotapi = "9.3.0" tgbotapi = "18.1.0"
kslog = "1.2.3"
ksp = "1.9.20-1.0.14" ksp = "2.0.20-1.0.24"
jb-exposed = "0.44.1" jb-exposed = "0.54.0"
jb-dokka = "1.9.10" jb-dokka = "1.9.20"
sqlite = "3.43.0.0" sqlite = "3.46.1.0"
klock = "4.0.10" gh-release = "2.5.2"
uuid = "0.8.1"
ktor = "2.3.5" koin = "3.5.6"
gh-release = "2.4.1"
koin = "3.5.0"
[libraries] [libraries]
@ -36,7 +30,8 @@ tgbotapi-behaviourBuilder-fsm = { module = "dev.inmo:tgbotapi.behaviour_builder.
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" } microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
microutils-koin = { module = "dev.inmo:micro_utils.koin", version.ref = "microutils" } microutils-koin = { module = "dev.inmo:micro_utils.koin", version.ref = "microutils" }
microutils-koin-generator = { module = "dev.inmo:micro_utils.koin.generator", version.ref = "microutils" } microutils-koin-generator = { module = "dev.inmo:micro_utils.koin.generator", version.ref = "microutils" }
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } microutils-startup-launcher = { module = "dev.inmo:micro_utils.startup.launcher", version.ref = "microutils" }
microutils-startup-plugin = { module = "dev.inmo:micro_utils.startup.plugin", version.ref = "microutils" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }

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.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -14,6 +14,7 @@ dependencies {
api libs.tgbotapi api libs.tgbotapi
api libs.microutils.repos.exposed api libs.microutils.repos.exposed
api libs.microutils.startup.plugin
api libs.koin api libs.koin
} }

View File

@ -49,18 +49,14 @@ publishing {
} }
} }
repositories { repositories {
if (project.hasProperty('GITEA_TOKEN') || System.getenv('GITEA_TOKEN') != null) { if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven { maven {
name = "Gitea" name = "InmoNexus"
url = uri("https://git.inmo.dev/api/packages/InsanusMokrassar/maven") url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials(HttpHeaderCredentials) { credentials {
name = "Authorization" username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
value = project.hasProperty('GITEA_TOKEN') ? project.property('GITEA_TOKEN') : System.getenv('GITEA_TOKEN') password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
authentication {
header(HttpHeaderAuthentication)
} }
} }

View File

@ -1 +1 @@
{"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/PlaguBot/LICENSE"}],"mavenConfig":{"name":"PlaguBot Plugin","description":"Base dependency for whole PlaguBot project","url":"https://github.com/InsanusMokrassar/PlaguBot","vcsUrl":"ssh://git@github.com/InsanusMokrassar/PlaguBot.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"Gitea","url":"https://git.inmo.dev/api/packages/InsanusMokrassar/maven","credsType":{"type":"dev.inmo.kmppscriptbuilder.core.models.MavenPublishingRepository.CredentialsType.HttpHeaderCredentials","headerName":"Authorization","headerValueProperty":"GITEA_TOKEN"}},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"} {"licenses":[{"id":"Apache-2.0","title":"Apache Software License 2.0","url":"https://github.com/InsanusMokrassar/PlaguBot/LICENSE"}],"mavenConfig":{"name":"PlaguBot Plugin","description":"Base dependency for whole PlaguBot project","url":"https://github.com/InsanusMokrassar/PlaguBot","vcsUrl":"ssh://git@github.com/InsanusMokrassar/PlaguBot.git","developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

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,12 +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 {
@ -43,8 +35,13 @@ object HelloPlugin : Plugin {
data class SaidHelloOnce(override val context: IdChatIdentifier) : InternalFSMState data class SaidHelloOnce(override val context: IdChatIdentifier) : InternalFSMState
} }
override suspend fun startPlugin(koin: Koin) {
super.startPlugin(koin)
logger.i { "This logic called BEFORE the bot will be started and setup" }
}
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

@ -1,13 +1,14 @@
package dev.inmo.plagubot package dev.inmo.plagubot
import dev.inmo.micro_utils.fsm.common.State import dev.inmo.micro_utils.fsm.common.State
import dev.inmo.micro_utils.startup.plugin.StartPlugin
import dev.inmo.tgbotapi.bot.ktor.KtorRequestsExecutorBuilder
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext 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**
@ -17,14 +18,17 @@ import org.koin.core.module.Module
* too. * too.
*/ */
@Serializable(PluginSerializer::class) @Serializable(PluginSerializer::class)
interface Plugin { interface Plugin : StartPlugin {
@Deprecated("Deprecated in favor to setupBotClient with arguments")
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
) {}
/** /**
* 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