mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-22 00:03:48 +00:00
add waitCommands* expectations
This commit is contained in:
parent
fff05a40d9
commit
9ea06de27c
@ -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 {
|
||||
_handleable.remove(data)
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user