diff --git a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitCallbackQuery.kt b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitCallbackQuery.kt new file mode 100644 index 0000000000..18716f49d7 --- /dev/null +++ b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/expectations/WaitCallbackQuery.kt @@ -0,0 +1,107 @@ +@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.CallbackQuery.* +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 dev.inmo.tgbotapi.types.update.CallbackQueryUpdate +import kotlinx.coroutines.flow.toList + +typealias CallbackQueryMapper = T.() -> T? + +private suspend fun Scenario.waitCallbackQueries( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + mapper: suspend CallbackQuery.() -> O? +): List = expectFlow( + initRequest, + count, + errorFactory +) { + it.asCallbackQueryUpdate() ?.data ?.mapper() +}.toList().toList() + + +private suspend inline fun Scenario.waitEvents( + count: Int = 1, + initRequest: Request<*>? = null, + noinline errorFactory: NullableRequestBuilder<*> = { null }, + noinline filter: CallbackQueryMapper? = null +) : List = waitCallbackQueries( + count, + initRequest, + errorFactory +) { + if (this is T) { + if (filter == null) { + this + } else { + filter(this) + } + } else { + null + } +} + + +suspend fun Scenario.waitDataCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitGameShortNameCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitInlineMessageIdCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitInlineMessageIdDataCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitInlineMessageIdGameShortNameCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitMessageCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitMessageDataCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitMessageGameShortNameCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) +suspend fun Scenario.waitUnknownCallbackQuery( + count: Int = 1, + initRequest: Request<*>? = null, + errorFactory: NullableRequestBuilder<*> = { null }, + filter: CallbackQueryMapper? = null +) = waitEvents(count, initRequest, errorFactory, filter) diff --git a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/CallbackQueryTriggers.kt b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/CallbackQueryTriggers.kt new file mode 100644 index 0000000000..1e9f97dc9b --- /dev/null +++ b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/CallbackQueryTriggers.kt @@ -0,0 +1,91 @@ +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.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 Scenario.onCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + noinline additionalFilter: (suspend (T) -> Boolean)? = null, + noinline scenarioReceiver: ScenarioAndTypeReceiver +) = flowsUpdatesFilter.expectFlow(bot) { + it.asCallbackQueryUpdate() ?.data ?.let { query -> + if (query is T) { + if (additionalFilter == null || additionalFilter(query)) query else null + } else { + null + } + } +}.subscribeSafelyWithoutExceptions(scope) { triggerQuery -> + val (jobToCancel, scenario) = if (includeFilterByChatInSubScenario) { + val subFilter = FlowsUpdatesFilter() + val subScenario = 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 subScenario + } else { + null to this + } + safelyWithoutExceptions { scenario.scenarioReceiver(triggerQuery) } + jobToCancel ?.cancel() +} + + +suspend fun Scenario.onDataCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (DataCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) + +suspend fun Scenario.onGameShortNameCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (GameShortNameCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onInlineMessageIdCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (InlineMessageIdCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onInlineMessageIdDataCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (InlineMessageIdDataCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onInlineMessageIdGameShortNameCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (InlineMessageIdGameShortNameCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onMessageCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (MessageCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onMessageDataCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (MessageDataCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onMessageGameShortNameCallbackQuery( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (MessageGameShortNameCallbackQuery) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) +suspend fun Scenario.onUnknownCallbackQueryType( + includeFilterByChatInSubScenario: Boolean = true, + additionalFilter: (suspend (UnknownCallbackQueryType) -> Boolean)? = null, + scenarioReceiver: ScenarioAndTypeReceiver +) = onCallbackQuery(includeFilterByChatInSubScenario, additionalFilter, scenarioReceiver) diff --git a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/ContentTriggers.kt b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/ContentTriggers.kt index bdcd80fbee..9bb3a49bb9 100644 --- a/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/ContentTriggers.kt +++ b/tgbotapi.extensions.steps/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/steps/triggers_handling/ContentTriggers.kt @@ -8,6 +8,7 @@ import dev.inmo.tgbotapi.extensions.steps.ScenarioAndTypeReceiver import dev.inmo.tgbotapi.extensions.steps.expectations.expectFlow import dev.inmo.tgbotapi.extensions.utils.asContentMessage import dev.inmo.tgbotapi.extensions.utils.asMessageUpdate +import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat import dev.inmo.tgbotapi.types.files.abstracts.TelegramMediaFile import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.content.* @@ -37,7 +38,8 @@ internal suspend inline fun Scenario.onContent( val subScenario = copy(flowsUpdatesFilter = subFilter) flowsUpdatesFilter.allUpdatesFlow.filter { - it.asMessageUpdate() ?.data ?.let { it.chat.id.chatId == triggerMessage.chat.id.chatId } == true + val chat = it.sourceChat() ?: return@filter false + chat.id.chatId == triggerMessage.chat.id.chatId }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subScenario } else { null to this 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 index a5117355b9..e447a0d842 100644 --- 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 @@ -7,6 +7,7 @@ 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.extensions.utils.extensions.sourceChat import dev.inmo.tgbotapi.types.files.abstracts.TelegramMediaFile import dev.inmo.tgbotapi.types.message.ChatEvents.* import dev.inmo.tgbotapi.types.message.ChatEvents.abstracts.* @@ -38,7 +39,8 @@ internal suspend inline fun Scenario.onEvent( val subScenario = copy(flowsUpdatesFilter = subFilter) flowsUpdatesFilter.allUpdatesFlow.filter { - it.asMessageUpdate() ?.data ?.let { it.chat.id.chatId == triggerMessage.chat.id.chatId } == true + val chat = it.sourceChat() ?: return@filter false + chat.id.chatId == triggerMessage.chat.id.chatId }.subscribeSafelyWithoutExceptions(scope, subFilter.asUpdateReceiver) to subScenario } else { null to this diff --git a/tgbotapi.extensions.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt b/tgbotapi.extensions.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt new file mode 100644 index 0000000000..153cbe2600 --- /dev/null +++ b/tgbotapi.extensions.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/UpdateChatRetriever.kt @@ -0,0 +1,26 @@ +package dev.inmo.tgbotapi.extensions.utils.extensions + +import dev.inmo.tgbotapi.extensions.utils.shortcuts.chat +import dev.inmo.tgbotapi.types.chat.abstracts.Chat +import dev.inmo.tgbotapi.types.update.* +import dev.inmo.tgbotapi.types.update.MediaGroupUpdates.* +import dev.inmo.tgbotapi.types.update.abstracts.BaseMessageUpdate +import dev.inmo.tgbotapi.types.update.abstracts.Update +import dev.inmo.tgbotapi.utils.PreviewFeature + +@PreviewFeature +fun Update.sourceChat(): Chat? = when (this) { + is MediaGroupUpdate -> when (this) { + is SentMediaGroupUpdate -> data.chat + is EditMediaGroupUpdate -> data.chat + else -> null + } + is BaseMessageUpdate -> data.chat + is InlineQueryUpdate -> data.from + is ChosenInlineResultUpdate -> data.user + is CallbackQueryUpdate -> data.user + is PreCheckoutQueryUpdate -> data.user + is PollAnswerUpdate -> data.user + is ShippingQueryUpdate -> data.user + else -> null +}