package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.doWithRegistration import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource import dev.inmo.tgbotapi.types.message.textsources.TextSource import kotlinx.coroutines.flow.* /** * Will filter all the messages and include required commands with [commandRegex]. * * * In case you wish to get only the commands at the start of message, use [requireCommandAtStart] * * In case you wish to exclude messages with more than one command, you may use [requireSingleCommand] * * In case you wish to exclude messages with commands params, you may use [requireCommandsWithoutParams] */ suspend fun BehaviourContext.waitCommandMessage( commandRegex: Regex, initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null } ) = channelFlow { triggersHolder.handleableCommandsHolder.doWithRegistration( commandRegex ) { waitTextMessage(initRequest, errorFactory).filter { it.content.textSources.any { it.botCommandTextSourceOrNull() ?.command ?.matches(commandRegex) == true } }.collect { send(it) } } } suspend fun BehaviourContext.waitCommandMessage( command: String, initRequest: Request<*>? = null, errorFactory: NullableRequestBuilder<*> = { null } ) = waitCommandMessage(Regex(command), initRequest, errorFactory) fun Flow>.requireCommandAtStart() = filter { (it.content.textSources.firstOrNull() as? BotCommandTextSource) != null } /** * Subsequent [Flow] will retrieve only messages with ONE [BotCommandTextSource]. It does not guarantee that this * [BotCommandTextSource] will be at the start of the message * * @see requireCommandAtStart */ fun Flow>.requireSingleCommand() = filter { var count = 0 it.content.textSources.forEach { if (it is BotCommandTextSource) { count++ if (count > 1) { return@filter false } } } true } /** * Subsequent [Flow] will retrieve only messages without [TextContent.textSources] which are not [BotCommandTextSource] */ fun Flow>.requireCommandsWithoutParams() = filter { it.content.textSources.none { it !is BotCommandTextSource } } /** * Map the commands with their arguments and source messages */ fun Flow>.commandsWithParams(): Flow, List>>>> = mapNotNull { var currentCommandTextSource: BotCommandTextSource? = null val currentArgs = mutableListOf() val result = mutableListOf>>() fun addCurrentCommandToResult() { currentCommandTextSource ?.let { result.add(it to currentArgs.toTypedArray()) currentArgs.clear() } } it.content.textSources.forEach { it.ifBotCommandTextSource { addCurrentCommandToResult() currentCommandTextSource = it return@forEach } currentArgs.add(it) } addCurrentCommandToResult() result.toList().takeIf { it.isNotEmpty() } ?.let { result -> it to result } } /** * Flat [commandsWithParams]. Each [Pair] of [BotCommandTextSource] and its [Array] of arg text sources will * be associated with its source message */ fun Flow>.flattenCommandsWithParams() = commandsWithParams().flatMapConcat { (message, commandsWithParams) -> commandsWithParams.map { message to it }.asFlow() } /** * Use [flattenCommandsWithParams] and filter out the commands which do not [matches] to [commandRegex] */ fun Flow>.commandParams(commandRegex: Regex) = flattenCommandsWithParams().filter { (_, commandWithParams) -> commandWithParams.first.command.matches(commandRegex) }