diff --git a/.github/workflows/packages_publishing.yml b/.github/workflows/packages_publishing.yml index 915f07af66..3047a68592 100644 --- a/.github/workflows/packages_publishing.yml +++ b/.github/workflows/packages_publishing.yml @@ -17,7 +17,7 @@ jobs: rm gradle.properties mv gradle.properties.tmp gradle.properties - name: KotlinSymbolProcessing execution - run: ./gradlew ksp + run: ./gradlew kspCommonMainKotlinMetadata - name: Build run: ./gradlew build - name: API compatibility check diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f52bd608..ed2129d7a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # TelegramBotAPI changelog +## 19.0.0 + +**THIS UPDATE CONTAINS BREAKING CHANGES** + +**THIS UPDATE CONTAINS UPGRADE UP TO KTOR 3.0 (thanks to [@d1snin](https://github.com/d1snin))** + ## 18.2.3 * `Core`: diff --git a/gradle.properties b/gradle.properties index 1a59b3fbcd..c9ed886d1f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ kotlin.incremental=true kotlin.incremental.js=true library_group=dev.inmo -library_version=18.2.3 +library_version=19.0.0 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index afbd1ea715..5bcd94d299 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,12 +8,12 @@ javax-activation = "1.1.1" korlibs = "5.4.0" uuid = "0.8.4" -ktor = "2.3.12" +ktor = "3.0.1" -ksp = "2.0.21-1.0.25" +ksp = "2.0.21-1.0.26" kotlin-poet = "1.18.1" -microutils = "0.22.7" +microutils = "0.23.0" kslog = "1.3.6" versions = "0.51.0" diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index 0003b914a7..153a7122c6 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -1034,7 +1034,7 @@ public final class dev/inmo/tgbotapi/requests/abstracts/MultipartFile : dev/inmo public fun equals (Ljava/lang/Object;)Z public fun getFileId ()Ljava/lang/String; public final fun getFilename ()Ljava/lang/String; - public final fun getInput ()Lio/ktor/utils/io/core/Input; + public final fun getInput ()Lkotlinx/io/Source; public fun hashCode ()I public fun toString ()Ljava/lang/String; } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt index d3f1f94490..df3c7ea281 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileChannelRequestCallFactory.kt @@ -8,9 +8,7 @@ import dev.inmo.tgbotapi.utils.ByteReadChannelAllocator import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import io.ktor.client.HttpClient -import io.ktor.client.call.receive import io.ktor.client.request.get -import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.bodyAsChannel import io.ktor.utils.io.* import kotlinx.coroutines.* diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt index 2d3a661e7a..593ffcb226 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DownloadFileRequestCallFactory.kt @@ -6,9 +6,9 @@ import dev.inmo.tgbotapi.requests.DownloadFile import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper -import io.ktor.client.HttpClient -import io.ktor.client.request.get -import io.ktor.client.statement.readBytes +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.client.statement.* import kotlinx.serialization.json.Json @RiskFeature @@ -17,13 +17,13 @@ object DownloadFileRequestCallFactory : KtorCallFactory { client: HttpClient, urlsKeeper: TelegramAPIUrlsKeeper, request: Request, - jsonFormatter: Json - ): T? = (request as? DownloadFile) ?.let { + jsonFormatter: Json, + ): T? = (request as? DownloadFile)?.let { val fullUrl = urlsKeeper.createFileLinkUrl(it.filePath) safely { @Suppress("UNCHECKED_CAST") - client.get(fullUrl).readBytes() as T // always ByteArray + client.get(fullUrl).readRawBytes() as T // always ByteArray } } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt index b8d08c10d8..819aa66132 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/MultipleClientKtorRequestsExecutor.kt @@ -38,7 +38,7 @@ internal expect inline fun platformClientCopy(client: HttpClient): HttpClient * @param requestExecutorsCount Amount of [DefaultKtorRequestsExecutor] which will be created and used under the * hood */ -class MultipleClientKtorRequestsExecutor ( +class MultipleClientKtorRequestsExecutor( telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, callsFactories: List, excludeDefaultFactories: Boolean, @@ -47,7 +47,7 @@ class MultipleClientKtorRequestsExecutor ( pipelineStepsHolder: TelegramBotPipelinesHandler, requestExecutorsCount: Int, logger: KSLog, - clientFactory: () -> HttpClient + clientFactory: () -> HttpClient, ) : BaseRequestsExecutor(telegramAPIUrlsKeeper) { private val requestExecutors = (0 until requestExecutorsCount).map { DefaultKtorRequestsExecutor( @@ -66,7 +66,7 @@ class MultipleClientKtorRequestsExecutor ( private val clientAllocationMutex = Mutex() private val takerFlow = freeClients.mapNotNull { clientAllocationMutex.withLock { - freeClients.value.firstOrNull() ?.also { + freeClients.value.firstOrNull()?.also { freeClients.value -= it } ?: return@mapNotNull null } @@ -81,7 +81,7 @@ class MultipleClientKtorRequestsExecutor ( jsonFormatter: Json, pipelineStepsHolder: TelegramBotPipelinesHandler, logger: KSLog, - diff: Unit + diff: Unit, ) : this( telegramAPIUrlsKeeper, callsFactories, @@ -89,7 +89,7 @@ class MultipleClientKtorRequestsExecutor ( requestsLimiter, jsonFormatter, pipelineStepsHolder, - client.engineConfig.threadsCount, + requestExecutorsCount = 4, // default threads count; configurable through dispatcher property logger, { platformClientCopy(client) } ) diff --git a/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt b/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt index b64c816a97..3d92174aaa 100644 --- a/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt +++ b/tgbotapi.core/src/jsMain/kotlin/dev/inmo/tgbotapi/utils/asInput.kt @@ -1,8 +1,6 @@ package dev.inmo.tgbotapi.utils -import io.ktor.util.toByteArray -import io.ktor.utils.io.ByteReadChannel -import io.ktor.utils.io.core.ByteReadPacket -import io.ktor.utils.io.core.Input +import io.ktor.utils.io.* +import io.ktor.utils.io.core.* actual suspend fun ByteReadChannel.asInput(): Input = ByteReadPacket(toByteArray()) diff --git a/tgbotapi.utils/api/tgbotapi.utils.api b/tgbotapi.utils/api/tgbotapi.utils.api index 52e4bb95c6..ff253465dd 100644 --- a/tgbotapi.utils/api/tgbotapi.utils.api +++ b/tgbotapi.utils/api/tgbotapi.utils.api @@ -3541,10 +3541,10 @@ public final class dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook public static synthetic fun includeWebhookHandlingInRoute$default (Lio/ktor/server/routing/Route;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun includeWebhookHandlingInRouteWithFlows (Lio/ktor/server/routing/Route;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;JLkotlin/jvm/functions/Function1;)V public static synthetic fun includeWebhookHandlingInRouteWithFlows$default (Lio/ktor/server/routing/Route;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V - public static final fun setWebhookInfoAndStartListenWebhooks (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILio/ktor/server/engine/ApplicationEngineFactory;Ldev/inmo/tgbotapi/requests/webhook/SetWebhookRequest;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun setWebhookInfoAndStartListenWebhooks$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILio/ktor/server/engine/ApplicationEngineFactory;Ldev/inmo/tgbotapi/requests/webhook/SetWebhookRequest;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public static final fun startListenWebhooks (ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lio/ktor/server/engine/ApplicationEngine; - public static synthetic fun startListenWebhooks$default (ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/ktor/server/engine/ApplicationEngine; + public static final fun setWebhookInfoAndStartListenWebhooks (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILio/ktor/server/engine/ApplicationEngineFactory;Ldev/inmo/tgbotapi/requests/webhook/SetWebhookRequest;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun setWebhookInfoAndStartListenWebhooks$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILio/ktor/server/engine/ApplicationEngineFactory;Ldev/inmo/tgbotapi/requests/webhook/SetWebhookRequest;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun startListenWebhooks (ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)Lio/ktor/server/engine/EmbeddedServer; + public static synthetic fun startListenWebhooks$default (ILio/ktor/server/engine/ApplicationEngineFactory;Lkotlin/jvm/functions/Function2;Ljava/lang/String;Ljava/lang/String;Ldev/inmo/tgbotapi/updateshandlers/webhook/WebhookPrivateKeyConfig;Lkotlinx/coroutines/CoroutineScope;JLkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/ktor/server/engine/EmbeddedServer; } public final class dev/inmo/tgbotapi/types/files/PathedFileAsStreamKt { diff --git a/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt b/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt index c7ab3b32de..09b2271aae 100644 --- a/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt +++ b/tgbotapi.utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/Webhook.kt @@ -1,19 +1,21 @@ package dev.inmo.tgbotapi.extensions.utils.updates.retrieving -import dev.inmo.micro_utils.coroutines.* +import dev.inmo.micro_utils.coroutines.ExceptionHandler +import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.tgbotapi.bot.RequestsExecutor import dev.inmo.tgbotapi.extensions.utils.nonstrictJsonFormat import dev.inmo.tgbotapi.extensions.utils.updates.flowsUpdatesFilter import dev.inmo.tgbotapi.requests.webhook.SetWebhookRequest import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy -import dev.inmo.tgbotapi.updateshandlers.* +import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter +import dev.inmo.tgbotapi.updateshandlers.UpdateReceiver +import dev.inmo.tgbotapi.updateshandlers.UpdatesFilter import dev.inmo.tgbotapi.updateshandlers.webhook.WebhookPrivateKeyConfig -import io.ktor.http.HttpStatusCode -import io.ktor.server.application.call +import io.ktor.http.* import io.ktor.server.engine.* -import io.ktor.server.request.receiveText -import io.ktor.server.response.respond +import io.ktor.server.request.* +import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.asCoroutineDispatcher @@ -38,7 +40,7 @@ fun Route.includeWebhookHandlingInRoute( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, mediaGroupsDebounceTimeMillis: Long = 1000L, - block: UpdateReceiver + block: UpdateReceiver, ) { val transformer = scope.updateHandlerWithMediaGroupsAdaptation(block, mediaGroupsDebounceTimeMillis) post { @@ -55,7 +57,7 @@ fun Route.includeWebhookHandlingInRoute( call.respond(HttpStatusCode.InternalServerError) }.getOrThrow() } catch (e: Throwable) { - exceptionsHandler ?.invoke(e) + exceptionsHandler?.invoke(e) } } } @@ -69,7 +71,7 @@ fun Route.includeWebhookHandlingInRouteWithFlows( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, mediaGroupsDebounceTimeMillis: Long = 1000L, - block: FlowsUpdatesFilter.() -> Unit + block: FlowsUpdatesFilter.() -> Unit, ) = includeWebhookHandlingInRoute( scope, exceptionsHandler, @@ -92,50 +94,57 @@ fun Route.includeWebhookHandlingInRouteWithFlows( * @see UpdatesFilter * @see UpdatesFilter.asUpdateReceiver */ -fun startListenWebhooks( +fun startListenWebhooks( listenPort: Int, - engineFactory: ApplicationEngineFactory<*, *>, + engineFactory: ApplicationEngineFactory, exceptionsHandler: ExceptionHandler, listenHost: String = "0.0.0.0", listenRoute: String? = null, privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), mediaGroupsDebounceTimeMillis: Long = 1000L, - additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, - block: UpdateReceiver -): ApplicationEngine { - val env = applicationEngineEnvironment { - - module { - routing { - listenRoute ?.also { - createRouteFromPath(it).includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) - } ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) - } - } - - privateKeyConfig ?.let { - sslConnector( - privateKeyConfig.keyStore, - privateKeyConfig.aliasName, - privateKeyConfig::keyStorePassword, - privateKeyConfig::aliasPassword - ) { + additionalApplicationEnvironmentConfigurator: ApplicationEnvironmentBuilder.() -> Unit = {}, + additionalEngineConfigurator: TConfiguration.() -> Unit = {}, + block: UpdateReceiver, +): EmbeddedServer = + embeddedServer( + factory = engineFactory, + environment = applicationEnvironment { + additionalApplicationEnvironmentConfigurator() + }, + configure = { + privateKeyConfig?.let { + sslConnector( + privateKeyConfig.keyStore, + privateKeyConfig.aliasName, + privateKeyConfig::keyStorePassword, + privateKeyConfig::aliasPassword + ) { + host = listenHost + port = listenPort + } + } ?: connector { host = listenHost port = listenPort } - } ?: connector { - host = listenHost - port = listenPort + + additionalEngineConfigurator() + }, + module = { + routing { + listenRoute?.also { + createRouteFromPath(it).includeWebhookHandlingInRoute( + scope, + exceptionsHandler, + mediaGroupsDebounceTimeMillis, + block + ) + } ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block) + } } - - additionalApplicationEngineEnvironmentConfigurator() - } - - return embeddedServer(engineFactory, env).also { + ).also { it.start(false) } -} /** * Setting up ktor server, set webhook info via [SetWebhookRequest] request. @@ -152,9 +161,9 @@ fun startListenWebhooks( * @see UpdatesFilter.asUpdateReceiver */ @Suppress("unused") -suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( +suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( listenPort: Int, - engineFactory: ApplicationEngineFactory<*, *>, + engineFactory: ApplicationEngineFactory, setWebhookRequest: SetWebhookRequest, exceptionsHandler: ExceptionHandler = {}, listenHost: String = "0.0.0.0", @@ -162,11 +171,24 @@ suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks( privateKeyConfig: WebhookPrivateKeyConfig? = null, scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()), mediaGroupsDebounceTimeMillis: Long = 1000L, - additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {}, - block: UpdateReceiver -): ApplicationEngine = try { + additionalApplicationEnvironmentConfigurator: ApplicationEnvironmentBuilder.() -> Unit = {}, + additionalEngineConfigurator: TConfiguration.() -> Unit = {}, + block: UpdateReceiver, +): EmbeddedServer = try { execute(setWebhookRequest) - startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, mediaGroupsDebounceTimeMillis, additionalApplicationEngineEnvironmentConfigurator, block) + startListenWebhooks( + listenPort, + engineFactory, + exceptionsHandler, + listenHost, + listenRoute, + privateKeyConfig, + scope, + mediaGroupsDebounceTimeMillis, + additionalApplicationEnvironmentConfigurator, + additionalEngineConfigurator, + block + ) } catch (e: Exception) { throw e }