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"))
}
}
}