diff --git a/CHANGELOG.md b/CHANGELOG.md index 93a45d0b79..536f7daa4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # TelegramBotAPI changelog +## 6.0.2 + +* `Core`: + * Long polling now uses media groups debounce as in webhooks + ## 6.0.1 * `Versions`: diff --git a/gradle.properties b/gradle.properties index cb26b19a09..7ece4563fa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ kotlin.incremental=true kotlin.incremental.js=true library_group=dev.inmo -library_version=6.0.1 +library_version=6.0.2 diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/utils/UpdatesHandling.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/utils/UpdatesHandling.kt index 1c660b8ad2..c0ad2cd264 100644 --- a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/utils/UpdatesHandling.kt +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/utils/UpdatesHandling.kt @@ -1,7 +1,8 @@ package dev.inmo.tgbotapi.extensions.api.utils +import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.tgbotapi.extensions.api.InternalUtils.convertWithMediaGroupUpdates -import dev.inmo.tgbotapi.types.message.abstracts.PossiblySentViaBotCommonMessage +import dev.inmo.tgbotapi.types.message.abstracts.PossiblyMediaGroupMessage import dev.inmo.tgbotapi.types.update.abstracts.BaseMessageUpdate import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.UpdateReceiver @@ -28,26 +29,18 @@ fun CoroutineScope.updateHandlerWithMediaGroupsAdaptation( ) launch { - launch { + launchSafelyWithoutExceptions { for (update in updatesChannel) { - val dataAsPossiblySentViaBotCommonMessage = update.data as? PossiblySentViaBotCommonMessage<*> - - if (dataAsPossiblySentViaBotCommonMessage == null) { - output(update) - continue + val data = update.data + when { + data is PossiblyMediaGroupMessage<*> && data.mediaGroupId != null -> { + mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate) + } + else -> output(update) } - - val mediaGroupId = dataAsPossiblySentViaBotCommonMessage.mediaGroupId - - if (mediaGroupId == null) { - output(update) - continue - } - - mediaGroupChannel.send("${mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate) } } - launch { + launchSafelyWithoutExceptions { for ((_, mediaGroup) in mediaGroupAccumulatedChannel) { mediaGroup.convertWithMediaGroupUpdates().forEach { output(it) diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt index f0efd52d45..03750af9b8 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt @@ -8,6 +8,7 @@ import dev.inmo.micro_utils.fsm.common.utils.StateHandlingErrorHandler import dev.inmo.micro_utils.fsm.common.utils.defaultStateHandlingErrorHandler import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.longPolling +import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.updateHandlerWithMediaGroupsAdaptation import dev.inmo.tgbotapi.types.Seconds import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter @@ -47,6 +48,10 @@ suspend fun TelegramBot.buildBehaviourWithFSM( * Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates * using [longPolling]. For [longPolling] will be used result [BehaviourContextWithFSM] for both parameters * flowsUpdatesFilter and scope + * + * @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 */ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( upstreamUpdatesFlow: Flow? = null, @@ -58,6 +63,7 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( timeoutSeconds: Seconds = 30, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, block: CustomBehaviourContextReceiver, Unit> ): Pair, Job> = buildBehaviourWithFSM( upstreamUpdatesFlow, @@ -70,7 +76,7 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( ).run { this to scope.launch { start() - longPolling(flowsUpdatesFilter, timeoutSeconds, scope, autoDisableWebhooks, autoSkipTimeoutExceptions, defaultExceptionsHandler) + longPolling(flowsUpdatesFilter, timeoutSeconds, scope, autoDisableWebhooks, autoSkipTimeoutExceptions, mediaGroupsDebounceTimeMillis, defaultExceptionsHandler) } } @@ -116,6 +122,10 @@ suspend fun TelegramBot.buildBehaviourWithFSM( * using [longPolling]. For [longPolling] will be used result [BehaviourContextWithFSM] for both parameters * flowsUpdatesFilter and scope * + * @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 + * * @see buildBehaviourWithFSMAndStartLongPolling * @see BehaviourContext * @see longPolling @@ -131,6 +141,7 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( timeoutSeconds: Seconds = 30, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, block: CustomBehaviourContextReceiver, Unit> ) = FlowsUpdatesFilter().let { buildBehaviourWithFSM( @@ -149,6 +160,7 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( scope, autoDisableWebhooks, autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis, defaultExceptionsHandler ) } diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt index 97a1cd09a6..85a5f0d402 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt @@ -11,6 +11,7 @@ import dev.inmo.tgbotapi.bot.ktor.KtorRequestsExecutorBuilder import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling +import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.updateHandlerWithMediaGroupsAdaptation import dev.inmo.tgbotapi.types.Seconds import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl @@ -26,6 +27,10 @@ import kotlin.coroutines.coroutineContext * **WARNING** This method WILL NOT launch any listening of updates. Use something like * [startGettingOfUpdatesByLongPolling] or tools for work with webhooks * + * @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 + * * @return Created bot which has been used to create [BehaviourContext] via [buildBehaviourWithFSM] * * @see [BehaviourContext] @@ -46,6 +51,7 @@ suspend fun telegramBotWithBehaviourAndFSM( timeoutSeconds: Seconds = 30, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, block: CustomBehaviourContextReceiver, Unit> ): TelegramBot = telegramBot( token, @@ -63,6 +69,7 @@ suspend fun telegramBotWithBehaviourAndFSM( timeoutSeconds, autoDisableWebhooks, autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis, block ) } @@ -71,6 +78,10 @@ suspend fun telegramBotWithBehaviourAndFSM( * Create bot using [telegramBot] and start listening for updates using [buildBehaviourWithFSMAndStartLongPolling]. This * method will launch updates retrieving via long polling inside of [buildBehaviourWithFSMAndStartLongPolling] * + * @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 + * * @return Pair of [TelegramBot] and [Job]. This [Job] can be used to stop listening updates in your [block] you passed * here * @@ -91,6 +102,7 @@ suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( timeoutSeconds: Seconds = 30, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, block: CustomBehaviourContextReceiver, Unit> ): Pair { return telegramBot( @@ -108,6 +120,7 @@ suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( timeoutSeconds, autoDisableWebhooks, autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis, block ) } diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourBuilders.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourBuilders.kt index 730122b758..1990b89eba 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourBuilders.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourBuilders.kt @@ -5,6 +5,7 @@ import dev.inmo.micro_utils.coroutines.ExceptionHandler import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.longPolling import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling +import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.updateHandlerWithMediaGroupsAdaptation import dev.inmo.tgbotapi.types.Seconds import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import kotlinx.coroutines.* @@ -47,6 +48,10 @@ suspend fun TelegramBot.buildBehaviour( * Use this method to build bot behaviour and run it via long polling. In case you wish to get [FlowsUpdatesFilter] for * additional manipulations, you must provide external [FlowsUpdatesFilter] in other [buildBehaviour] function. * + * @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 + * * @see buildBehaviour * @see BehaviourContext * @see startGettingOfUpdatesByLongPolling @@ -57,6 +62,7 @@ suspend fun TelegramBot.buildBehaviourWithLongPolling( timeoutSeconds: Seconds = 30, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, block: BehaviourContextReceiver ): Job { val behaviourContext = buildBehaviour( @@ -69,6 +75,7 @@ suspend fun TelegramBot.buildBehaviourWithLongPolling( scope = behaviourContext, timeoutSeconds = timeoutSeconds, autoDisableWebhooks = autoDisableWebhooks, - autoSkipTimeoutExceptions = autoSkipTimeoutExceptions + autoSkipTimeoutExceptions = autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis = mediaGroupsDebounceTimeMillis ) } diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBot.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBot.kt index 36c8df0ff9..a72e8df932 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBot.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBot.kt @@ -5,6 +5,7 @@ import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.ktor.KtorRequestsExecutorBuilder import dev.inmo.tgbotapi.bot.ktor.telegramBot import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling +import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.updateHandlerWithMediaGroupsAdaptation import dev.inmo.tgbotapi.types.Seconds import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl @@ -53,6 +54,10 @@ suspend fun telegramBotWithBehaviour( * * **WARNING** This method WILL launch updates listening inside of calling [buildBehaviourWithLongPolling] * + * @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 + * * @return Pair of [TelegramBot] and [Job]. This [Job] can be used to stop listening updates in your [block] you passed * here * @@ -70,6 +75,7 @@ suspend fun telegramBotWithBehaviourAndLongPolling( timeoutSeconds: Seconds = 30, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, block: BehaviourContextReceiver ): Pair { return telegramBot( @@ -84,6 +90,7 @@ suspend fun telegramBotWithBehaviourAndLongPolling( timeoutSeconds, autoDisableWebhooks, autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis, block ) } diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt index a0c36c0c35..83757480d8 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/UpdatesUtils.kt @@ -27,7 +27,8 @@ fun List.lastUpdateIdentifier(): UpdateIdentifier? { } /** - * Will convert incoming list of updates to list with [MediaGroupUpdate]s + * Will convert incoming list of [Update]s to list with [Update]s, which include [dev.inmo.tgbotapi.types.message.abstracts.ContentMessage]s + * with [dev.inmo.tgbotapi.types.message.content.MediaGroupContent] */ fun List.convertWithMediaGroupUpdates(): List { val resultUpdates = mutableListOf() @@ -67,4 +68,5 @@ fun List.convertWithMediaGroupUpdates(): List { * * @throws IllegalStateException */ +@Deprecated("Redundant", ReplaceWith("this")) fun BaseEditMessageUpdate.toEditMediaGroupUpdate() = this 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 50a592c86f..a891eed163 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 @@ -19,12 +19,18 @@ 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 + */ fun TelegramBot.longPollingFlow( timeoutSeconds: Seconds = 30, exceptionsHandler: (ExceptionHandler)? = null, allowedUpdates: List? = ALL_UPDATES_LIST, autoDisableWebhooks: Boolean = true, - autoSkipTimeoutExceptions: Boolean = true + autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, ): Flow = channelFlow { if (autoDisableWebhooks) { runCatchingSafely { @@ -47,6 +53,52 @@ fun TelegramBot.longPollingFlow( var lastUpdateIdentifier: UpdateIdentifier? = null + val updatesHandler: (suspend (List) -> Unit) = if (mediaGroupsDebounceTimeMillis != null) { + val scope = CoroutineScope(contextToWork) + val updatesReceiver = scope.updateHandlerWithMediaGroupsAdaptation( + { + withContext(contextToWork) { + send(it) + } + }, + mediaGroupsDebounceTimeMillis + ); + { originalUpdates: List -> + originalUpdates.forEach { + updatesReceiver(it) + lastUpdateIdentifier = maxOf(lastUpdateIdentifier ?: it.updateId, it.updateId) + } + } + } else { + { originalUpdates: List -> + val converted = originalUpdates.convertWithMediaGroupUpdates() + /** + * Dirty hack for cases when the media group was retrieved not fully: + * + * We are throw out the last media group and will reretrieve it again in the next get updates + * and it will guarantee that it is full + */ + val updates = if ( + originalUpdates.size == getUpdatesLimit.last + && ((converted.last() as? BaseSentMessageUpdate) ?.data as? CommonMessage<*>) ?.content is MediaGroupContent<*> + ) { + converted - converted.last() + } else { + converted + } + + safelyWithResult { + for (update in updates) { + send(update) + + lastUpdateIdentifier = update.updateId + } + }.onFailure { + cancel(it as? CancellationException ?: return@onFailure) + } + } + } + withContext(contextToWork) { while (isActive) { safely( @@ -64,50 +116,25 @@ fun TelegramBot.longPollingFlow( } } ) { - val updates = execute( + execute( GetUpdates( offset = lastUpdateIdentifier?.plus(1), timeout = timeoutSeconds, allowed_updates = allowedUpdates ) ).let { originalUpdates -> - val converted = originalUpdates.convertWithMediaGroupUpdates() - /** - * Dirty hack for cases when the media group was retrieved not fully: - * - * We are throw out the last media group and will reretrieve it again in the next get updates - * and it will guarantee that it is full - */ - /** - * Dirty hack for cases when the media group was retrieved not fully: - * - * We are throw out the last media group and will reretrieve it again in the next get updates - * and it will guarantee that it is full - */ - if ( - originalUpdates.size == getUpdatesLimit.last - && ((converted.last() as? BaseSentMessageUpdate) ?.data as? CommonMessage<*>) ?.content is MediaGroupContent<*> - ) { - converted - converted.last() - } else { - converted - } - } - - safelyWithResult { - for (update in updates) { - send(update) - - lastUpdateIdentifier = update.updateId - } - }.onFailure { - cancel(it as? CancellationException ?: return@onFailure) + updatesHandler(originalUpdates) } } } } } +/** + * @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 + */ fun TelegramBot.startGettingOfUpdatesByLongPolling( timeoutSeconds: Seconds = 30, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), @@ -115,13 +142,15 @@ fun TelegramBot.startGettingOfUpdatesByLongPolling( allowedUpdates: List? = ALL_UPDATES_LIST, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, updatesReceiver: UpdateReceiver ): Job = longPollingFlow( timeoutSeconds = timeoutSeconds, exceptionsHandler = exceptionsHandler, allowedUpdates = allowedUpdates, autoDisableWebhooks = autoDisableWebhooks, - autoSkipTimeoutExceptions = autoSkipTimeoutExceptions + autoSkipTimeoutExceptions = autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis = mediaGroupsDebounceTimeMillis ).subscribeSafely( scope, exceptionsHandler ?: defaultSafelyExceptionHandler, @@ -129,6 +158,10 @@ fun TelegramBot.startGettingOfUpdatesByLongPolling( ) /** + * @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 + * * @return [kotlinx.coroutines.flow.Flow] which will emit updates to the collector while they will be accumulated. Works * the same as [longPollingFlow], but it will cancel the flow after the first one [HttpRequestTimeoutException] */ @@ -137,7 +170,8 @@ fun TelegramBot.createAccumulatedUpdatesRetrieverFlow( avoidCallbackQueries: Boolean = false, exceptionsHandler: ExceptionHandler? = null, allowedUpdates: List? = ALL_UPDATES_LIST, - autoDisableWebhooks: Boolean = true + autoDisableWebhooks: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, ): Flow = longPollingFlow( timeoutSeconds = 0, exceptionsHandler = { @@ -149,11 +183,17 @@ fun TelegramBot.createAccumulatedUpdatesRetrieverFlow( }, allowedUpdates = allowedUpdates, autoDisableWebhooks = autoDisableWebhooks, - autoSkipTimeoutExceptions = false + autoSkipTimeoutExceptions = false, + mediaGroupsDebounceTimeMillis = mediaGroupsDebounceTimeMillis ).filter { !(it is InlineQueryUpdate && avoidInlineQueries || it is CallbackQueryUpdate && avoidCallbackQueries) } +/** + * @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 + */ fun TelegramBot.retrieveAccumulatedUpdates( avoidInlineQueries: Boolean = false, avoidCallbackQueries: Boolean = false, @@ -161,25 +201,33 @@ fun TelegramBot.retrieveAccumulatedUpdates( exceptionsHandler: (ExceptionHandler)? = null, allowedUpdates: List? = ALL_UPDATES_LIST, autoDisableWebhooks: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, updatesReceiver: UpdateReceiver ): Job = createAccumulatedUpdatesRetrieverFlow( avoidInlineQueries, avoidCallbackQueries, exceptionsHandler, allowedUpdates, - autoDisableWebhooks + autoDisableWebhooks, + mediaGroupsDebounceTimeMillis ).subscribeSafelyWithoutExceptions( scope.LinkedSupervisorScope() ) { updatesReceiver(it) } +/** + * @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 + */ fun TelegramBot.retrieveAccumulatedUpdates( flowsUpdatesFilter: FlowsUpdatesFilter, avoidInlineQueries: Boolean = false, avoidCallbackQueries: Boolean = false, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), autoDisableWebhooks: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, exceptionsHandler: ExceptionHandler? = null ) = retrieveAccumulatedUpdates( avoidInlineQueries, @@ -188,9 +236,15 @@ fun TelegramBot.retrieveAccumulatedUpdates( exceptionsHandler, flowsUpdatesFilter.allowedUpdates, autoDisableWebhooks, + mediaGroupsDebounceTimeMillis, flowsUpdatesFilter.asUpdateReceiver ) +/** + * @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 + */ suspend fun TelegramBot.flushAccumulatedUpdates( avoidInlineQueries: Boolean = false, avoidCallbackQueries: Boolean = false, @@ -198,6 +252,7 @@ suspend fun TelegramBot.flushAccumulatedUpdates( allowedUpdates: List? = ALL_UPDATES_LIST, exceptionsHandler: ExceptionHandler? = null, autoDisableWebhooks: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, updatesReceiver: UpdateReceiver = {} ) = retrieveAccumulatedUpdates( avoidInlineQueries, @@ -206,12 +261,17 @@ suspend fun TelegramBot.flushAccumulatedUpdates( exceptionsHandler, allowedUpdates, autoDisableWebhooks, + mediaGroupsDebounceTimeMillis, updatesReceiver ).join() /** - * Will [startGettingOfUpdatesByLongPolling] using incoming [flowsUpdatesFilter]. It is assumed that you ALREADY CONFIGURE + * Will [startGettingOfUpdatesByLongPolling] using incoming [updatesFilter]. It is assumed that you ALREADY CONFIGURE * all updates receivers, because this method will trigger getting of updates and. + * + * @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 */ fun TelegramBot.longPolling( updatesFilter: UpdatesFilter, @@ -219,6 +279,7 @@ fun TelegramBot.longPolling( scope: CoroutineScope = CoroutineScope(Dispatchers.Default), autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, exceptionsHandler: ExceptionHandler? = null ): Job = updatesFilter.run { startGettingOfUpdatesByLongPolling( @@ -228,6 +289,7 @@ fun TelegramBot.longPolling( allowedUpdates = allowedUpdates, autoDisableWebhooks = autoDisableWebhooks, autoSkipTimeoutExceptions = autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis = mediaGroupsDebounceTimeMillis, updatesReceiver = asUpdateReceiver ) } @@ -236,6 +298,10 @@ fun TelegramBot.longPolling( * Will enable [longPolling] by creating [FlowsUpdatesFilter] with [flowsUpdatesFilterUpdatesKeeperCount] as an argument * and applied [flowUpdatesPreset]. It is assumed that you WILL CONFIGURE all updates receivers in [flowUpdatesPreset], * because of after [flowUpdatesPreset] method calling will be triggered getting of updates. + * + * @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 */ @Suppress("unused") fun TelegramBot.longPolling( @@ -245,15 +311,22 @@ fun TelegramBot.longPolling( flowsUpdatesFilterUpdatesKeeperCount: Int = 100, autoDisableWebhooks: Boolean = true, autoSkipTimeoutExceptions: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, flowUpdatesPreset: FlowsUpdatesFilter.() -> Unit -): Job = longPolling(FlowsUpdatesFilter(flowsUpdatesFilterUpdatesKeeperCount).apply(flowUpdatesPreset), timeoutSeconds, scope, autoDisableWebhooks, autoSkipTimeoutExceptions, exceptionsHandler) +): Job = longPolling(FlowsUpdatesFilter(flowsUpdatesFilterUpdatesKeeperCount).apply(flowUpdatesPreset), timeoutSeconds, scope, autoDisableWebhooks, autoSkipTimeoutExceptions, mediaGroupsDebounceTimeMillis, exceptionsHandler) +/** + * @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 + */ fun RequestsExecutor.startGettingOfUpdatesByLongPolling( updatesFilter: UpdatesFilter, timeoutSeconds: Seconds = 30, exceptionsHandler: ExceptionHandler? = null, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), autoDisableWebhooks: Boolean = true, + mediaGroupsDebounceTimeMillis: Long? = 1000L, autoSkipTimeoutExceptions: Boolean = true, ): Job = startGettingOfUpdatesByLongPolling( timeoutSeconds, @@ -262,5 +335,6 @@ fun RequestsExecutor.startGettingOfUpdatesByLongPolling( updatesFilter.allowedUpdates, autoDisableWebhooks, autoSkipTimeoutExceptions, + mediaGroupsDebounceTimeMillis, updatesFilter.asUpdateReceiver ) diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/MediaGroupsIncluder.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/MediaGroupsIncluder.kt index a5d490d4b5..b2c4df5376 100644 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/MediaGroupsIncluder.kt +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/updates/retrieving/MediaGroupsIncluder.kt @@ -1,6 +1,5 @@ package dev.inmo.tgbotapi.extensions.utils.updates.retrieving -import dev.inmo.micro_utils.coroutines.launchSafely import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates import dev.inmo.tgbotapi.types.message.abstracts.PossiblyMediaGroupMessage @@ -12,7 +11,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch - /** * Create [UpdateReceiver] object which will correctly accumulate updates and send into output updates which INCLUDE * [dev.inmo.tgbotapi.types.update.MediaGroupUpdates.MediaGroupUpdate]s. 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 a675fa27e3..c7ab3b32de 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 @@ -26,6 +26,9 @@ import java.util.concurrent.Executors * @param [scope] Will be used for mapping of media groups * @param [exceptionsHandler] Pass this parameter to set custom exception handler for getting updates * @param [block] Some receiver block like [dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter] + * @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 * * @see dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter * @see UpdatesFilter @@ -57,6 +60,11 @@ fun Route.includeWebhookHandlingInRoute( } } +/** + * @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 + */ fun Route.includeWebhookHandlingInRouteWithFlows( scope: CoroutineScope, exceptionsHandler: ExceptionHandler? = null, @@ -76,6 +84,9 @@ fun Route.includeWebhookHandlingInRouteWithFlows( * @param listenRoute address to listen by bot. If null - will be set up in root of host * @param scope Scope which will be used for * @param privateKeyConfig If configured - server will be created with [sslConnector]. [connector] will be used otherwise + * @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 * * @see dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter * @see UpdatesFilter @@ -132,6 +143,9 @@ fun startListenWebhooks( * @param listenPort port which will be listen by bot * @param listenRoute address to listen by bot * @param scope Scope which will be used for + * @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 * * @see dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter * @see UpdatesFilter