Compare commits

...

47 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
4df6eafe6a Update CHANGELOG.md 2022-02-02 13:37:33 +06:00
3876262a7b Update gradle.properties 2022-02-02 13:36:59 +06:00
b10a00ca0d Update README.md 2022-01-06 12:46:23 +06:00
e6765b6c25 Merge pull request #24 from InsanusMokrassar/0.5.0
0.5.0
2022-01-04 17:41:05 +06:00
2fde3206e7 update changelog 2022-01-02 12:42:41 +06:00
4113fb2cc1 update dependencies 2022-01-02 12:41:15 +06:00
79ddfaf9bd start 0.5.0 2022-01-02 12:34:12 +06:00
8ae85ecc6e Merge pull request #23 from InsanusMokrassar/0.4.1
0.4.1
2021-11-12 17:38:41 +06:00
9d25b4b071 0.4.1 2021-11-12 17:38:18 +06:00
6d00bb6242 Merge pull request #22 from InsanusMokrassar/0.4.0
0.4.0
2021-11-12 15:33:37 +06:00
d7638d620f 0.4.0 2021-11-12 14:05:42 +06:00
8885047409 start 0.4.0 2021-11-12 13:38:19 +06:00
f09f13c13d Merge pull request #21 from InsanusMokrassar/0.3.2
0.3.2
2021-09-22 20:34:34 +06:00
8853af9a26 Update CHANGELOG.md 2021-09-22 20:34:03 +06:00
ab98b59633 Update dependencies 2021-09-22 20:30:52 +06:00
d88cf0c911 Update gradle.properties 2021-09-22 20:29:28 +06:00
5145b22dd5 Merge pull request #20 from InsanusMokrassar/0.3.1
0.3.1
2021-07-02 11:19:27 +06:00
90dd06db6d update dependencies 2021-07-01 22:44:01 +06:00
4e9b8349eb start 0.3.1 2021-07-01 22:38:54 +06:00
466e531faf Merge pull request #19 from InsanusMokrassar/0.3.0
0.3.0
2021-06-07 19:38:43 +06:00
8bf42e6fca Update CHANGELOG.md 2021-06-07 19:35:46 +06:00
a42bb91121 Update gradle.properties 2021-06-07 19:35:29 +06:00
fae2dca3d3 update changelog 2021-06-05 16:16:40 +06:00
a0d1bd2d54 update mircoutils and exposed 2021-06-05 15:25:17 +06:00
14e6c0c67d update versions 2021-06-04 12:12:58 +06:00
5a116d512a migration onto 0.3.0 2021-06-04 12:09:06 +06:00
3ba965f162 PluginsHolder, new Config deserialization and PlaguBot as correctly serializable/deserializable 2021-05-09 12:37:28 +06:00
aa8dc1af57 start 0.2.2 2021-05-08 21:35:53 +06:00
e83af6ba9f Merge pull request #17 from InsanusMokrassar/0.2.1
0.2.1
2021-05-06 03:01:26 +06:00
83c5c30555 update dependencies 2021-05-05 19:45:49 +06:00
2740ba690b start 0.2.1 2021-05-05 19:43:06 +06:00
9c681e25d2 Merge pull request #16 from InsanusMokrassar/0.2.0
0.2.0
2021-04-30 01:44:46 +06:00
d799845bea Update CHANGELOG.md 2021-04-30 01:44:24 +06:00
1f45b80b54 Update gradle.properties 2021-04-30 01:42:22 +06:00
9dabad898a Merge pull request #15 from InsanusMokrassar/0.1.9
0.1.9
2021-04-25 10:55:56 +06:00
3a718f8652 Update gradle.properties 2021-04-25 09:45:47 +06:00
6f70c9eab3 Update CHANGELOG.md 2021-04-25 09:41:36 +06:00
ca98c74952 start 0.1.9 2021-04-25 09:35:35 +06:00
4fd6922864 Merge pull request #14 from InsanusMokrassar/0.1.8
0.1.8
2021-04-05 21:27:52 +06:00
25 changed files with 354 additions and 410 deletions

View File

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

View File

@@ -1,5 +1,94 @@
# Changelog # Changelog
## 1.0.0
* `Versions`
* `kotlin`: `1.6.21`
* `coroutines`: `1.6.1`
* `serialization`: `1.3.3`
* `exposed`: `0.38.2`
* `tgbotapi`: `1.1.0`
* `microutils`: `0.10.4`
* `Common`:
* ___ALL THE SDI/KLASSINDEX FUNCTIONALITY HAS BEEN REMOVED___
* `Plugin`:
* Now plugins must have empty constructor
* Now plugins may provide realization of two methods: `setupDI` and `setupBotPlugin`
* `PlaguBot`:
* `Config` now is simple serializable `data class`
* `PlaguBot` now is more simple as a plugin
## 0.5.1
* `Versions`
* `tgbotapi`: `0.38.4`
* `microutils`: `0.9.5`
## 0.5.0
* `Versions`
* `kotlin`: `1.6.10`
* `coroutines`: `1.6.0`
* `serialization`: `1.3.2`
* `exposed`: `0.37.2`
* `tgbotapi`: `0.38.0`
* `microutils`: `0.9.0`
## 0.4.1
Temporal update for compatibility with java 1.8
## 0.3.2
* `Versions`
* `kotlin`: `1.5.20` -> `1.5.31`
* `coroutines`: `1.5.0` -> `1.5.2`
* `serialization`: `1.2.1` -> `1.2.2`
* `exposed`: `0.32.1` -> `0.34.2`
* `tgbotapi`: `0.35.1` -> `0.35.9`
* `microutils`: `0.5.15` -> `0.5.28`
## 0.3.1
* `Versions`
* `kotlin`: `1.5.10` -> `1.5.20`
* `tgbotapi`: `0.35.0` -> `0.35.1`
* `microutils`: `0.5.7` -> `0.5.15`
## 0.3.0
* `Versions`
* `kotlin`: `1.4.32` -> `1.5.10`
* `coroutines`: `1.4.3` -> `1.5.0`
* `serialization`: `1.1.0` -> `1.2.1`
* `exposed`: `0.31.1` -> `0.32.1`
* `sdi`: `0.4.1` -> `0.5.0`
* `tgbotapi`: `0.34.1` -> `0.35.0`
* `microutils`: `0.4.36` -> `0.5.7`
* `Bot`
* Add plugin `PluginsHolder`
* Rewrite mechanism of `Config` working
* `PlaguBot` now is correctly serializable/deserializable
## 0.2.1
* `Versions`
* `tgbotapi`: `0.33.4` -> `0.34.0`
* `sqlite`: `3.30.1` -> `3.34.0`
## 0.2.0
* `Versions`
* `tgbotapi`: `0.33.4` -> `0.34.0`
* `exposed`: `0.30.2` -> `0.31.1`
## 0.1.9
* `Versions`
* `tgbotapi`: `0.33.3` -> `0.33.4`
* `microutils`: `0.4.33` -> `0.4.36`
* `exposed`: `0.30.1` -> `0.30.2`
## 0.1.8 ## 0.1.8
* `Versions` * `Versions`

View File

@@ -11,8 +11,8 @@ You can create your bot using
| Template: | [![Use template](badges/use_template.svg)](https://github.com/InsanusMokrassar/PlaguBotPluginTemplate/generate) | | Template: | [![Use template](badges/use_template.svg)](https://github.com/InsanusMokrassar/PlaguBotPluginTemplate/generate) |
|-----------|-----------------------------------------------------------------------------------------------------------------| |-----------|-----------------------------------------------------------------------------------------------------------------|
| Bot version: | [![Download](https://api.bintray.com/packages/insanusmokrassar/PlaguBot/plagubot.bot/images/download.svg)](https://bintray.com/insanusmokrassar/PlaguBot/plagubot.bot/_latestVersion) | | Bot version: | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/plagubot.bot/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/plagubot.bot) |
| Plugin version: | [![Download](https://api.bintray.com/packages/insanusmokrassar/PlaguBot/plagubot.plugin/images/download.svg)](https://bintray.com/insanusmokrassar/PlaguBot/plagubot.plugin/_latestVersion) | | Plugin version: | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/dev.inmo/plagubot.plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/dev.inmo/plagubot.plugin) |
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.

View File

@@ -1,7 +1,6 @@
plugins { plugins {
id 'org.jetbrains.kotlin.jvm' id 'org.jetbrains.kotlin.jvm'
id "org.jetbrains.kotlin.plugin.serialization" id "org.jetbrains.kotlin.plugin.serialization"
id "org.jetbrains.kotlin.kapt"
id 'application' id 'application'
} }
@@ -11,21 +10,17 @@ project.version="$version"
apply from: "publish.gradle" apply from: "publish.gradle"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation libs.kt.stdlib
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" api libs.kt.coroutines
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version" api libs.kt.serialization
api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" api libs.jb.exposed.jdbc
api "org.jetbrains.exposed:exposed-jdbc:$kotlin_exposed_version"
api "dev.inmo:tgbotapi:$tgbotapi_version" api libs.tgbotapi
api "dev.inmo:sdi:$sdi_version" api libs.microutils.repos.exposed
api "dev.inmo:micro_utils.repos.exposed:$microutils_version"
api "com.github.matfax.klassindex:library:$klassindex_version" api libs.sqlite
kapt "com.github.matfax.klassindex:processor:$klassindex_version"
api "org.xerial:sqlite-jdbc:$sqlite_version"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testImplementation libs.kt.test.junit
api project(":plagubot.plugin") api project(":plagubot.plugin")
} }
@@ -34,10 +29,8 @@ application {
mainClassName = 'dev.inmo.plagubot.AppKt' mainClassName = 'dev.inmo.plagubot.AppKt'
} }
kapt { java {
arguments { sourceCompatibility = JavaVersion.VERSION_1_8
arg("com.github.matfax.klassindex.IndexSubclasses", "dev.inmo.plagubot.Plugin") targetCompatibility = JavaVersion.VERSION_1_8
arg("com.github.matfax.klassindex.IndexAnnotated", "dev.inmo.sdi.SDIIncluded")
}
} }

View File

@@ -1 +0,0 @@
{"bintrayConfig":{"repo":"PlaguBot","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/PlaguBot","autoPublish":true,"overridePublish":true},"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","includeGpgSigning":true,"publishToMavenCentral":true,"developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"JVM"}

View File

@@ -1,6 +1,4 @@
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'signing'
task javadocJar(type: Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
@@ -50,17 +48,8 @@ publishing {
} }
} }
repositories {
repositories { if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/PlaguBot/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
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/")
@@ -69,14 +58,24 @@ publishing {
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')
} }
} }
} }
}
} }
} }
} }
signing { if (project.hasProperty("signing.gnupg.keyName")) {
useGpgCmd() apply plugin: 'signing'
sign publishing.publications
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
} }

1
bot/publish.kpsb Normal file
View File

@@ -0,0 +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":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

View File

@@ -3,20 +3,9 @@ package dev.inmo.plagubot
import dev.inmo.plagubot.config.* import dev.inmo.plagubot.config.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import kotlinx.serialization.json.jsonObject
import java.io.File import java.io.File
@Deprecated(
"This method is redundant due to new class PlaguBot",
ReplaceWith(
"PlaguBot(config).start(scope)",
"dev.inmo.plagubot.PlaguBot"
)
)
suspend inline fun initPlaguBot(
config: Config,
scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
): Job = PlaguBot(config).start(scope)
/** /**
* 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
*/ */
@@ -24,7 +13,8 @@ suspend inline fun initPlaguBot(
suspend fun main(args: Array<String>) { suspend fun main(args: Array<String>) {
val (configPath) = args val (configPath) = args
val file = File(configPath) val file = File(configPath)
val config = configJsonFormat.decodeFromString(ConfigSerializer, file.readText()) val json = defaultJsonFormat.parseToJsonElement(file.readText()).jsonObject
val config = defaultJsonFormat.decodeFromJsonElement(Config.serializer(), json)
PlaguBot(config).start().join() PlaguBot(json, config).start().join()
} }

View File

@@ -1,16 +1,39 @@
package dev.inmo.plagubot 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.BehaviourContext
import kotlinx.serialization.SerialName import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import kotlinx.serialization.Serializable import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database 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 @Serializable
@SerialName("Hello") @SerialName("Hello")
data class HelloPlugin( class HelloPlugin : Plugin {
val parameter: String @Serializable
) : Plugin { data class HelloPluginConfig(
override suspend fun BehaviourContext.invoke(database: Database) { val print: String
println(parameter) )
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,56 +1,98 @@
package dev.inmo.plagubot package dev.inmo.plagubot
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.plagubot.config.*
import dev.inmo.plagubot.config.Config import dev.inmo.tgbotapi.bot.ktor.telegramBot
import dev.inmo.plagubot.config.database import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook
import dev.inmo.tgbotapi.bot.Ktor.telegramBot import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.buildBehaviour
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.botCommandsLimit
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin
import org.koin.core.KoinApplication
import org.koin.core.context.GlobalContext
import org.koin.core.module.Module
import org.koin.core.scope.Scope
import org.koin.dsl.module
import java.util.logging.Level
import java.util.logging.Logger
const val DefaultPlaguBotParamsKey = "plagubot" val Scope.plagubot: PlaguBot
val Map<String, Any>.plagubot get() = get()
get() = get(DefaultPlaguBotParamsKey) as? PlaguBot
@Serializable @Serializable
data class PlaguBot( data class PlaguBot(
private val json: JsonObject,
private val config: Config private val config: Config
) : Plugin { ) : Plugin {
@Transient
private val logger = Logger.getLogger("PlaguBot")
@Transient @Transient
private val bot = telegramBot(config.botToken) private val bot = telegramBot(config.botToken)
@Transient
private val paramsMap = config.params ?.toMap() ?: emptyMap()
@Transient
private val database = config.params ?.database ?: config.database.database
override suspend fun getCommands(): List<BotCommand> = config.plugins.flatMap { override fun Module.setupDI(database: Database, params: JsonObject) {
it.getCommands() single { config }
single { config.plugins }
single { config.databaseConfig }
single { config.databaseConfig.database }
single { defaultJsonFormat }
single { this@PlaguBot }
includes(
config.plugins.mapNotNull {
runCatching {
module {
with(it) {
setupDI(database, params)
}
}
}.onFailure { e ->
logger.log(Level.WARNING, "Unable to load DI part of $it", e)
}.getOrNull()
}
)
} }
override suspend fun BehaviourContext.invoke(database: Database, params: Map<String, Any>) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
config.plugins.forEach { config.plugins.forEach {
it.apply { invoke(database, params) } runCatching {
with(it) {
setupBotPlugin(koin)
}
}.onFailure { e ->
logger.log(Level.WARNING, "Unable to load bot part of $it", e)
}
} }
val commands = getCommands()
val futureUnavailable = commands.drop(botCommandsLimit.last)
if (futureUnavailable.isNotEmpty()) {
println("Next commands are out of range in setting command request and will be unavailable from autocompleting: $futureUnavailable")
}
safelyWithoutExceptions { setMyCommands(commands.take(botCommandsLimit.last)) }
} }
/** /**
* This method will create an [Job] which will be the main [Job] of ran instance * This method will create an [Job] which will be the main [Job] of ran instance
*/ */
suspend fun start( suspend fun start(
scope: CoroutineScope = CoroutineScope(Dispatchers.Default) scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
): Job = bot.buildBehaviour(scope) { ): Job {
invoke(database, paramsMap) logger.info("Start initialization")
val koinApp = KoinApplication.init()
koinApp.modules(
module {
setupDI(config.databaseConfig.database, json)
}
)
logger.info("Modules loaded")
GlobalContext.startKoin(koinApp)
logger.info("Koin started")
lateinit var behaviourContext: BehaviourContext
bot.buildBehaviour(scope = scope) {
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")
}
} }
} }

View File

@@ -1,166 +1,13 @@
package dev.inmo.plagubot.config package dev.inmo.plagubot.config
import com.github.matfax.klassindex.KlassIndex
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plagubot.PluginSerializer import kotlinx.serialization.SerialName
import dev.inmo.sdi.* import kotlinx.serialization.Serializable
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.*
import kotlin.reflect.KClass
@InternalSerializationApi
internal inline fun <T : Plugin> KClass<T>.includeIn(builder: PolymorphicModuleBuilder<Plugin>) = builder.subclass(this, serializer())
@InternalSerializationApi
internal val configJsonFormat: Json
get() = Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
polymorphic(Plugin::class) {
KlassIndex.getSubclasses(Plugin::class).flatMap { kclass ->
kclass.includeIn(this)
kclass.annotations.mapNotNull { it as? SerialName }.map {
it.value to kclass.serializer()
} + listOfNotNull(
kclass.simpleName ?.let {
it to kclass.serializer()
}
)
}.toMap().let {
default { requiredType ->
it[requiredType]
}
}
}
}
}
@Serializable @Serializable
data class Config( data class Config(
val plugins: List<@Contextual Plugin>,
val database: DatabaseConfig = DatabaseConfig(),
val botToken: String, val botToken: String,
@Contextual val plugins: List<Plugin>,
val params: Module? = null @SerialName("database")
val databaseConfig: DatabaseConfig = DatabaseConfig(),
) )
@Serializer(Plugin::class)
private class InternalPluginSerializer(
private val params: Module
) : KSerializer<Plugin> {
override val descriptor: SerialDescriptor = PluginSerializer.descriptor
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Plugin {
val asJson = JsonElement.serializer().deserialize(decoder)
return if (asJson is JsonPrimitive) {
params[asJson.jsonPrimitive.content] as Plugin
} else {
val jsonFormat = ((decoder as? JsonDecoder)?.json ?: configJsonFormat)
jsonFormat.decodeFromJsonElement(PluginSerializer, asJson)
}
}
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Plugin) {
params.keys.firstOrNull { params[it] === value } ?.also {
encoder.encodeString(it)
} ?: PluginSerializer.serialize(encoder, value)
}
}
private val DefaultModuleSerializer = ModuleSerializer(emptyList()) {
}
@Serializer(Module::class)
private class InternalModuleSerializer(
private val original: JsonElement?,
private val params: Module
) : KSerializer<Module> {
override val descriptor: SerialDescriptor = PluginSerializer.descriptor
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Module {
val asJson = JsonElement.serializer().deserialize(decoder)
return if (asJson == original) {
params
} else {
configJsonFormat.decodeFromJsonElement(DefaultModuleSerializer, asJson)
}
}
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Module) = DefaultModuleSerializer.serialize(encoder, value)
}
private fun internalPluginSerializerSerializersModule(
internalPluginSerializer: InternalPluginSerializer,
internalModuleSerializer: InternalModuleSerializer?
) = SerializersModule {
contextual(internalPluginSerializer)
contextual(internalModuleSerializer ?: return@SerializersModule)
}
@Serializer(Config::class)
internal object ConfigSerializer : KSerializer<Config> {
private val jsonSerializer = JsonObject.serializer()
private val moduleSerializer = ModuleSerializer()
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Config {
val json = jsonSerializer.deserialize(decoder)
val jsonFormat = (decoder as? JsonDecoder) ?.json ?: configJsonFormat
val paramsRow = json["params"]
val resultJsonFormat = if (paramsRow != null && paramsRow != JsonNull) {
val params = jsonFormat.decodeFromJsonElement(
moduleSerializer,
paramsRow
)
val pluginsSerializer = InternalPluginSerializer(params)
val moduleSerializer = InternalModuleSerializer(paramsRow, params)
Json(jsonFormat) {
serializersModule = decoder.serializersModule.overwriteWith(
internalPluginSerializerSerializersModule(pluginsSerializer, moduleSerializer)
)
}
} else {
jsonFormat
}
return resultJsonFormat.decodeFromJsonElement(
Config.serializer(),
json
)
}
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Config) {
if (value.params != null) {
val pluginsSerializer = InternalPluginSerializer(value.params)
val jsonFormat = Json(configJsonFormat) {
serializersModule = encoder.serializersModule.overwriteWith(
internalPluginSerializerSerializersModule(pluginsSerializer, null)
)
}
jsonSerializer.serialize(
encoder,
jsonFormat.encodeToJsonElement(
Config.serializer(),
value
) as JsonObject
)
} else {
Config.serializer().serialize(encoder, value)
}
}
}

View File

@@ -1,19 +1,17 @@
package dev.inmo.plagubot.config package dev.inmo.plagubot.config
import dev.inmo.sdi.SDIIncluded
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.sqlite.JDBC import org.sqlite.JDBC
import java.sql.Connection import java.sql.Connection
const val defaultDatabaseParamsName = "defaultDatabase" inline val Scope.database: Database?
inline val Map<String, Any>.database: Database? get() = getOrNull<Database>()
get() = (get(defaultDatabaseParamsName) as? DatabaseConfig) ?.database
@Serializable @Serializable
@SDIIncluded
data class DatabaseConfig( 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!!,

View File

@@ -0,0 +1,7 @@
package dev.inmo.plagubot.config
import kotlinx.serialization.json.Json
val defaultJsonFormat = Json {
ignoreUnknownKeys = true
}

View File

@@ -1,37 +0,0 @@
package dev.inmo.plagubot.config
import dev.inmo.plagubot.HelloPlugin
import kotlinx.serialization.InternalSerializationApi
import org.junit.Test
import kotlin.test.assertEquals
class ConfigTest {
@InternalSerializationApi
@Test
fun testThatPluginPassedToParamsWillBeCorrectlyUsedInPlugins() {
val rawConfig = """
{
"database": {
},
"botToken": "",
"plugins": [
"helloPlugin"
],
"params": {
"helloPlugin": {"type": "dev.inmo.plagubot.HelloPlugin", "parameter": "Example"}
}
}
""".trimIndent()
val config = configJsonFormat.decodeFromString(ConfigSerializer, rawConfig)
assert(config.plugins.size == 1)
assert(config.plugins.first() is HelloPlugin)
assert((config.plugins.first() as HelloPlugin).parameter == "Example")
val redecoded = configJsonFormat.decodeFromString(ConfigSerializer, configJsonFormat.encodeToString(ConfigSerializer, config))
assertEquals(config.database, redecoded.database)
assertEquals(config.plugins, redecoded.plugins)
assertEquals(config.botToken, redecoded.botToken)
assertEquals(config.params ?.toMap(), redecoded.params ?.toMap())
}
}

View File

@@ -1,20 +1,19 @@
buildscript { buildscript {
repositories { repositories {
jcenter() mavenCentral()
maven { url "https://plugins.gradle.org/m2/" } maven { url "https://plugins.gradle.org/m2/" }
} }
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath libs.buildscript.kt.gradle
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath libs.buildscript.kt.serialization
classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" classpath libs.buildscript.gh.release
} }
} }
allprojects { allprojects {
repositories { repositories {
mavenCentral() mavenCentral()
jcenter()
mavenLocal() mavenLocal()
maven { url 'https://jitpack.io' } maven { url 'https://jitpack.io' }
} }

View File

@@ -4,19 +4,5 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin_version=1.4.32
kotlin_coroutines_version=1.4.3
kotlin_serialisation_runtime_version=1.1.0
kotlin_exposed_version=0.30.1
sdi_version=0.4.1
tgbotapi_version=0.33.3
microutils_version=0.4.33
klassindex_version=4.1.0-rc.1
sqlite_version=3.30.1
github_release_plugin_version=2.2.12
group=dev.inmo group=dev.inmo
version=0.1.8 version=1.0.0

49
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,49 @@
[versions]
kt = "1.6.21"
kt-serialization = "1.3.3"
kt-coroutines = "1.6.1"
microutils = "0.10.4"
tgbotapi = "1.1.0"
jb-exposed = "0.38.2"
jb-dokka = "1.6.21"
sqlite = "3.36.0.3"
klock = "2.7.0"
uuid = "0.4.0"
ktor = "2.0.1"
gh-release = "2.3.7"
android-gradle = "7.0.4"
dexcount = "3.1.0"
koin = "3.2.0"
[libraries]
kt-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kt" }
kt-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kt-coroutines" }
kt-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kt-serialization" }
tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" }
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
koin = { module = "io.insert-koin:koin-core", version.ref = "koin" }
jb-exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "jb-exposed" }
sqlite = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite" }
kt-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kt" }
buildscript-kt-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", 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-gh-release = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }

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-6.8.3-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

@@ -9,9 +9,16 @@ project.version="$version"
apply from: "publish.gradle" apply from: "publish.gradle"
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation libs.kt.stdlib
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version" api libs.kt.serialization
api "dev.inmo:tgbotapi:$tgbotapi_version" api libs.tgbotapi
api "dev.inmo:micro_utils.repos.exposed:$microutils_version" api libs.microutils.repos.exposed
api libs.koin
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
} }

View File

@@ -1 +0,0 @@
{"bintrayConfig":{"repo":"PlaguBot","packageName":"${project.name}","packageVcs":"https://github.com/InsanusMokrassar/PlaguBot","autoPublish":true,"overridePublish":true},"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","includeGpgSigning":true,"publishToMavenCentral":true,"developers":[{"id":"InsanusMokrassar","name":"Aleksei Ovsiannikov","eMail":"ovsyannikov.alexey95@gmail.com"}]},"type":"JVM"}

View File

@@ -1,6 +1,4 @@
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
apply plugin: 'signing'
task javadocJar(type: Jar) { task javadocJar(type: Jar) {
from javadoc from javadoc
@@ -50,17 +48,8 @@ publishing {
} }
} }
repositories {
repositories { if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "bintray"
url = uri("https://api.bintray.com/maven/${project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')}/PlaguBot/${project.name}/;publish=1;override=1")
credentials {
username = project.hasProperty('BINTRAY_USER') ? project.property('BINTRAY_USER') : System.getenv('BINTRAY_USER')
password = project.hasProperty('BINTRAY_KEY') ? project.property('BINTRAY_KEY') : System.getenv('BINTRAY_KEY')
}
}
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/")
@@ -69,14 +58,24 @@ publishing {
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')
} }
} }
} }
}
} }
} }
} }
signing { if (project.hasProperty("signing.gnupg.keyName")) {
useGpgCmd() apply plugin: 'signing'
sign publishing.publications
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
} }

1
plugin/publish.kpsb Normal file
View File

@@ -0,0 +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":"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,14 +1,15 @@
package dev.inmo.plagubot package dev.inmo.plagubot
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin
import org.koin.core.module.Module
/** /**
* **ANY REALIZATION OF [Plugin] MUST HAVE CONSTRUCTOR WITH ABSENCE OF INCOMING PARAMETERS**
*
* Use this interface for your bot. It is possible to use [kotlinx.serialization.SerialName] annotations on your plugins * Use this interface for your bot. It is possible to use [kotlinx.serialization.SerialName] annotations on your plugins
* to set up short name for your plugin. Besides, simple name of your class will be used as key for deserialization * to set up short name for your plugin. Besides, simple name of your class will be used as key for deserialization
* too. * too.
@@ -16,32 +17,13 @@ import org.jetbrains.exposed.sql.Database
@Serializable(PluginSerializer::class) @Serializable(PluginSerializer::class)
interface Plugin { interface Plugin {
/** /**
* In case you want to publish some processed by your plugin commands, you can provide it via this method * This method will be called when this plugin should configure di module based on the incoming params
*
* @see BotCommand
*/ */
suspend fun getCommands(): List<BotCommand> = emptyList() fun Module.setupDI(
@Deprecated("Override other method with receiver BehaviourContext")
suspend operator fun invoke(
bot: TelegramBot,
database: Database, database: Database,
updatesFilter: FlowsUpdatesFilter, params: JsonObject
scope: CoroutineScope ) {}
suspend fun BehaviourContext.setupBotPlugin(
koin: Koin
) {} ) {}
/**
* This method (usually) will be invoked just one time in the whole application.
*/
suspend operator fun BehaviourContext.invoke(
database: Database
) = invoke(bot, database, flowsUpdatesFilter, scope)
/**
* This method (usually) will be invoked just one time in the whole application.
*/
suspend operator fun BehaviourContext.invoke(
database: Database,
params: Map<String, Any>
) = invoke(database)
} }

View File

@@ -1,53 +1,24 @@
package dev.inmo.plagubot package dev.inmo.plagubot
import kotlinx.serialization.* import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.json.*
private val defaultJson = Json {
ignoreUnknownKeys = true
}
@Serializer(Plugin::class) @Serializer(Plugin::class)
object PluginSerializer : KSerializer<Plugin> { class PluginSerializer : KSerializer<Plugin> {
private val polymorphic = PolymorphicSerializer(Plugin::class) override val descriptor: SerialDescriptor
override val descriptor: SerialDescriptor = JsonObject.serializer().descriptor get() = String.serializer().descriptor
@InternalSerializationApi
override fun deserialize(decoder: Decoder): Plugin { override fun deserialize(decoder: Decoder): Plugin {
val format = (decoder as? JsonDecoder) ?.json ?: defaultJson return Class.forName(decoder.decodeString()).getDeclaredConstructor().newInstance() as Plugin
val asJson = JsonElement.serializer().deserialize(decoder)
val jsonObject = (asJson as? JsonObject)
val type = (jsonObject ?.get("type") as? JsonPrimitive) ?.contentOrNull
val external = if (type != null) {
try {
Class.forName(type) ?.kotlin ?.serializerOrNull()
} catch (e: Exception) {
null
}
} else {
null
}
return if (jsonObject != null && external != null) {
format.decodeFromJsonElement(
external as KSerializer<Plugin>,
JsonObject(jsonObject.toMutableMap().also { it.remove("type") })
)
} else {
format.decodeFromJsonElement(
polymorphic,
asJson
)
}
} }
@InternalSerializationApi
override fun serialize(encoder: Encoder, value: Plugin) { override fun serialize(encoder: Encoder, value: Plugin) {
val serializer = (value::class.serializerOrNull() ?: polymorphic) as KSerializer<Plugin> encoder.encodeString(
serializer.serialize(encoder, value) value::class.java.canonicalName
)
} }
} }

View File

@@ -8,9 +8,9 @@
}, },
"botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678", "botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
"plugins": [ "plugins": [
{ "dev.inmo.plagubot.HelloPlugin"
"type": "Hello", ],
"parameter": "Example" "helloPlugin": {
} "print": "Hello World"
] }
} }