diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b10294d26..dacfb1c02e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # TelegramBotAPI changelog -## 9.3.0-RC +## 9.3.0 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` * `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 * `Core`: diff --git a/docs/build.gradle b/docs/build.gradle index be8d28de29..6cc0fbad7e 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -55,7 +55,7 @@ Object callback = { sourceLink { 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") } } diff --git a/gradle.properties b/gradle.properties index a60d35720f..7342f4c756 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ kotlin.incremental=true kotlin.incremental.js=true library_group=dev.inmo -library_version=9.3.0-RC +library_version=9.3.0 diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitCommandsMessages.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitCommandsMessages.kt index bd2927e5d5..df09bdd4be 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitCommandsMessages.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/expectations/WaitCommandsMessages.kt @@ -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.utils.handlers_registrar.doWithRegistration 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.types.BotCommand import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage @@ -79,35 +83,54 @@ fun Flow>.requireCommandsWithoutParams() = filter { } /** - * Map the commands with their arguments and source messages + * Uses [parseCommandsWithArgsSources] on incoming text sources and map them with [CommonMessage] */ 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 - } + it to it.content.textSources.parseCommandsWithArgsSources().toList() } +/** + * Uses [parseCommandsWithArgs] on incoming text sources and map them with [CommonMessage] + */ +fun Flow>.commandsWithArgs( + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex +): Flow, List>>>> = 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>.commandsWithArgs( + argsSeparator: String +): Flow, List>>>> = commandsWithArgs(Regex(argsSeparator)) + +/** + * Uses [parseCommandsWithNamedArgs] on incoming text sources and map them with [CommonMessage] + */ +fun Flow>.commandsWithNamedArgs( + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, +): Flow, List>>>>> = 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>.commandsWithNamedArgs( + argsSeparator: String, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, +): Flow, List>>>>> = commandsWithNamedArgs(Regex(argsSeparator), nameArgSeparator) + /** * Flat [commandsWithParams]. Each [Pair] of [BotCommandTextSource] and its [Array] of arg text sources will * be associated with its source message diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt index ebfa9d0cb3..bad353d964 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandling.kt @@ -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.times 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.message.content.TextContent import dev.inmo.tgbotapi.types.message.content.TextMessage @@ -124,6 +126,7 @@ suspend fun BC.commandWithArgs( initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver> ) = command( commandRegex, @@ -132,7 +135,7 @@ suspend fun BC.commandWithArgs( subcontextUpdatesFilter = subcontextUpdatesFilter, 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 commandsWithArgs[key] } ?: emptyArray() @@ -144,12 +147,14 @@ suspend fun BC.commandWithArgs( initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver> ) = commandWithArgs( command.toRegex(), initialFilter = initialFilter, subcontextUpdatesFilter = subcontextUpdatesFilter, markerFactory = markerFactory, + argsSeparator = argsSeparator, scenarioReceiver = scenarioReceiver ) @@ -158,12 +163,72 @@ suspend fun BC.commandWithArgs( initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver> ) = commandWithArgs( botCommand.command, initialFilter = initialFilter, subcontextUpdatesFilter = subcontextUpdatesFilter, markerFactory = markerFactory, + argsSeparator = argsSeparator, + scenarioReceiver = scenarioReceiver +) + +suspend fun BC.commandWithNamedArgs( + commandRegex: Regex, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, + markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver>> +) = 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.commandWithNamedArgs( + command: String, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, + markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver>> +) = commandWithNamedArgs( + command.toRegex(), + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory, + argsSeparator = argsSeparator, + nameArgSeparator = nameArgSeparator, + scenarioReceiver = scenarioReceiver +) + +suspend fun BC.commandWithNamedArgs( + botCommand: BotCommand, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, + markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver>> +) = commandWithNamedArgs( + botCommand.command, + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory, + argsSeparator = argsSeparator, + nameArgSeparator = nameArgSeparator, scenarioReceiver = scenarioReceiver ) @@ -172,21 +237,99 @@ suspend fun BC.onCommandWithArgs( initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver> -): Job = commandWithArgs(commandRegex, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver) +): Job = commandWithArgs( + commandRegex = commandRegex, + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory, + argsSeparator = argsSeparator, + scenarioReceiver = scenarioReceiver +) suspend fun BC.onCommandWithArgs( command: String, initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver> -): 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.onCommandWithArgs( botCommand: BotCommand, initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver> -): 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.onCommandWithNamedArgs( + commandRegex: Regex, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, + markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver>> +) = commandWithNamedArgs( + commandRegex, + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory, + argsSeparator = argsSeparator, + nameArgSeparator = nameArgSeparator, + scenarioReceiver = scenarioReceiver, +) + +suspend fun BC.onCommandWithNamedArgs( + command: String, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, + markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver>> +) = onCommandWithNamedArgs( + command.toRegex(), + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory, + argsSeparator = argsSeparator, + nameArgSeparator = nameArgSeparator, + scenarioReceiver = scenarioReceiver +) + +suspend fun BC.onCommandWithNamedArgs( + botCommand: BotCommand, + initialFilter: CommonMessageFilter? = CommonMessageFilterExcludeMediaGroups, + subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver? = MessageFilterByChat, + markerFactory: MarkerFactory = ByChatMessageMarkerFactory, + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, + scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver>> +) = onCommandWithNamedArgs( + botCommand.command, + initialFilter = initialFilter, + subcontextUpdatesFilter = subcontextUpdatesFilter, + markerFactory = markerFactory, + argsSeparator = argsSeparator, + nameArgSeparator = nameArgSeparator, + scenarioReceiver = scenarioReceiver +) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt index 4ec378cb27..de00275251 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/CommandHandlingUnhandled.kt @@ -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.times 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.TextMessage import dev.inmo.tgbotapi.types.update.abstracts.Update @@ -65,7 +65,7 @@ suspend fun BC.unhandledCommandWithArgs( subcontextUpdatesFilter = subcontextUpdatesFilter, markerFactory = markerFactory ) { - val args = it.parseCommandsWithParams().let { commandsWithArgs -> + val args = it.parseCommandsWithArgs().let { commandsWithArgs -> commandsWithArgs } scenarioReceiver(it, args) diff --git a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/DeepLinkHandling.kt b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/DeepLinkHandling.kt index 9ca2030f10..91b1f03872 100644 --- a/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/DeepLinkHandling.kt +++ b/tgbotapi.behaviour_builder/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/triggers_handling/DeepLinkHandling.kt @@ -4,22 +4,18 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling import dev.inmo.micro_utils.coroutines.* 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.utils.SimpleFilter 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.times 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.TextMessage import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource import dev.inmo.tgbotapi.types.update.abstracts.Update import io.ktor.http.decodeURLQueryComponent import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.filter private val startRegex = Regex("start") suspend fun BC.onDeepLink( diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/BotCommandsArgsParser.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/BotCommandsArgsParser.kt new file mode 100644 index 0000000000..d46bc0d8b0 --- /dev/null +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/BotCommandsArgsParser.kt @@ -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.parseCommandsWithArgs( + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex +): MutableMap> { + val result = mutableMapOf>() + 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.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.parseCommandsWithArgs( + argsSeparator: String +): MutableMap> = 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.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.parseCommandsWithNamedArgs( + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, +): Map>> { + 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.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.parseCommandsWithNamedArgs( + argsSeparator: String, + nameArgSeparator: Regex = TelegramBotCommandsDefaults.defaultNamesArgsSeparatorRegex, +): Map>> = 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.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.parseCommandsWithParams( + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex +): MutableMap> = 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.parseCommandsWithParams( + argsSeparator: Regex = TelegramBotCommandsDefaults.defaultArgsSeparatorRegex +) = parseCommandsWithArgs(argsSeparator) diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/BotCommandsArgsSourcesParser.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/BotCommandsArgsSourcesParser.kt new file mode 100644 index 0000000000..a60882b085 --- /dev/null +++ b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/BotCommandsArgsSourcesParser.kt @@ -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.parseCommandsWithArgsSources(): Map> { + var currentCommandTextSource: BotCommandTextSource? = null + val currentArgs = mutableListOf() + val result = mutableMapOf>() + + 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.parseCommandsWithArgsSources() = content.parseCommandsWithArgsSources() \ No newline at end of file diff --git a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/TextCaptionBotCommandsParser.kt b/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/TextCaptionBotCommandsParser.kt deleted file mode 100644 index c591e5e97d..0000000000 --- a/tgbotapi.utils/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/utils/extensions/TextCaptionBotCommandsParser.kt +++ /dev/null @@ -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.parseCommandsWithParams( - argsSeparator: Regex = defaultArgsSeparator -): MutableMap> { - val result = mutableMapOf>() - 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.parseCommandsWithParams( - argsSeparator: Regex = defaultArgsSeparator -) = content.parseCommandsWithParams(argsSeparator)