From 45585caab013be4e34ec1a7d8dea9532cd00e478 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 8 May 2022 00:27:48 +0600 Subject: [PATCH] fix of #560 --- CHANGELOG.md | 3 + .../BehaviourContextWithFSM.kt | 5 +- .../behaviour_builder/BehaviourContext.kt | 23 +++-- .../CallbackQueryTriggers.kt | 54 ++++++++++-- .../CallbackQueryTriggersUnhandled.kt | 87 +++++++++++++++++++ .../triggers_handling/CommandHandling.kt | 66 ++++++++++---- .../CommandHandlingUnhandled.kt | 76 ++++++++++++++++ .../behaviour_builder/utils/SimpleFilter.kt | 1 + .../HandleableCallbackBasedHolder.kt | 5 ++ .../HandleableRegexesHolder.kt | 7 ++ .../HandleableTriggersHolder.kt | 27 ++++++ .../handlers_registrar/TriggersRegistrar.kt | 9 ++ 12 files changed, 333 insertions(+), 30 deletions(-) create mode 100644 tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggersUnhandled.kt create mode 100644 tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt create mode 100644 tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableCallbackBasedHolder.kt create mode 100644 tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableRegexesHolder.kt create mode 100644 tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableTriggersHolder.kt create mode 100644 tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/TriggersRegistrar.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index cd1fc2601a..91ad059a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.38.23 +* `BehaviourHandler`: + * Add support of fallback triggers (fix of [#560](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/560)) + ## 0.38.22 * `Core`: diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt index 2bb6b3ea89..efac220bd1 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt @@ -6,6 +6,7 @@ import dev.inmo.micro_utils.fsm.common.* import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.micro_utils.coroutines.accumulatorFlow +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* @@ -59,6 +60,7 @@ interface BehaviourContextWithFSM : BehaviourContext, StatesMachine?, + triggersHolder: TriggersHolder, updatesFilter: BehaviourContextAndTypeReceiver? ): BehaviourContextWithFSM @@ -178,9 +180,10 @@ class DefaultBehaviourContextWithFSM( broadcastChannelsSize: Int, onBufferOverflow: BufferOverflow, upstreamUpdatesFlow: Flow?, + triggersHolder: TriggersHolder, updatesFilter: BehaviourContextAndTypeReceiver? ): DefaultBehaviourContextWithFSM = BehaviourContextWithFSM( - behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter), + behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder, updatesFilter), handlers, statesManager ) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt index a5d9a0e73d..26482ab620 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt @@ -4,6 +4,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder import dev.inmo.micro_utils.coroutines.* import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.* import dev.inmo.tgbotapi.utils.RiskFeature @@ -47,12 +48,15 @@ interface BehaviourContext : FlowsUpdatesFilter, TelegramBot, CoroutineScope { val flowsUpdatesFilter: FlowsUpdatesFilter get() = this + val triggersHolder: TriggersHolder + fun copy( bot: TelegramBot = this.bot, scope: CoroutineScope = this.scope, broadcastChannelsSize: Int = 100, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND, upstreamUpdatesFlow: Flow? = null, + triggersHolder: TriggersHolder = TriggersHolder(), updatesFilter: BehaviourContextAndTypeReceiver? = null ): BehaviourContext } @@ -63,6 +67,7 @@ class DefaultBehaviourContext( broadcastChannelsSize: Int = 100, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND, private val upstreamUpdatesFlow: Flow? = null, + override val triggersHolder: TriggersHolder = TriggersHolder(), private val updatesFilter: BehaviourContextAndTypeReceiver? = null ) : AbstractFlowsUpdatesFilter(), TelegramBot by bot, CoroutineScope by scope, BehaviourContext { @@ -89,22 +94,25 @@ class DefaultBehaviourContext( broadcastChannelsSize: Int, onBufferOverflow: BufferOverflow, upstreamUpdatesFlow: Flow?, + triggersHolder: TriggersHolder, updatesFilter: BehaviourContextAndTypeReceiver? - ): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter) + ): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder, updatesFilter) } fun BehaviourContext( bot: TelegramBot, scope: CoroutineScope, - flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter() -) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow) + flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(), + triggersHolder: TriggersHolder = TriggersHolder(), +) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder) inline fun BehaviourContext( bot: TelegramBot, scope: CoroutineScope, flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(), + triggersHolder: TriggersHolder = TriggersHolder(), crossinline block: BehaviourContext.() -> T -) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow).run(block) +) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder).run(block) /** * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and @@ -115,6 +123,7 @@ suspend fun BC.doInSubContextWithUpdatesFilter( stopOnCompletion: Boolean = true, updatesUpstreamFlow: Flow = allUpdatesFlow, scope: CoroutineScope = LinkedSupervisorScope(), + triggersHolder: TriggersHolder = this.triggersHolder, behaviourContextReceiver: CustomBehaviourContextReceiver ): T { val newContext = copy( @@ -126,7 +135,8 @@ suspend fun BC.doInSubContextWithUpdatesFilter( } ?: true } }, - upstreamUpdatesFlow = updatesUpstreamFlow + upstreamUpdatesFlow = updatesUpstreamFlow, + triggersHolder = triggersHolder ) as BC return withContext(currentCoroutineContext()) { newContext.behaviourContextReceiver().also { if (stopOnCompletion) newContext.stop() } @@ -137,8 +147,9 @@ suspend fun BehaviourContext.doInSubContext( stopOnCompletion: Boolean = true, updatesUpstreamFlow: Flow = allUpdatesFlow, scope: CoroutineScope = LinkedSupervisorScope(), + triggersHolder: TriggersHolder = this.triggersHolder, behaviourContextReceiver: BehaviourContextReceiver -) = doInSubContextWithUpdatesFilter(updatesFilter = null, stopOnCompletion, updatesUpstreamFlow, scope, behaviourContextReceiver) +) = doInSubContextWithUpdatesFilter(updatesFilter = null, stopOnCompletion, updatesUpstreamFlow, scope, triggersHolder, behaviourContextReceiver) /** * This method will cancel ALL subsequent contexts, expectations and waiters diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt index 4689320dd2..4b796d51dd 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt @@ -2,15 +2,17 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling +import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser -import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.* import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserCallbackQueryMarkerFactory import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory -import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus import dev.inmo.tgbotapi.extensions.utils.asCallbackQueryUpdate import dev.inmo.tgbotapi.types.CallbackQuery.* import dev.inmo.tgbotapi.types.update.abstracts.Update +import kotlinx.coroutines.Job internal suspend inline fun BC.onCallbackQuery( noinline initialFilter: SimpleFilter? = null, @@ -21,6 +23,48 @@ internal suspend inline fun B (it.asCallbackQueryUpdate() ?.data as? T) ?.let(::listOfNotNull) } +/** + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +internal suspend inline fun BC.onDataCallbackQueryCounted( + noinline initialFilter: SimpleFilter? = null, + noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, + markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, + noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver +): Job { + val newInitialFilter = SimpleFilter { + it is T && initialFilter ?.invoke(it) ?: true + } + return runCatchingSafely { + onCallbackQuery ( + initialFilter, + subcontextUpdatesFilter, + markerFactory, + scenarioReceiver + ) + }.onFailure { + triggersHolder.handleableCallbackQueriesDataHolder.unregisterHandleable(newInitialFilter) + }.onSuccess { + triggersHolder.handleableCallbackQueriesDataHolder.registerHandleable(newInitialFilter) + it.invokeOnCompletion { + runCatching { + launchSafelyWithoutExceptions { + triggersHolder.handleableCallbackQueriesDataHolder.unregisterHandleable(newInitialFilter) + } + } + } + }.getOrThrow() +} + /** * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, @@ -38,7 +82,7 @@ suspend fun BC.onDataCallbackQuery( subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, scenarioReceiver: CustomBehaviourContextAndTypeReceiver -) = onCallbackQuery( +) = onDataCallbackQueryCounted( initialFilter, subcontextUpdatesFilter, markerFactory, @@ -166,7 +210,7 @@ suspend fun BC.onInlineMessageIdDataCallbackQuery( subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, scenarioReceiver: CustomBehaviourContextAndTypeReceiver -) = onCallbackQuery( +) = onDataCallbackQueryCounted( initialFilter, subcontextUpdatesFilter, markerFactory, @@ -294,7 +338,7 @@ suspend fun BC.onMessageDataCallbackQuery( subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, scenarioReceiver: CustomBehaviourContextAndTypeReceiver -) = onCallbackQuery( +) = onDataCallbackQueryCounted( initialFilter, subcontextUpdatesFilter, markerFactory, diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggersUnhandled.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggersUnhandled.kt new file mode 100644 index 0000000000..d370bb52da --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggersUnhandled.kt @@ -0,0 +1,87 @@ +@file:Suppress("unused") + +package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling + +import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.runCatchingSafely +import dev.inmo.tgbotapi.extensions.behaviour_builder.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserCallbackQueryMarkerFactory +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory +import dev.inmo.tgbotapi.extensions.utils.asCallbackQueryUpdate +import dev.inmo.tgbotapi.types.CallbackQuery.* +import dev.inmo.tgbotapi.types.update.abstracts.Update +import kotlinx.coroutines.Job + +/** + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +suspend fun BC.onUnhandledDataCallbackQuery( + initialFilter: SimpleFilter? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, + markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver +) = onCallbackQuery ( + initialFilter * !SimpleFilter { triggersHolder.handleableCallbackQueriesDataHolder.isHandled(it) }, + subcontextUpdatesFilter, + markerFactory, + scenarioReceiver +) + +/** + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +suspend fun BC.onUnhandledInlineMessageIdDataCallbackQuery( + initialFilter: SimpleFilter? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, + markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver +) = onCallbackQuery ( + initialFilter * !SimpleFilter { triggersHolder.handleableCallbackQueriesDataHolder.isHandled(it) }, + subcontextUpdatesFilter, + markerFactory, + scenarioReceiver +) + +/** + * @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call + * @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example, + * this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage]. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own. + * Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times] + * to combinate several filters + * @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously + * in one "stream". Output of [markerFactory] will be used as a key for "stream" + * @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that + * data + */ +suspend fun BC.onUnhandledMessageDataCallbackQuery( + initialFilter: SimpleFilter? = null, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = CallbackQueryFilterByUser, + markerFactory: MarkerFactory = ByUserCallbackQueryMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver +) = onCallbackQuery( + initialFilter * !SimpleFilter { triggersHolder.handleableCallbackQueriesDataHolder.isHandled(it) }, + subcontextUpdatesFilter, + markerFactory, + scenarioReceiver +) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt index ad04efa0f2..b1943ce5ec 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt @@ -2,6 +2,8 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling +import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat @@ -15,8 +17,7 @@ import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.update.abstracts.Update import kotlinx.coroutines.Job - -suspend fun BC.command( +internal suspend fun BC.commandUncounted( commandRegex: Regex, requireOnlyCommandInMessage: Boolean = true, initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, @@ -43,6 +44,35 @@ suspend fun BC.command( scenarioReceiver ) +suspend fun BC.command( + commandRegex: Regex, + requireOnlyCommandInMessage: Boolean = true, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +): Job = runCatchingSafely { + commandUncounted( + commandRegex, + requireOnlyCommandInMessage, + initialFilter, + subcontextUpdatesFilter, + markerFactory, + scenarioReceiver + ) +}.onFailure { + triggersHolder.handleableCommandsHolder.unregisterHandleable(commandRegex) +}.onSuccess { + triggersHolder.handleableCommandsHolder.registerHandleable(commandRegex) + it.invokeOnCompletion { + runCatching { + launchSafelyWithoutExceptions { + triggersHolder.handleableCommandsHolder.unregisterHandleable(commandRegex) + } + } + } +}.getOrThrow() + suspend fun BC.command( command: String, requireOnlyCommandInMessage: Boolean = true, @@ -52,22 +82,22 @@ suspend fun BC.command( scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ) = command(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) -suspend inline fun BC.onCommand( +suspend fun BC.onCommand( commandRegex: Regex, requireOnlyCommandInMessage: Boolean = true, - noinline initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, - noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver> + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ): Job = command(commandRegex, requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) -suspend inline fun BC.onCommand( +suspend fun BC.onCommand( command: String, requireOnlyCommandInMessage: Boolean = true, - noinline initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, - noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver> + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> ): Job = onCommand(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) suspend fun BC.commandWithArgs( @@ -104,18 +134,18 @@ suspend fun BC.commandWithArgs( scenarioReceiver = scenarioReceiver ) -suspend inline fun BC.onCommandWithArgs( +suspend fun BC.onCommandWithArgs( commandRegex: Regex, - noinline initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, - noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - noinline scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver, Array> + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver, Array> ): Job = commandWithArgs(commandRegex, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) -suspend inline fun BC.onCommandWithArgs( +suspend fun BC.onCommandWithArgs( command: String, - noinline initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, - noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, - noinline scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver, Array> + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver, Array> ): Job = onCommandWithArgs(command.toRegex(), initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt new file mode 100644 index 0000000000..6126b19f5f --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt @@ -0,0 +1,76 @@ +@file:Suppress("unused") + +package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling + +import dev.inmo.tgbotapi.extensions.behaviour_builder.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups +import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatMessageMarkerFactory +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times +import dev.inmo.tgbotapi.extensions.utils.asBotCommandTextSource +import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams +import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage +import dev.inmo.tgbotapi.types.message.content.TextContent +import dev.inmo.tgbotapi.types.update.abstracts.Update +import kotlinx.coroutines.Job + + +suspend fun BC.unhandledCommand( + requireOnlyCommandInMessage: Boolean = true, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +): Job = onText( + CommonMessageFilter { message -> + val content = message.content + val textSources = content.textSources + val sizeRequirement = if (requireOnlyCommandInMessage) { + textSources.size == 1 + } else { + true + } + sizeRequirement && textSources.any { + val command = it.asBotCommandTextSource() ?.command ?: return@any false + !triggersHolder.handleableCommandsHolder.isHandled(command) + } + }.let { + initialFilter ?.times(it) ?: it + }, + subcontextUpdatesFilter, + markerFactory, + scenarioReceiver +) + +suspend fun BC.onUnhandledCommand( + requireOnlyCommandInMessage: Boolean = true, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTypeReceiver> +): Job = unhandledCommand(requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) + +suspend fun BC.unhandledCommandWithArgs( + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver, Map>> +) = onUnhandledCommand( + requireOnlyCommandInMessage = false, + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory +) { + val args = it.parseCommandsWithParams().let { commandsWithArgs -> + commandsWithArgs + } + scenarioReceiver(it, args) +} + +suspend fun BC.onUnhandledCommandWithArgs( + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver, Update> = MessageFilterByChat, + markerFactory: MarkerFactory, Any> = ByChatMessageMarkerFactory, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver, Map>> +): Job = unhandledCommandWithArgs(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/SimpleFilter.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/SimpleFilter.kt index 6f12abd39c..9c72aea0ad 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/SimpleFilter.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/SimpleFilter.kt @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.utils typealias SimpleFilter = suspend (T) -> Boolean inline fun SimpleFilter(noinline block: SimpleFilter) = block +val TrueSimpleFilter = SimpleFilter { true } /** * @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableCallbackBasedHolder.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableCallbackBasedHolder.kt new file mode 100644 index 0000000000..bf5b87c8e6 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableCallbackBasedHolder.kt @@ -0,0 +1,5 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar + +class HandleableCallbackBasedHolder : HandleableTriggersHolder Boolean>() { + suspend fun isHandled(data: T) = handleable.any { it(data) } +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableRegexesHolder.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableRegexesHolder.kt new file mode 100644 index 0000000000..5497d4e0b8 --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableRegexesHolder.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar + +class HandleableRegexesHolder : HandleableTriggersHolder() { + fun isHandled(command: String) = handleable.any { + it.matches(command) + } +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableTriggersHolder.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableTriggersHolder.kt new file mode 100644 index 0000000000..a182a7eced --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/HandleableTriggersHolder.kt @@ -0,0 +1,27 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar + +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +open class HandleableTriggersHolder( + preset: List = emptyList() +) { + protected val commandsMutex = Mutex() + protected val _handleable = mutableListOf().also { + it.addAll(preset) + } + val handleable: List + get() = _handleable.toList() + + suspend fun registerHandleable(data: T) { + commandsMutex.withLock { + _handleable.add(data) + } + } + + suspend fun unregisterHandleable(data: T) { + commandsMutex.withLock { + _handleable.remove(data) + } + } +} diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/TriggersRegistrar.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/TriggersRegistrar.kt new file mode 100644 index 0000000000..1f70e7571e --- /dev/null +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/utils/handlers_registrar/TriggersRegistrar.kt @@ -0,0 +1,9 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar + +import dev.inmo.tgbotapi.types.CallbackQuery.DataCallbackQuery +import dev.inmo.tgbotapi.types.CallbackQuery.MessageDataCallbackQuery + +class TriggersHolder { + val handleableCommandsHolder = HandleableRegexesHolder() + val handleableCallbackQueriesDataHolder = HandleableCallbackBasedHolder() +}