diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f6aa38769..0b8c7e45aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ * `ChatPermissions` now is interface and have two main realizations: `ChatPermissions.Granular` and `ChatPermissions.Common` * `RestrictedChatMember` now implements `ChatPermissions` too +* `API`: + * Now it is possible to pass all long polling parameters in all places used it +* `Issues`: + * Fix of [#697](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/697) ## 5.0.2 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 e090774496..f0efd52d45 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.types.Seconds import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import kotlinx.coroutines.* @@ -54,6 +55,9 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), presetHandlers: List> = listOf(), onStateHandlingErrorHandler: StateHandlingErrorHandler = defaultStateHandlingErrorHandler(), + timeoutSeconds: Seconds = 30, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, block: CustomBehaviourContextReceiver, Unit> ): Pair, Job> = buildBehaviourWithFSM( upstreamUpdatesFlow, @@ -66,7 +70,7 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( ).run { this to scope.launch { start() - longPolling(flowsUpdatesFilter, scope = scope) + longPolling(flowsUpdatesFilter, timeoutSeconds, scope, autoDisableWebhooks, autoSkipTimeoutExceptions, defaultExceptionsHandler) } } @@ -124,6 +128,9 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), presetHandlers: List> = listOf(), onStateHandlingErrorHandler: StateHandlingErrorHandler = defaultStateHandlingErrorHandler(), + timeoutSeconds: Seconds = 30, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, block: CustomBehaviourContextReceiver, Unit> ) = FlowsUpdatesFilter().let { buildBehaviourWithFSM( @@ -138,7 +145,11 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( start() longPolling( flowsUpdatesFilter, - scope = scope + timeoutSeconds, + scope, + autoDisableWebhooks, + autoSkipTimeoutExceptions, + 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 ca8a0a3253..97a1cd09a6 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.types.Seconds import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl import kotlinx.coroutines.CoroutineScope @@ -42,6 +43,9 @@ suspend fun telegramBotWithBehaviourAndFSM( presetHandlers: List> = listOf(), testServer: Boolean = false, onStateHandlingErrorHandler: StateHandlingErrorHandler = defaultStateHandlingErrorHandler(), + timeoutSeconds: Seconds = 30, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, block: CustomBehaviourContextReceiver, Unit> ): TelegramBot = telegramBot( token, @@ -56,6 +60,9 @@ suspend fun telegramBotWithBehaviourAndFSM( statesManager, presetHandlers, onStateHandlingErrorHandler, + timeoutSeconds, + autoDisableWebhooks, + autoSkipTimeoutExceptions, block ) } @@ -81,6 +88,9 @@ suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( presetHandlers: List> = listOf(), testServer: Boolean = false, onStateHandlingErrorHandler: StateHandlingErrorHandler = defaultStateHandlingErrorHandler(), + timeoutSeconds: Seconds = 30, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, block: CustomBehaviourContextReceiver, Unit> ): Pair { return telegramBot( @@ -95,6 +105,9 @@ suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( statesManager, presetHandlers, onStateHandlingErrorHandler, + timeoutSeconds, + autoDisableWebhooks, + autoSkipTimeoutExceptions, 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 8a8e0f91e6..730122b758 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.types.Seconds import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import kotlinx.coroutines.* @@ -53,6 +54,9 @@ suspend fun TelegramBot.buildBehaviour( suspend fun TelegramBot.buildBehaviourWithLongPolling( scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, + timeoutSeconds: Seconds = 30, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, block: BehaviourContextReceiver ): Job { val behaviourContext = buildBehaviour( @@ -62,6 +66,9 @@ suspend fun TelegramBot.buildBehaviourWithLongPolling( ) return longPolling( behaviourContext, - scope = behaviourContext + scope = behaviourContext, + timeoutSeconds = timeoutSeconds, + autoDisableWebhooks = autoDisableWebhooks, + autoSkipTimeoutExceptions = autoSkipTimeoutExceptions ) } 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 19b06325c4..36c8df0ff9 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.types.Seconds import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl import kotlinx.coroutines.* @@ -66,6 +67,9 @@ suspend fun telegramBotWithBehaviourAndLongPolling( builder: KtorRequestsExecutorBuilder.() -> Unit = {}, defaultExceptionsHandler: ExceptionHandler? = null, testServer: Boolean = false, + timeoutSeconds: Seconds = 30, + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, block: BehaviourContextReceiver ): Pair { return telegramBot( @@ -77,6 +81,9 @@ suspend fun telegramBotWithBehaviourAndLongPolling( it to it.buildBehaviourWithLongPolling( scope ?: CoroutineScope(coroutineContext), defaultExceptionsHandler, + timeoutSeconds, + autoDisableWebhooks, + autoSkipTimeoutExceptions, block ) } 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 89cdc3fde6..50a592c86f 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 @@ -5,7 +5,6 @@ import dev.inmo.tgbotapi.bot.RequestsExecutor import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.exceptions.* import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates -import dev.inmo.tgbotapi.extensions.utils.updates.lastUpdateIdentifier import dev.inmo.tgbotapi.requests.GetUpdates import dev.inmo.tgbotapi.requests.webhook.DeleteWebhook import dev.inmo.tgbotapi.types.* @@ -24,7 +23,8 @@ fun TelegramBot.longPollingFlow( timeoutSeconds: Seconds = 30, exceptionsHandler: (ExceptionHandler)? = null, allowedUpdates: List? = ALL_UPDATES_LIST, - autoDisableWebhooks: Boolean = true + autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true ): Flow = channelFlow { if (autoDisableWebhooks) { runCatchingSafely { @@ -32,58 +32,77 @@ fun TelegramBot.longPollingFlow( } } + val contextSafelyExceptionHandler = coroutineContext[ContextSafelyExceptionHandlerKey] + val contextToWork = if (contextSafelyExceptionHandler == null || !autoSkipTimeoutExceptions) { + coroutineContext + } else { + coroutineContext + ContextSafelyExceptionHandler { e -> + if (e is HttpRequestTimeoutException || (e is CommonBotException && e.cause is HttpRequestTimeoutException)) { + return@ContextSafelyExceptionHandler + } else { + contextSafelyExceptionHandler.handler(e) + } + } + } + var lastUpdateIdentifier: UpdateIdentifier? = null - while (isActive) { - safely( - { e -> - exceptionsHandler ?.invoke(e) - if (e is RequestException) { - delay(1000L) + withContext(contextToWork) { + while (isActive) { + safely( + { e -> + val isHttpRequestTimeoutException = e is HttpRequestTimeoutException || (e is CommonBotException && e.cause is HttpRequestTimeoutException) + if (isHttpRequestTimeoutException && autoSkipTimeoutExceptions) { + return@safely + } + exceptionsHandler ?.invoke(e) + if (e is RequestException) { + delay(1000L) + } + if (e is GetUpdatesConflict && (exceptionsHandler == null || exceptionsHandler == defaultSafelyExceptionHandler)) { + println("Warning!!! Other bot with the same bot token requests updates with getUpdate in parallel") + } } - if (e is GetUpdatesConflict && (exceptionsHandler == null || exceptionsHandler == defaultSafelyExceptionHandler)) { - println("Warning!!! Other bot with the same bot token requests updates with getUpdate in parallel") + ) { + val updates = 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 + } } - } - ) { - val updates = 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) + safelyWithResult { + for (update in updates) { + send(update) - lastUpdateIdentifier = update.updateId + lastUpdateIdentifier = update.updateId + } + }.onFailure { + cancel(it as? CancellationException ?: return@onFailure) } - }.onFailure { - cancel(it as? CancellationException ?: return@onFailure) } } } @@ -95,8 +114,15 @@ fun TelegramBot.startGettingOfUpdatesByLongPolling( exceptionsHandler: (ExceptionHandler)? = null, allowedUpdates: List? = ALL_UPDATES_LIST, autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, updatesReceiver: UpdateReceiver -): Job = longPollingFlow(timeoutSeconds, exceptionsHandler, allowedUpdates, autoDisableWebhooks).subscribeSafely( +): Job = longPollingFlow( + timeoutSeconds = timeoutSeconds, + exceptionsHandler = exceptionsHandler, + allowedUpdates = allowedUpdates, + autoDisableWebhooks = autoDisableWebhooks, + autoSkipTimeoutExceptions = autoSkipTimeoutExceptions +).subscribeSafely( scope, exceptionsHandler ?: defaultSafelyExceptionHandler, updatesReceiver @@ -111,7 +137,7 @@ fun TelegramBot.createAccumulatedUpdatesRetrieverFlow( avoidCallbackQueries: Boolean = false, exceptionsHandler: ExceptionHandler? = null, allowedUpdates: List? = ALL_UPDATES_LIST, - autoDisableWebhooks: Boolean = true, + autoDisableWebhooks: Boolean = true ): Flow = longPollingFlow( timeoutSeconds = 0, exceptionsHandler = { @@ -122,7 +148,8 @@ fun TelegramBot.createAccumulatedUpdatesRetrieverFlow( } }, allowedUpdates = allowedUpdates, - autoDisableWebhooks = autoDisableWebhooks + autoDisableWebhooks = autoDisableWebhooks, + autoSkipTimeoutExceptions = false ).filter { !(it is InlineQueryUpdate && avoidInlineQueries || it is CallbackQueryUpdate && avoidCallbackQueries) } @@ -191,9 +218,18 @@ fun TelegramBot.longPolling( timeoutSeconds: Seconds = 30, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, exceptionsHandler: ExceptionHandler? = null ): Job = updatesFilter.run { - startGettingOfUpdatesByLongPolling(timeoutSeconds, scope, exceptionsHandler, allowedUpdates, autoDisableWebhooks, asUpdateReceiver) + startGettingOfUpdatesByLongPolling( + timeoutSeconds = timeoutSeconds, + scope = scope, + exceptionsHandler = exceptionsHandler, + allowedUpdates = allowedUpdates, + autoDisableWebhooks = autoDisableWebhooks, + autoSkipTimeoutExceptions = autoSkipTimeoutExceptions, + updatesReceiver = asUpdateReceiver + ) } /** @@ -208,8 +244,9 @@ fun TelegramBot.longPolling( exceptionsHandler: ExceptionHandler? = null, flowsUpdatesFilterUpdatesKeeperCount: Int = 100, autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, flowUpdatesPreset: FlowsUpdatesFilter.() -> Unit -): Job = longPolling(FlowsUpdatesFilter(flowsUpdatesFilterUpdatesKeeperCount).apply(flowUpdatesPreset), timeoutSeconds, scope, autoDisableWebhooks, exceptionsHandler) +): Job = longPolling(FlowsUpdatesFilter(flowsUpdatesFilterUpdatesKeeperCount).apply(flowUpdatesPreset), timeoutSeconds, scope, autoDisableWebhooks, autoSkipTimeoutExceptions, exceptionsHandler) fun RequestsExecutor.startGettingOfUpdatesByLongPolling( updatesFilter: UpdatesFilter, @@ -217,11 +254,13 @@ fun RequestsExecutor.startGettingOfUpdatesByLongPolling( exceptionsHandler: ExceptionHandler? = null, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), autoDisableWebhooks: Boolean = true, + autoSkipTimeoutExceptions: Boolean = true, ): Job = startGettingOfUpdatesByLongPolling( timeoutSeconds, scope, exceptionsHandler, updatesFilter.allowedUpdates, autoDisableWebhooks, + autoSkipTimeoutExceptions, updatesFilter.asUpdateReceiver )