@file:Suppress("NOTHING_TO_INLINE") package dev.inmo.tgbotapi.extensions.behaviour_builder import dev.inmo.micro_utils.coroutines.* import dev.inmo.tgbotapi.bot.TelegramBot 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.* typealias CustomBehaviourContextReceiver = suspend BC.() -> T typealias BehaviourContextReceiver = CustomBehaviourContextReceiver typealias CustomBehaviourContextAndTypeReceiver = suspend BC.(I) -> T typealias BehaviourContextAndTypeReceiver = CustomBehaviourContextAndTypeReceiver typealias CustomBehaviourContextAndTwoTypesReceiver = suspend BC.(I1, I2) -> T typealias BehaviourContextAndTwoTypesReceiver = CustomBehaviourContextAndTwoTypesReceiver inline fun BehaviourContextReceiver(noinline block: BehaviourContextReceiver) = block inline fun CustomBehaviourContextReceiver(noinline block: CustomBehaviourContextReceiver) = block inline fun BehaviourContextAndTypeReceiver(noinline block: BehaviourContextAndTypeReceiver) = block inline fun BehaviourContextAndTwoTypesReceiver(noinline block: BehaviourContextAndTwoTypesReceiver) = block internal inline fun CustomBehaviourContextAndTwoTypesReceiver.toOneType( i1: I1, ): CustomBehaviourContextAndTypeReceiver = { invoke(this, i1, it) } /** * This class contains all necessary tools for work with bots and especially for [buildBehaviour] * * @see DefaultBehaviourContext */ interface BehaviourContext : FlowsUpdatesFilter, TelegramBot, CoroutineScope { val bot: TelegramBot get() = this /** * Will be used for creating of some subscriptions inside of methods, updates listening and different other things * in context of working with [CoroutineScope] and coroutines. */ val scope: CoroutineScope get() = this /** * This parameter will be used to subscribe on different types of update */ val flowsUpdatesFilter: FlowsUpdatesFilter get() = this fun copy( bot: TelegramBot = this.bot, scope: CoroutineScope = this.scope, broadcastChannelsSize: Int = 100, onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND, upstreamUpdatesFlow: Flow? = null, updatesFilter: BehaviourContextAndTypeReceiver? = null ): BehaviourContext @Deprecated("This method is not recommended to use and will be removed in near release") fun copy( bot: TelegramBot, scope: CoroutineScope = this.scope, flowsUpdatesFilter: FlowsUpdatesFilter = this.flowsUpdatesFilter ): BehaviourContext = copy(upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow) } class DefaultBehaviourContext( override val bot: TelegramBot, override val scope: CoroutineScope, private val broadcastChannelsSize: Int = 100, private val onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND, private val upstreamUpdatesFlow: Flow? = null, private val updatesFilter: BehaviourContextAndTypeReceiver? = null ) : AbstractFlowsUpdatesFilter(), TelegramBot by bot, CoroutineScope by scope, BehaviourContext { private val additionalUpdatesSharedFlow = MutableSharedFlow(0, broadcastChannelsSize, onBufferOverflow) override val allUpdatesFlow: Flow = (additionalUpdatesSharedFlow.asSharedFlow()).let { if (upstreamUpdatesFlow != null) { (it + upstreamUpdatesFlow).distinctUntilChanged { old, new -> old.updateId == new.updateId } } else { it } }.let { val updatesFilter = updatesFilter if (updatesFilter != null) { it.filter { updatesFilter(it) } } else { it } } override val asUpdateReceiver: UpdateReceiver = additionalUpdatesSharedFlow::emit override fun copy( bot: TelegramBot, scope: CoroutineScope, broadcastChannelsSize: Int, onBufferOverflow: BufferOverflow, upstreamUpdatesFlow: Flow?, updatesFilter: BehaviourContextAndTypeReceiver? ): BehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter) } fun BehaviourContext( bot: TelegramBot, scope: CoroutineScope, flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter() ) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow) inline fun BehaviourContext( bot: TelegramBot, scope: CoroutineScope, flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(), crossinline block: BehaviourContext.() -> T ) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow).run(block) /** * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [newFlowsUpdatesFilterSetUp] is provided and * [CoroutineScope] as new [BehaviourContext.scope]. You must do all subscription/running of longPolling manually. * * @param newFlowsUpdatesFilterSetUp As a parameter receives [FlowsUpdatesFilter] from old [this] [BehaviourContext.flowsUpdatesFilter] */ @RiskFeature("It is recommended to use doInSubContextWithUpdatesFilter instead. " + "This method is low level and should not be used in case you are not pretty sure you need it.") @Deprecated("This method is useless and will not be used in future") suspend fun BC.doInSubContextWithFlowsUpdatesFilterSetup( newFlowsUpdatesFilterSetUp: CustomBehaviourContextAndTypeReceiver?, stopOnCompletion: Boolean = true, behaviourContextReceiver: CustomBehaviourContextReceiver ): T = (copy( scope = LinkedSupervisorScope(), ) as BC).run { withContext(coroutineContext) { newFlowsUpdatesFilterSetUp ?.let { it.apply { invoke(this@run, this@doInSubContextWithFlowsUpdatesFilterSetup.flowsUpdatesFilter) } } behaviourContextReceiver().also { if (stopOnCompletion) stop() } } } /** * Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and * [CoroutineScope] as new [BehaviourContext.scope] */ suspend fun BC.doInSubContextWithUpdatesFilter( updatesFilter: CustomBehaviourContextAndTypeReceiver?, stopOnCompletion: Boolean = true, behaviourContextReceiver: CustomBehaviourContextReceiver ): T = copy( scope = LinkedSupervisorScope(), updatesFilter = updatesFilter ?.let { _ -> { (this as? BC) ?.run { updatesFilter(it) } ?: true } } ).run { withContext(coroutineContext) { behaviourContextReceiver().also { if (stopOnCompletion) stop() } } } suspend fun BehaviourContext.doInSubContext( stopOnCompletion: Boolean = true, behaviourContextReceiver: BehaviourContextReceiver ) = doInSubContextWithUpdatesFilter(updatesFilter = null, stopOnCompletion, behaviourContextReceiver) /** * This method will cancel ALL subsequent contexts, expectations and waiters */ fun BehaviourContext.stop() = scope.cancel()