1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-12-08 15:25:52 +00:00

Compare commits

..

15 Commits

36 changed files with 491 additions and 126 deletions

View File

@@ -4,6 +4,10 @@
__All the `tgbotapi.extensions.*` packages have been removed__
* `Versions`:
* `Kotlin`: `1.6.10` -> `1.6.21`
* `Ktor`: `1.6.8` -> `2.0.1`
* `MicroUtils`: `0.9.24` -> `0.10.2`
* `Core`:
* **`Ktor` package renamed. Migration:** `dev.inmo.tgbotapi.bot.Ktor` -> `dev.inmo.tgbotapi.bot.ktor`
* **`CallbackQuery` package renamed. Migration:** `dev.inmo.tgbotapi.types.CallbackQuery([\s\\.])` -> `dev.inmo.tgbotapi.types.queries.callback$1`
@@ -46,6 +50,21 @@ __All the `tgbotapi.extensions.*` packages have been removed__
* New typealias `FileUrl` (represents `FileId` but declare that they are the same)
* `BehaviourBuilder`:
* `SimpleFilter` now is a `fun interface` instead of just callback (fix of [#546](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/546))
* New extension `BehaviourContext#createSubContext`. It uses separated `AccumulatorFlow` and will retrieve updates by itself
* New extension `BehaviourContext#doInContext`
* Extension `BehaviourContext#doInSubContextWithUpdatesFilter` has been renamed to `BehaviourContext#createSubContextAndDoWithUpdatesFilter`
* Extension `BehaviourContext#doInSubContext` has been deprecated
* `BehaviourContextWithFSM`:
* `launchStateHandling` lost its parameter `contextUpdatesFlow: Flow`
* `handleState` of `BehaviourContextWithFSM` now will get/create sub context for the state and launch handling in it
* `BehaviourWithFSMStateHandler` now extends `StatesHandler`
* `BehaviourWithFSMStateHandlerHolder` now extends `CheckableHandlerHolder` and `BehaviourWithFSMStateHandler`
* Function `checkHandleable` of `BehaviourWithFSMStateHandlerHolder` now is `suspend`
## 0.38.23
* `BehaviourHandler`:
* Add support of fallback triggers (fix of [#560](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/560))
## 0.38.22

View File

@@ -12,7 +12,7 @@ klock_version=2.7.0
uuid_version=0.4.0
ktor_version=2.0.1
micro_utils_version=0.10.1
micro_utils_version=0.10.3
javax_activation_version=1.1.1

View File

@@ -5,11 +5,10 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.micro_utils.coroutines.accumulatorFlow
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import kotlin.jvm.JvmName
import kotlin.reflect.KClass
/**
@@ -22,18 +21,6 @@ import kotlin.reflect.KClass
interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T> {
suspend fun start() = start(this)
suspend fun launchStateHandling(
state: T,
contextUpdatesFlow: Flow<Update>,
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
): T? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
doInSubContext(updatesUpstreamFlow = contextUpdatesFlow) {
handleState(state)
}
}
}
/**
* Add NON STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Non strict means that
* for input [State] will be used [KClass.isInstance] and any inheritor of [kClass] will pass this requirement
@@ -59,6 +46,7 @@ interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T
broadcastChannelsSize: Int,
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
triggersHolder: TriggersHolder,
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
): BehaviourContextWithFSM<T>
@@ -102,19 +90,22 @@ class DefaultBehaviourContextWithFSM<T : State>(
private val statesManager: StatesManager<T>,
private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
private val updatesFlows = mutableMapOf<Any, Flow<Update>>()
private val updatesFlows = mutableMapOf<Any, DefaultBehaviourContextWithFSM<T>>()
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
private var actualHandlersList = additionalHandlers + handlers
private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) {
allUpdatesFlow.accumulatorFlow(scope)
private fun getSubContext(context: Any) = updatesFlows.getOrPut(context) {
createSubContext()
}
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(
state,
allUpdatesFlow,
actualHandlersList
)
override suspend fun StatesMachine<in T>.handleState(state: T): T? {
return getSubContext(
state.context
).launchStateHandling(
state,
actualHandlersList
)
}
override fun <I : T> add(kClass: KClass<I>, strict: Boolean, handler: BehaviourWithFSMStateHandler<I, T>) {
additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler))
@@ -123,7 +114,7 @@ class DefaultBehaviourContextWithFSM<T : State>(
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (T) -> Unit = { state: T ->
val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), actualHandlersList)
val newState = getSubContext(state.context).launchStateHandling(state, actualHandlersList)
if (newState != null) {
statesManager.update(state, newState)
} else {
@@ -178,9 +169,10 @@ class DefaultBehaviourContextWithFSM<T : State>(
broadcastChannelsSize: Int,
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
triggersHolder: TriggersHolder,
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter),
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder, updatesFilter),
handlers,
statesManager
)

View File

@@ -88,7 +88,6 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
* @see BehaviourContextWithFSM.strictlyOn
* @see BehaviourContextWithFSM.onStateOrSubstate
*/
@PreviewFeature
suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
flowUpdatesFilter: FlowsUpdatesFilter,
scope: CoroutineScope = defaultCoroutineScopeProvider(),
@@ -117,7 +116,6 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
* @see BehaviourContextWithFSM.strictlyOn
* @see BehaviourContextWithFSM.onStateOrSubstate
*/
@PreviewFeature
suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
scope: CoroutineScope = defaultCoroutineScopeProvider(),
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,

View File

@@ -2,6 +2,12 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder
import dev.inmo.micro_utils.fsm.common.*
fun interface BehaviourWithFSMStateHandler<I : O, O : State> {
fun interface BehaviourWithFSMStateHandler<I : O, O : State> : StatesHandler<I, O> {
suspend fun BehaviourContextWithFSM<in O>.handleState(state: I): O?
override suspend fun StatesMachine<in O>.handleState(state: I): O? = if (this is BehaviourContextWithFSM) {
handleState(state)
} else {
null
}
}

View File

@@ -1,12 +1,6 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder
import dev.inmo.micro_utils.coroutines.LinkedSupervisorScope
import dev.inmo.micro_utils.coroutines.weakLaunch
import dev.inmo.micro_utils.fsm.common.*
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlin.reflect.KClass
/**
@@ -23,21 +17,25 @@ class BehaviourWithFSMStateHandlerHolder<I : O, O : State>(
private val inputKlass: KClass<I>,
private val strict: Boolean = false,
private val delegateTo: BehaviourWithFSMStateHandler<I, O>
) {
) : CheckableHandlerHolder<O, O>, BehaviourWithFSMStateHandler<O, O> {
/**
* Check ability of [delegateTo] to handle this [state]
*
* @return When [state]::class exactly equals to [inputKlass] will always return true. Otherwise when [strict]
* mode is disabled, will be used [KClass.isInstance] of [inputKlass] for checking
*/
fun checkHandleable(state: O): Boolean = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
override suspend fun checkHandleable(state: O): Boolean = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
/**
* Handling of state :)
*/
suspend fun BehaviourContextWithFSM<in O>.handleState(
state: O
): O? = with(delegateTo) {
override suspend fun BehaviourContextWithFSM<in O>.handleState(state: O): O? = with(delegateTo) {
@Suppress("UNCHECKED_CAST")
handleState(state as I)
}
override suspend fun StatesMachine<in O>.handleState(state: O): O? = with(delegateTo) {
@Suppress("UNCHECKED_CAST")
handleState(state as I)
}
}

View File

@@ -25,7 +25,6 @@ expect var defaultCoroutineScopeProvider: () -> CoroutineScope
* @see [BehaviourContext]
* @see startGettingOfUpdatesByLongPolling
*/
@PreviewFeature
suspend fun TelegramBot.buildBehaviour(
flowUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
scope: CoroutineScope = defaultCoroutineScopeProvider(),
@@ -53,7 +52,6 @@ suspend fun TelegramBot.buildBehaviour(
* @see BehaviourContext
* @see startGettingOfUpdatesByLongPolling
*/
@PreviewFeature
suspend fun TelegramBot.buildBehaviourWithLongPolling(
scope: CoroutineScope = defaultCoroutineScopeProvider(),
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,

View File

@@ -4,9 +4,9 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder
import dev.inmo.micro_utils.coroutines.*
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.updateshandlers.*
import dev.inmo.tgbotapi.utils.RiskFeature
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
@@ -47,12 +47,15 @@ interface BehaviourContext : FlowsUpdatesFilter, TelegramBot, CoroutineScope {
val flowsUpdatesFilter: FlowsUpdatesFilter
get() = this
val triggersHolder: TriggersHolder
fun copy(
bot: TelegramBot = this.bot,
scope: CoroutineScope = this.scope,
broadcastChannelsSize: Int = 100,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
upstreamUpdatesFlow: Flow<Update>? = null,
triggersHolder: TriggersHolder = TriggersHolder(),
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
): BehaviourContext
}
@@ -63,6 +66,7 @@ class DefaultBehaviourContext(
broadcastChannelsSize: Int = 100,
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
private val upstreamUpdatesFlow: Flow<Update>? = null,
override val triggersHolder: TriggersHolder = TriggersHolder(),
private val updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
) : AbstractFlowsUpdatesFilter(), TelegramBot by bot, CoroutineScope by scope, BehaviourContext {
@@ -80,7 +84,7 @@ class DefaultBehaviourContext(
} else {
it
}
}
}.accumulatorFlow(scope)
override val asUpdateReceiver: UpdateReceiver<Update> = additionalUpdatesSharedFlow::emit
override fun copy(
@@ -89,56 +93,124 @@ class DefaultBehaviourContext(
broadcastChannelsSize: Int,
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
triggersHolder: TriggersHolder,
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter)
): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder, updatesFilter)
}
fun BehaviourContext(
bot: TelegramBot,
scope: CoroutineScope,
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter()
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow)
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
triggersHolder: TriggersHolder = TriggersHolder(),
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder)
inline fun <T> BehaviourContext(
bot: TelegramBot,
scope: CoroutineScope,
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
triggersHolder: TriggersHolder = TriggersHolder(),
crossinline block: BehaviourContext.() -> T
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow).run(block)
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder).run(block)
fun <BC : BehaviourContext> BC.createSubContext(
scope: CoroutineScope = LinkedSupervisorScope(),
triggersHolder: TriggersHolder = this.triggersHolder,
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
updatesFilter: CustomBehaviourContextAndTypeReceiver<BC, Boolean, Update>? = null,
) = copy(
scope = scope,
updatesFilter = updatesFilter ?.let { _ ->
{
(this as? BC) ?.run {
updatesFilter(it)
} ?: true
}
},
upstreamUpdatesFlow = updatesUpstreamFlow,
triggersHolder = triggersHolder
) as BC
/**
* Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and
* [CoroutineScope] as new [BehaviourContext.scope]
* Launch [behaviourContextReceiver] in context of [this] as [BehaviourContext] and as [kotlin.coroutines.CoroutineContext]
*
* @param stopOnCompletion ___FALSE BY DEFAULT___. Will stop [this] in case if passed true
*/
suspend fun <T, BC : BehaviourContext> BC.doInContext(
stopOnCompletion: Boolean = false,
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
): T {
return withContext(coroutineContext) {
behaviourContextReceiver().also { if (stopOnCompletion) stop() }
}
}
/**
* Creates new one [BehaviourContext] using [createSubContext] and launches [behaviourContextReceiver] in a new context
* using [doInContext]
*
* @param stopOnCompletion ___TRUE BY DEFAULT___
*/
suspend fun <T, BC : BehaviourContext> BC.createSubContextAndDoWithUpdatesFilter(
scope: CoroutineScope = LinkedSupervisorScope(),
triggersHolder: TriggersHolder = this.triggersHolder,
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
updatesFilter: CustomBehaviourContextAndTypeReceiver<BC, Boolean, Update>? = null,
stopOnCompletion: Boolean = true,
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
): T {
return createSubContext(
scope,
triggersHolder,
updatesUpstreamFlow,
updatesFilter
).doInContext(
stopOnCompletion,
behaviourContextReceiver
)
}
/**
* Creates new one [BehaviourContext] using [createSubContext] and launches [behaviourContextReceiver] in a new context
* using [doInContext]
*
* @param stopOnCompletion ___TRUE BY DEFAULT___
*/
@Deprecated("Renamed", ReplaceWith("createSubContextAndDoWithUpdatesFilter", "dev.inmo.tgbotapi.extensions.behaviour_builder.createSubContextAndDoWithUpdatesFilter"))
suspend fun <T, BC : BehaviourContext> BC.doInSubContextWithUpdatesFilter(
updatesFilter: CustomBehaviourContextAndTypeReceiver<BC, Boolean, Update>?,
stopOnCompletion: Boolean = true,
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
scope: CoroutineScope = LinkedSupervisorScope(),
triggersHolder: TriggersHolder = this.triggersHolder,
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
): T {
val newContext = copy(
scope = scope,
updatesFilter = updatesFilter ?.let { _ ->
{
(this as? BC) ?.run {
updatesFilter(it)
} ?: true
}
},
upstreamUpdatesFlow = updatesUpstreamFlow
) as BC
return withContext(currentCoroutineContext()) {
newContext.behaviourContextReceiver().also { if (stopOnCompletion) newContext.stop() }
}
return createSubContext(
scope,
triggersHolder,
updatesUpstreamFlow,
updatesFilter
).doInContext(
stopOnCompletion,
behaviourContextReceiver
)
}
@Deprecated("Redundant", ReplaceWith("createSubContextAndDoWithUpdatesFilter", "dev.inmo.tgbotapi.extensions.behaviour_builder.createSubContextAndDoWithUpdatesFilter"))
suspend fun <T> BehaviourContext.doInSubContext(
stopOnCompletion: Boolean = true,
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
scope: CoroutineScope = LinkedSupervisorScope(),
triggersHolder: TriggersHolder = this.triggersHolder,
behaviourContextReceiver: BehaviourContextReceiver<T>
) = doInSubContextWithUpdatesFilter(updatesFilter = null, stopOnCompletion, updatesUpstreamFlow, scope, behaviourContextReceiver)
) = createSubContextAndDoWithUpdatesFilter(
scope,
triggersHolder,
updatesUpstreamFlow,
updatesFilter = null,
stopOnCompletion,
behaviourContextReceiver
)
/**
* This method will cancel ALL subsequent contexts, expectations and waiters

View File

@@ -2,7 +2,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.asChatJoinRequestUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
import kotlinx.coroutines.flow.toList

View File

@@ -2,10 +2,9 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.asChosenInlineResultUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.*
import dev.inmo.tgbotapi.types.polls.*
import kotlinx.coroutines.flow.toList
typealias ChosenInlineResultMapper<T> = suspend T.() -> T?

View File

@@ -36,7 +36,7 @@ internal suspend fun <O : MessageContent> BehaviourContext.waitCommonMessage(
val messages = when (it) {
is SentMediaGroupUpdate -> {
if (includeMediaGroups) {
it.data.map { it as CommonMessage<MessageContent> }
it.data
} else {
emptyList()
}
@@ -45,7 +45,8 @@ internal suspend fun <O : MessageContent> BehaviourContext.waitCommonMessage(
else -> return@expectFlow emptyList()
}
messages.mapNotNull { message ->
val asCommonMessage = message as CommonMessage<MessageContent>
@Suppress("UNCHECKED_CAST")
val asCommonMessage = message as? CommonMessage<MessageContent> ?: return@mapNotNull null
if (filter == null || filter(asCommonMessage)) {
asCommonMessage.mapper()
} else {

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.toList
typealias MediaGroupFilter<T> = suspend List<MediaGroupMessage<T>>.() -> Boolean
@PreviewFeature
internal suspend inline fun <reified T : MediaGroupContent> BehaviourContext.buildMediaGroupWaiter(
count: Int = 1,
initRequest: Request<*>? = null,

View File

@@ -2,9 +2,9 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.asPollAnswerUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.polls.*
import dev.inmo.tgbotapi.types.polls.PollAnswer
import kotlinx.coroutines.flow.toList
typealias PollAnswerMapper = suspend PollAnswer.() -> PollAnswer?
@@ -45,14 +45,10 @@ private suspend inline fun BehaviourContext.waitPollAnswers(
}
}
) {
if (this is PollAnswer) {
if (mapper == null) {
this
} else {
mapper(this)
}
if (mapper == null) {
this
} else {
null
mapper(this)
}
}

View File

@@ -2,7 +2,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.asPollUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.polls.*
import kotlinx.coroutines.flow.toList

View File

@@ -3,10 +3,8 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.utils.asPreCheckoutQueryUpdate
import dev.inmo.tgbotapi.extensions.utils.asShippingQueryUpdate
import dev.inmo.tgbotapi.requests.abstracts.Request
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
import dev.inmo.tgbotapi.types.payments.ShippingQuery
import kotlinx.coroutines.flow.toList
typealias PreCheckoutQueryMapper = suspend PreCheckoutQuery.() -> PreCheckoutQuery?

View File

@@ -8,7 +8,7 @@ import dev.inmo.tgbotapi.types.update.abstracts.Update
/**
* Allow only messages which are not [MediaGroupMessage]
*/
val MessageFilterExcludingMediaGroups: BehaviourContextAndTwoTypesReceiver<Boolean, CommonMessage<*>, Update> = { message, update ->
val MessageFilterExcludingMediaGroups: BehaviourContextAndTwoTypesReceiver<Boolean, CommonMessage<*>, Update> = { _, update ->
update !is MediaGroupMessage<*>
}

View File

@@ -2,6 +2,8 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
@@ -11,6 +13,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus
import dev.inmo.tgbotapi.extensions.utils.asCallbackQueryUpdate
import dev.inmo.tgbotapi.types.queries.callback.*
import dev.inmo.tgbotapi.types.update.abstracts.Update
import kotlinx.coroutines.Job
internal suspend inline fun <BC : BehaviourContext, reified T : CallbackQuery> BC.onCallbackQuery(
initialFilter: SimpleFilter<T>? = null,
@@ -21,6 +24,48 @@ internal suspend inline fun <BC : BehaviourContext, reified T : CallbackQuery> B
(it.asCallbackQueryUpdate() ?.data as? T) ?.let(::listOfNotNull)
}
/**
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
* to combinate several filters
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
* data
*/
internal suspend inline fun <BC : BehaviourContext, reified T : DataCallbackQuery> BC.onDataCallbackQueryCounted(
initialFilter: SimpleFilter<T>? = null,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, T, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in T, Any> = ByUserCallbackQueryMarkerFactory,
noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, T>
): Job {
val newInitialFilter = SimpleFilter<DataCallbackQuery> {
it is T && initialFilter ?.invoke(it) ?: true
}::invoke
return runCatchingSafely {
onCallbackQuery (
initialFilter,
subcontextUpdatesFilter,
markerFactory,
scenarioReceiver
)
}.onFailure {
triggersHolder.handleableCallbackQueriesDataHolder.unregisterHandleable(newInitialFilter)
}.onSuccess {
triggersHolder.handleableCallbackQueriesDataHolder.registerHandleable(newInitialFilter)
it.invokeOnCompletion {
runCatching {
launchSafelyWithoutExceptions {
triggersHolder.handleableCallbackQueriesDataHolder.unregisterHandleable(newInitialFilter)
}
}
}
}.getOrThrow()
}
/**
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
@@ -38,7 +83,7 @@ suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
) = onCallbackQuery(
) = onDataCallbackQueryCounted(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@@ -166,7 +211,7 @@ suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
) = onCallbackQuery(
) = onDataCallbackQueryCounted(
initialFilter,
subcontextUpdatesFilter,
markerFactory,
@@ -294,7 +339,7 @@ suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
) = onCallbackQuery(
) = onDataCallbackQueryCounted(
initialFilter,
subcontextUpdatesFilter,
markerFactory,

View File

@@ -0,0 +1,87 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserCallbackQueryMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.types.queries.callback.*
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.PreviewFeature
/**
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
* to combinate several filters
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
* data
*/
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.onUnhandledDataCallbackQuery(
initialFilter: SimpleFilter<DataCallbackQuery>? = null,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
) = onCallbackQuery (
initialFilter * !SimpleFilter<MessageDataCallbackQuery> { triggersHolder.handleableCallbackQueriesDataHolder.isHandled(it) },
subcontextUpdatesFilter,
markerFactory,
scenarioReceiver
)
/**
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
* to combinate several filters
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
* data
*/
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.onUnhandledInlineMessageIdDataCallbackQuery(
initialFilter: SimpleFilter<InlineMessageIdDataCallbackQuery>? = null,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
) = onCallbackQuery (
initialFilter * !SimpleFilter<MessageDataCallbackQuery> { triggersHolder.handleableCallbackQueriesDataHolder.isHandled(it) },
subcontextUpdatesFilter,
markerFactory,
scenarioReceiver
)
/**
* @param initialFilter This filter will be called to remove unnecessary data BEFORE [scenarioReceiver] call
* @param subcontextUpdatesFilter This filter will be applied to each update inside of [scenarioReceiver]. For example,
* this filter will be used if you will call [dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage].
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContextAndTwoTypesReceiver] function to create your own.
* Use [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.plus] or [dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times]
* to combinate several filters
* @param [markerFactory] Will be used to identify different "stream". [scenarioReceiver] will be called synchronously
* in one "stream". Output of [markerFactory] will be used as a key for "stream"
* @param scenarioReceiver Main callback which will be used to handle incoming data if [initialFilter] will pass that
* data
*/
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.onUnhandledMessageDataCallbackQuery(
initialFilter: SimpleFilter<MessageDataCallbackQuery>? = null,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
) = onCallbackQuery(
initialFilter * !SimpleFilter<MessageDataCallbackQuery> { triggersHolder.handleableCallbackQueriesDataHolder.isHandled(it) },
subcontextUpdatesFilter,
markerFactory,
scenarioReceiver
)

View File

@@ -1,9 +1,10 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.ChatJoinRequestFilterByChat
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatChatJoinRequestMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.utils.asChatJoinRequestUpdate
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
import dev.inmo.tgbotapi.types.update.abstracts.Update

View File

@@ -4,11 +4,10 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserIdChosenInlineResultMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.utils.asChosenInlineResultUpdate
import dev.inmo.tgbotapi.extensions.utils.asPollUpdate
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.*
import dev.inmo.tgbotapi.types.polls.*
import dev.inmo.tgbotapi.types.update.abstracts.Update
internal suspend inline fun <BC : BehaviourContext, reified T : ChosenInlineResult> BC.onChosenInlineResultBase(

View File

@@ -2,6 +2,8 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat
@@ -15,8 +17,7 @@ import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.update.abstracts.Update
import kotlinx.coroutines.Job
suspend fun <BC : BehaviourContext> BC.command(
internal suspend fun <BC : BehaviourContext> BC.commandUncounted(
commandRegex: Regex,
requireOnlyCommandInMessage: Boolean = true,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
@@ -43,6 +44,35 @@ suspend fun <BC : BehaviourContext> BC.command(
scenarioReceiver
)
suspend fun <BC : BehaviourContext> BC.command(
commandRegex: Regex,
requireOnlyCommandInMessage: Boolean = true,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
): Job = runCatchingSafely {
commandUncounted(
commandRegex,
requireOnlyCommandInMessage,
initialFilter,
subcontextUpdatesFilter,
markerFactory,
scenarioReceiver
)
}.onFailure {
triggersHolder.handleableCommandsHolder.unregisterHandleable(commandRegex)
}.onSuccess {
triggersHolder.handleableCommandsHolder.registerHandleable(commandRegex)
it.invokeOnCompletion {
runCatching {
launchSafelyWithoutExceptions {
triggersHolder.handleableCommandsHolder.unregisterHandleable(commandRegex)
}
}
}
}.getOrThrow()
suspend fun <BC : BehaviourContext> BC.command(
command: String,
requireOnlyCommandInMessage: Boolean = true,
@@ -52,22 +82,22 @@ suspend fun <BC : BehaviourContext> BC.command(
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
) = command(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
suspend inline fun <BC : BehaviourContext> BC.onCommand(
suspend fun <BC : BehaviourContext> BC.onCommand(
commandRegex: Regex,
requireOnlyCommandInMessage: Boolean = true,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
): Job = command(commandRegex, requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
suspend inline fun <BC : BehaviourContext> BC.onCommand(
suspend fun <BC : BehaviourContext> BC.onCommand(
command: String,
requireOnlyCommandInMessage: Boolean = true,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
): Job = onCommand(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
suspend fun <BC : BehaviourContext> BC.commandWithArgs(
@@ -104,18 +134,18 @@ suspend fun <BC : BehaviourContext> BC.commandWithArgs(
scenarioReceiver = scenarioReceiver
)
suspend inline fun <BC : BehaviourContext> BC.onCommandWithArgs(
suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
commandRegex: Regex,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
noinline scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, CommonMessage<TextContent>, Array<String>>
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, CommonMessage<TextContent>, Array<String>>
): Job = commandWithArgs(commandRegex, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
suspend inline fun <BC : BehaviourContext> BC.onCommandWithArgs(
suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
command: String,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
noinline scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, CommonMessage<TextContent>, Array<String>>
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, CommonMessage<TextContent>, Array<String>>
): Job = onCommandWithArgs(command.toRegex(), initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)

View File

@@ -0,0 +1,81 @@
@file:Suppress("unused")
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
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.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.asBotCommandTextSource
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
import dev.inmo.tgbotapi.types.message.content.TextContent
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.PreviewFeature
import kotlinx.coroutines.Job
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.unhandledCommand(
requireOnlyCommandInMessage: Boolean = true,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
): Job = onText(
CommonMessageFilter<TextContent> { message ->
val content = message.content
val textSources = content.textSources
val sizeRequirement = if (requireOnlyCommandInMessage) {
textSources.size == 1
} else {
true
}
sizeRequirement && textSources.any {
val command = it.asBotCommandTextSource() ?.command ?: return@any false
!triggersHolder.handleableCommandsHolder.isHandled(command)
}
}.let {
initialFilter ?.times(it) ?: it
},
subcontextUpdatesFilter,
markerFactory,
scenarioReceiver
)
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.onUnhandledCommand(
requireOnlyCommandInMessage: Boolean = true,
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
): Job = unhandledCommand(requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.unhandledCommandWithArgs(
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, CommonMessage<TextContent>, Map<String, Array<String>>>
) = onUnhandledCommand(
requireOnlyCommandInMessage = false,
initialFilter = initialFilter,
subcontextUpdatesFilter = subcontextUpdatesFilter,
markerFactory = markerFactory
) {
val args = it.parseCommandsWithParams().let { commandsWithArgs ->
commandsWithArgs
}
scenarioReceiver(it, args)
}
@PreviewFeature
suspend fun <BC : BehaviourContext> BC.onUnhandledCommandWithArgs(
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<TextContent>, Update> = MessageFilterByChat,
markerFactory: MarkerFactory<in CommonMessage<TextContent>, Any> = ByChatMessageMarkerFactory,
scenarioReceiver: CustomBehaviourContextAndTwoTypesReceiver<BC, Unit, CommonMessage<TextContent>, Map<String, Array<String>>>
): Job = unhandledCommandWithArgs(initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)

View File

@@ -36,7 +36,6 @@ import dev.inmo.tgbotapi.types.update.abstracts.BaseEditMessageUpdate
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.utils.PreviewFeature
@PreviewFeature
internal suspend inline fun <BC : BehaviourContext, reified T : MessageContent> BC.onEditedContent(
initialFilter: CommonMessageFilter<T>? = null,
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<T>, Update>? = MessageFilterByChat,

View File

@@ -23,6 +23,7 @@ internal suspend inline fun <BC : BehaviourContext, reified T : ChatEvent> BC.on
markerFactory: MarkerFactory<in ChatEventMessage<T>, Any> = ByChatMessageMarkerFactory,
noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, ChatEventMessage<T>>
) = on(markerFactory, initialFilter, subcontextUpdatesFilter, scenarioReceiver) {
@Suppress("UNCHECKED_CAST")
(it.asBaseSentMessageUpdate() ?.data ?.asChatEventMessage() ?.takeIf { it.chatEvent is T } as? ChatEventMessage<T>) ?.let(::listOfNotNull)
}
@@ -584,7 +585,7 @@ suspend fun <BC : BehaviourContext> BC.onWebAppData(
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, PrivateEventMessage<WebAppData>>
) = onEvent(
initialFilter ?.let { { it is PrivateEventMessage<WebAppData> && initialFilter(it) } },
subcontextUpdatesFilter ?.let { { it: ChatEventMessage<WebAppData>, update: Update -> it is PrivateEventMessage<WebAppData> && subcontextUpdatesFilter(it, update) } },
subcontextUpdatesFilter ?.let { { message: ChatEventMessage<WebAppData>, update: Update -> message is PrivateEventMessage<WebAppData> && subcontextUpdatesFilter(message, update) } },
markerFactory
) {
if (it is PrivateEventMessage<WebAppData>) {

View File

@@ -21,12 +21,9 @@ internal suspend inline fun <BC : BehaviourContext, reified T> BC.on(
scope,
markerFactory::invoke
) { triggerData ->
val scope = LinkedSupervisorScope()
doInSubContextWithUpdatesFilter(
createSubContextAndDoWithUpdatesFilter(
updatesFilter = subcontextUpdatesFilter ?.toOneType(triggerData),
stopOnCompletion = false,
updatesUpstreamFlow = allUpdatesFlow.accumulatorFlow(scope),
scope = scope
stopOnCompletion = false
) {
scenarioReceiver(triggerData)
}

View File

@@ -4,10 +4,10 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByIdPollAnswerMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.utils.asPollAnswerUpdate
import dev.inmo.tgbotapi.extensions.utils.asPollUpdate
import dev.inmo.tgbotapi.types.polls.*
import dev.inmo.tgbotapi.types.polls.PollAnswer
import dev.inmo.tgbotapi.types.update.abstracts.Update
internal suspend inline fun <BC : BehaviourContext> BC.onPollAnswered(

View File

@@ -4,7 +4,8 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByIdPollMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.utils.asPollUpdate
import dev.inmo.tgbotapi.types.polls.*
import dev.inmo.tgbotapi.types.update.abstracts.Update

View File

@@ -1,13 +1,12 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.PreCheckoutQueryFilterByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserPreCheckoutQueryMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.utils.asPreCheckoutQueryUpdate
import dev.inmo.tgbotapi.extensions.utils.asShippingQueryUpdate
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
import dev.inmo.tgbotapi.types.payments.ShippingQuery
import dev.inmo.tgbotapi.types.update.abstracts.Update
/**

View File

@@ -1,10 +1,10 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.ShippingQueryFilterByUser
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByUserShippingQueryMarkerFactory
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
import dev.inmo.tgbotapi.extensions.utils.asShippingQueryUpdate
import dev.inmo.tgbotapi.types.payments.ShippingQuery
import dev.inmo.tgbotapi.types.update.abstracts.Update

View File

@@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.utils
fun interface SimpleFilter<in T> {
suspend operator fun invoke(o: T): Boolean
}
val TrueSimpleFilter = SimpleFilter<Any?> { true }
/**
* @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter

View File

@@ -0,0 +1,5 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
class HandleableCallbackBasedHolder<T> : HandleableTriggersHolder<suspend (T) -> Boolean>() {
suspend fun isHandled(data: T) = handleable.any { it(data) }
}

View File

@@ -0,0 +1,7 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
class HandleableRegexesHolder : HandleableTriggersHolder<Regex>() {
fun isHandled(command: String) = handleable.any {
it.matches(command)
}
}

View File

@@ -0,0 +1,27 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
open class HandleableTriggersHolder<T>(
preset: List<T> = emptyList()
) {
protected val commandsMutex = Mutex()
protected val _handleable = mutableListOf<T>().also {
it.addAll(preset)
}
val handleable: List<T>
get() = _handleable.toList()
suspend fun registerHandleable(data: T) {
commandsMutex.withLock {
_handleable.add(data)
}
}
suspend fun unregisterHandleable(data: T) {
commandsMutex.withLock {
_handleable.remove(data)
}
}
}

View File

@@ -0,0 +1,8 @@
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
import dev.inmo.tgbotapi.types.queries.callback.DataCallbackQuery
class TriggersHolder {
val handleableCommandsHolder = HandleableRegexesHolder()
val handleableCallbackQueriesDataHolder = HandleableCallbackBasedHolder<DataCallbackQuery>()
}

View File

@@ -391,6 +391,8 @@ const val requireShippingAddressField = "need_shipping_address"
const val shouldSendPhoneNumberToProviderField = "send_phone_number_to_provider"
const val shouldSendEmailToProviderField = "send_email_to_provider"
const val resizeKeyboardField = "resize_keyboard"
const val oneTimeKeyboardField = "one_time_keyboard"
const val inputFieldPlaceholderField = "input_field_placeholder"
const val priceDependOnShipAddressField = "is_flexible"

View File

@@ -1,16 +1,15 @@
package dev.inmo.tgbotapi.types.buttons
import dev.inmo.tgbotapi.types.inputFieldPlaceholderField
import dev.inmo.tgbotapi.types.inputFieldPlaceholderLimit
import dev.inmo.tgbotapi.types.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ReplyKeyboardMarkup(
val keyboard: Matrix<KeyboardButton>,
@SerialName("resize_keyboard")
@SerialName(resizeKeyboardField)
val resizeKeyboard: Boolean? = null,
@SerialName("one_time_keyboard")
@SerialName(oneTimeKeyboardField)
val oneTimeKeyboard: Boolean? = null,
@SerialName(inputFieldPlaceholderField)
val inputFieldPlaceholder: String? = null,