Merge branch 'master' into 9.3.0

This commit is contained in:
InsanusMokrassar 2023-10-31 18:45:52 +06:00
commit bba667db30
10 changed files with 509 additions and 93 deletions

View File

@ -1,6 +1,6 @@
# TelegramBotAPI changelog # TelegramBotAPI changelog
## 9.3.0-RC ## 9.3.0
This release become possible thanks to [Anton Lakotka](https://youtrack.jetbrains.com/users/anton.lakotka) This release become possible thanks to [Anton Lakotka](https://youtrack.jetbrains.com/users/anton.lakotka)
@ -16,6 +16,14 @@ This release become possible thanks to [Anton Lakotka](https://youtrack.jetbrain
* `Ktor`: `2.3.4` -> `2.3.5` * `Ktor`: `2.3.4` -> `2.3.5`
* `MicroUtils`: `0.19.9` -> `0.20.8` * `MicroUtils`: `0.19.9` -> `0.20.8`
## 9.2.4
* `Utils`:
* New extensions `*.parseCommandsWithNamedArgs`
* `BehaviourBuilder`:
* In expectaters and triggers of `commands` add `*WithNamedArgs` variants
* In expectaters and triggers of `commands` add opportunity to use custom separator
## 9.2.3 ## 9.2.3
* `Core`: * `Core`:

View File

@ -55,7 +55,7 @@ Object callback = {
sourceLink { sourceLink {
localDirectory.set(file("../")) localDirectory.set(file("../"))
remoteUrl.set(new URL("https://github.com/InsanusMokrassar/TelegramBotAPI/blob/master/")) remoteUrl.set(new URL("https://github.com/InsanusMokrassar/ktgbotapi/tree/master"))
remoteLineSuffix.set("#L") remoteLineSuffix.set("#L")
} }
} }

View File

@ -6,4 +6,4 @@ kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
library_group=dev.inmo library_group=dev.inmo
library_version=9.3.0-RC library_version=9.3.0

View File

@ -3,6 +3,10 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.doWithRegistration import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.doWithRegistration
import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.TelegramBotCommandsDefaults
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgsSources
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithNamedArgs
import dev.inmo.tgbotapi.requests.abstracts.Request import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.BotCommand import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
@ -79,35 +83,54 @@ fun Flow<CommonMessage<TextContent>>.requireCommandsWithoutParams() = filter {
} }
/** /**
* Map the commands with their arguments and source messages * Uses [parseCommandsWithArgsSources] on incoming text sources and map them with [CommonMessage]
*/ */
fun Flow<CommonMessage<TextContent>>.commandsWithParams(): Flow<Pair<CommonMessage<TextContent>, List<Pair<BotCommandTextSource, Array<TextSource>>>>> = mapNotNull { fun Flow<CommonMessage<TextContent>>.commandsWithParams(): Flow<Pair<CommonMessage<TextContent>, List<Pair<BotCommandTextSource, Array<TextSource>>>>> = mapNotNull {
var currentCommandTextSource: BotCommandTextSource? = null it to it.content.textSources.parseCommandsWithArgsSources().toList()
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
}
} }
/**
* Uses [parseCommandsWithArgs] on incoming text sources and map them with [CommonMessage]
*/
fun Flow<CommonMessage<TextContent>>.commandsWithArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
): Flow<Pair<CommonMessage<TextContent>, List<Pair<String, Array<String>>>>> = mapNotNull {
val commandsWithArgs = it.content.textSources.parseCommandsWithArgs(argsSeparator).toList().ifEmpty {
return@mapNotNull null
}
it to commandsWithArgs
}
/**
* Uses [parseCommandsWithArgs] on incoming text sources and map them with [CommonMessage]
*/
fun Flow<CommonMessage<TextContent>>.commandsWithArgs(
argsSeparator: String
): Flow<Pair<CommonMessage<TextContent>, List<Pair<String, Array<String>>>>> = commandsWithArgs(Regex(argsSeparator))
/**
* Uses [parseCommandsWithNamedArgs] on incoming text sources and map them with [CommonMessage]
*/
fun Flow<CommonMessage<TextContent>>.commandsWithNamedArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
): Flow<Pair<CommonMessage<TextContent>, List<Pair<String, List<Pair<String, String>>>>>> = mapNotNull {
val commandsWithArgs = it.content.textSources.parseCommandsWithNamedArgs(argsSeparator, nameArgSeparator).toList().ifEmpty {
return@mapNotNull null
}
it to commandsWithArgs
}
/**
* Uses [parseCommandsWithNamedArgs] on incoming text sources and map them with [CommonMessage]
*/
fun Flow<CommonMessage<TextContent>>.commandsWithNamedArgs(
argsSeparator: String,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
): Flow<Pair<CommonMessage<TextContent>, List<Pair<String, List<Pair<String, String>>>>>> = commandsWithNamedArgs(Regex(argsSeparator), nameArgSeparator)
/** /**
* Flat [commandsWithParams]. Each [Pair] of [BotCommandTextSource] and its [Array] of arg text sources will * Flat [commandsWithParams]. Each [Pair] of [BotCommandTextSource] and its [Array] of arg text sources will
* be associated with its source message * be associated with its source message

View File

@ -11,7 +11,9 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByC
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times
import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams import dev.inmo.tgbotapi.extensions.utils.extensions.TelegramBotCommandsDefaults
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithNamedArgs
import dev.inmo.tgbotapi.types.BotCommand import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.TextMessage import dev.inmo.tgbotapi.types.message.content.TextMessage
@ -124,6 +126,7 @@ suspend fun <BC : BehaviourContext> BC.commandWithArgs(
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups, initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory, markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>> scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>>
) = command( ) = command(
commandRegex, commandRegex,
@ -132,7 +135,7 @@ suspend fun <BC : BehaviourContext> BC.commandWithArgs(
subcontextUpdatesFilter = subcontextUpdatesFilter, subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory markerFactory = markerFactory
) { ) {
val args = it.parseCommandsWithParams().let { commandsWithArgs -> val args = it.parseCommandsWithArgs(argsSeparator = argsSeparator).let { commandsWithArgs ->
val key = commandsWithArgs.keys.firstOrNull { it.matches(commandRegex) } ?: return@let null val key = commandsWithArgs.keys.firstOrNull { it.matches(commandRegex) } ?: return@let null
commandsWithArgs[key] commandsWithArgs[key]
} ?: emptyArray() } ?: emptyArray()
@ -144,12 +147,14 @@ suspend fun <BC : BehaviourContext> BC.commandWithArgs(
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups, initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory, markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>> scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>>
) = commandWithArgs( ) = commandWithArgs(
command.toRegex(), command.toRegex(),
initialFilter = initialFilter, initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter, subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory, markerFactory = markerFactory,
argsSeparator = argsSeparator,
scenarioReceiver = scenarioReceiver scenarioReceiver = scenarioReceiver
) )
@ -158,12 +163,72 @@ suspend fun <BC : BehaviourContext> BC.commandWithArgs(
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups, initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory, markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>> scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>>
) = commandWithArgs( ) = commandWithArgs(
botCommand.command, botCommand.command,
initialFilter = initialFilter, initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter, subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory, markerFactory = markerFactory,
argsSeparator = argsSeparator,
scenarioReceiver = scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.commandWithNamedArgs(
commandRegex: Regex,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, List<Pair<String, String>>>
) = command(
commandRegex,
requireOnlyCommandInMessage = false,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory
) {
val args = it.parseCommandsWithNamedArgs(argsSeparator = argsSeparator, nameArgSeparator = nameArgSeparator).let { commandsWithArgs ->
val key = commandsWithArgs.keys.firstOrNull { it.matches(commandRegex) } ?: return@let null
commandsWithArgs[key]
} ?: emptyList()
scenarioReceiver(it, args)
}
suspend fun <BC : BehaviourContext> BC.commandWithNamedArgs(
command: String,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, List<Pair<String, String>>>
) = commandWithNamedArgs(
command.toRegex(),
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
nameArgSeparator = nameArgSeparator,
scenarioReceiver = scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.commandWithNamedArgs(
botCommand: BotCommand,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, List<Pair<String, String>>>
) = commandWithNamedArgs(
botCommand.command,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
nameArgSeparator = nameArgSeparator,
scenarioReceiver = scenarioReceiver scenarioReceiver = scenarioReceiver
) )
@ -172,21 +237,99 @@ suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups, initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory, markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>> scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>>
): Job = commandWithArgs(commandRegex, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) ): Job = commandWithArgs(
commandRegex = commandRegex,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
scenarioReceiver = scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.onCommandWithArgs( suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
command: String, command: String,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups, initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory, markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>> scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>>
): Job = onCommandWithArgs(command.toRegex(), initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) ): Job = onCommandWithArgs(
commandRegex = command.toRegex(),
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
scenarioReceiver = scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.onCommandWithArgs( suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
botCommand: BotCommand, botCommand: BotCommand,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups, initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory, markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>> scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, Array<String>>
): Job = onCommandWithArgs(botCommand.command, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) ): Job = onCommandWithArgs(
command = botCommand.command,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
scenarioReceiver = scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.onCommandWithNamedArgs(
commandRegex: Regex,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, List<Pair<String, String>>>
) = commandWithNamedArgs(
commandRegex,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
nameArgSeparator = nameArgSeparator,
scenarioReceiver = scenarioReceiver,
)
suspend fun <BC : BehaviourContext> BC.onCommandWithNamedArgs(
command: String,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, List<Pair<String, String>>>
) = onCommandWithNamedArgs(
command.toRegex(),
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
nameArgSeparator = nameArgSeparator,
scenarioReceiver = scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.onCommandWithNamedArgs(
botCommand: BotCommand,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, TextMessage, Update>? = MessageFilterByChat,
markerFactory: MarkerFactory<in TextMessage, Any> = ByChatMessageMarkerFactory,
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, TextMessage, List<Pair<String, String>>>
) = onCommandWithNamedArgs(
botCommand.command,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory,
argsSeparator = argsSeparator,
nameArgSeparator = nameArgSeparator,
scenarioReceiver = scenarioReceiver
)

View File

@ -9,7 +9,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByC
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times
import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs
import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.TextMessage import dev.inmo.tgbotapi.types.message.content.TextMessage
import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.Update
@ -65,7 +65,7 @@ suspend fun <BC : BehaviourContext> BC.unhandledCommandWithArgs(
subcontextUpdatesFilter = subcontextUpdatesFilter, subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory markerFactory = markerFactory
) { ) {
val args = it.parseCommandsWithParams().let { commandsWithArgs -> val args = it.parseCommandsWithArgs().let { commandsWithArgs ->
commandsWithArgs commandsWithArgs
} }
scenarioReceiver(it, args) scenarioReceiver(it, args)

View File

@ -4,22 +4,18 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.* import dev.inmo.micro_utils.coroutines.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDeepLinks
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatMessageMarkerFactory import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatMessageMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times
import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams
import dev.inmo.tgbotapi.types.message.content.TextContent import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.content.TextMessage import dev.inmo.tgbotapi.types.message.content.TextMessage
import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource
import dev.inmo.tgbotapi.types.update.abstracts.Update import dev.inmo.tgbotapi.types.update.abstracts.Update
import io.ktor.http.decodeURLQueryComponent import io.ktor.http.decodeURLQueryComponent
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.filter
private val startRegex = Regex("start") private val startRegex = Regex("start")
suspend fun <BC : BehaviourContext> BC.onDeepLink( suspend fun <BC : BehaviourContext> BC.onDeepLink(

View File

@ -0,0 +1,247 @@
package dev.inmo.tgbotapi.extensions.utils.extensions
import dev.inmo.tgbotapi.abstracts.TextedWithTextSources
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
import dev.inmo.tgbotapi.types.message.textsources.TextSource
object TelegramBotCommandsDefaults {
const val defaultArgsSeparator = " "
val defaultArgsSeparatorRegex = Regex(defaultArgsSeparator)
const val defaultNamesArgsSeparator = "="
val defaultNamesArgsSeparatorRegex = Regex(defaultNamesArgsSeparator)
}
@Deprecated(message = "Replaced", replaceWith = ReplaceWith("TelegramBotCommandsDefaults.defaultArgsSeparatorRegex", "dev.inmo.tgbotapi.extensions.utils.extensions.TelegramBotCommandsDefaults"))
val defaultArgsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun List<TextSource>.parseCommandsWithArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
): MutableMap<String, Array<String>> {
val result = mutableMapOf<String, Array<String>>()
var currentBotCommandSource: BotCommandTextSource? = null
var currentArgs = ""
fun includeCurrent() = currentBotCommandSource?.let {
currentArgs = currentArgs.trim()
result[it.command] = if (currentArgs.isNotEmpty()) {
currentArgs.split(argsSeparator).toTypedArray()
} else {
emptyArray()
}
currentArgs = ""
currentBotCommandSource = null
}
for (textSource in this) {
if (textSource is BotCommandTextSource) {
includeCurrent()
currentBotCommandSource = textSource
} else {
currentArgs += textSource.source
}
}
includeCurrent()
return result
}
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun TextedWithTextSources.parseCommandsWithArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
) = textSources?.parseCommandsWithArgs(argsSeparator) ?: emptyMap()
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun ContentMessage<TextContent>.parseCommandsWithArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
) = content.parseCommandsWithArgs(argsSeparator)
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun List<TextSource>.parseCommandsWithArgs(
argsSeparator: String
): MutableMap<String, Array<String>> = parseCommandsWithArgs(Regex(argsSeparator))
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun TextedWithTextSources.parseCommandsWithArgs(
argsSeparator: String
) = parseCommandsWithArgs(Regex(argsSeparator))
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun ContentMessage<TextContent>.parseCommandsWithArgs(
argsSeparator: String
) = parseCommandsWithArgs(Regex(argsSeparator))
/**
* Uses [parseCommandsWithArgs] to create base [argsSeparator] split args for commands and map their as k-v pairs.
* Sample:
*
* ```bash
* /command args1=value1 arg2=value2 arg1=value3
* ```
*
* Will produce [Map] with one key `command` and the list of three pairs:
*
* 1. `args1` to `value1`
* 2. `args2` to `value2`
* 3. `args1` to `value3`
*
* @return Array of named arguments
*/
fun List<TextSource>.parseCommandsWithNamedArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
): Map<String, List<Pair<String, String>>> {
val withArgs = parseCommandsWithArgs(argsSeparator)
return withArgs.mapValues { (k, v) ->
v.flatMap {
it.split(nameArgSeparator, 2).map { v -> it to v }
}
}
}
/**
* Uses [parseCommandsWithArgs] to create base [argsSeparator] split args for commands and map their as k-v pairs.
* Sample:
*
* ```bash
* /command args1=value1 arg2=value2 arg1=value3
* ```
*
* Will produce [Map] with one key `command` and the list of three pairs:
*
* 1. `args1` to `value1`
* 2. `args2` to `value2`
* 3. `args1` to `value3`
*
* @return Array of named arguments
*/
fun TextedWithTextSources.parseCommandsWithNamedArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
) = textSources?.parseCommandsWithNamedArgs(argsSeparator = argsSeparator, nameArgSeparator = nameArgSeparator) ?: emptyMap()
/**
* Uses [parseCommandsWithArgs] to create base [argsSeparator] split args for commands and map their as k-v pairs.
* Sample:
*
* ```bash
* /command args1=value1 arg2=value2 arg1=value3
* ```
*
* Will produce [Map] with one key `command` and the list of three pairs:
*
* 1. `args1` to `value1`
* 2. `args2` to `value2`
* 3. `args1` to `value3`
*
* @return Array of named arguments
*/
fun ContentMessage<TextContent>.parseCommandsWithNamedArgs(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
) = content.parseCommandsWithNamedArgs(argsSeparator = argsSeparator, nameArgSeparator = nameArgSeparator)
/**
* Uses [parseCommandsWithArgs] to create base [argsSeparator] split args for commands and map their as k-v pairs.
* Sample:
*
* ```bash
* /command args1=value1 arg2=value2 arg1=value3
* ```
*
* Will produce [Map] with one key `command` and the list of three pairs:
*
* 1. `args1` to `value1`
* 2. `args2` to `value2`
* 3. `args1` to `value3`
*
* @return Array of named arguments
*/
fun List<TextSource>.parseCommandsWithNamedArgs(
argsSeparator: String,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
): Map<String, List<Pair<String, String>>> = parseCommandsWithNamedArgs(Regex(pattern = argsSeparator), nameArgSeparator)
/**
* Uses [parseCommandsWithArgs] to create base [argsSeparator] split args for commands and map their as k-v pairs.
* Sample:
*
* ```bash
* /command args1=value1 arg2=value2 arg1=value3
* ```
*
* Will produce [Map] with one key `command` and the list of three pairs:
*
* 1. `args1` to `value1`
* 2. `args2` to `value2`
* 3. `args1` to `value3`
*
* @return Array of named arguments
*/
fun TextedWithTextSources.parseCommandsWithNamedArgs(
argsSeparator: String,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
) = parseCommandsWithNamedArgs(argsSeparator = Regex(pattern = argsSeparator), nameArgSeparator = nameArgSeparator)
/**
* Uses [parseCommandsWithArgs] to create base [argsSeparator] split args for commands and map their as k-v pairs.
* Sample:
*
* ```bash
* /command args1=value1 arg2=value2 arg1=value3
* ```
*
* Will produce [Map] with one key `command` and the list of three pairs:
*
* 1. `args1` to `value1`
* 2. `args2` to `value2`
* 3. `args1` to `value3`
*
* @return Array of named arguments
*/
fun ContentMessage<TextContent>.parseCommandsWithNamedArgs(
argsSeparator: String,
nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex,
) = parseCommandsWithNamedArgs(argsSeparator = Regex(pattern = argsSeparator), nameArgSeparator = nameArgSeparator)
// Deprecations
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
@Deprecated("Renamed", ReplaceWith("parseCommandsWithArgs(argsSeparator)", "dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs"))
fun List<TextSource>.parseCommandsWithParams(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
): MutableMap<String, Array<String>> = parseCommandsWithArgs(argsSeparator)
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
@Deprecated("Renamed", ReplaceWith("parseCommandsWithArgs(argsSeparator)", "dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs"))
fun TextedWithTextSources.parseCommandsWithParams(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
) = parseCommandsWithArgs(argsSeparator)
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
@Deprecated("Renamed", ReplaceWith("parseCommandsWithArgs(argsSeparator)", "dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithArgs"))
fun ContentMessage<TextContent>.parseCommandsWithParams(
argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex
) = parseCommandsWithArgs(argsSeparator)

View File

@ -0,0 +1,53 @@
package dev.inmo.tgbotapi.extensions.utils.extensions
import dev.inmo.tgbotapi.abstracts.TextedWithTextSources
import dev.inmo.tgbotapi.extensions.utils.botCommandTextSourceOrNull
import dev.inmo.tgbotapi.extensions.utils.ifBotCommandTextSource
import dev.inmo.tgbotapi.extensions.utils.whenBotCommandTextSource
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
import dev.inmo.tgbotapi.types.message.textsources.TextSource
// Sources
/**
* Parse text sources to find commands with their arguments. This method will skip all the text sources __before__
* first command and all following text sources until the next command will be guessed as an args of last found command
*/
fun List<TextSource>.parseCommandsWithArgsSources(): Map<BotCommandTextSource, Array<TextSource>> {
var currentCommandTextSource: BotCommandTextSource? = null
val currentArgs = mutableListOf<TextSource>()
val result = mutableMapOf<BotCommandTextSource, Array<TextSource>>()
fun addCurrentCommandToResult() {
currentCommandTextSource ?.let {
result[it] = currentArgs.toTypedArray()
currentArgs.clear()
}
}
forEach {
it.whenBotCommandTextSource {
addCurrentCommandToResult()
currentCommandTextSource = it
return@forEach
}
currentArgs.add(it)
}
addCurrentCommandToResult()
return result
}
/**
* Parse text sources to find commands with their arguments. This method will skip all the text sources __before__
* first command and all following text sources until the next command will be guessed as an args of last found command
*/
fun TextedWithTextSources.parseCommandsWithArgsSources() = textSources?.parseCommandsWithArgsSources() ?: emptyMap()
/**
* Parse text sources to find commands with their arguments. This method will skip all the text sources __before__
* first command and all following text sources until the next command will be guessed as an args of last found command
*/
fun ContentMessage<TextContent>.parseCommandsWithArgsSources() = content.parseCommandsWithArgsSources()

View File

@ -1,54 +0,0 @@
package dev.inmo.tgbotapi.extensions.utils.extensions
import dev.inmo.tgbotapi.abstracts.TextedWithTextSources
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
import dev.inmo.tgbotapi.types.message.textsources.TextSource
val defaultArgsSeparator = Regex(" ")
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun List<TextSource>.parseCommandsWithParams(
argsSeparator: Regex = defaultArgsSeparator
): MutableMap<String, Array<String>> {
val result = mutableMapOf<String, Array<String>>()
var currentBotCommandSource: BotCommandTextSource? = null
var currentArgs = ""
fun includeCurrent() = currentBotCommandSource ?.let {
currentArgs = currentArgs.trim()
result[it.command] = if (currentArgs.isNotEmpty()) {
currentArgs.split(argsSeparator).toTypedArray()
} else {
emptyArray()
}
currentArgs = ""
currentBotCommandSource = null
}
for (textSource in this) {
if (textSource is BotCommandTextSource) {
includeCurrent()
currentBotCommandSource = textSource
} else {
currentArgs += textSource.source
}
}
includeCurrent()
return result
}
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun TextedWithTextSources.parseCommandsWithParams(
argsSeparator: Regex = defaultArgsSeparator
) = textSources ?.parseCommandsWithParams(argsSeparator) ?: emptyMap()
/**
* Parse commands and their args. Logic will find command, get all subsequent data as args until new command
*/
fun ContentMessage<TextContent>.parseCommandsWithParams(
argsSeparator: Regex = defaultArgsSeparator
) = content.parseCommandsWithParams(argsSeparator)