diff --git a/CHANGELOG.md b/CHANGELOG.md index 62c7cfebec..252526a2af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,22 @@ __All the `tgbotapi.extensions.*` packages have been removed__ * `BehaviourBuilder`: * `SimpleFilter` now is a `fun interface` instead of just callback (fix of [#546](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/546)) +## 0.38.20 + +* `BehaviourBuilder FSM`: + * Hotfixes +* `WebApps`: + * New extension `TelegramBot#answerWebAppQuery` + * New function `handleResult` + +## 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/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..2bb6b3ea89 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 @@ -26,10 +28,31 @@ interface BehaviourContextWithFSM : BehaviourContext, StatesMachine> ): T? { return handlers.firstOrNull { it.checkHandleable(state) } ?.run { - handleState(contextUpdatesFlow, state) + 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 + * + * @see BehaviourWithFSMStateHandlerHolder + * @see onStateOrSubstate + */ + fun 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 +71,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 +103,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 +113,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 +147,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 +179,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/BehaviourWithFSMStateHandlerHolder.kt b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt index 788c1cf3c1..fa3bdbd0e2 100644 --- a/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt +++ b/tgbotapi.behaviour_builder.fsm/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/behaviour_builder/BehaviourWithFSMStateHandlerHolder.kt @@ -34,20 +34,11 @@ class BehaviourWithFSMStateHandlerHolder( /** * Handling of state :) - * - * @param contextUpdatesFlow This [Flow] will be used as source of updates. By contract, this [Flow] must be common - * for all [State]s of incoming [state] [State.context] and for the whole chain inside of [BehaviourContextWithFSM] */ suspend fun BehaviourContextWithFSM.handleState( - contextUpdatesFlow: Flow, state: O - ): O? { - val subscope = scope.LinkedSupervisorScope() - return with(copy(scope = subscope, upstreamUpdatesFlow = contextUpdatesFlow)) { - with(delegateTo) { - handleState(state as I) - } - } + ): O? = with(delegateTo) { + handleState(state as I) } } 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 7bd59ad4f9..bcbe3d7116 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, diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt index ceacda665f..859959aebb 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/answers/AnswerWebAppQuery.kt @@ -6,6 +6,9 @@ import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.InlineQ import dev.inmo.tgbotapi.types.webapps.query.SentWebAppMessage import kotlinx.serialization.* +/** + * @param webAppQueryId [dev.inmo.tgbotapi.webapps.WebAppInitData.queryId] + */ @Serializable data class AnswerWebAppQuery( @SerialName(webAppQueryIdField) diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/AnswerWebAppQuery.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/AnswerWebAppQuery.kt new file mode 100644 index 0000000000..c77aa0b763 --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/AnswerWebAppQuery.kt @@ -0,0 +1,11 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.answers.AnswerWebAppQuery +import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.InlineQueryResult + +suspend fun TelegramBot.answerWebAppQuery( + result: InlineQueryResult +) = webApp.initDataUnsafe.queryId ?.let { + execute(AnswerWebAppQuery(it, result)) +} diff --git a/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/SendData.kt b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/SendData.kt new file mode 100644 index 0000000000..5f469cfd0f --- /dev/null +++ b/tgbotapi.webapps/src/jsMain/kotlin/dev/inmo/tgbotapi/webapps/SendData.kt @@ -0,0 +1,18 @@ +package dev.inmo.tgbotapi.webapps + +import dev.inmo.tgbotapi.types.WebAppQueryId + +/** + * @param onSendData Should return the data which must be used in [WebApp.sendData]. If returns null, data will not be sent + * @param onAnswerWebAppQuery In case if [WebAppInitData.queryId] is presented in [WebApp.initDataUnsafe], will be called + * that callback. Before and after calling of this callback will not be used any method of answering to the telegram + * system, so, you must use something like [answerWebAppQuery] by yourself to send the result + */ +inline fun handleResult( + onSendData: () -> String?, + onAnswerWebAppQuery: (WebAppQueryId) -> Unit +) { + webApp.initDataUnsafe.queryId ?.let { + onAnswerWebAppQuery(it) + } ?: webApp.sendData(onSendData() ?: return) +}