mirror of
				https://github.com/InsanusMokrassar/TelegramBotAPI.git
				synced 2025-11-04 06:00:15 +00:00 
			
		
		
		
	add waitCommands* expectations
This commit is contained in:
		@@ -0,0 +1,113 @@
 | 
			
		||||
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)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Flow<CommonMessage<TextContent>>.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<CommonMessage<TextContent>>.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<CommonMessage<TextContent>>.requireCommandsWithoutParams() = filter {
 | 
			
		||||
    it.content.textSources.none { it !is BotCommandTextSource }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map the commands with their arguments and source messages
 | 
			
		||||
 */
 | 
			
		||||
fun Flow<CommonMessage<TextContent>>.commandsWithParams(): Flow<Pair<CommonMessage<TextContent>, List<Pair<BotCommandTextSource, Array<TextSource>>>>> = mapNotNull {
 | 
			
		||||
    var currentCommandTextSource: BotCommandTextSource? = null
 | 
			
		||||
    val currentArgs = mutableListOf<TextSource>()
 | 
			
		||||
    val result = mutableListOf<Pair<BotCommandTextSource, Array<TextSource>>>()
 | 
			
		||||
 | 
			
		||||
    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<CommonMessage<TextContent>>.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<CommonMessage<TextContent>>.commandParams(commandRegex: Regex) = flattenCommandsWithParams().filter { (_, commandWithParams) ->
 | 
			
		||||
    commandWithParams.first.command.matches(commandRegex)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
 | 
			
		||||
 | 
			
		||||
import dev.inmo.micro_utils.coroutines.runCatchingSafely
 | 
			
		||||
import kotlinx.coroutines.sync.Mutex
 | 
			
		||||
import kotlinx.coroutines.sync.withLock
 | 
			
		||||
 | 
			
		||||
@@ -7,6 +8,7 @@ open class HandleableTriggersHolder<T>(
 | 
			
		||||
    preset: List<T> = emptyList()
 | 
			
		||||
) {
 | 
			
		||||
    protected val commandsMutex = Mutex()
 | 
			
		||||
    protected val handleableCounts = mutableMapOf<T, Int>()
 | 
			
		||||
    protected val _handleable = mutableListOf<T>().also {
 | 
			
		||||
        it.addAll(preset)
 | 
			
		||||
    }
 | 
			
		||||
@@ -16,12 +18,31 @@ open class HandleableTriggersHolder<T>(
 | 
			
		||||
    suspend fun registerHandleable(data: T) {
 | 
			
		||||
        commandsMutex.withLock {
 | 
			
		||||
            _handleable.add(data)
 | 
			
		||||
            handleableCounts[data] = (handleableCounts[data] ?: 0) + 1
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    suspend fun unregisterHandleable(data: T) {
 | 
			
		||||
        commandsMutex.withLock {
 | 
			
		||||
            val newHandleableCount = (handleableCounts[data] ?: 0) - 1
 | 
			
		||||
            if (newHandleableCount > 0) {
 | 
			
		||||
                handleableCounts[data] = newHandleableCount
 | 
			
		||||
            } else {
 | 
			
		||||
                handleableCounts.remove(data)
 | 
			
		||||
                _handleable.remove(data)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend fun <T, R> HandleableTriggersHolder<T>.doWithRegistration(
 | 
			
		||||
    data: T,
 | 
			
		||||
    block: suspend () -> R
 | 
			
		||||
): R {
 | 
			
		||||
    registerHandleable(data)
 | 
			
		||||
    val result = runCatchingSafely {
 | 
			
		||||
        block()
 | 
			
		||||
    }
 | 
			
		||||
    unregisterHandleable(data)
 | 
			
		||||
    return result.getOrThrow()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -81,6 +81,8 @@ sealed interface StickerType {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val usernameRegex = Regex("@[\\w\\d_]+")
 | 
			
		||||
 | 
			
		||||
val degreesLimit = 1 .. 360
 | 
			
		||||
val horizontalAccuracyLimit = 0F .. 1500F
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package dev.inmo.tgbotapi.types.message.textsources
 | 
			
		||||
 | 
			
		||||
import dev.inmo.tgbotapi.types.usernameRegex
 | 
			
		||||
import dev.inmo.tgbotapi.utils.RiskFeature
 | 
			
		||||
import dev.inmo.tgbotapi.utils.internal.*
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
@@ -16,6 +17,9 @@ data class BotCommandTextSource @RiskFeature(DirectInvocationOfTextSourceConstru
 | 
			
		||||
    val command: String by lazy {
 | 
			
		||||
        commandRegex.find(source) ?.value ?.substring(1) ?: source.substring(1)// skip first symbol like "/" or "!"
 | 
			
		||||
    }
 | 
			
		||||
    val username: String? by lazy {
 | 
			
		||||
        usernameRegex.find(source) ?.value ?.substring(1) ?: source.substring(1)// skip first symbol "@"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val markdown: String by lazy { source.commandMarkdown() }
 | 
			
		||||
    override val markdownV2: String by lazy { source.commandMarkdownV2() }
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ fun <T> Flow<Iterable<T>>.flatten(): Flow<T> = flow {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun <T, R> Flow<T>.flatMap(mapper: (T) -> Iterable<R>): Flow<R> = flow {
 | 
			
		||||
fun <T, R> Flow<T>.flatMap(mapper: suspend (T) -> Iterable<R>): Flow<R> = flow {
 | 
			
		||||
    collect {
 | 
			
		||||
        mapper(it).forEach {
 | 
			
		||||
            emit(it)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user