diff --git a/CHANGELOG.md b/CHANGELOG.md index 223c137bd4..ed2d1a0850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,14 @@ * `KSP`: `2.2.20-2.0.4` -> `2.3.2` * `MicroUtils`: `0.26.6` -> `0.26.8` * `KSLog`: `1.5.1` -> `1.5.2` +* `Core`: + * Allow to use `SetWebhook` with `maxAllowedConnections` up to `100000` * `KSP`: * Fixed annotation property access for KSP2 compatibility using `withNoSuchElementWorkaround` * Removed `ksp.useKSP2=false` workaround from `gradle.properties` (KSP2 is now properly supported) * `Utils`: * Regenerated class casts extensions + * Allow to use custom `GetUpdates` in `longPollingFlow` ## 30.0.1 diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index 818fd9d732..6f1d5d6b5a 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -10654,6 +10654,7 @@ public final class dev/inmo/tgbotapi/types/CommonKt { public static final field yShiftField Ljava/lang/String; public static final field yearField Ljava/lang/String; public static final fun getAllowedConnectionsLength ()Lkotlin/ranges/IntRange; + public static final fun getAllowedConnectionsWithLocalServerLength ()Lkotlin/ranges/IntRange; public static final fun getBasketballAndFootballDiceResultLimit ()Lkotlin/ranges/IntRange; public static final fun getBotCommandDescriptionLimit ()Lkotlin/ranges/IntRange; public static final fun getBotCommandLengthLimit ()Lkotlin/ranges/IntRange; diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/webhook/SetWebhook.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/webhook/SetWebhook.kt index 950f898bc7..ce34d6fd4e 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/webhook/SetWebhook.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/webhook/SetWebhook.kt @@ -1,9 +1,11 @@ package dev.inmo.tgbotapi.requests.webhook +import dev.inmo.kslog.common.w import dev.inmo.tgbotapi.requests.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.DataRequest import dev.inmo.tgbotapi.requests.send.media.base.MultipartRequestImpl import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog import kotlinx.serialization.* import kotlinx.serialization.builtins.serializer @@ -118,11 +120,22 @@ fun SetWebhook( ) /** - * Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update - * for the bot, we will send an HTTPS POST request to the specified url, containing a JSON-serialized Update. + * Represents a request for setting a webhook in Telegram's Bot API. A webhook allows Telegram to send updates directly + * to the bot via an HTTPS POST request to the provided URL, enabling real-time interaction. * - * If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a secret path in the [url], - * e.g. https://www.example.com/. Since nobody else knows your bot's token, you can be pretty sure it's us. + * @constructor Creates a data class holding configuration options for the webhook. + * + * @property url The HTTPS URL to which updates will be posted. Must be valid and accessible. + * @property certificateFile An optional path to a public certificate file for webhook verification. Use only if a self-signed certificate is applied. + * @property ipAddress The fixed IP address for incoming webhook connections. + * @property maxAllowedConnections The maximum number of simultaneous HTTPS connections allowed to the webhook for + * delivering updates. You may use value outside of [allowedConnectionsLength], but be sure that it is in + * [allowedConnectionsWithLocalServerLength] and you are using local bot api url + * @property allowedUpdates A list of update types the bot will receive. Defaults to all update types. + * @property dropPendingUpdates If true, all pending updates will be dropped when the webhook is changed. + * @property secretToken An optional arbitrary secret key to ensure the webhook updates are coming from Telegram. + * + * @throws IllegalArgumentException if the provided maxAllowedConnections value is outside the permitted range (both [allowedConnectionsLength] and [allowedConnectionsWithLocalServerLength]) */ @ConsistentCopyVisibility @Serializable @@ -150,8 +163,19 @@ data class SetWebhook internal constructor( init { maxAllowedConnections ?.let { - if (it !in allowedConnectionsLength) { - throw IllegalArgumentException("Allowed connection for webhook must be in $allowedConnectionsLength range (but passed $it)") + when { + it !in allowedConnectionsLength && it in allowedConnectionsWithLocalServerLength -> { + DefaultKTgBotAPIKSLog.w { + """ + Passed amount of allowed connections to server is $it and it exceeds default amount of + connections $allowedConnectionsLength, but can be used with local bot api server. Make sure + you are using local bot api url in your bot. + """.trimIndent() + } + } + it !in allowedConnectionsLength -> { + throw IllegalArgumentException("Allowed connection for webhook must be in $allowedConnectionsLength range (but passed $it)") + } } } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt index 2c2780363b..acd46bf678 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt @@ -64,6 +64,7 @@ val threadNameLength = 1 until 128 val chatDescriptionLength = 0 until 256 val inlineResultQueryIdLingth = 1 until 64 val allowedConnectionsLength = 1 .. 100 +val allowedConnectionsWithLocalServerLength = 1 .. 100000 val invoiceTitleLimit = 1 until 32 val invoiceDescriptionLimit = 1 until 256 diff --git a/tgbotapi.utils/api/tgbotapi.utils.api b/tgbotapi.utils/api/tgbotapi.utils.api index 31845cd5c3..68d4cce07a 100644 --- a/tgbotapi.utils/api/tgbotapi.utils.api +++ b/tgbotapi.utils/api/tgbotapi.utils.api @@ -3705,7 +3705,9 @@ public final class dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPol public static synthetic fun longPolling$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;IZZLjava/lang/Long;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static synthetic fun longPolling$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;Ldev/inmo/tgbotapi/updateshandlers/UpdatesFilter;ILkotlinx/coroutines/CoroutineScope;ZZLjava/lang/Long;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static final fun longPollingFlow (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILkotlin/jvm/functions/Function2;Ljava/util/List;ZZLjava/lang/Long;)Lkotlinx/coroutines/flow/Flow; + public static final fun longPollingFlow (Ldev/inmo/tgbotapi/bot/RequestsExecutor;Lkotlin/jvm/functions/Function2;ZZLjava/lang/Long;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow; public static synthetic fun longPollingFlow$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ILkotlin/jvm/functions/Function2;Ljava/util/List;ZZLjava/lang/Long;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; + public static synthetic fun longPollingFlow$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;Lkotlin/jvm/functions/Function2;ZZLjava/lang/Long;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; public static final fun retrieveAccumulatedUpdates (Ldev/inmo/tgbotapi/bot/RequestsExecutor;Ldev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter;ZZLkotlinx/coroutines/CoroutineScope;ZLjava/lang/Long;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; public static final fun retrieveAccumulatedUpdates (Ldev/inmo/tgbotapi/bot/RequestsExecutor;ZZLkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;Ljava/util/List;ZLjava/lang/Long;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job; public static synthetic fun retrieveAccumulatedUpdates$default (Ldev/inmo/tgbotapi/bot/RequestsExecutor;Ldev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter;ZZLkotlinx/coroutines/CoroutineScope;ZLjava/lang/Long;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt index 148747dbc1..d49ac7e9e6 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/LongPolling.kt @@ -1,6 +1,5 @@ package dev.inmo.tgbotapi.extensions.utils.updates.retrieving -import dev.inmo.kslog.common.logger import dev.inmo.micro_utils.coroutines.* import dev.inmo.tgbotapi.bot.RequestsExecutor import dev.inmo.tgbotapi.bot.TelegramBot @@ -15,30 +14,36 @@ import dev.inmo.tgbotapi.types.update.* import dev.inmo.tgbotapi.types.update.abstracts.BaseSentMessageUpdate import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.* -import dev.inmo.tgbotapi.utils.DefaultKTgBotAPIKSLog -import dev.inmo.tgbotapi.utils.causedCancellationException -import dev.inmo.tgbotapi.utils.isCausedByCancellation import dev.inmo.tgbotapi.utils.isCausedUnresolvedAddressException import dev.inmo.tgbotapi.utils.subscribeWithBotLogger import io.ktor.client.network.sockets.ConnectTimeoutException import io.ktor.client.plugins.HttpRequestTimeoutException -import io.ktor.util.network.UnresolvedAddressException import io.ktor.utils.io.CancellationException import kotlinx.coroutines.* import kotlinx.coroutines.flow.* /** - * @param mediaGroupsDebounceTimeMillis Will be used for calling of [updateHandlerWithMediaGroupsAdaptation]. Pass null - * in case you wish to enable classic way of updates handling, but in that mode some media group messages can be - * retrieved in different updates + * Starts a long polling flow to receive updates continuously from the Telegram Bot API. + * + * This method retrieves updates from the bot, processes them, and emits them as a flow of `Update` objects. + * It allows handling of updates with features like automatic webhook disabling, timeout exception skipping, + * and media group handling with debounce time. + * + * @param exceptionsHandler Optional exception handler to manage exceptions that occur during the polling process. + * @param autoDisableWebhooks Specifies whether to automatically disable existing webhooks before starting the long polling flow (default: `true`). + * @param autoSkipTimeoutExceptions Defines if timeout-related exceptions should automatically be skipped during the polling (default: `true`). + * @param mediaGroupsDebounceTimeMillis The debounce time in milliseconds for processing media group updates. + * If set to `null`, media group handling is disabled (default: `1000L`). + * @param getUpdatesRequestCreator A function that creates a `GetUpdates` request for retrieving updates. + * This function accepts the identifier of the most recent update as an input and returns the new request. + * @return A [Flow] of [Update] objects that represents the continuous stream of updates received. */ fun TelegramBot.longPollingFlow( - timeoutSeconds: Seconds = 30, exceptionsHandler: (ExceptionHandler)? = null, - allowedUpdates: List? = ALL_UPDATES_LIST, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, mediaGroupsDebounceTimeMillis: Long? = 1000L, + getUpdatesRequestCreator: (sinceUpdate: UpdateId?) -> GetUpdates ): Flow = channelFlow { if (autoDisableWebhooks) { runCatchingLogging(logger = Log) { @@ -119,11 +124,7 @@ fun TelegramBot.longPollingFlow( while (isActive) { runCatchingLogging(logger = Log) { execute( - GetUpdates( - offset = lastUpdateIdentifier?.plus(1), - timeout = timeoutSeconds, - allowed_updates = allowedUpdates - ) + getUpdatesRequestCreator(lastUpdateIdentifier ?.plus(1)) ).let { originalUpdates -> updatesHandler(originalUpdates) } @@ -148,6 +149,45 @@ fun TelegramBot.longPollingFlow( } } +/** + * Initiates a long polling flow for receiving updates continuously from the Telegram Bot API. + * This method provides a customized way to handle the retrieval of updates with options + * to configure timeouts, update types, exception handling, and media group processing. + * + * @param timeoutSeconds The maximum time in seconds for the server to wait for available updates + * before responding (default: `30`). + * @param exceptionsHandler An optional exception handler for managing exceptions that occur during the + * long polling process. If not provided, exceptions will pass through unhandled. + * @param allowedUpdates A list of the update types to retrieve. By default, retrieves all possible + * update types as defined in `ALL_UPDATES_LIST`. + * @param autoDisableWebhooks Whether the current webhook should be disabled automatically before starting the + * long polling flow (default: `true`). + * @param autoSkipTimeoutExceptions Determines if timeout-related exceptions should be automatically skipped + * during the polling process (default: `true`). + * @param mediaGroupsDebounceTimeMillis The debounce time in milliseconds for processing updates containing + * media groups. If set to `null`, media group handling is disabled (default: `1000L`). + * @return A Flow that emits Update objects representing the updates fetched from the Telegram Bot API. + */ +fun TelegramBot.longPollingFlow( + timeoutSeconds: Seconds = 30, + exceptionsHandler: (ExceptionHandler)? = null, + allowedUpdates: List? = ALL_UPDATES_LIST, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, +): Flow = longPollingFlow( + exceptionsHandler = exceptionsHandler, + autoDisableWebhooks = autoDisableWebhooks, + autoSkipTimeoutExceptions = autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis = mediaGroupsDebounceTimeMillis, +) { + GetUpdates( + offset = it, + timeout = timeoutSeconds, + allowed_updates = allowedUpdates + ) +} + /** * @param mediaGroupsDebounceTimeMillis Will be used for calling of [updateHandlerWithMediaGroupsAdaptation]. Pass null * in case you wish to enable classic way of updates handling, but in that mode some media group messages can be