diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5dfe89dc..49f696735e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,17 @@ * `Version`: * `MicroUtils`: `0.4.16` -> `0.4.19` * `Core`: - * **BC** Now `MediaGroupMessage` have a generic type related to `MediaGroupContent` + * **BREAKING CHANGE** Now `MediaGroupMessage` have a generic type related to `MediaGroupContent` * Methods and types related to `MediaGroupMessage` have been modified according to their meanings + * **Important Change** `FlowsUpdatesFilter` now is an interface. Old class has been renamed to + `DefaultFlowsUpdatesFilter` and factory method `FlowsUpdatesFilter` has been added +* `Behaviour Builder`: + * Trigger and expectation extensions for `MessageContent` (`onContentMessage` and `waitContentMessage`) + * `onMediaGroup` has been replaced + * `waitMediaGroup` has been added + * `onVisualMediaGroup` now is just an alternative to `onVisualGallery` + * `command` and `onCommand` expectations has been added for commands `String` variant + * New extensions `BehaviourContext#oneOf`, `BehaviourContext#parallel` and `Deferred#withAction` ## 0.31.0 diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt index e44d863dd5..b07bc97cf6 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/updateshandlers/FlowsUpdatesFilter.kt @@ -7,15 +7,47 @@ import dev.inmo.tgbotapi.types.update.abstracts.UnknownUpdate import dev.inmo.tgbotapi.types.update.abstracts.Update import kotlinx.coroutines.flow.* -@Suppress("EXPERIMENTAL_API_USAGE", "unused") -class FlowsUpdatesFilter( +interface FlowsUpdatesFilter : UpdatesFilter { + override val allowedUpdates: List + get() = ALL_UPDATES_LIST + val allUpdatesFlow: Flow + val allUpdatesWithoutMediaGroupsGroupingFlow: Flow + + val messageFlow: Flow + val messageMediaGroupFlow: Flow + val editedMessageFlow: Flow + val editedMessageMediaGroupFlow: Flow + val channelPostFlow: Flow + val channelPostMediaGroupFlow: Flow + val editedChannelPostFlow: Flow + val editedChannelPostMediaGroupFlow: Flow + val chosenInlineResultFlow: Flow + val inlineQueryFlow: Flow + val callbackQueryFlow: Flow + val shippingQueryFlow: Flow + val preCheckoutQueryFlow: Flow + val pollFlow: Flow + val pollAnswerFlow: Flow + val unknownUpdateTypeFlow: Flow +} + +/** + * Creates [DefaultFlowsUpdatesFilter] + */ +@Suppress("FunctionName") +fun FlowsUpdatesFilter( broadcastChannelsSize: Int = 100 -): UpdatesFilter { +) = DefaultFlowsUpdatesFilter(broadcastChannelsSize) + +@Suppress("EXPERIMENTAL_API_USAGE", "unused") +class DefaultFlowsUpdatesFilter( + broadcastChannelsSize: Int = 100 +): FlowsUpdatesFilter { private val updatesSharedFlow = MutableSharedFlow(extraBufferCapacity = broadcastChannelsSize) @Suppress("MemberVisibilityCanBePrivate") - val allUpdatesFlow: Flow = updatesSharedFlow.asSharedFlow() + override val allUpdatesFlow: Flow = updatesSharedFlow.asSharedFlow() @Suppress("MemberVisibilityCanBePrivate") - val allUpdatesWithoutMediaGroupsGroupingFlow: Flow = updatesSharedFlow.flatMapConcat { + override val allUpdatesWithoutMediaGroupsGroupingFlow: Flow = allUpdatesFlow.flatMapConcat { when (it) { is SentMediaGroupUpdate -> it.origins.asFlow() is EditMediaGroupUpdate -> flowOf(it.origin) @@ -23,26 +55,24 @@ class FlowsUpdatesFilter( } } - override val allowedUpdates: List - get() = ALL_UPDATES_LIST override val asUpdateReceiver: UpdateReceiver = { updatesSharedFlow.emit(it) } - val messageFlow: Flow = allUpdatesFlow.filterIsInstance() - val messageMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() - val editedMessageFlow: Flow = allUpdatesFlow.filterIsInstance() - val editedMessageMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() - val channelPostFlow: Flow = allUpdatesFlow.filterIsInstance() - val channelPostMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() - val editedChannelPostFlow: Flow = allUpdatesFlow.filterIsInstance() - val editedChannelPostMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() - val chosenInlineResultFlow: Flow = allUpdatesFlow.filterIsInstance() - val inlineQueryFlow: Flow = allUpdatesFlow.filterIsInstance() - val callbackQueryFlow: Flow = allUpdatesFlow.filterIsInstance() - val shippingQueryFlow: Flow = allUpdatesFlow.filterIsInstance() - val preCheckoutQueryFlow: Flow = allUpdatesFlow.filterIsInstance() - val pollFlow: Flow = allUpdatesFlow.filterIsInstance() - val pollAnswerFlow: Flow = allUpdatesFlow.filterIsInstance() - val unknownUpdateTypeFlow: Flow = allUpdatesFlow.filterIsInstance() + override val messageFlow: Flow = allUpdatesFlow.filterIsInstance() + override val messageMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() + override val editedMessageFlow: Flow = allUpdatesFlow.filterIsInstance() + override val editedMessageMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() + override val channelPostFlow: Flow = allUpdatesFlow.filterIsInstance() + override val channelPostMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() + override val editedChannelPostFlow: Flow = allUpdatesFlow.filterIsInstance() + override val editedChannelPostMediaGroupFlow: Flow = allUpdatesFlow.filterIsInstance() + override val chosenInlineResultFlow: Flow = allUpdatesFlow.filterIsInstance() + override val inlineQueryFlow: Flow = allUpdatesFlow.filterIsInstance() + override val callbackQueryFlow: Flow = allUpdatesFlow.filterIsInstance() + override val shippingQueryFlow: Flow = allUpdatesFlow.filterIsInstance() + override val preCheckoutQueryFlow: Flow = allUpdatesFlow.filterIsInstance() + override val pollFlow: Flow = allUpdatesFlow.filterIsInstance() + override val pollAnswerFlow: Flow = allUpdatesFlow.filterIsInstance() + override val unknownUpdateTypeFlow: Flow = allUpdatesFlow.filterIsInstance() } \ No newline at end of file diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt index 5d3afb72a1..d881a5f1ba 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContext.kt @@ -1,9 +1,11 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter -import dev.inmo.tgbotapi.updateshandlers.UpdatesFilter -import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.filter typealias BehaviourContextReceiver = suspend BehaviourContext.() -> T typealias BehaviourContextAndTypeReceiver = suspend BehaviourContext.(I) -> T @@ -19,4 +21,48 @@ data class BehaviourContext( val bot: TelegramBot, val scope: CoroutineScope, val flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter() -) : UpdatesFilter by flowsUpdatesFilter, TelegramBot by bot, CoroutineScope by scope +) : FlowsUpdatesFilter by flowsUpdatesFilter, TelegramBot by bot, CoroutineScope by scope + +/** + * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [newFlowsUpdatesFilterSetUp] is provided and + * [CoroutineScope] as new [BehaviourContext.scope] + * + * @param newFlowsUpdatesFilterSetUp As a parameter receives [FlowsUpdatesFilter] from old [this] [BehaviourContext.flowsUpdatesFilter] + */ +suspend fun BehaviourContext.doInSubContextWithFlowsUpdatesFilterSetup( + newFlowsUpdatesFilterSetUp: BehaviourContextAndTypeReceiver?, + behaviourContextReceiver: BehaviourContextReceiver +) = copy( + flowsUpdatesFilter = FlowsUpdatesFilter(), + scope = CoroutineScope(scope.coroutineContext + SupervisorJob()) +).run { + newFlowsUpdatesFilterSetUp ?.let { + it.apply { invoke(this@run, this@doInSubContextWithFlowsUpdatesFilterSetup.flowsUpdatesFilter) } + } + behaviourContextReceiver().also { stop() } +} + +/** + * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and + * [CoroutineScope] as new [BehaviourContext.scope] + */ +suspend fun BehaviourContext.doInSubContextWithUpdatesFilter( + updatesFilter: BehaviourContextAndTypeReceiver?, + behaviourContextReceiver: BehaviourContextReceiver +) = doInSubContextWithFlowsUpdatesFilterSetup( + newFlowsUpdatesFilterSetUp = updatesFilter ?.let { + { oldOne -> + oldOne.allUpdatesFlow.filter { updatesFilter(it) }.subscribeSafelyWithoutExceptions(scope, asUpdateReceiver) + } + }, + behaviourContextReceiver +) + +suspend fun BehaviourContext.doInSubContext( + behaviourContextReceiver: BehaviourContextReceiver +) = doInSubContextWithFlowsUpdatesFilterSetup(newFlowsUpdatesFilterSetUp = null, behaviourContextReceiver) + +/** + * This method will cancel ALL subsequent contexts, expectations and waiters + */ +fun BehaviourContext.stop() = scope.cancel() diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/Variants.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/Variants.kt new file mode 100644 index 0000000000..4289c347a5 --- /dev/null +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/Variants.kt @@ -0,0 +1,21 @@ +package dev.inmo.tgbotapi.extensions.behaviour_builder + +import dev.inmo.micro_utils.coroutines.DeferredAction +import dev.inmo.micro_utils.coroutines.invokeFirstOf +import kotlinx.coroutines.* + +suspend fun BehaviourContext.parallel( + action: BehaviourContextReceiver +) = async { + action() +} + +inline infix fun Deferred.withAction(noinline callback: suspend (T) -> O) = DeferredAction(this, callback) + +suspend fun BehaviourContext.oneOf( + deferredActions: Iterable> +) = deferredActions.invokeFirstOf(scope) + +suspend fun BehaviourContext.oneOf( + vararg deferredActions: DeferredAction<*, O> +) = oneOf(deferredActions.toList()) diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitContent.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitContent.kt index 26a55849df..fe5cdbf153 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitContent.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitContent.kt @@ -59,6 +59,12 @@ private suspend inline fun BehaviourContext.waitCon } } +suspend fun BehaviourContext.waitContentMessage( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: CommonMessageToContentMapper? = null +) = waitContent(count, initRequest, false, errorFactory, filter) suspend fun BehaviourContext.waitContact( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, @@ -101,14 +107,14 @@ suspend fun BehaviourContext.waitVenue( count: Int = 1, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, false, errorFactory, filter) -suspend fun BehaviourContext.waitAudioMediaGroup( +suspend fun BehaviourContext.waitAudioMediaGroupContent( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, includeMediaGroups: Boolean = true, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) -suspend fun BehaviourContext.waitDocumentMediaGroup( +suspend fun BehaviourContext.waitDocumentMediaGroupContent( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, @@ -119,17 +125,17 @@ suspend fun BehaviourContext.waitMedia( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) -suspend fun BehaviourContext.waitMediaGroup( +suspend fun BehaviourContext.waitAnyMediaGroupContent( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, includeMediaGroups: Boolean = true, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) -suspend fun BehaviourContext.waitVisualMediaGroup( +suspend fun BehaviourContext.waitVisualMediaGroupContent( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, @@ -146,21 +152,21 @@ suspend fun BehaviourContext.waitAudio( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) suspend fun BehaviourContext.waitDocument( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) suspend fun BehaviourContext.waitPhoto( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) suspend fun BehaviourContext.waitSticker( @@ -173,7 +179,7 @@ suspend fun BehaviourContext.waitVideo( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, filter: CommonMessageToContentMapper? = null ) = waitContent(count, initRequest, includeMediaGroups, errorFactory, filter) suspend fun BehaviourContext.waitVideoNote( diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitMediaGroup.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitMediaGroup.kt index 0ed50f8add..5aa0bc8e90 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitMediaGroup.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitMediaGroup.kt @@ -12,7 +12,7 @@ import kotlinx.coroutines.flow.take import kotlinx.coroutines.flow.toList @PreviewFeature -internal suspend inline fun BehaviourContext.onMediaGroup( +internal suspend inline fun BehaviourContext.buildMediaGroupWaiter( count: Int = 1, initRequest: Request<*>? = null, noinline errorFactory: NullableRequestBuilder<*> = { null }, @@ -21,7 +21,7 @@ internal suspend inline fun BehaviourContext.onM update.asSentMediaGroupUpdate() ?.data ?.let { mediaGroup -> if (mediaGroup.all { message -> message.content is T } && (filter == null || filter(mediaGroup as List>))) { listOf( - mediaGroup.map { it.content as T } as List> + mediaGroup.map { it.content as T } ) } else { null @@ -29,33 +29,39 @@ internal suspend inline fun BehaviourContext.onM } ?: emptyList() }.take(count).toList() +suspend fun BehaviourContext.waitMediaGroup( + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + count: Int = 1, + filter: (suspend (List>) -> Boolean)? = null +) = buildMediaGroupWaiter(count, initRequest, errorFactory, filter) suspend fun BehaviourContext.waitPlaylist( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, filter: (suspend (List>) -> Boolean)? = null -) = onMediaGroup(count, initRequest, errorFactory, filter) +) = buildMediaGroupWaiter(count, initRequest, errorFactory, filter) suspend fun BehaviourContext.waitDocumentsGroup( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, filter: (suspend (List>) -> Boolean)? = null -) = onMediaGroup(count, initRequest, errorFactory, filter) +) = buildMediaGroupWaiter(count, initRequest, errorFactory, filter) suspend fun BehaviourContext.waitVisualGallery( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, filter: (suspend (List>) -> Boolean)? = null -) = onMediaGroup(count, initRequest, errorFactory, filter) +) = buildMediaGroupWaiter(count, initRequest, errorFactory, filter) suspend fun BehaviourContext.waitPhotoGallery( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, filter: (suspend (List>) -> Boolean)? = null -) = onMediaGroup(count, initRequest, errorFactory, filter) +) = buildMediaGroupWaiter(count, initRequest, errorFactory, filter) suspend fun BehaviourContext.waitVideoGallery( initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null }, count: Int = 1, filter: (suspend (List>) -> Boolean)? = null -) = onMediaGroup(count, initRequest, errorFactory, filter) +) = buildMediaGroupWaiter(count, initRequest, errorFactory, filter) diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt index 5432c7fa58..d66c64eab8 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CallbackQueryTriggers.kt @@ -1,18 +1,12 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling -import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat import dev.inmo.tgbotapi.types.CallbackQuery.* -import dev.inmo.tgbotapi.types.message.ChatEvents.* -import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.* -import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter -import kotlinx.coroutines.flow.filter internal suspend inline fun BehaviourContext.onCallbackQuery( includeFilterByChatInBehaviourSubContext: Boolean = true, @@ -27,19 +21,15 @@ internal suspend inline fun BehaviourContext.onCallb } }.let(::listOfNotNull) }.subscribeSafelyWithoutExceptions(scope) { triggerQuery -> - val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) { - val subFilter = FlowsUpdatesFilter() - val subBehaviourContext = copy(flowsUpdatesFilter = subFilter) - - flowsUpdatesFilter.allUpdatesFlow.filter { - val chat = it.sourceChat() ?: return@filter false - chat.id.chatId == triggerQuery.user.id.chatId - }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext - } else { - null to this + doInSubContextWithUpdatesFilter( + updatesFilter = if (includeFilterByChatInBehaviourSubContext) { + { it.sourceChat() ?.id ?.chatId == triggerQuery.user.id.chatId } + } else { + null + } + ) { + scenarioReceiver(triggerQuery) } - safelyWithoutExceptions { scenario.scenarioReceiver(triggerQuery) } - jobToCancel ?.cancel() } diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt index ab7ebe9060..35c88e1f0e 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt @@ -26,6 +26,12 @@ suspend fun BehaviourContext.command( }, scenarioReceiver ) +suspend fun BehaviourContext.command( + command: String, + requireOnlyCommandInMessage: Boolean = true, + includeFilterByChatInBehaviourSubContext: Boolean = true, + scenarioReceiver: BehaviourContextAndTypeReceiver> +) = command(command.toRegex(), requireOnlyCommandInMessage, includeFilterByChatInBehaviourSubContext, scenarioReceiver) suspend inline fun BehaviourContext.onCommand( commandRegex: Regex, @@ -33,3 +39,10 @@ suspend inline fun BehaviourContext.onCommand( includeFilterByChatInBehaviourSubContext: Boolean = true, noinline scenarioReceiver: BehaviourContextAndTypeReceiver> ): Job = command(commandRegex, requireOnlyCommandInMessage, includeFilterByChatInBehaviourSubContext, scenarioReceiver) + +suspend inline fun BehaviourContext.onCommand( + command: String, + requireOnlyCommandInMessage: Boolean = true, + includeFilterByChatInBehaviourSubContext: Boolean = true, + noinline scenarioReceiver: BehaviourContextAndTypeReceiver> +): Job = onCommand(command.toRegex(), requireOnlyCommandInMessage, includeFilterByChatInBehaviourSubContext, scenarioReceiver) diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ContentTriggers.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ContentTriggers.kt index a12e414286..591686026e 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ContentTriggers.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/ContentTriggers.kt @@ -2,11 +2,8 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling - -import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat @@ -16,9 +13,7 @@ import dev.inmo.tgbotapi.types.message.content.* import dev.inmo.tgbotapi.types.message.content.abstracts.* import dev.inmo.tgbotapi.types.message.content.media.* import dev.inmo.tgbotapi.types.message.payments.InvoiceContent -import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.PreviewFeature -import kotlinx.coroutines.flow.filter typealias CommonMessageFilter = (suspend (CommonMessage) -> Boolean) @@ -50,21 +45,22 @@ internal suspend inline fun BehaviourContext.onCont } }.let(::listOfNotNull) }.subscribeSafelyWithoutExceptions(scope) { triggerMessage -> - val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) { - val subFilter = FlowsUpdatesFilter() - val subBehaviourContext = copy(flowsUpdatesFilter = subFilter) - - flowsUpdatesFilter.allUpdatesFlow.filter { - val chat = it.sourceChat() ?: return@filter false - chat.id.chatId == triggerMessage.chat.id.chatId - }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext - } else { - null to this + doInSubContextWithUpdatesFilter( + updatesFilter = if (includeFilterByChatInBehaviourSubContext) { + { it.sourceChat() ?.id ?.chatId == triggerMessage.chat.id.chatId } + } else { + null + } + ) { + scenarioReceiver(triggerMessage) } - safelyWithoutExceptions { scenario.scenarioReceiver(triggerMessage) } - jobToCancel ?.cancel() } +suspend fun BehaviourContext.onContentMessage( + includeFilterByChatInBehaviourSubContext: Boolean = true, + additionalFilter: CommonMessageFilter? = null, + scenarioReceiver: BehaviourContextAndTypeReceiver> +) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onContact( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: CommonMessageFilter? = null, @@ -105,7 +101,7 @@ suspend fun BehaviourContext.onAudioMediaGroup( additionalFilter: CommonMessageFilter? = null, scenarioReceiver: BehaviourContextAndTypeReceiver> ) = onContent(includeFilterByChatInBehaviourSubContext, true, additionalFilter, scenarioReceiver) -suspend fun BehaviourContext.onDocumentMediaGroup( +suspend fun BehaviourContext.onDocumentMediaGroupContent( includeFilterByChatInBehaviourSubContext: Boolean = true, includeMediaGroups: Boolean = true, additionalFilter: CommonMessageFilter? = null, @@ -113,7 +109,7 @@ suspend fun BehaviourContext.onDocumentMediaGroup( ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onMediaCollection( includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, additionalFilter: (suspend (CommonMessage>) -> Boolean)? = null, scenarioReceiver: BehaviourContextAndTypeReceiver>> ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) @@ -123,18 +119,6 @@ suspend fun BehaviourContext.onMedia( additionalFilter: CommonMessageFilter? = null, scenarioReceiver: BehaviourContextAndTypeReceiver> ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) -suspend fun BehaviourContext.onMediaGroup( - includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, - additionalFilter: CommonMessageFilter? = null, - scenarioReceiver: BehaviourContextAndTypeReceiver> -) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) -suspend fun BehaviourContext.onVisualMediaGroup( - includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, - additionalFilter: CommonMessageFilter? = null, - scenarioReceiver: BehaviourContextAndTypeReceiver> -) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onAnimation( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: CommonMessageFilter? = null, @@ -142,19 +126,19 @@ suspend fun BehaviourContext.onAnimation( ) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onAudio( includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, additionalFilter: CommonMessageFilter? = null, scenarioReceiver: BehaviourContextAndTypeReceiver> ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onDocument( includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, additionalFilter: CommonMessageFilter? = null, scenarioReceiver: BehaviourContextAndTypeReceiver> ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onPhoto( includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, additionalFilter: CommonMessageFilter? = null, scenarioReceiver: BehaviourContextAndTypeReceiver> ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) @@ -165,7 +149,7 @@ suspend fun BehaviourContext.onSticker( ) = onContent(includeFilterByChatInBehaviourSubContext, false, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onVideo( includeFilterByChatInBehaviourSubContext: Boolean = true, - includeMediaGroups: Boolean = true, + includeMediaGroups: Boolean = false, additionalFilter: CommonMessageFilter? = null, scenarioReceiver: BehaviourContextAndTypeReceiver> ) = onContent(includeFilterByChatInBehaviourSubContext, includeMediaGroups, additionalFilter, scenarioReceiver) diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt index c3a9d1b0da..d5b9404550 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/EventTriggers.kt @@ -1,21 +1,14 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling -import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat import dev.inmo.tgbotapi.types.message.ChatEvents.* import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.* import dev.inmo.tgbotapi.types.message.abstracts.ChatEventMessage -import dev.inmo.tgbotapi.types.message.content.* -import dev.inmo.tgbotapi.types.message.content.abstracts.* -import dev.inmo.tgbotapi.types.message.content.media.* -import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter -import kotlinx.coroutines.flow.filter internal suspend inline fun BehaviourContext.onEvent( includeFilterByChatInBehaviourSubContext: Boolean = true, @@ -31,19 +24,13 @@ internal suspend inline fun BehaviourContext.onEvent( } }.let(::listOfNotNull) }.subscribeSafelyWithoutExceptions(scope) { triggerMessage -> - val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) { - val subFilter = FlowsUpdatesFilter() - val subBehaviourContext = copy(flowsUpdatesFilter = subFilter) - - flowsUpdatesFilter.allUpdatesFlow.filter { - val chat = it.sourceChat() ?: return@filter false - chat.id.chatId == triggerMessage.chat.id.chatId - }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext - } else { - null to this + doInSubContextWithUpdatesFilter( + updatesFilter = if (includeFilterByChatInBehaviourSubContext) { + { it.sourceChat() ?.id ?.chatId == triggerMessage.chat.id.chatId } + } else null + ) { + scenarioReceiver(triggerMessage) } - safelyWithoutExceptions { scenario.scenarioReceiver(triggerMessage) } - jobToCancel ?.cancel() } suspend fun BehaviourContext.onChannelEvent( diff --git a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/MediaGroupTriggers.kt b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/MediaGroupTriggers.kt index 6e195fddbc..639f2cbeff 100644 --- a/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/MediaGroupTriggers.kt +++ b/tgbotapi.extensions.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/MediaGroupTriggers.kt @@ -2,10 +2,8 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling -import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTypeReceiver +import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.expectFlow import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat @@ -13,12 +11,10 @@ import dev.inmo.tgbotapi.extensions.utils.shortcuts.chat import dev.inmo.tgbotapi.types.message.abstracts.MediaGroupMessage import dev.inmo.tgbotapi.types.message.content.abstracts.* import dev.inmo.tgbotapi.types.message.content.media.* -import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter import dev.inmo.tgbotapi.utils.PreviewFeature -import kotlinx.coroutines.flow.filter @PreviewFeature -internal suspend inline fun BehaviourContext.onMediaGroup( +internal suspend inline fun BehaviourContext.buildMediaGroupTrigger( includeFilterByChatInBehaviourSubContext: Boolean = true, noinline additionalFilter: (suspend (List>) -> Boolean)? = null, noinline scenarioReceiver: BehaviourContextAndTypeReceiver>> @@ -31,45 +27,49 @@ internal suspend inline fun BehaviourContext.onM } } ?: emptyList() }.subscribeSafelyWithoutExceptions(scope) { mediaGroup -> - val (jobToCancel, scenario) = if (includeFilterByChatInBehaviourSubContext) { - val subFilter = FlowsUpdatesFilter() - val subBehaviourContext = copy(flowsUpdatesFilter = subFilter) - val mediaGroupChat = mediaGroup.chat!! - - flowsUpdatesFilter.allUpdatesFlow.filter { - val chat = it.sourceChat() ?: return@filter false - chat.id.chatId == mediaGroupChat.id.chatId - }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subBehaviourContext - } else { - null to this + val mediaGroupChat = mediaGroup.chat!! + doInSubContextWithUpdatesFilter( + updatesFilter = if (includeFilterByChatInBehaviourSubContext) { + { it.sourceChat() ?.id ?.chatId == mediaGroupChat.id.chatId } + } else null + ) { + scenarioReceiver(mediaGroup) } - safelyWithoutExceptions { scenario.scenarioReceiver(mediaGroup) } - jobToCancel ?.cancel() } +suspend fun BehaviourContext.onMediaGroup( + includeFilterByChatInBehaviourSubContext: Boolean = true, + additionalFilter: (suspend (List>) -> Boolean)? = null, + scenarioReceiver: BehaviourContextAndTypeReceiver>> +) = buildMediaGroupTrigger(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onPlaylist( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: (suspend (List>) -> Boolean)? = null, scenarioReceiver: BehaviourContextAndTypeReceiver>> -) = onMediaGroup(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) +) = buildMediaGroupTrigger(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onDocumentsGroup( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: (suspend (List>) -> Boolean)? = null, scenarioReceiver: BehaviourContextAndTypeReceiver>> -) = onMediaGroup(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) +) = buildMediaGroupTrigger(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onVisualGallery( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: (suspend (List>) -> Boolean)? = null, scenarioReceiver: BehaviourContextAndTypeReceiver>> -) = onMediaGroup(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) +) = buildMediaGroupTrigger(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) +suspend fun BehaviourContext.onVisualMediaGroup( + includeFilterByChatInBehaviourSubContext: Boolean = true, + additionalFilter: (suspend (List>) -> Boolean)? = null, + scenarioReceiver: BehaviourContextAndTypeReceiver>> +) = onVisualGallery(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onPhotoGallery( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: (suspend (List>) -> Boolean)? = null, scenarioReceiver: BehaviourContextAndTypeReceiver>> -) = onMediaGroup(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) +) = buildMediaGroupTrigger(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) suspend fun BehaviourContext.onVideoGallery( includeFilterByChatInBehaviourSubContext: Boolean = true, additionalFilter: (suspend (List>) -> Boolean)? = null, scenarioReceiver: BehaviourContextAndTypeReceiver>> -) = onMediaGroup(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver) +) = buildMediaGroupTrigger(includeFilterByChatInBehaviourSubContext, additionalFilter, scenarioReceiver)