diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f05e77f22..ececa0b868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # TelegramBotAPI changelog +## 0.38.19 + +* `BehaviourBuilder`: + * Hotfixes +* `BehaviourBuilder FSM`: + * `BehaviourContextWithFSMBuilder` deprecated in favor to `BehaviourContextWithFSM` + * Now it is possible to define additional handlers in subcontexts of `BehaviourBuilderWithFSM` + ## 0.38.18 * `Core`: diff --git a/gradle.properties b/gradle.properties index 876f4856d8..0401f4f412 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,6 +20,6 @@ javax_activation_version=1.1.1 dokka_version=1.6.10 library_group=dev.inmo -library_version=0.38.18 +library_version=0.38.19 github_release_plugin_version=2.3.7 diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt index be5cdb8923..6212a4134a 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSM.kt @@ -9,6 +9,8 @@ import dev.inmo.micro_utils.coroutines.accumulatorFlow import kotlinx.coroutines.* import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.* +import kotlin.jvm.JvmName +import kotlin.reflect.KClass /** * Interface which combine [BehaviourContext] and [StatesMachine]. Subcontext of triggers and states contexts must have @@ -30,6 +32,25 @@ interface BehaviourContextWithFSM : BehaviourContext, StatesMachine add(kClass: KClass, strict: Boolean = false, handler: BehaviourWithFSMStateHandler) + + /** + * Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that + * for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass + * requirements + * + * @see BehaviourWithFSMStateHandlerHolder + * @see strictlyOn + */ + fun addStrict(kClass: KClass, handler: BehaviourWithFSMStateHandler) = add(kClass, strict = true, handler) + override fun copy( bot: TelegramBot, scope: CoroutineScope, @@ -48,6 +69,28 @@ interface BehaviourContextWithFSM : BehaviourContext, StatesMachine BehaviourContextWithFSM.onStateOrSubstate(handler: BehaviourWithFSMStateHandler) = add(I::class, strict = false, handler) + +/** + * Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that + * for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass + * requirements + * + * @see BehaviourWithFSMStateHandlerHolder + * @see BehaviourContextWithFSM.addStrict + */ +@Suppress("MemberVisibilityCanBePrivate") +inline fun BehaviourContextWithFSM.strictlyOn(handler: BehaviourWithFSMStateHandler) = addStrict(I::class, handler) + /** * Default realization of [BehaviourContextWithFSM]. It uses [behaviourContext] as a base for this object as * [BehaviourContext], but managing substates contexts updates for avoiding of updates lost between states @@ -58,6 +101,9 @@ class DefaultBehaviourContextWithFSM( private val handlers: List> ) : BehaviourContext by behaviourContext, BehaviourContextWithFSM { private val updatesFlows = mutableMapOf>() + private val additionalHandlers = mutableListOf>() + private var actualHandlersList = additionalHandlers + handlers + private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) { allUpdatesFlow.accumulatorFlow(scope) } @@ -65,12 +111,17 @@ class DefaultBehaviourContextWithFSM( override suspend fun StatesMachine.handleState(state: T): T? = launchStateHandling( state, allUpdatesFlow, - handlers + actualHandlersList ) + override fun add(kClass: KClass, strict: Boolean, handler: BehaviourWithFSMStateHandler) { + additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler)) + actualHandlersList = additionalHandlers + handlers + } + override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions { val statePerformer: suspend (T) -> Unit = { state: T -> - val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), handlers) + val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), actualHandlersList) if (newState != null) { statesManager.update(state, newState) } else { @@ -94,6 +145,26 @@ class DefaultBehaviourContextWithFSM( launch { statePerformer(it) } } } + /** + * 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 + * + * @see BehaviourWithFSMStateHandlerHolder + * @see BehaviourContextWithFSM.add + */ + @Suppress("MemberVisibilityCanBePrivate") + inline fun onStateOrSubstate(handler: BehaviourWithFSMStateHandler) = add(I::class, strict = false, handler) + + /** + * Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that + * for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass + * requirements + * + * @see BehaviourWithFSMStateHandlerHolder + * @see BehaviourContextWithFSM.addStrict + */ + @Suppress("MemberVisibilityCanBePrivate") + inline fun strictlyOn(handler: BehaviourWithFSMStateHandler) = addStrict(I::class, handler) override suspend fun startChain(state: T) { statesManager.startChain(state) @@ -106,7 +177,7 @@ class DefaultBehaviourContextWithFSM( onBufferOverflow: BufferOverflow, upstreamUpdatesFlow: Flow?, updatesFilter: BehaviourContextAndTypeReceiver? - ): BehaviourContextWithFSM = BehaviourContextWithFSM( + ): DefaultBehaviourContextWithFSM = BehaviourContextWithFSM( behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter), handlers, statesManager diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt index 7723b81c70..b1dd83032b 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourContextWithFSMBuilder.kt @@ -14,70 +14,8 @@ import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlin.reflect.KClass -class BehaviourContextWithFSMBuilder internal constructor( - private val resultBehaviourContext: BehaviourContextWithFSM, - private val handlers: MutableList> -) : BehaviourContextWithFSM by resultBehaviourContext { - internal constructor( - baseBehaviourContext: BehaviourContext, - statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - handlers: MutableList> = mutableListOf() - ) : this(DefaultBehaviourContextWithFSM(baseBehaviourContext, statesManager, handlers), handlers) - - /** - * 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 - * - * @see BehaviourWithFSMStateHandlerHolder - * @see onStateOrSubstate - */ - fun add(kClass: KClass, handler: BehaviourWithFSMStateHandler) { - handlers.add(BehaviourWithFSMStateHandlerHolder(kClass, false, handler)) - } - - /** - * Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that - * for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass - * requirements - * - * @see BehaviourWithFSMStateHandlerHolder - * @see strictlyOn - */ - fun addStrict(kClass: KClass, handler: BehaviourWithFSMStateHandler) { - handlers.add(BehaviourWithFSMStateHandlerHolder(kClass, true, handler)) - } - - - /** - * 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 - * - * @see BehaviourWithFSMStateHandlerHolder - * @see BehaviourContextWithFSMBuilder.add - */ - @Suppress("MemberVisibilityCanBePrivate") - inline fun onStateOrSubstate(handler: BehaviourWithFSMStateHandler) { - add(I::class, handler) - } - - /** - * Add STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Strict means that - * for input [State] will be used [State]::class == [kClass] and any [State] with exactly the same type will pass - * requirements - * - * @see BehaviourWithFSMStateHandlerHolder - * @see BehaviourContextWithFSMBuilder.addStrict - */ - @Suppress("MemberVisibilityCanBePrivate") - inline fun strictlyOn(handler: BehaviourWithFSMStateHandler) { - addStrict(I::class, handler) - } - - /** - * Returns completed [resultBehaviourContext], [handlers] and [statesManager] - */ - internal fun build() = resultBehaviourContext -} +@Deprecated("Will be removed soon") +typealias BehaviourContextWithFSMBuilder = BehaviourContextWithFSM /** * Creates [BehaviourContextWithFSM] via creating of [DefaultBehaviourContext] with [this] as [TelegramBot], @@ -94,17 +32,17 @@ suspend fun TelegramBot.buildBehaviourWithFSM( scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver, Unit> -): BehaviourContextWithFSM = BehaviourContextWithFSMBuilder( + presetHandlers: List> = listOf(), + block: CustomBehaviourContextReceiver, Unit> +): DefaultBehaviourContextWithFSM = BehaviourContextWithFSM( DefaultBehaviourContext( this, defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope, upstreamUpdatesFlow = upstreamUpdatesFlow ), - statesManager, - presetHandlers -).apply { block() }.build() + presetHandlers, + statesManager +).apply { block() } /** * Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates @@ -116,9 +54,9 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver, Unit> -): Pair, Job> = buildBehaviourWithFSM( + presetHandlers: List> = listOf(), + block: CustomBehaviourContextReceiver, Unit> +): Pair, Job> = buildBehaviourWithFSM( upstreamUpdatesFlow, scope, defaultExceptionsHandler, @@ -147,8 +85,8 @@ suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( * @see BehaviourContext * @see BehaviourContextWithFSM * @see longPolling - * @see BehaviourContextWithFSMBuilder.strictlyOn - * @see BehaviourContextWithFSMBuilder.onStateOrSubstate + * @see BehaviourContextWithFSM.strictlyOn + * @see BehaviourContextWithFSM.onStateOrSubstate */ @PreviewFeature suspend fun TelegramBot.buildBehaviourWithFSM( @@ -156,17 +94,17 @@ suspend fun TelegramBot.buildBehaviourWithFSM( scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver, Unit> -): BehaviourContextWithFSM = BehaviourContextWithFSMBuilder( + presetHandlers: List> = listOf(), + block: CustomBehaviourContextReceiver, Unit> +): DefaultBehaviourContextWithFSM = BehaviourContextWithFSM( DefaultBehaviourContext( this, defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope, upstreamUpdatesFlow = flowUpdatesFilter.allUpdatesFlow ), - statesManager, - presetHandlers -).apply { block() }.build() + presetHandlers, + statesManager +).apply { block() } /** * Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates @@ -176,16 +114,16 @@ suspend fun TelegramBot.buildBehaviourWithFSM( * @see buildBehaviourWithFSMAndStartLongPolling * @see BehaviourContext * @see longPolling - * @see BehaviourContextWithFSMBuilder.strictlyOn - * @see BehaviourContextWithFSMBuilder.onStateOrSubstate + * @see BehaviourContextWithFSM.strictlyOn + * @see BehaviourContextWithFSM.onStateOrSubstate */ @PreviewFeature suspend fun TelegramBot.buildBehaviourWithFSMAndStartLongPolling( scope: CoroutineScope = defaultCoroutineScopeProvider(), defaultExceptionsHandler: ExceptionHandler? = null, statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), - block: CustomBehaviourContextReceiver, Unit> + presetHandlers: List> = listOf(), + block: CustomBehaviourContextReceiver, Unit> ) = FlowsUpdatesFilter().let { buildBehaviourWithFSM( it, diff --git a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt index edfc67de48..2d55cf322a 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/TelegramBotWithFSM.kt @@ -37,9 +37,9 @@ suspend fun telegramBotWithBehaviourAndFSM( builder: KtorRequestsExecutorBuilder.() -> Unit = {}, defaultExceptionsHandler: ExceptionHandler? = null, statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), + presetHandlers: List> = listOf(), testServer: Boolean = false, - block: CustomBehaviourContextReceiver, Unit> + block: CustomBehaviourContextReceiver, Unit> ): TelegramBot = telegramBot( token, apiUrl, @@ -74,9 +74,9 @@ suspend fun telegramBotWithBehaviourAndFSMAndStartLongPolling( builder: KtorRequestsExecutorBuilder.() -> Unit = {}, defaultExceptionsHandler: ExceptionHandler? = null, statesManager: StatesManager = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()), - presetHandlers: MutableList> = mutableListOf(), + presetHandlers: List> = listOf(), testServer: Boolean = false, - block: CustomBehaviourContextReceiver, Unit> + block: CustomBehaviourContextReceiver, Unit> ): Pair { return telegramBot( token,