All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext.kt Maven / Gradle / Ivy

There is a newer version: 20.0.1
Show newest version
@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.extensions.behaviour_builder.utils.handlers_registrar.TriggersHolder
import dev.inmo.tgbotapi.types.UpdateId
import dev.inmo.tgbotapi.types.update.abstracts.Update
import dev.inmo.tgbotapi.updateshandlers.*
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 [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

    val triggersHolder: TriggersHolder

    fun copy(
        bot: TelegramBot = this.bot,
        scope: CoroutineScope = this.scope,
        broadcastChannelsSize: Int = 100,
        onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
        upstreamUpdatesFlow: Flow? = null,
        triggersHolder: TriggersHolder = TriggersHolder()
    ): BehaviourContext
}

class DefaultBehaviourContext(
    override val bot: TelegramBot,
    override val scope: CoroutineScope,
    broadcastChannelsSize: Int = 100,
    onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
    private val upstreamUpdatesFlow: Flow? = null,
    override val triggersHolder: TriggersHolder = TriggersHolder()
) : 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) {
            val handledUpdates = mutableSetOf()
            (it + upstreamUpdatesFlow).filter {
                val passed = handledUpdates.add(it.updateId)
                (passed).also { passed ->
                    val needToDropCount = handledUpdates.size - broadcastChannelsSize
                    if (needToDropCount > 0) {
                        handledUpdates.removeAll(
                            handledUpdates.take(needToDropCount).ifEmpty { return@also }
                        )
                    }
                }
            }
        } else {
            it
        }
    }.accumulatorFlow(scope)
    override val asUpdateReceiver: UpdateReceiver = additionalUpdatesSharedFlow::emit

    override fun copy(
        bot: TelegramBot,
        scope: CoroutineScope,
        broadcastChannelsSize: Int,
        onBufferOverflow: BufferOverflow,
        upstreamUpdatesFlow: Flow?,
        triggersHolder: TriggersHolder
    ): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder)
}

fun BehaviourContext(
    bot: TelegramBot,
    scope: CoroutineScope,
    flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
    triggersHolder: TriggersHolder = TriggersHolder(),
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder)

inline fun  BehaviourContext(
    bot: TelegramBot,
    scope: CoroutineScope,
    flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
    triggersHolder: TriggersHolder = TriggersHolder(),
    crossinline block: BehaviourContext.() -> T
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder).run(block)

/**
 * Creates new [BehaviourContext] using its [BehaviourContext.copy] method
 *
 * @param updatesFilter This param will not be used anymore
 */
fun  BC.createSubContext(
    scope: CoroutineScope = LinkedSupervisorScope(),
    triggersHolder: TriggersHolder = this.triggersHolder,
    updatesUpstreamFlow: Flow = allUpdatesFlow,
) = copy(
    scope = scope,
    upstreamUpdatesFlow = updatesUpstreamFlow,
    triggersHolder = triggersHolder
) as BC

/**
 * 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  BC.doInContext(
    stopOnCompletion: Boolean = false,
    behaviourContextReceiver: CustomBehaviourContextReceiver
): 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  BC.createSubContextAndDoWithUpdatesFilter(
    scope: CoroutineScope = LinkedSupervisorScope(),
    triggersHolder: TriggersHolder = this.triggersHolder,
    updatesUpstreamFlow: Flow = allUpdatesFlow,
    stopOnCompletion: Boolean = true,
    behaviourContextReceiver: CustomBehaviourContextReceiver
): T {
    return createSubContext(
        scope,
        triggersHolder,
        updatesUpstreamFlow
    ).doInContext(
        stopOnCompletion,
        behaviourContextReceiver
    )
}

/**
 * This method will cancel ALL subsequent contexts, expectations and waiters
 */
fun BehaviourContext.stop() = scope.cancel()




© 2015 - 2024 Weber Informatics LLC | Privacy Policy