From cc9962287622f6061da099439d6c089dcaa97774 Mon Sep 17 00:00:00 2001 From: Jey Date: Tue, 20 Aug 2024 20:55:13 +0300 Subject: [PATCH] 7.2.0 (#221) * Added new plugin for a more convenient installation of the library. * Moved from `ChainStateManager` to `StatefulLink` flow, since it's more convenient. --- .github/workflows/ci.build.yml | 44 +++- .github/workflows/ci.gh.snap.yml | 26 ++- .github/workflows/ci.release.yml | 21 +- CHANGELOG.md | 5 + README.md | 58 ++++-- build.gradle.kts | 2 +- buildSrc/src/main/kotlin/publish.gradle.kts | 23 +- gradle.properties | 5 +- gradle/libs.versions.toml | 7 +- helper/build.gradle.kts | 14 +- .../kotlin/eu/vendeli/ksp/ApiProcessor.kt | 2 - ksp/build.gradle.kts | 12 +- .../eu/vendeli/ksp/ActivityCollectors.kt | 52 ++--- .../eu/vendeli/ksp/ActivityProcessor.kt | 34 ++- .../kotlin/eu/vendeli/ksp/BotCtxCollector.kt | 196 ++++++++---------- .../vendeli/ksp/ChainStateBindingsBuilder.kt | 83 ++++++++ .../eu/vendeli/ksp/InputChainCollector.kt | 151 +++++++------- ...ocessorCtxData.kt => CollectorsContext.kt} | 6 +- .../eu/vendeli/ksp/utils/HelperUtils.kt | 7 +- ktgram-botctx-redis/build.gradle.kts | 14 +- .../botctx/redis/RedisStateManagerImpl.kt | 30 --- .../redis/chain/BaseRedisLinkStateManager.kt | 12 ++ .../redis/chain/RedisBaseStatefulLink.kt | 14 ++ .../redis/chain/RedisLinkStateManager.kt | 39 ++++ .../redis/{ => ctx}/ClassDataRedisImpl.kt | 2 +- .../redis/{ => ctx}/UserDataRedisImpl.kt | 2 +- ktgram-config-env/build.gradle.kts | 10 +- ktgram-config-toml/build.gradle.kts | 8 +- ktgram-gradle-plugin/build.gradle.kts | 34 +++ .../eu/vendeli/ktgram/gradle/KtGramExt.kt | 16 ++ .../eu/vendeli/ktgram/gradle/KtGramPlugin.kt | 92 ++++++++ .../src/main/resources/ktgram.properties | 1 + ktgram-utils/build.gradle.kts | 6 +- ktor-starter/build.gradle.kts | 14 +- settings.gradle.kts | 4 + spring-ktgram-starter/build.gradle.kts | 10 +- telegram-bot/api/telegram-bot.api | 127 ++++-------- telegram-bot/build.gradle.kts | 66 +++--- .../implementations/ChainStateManagerImpl.kt | 20 -- .../tgbot/interfaces/ctx/ChainStateManager.kt | 12 -- .../tgbot/types/internal/ChainState.kt | 33 --- .../tgbot/types/internal/InputChain.kt | 16 +- .../internal/chain/BaseLinkStateManager.kt | 18 ++ .../types/internal/chain/BaseStatefulLink.kt | 3 + .../tgbot/types/internal/chain/Link.kt | 18 ++ .../types/internal/chain/LinkStateManager.kt | 11 + .../types/internal/chain/StatefulLink.kt | 12 ++ .../vendeli/tgbot/utils/CommonExtenstions.kt | 10 +- .../eu/vendeli/api/HandlerMechanicsTest.kt | 19 ++ .../api/botactions/SetMyActionsTest.kt | 1 + .../api/chat/ChatInviteLinkMethodsTest.kt | 7 +- .../eu/vendeli/api/chat/ChatSetMethodsTest.kt | 11 +- .../eu/vendeli/fixtures/Conversation.kt | 16 +- .../vendeli/fixtures/GeneratedActivities.kt | 25 +-- webapps/build.gradle.kts | 8 +- 55 files changed, 900 insertions(+), 589 deletions(-) create mode 100644 ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ChainStateBindingsBuilder.kt rename ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/{ProcessorCtxData.kt => CollectorsContext.kt} (61%) delete mode 100644 ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/RedisStateManagerImpl.kt create mode 100644 ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/BaseRedisLinkStateManager.kt create mode 100644 ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisBaseStatefulLink.kt create mode 100644 ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisLinkStateManager.kt rename ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/{ => ctx}/ClassDataRedisImpl.kt (97%) rename ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/{ => ctx}/UserDataRedisImpl.kt (96%) create mode 100644 ktgram-gradle-plugin/build.gradle.kts create mode 100644 ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramExt.kt create mode 100644 ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramPlugin.kt create mode 100644 ktgram-gradle-plugin/src/main/resources/ktgram.properties delete mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/implementations/ChainStateManagerImpl.kt delete mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/interfaces/ctx/ChainStateManager.kt delete mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/ChainState.kt create mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseLinkStateManager.kt create mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseStatefulLink.kt create mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/Link.kt create mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/LinkStateManager.kt create mode 100644 telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/StatefulLink.kt create mode 100644 telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/HandlerMechanicsTest.kt diff --git a/.github/workflows/ci.build.yml b/.github/workflows/ci.build.yml index 03d47d257c..4a3df03615 100644 --- a/.github/workflows/ci.build.yml +++ b/.github/workflows/ci.build.yml @@ -4,7 +4,39 @@ on: push: branches: - master + paths-ignore: + - '.editorconfig' + - '.gitignore' + - 'CHANGELOG.md' + - 'CODE_OF_CONDUCT.md' + - 'CONTRIBUTING.md' + - 'FUNDING.yml' + - 'LICENSE' + - 'README.md' + - 'SECURITY.md' + - 'gradlew' + - 'gradlew.bat' + - 'parse_changelog.sh' + - 'renovate.json' + - '.github/**' + - 'assets/**' pull_request: + paths-ignore: + - '.editorconfig' + - '.gitignore' + - 'CHANGELOG.md' + - 'CODE_OF_CONDUCT.md' + - 'CONTRIBUTING.md' + - 'FUNDING.yml' + - 'LICENSE' + - 'README.md' + - 'SECURITY.md' + - 'gradlew' + - 'gradlew.bat' + - 'parse_changelog.sh' + - 'renovate.json' + - '.github/**' + - 'assets/**' permissions: contents: write @@ -22,11 +54,19 @@ jobs: java-version: 17 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v3-beta + uses: gradle/actions/setup-gradle@v4 with: gradle-version: current dependency-graph: generate-and-submit + - name: Cache konan dependencies + uses: actions/cache@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Build artifact env: TELEGRAM_ID: ${{ secrets.TELEGRAM_ID }} @@ -35,7 +75,7 @@ jobs: BOT_TOKEN: ${{ secrets.BOT_TOKEN }} BOT_TOKEN_2: ${{ secrets.BOT_TOKEN_2 }} CHANNEL_ID: ${{ secrets.CHANNEL_ID }} - run: ./gradlew clean koverXmlReport + run: ./gradlew koverXmlReport - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/ci.gh.snap.yml b/.github/workflows/ci.gh.snap.yml index d966de3dfa..fec604cbc7 100644 --- a/.github/workflows/ci.gh.snap.yml +++ b/.github/workflows/ci.gh.snap.yml @@ -3,6 +3,7 @@ name: GH Packages Autorelease on: push: paths: + - 'buildSrc/**' - 'helper/**' - 'telegram-bot/**' - 'ksp/**' @@ -17,7 +18,20 @@ on: jobs: publish: runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref_type == 'branch' + if: github.event_name == 'push' && github.ref_type == 'branch' && !startsWith(github.ref, 'refs/heads/renovate/') + strategy: + matrix: + module: [ + ":telegram-bot", + ":ksp", + ":webapps", + ":ktgram-utils", + ":ktgram-botctx-redis", + ":ktgram-config-env", + ":ktgram-config-toml", + ":ktor-starter", + ":spring-ktgram-starter" + ] steps: - uses: actions/checkout@v4 @@ -40,7 +54,15 @@ jobs: with: gradle-version: current + - name: Cache konan dependencies + uses: actions/cache@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Publish to Gh env: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} - run: ./gradlew publishAllPublicationsToGHPackagesRepository -PlibVersion=${{ env.git_branch }}-${{ env.date }}~${{ env.git_hash }} \ No newline at end of file + run: ./gradlew ${{ matrix.module }}:publishAllPublicationsToGHPackagesRepository -PlibVersion=${{ env.git_branch }}-${{ env.date }}~${{ env.git_hash }} --no-parallel \ No newline at end of file diff --git a/.github/workflows/ci.release.yml b/.github/workflows/ci.release.yml index 6c0cb8a9e1..a908897742 100644 --- a/.github/workflows/ci.release.yml +++ b/.github/workflows/ci.release.yml @@ -34,10 +34,18 @@ jobs: java-version: 17 - name: Setup gradle - uses: gradle/actions/setup-gradle@v3-beta + uses: gradle/actions/setup-gradle@v4 with: gradle-version: current + - name: Cache konan dependencies + uses: actions/cache@v4 + with: + path: ~/.konan + key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + - name: Prepare to publish run: | echo '${{secrets.GPG_KEY_CONTENTS}}' | base64 -d > publish_key.gpg @@ -56,8 +64,13 @@ jobs: BRANCH: gh-pages FOLDER: telegram-bot/build/dokka + - run: ./gradlew publishPlugins -PlibVersion=${{ needs.version.outputs.version }} + env: + GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} + GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} + readme: - needs: publish + needs: [ version, publish ] name: Replace version in README.md runs-on: ubuntu-latest permissions: @@ -68,8 +81,8 @@ jobs: ref: ${{ github.head_ref }} - uses: jacobtomlinson/gha-find-replace@v3 with: - find: '[0-9]+.[0-9]+.[0-9]+\"\)' - replace: "${{ needs.version.outputs.version }}\")" + find: '[:"]([0-9]+\.[0-9]+\.[0-9]+)\"' + replace: "${{ needs.version.outputs.version }}" include: "**README.md" - name: Push changes uses: stefanzweifel/git-auto-commit-action@v5 diff --git a/CHANGELOG.md b/CHANGELOG.md index e6049b172f..1003e028f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Telegram-bot (KtGram) changelog +## 7.2.0 + +* Added new plugin for a more convenient installation of the library. +* Moved from `ChainStateManager` to `StatefulLink` flow, since it's more convenient. + ## 7.1.0 * Cover Telegram API [7.9](https://core.telegram.org/bots/api#august-14-2024) version. diff --git a/README.md b/README.md index dc976d3dbc..3cf8afb286 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,22 @@ Telegram Bot Api wrapper with a user-friendly interface. # Installation -Add the ksp plugin and library to the dependencies. +Add the ksp plugin and library plugin to your Gradle build file. build.gradle.kts example: +```gradle +plugins { + // ... + id("com.google.devtools.ksp") version "2.0.10-1.0.24" + id("eu.vendeli.telegram-bot") version "7.1.0" +} +``` + +
+Manually +To set up the project without using the plugin, you need to add a dependency and configure the ksp processor: + ```gradle plugins { // ... @@ -30,12 +42,27 @@ dependencies { } ``` +For multiplatform, you need to add the dependency to common sources and define ksp for the targets you need, see example +in [native-example](https://github.com/ktgram/native-example/blob/master/build.gradle.kts). +
+ +
Snapshots [![Snapshot version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fv229149.hosted-by-vdsina.ru%2Fsnap-ver%2Ftelegram-bot&query=%24%5B0%5D.name&logo=github&label=SNAPSHOT&link=https%3A%2F%2Fgithub.com%2Fvendelieu%3Ftab%3Dpackages%26repo_name%3Dtelegram-bot)](https://github.com/vendelieu?tab=packages&repo_name=telegram-bot) -To install snapshot versions add dev repository: +To install snapshot versions, add snapshot repository, +if you're using plugin just use `addSnapshotRepo` parameter: + +```gradle +ktGram { + forceVersion = "branch-xxxxxx~xxxxxx" + addSnapshotRepo = true +} +``` + +or manually add repository: ```gradle repositories { @@ -45,8 +72,8 @@ repositories { } ``` -And use the latest package version from [packages](https://github.com/vendelieu?tab=packages&repo_name=telegram-bot) or -from badge above. +And add library dependency (with ksp processor) as described in `manually` section using the latest package version +from [packages](https://github.com/vendelieu?tab=packages&repo_name=telegram-bot) or from badge above.
@@ -128,7 +155,7 @@ fun main() = runBlocking { ### Configuration -The library has very flexible customization options, \, +The library has very flexible customization options, \, and there are different options to configure through external sources. You can read more in a [Bot configuration](https://github.com/vendelieu/telegram-bot/wiki/Bot-configuration) article. @@ -136,12 +163,15 @@ You can read more in a [Bot configuration](https://github.com/vendelieu/telegram ### Processing responses To process over response or/and have more control over request flow -use [`sendReturning()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.interfaces/-action/send-returning.html) +use [ +`sendReturning()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.interfaces.action/-action/send-returning.html) instead -of [`send()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.interfaces/-action/send.html) +of [ +`send()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.interfaces.action/-action/send.html) method, which -returns [`Response`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/-response/index.html): +returns [ +`Response`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/-response/index.html): ```kotlin message { "test" }.sendReturning(user, bot).onFailure { @@ -150,11 +180,15 @@ message { "test" }.sendReturning(user, bot).onFailure { ``` Any `sendReturning` method returns -a [`Response`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/-response/index.html) +a [ +`Response`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/-response/index.html) on which you can also use -methods [`getOrNull()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/get-or-null.html) -, [`isSuccess()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/is-success.html) -, [`onFailure()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/on-failure.html) +methods [ +`getOrNull()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/get-or-null.html) +, [ +`isSuccess()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/is-success.html) +, [ +`onFailure()`](https://vendelieu.github.io/telegram-bot/-telegram%20-bot/eu.vendeli.tgbot.types.internal/on-failure.html) ### Additional resources diff --git a/build.gradle.kts b/build.gradle.kts index 45162c5c0d..04a0e98277 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,9 +13,9 @@ plugins { } tasks.create("prepareRelease") { - dependsOn("ksp:formatKotlin") dependsOn("telegram-bot:kdocUpdate") dependsOn("telegram-bot:formatKotlin") + dependsOn("ktgram-gradle-plugin:formatKotlin") dependsOn("helper:formatKotlin") dependsOn("ksp:detekt") diff --git a/buildSrc/src/main/kotlin/publish.gradle.kts b/buildSrc/src/main/kotlin/publish.gradle.kts index 4def0c7efc..617be9ec99 100644 --- a/buildSrc/src/main/kotlin/publish.gradle.kts +++ b/buildSrc/src/main/kotlin/publish.gradle.kts @@ -1,4 +1,5 @@ import com.vanniktech.maven.publish.JavadocJar +import com.vanniktech.maven.publish.KotlinJvm import com.vanniktech.maven.publish.KotlinMultiplatform import com.vanniktech.maven.publish.SonatypeHost @@ -6,22 +7,24 @@ plugins { id("com.vanniktech.maven.publish") } -apply(plugin = "org.jetbrains.kotlin.multiplatform") val libraryData = extensions.create("libraryData", PublishingExtension::class) -val doSign = providers.gradleProperty("signing.keyId").isPresent +val releaseMode = providers.gradleProperty("signing.keyId").isPresent +val isMultiplatform = !project.plugins.hasPlugin("org.jetbrains.kotlin.jvm") + +if (isMultiplatform) apply(plugin = "org.jetbrains.kotlin.multiplatform") mavenPublishing { coordinates("eu.vendeli", project.name, project.version.toString()) publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true) - if (doSign) signAllPublications() + if (releaseMode) signAllPublications() + + val javaDoc = if (project.name == "telegram-bot" && releaseMode) JavadocJar.Dokka("dokkaHtml") + else JavadocJar.Empty() + + val platformArtifact = if (isMultiplatform) KotlinMultiplatform(javaDoc, true) + else KotlinJvm(JavadocJar.None(), true) - configure( - KotlinMultiplatform( - javadocJar = if (project.name == "telegram-bot" && doSign) JavadocJar.Dokka("dokkaHtml") - else JavadocJar.Empty(), - sourcesJar = true, - ), - ) + configure(platformArtifact) pom { name = libraryData.name diff --git a/gradle.properties b/gradle.properties index 26d3bc0ee4..26e2f0742b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,6 @@ -org.gradle.jvmargs=-Xmx4096M -XX:MaxMetaspaceSize=1024m +org.gradle.jvmargs=-Xmx1g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8 org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache-problems=warn +org.gradle.configuration-cache=true kotlin.incremental=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6734d3c6f9..1b2d2f416d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,7 @@ ktor = "2.3.12" redis = "6.4.0.RELEASE" logging = "2.0.4" -logback = "1.5.6" +logback = "1.5.7" datetime = "0.6.0" serialization = "1.7.1" @@ -27,6 +27,7 @@ poet = "1.18.1" binvalid = "0.16.3" publisher = "0.29.0" +gradle-publish = "1.2.1" env = "1.0.4" [libraries] @@ -62,6 +63,7 @@ ksp = { module = "com.google.devtools.ksp:symbol-processing-api", version.ref = poet = { module = "com.squareup:kotlinpoet", version.ref = "poet" } poet-ksp = { module = "com.squareup:kotlinpoet-ksp", version.ref = "poet" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } ssl-utils = { module = "io.github.hakky54:sslcontext-kickstart-for-pem", version.ref = "sslcontext" } spring-starter = { module = "org.springframework.boot:spring-boot-starter", version.ref = "spring" } @@ -71,12 +73,11 @@ redis = { module = "io.lettuce:lettuce-core", version.ref = "redis" } env = { module = "dev.scottpierce:kotlin-env-var", version.ref = "env" } toml = { module = "net.peanuuutz.tomlkt:tomlkt", version.ref = "toml" } - [plugins] -kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-js-plain-objects = { id = "org.jetbrains.kotlin.plugin.js-plain-objects", version.ref = "kotlin" } kotlin-compatability-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binvalid" } +gradle-publish = { id = "com.gradle.plugin-publish", version.ref = "gradle-publish"} ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } diff --git a/helper/build.gradle.kts b/helper/build.gradle.kts index 9ea6672d26..dfcc745ee5 100644 --- a/helper/build.gradle.kts +++ b/helper/build.gradle.kts @@ -6,14 +6,12 @@ plugins { onlyJvmConfiguredKotlin { sourceSets { - jvmMain { - dependencies { - implementation(libs.kotlin.serialization) - implementation(libs.ksp) - implementation(libs.poet) - implementation(libs.poet.ksp) - implementation(project(":telegram-bot")) - } + jvmMain.dependencies { + implementation(libs.kotlin.serialization) + implementation(libs.ksp) + implementation(libs.poet) + implementation(libs.poet.ksp) + implementation(project(":telegram-bot")) } } } diff --git a/helper/src/jvmMain/kotlin/eu/vendeli/ksp/ApiProcessor.kt b/helper/src/jvmMain/kotlin/eu/vendeli/ksp/ApiProcessor.kt index b82199705e..400d62f0b0 100644 --- a/helper/src/jvmMain/kotlin/eu/vendeli/ksp/ApiProcessor.kt +++ b/helper/src/jvmMain/kotlin/eu/vendeli/ksp/ApiProcessor.kt @@ -2,7 +2,6 @@ package eu.vendeli.ksp -import com.google.devtools.ksp.KspExperimental import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.processing.SymbolProcessor @@ -31,7 +30,6 @@ class ApiProcessor( private val utilsDir = options["utilsDir"]!! private val apiFile = options["apiFile"]!! - @OptIn(KspExperimental::class) override fun process(resolver: Resolver): List { val fileSpec = FileSpec.builder("eu.vendeli.ktgram.extutils", "TelegramBotSc") val apiMash = resolver.getSymbolsWithAnnotation(TgAPI::class.fullName) diff --git a/ksp/build.gradle.kts b/ksp/build.gradle.kts index 2a3b9ca572..94898b5ea7 100644 --- a/ksp/build.gradle.kts +++ b/ksp/build.gradle.kts @@ -11,13 +11,11 @@ libraryData { onlyJvmConfiguredKotlin { sourceSets { - jvmMain { - dependencies { - implementation(libs.ksp) - implementation(libs.poet) - implementation(libs.poet.ksp) - implementation(project(":telegram-bot")) - } + jvmMain.dependencies { + implementation(libs.ksp) + implementation(libs.poet) + implementation(libs.poet.ksp) + implementation(project(":telegram-bot")) } } } diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityCollectors.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityCollectors.kt index fc4a72660b..34c3b80e52 100644 --- a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityCollectors.kt +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityCollectors.kt @@ -11,9 +11,8 @@ import com.squareup.kotlinpoet.STRING import com.squareup.kotlinpoet.TypeVariableName import com.squareup.kotlinpoet.asTypeName import com.squareup.kotlinpoet.buildCodeBlock +import eu.vendeli.ksp.dto.CollectorsContext import eu.vendeli.ksp.dto.CommonAnnotationData -import eu.vendeli.ksp.dto.ProcessorCtxData -import eu.vendeli.ksp.utils.FileBuilder import eu.vendeli.ksp.utils.addMap import eu.vendeli.ksp.utils.commonMatcherClass import eu.vendeli.ksp.utils.invocableType @@ -27,13 +26,12 @@ import eu.vendeli.tgbot.annotations.InputHandler import eu.vendeli.tgbot.annotations.UpdateHandler import eu.vendeli.tgbot.types.internal.UpdateType -internal fun FileBuilder.collectCommandActivities( +internal fun collectCommandActivities( symbols: Sequence, - ctx: ProcessorCtxData, - pkg: String? = null, + ctx: CollectorsContext, ) = ctx.run { logger.info("Collecting commands.") - addMap( + activitiesFile.addMap( "__TG_COMMANDS$idxPostfix", MAP.parameterizedBy( Pair::class.asTypeName().parameterizedBy(STRING, UpdateType::class.asTypeName()), @@ -64,7 +62,7 @@ internal fun FileBuilder.collectCommandActivities( addStatement( "(\"$it\" to %L) to (%L to InvocationMeta(\"%L\", \"%L\", %L, %L::class)),", updT, - buildInvocationLambdaCodeBlock(function, injectableTypes, pkg), + activitiesFile.buildInvocationLambdaCodeBlock(function, injectableTypes, pkg), function.qualifiedName!!.getQualifier(), function.simpleName.asString(), annotationData.rateLimits.toRateLimits(), @@ -75,16 +73,15 @@ internal fun FileBuilder.collectCommandActivities( } } -internal fun FileBuilder.collectInputActivities( +internal fun collectInputActivities( symbols: Sequence, chainSymbols: Sequence, - ctx: ProcessorCtxData, - pkg: String? = null, + ctx: CollectorsContext, ) = ctx.run { logger.info("Collecting inputs.") - val tailBlock = collectInputChains(chainSymbols, logger, pkg) + val tailBlock = collectInputChains(chainSymbols, ctx) - addMap( + activitiesFile.addMap( "__TG_INPUTS$idxPostfix", MAP.parameterizedBy(STRING, invocableType), symbols, @@ -99,7 +96,7 @@ internal fun FileBuilder.collectInputActivities( logger.info("Input: $it --> ${function.qualifiedName?.asString()}") addStatement( "\"$it\" to (%L to InvocationMeta(\"%L\", \"%L\", %L, %L::class)),", - buildInvocationLambdaCodeBlock(function, injectableTypes, pkg), + activitiesFile.buildInvocationLambdaCodeBlock(function, injectableTypes, pkg), function.qualifiedName!!.getQualifier(), function.simpleName.asString(), annotationData.second.toRateLimits(), @@ -109,12 +106,12 @@ internal fun FileBuilder.collectInputActivities( } } -internal fun FileBuilder.collectUpdateTypeActivities( +internal fun collectUpdateTypeActivities( symbols: Sequence, - ctx: ProcessorCtxData, + ctx: CollectorsContext, ) = ctx.run { logger.info("Collecting `UpdateType` handlers.") - addMap( + activitiesFile.addMap( "__TG_UPDATE_TYPES$idxPostfix", MAP.parameterizedBy(UpdateType::class.asTypeName(), TypeVariableName("InvocationLambda")), symbols, @@ -130,19 +127,18 @@ internal fun FileBuilder.collectUpdateTypeActivities( addStatement( "%L to %L,", it, - buildInvocationLambdaCodeBlock(function, injectableTypes), + activitiesFile.buildInvocationLambdaCodeBlock(function, injectableTypes), ) } } } -internal fun FileBuilder.collectCommonActivities( +internal fun collectCommonActivities( data: List, - ctx: ProcessorCtxData, - pkg: String? = null, + ctx: CollectorsContext, ) = ctx.run { logger.info("Collecting common handlers.") - addProperty( + activitiesFile.addProperty( PropertySpec .builder( "__TG_COMMONS$idxPostfix", @@ -158,7 +154,11 @@ internal fun FileBuilder.collectCommonActivities( addStatement( "%L to (%L to InvocationMeta(\"%L\", \"%L\", %L)),", it.value.toCommonMatcher(it.filter, it.scope), - buildInvocationLambdaCodeBlock(it.funDeclaration, injectableTypes, pkg), + activitiesFile.buildInvocationLambdaCodeBlock( + it.funDeclaration, + injectableTypes, + pkg, + ), it.funQualifier, it.funSimpleName, it.rateLimits.let { l -> @@ -175,11 +175,11 @@ internal fun FileBuilder.collectCommonActivities( ) } -internal fun FileBuilder.collectUnprocessed( +internal fun collectUnprocessed( unprocessedHandlerSymbols: KSFunctionDeclaration?, - ctx: ProcessorCtxData, + ctx: CollectorsContext, ) = ctx.run { - addProperty( + activitiesFile.addProperty( PropertySpec .builder( "__TG_UNPROCESSED$idxPostfix", @@ -192,7 +192,7 @@ internal fun FileBuilder.collectUnprocessed( "%L", unprocessedHandlerSymbols?.let { logger.info("Unprocessed handler --> ${it.qualifiedName?.asString()}") - buildInvocationLambdaCodeBlock(it, injectableTypes) + activitiesFile.buildInvocationLambdaCodeBlock(it, injectableTypes) }, ) }, diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityProcessor.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityProcessor.kt index 17295cb0fb..6b44b1aae3 100644 --- a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityProcessor.kt +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ActivityProcessor.kt @@ -20,7 +20,7 @@ import com.squareup.kotlinpoet.TypeVariableName import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toTypeName import com.squareup.kotlinpoet.ksp.writeTo -import eu.vendeli.ksp.dto.ProcessorCtxData +import eu.vendeli.ksp.dto.CollectorsContext import eu.vendeli.ksp.utils.CommonAnnotationHandler import eu.vendeli.ksp.utils.FileBuilder import eu.vendeli.ksp.utils.activitiesType @@ -58,6 +58,7 @@ class ActivityProcessor( } targetPackage?.forEachIndexed { idx, pkg -> + if (pkg.isBlank()) logger.error("Defined package #$idx is blank.") processPackage(fileSpec, resolver, idx to pkg) } ?: processPackage(fileSpec, resolver) @@ -93,11 +94,13 @@ class ActivityProcessor( return emptyList() } - private fun processPackage(fileSpec: FileSpec.Builder, resolver: Resolver, target: Pair? = null) { + private fun processPackage(fileSpec: FileBuilder, resolver: Resolver, target: Pair? = null) { val pkg = target?.second val idxPostfix = target?.first?.let { "$it" } ?: "0" + val filePkg = pkg ?: "eu.vendeli.tgbot.generated" - processCtxProviders(codeGenerator, resolver, pkg) + val botCtxSpec = FileSpec.builder(filePkg, "BotCtx") + processCtxProviders(botCtxSpec, resolver, pkg) val commandHandlerSymbols = resolver.getAnnotatedFnSymbols(pkg, CommandHandler::class, CallbackQuery::class) val inputHandlerSymbols = resolver.getAnnotatedFnSymbols(pkg, InputHandler::class) @@ -133,18 +136,27 @@ class ActivityProcessor( .toTypeName() to c.toClassName() } - val classRefPkg = pkg ?: "eu.vendeli.tgbot.generated" - val collectorsData = ProcessorCtxData( + val context = CollectorsContext( + activitiesFile = fileSpec, + botCtxFile = botCtxSpec, injectableTypes = injectableTypes, logger = logger, idxPostfix = idxPostfix, + pkg = filePkg, ) - fileSpec.apply { - collectCommandActivities(commandHandlerSymbols, collectorsData, classRefPkg) - collectInputActivities(inputHandlerSymbols, inputChainSymbols, collectorsData, classRefPkg) - collectCommonActivities(commonHandlerData, collectorsData, classRefPkg) - collectUpdateTypeActivities(updateHandlerSymbols, collectorsData) - collectUnprocessed(unprocessedHandlerSymbol, collectorsData) + + collectCommandActivities(commandHandlerSymbols, context) + collectInputActivities(inputHandlerSymbols, inputChainSymbols, context) + collectCommonActivities(commonHandlerData, context) + collectUpdateTypeActivities(updateHandlerSymbols, context) + collectUnprocessed(unprocessedHandlerSymbol, context) + + @Suppress("SpreadOperator") + botCtxSpec.build().runCatching { + writeTo( + codeGenerator = codeGenerator, + dependencies = Dependencies(false, *resolver.getAllFiles().toList().toTypedArray()), + ) } } diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/BotCtxCollector.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/BotCtxCollector.kt index 7ebfb2d68c..c80fcc6549 100644 --- a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/BotCtxCollector.kt +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/BotCtxCollector.kt @@ -1,18 +1,15 @@ package eu.vendeli.ksp import com.google.devtools.ksp.getAllSuperTypes -import com.google.devtools.ksp.processing.CodeGenerator -import com.google.devtools.ksp.processing.Dependencies import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.ClassKind -import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.LONG import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.STRING import com.squareup.kotlinpoet.ksp.toClassName -import com.squareup.kotlinpoet.ksp.writeTo +import eu.vendeli.ksp.utils.FileBuilder import eu.vendeli.ksp.utils.botClass import eu.vendeli.ksp.utils.classDataCtx import eu.vendeli.ksp.utils.classDataCtxDef @@ -24,117 +21,104 @@ import eu.vendeli.tgbot.annotations.CtxProvider import eu.vendeli.tgbot.annotations.internal.InternalApi internal fun processCtxProviders( - codeGenerator: CodeGenerator, + fileSpec: FileBuilder, resolver: Resolver, pkg: String? = null, -) { - val filePkg = pkg ?: "eu.vendeli.tgbot.generated" - FileSpec - .builder( - filePkg, - "BotCtx", - ).apply { - val providers = resolver.getAnnotatedClassSymbols(CtxProvider::class, pkg) - var userDataType = STRING +) = fileSpec.apply { + val providers = resolver.getAnnotatedClassSymbols(CtxProvider::class, pkg) + var userDataType = STRING - val userDataCtxClass = providers - .firstOrNull { c -> - c.getAllSuperTypes().firstOrNull { it.toClassName() == userDataCtx }?.also { - userDataType = it.arguments - .first() - .type - ?.resolve() - ?.toClassName() ?: STRING - } != null - }?.takeIf { - it.classKind == ClassKind.CLASS - }?.toClassName() ?: userDataCtxDef + val userDataCtxClass = providers + .firstOrNull { c -> + c.getAllSuperTypes().firstOrNull { it.toClassName() == userDataCtx }?.also { + userDataType = it.arguments + .first() + .type + ?.resolve() + ?.toClassName() ?: STRING + } != null + }?.takeIf { + it.classKind == ClassKind.CLASS + }?.toClassName() ?: userDataCtxDef - val classDataCtxClass = providers - .firstOrNull { c -> - c.getAllSuperTypes().any { it.toClassName() == classDataCtx } - }?.takeIf { - it.classKind == ClassKind.CLASS - }?.toClassName() ?: classDataCtxDef + val classDataCtxClass = providers + .firstOrNull { c -> + c.getAllSuperTypes().any { it.toClassName() == classDataCtx } + }?.takeIf { + it.classKind == ClassKind.CLASS + }?.toClassName() ?: classDataCtxDef - addProperty( - PropertySpec - .builder( - "_userData", - userDataCtxClass, - KModifier.PRIVATE, - ).apply { - initializer(userDataCtxClass.reflectionName() + "()") - }.build(), - ) + addProperty( + PropertySpec + .builder( + "_userData", + userDataCtxClass, + KModifier.PRIVATE, + ).apply { + initializer(userDataCtxClass.reflectionName() + "()") + }.build(), + ) - addProperty( - PropertySpec - .builder( - "_classData", - classDataCtxClass, - KModifier.PRIVATE, - ).apply { - initializer(classDataCtxClass.reflectionName() + "()") - }.build(), - ) + addProperty( + PropertySpec + .builder( + "_classData", + classDataCtxClass, + KModifier.PRIVATE, + ).apply { + initializer(classDataCtxClass.reflectionName() + "()") + }.build(), + ) - addFunction( - FunSpec - .builder("____clearClassData") - .addModifiers(KModifier.SUSPEND) - .addParameter("tgId", LONG) - .addAnnotation(InternalApi::class) - .addCode("return _classData.clearAll(tgId)") - .build(), - ) + addFunction( + FunSpec + .builder("____clearClassData") + .addModifiers(KModifier.SUSPEND) + .addParameter("tgId", LONG) + .addAnnotation(InternalApi::class) + .addCode("return _classData.clearAll(tgId)") + .build(), + ) - addProperty( - PropertySpec - .builder( - "userData", - userDataCtxClass, - ).receiver(botClass) - .getter(FunSpec.getterBuilder().addCode("return _userData").build()) - .build(), - ) + addProperty( + PropertySpec + .builder( + "userData", + userDataCtxClass, + ).receiver(botClass) + .getter(FunSpec.getterBuilder().addCode("return _userData").build()) + .build(), + ) - addProperty( - PropertySpec - .builder( - "classData", - classDataCtxClass, - ).receiver(botClass) - .getter(FunSpec.getterBuilder().addCode("return _classData").build()) - .build(), - ) + addProperty( + PropertySpec + .builder( + "classData", + classDataCtxClass, + ).receiver(botClass) + .getter(FunSpec.getterBuilder().addCode("return _classData").build()) + .build(), + ) - addFunction( - FunSpec - .builder("get") - .addModifiers(KModifier.OPERATOR) - .receiver(userClass) - .addParameter("key", STRING) - .returns(userDataType.copy(true)) - .addCode("return _userData[id, key]") - .build(), - ) + addFunction( + FunSpec + .builder("get") + .addModifiers(KModifier.OPERATOR) + .receiver(userClass) + .addParameter("key", STRING) + .returns(userDataType.copy(true)) + .addCode("return _userData[id, key]") + .build(), + ) - addFunction( - FunSpec - .builder("set") - .addModifiers(KModifier.OPERATOR) - .receiver(userClass) - .addParameter("key", STRING) - .addParameter("value", userDataType) - .addCode("return _userData.set(id, key, value)") - .build(), - ) - }.build() - .runCatching { - writeTo( - codeGenerator = codeGenerator, - dependencies = Dependencies(false, *resolver.getAllFiles().toList().toTypedArray()), - ) - } + addFunction( + FunSpec + .builder("set") + .addModifiers(KModifier.OPERATOR) + .receiver(userClass) + .addParameter("key", STRING) + .addParameter("value", userDataType) + .addCode("return _userData.set(id, key, value)") + .build(), + ) } diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ChainStateBindingsBuilder.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ChainStateBindingsBuilder.kt new file mode 100644 index 0000000000..4ef692cd12 --- /dev/null +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/ChainStateBindingsBuilder.kt @@ -0,0 +1,83 @@ +package eu.vendeli.ksp + +import com.google.devtools.ksp.getDeclaredFunctions +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toTypeName +import eu.vendeli.ksp.utils.FileBuilder +import eu.vendeli.ksp.utils.userClass + +fun buildChainStateBindings( + botCtxBuilder: FileBuilder, + chainDeclaration: KSClassDeclaration, + statefulLinks: HashSet, + logger: KSPLogger, +) = botCtxBuilder.run { + val providerName = "${chainDeclaration.simpleName.getShortName()}AllState" + val chainType = chainDeclaration.toClassName() + addImport("kotlinx.coroutines", "runBlocking") + + val user = FunSpec + .constructorBuilder() + .addParameter("user", userClass) + .build() + + val stateBindingClass = TypeSpec + .classBuilder(providerName) + .primaryConstructor(user) + .addProperty( + PropertySpec + .builder("user", userClass) + .initializer("user") + .addModifiers(KModifier.PRIVATE) + .build(), + ) + + statefulLinks.forEach { l -> + val linkName = l.simpleName.getShortName() + val linkQName = l.qualifiedName!!.asString() + + if (l.classKind != ClassKind.OBJECT) { + logger.warn("$linkQName is not an object, so `getAllState` binding is skipped.") + return@forEach + } + + val linkType = l + .getDeclaredFunctions() + .first { + it.simpleName.getShortName() == "action" + }.returnType!! + .toTypeName() + .copy(true) + + stateBindingClass.addProperty( + PropertySpec + .builder(linkName, linkType) + .getter( + FunSpec + .getterBuilder() + .addStatement("return runBlocking {\n\t $linkQName.state.get(user) \n\t}") + .build(), + ).build(), + ) + } + + addFunction( + FunSpec + .builder("getAllState") + .receiver(userClass) + .returns(ClassName(packageName, providerName)) + .addParameter("chain", chainType) + .addCode("return $providerName(this)") + .build(), + ) + + addType(stateBindingClass.build()) +} diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/InputChainCollector.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/InputChainCollector.kt index d7c3063781..a653ee62f6 100644 --- a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/InputChainCollector.kt +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/InputChainCollector.kt @@ -1,92 +1,97 @@ package eu.vendeli.ksp import com.google.devtools.ksp.getAllSuperTypes -import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.KSClassDeclaration import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.buildCodeBlock -import com.squareup.kotlinpoet.ksp.toClassName -import com.squareup.kotlinpoet.ksp.toTypeName -import eu.vendeli.ksp.utils.FileBuilder +import eu.vendeli.ksp.dto.CollectorsContext import eu.vendeli.ksp.utils.cast -import eu.vendeli.ksp.utils.chainLinkClass -import eu.vendeli.ksp.utils.stateManager +import eu.vendeli.ksp.utils.linkQName import eu.vendeli.tgbot.implementations.DefaultGuard +import eu.vendeli.tgbot.types.internal.chain.StatefulLink -internal fun FileBuilder.collectInputChains( +internal fun collectInputChains( symbols: Sequence, - logger: KSPLogger, - pkg: String? = null, -): CodeBlock? = buildCodeBlock { - if (!symbols.iterator().hasNext()) { - logger.info("No input chains were found.") - return null - } - symbols.forEach { chain -> - val links = chain.declarations - .filter { i -> - i is KSClassDeclaration && i.getAllSuperTypes().any { it.toTypeName() == chainLinkClass } - }.toList() - .cast>() + ctx: CollectorsContext, +): CodeBlock? = ctx.run { + buildCodeBlock { + if (!symbols.iterator().hasNext()) { + logger.info("No input chains were found.") + return null + } + symbols.forEach { chain -> + val links = chain.declarations + .filter { i -> + i is KSClassDeclaration && + i.getAllSuperTypes().any { + it.declaration.qualifiedName?.asString() == linkQName + } + }.toList() + .cast>() - val isStateManagerChain = chain.getAllSuperTypes().firstOrNull { - it.toClassName() == stateManager - } != null + val statefulLinks = chain.declarations + .filter { d -> + d is KSClassDeclaration && + d + .getAllSuperTypes() + .find { it.declaration.simpleName.asString() == StatefulLink::class.simpleName } != null + }.cast>() + .toHashSet() - if (isStateManagerChain) { - addImport("eu.vendeli.tgbot.types.internal", "StoredState") - } + if (statefulLinks.isNotEmpty()) buildChainStateBindings(ctx.botCtxFile, chain, statefulLinks, logger) - links.asSequence().forEachIndexed { idx, link -> - val curLinkId = link.qualifiedName!!.asString() - val qualifier = link.qualifiedName!!.getQualifier() - val name = link.simpleName.asString() - val reference = "$qualifier.$name" + links.asSequence().forEachIndexed { idx, link -> + val curLinkId = link.qualifiedName!!.asString() + val qualifier = link.qualifiedName!!.getQualifier() + val name = link.simpleName.asString() + val reference = "$qualifier.$name" - val nextLink = if (idx < links.lastIndex) { - links.getOrNull(idx + 1) - } else { - null - }?.qualifiedName?.asString() + val nextLink = if (idx < links.lastIndex) { + links.getOrNull(idx + 1) + } else { + null + }?.qualifiedName?.asString() + val isStatefulLink = statefulLinks.contains(link) - val block = buildCodeBlock { - indent() - beginControlFlow("suspendCall { classManager, update, user, bot, parameters ->") - .apply { - add("if(user == null) return@suspendCall Unit\n") - add("val inst = classManager.getInstance($reference::class) as $reference\n") - add("inst.beforeAction?.invoke(user, update, bot)\n") - add("val nextLink: String? = %P\n", nextLink) - add("val breakPoint = inst.breakCondition?.invoke(user, update, bot) ?: false\n") - add( - "if (breakPoint && inst.retryAfterBreak){\nbot.inputListener[user] = \"%L\"\n}\n", - curLinkId, - ) - add("if (breakPoint) {\ninst.breakAction(user, update, bot)\nreturn@suspendCall Unit\n}\n") - if (isStateManagerChain) { - add("val chainInst = classManager.getInstance($qualifier::class) as $qualifier\n") - if (idx == 0) add("chainInst.clearAllState(user, \"$qualifier\")\n") - add("chainInst.setState(user, \"$reference\", StoredState(update, inst.stateSelector))\n") - } - if (pkg != null) add( - "if (\n\tbot.update.userClassSteps[user.id] != %S\n) %L.____clearClassData(user.id)\n", - qualifier, - pkg, - ) - add("inst.action(user, update, bot)\n") - add("if (nextLink != null) bot.inputListener[user] = nextLink\n") - add("inst.afterAction?.invoke(user, update, bot)\n") - }.endControlFlow() - } + val block = buildCodeBlock { + indent() + beginControlFlow("suspendCall { classManager, update, user, bot, parameters ->") + .apply { + add("if(user == null) return@suspendCall Unit\n") + add("val inst = classManager.getInstance($reference::class) as $reference\n") + add("inst.beforeAction?.invoke(user, update, bot)\n") + add("val nextLink: String? = %P\n", nextLink) + add("val breakPoint = inst.breakCondition?.invoke(user, update, bot) ?: false\n") + add( + "if (breakPoint && inst.retryAfterBreak){\nbot.inputListener[user] = \"%L\"\n}\n", + curLinkId, + ) + add("if (breakPoint) {\ninst.breakAction(user, update, bot)\nreturn@suspendCall Unit\n}\n") + if (pkg != null) add( + "if (\n\tbot.update.userClassSteps[user.id] != %S\n) %L.____clearClassData(user.id)\n", + qualifier, + pkg, + ) + if (isStatefulLink) { + add("val linkState = inst.action(user, update, bot)\n") + add("inst.state.set(user, linkState)\n") + } else { + add("inst.action(user, update, bot)\n") + } + add("if (nextLink != null) bot.inputListener[user] = nextLink\n") + add("inst.afterAction?.invoke(user, update, bot)\n") + }.endControlFlow() + } - add( - "\"$curLinkId\" to (%L to InvocationMeta(\"%L\", \"%L\", %L, %L::class)),\n", - block, - qualifier, - name, - "zeroRateLimits", - DefaultGuard::class.qualifiedName, - ) + add( + "\"$curLinkId\" to (%L to InvocationMeta(\"%L\", \"%L\", %L, %L::class)),\n", + block, + qualifier, + name, + "zeroRateLimits", + DefaultGuard::class.qualifiedName, + ) + } } } } diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/ProcessorCtxData.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/CollectorsContext.kt similarity index 61% rename from ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/ProcessorCtxData.kt rename to ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/CollectorsContext.kt index 963ee75cf8..8370a21992 100644 --- a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/ProcessorCtxData.kt +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/dto/CollectorsContext.kt @@ -3,9 +3,13 @@ package eu.vendeli.ksp.dto import com.google.devtools.ksp.processing.KSPLogger import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.TypeName +import eu.vendeli.ksp.utils.FileBuilder -data class ProcessorCtxData( +data class CollectorsContext( + val activitiesFile: FileBuilder, + val botCtxFile: FileBuilder, val injectableTypes: Map, val logger: KSPLogger, val idxPostfix: String, + val pkg: String? = null, ) diff --git a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/utils/HelperUtils.kt b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/utils/HelperUtils.kt index 7816a5e865..5299959edb 100644 --- a/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/utils/HelperUtils.kt +++ b/ksp/src/jvmMain/kotlin/eu/vendeli/ksp/utils/HelperUtils.kt @@ -19,7 +19,6 @@ import com.squareup.kotlinpoet.asTypeName import eu.vendeli.tgbot.TelegramBot import eu.vendeli.tgbot.implementations.ClassDataImpl import eu.vendeli.tgbot.implementations.UserDataMapImpl -import eu.vendeli.tgbot.interfaces.ctx.ChainStateManager import eu.vendeli.tgbot.interfaces.ctx.ClassData import eu.vendeli.tgbot.interfaces.ctx.UserData import eu.vendeli.tgbot.interfaces.marker.Autowiring @@ -27,7 +26,6 @@ import eu.vendeli.tgbot.types.User import eu.vendeli.tgbot.types.internal.BusinessConnectionUpdate import eu.vendeli.tgbot.types.internal.BusinessMessageUpdate import eu.vendeli.tgbot.types.internal.CallbackQueryUpdate -import eu.vendeli.tgbot.types.internal.ChainLink import eu.vendeli.tgbot.types.internal.ChannelPostUpdate import eu.vendeli.tgbot.types.internal.ChatBoostUpdate import eu.vendeli.tgbot.types.internal.ChatJoinRequestUpdate @@ -50,7 +48,9 @@ import eu.vendeli.tgbot.types.internal.ProcessedUpdate import eu.vendeli.tgbot.types.internal.RemovedChatBoostUpdate import eu.vendeli.tgbot.types.internal.ShippingQueryUpdate import eu.vendeli.tgbot.types.internal.UpdateType +import eu.vendeli.tgbot.types.internal.chain.Link import eu.vendeli.tgbot.types.internal.configuration.RateLimits +import eu.vendeli.tgbot.utils.fullName import kotlin.reflect.KClass internal typealias FileBuilder = FileSpec.Builder @@ -60,7 +60,7 @@ internal val activitiesType = Map::class.asTypeName().parameterizedBy( List::class.asTypeName().parameterizedBy(ANY.copy(true)), ) internal val invocableType = TypeVariableName("Invocable") -internal val chainLinkClass = ChainLink::class.asTypeName() +internal val linkQName = Link::class.fullName internal val autoWiringClassName = Autowiring::class.asClassName() internal val rateLimitsClass = RateLimits::class.asTypeName() @@ -103,7 +103,6 @@ internal val userDataCtx = UserData::class.asTypeName() internal val userDataCtxDef = UserDataMapImpl::class.asTypeName() internal val classDataCtx = ClassData::class.asTypeName() internal val classDataCtxDef = ClassDataImpl::class.asTypeName() -internal val stateManager = ChainStateManager::class.asTypeName() internal val callbackQueryList = listOf(UpdateType.CALLBACK_QUERY) internal val messageList = listOf(UpdateType.MESSAGE) diff --git a/ktgram-botctx-redis/build.gradle.kts b/ktgram-botctx-redis/build.gradle.kts index ea1fb982fc..5ab7d2f44b 100644 --- a/ktgram-botctx-redis/build.gradle.kts +++ b/ktgram-botctx-redis/build.gradle.kts @@ -4,16 +4,12 @@ plugins { onlyJvmConfiguredKotlin { sourceSets { - commonMain { - dependencies { - implementation(project(":telegram-bot")) - implementation(libs.kotlin.serialization) - } + commonMain.dependencies { + implementation(project(":telegram-bot")) + implementation(libs.kotlin.serialization) } - jvmMain { - dependencies { - implementation(libs.redis) - } + jvmMain.dependencies { + implementation(libs.redis) } } } diff --git a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/RedisStateManagerImpl.kt b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/RedisStateManagerImpl.kt deleted file mode 100644 index 0e2dfce80d..0000000000 --- a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/RedisStateManagerImpl.kt +++ /dev/null @@ -1,30 +0,0 @@ -package eu.vendeli.ktgram.botctx.redis - -import eu.vendeli.tgbot.interfaces.ctx.ChainStateManager -import eu.vendeli.tgbot.types.User -import eu.vendeli.tgbot.types.internal.StoredState -import io.lettuce.core.RedisClient -import io.lettuce.core.api.reactive.RedisReactiveCommands -import kotlinx.serialization.json.Json - -abstract class RedisStateManagerImpl( - url: String = "redis://localhost", -) : ChainStateManager { - open val redis: RedisReactiveCommands = RedisClient.create(url).connect().reactive() - private val storedStateSerde = StoredState.serializer() - - override suspend fun getState(user: User, link: String): StoredState? = - redis.get("chainState-${user.id}-$link").block()?.let { - Json.decodeFromString(storedStateSerde, it) - } - - override suspend fun setState(user: User, link: String, value: StoredState) { - redis.set("chainState-${user.id}-$link", Json.encodeToString(storedStateSerde, value)) - } - - override suspend fun clearAllState(user: User, chain: String) { - redis.keys("chainState-${user.id}-$chain*").collectList().block()?.let { - redis.del(*it.toTypedArray()) - } - } -} diff --git a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/BaseRedisLinkStateManager.kt b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/BaseRedisLinkStateManager.kt new file mode 100644 index 0000000000..8642363c09 --- /dev/null +++ b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/BaseRedisLinkStateManager.kt @@ -0,0 +1,12 @@ +package eu.vendeli.ktgram.botctx.redis.chain + +import eu.vendeli.tgbot.types.internal.chain.StatefulLink +import kotlinx.serialization.json.Json +import kotlin.reflect.KClass + +open class BaseRedisLinkStateManager, T : Any>( + url: String = "redis://localhost", + storageType: KClass, + linkRef: KClass, + serializer: Json = Json, +) : RedisLinkStateManager(url, storageType, linkRef, serializer) diff --git a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisBaseStatefulLink.kt b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisBaseStatefulLink.kt new file mode 100644 index 0000000000..5d80aedf84 --- /dev/null +++ b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisBaseStatefulLink.kt @@ -0,0 +1,14 @@ +package eu.vendeli.ktgram.botctx.redis.chain + +import eu.vendeli.tgbot.types.internal.chain.LinkStateManager +import eu.vendeli.tgbot.types.internal.chain.StatefulLink +import kotlinx.serialization.json.Json +import kotlin.reflect.KClass + +abstract class RedisBaseStatefulLink>( + url: String = "redis://localhost", + linkRef: KClass, + serializer: Json, +) : StatefulLink() { + override val state: LinkStateManager = BaseRedisLinkStateManager(url, String::class, linkRef, serializer) +} diff --git a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisLinkStateManager.kt b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisLinkStateManager.kt new file mode 100644 index 0000000000..8befb60655 --- /dev/null +++ b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/chain/RedisLinkStateManager.kt @@ -0,0 +1,39 @@ +package eu.vendeli.ktgram.botctx.redis.chain + +import eu.vendeli.tgbot.types.User +import eu.vendeli.tgbot.types.internal.chain.LinkStateManager +import eu.vendeli.tgbot.types.internal.chain.StatefulLink +import eu.vendeli.tgbot.utils.fullName +import io.lettuce.core.RedisClient +import io.lettuce.core.api.reactive.RedisReactiveCommands +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.json.Json +import kotlinx.serialization.serializerOrNull +import kotlin.reflect.KClass + +abstract class RedisLinkStateManager, T : Any>( + url: String = "redis://localhost", + storageType: KClass, + private val linkRef: KClass, + private val serializer: Json = Json, +) : LinkStateManager { + open val redis: RedisReactiveCommands = RedisClient.create(url).connect().reactive() + + @OptIn(InternalSerializationApi::class) + private val storageTypeSerializer = + storageType.serializerOrNull() ?: error("Serializer for $storageType is not found") + + override suspend fun get(user: User): T? = + redis.get("linkState-${linkRef::class.fullName}-${user.id}").block()?.let { + serializer.decodeFromString(storageTypeSerializer, it) + } + + override suspend fun del(user: User) { + redis.del("linkState-${linkRef::class.fullName}-${user.id}").block() + } + + override suspend fun set(user: User, value: T) { + val stringValue = serializer.encodeToString(storageTypeSerializer, value) + redis.set("linkState-${linkRef::class.fullName}-${user.id}", stringValue).block() + } +} diff --git a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ClassDataRedisImpl.kt b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ctx/ClassDataRedisImpl.kt similarity index 97% rename from ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ClassDataRedisImpl.kt rename to ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ctx/ClassDataRedisImpl.kt index 4889f55fa9..440e7b1004 100644 --- a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ClassDataRedisImpl.kt +++ b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ctx/ClassDataRedisImpl.kt @@ -1,4 +1,4 @@ -package eu.vendeli.ktgram.botctx.redis +package eu.vendeli.ktgram.botctx.redis.ctx import eu.vendeli.tgbot.interfaces.ctx.ClassData import io.lettuce.core.RedisClient diff --git a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/UserDataRedisImpl.kt b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ctx/UserDataRedisImpl.kt similarity index 96% rename from ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/UserDataRedisImpl.kt rename to ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ctx/UserDataRedisImpl.kt index 07e6ca40bc..fd576c6650 100644 --- a/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/UserDataRedisImpl.kt +++ b/ktgram-botctx-redis/src/jvmMain/kotlin/eu/vendeli/ktgram/botctx/redis/ctx/UserDataRedisImpl.kt @@ -1,4 +1,4 @@ -package eu.vendeli.ktgram.botctx.redis +package eu.vendeli.ktgram.botctx.redis.ctx import eu.vendeli.tgbot.interfaces.ctx.UserData import io.lettuce.core.RedisClient diff --git a/ktgram-config-env/build.gradle.kts b/ktgram-config-env/build.gradle.kts index 975cebf3ae..4079222e6c 100644 --- a/ktgram-config-env/build.gradle.kts +++ b/ktgram-config-env/build.gradle.kts @@ -4,12 +4,10 @@ plugins { configuredKotlin { sourceSets { - commonMain { - dependencies { - implementation(project(":telegram-bot")) - implementation(libs.env) - implementation(libs.ktor.client.core) - } + commonMain.dependencies { + implementation(project(":telegram-bot")) + implementation(libs.env) + implementation(libs.ktor.client.core) } } } diff --git a/ktgram-config-toml/build.gradle.kts b/ktgram-config-toml/build.gradle.kts index 2cc6011d47..f1ee95ac86 100644 --- a/ktgram-config-toml/build.gradle.kts +++ b/ktgram-config-toml/build.gradle.kts @@ -7,11 +7,9 @@ onlyJvmConfiguredKotlin { linuxX64() sourceSets { - commonMain { - dependencies { - implementation(project(":telegram-bot")) - implementation(libs.toml) - } + commonMain.dependencies { + implementation(project(":telegram-bot")) + implementation(libs.toml) } } } diff --git a/ktgram-gradle-plugin/build.gradle.kts b/ktgram-gradle-plugin/build.gradle.kts new file mode 100644 index 0000000000..3ca869a79a --- /dev/null +++ b/ktgram-gradle-plugin/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + kotlin("jvm") + `kotlin-dsl` + `java-gradle-plugin` + alias(libs.plugins.ktlinter) + alias(libs.plugins.gradle.publish) +} + +gradlePlugin { + website = "https://vendeli.eu" + vcsUrl = "https://github.com/vendelieu/telegram-bot" + + plugins.register("telegram-bot") { + id = "eu.vendeli.telegram-bot" + displayName = "Telegram-bot Gradle Plugin" + description = "A plugin for customizing and adding Telegram-bot library." + @Suppress("UnstableApiUsage") + tags = listOf("kotlin", "telegram", "bot", "spring-boot", "ktor", "multiplatform") + implementationClass = "eu.vendeli.ktgram.gradle.KtGramPlugin" + } +} + +tasks.processResources { + val projectVersion = project.version + inputs.property("version", projectVersion) + filesMatching("ktgram.properties") { + expand("ktgramVer" to projectVersion) + } +} + +dependencies { + compileOnly(libs.kotlin.gradlePlugin) + compileOnly(libs.ksp.plugin) +} diff --git a/ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramExt.kt b/ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramExt.kt new file mode 100644 index 0000000000..43c854a02d --- /dev/null +++ b/ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramExt.kt @@ -0,0 +1,16 @@ +package eu.vendeli.ktgram.gradle + +import org.gradle.api.model.ObjectFactory +import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property +import javax.inject.Inject + +abstract class KtGramExt + @Inject + constructor( + factory: ObjectFactory, + ) { + val packages = factory.listProperty() + val addSnapshotRepo = factory.property() + val forceVersion = factory.property() + } diff --git a/ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramPlugin.kt b/ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramPlugin.kt new file mode 100644 index 0000000000..c0ed031669 --- /dev/null +++ b/ktgram-gradle-plugin/src/main/kotlin/eu/vendeli/ktgram/gradle/KtGramPlugin.kt @@ -0,0 +1,92 @@ +package eu.vendeli.ktgram.gradle + +import com.google.devtools.ksp.gradle.KspExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.logging.Logging +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.get +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension +import org.jetbrains.kotlin.gradle.utils.loadPropertyFromResources + +abstract class KtGramPlugin : Plugin { + private val log = Logging.getLogger(KtGramPlugin::class.java) + private val libVer = loadPropertyFromResources("ktgram.properties", "ktgram.version") + + final override fun apply(project: Project) { + val pluginExtension = project.extensions.create("ktGram", KtGramExt::class.java) + val kspPluginPresent = project.plugins.hasPlugin("com.google.devtools.ksp") + var kspProcessorApplied = false + val isMultiplatform = project.plugins.hasPlugin("org.jetbrains.kotlin.multiplatform") + + project.configurations.configureEach { + if (name.startsWith("ksp")) dependencies.whenObjectAdded { + if (group == "eu.vendeli" && name == "ksp") kspProcessorApplied = true + } + } + + project.afterEvaluate { + val targetVer = pluginExtension.forceVersion.getOrElse(libVer) + if (pluginExtension.addSnapshotRepo.getOrElse(false)) { + project.repositories.maven { + name = "KtGramSnapRepo" + setUrl("https://mvn.vendeli.eu/telegram-bot") + } + } + + project.applyDependencies(targetVer, isMultiplatform, kspProcessorApplied) + + project.extensions.configure { + pluginExtension.packages.orNull?.takeIf { it.isNotEmpty() }?.joinToString(";")?.let { + arg("package", it) + } + } + + if (!kspPluginPresent) log.error( + "For full use of `telegram-bot` KSP Gradle Plugin is needed. " + + "Add 'com.google.devtools.ksp' plugin to your build, " + + "f.e by adding 'id(\"com.google.devtools.ksp\")' to 'plugins' section of the build.gradle[.kts] file", + ) + } + } + + private fun Project.applyDependencies(depVersion: String, isMultiplatform: Boolean, kspProcessorApplied: Boolean) { + if (isMultiplatform) project.extensions.configure { + targets.forEach { target -> + val tName = if (target.targetName == "metadata") "CommonMainMetadata" + else target.targetName.replaceFirstChar { it.uppercaseChar() } + + project.dependencies.add( + "ksp$tName", + "eu.vendeli:ksp:$depVersion", + ) + } + } + + when { + kspProcessorApplied -> {} + isMultiplatform -> { + project.extensions.configure(KotlinProjectExtension::class.java) { + sourceSets["commonMain"].apply { + dependencies { + implementation("eu.vendeli:telegram-bot:$depVersion") + } + } + } + + project.dependencies { + add("ksp", "eu.vendeli:ksp:$depVersion") + } + } + + else -> { + project.dependencies.add("implementation", "eu.vendeli:telegram-bot:$depVersion") + project.dependencies { + add("ksp", "eu.vendeli:ksp:$depVersion") + } + } + } + } +} diff --git a/ktgram-gradle-plugin/src/main/resources/ktgram.properties b/ktgram-gradle-plugin/src/main/resources/ktgram.properties new file mode 100644 index 0000000000..6a0194378f --- /dev/null +++ b/ktgram-gradle-plugin/src/main/resources/ktgram.properties @@ -0,0 +1 @@ +ktgram.version=${ktgramVer} \ No newline at end of file diff --git a/ktgram-utils/build.gradle.kts b/ktgram-utils/build.gradle.kts index 81978ab992..a0f6b38072 100644 --- a/ktgram-utils/build.gradle.kts +++ b/ktgram-utils/build.gradle.kts @@ -4,10 +4,8 @@ plugins { configuredKotlin { sourceSets { - commonMain { - dependencies { - implementation(project(":telegram-bot")) - } + commonMain.dependencies { + implementation(project(":telegram-bot")) } } } diff --git a/ktor-starter/build.gradle.kts b/ktor-starter/build.gradle.kts index 3abc82a00c..374c3f906e 100644 --- a/ktor-starter/build.gradle.kts +++ b/ktor-starter/build.gradle.kts @@ -4,14 +4,12 @@ plugins { onlyJvmConfiguredKotlin { sourceSets { - jvmMain { - dependencies { - compileOnly(project(":telegram-bot")) - compileOnly(libs.ktor.client.core) - api(libs.ktor.server.core) - api(libs.ktor.server.netty) - implementation(libs.ssl.utils) - } + jvmMain.dependencies { + compileOnly(project(":telegram-bot")) + compileOnly(libs.ktor.client.core) + api(libs.ktor.server.core) + api(libs.ktor.server.netty) + implementation(libs.ssl.utils) } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 90e9789a6f..20bfa879af 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,6 @@ +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" +} rootProject.name = "ktgram" include(":telegram-bot") @@ -8,5 +11,6 @@ include(":ktgram-utils") include(":ktgram-botctx-redis") include(":ktgram-config-env") include(":ktgram-config-toml") +include(":ktgram-gradle-plugin") include(":ktor-starter") include(":spring-ktgram-starter") diff --git a/spring-ktgram-starter/build.gradle.kts b/spring-ktgram-starter/build.gradle.kts index 4a9c887df7..94cdf578bc 100644 --- a/spring-ktgram-starter/build.gradle.kts +++ b/spring-ktgram-starter/build.gradle.kts @@ -4,12 +4,10 @@ plugins { onlyJvmConfiguredKotlin { sourceSets { - jvmMain { - dependencies { - compileOnly(project(":telegram-bot")) - compileOnly(libs.ktor.client.core) - compileOnly(libs.spring.starter) - } + jvmMain.dependencies { + compileOnly(project(":telegram-bot")) + compileOnly(libs.ktor.client.core) + compileOnly(libs.spring.starter) } } } diff --git a/telegram-bot/api/telegram-bot.api b/telegram-bot/api/telegram-bot.api index 2158cc82d4..5cbe60b4ba 100644 --- a/telegram-bot/api/telegram-bot.api +++ b/telegram-bot/api/telegram-bot.api @@ -2280,14 +2280,6 @@ public abstract class eu/vendeli/tgbot/implementations/BotContextMapImpl : eu/ve public fun setAsync (JLjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public abstract class eu/vendeli/tgbot/implementations/ChainStateManagerImpl : eu/vendeli/tgbot/interfaces/ctx/ChainStateManager { - public fun ()V - public fun clearAllState (Leu/vendeli/tgbot/types/User;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun getState ()Lco/touchlab/stately/collections/ConcurrentMutableMap; - public fun getState (Leu/vendeli/tgbot/types/User;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun setState (Leu/vendeli/tgbot/types/User;Ljava/lang/String;Leu/vendeli/tgbot/types/internal/StoredState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - public final class eu/vendeli/tgbot/implementations/ClassDataImpl : eu/vendeli/tgbot/implementations/BotContextMapImpl, eu/vendeli/tgbot/interfaces/ctx/ClassData { public fun ()V public fun clearAll (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -2405,12 +2397,6 @@ public final class eu/vendeli/tgbot/interfaces/ctx/BotContext$DefaultImpls { public static fun set (Leu/vendeli/tgbot/interfaces/ctx/BotContext;Leu/vendeli/tgbot/types/User;Ljava/lang/String;Ljava/lang/Object;)V } -public abstract interface class eu/vendeli/tgbot/interfaces/ctx/ChainStateManager { - public abstract fun clearAllState (Leu/vendeli/tgbot/types/User;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun getState (Leu/vendeli/tgbot/types/User;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public abstract fun setState (Leu/vendeli/tgbot/types/User;Ljava/lang/String;Leu/vendeli/tgbot/types/internal/StoredState;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - public abstract interface class eu/vendeli/tgbot/interfaces/ctx/ClassData : eu/vendeli/tgbot/interfaces/ctx/BotContext { public abstract fun clearAll (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -7155,15 +7141,13 @@ public final class eu/vendeli/tgbot/types/internal/CallbackQueryUpdate : eu/vend public fun toString ()Ljava/lang/String; } -public abstract class eu/vendeli/tgbot/types/internal/ChainLink { +public abstract class eu/vendeli/tgbot/types/internal/ChainLink : eu/vendeli/tgbot/types/internal/chain/Link { public fun ()V - public abstract fun action (Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/TelegramBot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun breakAction (Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/TelegramBot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getAfterAction ()Leu/vendeli/tgbot/types/internal/Action; public fun getBeforeAction ()Leu/vendeli/tgbot/types/internal/Action; public fun getBreakCondition ()Leu/vendeli/tgbot/types/internal/BreakCondition; public fun getRetryAfterBreak ()Z - public fun getStateSelector ()Leu/vendeli/tgbot/types/internal/StateSelector; } public final class eu/vendeli/tgbot/types/internal/ChannelPostUpdate : eu/vendeli/tgbot/types/internal/ProcessedUpdate, eu/vendeli/tgbot/types/internal/UserReference { @@ -7936,71 +7920,6 @@ public final class eu/vendeli/tgbot/types/internal/SingleInputChain { public fun toString ()Ljava/lang/String; } -public abstract class eu/vendeli/tgbot/types/internal/StateSelector { - public static final field Companion Leu/vendeli/tgbot/types/internal/StateSelector$Companion; - public synthetic fun (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V - public abstract fun getState ()Lkotlin/jvm/functions/Function1; - public abstract fun getType ()Lkotlin/reflect/KClass; - public static final synthetic fun write$Self (Leu/vendeli/tgbot/types/internal/StateSelector;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V -} - -public final class eu/vendeli/tgbot/types/internal/StateSelector$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - -public final class eu/vendeli/tgbot/types/internal/StateSelector$Custom : eu/vendeli/tgbot/types/internal/StateSelector { - public fun (Lkotlin/jvm/functions/Function1;Lkotlin/reflect/KClass;)V - public final fun component1 ()Lkotlin/jvm/functions/Function1; - public final fun component2 ()Lkotlin/reflect/KClass; - public final fun copy (Lkotlin/jvm/functions/Function1;Lkotlin/reflect/KClass;)Leu/vendeli/tgbot/types/internal/StateSelector$Custom; - public static synthetic fun copy$default (Leu/vendeli/tgbot/types/internal/StateSelector$Custom;Lkotlin/jvm/functions/Function1;Lkotlin/reflect/KClass;ILjava/lang/Object;)Leu/vendeli/tgbot/types/internal/StateSelector$Custom; - public fun equals (Ljava/lang/Object;)Z - public fun getState ()Lkotlin/jvm/functions/Function1; - public fun getType ()Lkotlin/reflect/KClass; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class eu/vendeli/tgbot/types/internal/StateSelector$Text : eu/vendeli/tgbot/types/internal/StateSelector { - public static final field INSTANCE Leu/vendeli/tgbot/types/internal/StateSelector$Text; - public fun equals (Ljava/lang/Object;)Z - public fun getState ()Lkotlin/jvm/functions/Function1; - public fun getType ()Lkotlin/reflect/KClass; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class eu/vendeli/tgbot/types/internal/StoredState { - public static final field Companion Leu/vendeli/tgbot/types/internal/StoredState$Companion; - public fun (Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/types/internal/StateSelector;)V - public final fun component1 ()Leu/vendeli/tgbot/types/internal/ProcessedUpdate; - public final fun component2 ()Leu/vendeli/tgbot/types/internal/StateSelector; - public final fun copy (Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/types/internal/StateSelector;)Leu/vendeli/tgbot/types/internal/StoredState; - public static synthetic fun copy$default (Leu/vendeli/tgbot/types/internal/StoredState;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/types/internal/StateSelector;ILjava/lang/Object;)Leu/vendeli/tgbot/types/internal/StoredState; - public fun equals (Ljava/lang/Object;)Z - public final fun getData ()Ljava/lang/Object; - public final fun getSelector ()Leu/vendeli/tgbot/types/internal/StateSelector; - public final fun getText ()Ljava/lang/String; - public final fun getUpdate ()Leu/vendeli/tgbot/types/internal/ProcessedUpdate; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public synthetic class eu/vendeli/tgbot/types/internal/StoredState$$serializer : kotlinx/serialization/internal/GeneratedSerializer { - public static final field INSTANCE Leu/vendeli/tgbot/types/internal/StoredState$$serializer; - public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; - public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Leu/vendeli/tgbot/types/internal/StoredState; - public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; - public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; - public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Leu/vendeli/tgbot/types/internal/StoredState;)V - public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V - public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; -} - -public final class eu/vendeli/tgbot/types/internal/StoredState$Companion { - public final fun serializer ()Lkotlinx/serialization/KSerializer; -} - public abstract interface class eu/vendeli/tgbot/types/internal/TextReference { public abstract fun getText ()Ljava/lang/String; } @@ -8047,6 +7966,47 @@ public abstract interface class eu/vendeli/tgbot/types/internal/UserReference { public abstract fun getUser ()Leu/vendeli/tgbot/types/User; } +public class eu/vendeli/tgbot/types/internal/chain/BaseLinkStateManager : eu/vendeli/tgbot/types/internal/chain/LinkStateManager { + public fun ()V + public fun del (Leu/vendeli/tgbot/types/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun get (Leu/vendeli/tgbot/types/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + protected final fun getData ()Lco/touchlab/stately/collections/ConcurrentMutableMap; + public fun set (Leu/vendeli/tgbot/types/User;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract class eu/vendeli/tgbot/types/internal/chain/BaseStatefulLink : eu/vendeli/tgbot/types/internal/chain/StatefulLink { + public fun ()V +} + +public abstract interface class eu/vendeli/tgbot/types/internal/chain/Link { + public abstract fun action (Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/TelegramBot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun breakAction (Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/TelegramBot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getAfterAction ()Leu/vendeli/tgbot/types/internal/Action; + public abstract fun getBeforeAction ()Leu/vendeli/tgbot/types/internal/Action; + public abstract fun getBreakCondition ()Leu/vendeli/tgbot/types/internal/BreakCondition; + public abstract fun getRetryAfterBreak ()Z +} + +public final class eu/vendeli/tgbot/types/internal/chain/Link$DefaultImpls { + public static fun breakAction (Leu/vendeli/tgbot/types/internal/chain/Link;Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/TelegramBot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract interface class eu/vendeli/tgbot/types/internal/chain/LinkStateManager { + public abstract fun del (Leu/vendeli/tgbot/types/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun get (Leu/vendeli/tgbot/types/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun set (Leu/vendeli/tgbot/types/User;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public abstract class eu/vendeli/tgbot/types/internal/chain/StatefulLink : eu/vendeli/tgbot/types/internal/chain/Link { + public fun ()V + public fun breakAction (Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ProcessedUpdate;Leu/vendeli/tgbot/TelegramBot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getAfterAction ()Leu/vendeli/tgbot/types/internal/Action; + public fun getBeforeAction ()Leu/vendeli/tgbot/types/internal/Action; + public fun getBreakCondition ()Leu/vendeli/tgbot/types/internal/BreakCondition; + public fun getRetryAfterBreak ()Z + public fun getState ()Leu/vendeli/tgbot/types/internal/chain/LinkStateManager; +} + public final class eu/vendeli/tgbot/types/internal/configuration/BotConfiguration { public static final field Companion Leu/vendeli/tgbot/types/internal/configuration/BotConfiguration$Companion; public fun ()V @@ -13422,10 +13382,9 @@ public final class eu/vendeli/tgbot/utils/CONSTKt { public final class eu/vendeli/tgbot/utils/CommonExtenstionsKt { public static final fun checkIsInitDataSafe (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z - public static final fun getState (Leu/vendeli/tgbot/interfaces/ctx/ChainStateManager;Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ChainLink;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun runExceptionHandler (Leu/vendeli/tgbot/core/TgUpdateHandler;Lkotlinx/coroutines/CoroutineDispatcher;JLeu/vendeli/tgbot/interfaces/helper/ExceptionHandler;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun runExceptionHandler$default (Leu/vendeli/tgbot/core/TgUpdateHandler;Lkotlinx/coroutines/CoroutineDispatcher;JLeu/vendeli/tgbot/interfaces/helper/ExceptionHandler;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static final fun setChain (Leu/vendeli/tgbot/interfaces/ctx/InputListener;Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/ChainLink;)V + public static final fun setChain (Leu/vendeli/tgbot/interfaces/ctx/InputListener;Leu/vendeli/tgbot/types/User;Leu/vendeli/tgbot/types/internal/chain/Link;)V } public final class eu/vendeli/tgbot/utils/FileExtHelpersKt { diff --git a/telegram-bot/build.gradle.kts b/telegram-bot/build.gradle.kts index 1cf11e234d..a2783cfc50 100644 --- a/telegram-bot/build.gradle.kts +++ b/telegram-bot/build.gradle.kts @@ -15,53 +15,41 @@ plugins { configuredKotlin { sourceSets { - commonMain { - dependencies { - implementation(libs.kotlin.serialization) - implementation(libs.kotlin.reflect) + commonMain.dependencies { + implementation(libs.kotlin.serialization) + implementation(libs.kotlin.reflect) - implementation(libs.krypto) - implementation(libs.stately) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.logging) + implementation(libs.krypto) + implementation(libs.stately) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.logging) - api(libs.coroutines.core) - api(libs.kotlin.datetime) - } + api(libs.coroutines.core) + api(libs.kotlin.datetime) } - jvmTest { - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.8.1") - implementation(libs.logback) + jvmTest.dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:1.8.1") + implementation(libs.logback) - implementation(libs.test.kotest.junit5) - implementation(libs.test.kotest.assertions) - implementation(libs.mockk) - } + implementation(libs.test.kotest.junit5) + implementation(libs.test.kotest.assertions) + implementation(libs.mockk) } - jvmMain { - dependencies { - implementation(libs.logback) - implementation(libs.ktor.client.java) - } + jvmMain.dependencies { + implementation(libs.logback) + implementation(libs.ktor.client.java) } - jsMain { - dependencies { - implementation(libs.logging) - implementation(libs.ktor.client.js) - } + jsMain.dependencies { + implementation(libs.logging) + implementation(libs.ktor.client.js) } - named("linuxX64Main") { - dependencies { - implementation(libs.logging) - implementation(libs.ktor.client.curl) - } + named("linuxX64Main").dependencies { + implementation(libs.logging) + implementation(libs.ktor.client.curl) } - named("mingwX64Main") { - dependencies { - implementation(libs.logging) - implementation(libs.ktor.client.winhttp) - } + named("mingwX64Main").dependencies { + implementation(libs.logging) + implementation(libs.ktor.client.winhttp) } } diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/implementations/ChainStateManagerImpl.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/implementations/ChainStateManagerImpl.kt deleted file mode 100644 index 8850011a18..0000000000 --- a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/implementations/ChainStateManagerImpl.kt +++ /dev/null @@ -1,20 +0,0 @@ -package eu.vendeli.tgbot.implementations - -import co.touchlab.stately.collections.ConcurrentMutableMap -import eu.vendeli.tgbot.interfaces.ctx.ChainStateManager -import eu.vendeli.tgbot.types.User -import eu.vendeli.tgbot.types.internal.StoredState - -abstract class ChainStateManagerImpl : ChainStateManager { - val state = ConcurrentMutableMap>() - - override suspend fun getState(user: User, link: String): StoredState? = state[user]?.get(link) - - override suspend fun setState(user: User, link: String, value: StoredState): Unit = state.block { - state[user]?.put(link, value) - } - - override suspend fun clearAllState(user: User, chain: String): Unit = state.block { - state[user]?.entries?.removeAll { it.key.startsWith(chain) } - } -} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/interfaces/ctx/ChainStateManager.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/interfaces/ctx/ChainStateManager.kt deleted file mode 100644 index cfd246de6c..0000000000 --- a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/interfaces/ctx/ChainStateManager.kt +++ /dev/null @@ -1,12 +0,0 @@ -package eu.vendeli.tgbot.interfaces.ctx - -import eu.vendeli.tgbot.types.User -import eu.vendeli.tgbot.types.internal.StoredState - -interface ChainStateManager { - suspend fun getState(user: User, link: String): StoredState? - - suspend fun setState(user: User, link: String, value: StoredState) - - suspend fun clearAllState(user: User, chain: String) -} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/ChainState.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/ChainState.kt deleted file mode 100644 index 56cf2447e2..0000000000 --- a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/ChainState.kt +++ /dev/null @@ -1,33 +0,0 @@ -package eu.vendeli.tgbot.types.internal - -import kotlinx.serialization.Serializable -import kotlin.reflect.KClass - -@Serializable -sealed class StateSelector { - abstract val state: ProcessedUpdate.() -> Any? - abstract val type: KClass<*> - - data object Text : StateSelector() { - override val state: ProcessedUpdate.() -> String = { text } - override val type = String::class - } - - data class Custom( - override val state: ProcessedUpdate.() -> T?, - override val type: KClass, - ) : StateSelector() - - companion object { - inline fun Custom(noinline selector: ProcessedUpdate.() -> T?) = Custom(selector, T::class) - } -} - -@Serializable -data class StoredState( - val update: ProcessedUpdate, - val selector: StateSelector, -) { - val text get() = if (selector.type == String::class) selector.state(update) as String else "" - val data get() = selector.state(update) -} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/InputChain.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/InputChain.kt index 7b1fae2311..318521e081 100644 --- a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/InputChain.kt +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/InputChain.kt @@ -2,6 +2,7 @@ package eu.vendeli.tgbot.types.internal import eu.vendeli.tgbot.TelegramBot import eu.vendeli.tgbot.types.User +import eu.vendeli.tgbot.types.internal.chain.Link fun interface Action { suspend fun invoke(user: User, update: ProcessedUpdate, bot: TelegramBot) @@ -11,14 +12,9 @@ fun interface BreakCondition { suspend fun invoke(user: User, update: ProcessedUpdate, bot: TelegramBot): Boolean } -abstract class ChainLink { - open val retryAfterBreak = true - open val breakCondition: BreakCondition? = null - open val beforeAction: Action? = null - open val afterAction: Action? = null - open val stateSelector: StateSelector = StateSelector.Text - - abstract suspend fun action(user: User, update: ProcessedUpdate, bot: TelegramBot) - - open suspend fun breakAction(user: User, update: ProcessedUpdate, bot: TelegramBot) {} +abstract class ChainLink : Link { + override val retryAfterBreak = true + override val breakCondition: BreakCondition? = null + override val beforeAction: Action? = null + override val afterAction: Action? = null } diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseLinkStateManager.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseLinkStateManager.kt new file mode 100644 index 0000000000..4e25efc898 --- /dev/null +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseLinkStateManager.kt @@ -0,0 +1,18 @@ +package eu.vendeli.tgbot.types.internal.chain + +import co.touchlab.stately.collections.ConcurrentMutableMap +import eu.vendeli.tgbot.types.User + +open class BaseLinkStateManager : LinkStateManager { + protected val data: ConcurrentMutableMap = ConcurrentMutableMap() + + override suspend fun get(user: User): T? = data[user.id] + + override suspend fun set(user: User, value: T) { + data[user.id] = value + } + + override suspend fun del(user: User) { + data.remove(user.id) + } +} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseStatefulLink.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseStatefulLink.kt new file mode 100644 index 0000000000..3392c07e33 --- /dev/null +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/BaseStatefulLink.kt @@ -0,0 +1,3 @@ +package eu.vendeli.tgbot.types.internal.chain + +abstract class BaseStatefulLink : StatefulLink() diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/Link.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/Link.kt new file mode 100644 index 0000000000..d2e7f6005b --- /dev/null +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/Link.kt @@ -0,0 +1,18 @@ +package eu.vendeli.tgbot.types.internal.chain + +import eu.vendeli.tgbot.TelegramBot +import eu.vendeli.tgbot.types.User +import eu.vendeli.tgbot.types.internal.Action +import eu.vendeli.tgbot.types.internal.BreakCondition +import eu.vendeli.tgbot.types.internal.ProcessedUpdate + +interface Link { + val retryAfterBreak: Boolean + val breakCondition: BreakCondition? + val beforeAction: Action? + val afterAction: Action? + + suspend fun action(user: User, update: ProcessedUpdate, bot: TelegramBot): T + + suspend fun breakAction(user: User, update: ProcessedUpdate, bot: TelegramBot) {} +} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/LinkStateManager.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/LinkStateManager.kt new file mode 100644 index 0000000000..45838144ef --- /dev/null +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/LinkStateManager.kt @@ -0,0 +1,11 @@ +package eu.vendeli.tgbot.types.internal.chain + +import eu.vendeli.tgbot.types.User + +interface LinkStateManager { + suspend fun get(user: User): T? + + suspend fun set(user: User, value: T) + + suspend fun del(user: User) +} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/StatefulLink.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/StatefulLink.kt new file mode 100644 index 0000000000..e303c8d36e --- /dev/null +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/types/internal/chain/StatefulLink.kt @@ -0,0 +1,12 @@ +package eu.vendeli.tgbot.types.internal.chain + +import eu.vendeli.tgbot.types.internal.Action +import eu.vendeli.tgbot.types.internal.BreakCondition + +abstract class StatefulLink : Link { + open val state: LinkStateManager = BaseLinkStateManager() + override val retryAfterBreak = true + override val breakCondition: BreakCondition? = null + override val beforeAction: Action? = null + override val afterAction: Action? = null +} diff --git a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/utils/CommonExtenstions.kt b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/utils/CommonExtenstions.kt index 11f2e42202..a011f04f11 100644 --- a/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/utils/CommonExtenstions.kt +++ b/telegram-bot/src/commonMain/kotlin/eu/vendeli/tgbot/utils/CommonExtenstions.kt @@ -3,11 +3,10 @@ package eu.vendeli.tgbot.utils import eu.vendeli.tgbot.TelegramBot import eu.vendeli.tgbot.annotations.internal.ExperimentalFeature import eu.vendeli.tgbot.core.TgUpdateHandler -import eu.vendeli.tgbot.interfaces.ctx.ChainStateManager import eu.vendeli.tgbot.interfaces.ctx.InputListener import eu.vendeli.tgbot.interfaces.helper.ExceptionHandler import eu.vendeli.tgbot.types.User -import eu.vendeli.tgbot.types.internal.ChainLink +import eu.vendeli.tgbot.types.internal.chain.Link import eu.vendeli.tgbot.types.keyboard.InlineKeyboardMarkup import eu.vendeli.tgbot.utils.builders.inlineKeyboardMarkup import io.ktor.http.decodeURLQueryComponent @@ -52,11 +51,6 @@ suspend fun TgUpdateHandler.runExceptionHandler( } } -/** - * Get state which is stored for [link]. - */ -suspend fun ChainStateManager.getState(user: User, link: T) = getState(user, link::class.fullName) - /** * Set chain for input listening. * @@ -66,7 +60,7 @@ suspend fun ChainStateManager.getState(user: User, link: T) = ge * @param user The user for whom it will be set. * @param firstLink The First link that will be processed (it doesn't have to be the first link in the chain, feel free to set up any of). */ -fun InputListener.setChain(user: User, firstLink: T) = set(user, firstLink::class.fullName) +fun > InputListener.setChain(user: User, firstLink: T) = set(user, firstLink::class.fullName) /** * Method to get given class instance through defined ClassManager. diff --git a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/HandlerMechanicsTest.kt b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/HandlerMechanicsTest.kt new file mode 100644 index 0000000000..00ba4c8f0c --- /dev/null +++ b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/HandlerMechanicsTest.kt @@ -0,0 +1,19 @@ +package eu.vendeli.api + +import BotTestContext +import eu.vendeli.tgbot.implementations.DefaultFilter +import eu.vendeli.tgbot.implementations.DefaultGuard +import eu.vendeli.utils.MockUpdate +import io.kotest.matchers.booleans.shouldBeTrue + +class HandlerMechanicsTest : BotTestContext() { + @Test + suspend fun `check default guard test`() { + DefaultGuard.condition(null, MockUpdate.SINGLE().updates.first(), bot).shouldBeTrue() + } + + @Test + suspend fun `check default filter test`() { + DefaultFilter.match(null, MockUpdate.SINGLE().updates.first(), bot).shouldBeTrue() + } +} diff --git a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/botactions/SetMyActionsTest.kt b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/botactions/SetMyActionsTest.kt index 5b174a9bb9..755bd07181 100644 --- a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/botactions/SetMyActionsTest.kt +++ b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/botactions/SetMyActionsTest.kt @@ -88,6 +88,7 @@ class SetMyActionsTest : BotTestContext() { suspend fun `set my commands method testing`() { setMyCommands { botCommand("test", "testD") + "test2" description "testC" }.send(bot) val result = getMyCommands().sendReq().shouldSuccess() diff --git a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatInviteLinkMethodsTest.kt b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatInviteLinkMethodsTest.kt index 649fd23975..f22825a24a 100644 --- a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatInviteLinkMethodsTest.kt +++ b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatInviteLinkMethodsTest.kt @@ -46,7 +46,8 @@ class ChatInviteLinkMethodsTest : BotTestContext() { val expireUnix = CUR_INSTANT.plus(30.days) createChatSubscriptionInviteLink(10, "test") .sendReturning(CHAT_ID, bot) - .getOrNull()?.run { + .getOrNull() + ?.run { creator.id shouldBe BOT_ID isPrimary.shouldBeFalse() isRevoked.shouldBeFalse() @@ -60,7 +61,8 @@ class ChatInviteLinkMethodsTest : BotTestContext() { suspend fun `edit chat subscription invite link method test`() { editChatSubscriptionInviteLink("test", "test2") .sendReturning(CHAT_ID, bot) - .getOrNull()?.run { + .getOrNull() + ?.run { creator.id shouldBe BOT_ID isPrimary.shouldBeFalse() isRevoked.shouldBeFalse() @@ -68,6 +70,7 @@ class ChatInviteLinkMethodsTest : BotTestContext() { createsJoinRequest.shouldBeTrue() } } + @Test suspend fun `edit chat invite link method test`() { val inviteLink = createChatInviteLink() diff --git a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatSetMethodsTest.kt b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatSetMethodsTest.kt index c36ec7e3bd..17bf2be5eb 100644 --- a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatSetMethodsTest.kt +++ b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/api/chat/ChatSetMethodsTest.kt @@ -21,7 +21,10 @@ import io.kotest.matchers.booleans.shouldBeTrue import io.kotest.matchers.nulls.shouldNotBeNull import io.ktor.client.request.get import io.ktor.client.statement.readBytes +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext import utils.RandomPicResource +import java.io.File @EnabledIf(ChatTestingOnlyCondition::class) class ChatSetMethodsTest : BotTestContext() { @@ -78,10 +81,16 @@ class ChatSetMethodsTest : BotTestContext() { @Test suspend fun `set chat photo method test`() { - val file = bot.httpClient.get(RandomPicResource.current.getPicUrl(200, 200)).readBytes() + val file = bot.httpClient.get(RandomPicResource.current.getPicUrl(400, 400)).readBytes() val result = setChatPhoto(file).sendReturning(CHAT_ID, bot).getOrNull() ?: return result.shouldBeTrue() + val fileImage = withContext(Dispatchers.IO) { + File.createTempFile("test", "test2").also { it.writeBytes(file) } + } + val fileImageResult = setChatPhoto(fileImage).sendReturning(CHAT_ID, bot).getOrNull() ?: return + fileImageResult.shouldBeTrue() + deleteChatPhoto().sendReturning(CHAT_ID, bot).shouldSuccess() } diff --git a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/Conversation.kt b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/Conversation.kt index 499a8e08bb..6648e38e69 100644 --- a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/Conversation.kt +++ b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/Conversation.kt @@ -3,23 +3,22 @@ package eu.vendeli.fixtures import eu.vendeli.tgbot.TelegramBot import eu.vendeli.tgbot.annotations.InputChain import eu.vendeli.tgbot.api.message.message -import eu.vendeli.tgbot.implementations.ChainStateManagerImpl import eu.vendeli.tgbot.types.User import eu.vendeli.tgbot.types.internal.BreakCondition import eu.vendeli.tgbot.types.internal.ChainLink import eu.vendeli.tgbot.types.internal.ProcessedUpdate -import eu.vendeli.tgbot.types.internal.StateSelector +import eu.vendeli.tgbot.types.internal.chain.BaseStatefulLink @InputChain -class Conversation : ChainStateManagerImpl() { - object Name : ChainLink() { +class Conversation { + object Name : BaseStatefulLink() { override val breakCondition = BreakCondition { _, update, _ -> update.text.isEmpty() } - override suspend fun action(user: User, update: ProcessedUpdate, bot: TelegramBot) { - user["name"] = update.text - + override suspend fun action(user: User, update: ProcessedUpdate, bot: TelegramBot): String { message { "Oh, ${update.text}, nice to meet you!" }.send(user, bot) message { "How old are you?" }.send(user, bot) + + return update.text } override suspend fun breakAction(user: User, update: ProcessedUpdate, bot: TelegramBot) { @@ -32,10 +31,9 @@ class Conversation : ChainStateManagerImpl() { object Age : ChainLink() { override val breakCondition = BreakCondition { _, update, _ -> update.text.toIntOrNull() == null } override val retryAfterBreak = false - override val stateSelector = StateSelector.Custom { updateId } override suspend fun action(user: User, update: ProcessedUpdate, bot: TelegramBot) { - val name = user["name"] + val name = Name.state.get(user) message { "I'm not good at remembering, but I remembered you! You're $name and you're ${update.text} years old." }.send(user, bot) diff --git a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/GeneratedActivities.kt b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/GeneratedActivities.kt index b643b11c66..fd2db3752d 100644 --- a/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/GeneratedActivities.kt +++ b/telegram-bot/src/jvmTest/kotlin/eu/vendeli/fixtures/GeneratedActivities.kt @@ -17,7 +17,6 @@ package eu.vendeli.fixtures import eu.vendeli.tgbot.annotations.internal.InternalApi import eu.vendeli.tgbot.types.internal.CommonMatcher import eu.vendeli.tgbot.types.internal.InvocationMeta -import eu.vendeli.tgbot.types.internal.StoredState import eu.vendeli.tgbot.types.internal.UpdateType import eu.vendeli.tgbot.types.internal.configuration.RateLimits import eu.vendeli.tgbot.utils.Invocable @@ -165,20 +164,10 @@ private val __TG_INPUTS0: Map = mapOf( inst.breakAction(user, update, bot) return@suspendCall Unit } - val chainInst = classManager.getInstance(eu.vendeli.fixtures.Conversation::class) as - eu.vendeli.fixtures.Conversation - chainInst.clearAllState(user, "eu.vendeli.fixtures.Conversation") - chainInst.setState( - user, - "eu.vendeli.fixtures.Conversation.Name", - StoredState( - update, - inst.stateSelector, - ), - ) if (bot.update.userClassSteps[user.id] != "eu.vendeli.fixtures.Conversation") eu.vendeli.fixtures.____clearClassData(user.id) - inst.action(user, update, bot) + val linkState = inst.action(user, update, bot) + inst.state.set(user, linkState) if (nextLink != null) bot.inputListener[user] = nextLink inst.afterAction?.invoke(user, update, bot) } @@ -210,16 +199,6 @@ private val __TG_INPUTS0: Map = mapOf( inst.breakAction(user, update, bot) return@suspendCall Unit } - val chainInst = classManager.getInstance(eu.vendeli.fixtures.Conversation::class) as - eu.vendeli.fixtures.Conversation - chainInst.setState( - user, - "eu.vendeli.fixtures.Conversation.Age", - StoredState( - update, - inst.stateSelector, - ), - ) if (bot.update.userClassSteps[user.id] != "eu.vendeli.fixtures.Conversation") eu.vendeli.fixtures.____clearClassData(user.id) inst.action(user, update, bot) diff --git a/webapps/build.gradle.kts b/webapps/build.gradle.kts index ae7d3664e4..0591cbc90c 100644 --- a/webapps/build.gradle.kts +++ b/webapps/build.gradle.kts @@ -11,11 +11,9 @@ libraryData { configuredKotlin { sourceSets { - val commonMain by getting { - dependencies { - implementation(libs.kotlin.serialization) - implementation(project(":telegram-bot")) - } + commonMain.dependencies { + implementation(libs.kotlin.serialization) + implementation(project(":telegram-bot")) } } }