Compare commits

..

8 Commits

Author SHA1 Message Date
d6770e1c02 remove redundant config test and update build workflow 2022-05-16 14:12:56 -04:00
8aaaa86dd4 add logging inside of plagubot 2022-05-16 14:08:45 -04:00
6b2b0b07f5 fill changelog 2022-05-16 14:03:27 -04:00
bb1856de90 complete 1.0.0 2022-05-16 13:58:16 -04:00
41885b8f7b temporal progress 2022-05-16 20:07:57 +06:00
a0f9e31b04 start migration 2022-05-13 13:04:49 +06:00
a7937f297d start 1.0.0 2022-05-13 12:34:39 +06:00
e0ebbfa7ec Merge pull request #25 from InsanusMokrassar/0.5.1
0.5.1
2022-02-02 13:39:32 +06:00
34 changed files with 174 additions and 1149 deletions

View File

View File

@@ -8,9 +8,9 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 17 - name: Set up JDK 11
uses: actions/setup-java@v1 uses: actions/setup-java@v1
with: with:
java-version: 17 java-version: 11
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build

2
.gitignore vendored
View File

@@ -10,7 +10,5 @@ build/
out/ out/
local.properties local.properties
local.*
local.*/
config.json config.json
secret.gradle secret.gradle

View File

@@ -1,580 +1,5 @@
# Changelog # Changelog
## 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
* `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
* `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
* `Versions`:
* `tgbotapi`: `9.2.2`
* `exposed`: `0.44.0`
* `koin`: `3.5.0`
* `ktor`: `2.3.5`
## 7.2.2
* `Bot`:
* Now you may customize both `onStart` and `onUpdate` conflicts resolvers
## 7.2.1
* `Versions`:
* `tgbotapi`: `9.2.1`
* `ktor`: `2.3.4`
## 7.2.0
* `Version`:
* `tgbotapi`: `9.2.0`
* `kslog`: `1.1.2`
* `sqlite`: `3.43.0.0`
## 7.1.0
* `Version`:
* `microutils`: `0.19.9`
* `tgbotapi`: `9.1.0`
* `ktor`: `2.3.3`
* `coroutines`: `1.7.3`
* `koin`: `3.4.3`
## 7.0.0
* `Version`:
* `microutils`: `0.19.7`
* `tgbotapi`: `9.0.0`
* `ktor`: `2.3.2`
* `coroutines`: `1.7.2`
## 6.1.0
* `Version`:
* `kotlin`: `1.8.22`
* `microutils`: `0.19.4`
* `tgbotapi`: `8.1.0`
* `koin`: `3.4.2`
* `sqlite`: `3.42.0.0`
## 6.0.1
* `Version`:
* `microutils`: `0.19.2`
* `tgbotapi`: `8.0.1`
* `uuid`: `0.7.1`
* `ktor`: `2.3.1`
* `koin`: `3.4.1`
## 6.0.0
* `Versions`:
* `microutils`: `0.19.1`
* `tgbotapi`: `8.0.0`
* `klock`: `4.0.3`
## 5.1.3
* `Versions`:
* `serialization`: `1.5.1`
* `microutils`: `0.18.4`
* `tgbotapi`: `7.1.3`
## 5.1.2
* `Versions`:
* `microutils`: `0.18.1`
* `tgbotapi`: `7.1.2`
## 5.1.1
* `Versions`:
* `kotlin`: `1.8.21`
* `microutils`: `0.18.0`
* `tgbotapi`: `7.1.1`
## 5.1.0
* `Versions`:
* `tgbotapi`: `7.1.0`
* `sqlite`: `3.41.2.1`
## 5.0.2
* `Versions`:
* `kotlin`: `1.8.20`
* `microutils`: `0.17.8`
* `tgbotapi`: `7.0.2`
* `kslog`: `1.1.1`
* `ktor`: `2.3.0`
* `koin`: `3.4.0`
## 5.0.1
* `Versions`:
* `tgbotapi`: `7.0.1`
## 5.0.0
* `Versions`:
* `tgbotapi`: `7.0.0`
* `microutils`: `0.17.5`
## 4.1.0
* `Versions`:
* `tgbotapi`: `6.1.0`
* `microutils`: `0.17.3`
## 4.0.3
* `Versions`:
* `tgbotapi`: `6.0.3`
* `microutils`: `0.17.2`
## 4.0.2
* `Versions`:
* `tgbotapi`: `6.0.2`
## 4.0.1
* `Versions`:
* `tgbotapi`: `6.0.1`
* `microutils`: `17.0.1`
* `ktor`: `2.2.4`
## 4.0.0
* `Versions`:
* `kotlin`: `1.8.10`
* `tgbotapi`: `6.0.0`
* `microutils`: `0.17.0`
## 3.5.0
* `Versions`:
* `tgbotapi`: `5.2.0`
* `microutils`: `0.16.10`
* `koin`: `3.3.2`
## 3.4.1
* `setupBotPlugin` now works synchronously
## 3.4.0
* `Versions`:
* `tgbotapi`: `5.1.0`
* `microutils`: `0.16.8`
* `ktor`: `2.2.3`
## 3.3.1
* `Versions`:
* `tgbotapi`: `5.0.1`
* `microutils`: `0.16.6`
* `ktor`: `2.2.2`
## 3.3.0
* `Versions`:
* `tgbotapi`: `5.0.0`
## 3.2.3
* `Versions`:
* `tgbotapi`: `4.2.3`
* `microutils`: `0.16.4`
## 3.2.2
* `Versions`:
* `tgbotapi`: `4.2.2`
* `microutils`: `0.16.2`
## 3.2.1
* `Versions`:
* `tgbotapi`: `4.2.1`
* `microutils`: `0.16.0`
* `ktor`: `2.2.1`
## 3.2.0
* `Versions`:
* `kotlin`: `1.7.22`
* `tgbotapi`: `4.2.0`
* `microutils`: `0.15.0`
* `kslog`: `0.5.4`
* `sqlite`: `3.40.0.0`
## 3.1.4
* `Versions`:
* `tgbotapi`: `4.1.3`
* `microutils`: `0.14.4`
## 3.1.3
* `Versions`:
* `tgbotapi`: `4.1.2`
## 3.1.2
* `Versions`:
* `microutils`: `0.14.2`
* `exposed`: `0.41.1`
## 3.1.1
* `Versions`:
* `tgbotapi`: `4.1.1`
## 3.1.0
* `Versions`:
* `kotlin`: `1.7.21`
* `microutils`: `0.14.1`
* `tgbotapi`: `4.1.0`
* `klock`: `3.4.0`
* `uuid`: `0.6.0`
## 3.0.0
* `Versions`:
* `microutils`: `0.14.0`
* `tgbotapi`: `4.0.0`
* `kslog`: `0.5.3`
* `exposed`: `0.40.1`
* `klock`: `3.3.1`
## 2.4.1
* `Versions`:
* `microutils`: `0.13.2`
* `tgbotapi`: `3.3.1`
* `klock`: `3.3.0`
* `ktor`: `2.1.3`
* `koin`: `3.2.2`
## 2.4.0
* `Versions`:
* `kotlin`: `1.7.20`
* `serialization`: `1.4.1`
* `tgbotapi`: `3.3.0`
* `microutils`: `0.13.1`
* `klock`: `3.2.0`
* `ktor`: `2.1.2`
## 2.3.4
* `Versions`:
* `tgbotapi`: `3.2.7`
* `microutils`: `0.12.16`
## 2.3.3
* `Versions`:
* `tgbotapi`: `3.2.6`
* `sqlite`: `3.39.3.0`
## 2.3.2
* `Versions`:
* `tgbotapi`: `3.2.3`
* `microutils`: `0.12.13`
* `kslog`: `0.5.2`
## 2.3.1
* `Versions`:
* `klock`: `3.1.0`
* `tgbotapi`: `3.2.1`
* `microutils`: `0.12.11`
* `ktor`: `2.1.1`
## 2.3.0
* `Bot`:
* Add option `reconnectOptions` in database config
## 2.2.0
* `Versions`:
* `serialization`: `1.4.0`
* `tgbotapi`: `3.2.0`
* `microutils`: `0.12.4`
* `kslog`: `0.5.1`
## 2.1.1
* `Bot`:
* Now it is possible to get bot from `koin`
## 2.1.0
* `Versions`:
* `tgbotapi`: `3.1.1`
* `ktor`: `2.1.0`
* `microutils`: `0.12.1`
* `Plugins`:
* New fum of `Plugin` with `BehaviourContextWithFSM` receiver
* `Bot`:
* Now bot uses `buildBehaviourWithFSM` to be able to setup bot with FSM
## 2.0.0
* `Versions`:
* `kotlin`: `1.7.10`
* `serialization`: `1.4.0-RC`
* `tgbotapi`: `3.0.2`
* `kslog`: `0.5.0`
* `uuid`: `0.5.0`
* `exposed`: `0.39.2`
* `microutils`: `0.12.0`
## 1.4.1
* `Versions`:
* `tgbotapi`: `2.2.2`
* `kslog`: `0.4.2`
## 1.4.0
* `Versions`:
* `kslog`: `0.4.1`
## 1.3.1
* `Versions`:
* `tgbotapi`: `2.2.1`
* `microutils`: `0.11.13`
## 1.3.0
* `Versions`
* `tgbotapi`: `2.2.0`
## 1.2.3
* `Versions`
* `tgbotapi`: `2.1.3`
## 1.2.2
* `Versions`
* `tgbotapi`: `2.1.2`
* `microutils`: `0.11.12`
* `coroutines`: `1.6.3`
* `ktor`: `2.0.3`
## 1.2.1
* `Versions`
* `tgbotapi`: `2.1.1`
* `microutils`: `0.11.6`
* `kslog`: `0.3.2`
## 1.2.0
* `Versions`
* `tgbotapi`: `2.1.0`
## 1.1.2
* `Versions`
* `tgbotapi`: `2.0.3`
* `microutils`: `0.11.3`
* `kslog`: `0.3.1`
* `Plugin`:
* Now it is possible to use `object`s of plugins instead of classes
## 1.1.1
* `Versions`
* `coroutines`: `1.6.2`
* `tgbotapi`: `2.0.2`
* `microutils`: `0.11.0`
* `ktor`: `2.0.2`
* `uuid`: `0.4.1`
## 1.1.0
* `Versions`
* `tgbotapi`: `2.0.0`
* `microutils`: `0.10.5`
* `Plugin`:
* All plugins will be loaded in parallel
## 1.0.0 ## 1.0.0
* `Versions` * `Versions`

View File

@@ -16,43 +16,3 @@ You can create your bot using
That is a set of libraries for plagubots. Look at the That is a set of libraries for plagubots. Look at the
[PlaguBot Plugin template](https://insanusmokrassar.github.io/PlaguBotPluginTemplate/) to find how to create your bot. [PlaguBot Plugin template](https://insanusmokrassar.github.io/PlaguBotPluginTemplate/) to find how to create your bot.
### 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:
```kotlin
single<StatesManager<State>> {
// setup your manager and return here
// Default is:
DefaultStatesManager(
InMemoryDefaultStatesManagerRepo()
)
}
```
Besides, you may setup handling errors lambda in the same function:
```kotlin
single<StateHandlingErrorHandler<State>> {
{ state, e ->
logger.eS(e) { "Unable to handle state $state" } // logging by default
null // you should return new state or null, default callback will return null
}
}
```
#### 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

@@ -2,7 +2,6 @@ plugins {
id 'org.jetbrains.kotlin.jvm' id 'org.jetbrains.kotlin.jvm'
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id 'application' id 'application'
id "com.google.devtools.ksp"
} }
project.group="$group" project.group="$group"
@@ -17,10 +16,7 @@ dependencies {
api libs.jb.exposed.jdbc api libs.jb.exposed.jdbc
api libs.tgbotapi api libs.tgbotapi
api libs.tgbotapi.behaviourBuilder.fsm
api libs.microutils.repos.exposed api libs.microutils.repos.exposed
api libs.microutils.koin
api libs.microutils.startup.launcher
api libs.sqlite api libs.sqlite
@@ -34,10 +30,7 @@ application {
} }
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_1_8
}
ksp { // this generator do not require any arguments and we should left `ksp` empty
} }

View File

@@ -2,11 +2,11 @@ apply plugin: 'maven-publish'
task javadocJar(type: Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
archiveClassifier = 'javadoc' classifier = 'javadoc'
} }
task sourcesJar(type: Jar) { task sourcesJar(type: Jar) {
from sourceSets.main.allSource from sourceSets.main.allSource
archiveClassifier = 'sources' classifier = 'sources'
} }
publishing { publishing {
@@ -49,28 +49,14 @@ publishing {
} }
} }
repositories { repositories {
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven { maven {
name = "sonatype" name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials { credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
} }
} }
} }
} }
@@ -92,27 +78,4 @@ if (project.hasProperty("signing.gnupg.keyName")) {
dependsOn(it) dependsOn(it)
} }
} }
// Workaround to make android sign operations depend on signing tasks
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks)
}
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

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":"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"} {"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":"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,11 +1,20 @@
package dev.inmo.plagubot package dev.inmo.plagubot
import dev.inmo.plagubot.config.*
import kotlinx.coroutines.*
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>) {
PlaguBot.start(args).join() val (configPath) = args
val file = File(configPath)
val json = defaultJsonFormat.parseToJsonElement(file.readText()).jsonObject
val config = defaultJsonFormat.decodeFromJsonElement(Config.serializer(), json)
PlaguBot(json, config).start().join()
} }

View File

@@ -0,0 +1,39 @@
package dev.inmo.plagubot
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import kotlinx.serialization.*
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.KoinApplication
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.module.Module
import org.koin.dsl.module
@Serializable
@SerialName("Hello")
class HelloPlugin : Plugin {
@Serializable
data class HelloPluginConfig(
val print: String
)
override fun Module.setupDI(database: Database, params: JsonObject) {
single {
get<Json>().decodeFromJsonElement(HelloPluginConfig.serializer(), params["helloPlugin"] ?: return@single null)
}
}
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
println(koin.get<HelloPluginConfig>().print)
println(getMe())
onCommand("hello_world") {
reply(it, "Hello :)")
}
}
}

View File

@@ -1,28 +0,0 @@
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

@@ -1,16 +0,0 @@
package dev.inmo.plagubot
import dev.inmo.micro_utils.fsm.common.State
fun interface OnStartContextsConflictResolver {
/**
* @param old Old state which is currently placed on the [State.context]
* @param new New state pretend to replace [old] one
* @return Should return:
*
* * Null in case when current realization unable to resolve conflict
* * False when current realization knows that [new] [State] must **not** replace [old] one
* * True when current realization knows that [new] [State] must replace [old] one
*/
suspend operator fun invoke(old: State, new: State): Boolean?
}

View File

@@ -1,20 +0,0 @@
package dev.inmo.plagubot
import dev.inmo.micro_utils.fsm.common.State
fun interface OnUpdateContextsConflictResolver {
/**
* This method will be called when [sourceStateWithOldContext] [State.context] and [newStateWithNewContext] are not equal and currently there is
* launched [currentStateOnNewContext] state on the chain with [State.context] from [currentStateOnNewContext]
*
* @param sourceStateWithOldContext Old state where from [newStateWithNewContext] came
* @param newStateWithNewContext New state with changing [State.context] (it is different with [sourceStateWithOldContext] [State.context])
* @param currentStateOnNewContext State which is currently running on [newStateWithNewContext] [State.context]
* @return Should return:
*
* * Null in case when current realization unable to resolve conflict
* * False when [currentStateOnNewContext] **should not** be stopped in favor to [newStateWithNewContext]
* * True when [currentStateOnNewContext] **should** be stopped in favor to [newStateWithNewContext]
*/
suspend operator fun invoke(sourceStateWithOldContext: State, newStateWithNewContext: State, currentStateOnNewContext: State): Boolean?
}

View File

@@ -1,167 +1,98 @@
package dev.inmo.plagubot package dev.inmo.plagubot
import dev.inmo.kslog.common.*
import dev.inmo.micro_utils.common.Warning
import dev.inmo.micro_utils.coroutines.runCatchingSafely
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.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.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.json.* import kotlinx.serialization.Transient
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 java.io.File import org.koin.dsl.module
import java.util.logging.Level
import java.util.logging.Logger
val Scope.plagubot: PlaguBot
get() = get()
@OptIn(Warning::class)
@Serializable @Serializable
object PlaguBot : Plugin { data class PlaguBot(
override fun KtorRequestsExecutorBuilder.setupBotClient(scope: Scope, params: JsonObject) { private val json: JsonObject,
scope.plugins.filter { it !== this@PlaguBot }.forEach { private val config: Config
with(it) { ) : Plugin {
setupBotClient(scope, params) @Transient
} private val logger = Logger.getLogger("PlaguBot")
} @Transient
} private val bot = telegramBot(config.botToken)
override fun Module.setupDI(config: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { get<Json>().decodeFromJsonElement(Config.serializer(), config) }
single { config } single { config }
single { get<Config>().databaseConfig } single { config.plugins }
single { get<Config>().databaseConfig.database } single { config.databaseConfig }
single { config.databaseConfig.database }
single { defaultJsonFormat }
single { this@PlaguBot } single { this@PlaguBot }
singlePlugins { get<dev.inmo.micro_utils.startup.launcher.Config>().plugins.filterIsInstance<Plugin>() }
single { includes(
val config = get<Config>() config.plugins.mapNotNull {
telegramBot( runCatching {
token = config.botToken, module {
testServer = config.testServer, with(it) {
apiUrl = config.botApiServer setupDI(database, params)
) { }
setupBotClient(this@single, get<JsonObject>()) }
}.onFailure { e ->
logger.log(Level.WARNING, "Unable to load DI part of $it", e)
}.getOrNull()
} }
} )
} }
/** override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
* Getting all [OnStartContextsConflictResolver], [OnUpdateContextsConflictResolver], [StatesManager] and [DefaultStatesManagerRepo] config.plugins.forEach {
* and pass them into [buildBehaviourWithFSM] on top of [TelegramBot] took from [koin]. In time of runCatching {
* [buildBehaviourWithFSM] configuration will call [setupBotPlugin] and [deleteWebhook]. with(it) {
*
* 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) {
koin.plugins.filter { it !== this@PlaguBot }.forEach { plugin ->
runCatchingSafely {
logger.i("Start loading of $plugin")
with(plugin) {
setupBotPlugin(koin) setupBotPlugin(koin)
} }
}.onFailure { e -> }.onFailure { e ->
logger.w("Unable to load bot part of $plugin", e) logger.log(Level.WARNING, "Unable to load bot part of $it", e)
}.onSuccess {
logger.i("Complete loading of $plugin")
} }
} }
} }
/** /**
* Starting plugins system using [StartLauncherPlugin.start]. In time of parsing [initialJson] [PlaguBot] may * This method will create an [Job] which will be the main [Job] of ran instance
* 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(initialJson: JsonObject): Job { suspend fun start(
val initialConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), initialJson) scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
): Job {
KSLog.i("Config has been read") logger.info("Start initialization")
val koinApp = KoinApplication.init()
// Adding of PlaguBot when it absent in config koinApp.modules(
val (resultJson, resultConfig) = if (PlaguBot in initialConfig.plugins) { module {
KSLog.i("Initial config contains PlaguBot, pass config as is to StartLauncherPlugin") setupDI(config.databaseConfig.database, json)
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") logger.info("Modules loaded")
val resultJson = JsonObject( GlobalContext.startKoin(koinApp)
initialJson + Pair("plugins", JsonArray(initialJson["plugins"]!!.jsonArray + JsonPrimitive(PlaguBot::class.qualifiedName!!))) logger.info("Koin started")
) lateinit var behaviourContext: BehaviourContext
val resultConfig = defaultJsonFormat.decodeFromJsonElement(dev.inmo.micro_utils.startup.launcher.Config.serializer(), resultJson) bot.buildBehaviour(scope = scope) {
resultJson to resultConfig logger.info("Start setup of bot part")
behaviourContext = this
setupBotPlugin(koinApp.koin)
deleteWebhook()
}
logger.info("Behaviour builder has been setup")
return bot.startGettingOfUpdatesByLongPolling(scope = behaviourContext, updatesFilter = behaviourContext).also {
logger.info("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,18 +1,13 @@
package dev.inmo.plagubot.config package dev.inmo.plagubot.config
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 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,9 +0,0 @@
package dev.inmo.plagubot.config
import kotlinx.serialization.Serializable
@Serializable
data class DBConnectOptions(
val attempts: Int = 3,
val delay: Long = 1000L
)

View File

@@ -1,15 +1,11 @@
package dev.inmo.plagubot.config package dev.inmo.plagubot.config
import dev.inmo.kslog.common.e
import dev.inmo.kslog.common.logger
import kotlinx.coroutines.delay
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transactionManager import org.jetbrains.exposed.sql.transactions.transactionManager
import org.koin.core.scope.Scope import org.koin.core.scope.Scope
import org.sqlite.JDBC import org.sqlite.JDBC
import java.lang.Exception
import java.sql.Connection import java.sql.Connection
inline val Scope.database: Database? inline val Scope.database: Database?
@@ -20,24 +16,15 @@ data class DatabaseConfig(
val url: String = "jdbc:sqlite:file:test?mode=memory&cache=shared", val url: String = "jdbc:sqlite:file:test?mode=memory&cache=shared",
val driver: String = JDBC::class.qualifiedName!!, val driver: String = JDBC::class.qualifiedName!!,
val username: String = "", val username: String = "",
val password: String = "", val password: String = ""
val reconnectOptions: DBConnectOptions? = DBConnectOptions()
) { ) {
@Transient @Transient
val database: Database = (0 until (reconnectOptions ?.attempts ?: 1)).firstNotNullOfOrNull { val database: Database = Database.connect(
runCatching { url,
Database.connect( driver,
url, username,
driver, password
username, ).also {
password it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE // Or Connection.TRANSACTION_READ_UNCOMMITTED
).also { }
it.transactionManager.defaultIsolationLevel = Connection.TRANSACTION_SERIALIZABLE // Or Connection.TRANSACTION_READ_UNCOMMITTED
it.connector().close()
}
}.onFailure {
logger.e(it)
Thread.sleep(reconnectOptions ?.delay ?: return@onFailure)
}.getOrNull()
} ?: error("Unable to create database")
} }

View File

@@ -1,9 +1,7 @@
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

@@ -1,3 +0,0 @@
package dev.inmo.plagubot.config
typealias StateHandlingErrorHandler<T> = suspend (T, Throwable) -> T?

View File

@@ -8,7 +8,6 @@ buildscript {
classpath libs.buildscript.kt.gradle classpath libs.buildscript.kt.gradle
classpath libs.buildscript.kt.serialization classpath libs.buildscript.kt.serialization
classpath libs.buildscript.gh.release classpath libs.buildscript.gh.release
classpath libs.buildscript.ksp
} }
} }
@@ -17,7 +16,6 @@ allprojects {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
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 "${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=10.2.1 version=1.0.0

View File

@@ -1,22 +1,27 @@
[versions] [versions]
kt = "2.1.0" kt = "1.6.21"
kt-serialization = "1.7.3" kt-serialization = "1.3.3"
kt-coroutines = "1.9.0" kt-coroutines = "1.6.1"
microutils = "0.23.1" microutils = "0.10.4"
tgbotapi = "21.0.0" tgbotapi = "1.1.0"
ksp = "2.1.0-1.0.28" jb-exposed = "0.38.2"
jb-dokka = "1.6.21"
jb-exposed = "0.56.0" sqlite = "3.36.0.3"
jb-dokka = "1.9.20"
sqlite = "3.47.1.0" klock = "2.7.0"
uuid = "0.4.0"
gh-release = "2.5.2" ktor = "2.0.1"
koin = "4.0.0" gh-release = "2.3.7"
android-gradle = "7.0.4"
dexcount = "3.1.0"
koin = "3.2.0"
[libraries] [libraries]
@@ -26,12 +31,7 @@ kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", vers
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" } kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" } tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" }
tgbotapi-behaviourBuilder-fsm = { module = "dev.inmo:tgbotapi.behaviour_builder.fsm", version.ref = "tgbotapi" }
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-generator = { module = "dev.inmo:micro_utils.koin.generator", version.ref = "microutils" }
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" }
@@ -47,4 +47,3 @@ buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin",
buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" } buildscript-kt-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kt" }
buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" } buildscript-jb-dokka = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "jb-dokka" }
buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" } buildscript-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
buildscript-ksp = { module = "com.google.devtools.ksp:symbol-processing-gradle-plugin", version.ref = "ksp" }

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

View File

@@ -14,12 +14,11 @@ 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
} }
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_1_8
} }

View File

@@ -2,11 +2,11 @@ apply plugin: 'maven-publish'
task javadocJar(type: Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
archiveClassifier = 'javadoc' classifier = 'javadoc'
} }
task sourcesJar(type: Jar) { task sourcesJar(type: Jar) {
from sourceSets.main.allSource from sourceSets.main.allSource
archiveClassifier = 'sources' classifier = 'sources'
} }
publishing { publishing {
@@ -49,28 +49,14 @@ publishing {
} }
} }
repositories { repositories {
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven { maven {
name = "sonatype" name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials { credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD') password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
} }
} }
} }
} }
@@ -92,27 +78,4 @@ if (project.hasProperty("signing.gnupg.keyName")) {
dependsOn(it) dependsOn(it)
} }
} }
// Workaround to make android sign operations depend on signing tasks
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks)
}
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

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":"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"} {"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":"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,62 +0,0 @@
package dev.inmo.plagubot
import dev.inmo.kslog.common.*
import dev.inmo.micro_utils.fsm.common.State
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.waitTextMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
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.koin.core.Koin
import org.koin.core.module.Module
@Serializable
@SerialName("Hello")
object HelloPlugin : Plugin {
@Serializable
data class HelloPluginConfig(
val print: String
)
override fun Module.setupDI(config: JsonObject) {
registerConfig<HelloPluginConfig>("helloPlugin") { null }
}
private sealed interface InternalFSMState : State {
override val context: IdChatIdentifier
data class DidntSaidHello(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) {
val toPrint = koin.configOrNull<HelloPluginConfig>() ?.print ?: "Hello :)"
logger.d { toPrint }
logger.dS { getMe().toString() }
onCommand("hello_world") {
startChain(InternalFSMState.DidntSaidHello(it.chat.id))
}
strictlyOn { state: InternalFSMState.DidntSaidHello ->
sendMessage(state.context, toPrint)
InternalFSMState.SaidHelloOnce(state.context)
}
strictlyOn { state: InternalFSMState.SaidHelloOnce ->
val message = waitTextMessage().first()
reply(message, "Sorry, I can answer only this: $toPrint")
InternalFSMState.SaidHelloOnce(state.context)
}
}
}

View File

@@ -1,11 +0,0 @@
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

@@ -1,55 +0,0 @@
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,14 +1,11 @@
package dev.inmo.plagubot package dev.inmo.plagubot
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 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.scope.Scope import org.koin.core.module.Module
/** /**
* **ANY REALIZATION OF [Plugin] MUST HAVE CONSTRUCTOR WITH ABSENCE OF INCOMING PARAMETERS** * **ANY REALIZATION OF [Plugin] MUST HAVE CONSTRUCTOR WITH ABSENCE OF INCOMING PARAMETERS**
@@ -18,31 +15,15 @@ import org.koin.core.scope.Scope
* too. * too.
*/ */
@Serializable(PluginSerializer::class) @Serializable(PluginSerializer::class)
interface Plugin : StartPlugin { interface Plugin {
@Deprecated("Deprecated in favor to setupBotClient with arguments")
fun KtorRequestsExecutorBuilder.setupBotClient() {}
/** /**
* Will be called on stage of bot setup * This method will be called when this plugin should configure di module based on the incoming params
*
* @param scope The scope of [org.koin.core.module.Module.single] of bot definition
* @param params Params (in fact, the whole bot config)
*/
fun KtorRequestsExecutorBuilder.setupBotClient(scope: Scope, params: JsonObject) = setupBotClient()
/**
* 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]
*/ */
fun Module.setupDI(
database: Database,
params: JsonObject
) {}
suspend fun BehaviourContext.setupBotPlugin( suspend fun BehaviourContext.setupBotPlugin(
koin: Koin koin: 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
* for the FSM configuration
*/
suspend fun BehaviourContextWithFSM<State>.setupBotPlugin(koin: Koin) {
(this as BehaviourContext).setupBotPlugin(koin)
}
} }

View File

@@ -13,8 +13,7 @@ class PluginSerializer : KSerializer<Plugin> {
get() = String.serializer().descriptor get() = String.serializer().descriptor
override fun deserialize(decoder: Decoder): Plugin { override fun deserialize(decoder: Decoder): Plugin {
val kclass = Class.forName(decoder.decodeString()).kotlin return Class.forName(decoder.decodeString()).getDeclaredConstructor().newInstance() as Plugin
return (kclass.objectInstance ?: kclass.constructors.first { it.parameters.isEmpty() }.call()) as Plugin
} }
override fun serialize(encoder: Encoder, value: Plugin) { override fun serialize(encoder: Encoder, value: Plugin) {

View File

@@ -2,12 +2,7 @@ String[] toInclude = [":bot", ":plugin"]
rootProject.name = 'plagubot' rootProject.name = 'plagubot'
toInclude.each { originalName -> toInclude.each {
String projectDirectory = "${rootProject.projectDir.getAbsolutePath()}${originalName.replace(":", File.separator)}" include (it)
String projectName = "${rootProject.name}${originalName.replace(":", ".")}" project(it).name = "${rootProject.name}${it.replace(":", ".")}"
String projectIdentifier = ":${projectName}"
include projectIdentifier
ProjectDescriptor project = project(projectIdentifier)
project.name = projectName
project.projectDir = new File(projectDirectory)
} }

View File

@@ -4,10 +4,7 @@
"driver": "org.sqlite.JDBC", "driver": "org.sqlite.JDBC",
"username": "OPTIONAL username", "username": "OPTIONAL username",
"password": "OPTIONAL password", "password": "OPTIONAL password",
"reconnectOptions": { "initAutomatically": false
"attempts": 3,
"delay": 1000
}
}, },
"botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678", "botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
"plugins": [ "plugins": [