diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ceab9f2..65f648c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,9 +8,9 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 1.8 + - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 11 - name: Build with Gradle run: ./gradlew build diff --git a/CHANGELOG.md b/CHANGELOG.md index 158d5c6..1bab58f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # 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` diff --git a/bot/build.gradle b/bot/build.gradle index 46e7105..ec782bc 100644 --- a/bot/build.gradle +++ b/bot/build.gradle @@ -1,7 +1,6 @@ plugins { id 'org.jetbrains.kotlin.jvm' id "org.jetbrains.kotlin.plugin.serialization" - id "org.jetbrains.kotlin.kapt" id 'application' } @@ -11,21 +10,17 @@ project.version="$version" apply from: "publish.gradle" dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines_version" - api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version" - api "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - api "org.jetbrains.exposed:exposed-jdbc:$kotlin_exposed_version" + implementation libs.kt.stdlib + api libs.kt.coroutines + api libs.kt.serialization + api libs.jb.exposed.jdbc - api "dev.inmo:tgbotapi:$tgbotapi_version" - api "dev.inmo:sdi:$sdi_version" - api "dev.inmo:micro_utils.repos.exposed:$microutils_version" + api libs.tgbotapi + api libs.microutils.repos.exposed - api "com.github.matfax.klassindex:library:$klassindex_version" - kapt "com.github.matfax.klassindex:processor:$klassindex_version" - api "org.xerial:sqlite-jdbc:$sqlite_version" + api libs.sqlite - testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" + testImplementation libs.kt.test.junit api project(":plagubot.plugin") } @@ -34,13 +29,6 @@ application { mainClassName = 'dev.inmo.plagubot.AppKt' } -kapt { - arguments { - arg("com.github.matfax.klassindex.IndexSubclasses", "dev.inmo.plagubot.Plugin") - arg("com.github.matfax.klassindex.IndexAnnotated", "dev.inmo.sdi.SDIIncluded") - } -} - java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 diff --git a/bot/pubconf.kpsb b/bot/pubconf.kpsb deleted file mode 100644 index aac6132..0000000 --- a/bot/pubconf.kpsb +++ /dev/null @@ -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"} \ No newline at end of file diff --git a/bot/publish.gradle b/bot/publish.gradle index b2257cc..a65131e 100644 --- a/bot/publish.gradle +++ b/bot/publish.gradle @@ -1,6 +1,4 @@ apply plugin: 'maven-publish' -apply plugin: 'signing' - task javadocJar(type: Jar) { from javadoc @@ -50,17 +48,8 @@ publishing { } } - - repositories { - 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') - } - } - + repositories { + if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { maven { name = "sonatype" 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') } } - } - + } } } } -signing { - useGpgCmd() - sign publishing.publications +if (project.hasProperty("signing.gnupg.keyName")) { + apply plugin: 'signing' + + signing { + useGpgCmd() + + sign publishing.publications + } + + task signAll { + tasks.withType(Sign).forEach { + dependsOn(it) + } + } } diff --git a/bot/publish.kpsb b/bot/publish.kpsb new file mode 100644 index 0000000..b1382ea --- /dev/null +++ b/bot/publish.kpsb @@ -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"} \ No newline at end of file diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/App.kt b/bot/src/main/kotlin/dev/inmo/plagubot/App.kt index 133d990..bfca750 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/App.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/App.kt @@ -3,6 +3,7 @@ package dev.inmo.plagubot import dev.inmo.plagubot.config.* import kotlinx.coroutines.* import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.json.jsonObject import java.io.File /** @@ -12,7 +13,8 @@ import java.io.File suspend fun main(args: Array) { val (configPath) = args val file = File(configPath) - val config = configAndPluginsConfigJsonFormat.decodeFromString(PluginsConfigurationSerializer, file.readText()) as Config + val json = defaultJsonFormat.parseToJsonElement(file.readText()).jsonObject + val config = defaultJsonFormat.decodeFromJsonElement(Config.serializer(), json) - PlaguBot(config).start().join() + PlaguBot(json, config).start().join() } diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt b/bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt index 1b66d96..ed82f6e 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/HelloPlugin.kt @@ -1,16 +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 kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable +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") -data class HelloPlugin( - val parameter: String -) : Plugin { - override suspend fun BehaviourContext.invoke(database: Database) { - println(parameter) +class HelloPlugin : Plugin { + @Serializable + data class HelloPluginConfig( + val print: String + ) + + override fun Module.setupDI(database: Database, params: JsonObject) { + single { + get().decodeFromJsonElement(HelloPluginConfig.serializer(), params["helloPlugin"] ?: return@single null) + } + } + + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { + println(koin.get().print) + println(getMe()) + onCommand("hello_world") { + reply(it, "Hello :)") + } } } diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt b/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt index 5cfa092..f698aa2 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/PlaguBot.kt @@ -1,55 +1,98 @@ package dev.inmo.plagubot -import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.plagubot.config.* -import dev.inmo.tgbotapi.bot.Ktor.telegramBot -import dev.inmo.tgbotapi.extensions.api.bot.setMyCommands +import dev.inmo.tgbotapi.bot.ktor.telegramBot +import dev.inmo.tgbotapi.extensions.api.webhook.deleteWebhook import dev.inmo.tgbotapi.extensions.behaviour_builder.* -import dev.inmo.tgbotapi.types.BotCommand -import dev.inmo.tgbotapi.types.botCommandsLimit +import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling import kotlinx.coroutines.* import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +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.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 Map.plagubot - get() = get(DefaultPlaguBotParamsKey) as? PlaguBot +val Scope.plagubot: PlaguBot + get() = get() @Serializable data class PlaguBot( - @Serializable(PluginsConfigurationSerializer::class) + private val json: JsonObject, private val config: Config ) : Plugin { + @Transient + private val logger = Logger.getLogger("PlaguBot") @Transient 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 = config.plugins.flatMap { - it.getCommands() + override fun Module.setupDI(database: Database, params: JsonObject) { + single { config } + single { config.plugins } + single { config.databaseConfig } + single { config.databaseConfig.database } + single { defaultJsonFormat } + single { this@PlaguBot } + + 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) { + override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { 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 */ suspend fun start( - scope: CoroutineScope = CoroutineScope(Dispatchers.Default) - ): Job = bot.buildBehaviourWithLongPolling(scope) { - invoke(database, paramsMap) + scope: CoroutineScope = CoroutineScope(Dispatchers.IO) + ): Job { + 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") + } } } diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt b/bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt deleted file mode 100644 index a30a362..0000000 --- a/bot/src/main/kotlin/dev/inmo/plagubot/PluginsHolder.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.inmo.plagubot - -import dev.inmo.plagubot.config.PluginsConfigurationSerializer -import dev.inmo.plagubot.config.SimplePluginsConfiguration -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.types.BotCommand -import kotlinx.serialization.Serializable -import org.jetbrains.exposed.sql.Database - -@Serializable -data class PluginsHolder( - @Serializable(PluginsConfigurationSerializer::class) - private val pluginsConfiguration: SimplePluginsConfiguration -) : Plugin { - override suspend fun getCommands(): List = pluginsConfiguration.plugins.flatMap { - it.getCommands() - } - - override suspend fun BehaviourContext.invoke(database: Database, params: Map) { - val finalParams = pluginsConfiguration.params ?.plus(params) ?: params - pluginsConfiguration.plugins.forEach { - it.apply { invoke(database, finalParams) } - } - } -} \ No newline at end of file diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt index d4b0238..5135e5b 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/Config.kt @@ -1,15 +1,13 @@ package dev.inmo.plagubot.config import dev.inmo.plagubot.Plugin -import dev.inmo.sdi.Module -import kotlinx.serialization.Contextual +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class Config( - override val plugins: List<@Contextual Plugin>, - val database: DatabaseConfig = DatabaseConfig(), val botToken: String, - @Contextual - override val params: Module? = null -) : PluginsConfiguration + val plugins: List, + @SerialName("database") + val databaseConfig: DatabaseConfig = DatabaseConfig(), +) diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/DatabaseConfig.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/DatabaseConfig.kt index 86750c1..7d38b13 100644 --- a/bot/src/main/kotlin/dev/inmo/plagubot/config/DatabaseConfig.kt +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/DatabaseConfig.kt @@ -1,19 +1,17 @@ package dev.inmo.plagubot.config -import dev.inmo.sdi.SDIIncluded import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.transactions.transactionManager +import org.koin.core.scope.Scope import org.sqlite.JDBC import java.sql.Connection -const val defaultDatabaseParamsName = "defaultDatabase" -inline val Map.database: Database? - get() = (get(defaultDatabaseParamsName) as? DatabaseConfig) ?.database +inline val Scope.database: Database? + get() = getOrNull() @Serializable -@SDIIncluded data class DatabaseConfig( val url: String = "jdbc:sqlite:file:test?mode=memory&cache=shared", val driver: String = JDBC::class.qualifiedName!!, diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt new file mode 100644 index 0000000..4202346 --- /dev/null +++ b/bot/src/main/kotlin/dev/inmo/plagubot/config/JsonFormat.kt @@ -0,0 +1,7 @@ +package dev.inmo.plagubot.config + +import kotlinx.serialization.json.Json + +val defaultJsonFormat = Json { + ignoreUnknownKeys = true +} diff --git a/bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt b/bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt deleted file mode 100644 index c110de2..0000000 --- a/bot/src/main/kotlin/dev/inmo/plagubot/config/PluginsConfiguration.kt +++ /dev/null @@ -1,190 +0,0 @@ -package dev.inmo.plagubot.config - -import com.github.matfax.klassindex.KlassIndex -import dev.inmo.plagubot.Plugin -import dev.inmo.plagubot.PluginSerializer -import dev.inmo.sdi.Module -import dev.inmo.sdi.ModuleSerializer -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 KClass.includeIn(builder: PolymorphicModuleBuilder) = builder.subclass(this, serializer()) - -@InternalSerializationApi -internal val configAndPluginsConfigJsonFormat: 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] - } - } - } - } - } - -internal interface PluginsConfiguration { - val plugins: List - val params: Module? -} - -@Serializable -data class SimplePluginsConfiguration( - override val plugins: List<@Contextual Plugin>, - @Contextual - override val params: Module? = null -) : PluginsConfiguration - - -internal val DefaultModuleSerializer = ModuleSerializer(emptyList()) { - -} - -@Serializer(Plugin::class) -internal class InternalPluginSerializer( - private val params: Module -) : KSerializer { - override val descriptor: SerialDescriptor = PluginSerializer.descriptor - - @OptIn(InternalSerializationApi::class) - 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 ?: configAndPluginsConfigJsonFormat) - jsonFormat.decodeFromJsonElement(PluginSerializer, asJson) - } - } - - @OptIn(InternalSerializationApi::class) - override fun serialize(encoder: Encoder, value: Plugin) { - params.keys.firstOrNull { params[it] === value } ?.also { - encoder.encodeString(it) - } ?: PluginSerializer.serialize(encoder, value) - } -} - -@Serializer(Module::class) -internal class InternalModuleSerializer( - private val original: JsonElement?, - private val params: Module -) : KSerializer { - override val descriptor: SerialDescriptor = PluginSerializer.descriptor - - @OptIn(InternalSerializationApi::class) - override fun deserialize(decoder: Decoder): Module { - val asJson = JsonElement.serializer().deserialize(decoder) - - return if (asJson == original) { - params - } else { - configAndPluginsConfigJsonFormat.decodeFromJsonElement(DefaultModuleSerializer, asJson) - } - } - - @OptIn(InternalSerializationApi::class) - override fun serialize(encoder: Encoder, value: Module) = DefaultModuleSerializer.serialize(encoder, value) -} - -internal fun internalPluginSerializerSerializersModule( - internalPluginSerializer: InternalPluginSerializer, - internalModuleSerializer: InternalModuleSerializer? -) = SerializersModule { - contextual(internalPluginSerializer) - contextual(internalModuleSerializer ?: return@SerializersModule) -} - -@Serializer(PluginsConfiguration::class) -internal object PluginsConfigurationSerializer : KSerializer { - private val jsonSerializer = JsonObject.serializer() - private val moduleSerializer = ModuleSerializer() - override val descriptor: SerialDescriptor = jsonSerializer.descriptor - - @OptIn(InternalSerializationApi::class) - override fun deserialize(decoder: Decoder): PluginsConfiguration { - val json = jsonSerializer.deserialize(decoder) - val jsonFormat = (decoder as? JsonDecoder) ?.json ?: configAndPluginsConfigJsonFormat - 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 try { - resultJsonFormat.decodeFromJsonElement( - Config.serializer(), - json - ) - } catch (e: SerializationException) { - resultJsonFormat.decodeFromJsonElement( - SimplePluginsConfiguration.serializer(), - json - ) - } - } - - @OptIn(InternalSerializationApi::class) - override fun serialize(encoder: Encoder, value: PluginsConfiguration) { - val params = value.params - val serializer = when (value) { - is Config -> Config.serializer() - is SimplePluginsConfiguration -> SimplePluginsConfiguration.serializer() - else -> return - } - if (params != null) { - val pluginsSerializer = InternalPluginSerializer(params) - - val jsonFormat = Json(configAndPluginsConfigJsonFormat) { - serializersModule = encoder.serializersModule.overwriteWith( - internalPluginSerializerSerializersModule(pluginsSerializer, null) - ) - } - - jsonSerializer.serialize( - encoder, - when (value) { - is Config -> jsonFormat.encodeToJsonElement(Config.serializer(), value) - is SimplePluginsConfiguration -> jsonFormat.encodeToJsonElement(SimplePluginsConfiguration.serializer(), value) - else -> return - } as JsonObject - ) - } else { - when (value) { - is Config -> Config.serializer().serialize(encoder, value) - is SimplePluginsConfiguration -> SimplePluginsConfiguration.serializer().serialize(encoder, value) - else -> return - } - } - } -} diff --git a/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt b/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt deleted file mode 100644 index 3cd4fe9..0000000 --- a/bot/src/test/kotlin/dev/inmo/plagubot/config/ConfigTest.kt +++ /dev/null @@ -1,40 +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 = configAndPluginsConfigJsonFormat.decodeFromString(PluginsConfigurationSerializer, rawConfig) as Config - - assert(config.plugins.size == 1) - assert(config.plugins.first() is HelloPlugin) - assert((config.plugins.first() as HelloPlugin).parameter == "Example") - - val redecoded = configAndPluginsConfigJsonFormat.decodeFromString( - PluginsConfigurationSerializer, - configAndPluginsConfigJsonFormat.encodeToString(PluginsConfigurationSerializer, config) - ) as Config - assertEquals(config.database, redecoded.database) - assertEquals(config.plugins, redecoded.plugins) - assertEquals(config.botToken, redecoded.botToken) - assertEquals(config.params ?.toMap(), redecoded.params ?.toMap()) - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4d09d52..208b3a2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,20 +1,19 @@ buildscript { repositories { - jcenter() + mavenCentral() maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath "com.github.breadmoirai:github-release:$github_release_plugin_version" + classpath libs.buildscript.kt.gradle + classpath libs.buildscript.kt.serialization + classpath libs.buildscript.gh.release } } allprojects { repositories { mavenCentral() - jcenter() mavenLocal() maven { url 'https://jitpack.io' } } diff --git a/gradle.properties b/gradle.properties index d2314ab..5707ff5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,19 +4,5 @@ org.gradle.parallel=true kotlin.js.generate.externals=true kotlin.incremental=true -kotlin_version=1.6.10 -kotlin_coroutines_version=1.6.0 -kotlin_serialisation_runtime_version=1.3.2 -kotlin_exposed_version=0.37.2 - -sdi_version=0.6.0 -tgbotapi_version=0.38.4 -microutils_version=0.9.5 -klassindex_version=4.1.0-rc.1 - -sqlite_version=3.36.0.3 - -github_release_plugin_version=2.2.12 - group=dev.inmo -version=0.5.1 +version=1.0.0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..48b2b7f --- /dev/null +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index e750102..aa991fc 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/plugin/build.gradle b/plugin/build.gradle index 3fcd11e..7e75844 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -9,11 +9,13 @@ project.version="$version" apply from: "publish.gradle" dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version" + implementation libs.kt.stdlib + api libs.kt.serialization - api "dev.inmo:tgbotapi:$tgbotapi_version" - api "dev.inmo:micro_utils.repos.exposed:$microutils_version" + api libs.tgbotapi + api libs.microutils.repos.exposed + + api libs.koin } java { diff --git a/plugin/pubconf.kpsb b/plugin/pubconf.kpsb deleted file mode 100644 index 433c2f0..0000000 --- a/plugin/pubconf.kpsb +++ /dev/null @@ -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"} \ No newline at end of file diff --git a/plugin/publish.gradle b/plugin/publish.gradle index 096e732..ed05a9d 100644 --- a/plugin/publish.gradle +++ b/plugin/publish.gradle @@ -1,6 +1,4 @@ apply plugin: 'maven-publish' -apply plugin: 'signing' - task javadocJar(type: Jar) { from javadoc @@ -50,17 +48,8 @@ publishing { } } - - repositories { - 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') - } - } - + repositories { + if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { maven { name = "sonatype" 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') } } - } - + } } } } -signing { - useGpgCmd() - sign publishing.publications +if (project.hasProperty("signing.gnupg.keyName")) { + apply plugin: 'signing' + + signing { + useGpgCmd() + + sign publishing.publications + } + + task signAll { + tasks.withType(Sign).forEach { + dependsOn(it) + } + } } diff --git a/plugin/publish.kpsb b/plugin/publish.kpsb new file mode 100644 index 0000000..40f786b --- /dev/null +++ b/plugin/publish.kpsb @@ -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"} \ No newline at end of file diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt index 4f1ece8..87cfdee 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/Plugin.kt @@ -1,14 +1,15 @@ package dev.inmo.plagubot -import dev.inmo.tgbotapi.bot.TelegramBot 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.json.JsonObject 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 * to set up short name for your plugin. Besides, simple name of your class will be used as key for deserialization * too. @@ -16,32 +17,13 @@ import org.jetbrains.exposed.sql.Database @Serializable(PluginSerializer::class) interface Plugin { /** - * In case you want to publish some processed by your plugin commands, you can provide it via this method - * - * @see BotCommand + * This method will be called when this plugin should configure di module based on the incoming params */ - suspend fun getCommands(): List = emptyList() - - @Deprecated("Override other method with receiver BehaviourContext") - suspend operator fun invoke( - bot: TelegramBot, + fun Module.setupDI( database: Database, - updatesFilter: FlowsUpdatesFilter, - scope: CoroutineScope + params: JsonObject + ) {} + 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 - ) = invoke(database) } diff --git a/plugin/src/main/kotlin/dev/inmo/plagubot/PluginSerializer.kt b/plugin/src/main/kotlin/dev/inmo/plagubot/PluginSerializer.kt index aa2b617..ad2252d 100644 --- a/plugin/src/main/kotlin/dev/inmo/plagubot/PluginSerializer.kt +++ b/plugin/src/main/kotlin/dev/inmo/plagubot/PluginSerializer.kt @@ -1,53 +1,24 @@ 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.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import kotlinx.serialization.json.* - -private val defaultJson = Json { - ignoreUnknownKeys = true -} @Serializer(Plugin::class) -object PluginSerializer : KSerializer { - private val polymorphic = PolymorphicSerializer(Plugin::class) - override val descriptor: SerialDescriptor = JsonObject.serializer().descriptor +class PluginSerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = String.serializer().descriptor - @OptIn(InternalSerializationApi::class) override fun deserialize(decoder: Decoder): Plugin { - val format = (decoder as? JsonDecoder) ?.json ?: defaultJson - 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, - JsonObject(jsonObject.toMutableMap().also { it.remove("type") }) - ) - } else { - format.decodeFromJsonElement( - polymorphic, - asJson - ) - } + return Class.forName(decoder.decodeString()).getDeclaredConstructor().newInstance() as Plugin } - @OptIn(InternalSerializationApi::class) override fun serialize(encoder: Encoder, value: Plugin) { - val serializer = (value::class.serializerOrNull() ?: polymorphic) as KSerializer - serializer.serialize(encoder, value) + encoder.encodeString( + value::class.java.canonicalName + ) } } diff --git a/template.config.json b/template.config.json index d360991..0d12e3e 100644 --- a/template.config.json +++ b/template.config.json @@ -8,9 +8,9 @@ }, "botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678", "plugins": [ - { - "type": "Hello", - "parameter": "Example" - } - ] -} \ No newline at end of file + "dev.inmo.plagubot.HelloPlugin" + ], + "helloPlugin": { + "print": "Hello World" + } +}