All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.KeyboardBuilder.kt Maven / Gradle / Ivy

package dev.inmo.tgbotapi.keyboards.lib

import dev.inmo.micro_utils.common.Either
import dev.inmo.micro_utils.common.mapOnFirst
import dev.inmo.micro_utils.common.mapOnSecond
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.CustomBehaviourContextAndTypeReceiver
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitBaseInlineQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitInlineMessageIdDataCallbackQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitPreCheckoutQueries
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onBaseInlineQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onDataCallbackQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onPreCheckoutQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.extensions.sameMessage
import dev.inmo.tgbotapi.requests.edit.reply_markup.EditChatMessageReplyMarkup
import dev.inmo.tgbotapi.requests.edit.reply_markup.EditInlineMessageReplyMarkup
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.InlineQueries.query.BaseInlineQuery
import dev.inmo.tgbotapi.types.InlineQueries.query.InlineQuery
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.*
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
import dev.inmo.tgbotapi.types.queries.callback.DataCallbackQuery
import dev.inmo.tgbotapi.types.queries.callback.InaccessibleMessageDataCallbackQuery
import dev.inmo.tgbotapi.types.queries.callback.InlineMessageIdDataCallbackQuery
import dev.inmo.tgbotapi.types.queries.callback.MessageDataCallbackQuery
import dev.inmo.tgbotapi.types.webapps.WebAppInfo
import dev.inmo.tgbotapi.utils.MatrixBuilder
import kotlinx.coroutines.flow.*

class KeyboardBuilder : MatrixBuilder>() {
    sealed interface Button {
        class Data (
            val id: String,
            val reaction: Reaction,
            val callbacksRegex: Regex = Regex(id),
            val textBuilder: suspend BC.() -> String
        ) : Button {
            /**
             * Reaction onto [DataCallbackQuery] event
             */
            sealed interface Reaction {
                /**
                 * When [DataCallbackQuery] event happen, will build new [KeyboardMenu] as well as react onto event
                 *
                 * @param keyboardMenuBuilder Will receive null [DataCallbackQuery] on setup of triggers stage
                 */
                class Keyboard(
                    val keyboardMenuBuilder: suspend BC.(DataCallbackQuery?) -> KeyboardMenu?
                ) : Reaction

                /**
                 * Simple action as reaction onto [DataCallbackQuery] event
                 */
                class Action(
                    val callback: suspend BC.(DataCallbackQuery) -> Unit
                ) : Reaction

                companion object
            }

            override suspend fun buildButton(context: BC): InlineKeyboardButton = CallbackDataInlineKeyboardButton(
                text = context.textBuilder(),
                callbackData = id
            )

            override suspend fun includeTriggers(context: BC) {
                with(context) {
                    when (reaction) {
                        is Reaction.Action -> onDataCallbackQuery(callbacksRegex) {
                            reaction.callback(this, it)
                        }
                        is Reaction.Keyboard -> {
                            reaction.keyboardMenuBuilder.invoke(this, null) ?.setupTriggers(this)
                            onDataCallbackQuery(callbacksRegex) {
                                val keyboard = reaction.keyboardMenuBuilder(this, it) ?.buildButtons(this)
                                when (it) {
                                    is InlineMessageIdDataCallbackQuery -> execute(
                                        EditInlineMessageReplyMarkup(
                                            it.inlineMessageId,
                                            keyboard
                                        )
                                    )
                                    is MessageDataCallbackQuery -> execute(
                                        EditChatMessageReplyMarkup(
                                            it.message.chat.id,
                                            it.message.messageId,
                                            keyboard
                                        )
                                    )
                                    is InaccessibleMessageDataCallbackQuery -> return@onDataCallbackQuery
                                }
                            }
                        }
                    }
                }
            }

            override suspend fun performWaiters(
                context: BC,
                messageInfo: Either, InlineMessageId>
            ): Flow?> {
                val filteredFlow = with(context) {
                    messageInfo.mapOnFirst { (chatId, messageId) ->
                        waitMessageDataCallbackQuery().filter {
                            it.message.sameMessage(chatId, messageId) && callbacksRegex.matches(it.data)
                        }
                    } ?: messageInfo.mapOnSecond { messageId ->
                        waitInlineMessageIdDataCallbackQuery().filter {
                            it.inlineMessageId == messageId && callbacksRegex.matches(it.data)
                        }
                    } ?: emptyFlow()
                }
                val resultFlow: Flow?> = when (reaction) {
                    is Reaction.Action -> with(context) {
                        launchSafelyWithoutExceptions {
                            filteredFlow.collect {
                                reaction.callback(context, it)
                            }
                        }
                        emptyFlow()
                    }
                    is Reaction.Keyboard -> filteredFlow.map {
                        reaction.keyboardMenuBuilder(context, it)
                    }
                }
                return resultFlow
            }
        }
        class Game (
            val textBuilder: suspend BC.() -> String
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = CallbackGameInlineKeyboardButton(
                textBuilder(context)
            )

            override suspend fun includeTriggers(context: BC) {}
        }
        class LoginUrl (
            val textBuilder: suspend BC.() -> String,
            val loginUrlBuilder: suspend BC.() -> LoginURL
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = LoginURLInlineKeyboardButton(
                textBuilder(context),
                loginUrlBuilder(context)
            )

            override suspend fun includeTriggers(context: BC) {}
        }
        class Pay (
            val textBuilder: suspend BC.() -> String,
            val initialFilter: SimpleFilter? = null,
            val onPreCheckoutQueryCallback: CustomBehaviourContextAndTypeReceiver? = null
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = PayInlineKeyboardButton(
                textBuilder(context),
            )

            override suspend fun includeTriggers(context: BC) {
                with(context) {
                    onPreCheckoutQueryCallback ?.let { onPreCheckout ->
                        onPreCheckoutQuery(initialFilter) {
                            onPreCheckout(it)
                        }
                    }
                }
            }

            override suspend fun performWaiters(
                context: BC,
                messageInfo: Either, InlineMessageId>
            ): Flow?> {
                with (context) {
                    onPreCheckoutQueryCallback ?.let { onPreCheckoutQueryCallback ->
                        launchSafelyWithoutExceptions {
                            waitPreCheckoutQueries().collect {
                                onPreCheckoutQueryCallback(context, it)
                            }
                        }
                    }
                }
                return super.performWaiters(context, messageInfo)
            }
        }
        class SwitchInlineQueryChosenChat (
            val textBuilder: suspend BC.() -> String,
            val parametersBuilder: suspend BC.() -> dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.SwitchInlineQueryChosenChat,
            val initialFilter: SimpleFilter? = null,
            val onBaseInlineQueryCallback: CustomBehaviourContextAndTypeReceiver? = null
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = SwitchInlineQueryChosenChatInlineKeyboardButton(
                textBuilder(context),
                parametersBuilder(context),
            )

            override suspend fun includeTriggers(context: BC) {
                with(context) {
                    onBaseInlineQueryCallback ?.let { onBaseInlineQueryCallback ->
                        onBaseInlineQuery(initialFilter) {
                            onBaseInlineQueryCallback(it)
                        }
                    }
                }
            }

            override suspend fun performWaiters(
                context: BC,
                messageInfo: Either, InlineMessageId>
            ): Flow?> {
                with (context) {
                    onBaseInlineQueryCallback ?.let { onBaseInlineQueryCallback ->
                        launchSafelyWithoutExceptions {
                            waitBaseInlineQuery().collect {
                                onBaseInlineQueryCallback(context, it)
                            }
                        }
                    }
                }
                return super.performWaiters(context, messageInfo)
            }
        }
        class SwitchInlineQueryCurrentChat (
            val textBuilder: suspend BC.() -> String,
            val initialInlineQueryBuilder: suspend BC.() -> String,
            val initialFilter: SimpleFilter? = null,
            val onBaseInlineQueryCallback: CustomBehaviourContextAndTypeReceiver? = null
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = SwitchInlineQueryCurrentChatInlineKeyboardButton(
                textBuilder(context),
                initialInlineQueryBuilder(context),
            )

            override suspend fun includeTriggers(context: BC) {
                with(context) {
                    onBaseInlineQueryCallback ?.let { onBaseInlineQueryCallback ->
                        onBaseInlineQuery(initialFilter) {
                            onBaseInlineQueryCallback(it)
                        }
                    }
                }
            }


            override suspend fun performWaiters(
                context: BC,
                messageInfo: Either, InlineMessageId>
            ): Flow?> {
                with (context) {
                    onBaseInlineQueryCallback ?.let { onBaseInlineQueryCallback ->
                        launchSafelyWithoutExceptions {
                            waitBaseInlineQuery().collect {
                                onBaseInlineQueryCallback(context, it)
                            }
                        }
                    }
                }
                return super.performWaiters(context, messageInfo)
            }
        }
        class SwitchInlineQuery (
            val textBuilder: suspend BC.() -> String,
            val initialInlineQueryBuilder: suspend BC.() -> String,
            val initialFilter: SimpleFilter? = null,
            val onBaseInlineQueryCallback: CustomBehaviourContextAndTypeReceiver? = null
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = SwitchInlineQueryInlineKeyboardButton(
                textBuilder(context),
                initialInlineQueryBuilder(context),
            )

            override suspend fun includeTriggers(context: BC) {
                with(context) {
                    onBaseInlineQueryCallback ?.let { onBaseInlineQueryCallback ->
                        onBaseInlineQuery(initialFilter) {
                            onBaseInlineQueryCallback(it)
                        }
                    }
                }
            }


            override suspend fun performWaiters(
                context: BC,
                messageInfo: Either, InlineMessageId>
            ): Flow?> {
                with (context) {
                    onBaseInlineQueryCallback ?.let { onBaseInlineQueryCallback ->
                        launchSafelyWithoutExceptions {
                            waitBaseInlineQuery().collect {
                                onBaseInlineQueryCallback(context, it)
                            }
                        }
                    }
                }
                return super.performWaiters(context, messageInfo)
            }
        }
        class URL (
            val textBuilder: suspend BC.() -> String,
            val urlBuilder: suspend BC.() -> String
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = URLInlineKeyboardButton(
                textBuilder(context),
                urlBuilder(context),
            )

            override suspend fun includeTriggers(context: BC) {}
        }
        class WebApp (
            val textBuilder: suspend BC.() -> String,
            val webAppInfoBuilder: suspend BC.() -> WebAppInfo
        ) : Button {
            override suspend fun buildButton(context: BC): InlineKeyboardButton = WebAppInlineKeyboardButton(
                textBuilder(context),
                webAppInfoBuilder(context),
            )

            override suspend fun includeTriggers(context: BC) {}
        }


        suspend fun buildButton(context: BC): InlineKeyboardButton
        suspend fun includeTriggers(context: BC)
        suspend fun performWaiters(context: BC, messageInfo: Either, InlineMessageId>): Flow?> = emptyFlow()
    }

    fun buildFreezed(): KeyboardMenu {
        val freezedMatrix = matrix.map { it.toList() }
        return KeyboardMenu(
            freezedMatrix
        )
    }

    fun buildLazy(): KeyboardMenu {
        return KeyboardMenu {
            matrix
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy