From 965b8c3c505851f81e05fa8705e4344ca2d06701 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Thu, 7 Jan 2021 17:24:58 +0600 Subject: [PATCH] add chat event triggers and expectations in steps --- .../steps/expectations/WaitContent.kt | 5 +- .../steps/expectations/WaitEventAction.kt | 144 ++++++++++++++++++ .../steps/triggers_handling/EventTriggers.kt | 125 +++++++++++++++ tgbotapi/build.gradle | 1 + 4 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitEventAction.kt create mode 100644 tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/EventTriggers.kt diff --git a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitContent.kt b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitContent.kt index 3538e829b8..20e15bbcb6 100644 --- a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitContent.kt +++ b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitContent.kt @@ -1,3 +1,5 @@ +@file:Suppress("unused") + package dev.inmo.tgbotapi.extensions.steps.expectations import dev.inmo.tgbotapi.extensions.steps.Scenario @@ -30,13 +32,14 @@ private suspend inline fun Scenario.waitContent( count: Int = 1, initRequest: Request<*>? = null, noinline errorFactory: NullableRequestBuilder<*> = { null }, - noinline filter: (suspend (ContentMessage) -> T?)? = null + noinline filter: ContentMessageToContentMapper? = null ) : List = waitContentMessage( count, initRequest, errorFactory ) { if (content is T) { + @Suppress("UNCHECKED_CAST") val message = (this as ContentMessage) if (filter == null) { message.content diff --git a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitEventAction.kt b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitEventAction.kt new file mode 100644 index 0000000000..b96a999eed --- /dev/null +++ b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitEventAction.kt @@ -0,0 +1,144 @@ +@file:Suppress("unused") + +package dev.inmo.tgbotapi.extensions.steps.expectations + +import dev.inmo.tgbotapi.extensions.steps.Scenario +import dev.inmo.tgbotapi.extensions.utils.* +import dev.inmo.tgbotapi.requests.abstracts.Request +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.ContactContent +import kotlinx.coroutines.flow.toList + +typealias EventMessageToEventMapper = suspend ChatEventMessage.() -> T? + +private suspend fun Scenario.waitEventMessages( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + mapper: suspend ChatEventMessage.() -> O? +): List = expectFlow( + initRequest, + count, + errorFactory +) { + it.asMessageUpdate() ?.data ?.asChatEventMessage() ?.mapper() +}.toList().toList() + + +private suspend inline fun Scenario.waitEvents( + count: Int = 1, + initRequest: Request<*>? = null, + noinline errorFactory: NullableRequestBuilder<*> = { null }, + noinline filter: EventMessageToEventMapper? = null +) : List = waitEventMessages( + count, + initRequest, + errorFactory +) { + if (chatEvent is T) { + @Suppress("UNCHECKED_CAST") + val message = (this as ChatEventMessage) + if (filter == null) { + message.chatEvent + } else { + filter(message) + } + } else { + null + } +} + +suspend fun Scenario.waitChannelEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) + +suspend fun Scenario.waitChatEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitCommonEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitGroupEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitSupergroupEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) + +suspend fun Scenario.waitChannelChatCreatedEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitDeleteChatPhotoEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitGroupChatCreatedEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitLeftChatMemberEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitNewChatPhotoEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitNewChatMembersEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitNewChatTitleEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitPinnedMessageEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitProximityAlertTriggeredEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitSupergroupChatCreatedEvents( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: EventMessageToEventMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) diff --git a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/EventTriggers.kt b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/EventTriggers.kt new file mode 100644 index 0000000000..a5117355b9 --- /dev/null +++ b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/EventTriggers.kt @@ -0,0 +1,125 @@ +package dev.inmo.tgbotapi.extensions.steps.triggers_handling + + +import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions +import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions +import dev.inmo.tgbotapi.extensions.steps.Scenario +import dev.inmo.tgbotapi.extensions.steps.ScenarioAndTypeReceiver +import dev.inmo.tgbotapi.extensions.steps.expectations.expectFlow +import dev.inmo.tgbotapi.extensions.utils.* +import dev.inmo.tgbotapi.types.files.abstracts.TelegramMediaFile +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.abstracts.ContentMessage +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 kotlinx.coroutines.flow.filter + +internal suspend inline fun Scenario.onEvent( + includeFilterByChatInSubScenario: Boolean = true, + noinline additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + noinline scenarioReceiver: ScenarioAndTypeReceiver> +) = flowsUpdatesFilter.expectFlow(bot) { + it.asMessageUpdate() ?.data ?.asChatEventMessage() ?.let { message -> + if (message.chatEvent is T) { + val adaptedMessage = message as ChatEventMessage + if (additionalFilter == null || additionalFilter(adaptedMessage)) adaptedMessage else null + } else { + null + } + } +}.subscribeSafelyWithoutExceptions(scope) { triggerMessage -> + val (jobToCancel, scenario) = if (includeFilterByChatInSubScenario) { + val subFilter = FlowsUpdatesFilter() + val subScenario = copy(flowsUpdatesFilter = subFilter) + + flowsUpdatesFilter.allUpdatesFlow.filter { + it.asMessageUpdate() ?.data ?.let { it.chat.id.chatId == triggerMessage.chat.id.chatId } == true + }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subScenario + } else { + null to this + } + safelyWithoutExceptions { scenario.scenarioReceiver(triggerMessage) } + jobToCancel ?.cancel() +} + +suspend fun Scenario.onChannelEvent( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onChatEvent( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onCommonEvent( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onGroupEvent( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onSupergroupEvent( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) + +suspend fun Scenario.onChannelChatCreated( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onDeleteChatPhoto( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onGroupChatCreated( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onLeftChatMember( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onNewChatMembers( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onNewChatPhoto( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onNewChatTitle( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onPinnedMessage( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onProximityAlertTriggered( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onSupergroupChatCreated( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (ChatEventMessage) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver> +) = onEvent(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) diff --git a/tgbotapi/build.gradle b/tgbotapi/build.gradle index 36ee69d99c..b033b525c2 100644 --- a/tgbotapi/build.gradle +++ b/tgbotapi/build.gradle @@ -42,6 +42,7 @@ kotlin { api project(":tgbotapi.core") api project(":tgbotapi.extensions.api") api project(":tgbotapi.extensions.utils") + api project(":tgbotapi.extensions.steps") } } }