mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2026-01-06 13:29:20 +00:00
Compare commits
60 Commits
6f8b881668
...
1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| c759b9a466 | |||
| 58c1f2ee6a | |||
| 9d16ca3b7e | |||
| 9d40e598f1 | |||
| f1be8bf16e | |||
| 56a8804e99 | |||
| 1e73aac750 | |||
| 82c6eda0b7 | |||
| e9ff93cde1 | |||
| 9b179ea1c9 | |||
| 72d20d2344 | |||
| bcd288fe05 | |||
| 75477060e9 | |||
| 60df609486 | |||
| 556b492b35 | |||
| 53e8c53fb1 | |||
| 69d36c5c1b | |||
| ad97008f12 | |||
| 3e58114eeb | |||
| d55d8fa000 | |||
| bd32dbb3ab | |||
| 8e0389f032 | |||
| cb37b85234 | |||
| df01976ea8 | |||
| 7be74e468b | |||
| 437176203a | |||
| 7b25862035 | |||
| 45585caab0 | |||
| 6fea2a12d8 | |||
| ae639d38e0 | |||
| 0510f72859 | |||
| 233c789054 | |||
| 52f405c3b1 | |||
| 199856acce | |||
| f36d642ec8 | |||
| aa6e5b2284 | |||
| 32b83ac687 | |||
| a2206b99a2 | |||
| 90e0b1ac81 | |||
| 1b5b3af45b | |||
| 7ff6412ec5 | |||
| bd23d3fbdb | |||
| 08147fc33b | |||
| ba4c153659 | |||
| 3c3607d817 | |||
| d0add888c4 | |||
| 3e3adab46b | |||
| 89d13de307 | |||
| 32451f4e1c | |||
| 721c3f2430 | |||
| fc92df8fbe | |||
| ad50f41d1e | |||
| 8c9cd9df67 | |||
| 8e7f7a03c8 | |||
| 484e09374d | |||
|
|
619c82bb32 | ||
| 33fb75a5eb | |||
| 901c1e8956 | |||
| 2ae105cb15 | |||
|
|
db14eee3b6 |
75
CHANGELOG.md
75
CHANGELOG.md
@@ -1,9 +1,34 @@
|
|||||||
# TelegramBotAPI changelog
|
# TelegramBotAPI changelog
|
||||||
|
|
||||||
|
## 1.1.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `MicroUtils.Crypto` will not be provided with that library anymore. Instead, it is recommended to use `Korlibs.Krypto`. You still can add crypto from microutils using next groovy dependency: `dev.inmo:micro_utils.crypto:$micro_utils_version`
|
||||||
|
* `Core`:
|
||||||
|
* Improvements in `TelegramAPIUrlsKeeper#checkWebAppLink`
|
||||||
|
* New field in `TelegramAPIUrlsKeeper#webAppDataSecretKeyHash`
|
||||||
|
* `Behaviour Builder`:
|
||||||
|
* Extension `TelegramBot#buildBehaviour` now returns `BehaviourContext`
|
||||||
|
|
||||||
|
## 1.1.0
|
||||||
|
|
||||||
|
* `Utils`:
|
||||||
|
* New parameter `additionalApplicationEngineEnvironmentConfigurator` in `RequestsExecutor#setWebhookInfoAndStartListenWebhooks` and `startListenWebhooks`
|
||||||
|
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.3.2` -> `1.3.3`
|
||||||
|
* `MicroUtils`: `0.10.3` -> `0.10.4`
|
||||||
|
|
||||||
## 1.0.0
|
## 1.0.0
|
||||||
|
|
||||||
__All the `tgbotapi.extensions.*` packages have been removed__
|
__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.3`
|
||||||
* `Core`:
|
* `Core`:
|
||||||
* **`Ktor` package renamed. Migration:** `dev.inmo.tgbotapi.bot.Ktor` -> `dev.inmo.tgbotapi.bot.ktor`
|
* **`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`
|
* **`CallbackQuery` package renamed. Migration:** `dev.inmo.tgbotapi.types.CallbackQuery([\s\\.])` -> `dev.inmo.tgbotapi.types.queries.callback$1`
|
||||||
@@ -46,6 +71,56 @@ __All the `tgbotapi.extensions.*` packages have been removed__
|
|||||||
* New typealias `FileUrl` (represents `FileId` but declare that they are the same)
|
* New typealias `FileUrl` (represents `FileId` but declare that they are the same)
|
||||||
* `BehaviourBuilder`:
|
* `BehaviourBuilder`:
|
||||||
* `SimpleFilter` now is a `fun interface` instead of just callback (fix of [#546](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/546))
|
* `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
|
||||||
|
|
||||||
|
* `Core`:
|
||||||
|
* New constant `tgWebAppStartParamField`
|
||||||
|
* All keyboards builders and rows blocks becomes not crossinline
|
||||||
|
|
||||||
|
## 0.38.21
|
||||||
|
|
||||||
|
* `WebApps`:
|
||||||
|
* `WebAppInitData#queryId` now have correct js name of field
|
||||||
|
* New function `sendDataOrWorkWithQueryId`
|
||||||
|
|
||||||
|
## 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`:
|
||||||
|
* Add support of test servers (fix of [#577](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/577))
|
||||||
|
* `BehaviourBuilder`:
|
||||||
|
* Fixes in extension `BehaviourContext#doInSubContextWithUpdatesFilter` (thanks to [xzima](https://github.com/xzima))
|
||||||
|
|
||||||
## 0.38.17
|
## 0.38.17
|
||||||
|
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ kotlin.incremental.js=true
|
|||||||
|
|
||||||
kotlin_version=1.6.21
|
kotlin_version=1.6.21
|
||||||
kotlin_coroutines_version=1.6.1
|
kotlin_coroutines_version=1.6.1
|
||||||
kotlin_serialisation_runtime_version=1.3.2
|
kotlin_serialisation_runtime_version=1.3.3
|
||||||
klock_version=2.7.0
|
korlibs_version=2.7.0
|
||||||
uuid_version=0.4.0
|
uuid_version=0.4.0
|
||||||
ktor_version=2.0.1
|
ktor_version=2.0.1
|
||||||
|
|
||||||
micro_utils_version=0.10.1
|
micro_utils_version=0.10.4
|
||||||
|
|
||||||
javax_activation_version=1.1.1
|
javax_activation_version=1.1.1
|
||||||
|
|
||||||
@@ -20,6 +20,6 @@ javax_activation_version=1.1.1
|
|||||||
dokka_version=1.6.21
|
dokka_version=1.6.21
|
||||||
|
|
||||||
library_group=dev.inmo
|
library_group=dev.inmo
|
||||||
library_version=1.0.0
|
library_version=1.1.1
|
||||||
|
|
||||||
github_release_plugin_version=2.3.7
|
github_release_plugin_version=2.3.7
|
||||||
|
|||||||
@@ -40,8 +40,9 @@ data class BotBuilder internal constructor(
|
|||||||
fun buildBot(
|
fun buildBot(
|
||||||
token: String,
|
token: String,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
block: BotBuilder.() -> Unit
|
block: BotBuilder.() -> Unit
|
||||||
) = telegramBot(
|
) = telegramBot(
|
||||||
TelegramAPIUrlsKeeper(token, apiUrl),
|
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
|
||||||
BotBuilder().apply(block).createHttpClient()
|
BotBuilder().apply(block).createHttpClient()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -66,17 +66,19 @@ inline fun telegramBot(
|
|||||||
inline fun telegramBot(
|
inline fun telegramBot(
|
||||||
token: String,
|
token: String,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
client: HttpClient = HttpClient()
|
client: HttpClient = HttpClient()
|
||||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), client)
|
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), client)
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun <T: HttpClientEngineConfig> telegramBot(
|
inline fun <T: HttpClientEngineConfig> telegramBot(
|
||||||
token: String,
|
token: String,
|
||||||
clientFactory: HttpClientEngineFactory<T>,
|
clientFactory: HttpClientEngineFactory<T>,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
noinline clientConfig: HttpClientConfig<T>.() -> Unit = {}
|
noinline clientConfig: HttpClientConfig<T>.() -> Unit = {}
|
||||||
) = telegramBot(
|
) = telegramBot(
|
||||||
TelegramAPIUrlsKeeper(token, apiUrl),
|
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
|
||||||
clientFactory,
|
clientFactory,
|
||||||
clientConfig
|
clientConfig
|
||||||
)
|
)
|
||||||
@@ -90,9 +92,10 @@ inline fun telegramBot(
|
|||||||
token: String,
|
token: String,
|
||||||
clientEngine: HttpClientEngine,
|
clientEngine: HttpClientEngine,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
noinline clientConfig: HttpClientConfig<*>.() -> Unit = {}
|
noinline clientConfig: HttpClientConfig<*>.() -> Unit = {}
|
||||||
) = telegramBot(
|
) = telegramBot(
|
||||||
TelegramAPIUrlsKeeper(token, apiUrl),
|
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
|
||||||
clientEngine,
|
clientEngine,
|
||||||
clientConfig
|
clientConfig
|
||||||
)
|
)
|
||||||
@@ -105,8 +108,9 @@ inline fun telegramBot(
|
|||||||
inline fun telegramBot(
|
inline fun telegramBot(
|
||||||
token: String,
|
token: String,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
noinline clientConfig: HttpClientConfig<*>.() -> Unit
|
noinline clientConfig: HttpClientConfig<*>.() -> Unit
|
||||||
) = telegramBot(
|
) = telegramBot(
|
||||||
TelegramAPIUrlsKeeper(token, apiUrl),
|
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
|
||||||
clientConfig
|
clientConfig
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
|
|||||||
import dev.inmo.micro_utils.fsm.common.*
|
import dev.inmo.micro_utils.fsm.common.*
|
||||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
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.*
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface which combine [BehaviourContext] and [StatesMachine]. Subcontext of triggers and states contexts must have
|
* Interface which combine [BehaviourContext] and [StatesMachine]. Subcontext of triggers and states contexts must have
|
||||||
@@ -20,15 +21,24 @@ import kotlinx.coroutines.flow.*
|
|||||||
interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T> {
|
interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T> {
|
||||||
suspend fun start() = start(this)
|
suspend fun start() = start(this)
|
||||||
|
|
||||||
suspend fun launchStateHandling(
|
/**
|
||||||
state: T,
|
* Add NON STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Non strict means that
|
||||||
contextUpdatesFlow: Flow<Update>,
|
* for input [State] will be used [KClass.isInstance] and any inheritor of [kClass] will pass this requirement
|
||||||
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
|
*
|
||||||
): T? {
|
* @see BehaviourWithFSMStateHandlerHolder
|
||||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
* @see onStateOrSubstate
|
||||||
handleState(contextUpdatesFlow, state)
|
*/
|
||||||
}
|
fun <I : T> add(kClass: KClass<I>, strict: Boolean = false, handler: BehaviourWithFSMStateHandler<I, T>)
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* 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 <I : T> addStrict(kClass: KClass<I>, handler: BehaviourWithFSMStateHandler<I, T>) = add(kClass, strict = true, handler)
|
||||||
|
|
||||||
override fun copy(
|
override fun copy(
|
||||||
bot: TelegramBot,
|
bot: TelegramBot,
|
||||||
@@ -36,6 +46,7 @@ interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T
|
|||||||
broadcastChannelsSize: Int,
|
broadcastChannelsSize: Int,
|
||||||
onBufferOverflow: BufferOverflow,
|
onBufferOverflow: BufferOverflow,
|
||||||
upstreamUpdatesFlow: Flow<Update>?,
|
upstreamUpdatesFlow: Flow<Update>?,
|
||||||
|
triggersHolder: TriggersHolder,
|
||||||
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
||||||
): BehaviourContextWithFSM<T>
|
): BehaviourContextWithFSM<T>
|
||||||
|
|
||||||
@@ -48,6 +59,28 @@ interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 <reified I : O, O: State> BehaviourContextWithFSM<O>.onStateOrSubstate(handler: BehaviourWithFSMStateHandler<I, O>) = 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 <reified I : O, O: State> BehaviourContextWithFSM<O>.strictlyOn(handler: BehaviourWithFSMStateHandler<I, O>) = addStrict(I::class, handler)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default realization of [BehaviourContextWithFSM]. It uses [behaviourContext] as a base for this object as
|
* 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
|
* [BehaviourContext], but managing substates contexts updates for avoiding of updates lost between states
|
||||||
@@ -57,20 +90,31 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
|||||||
private val statesManager: StatesManager<T>,
|
private val statesManager: StatesManager<T>,
|
||||||
private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
|
private val handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
|
||||||
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
|
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
|
||||||
private val updatesFlows = mutableMapOf<Any, Flow<Update>>()
|
private val updatesFlows = mutableMapOf<Any, DefaultBehaviourContextWithFSM<T>>()
|
||||||
private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) {
|
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
|
||||||
allUpdatesFlow.accumulatorFlow(scope)
|
private var actualHandlersList = additionalHandlers + handlers
|
||||||
|
|
||||||
|
private fun getSubContext(context: Any) = updatesFlows.getOrPut(context) {
|
||||||
|
createSubContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(
|
override suspend fun StatesMachine<in T>.handleState(state: T): T? {
|
||||||
state,
|
return getSubContext(
|
||||||
allUpdatesFlow,
|
state.context
|
||||||
handlers
|
).launchStateHandling(
|
||||||
)
|
state,
|
||||||
|
actualHandlersList
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <I : T> add(kClass: KClass<I>, strict: Boolean, handler: BehaviourWithFSMStateHandler<I, T>) {
|
||||||
|
additionalHandlers.add(BehaviourWithFSMStateHandlerHolder(kClass, strict, handler))
|
||||||
|
actualHandlersList = additionalHandlers + handlers
|
||||||
|
}
|
||||||
|
|
||||||
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
override fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
|
||||||
val statePerformer: suspend (T) -> Unit = { state: T ->
|
val statePerformer: suspend (T) -> Unit = { state: T ->
|
||||||
val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), handlers)
|
val newState = getSubContext(state.context).launchStateHandling(state, actualHandlersList)
|
||||||
if (newState != null) {
|
if (newState != null) {
|
||||||
statesManager.update(state, newState)
|
statesManager.update(state, newState)
|
||||||
} else {
|
} else {
|
||||||
@@ -94,6 +138,26 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
|||||||
launch { statePerformer(it) }
|
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 <reified I : T> onStateOrSubstate(handler: BehaviourWithFSMStateHandler<I, T>) = 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 <reified I : T> strictlyOn(handler: BehaviourWithFSMStateHandler<I, T>) = addStrict(I::class, handler)
|
||||||
|
|
||||||
override suspend fun startChain(state: T) {
|
override suspend fun startChain(state: T) {
|
||||||
statesManager.startChain(state)
|
statesManager.startChain(state)
|
||||||
@@ -105,9 +169,10 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
|||||||
broadcastChannelsSize: Int,
|
broadcastChannelsSize: Int,
|
||||||
onBufferOverflow: BufferOverflow,
|
onBufferOverflow: BufferOverflow,
|
||||||
upstreamUpdatesFlow: Flow<Update>?,
|
upstreamUpdatesFlow: Flow<Update>?,
|
||||||
|
triggersHolder: TriggersHolder,
|
||||||
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
||||||
): BehaviourContextWithFSM<T> = BehaviourContextWithFSM(
|
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
|
||||||
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter),
|
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder, updatesFilter),
|
||||||
handlers,
|
handlers,
|
||||||
statesManager
|
statesManager
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,70 +14,8 @@ import kotlinx.coroutines.*
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class BehaviourContextWithFSMBuilder<T : State> internal constructor(
|
@Deprecated("Will be removed soon")
|
||||||
private val resultBehaviourContext: BehaviourContextWithFSM<T>,
|
typealias BehaviourContextWithFSMBuilder<T> = BehaviourContextWithFSM<T>
|
||||||
private val handlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>>
|
|
||||||
) : BehaviourContextWithFSM<T> by resultBehaviourContext {
|
|
||||||
internal constructor(
|
|
||||||
baseBehaviourContext: BehaviourContext,
|
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
|
||||||
handlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = 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 <I : T> add(kClass: KClass<I>, handler: BehaviourWithFSMStateHandler<I, T>) {
|
|
||||||
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 <I : T> addStrict(kClass: KClass<I>, handler: BehaviourWithFSMStateHandler<I, T>) {
|
|
||||||
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 <reified I : T> onStateOrSubstate(handler: BehaviourWithFSMStateHandler<I, T>) {
|
|
||||||
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 <reified I : T> strictlyOn(handler: BehaviourWithFSMStateHandler<I, T>) {
|
|
||||||
addStrict(I::class, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns completed [resultBehaviourContext], [handlers] and [statesManager]
|
|
||||||
*/
|
|
||||||
internal fun build() = resultBehaviourContext
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates [BehaviourContextWithFSM] via creating of [DefaultBehaviourContext] with [this] as [TelegramBot],
|
* Creates [BehaviourContextWithFSM] via creating of [DefaultBehaviourContext] with [this] as [TelegramBot],
|
||||||
@@ -94,17 +32,17 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
|||||||
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
|
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||||
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
|
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||||
): BehaviourContextWithFSM<T> = BehaviourContextWithFSMBuilder(
|
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
|
||||||
DefaultBehaviourContext(
|
DefaultBehaviourContext(
|
||||||
this,
|
this,
|
||||||
defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope,
|
defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope,
|
||||||
upstreamUpdatesFlow = upstreamUpdatesFlow
|
upstreamUpdatesFlow = upstreamUpdatesFlow
|
||||||
),
|
),
|
||||||
statesManager,
|
presetHandlers,
|
||||||
presetHandlers
|
statesManager
|
||||||
).apply { block() }.build()
|
).apply { block() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates
|
* Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates
|
||||||
@@ -116,9 +54,9 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
|||||||
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
|
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||||
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
|
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||||
): Pair<BehaviourContextWithFSM<T>, Job> = buildBehaviourWithFSM(
|
): Pair<DefaultBehaviourContextWithFSM<T>, Job> = buildBehaviourWithFSM(
|
||||||
upstreamUpdatesFlow,
|
upstreamUpdatesFlow,
|
||||||
scope,
|
scope,
|
||||||
defaultExceptionsHandler,
|
defaultExceptionsHandler,
|
||||||
@@ -147,26 +85,25 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
|||||||
* @see BehaviourContext
|
* @see BehaviourContext
|
||||||
* @see BehaviourContextWithFSM
|
* @see BehaviourContextWithFSM
|
||||||
* @see longPolling
|
* @see longPolling
|
||||||
* @see BehaviourContextWithFSMBuilder.strictlyOn
|
* @see BehaviourContextWithFSM.strictlyOn
|
||||||
* @see BehaviourContextWithFSMBuilder.onStateOrSubstate
|
* @see BehaviourContextWithFSM.onStateOrSubstate
|
||||||
*/
|
*/
|
||||||
@PreviewFeature
|
|
||||||
suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
||||||
flowUpdatesFilter: FlowsUpdatesFilter,
|
flowUpdatesFilter: FlowsUpdatesFilter,
|
||||||
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
|
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||||
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
|
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||||
): BehaviourContextWithFSM<T> = BehaviourContextWithFSMBuilder(
|
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
|
||||||
DefaultBehaviourContext(
|
DefaultBehaviourContext(
|
||||||
this,
|
this,
|
||||||
defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope,
|
defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope,
|
||||||
upstreamUpdatesFlow = flowUpdatesFilter.allUpdatesFlow
|
upstreamUpdatesFlow = flowUpdatesFilter.allUpdatesFlow
|
||||||
),
|
),
|
||||||
statesManager,
|
presetHandlers,
|
||||||
presetHandlers
|
statesManager
|
||||||
).apply { block() }.build()
|
).apply { block() }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates
|
* Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates
|
||||||
@@ -176,16 +113,15 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
|||||||
* @see buildBehaviourWithFSMAndStartLongPolling
|
* @see buildBehaviourWithFSMAndStartLongPolling
|
||||||
* @see BehaviourContext
|
* @see BehaviourContext
|
||||||
* @see longPolling
|
* @see longPolling
|
||||||
* @see BehaviourContextWithFSMBuilder.strictlyOn
|
* @see BehaviourContextWithFSM.strictlyOn
|
||||||
* @see BehaviourContextWithFSMBuilder.onStateOrSubstate
|
* @see BehaviourContextWithFSM.onStateOrSubstate
|
||||||
*/
|
*/
|
||||||
@PreviewFeature
|
|
||||||
suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
|
||||||
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
|
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||||
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
|
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||||
) = FlowsUpdatesFilter().let {
|
) = FlowsUpdatesFilter().let {
|
||||||
buildBehaviourWithFSM(
|
buildBehaviourWithFSM(
|
||||||
it,
|
it,
|
||||||
|
|||||||
@@ -2,6 +2,12 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.fsm.common.*
|
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?
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder
|
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.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
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,31 +17,26 @@ class BehaviourWithFSMStateHandlerHolder<I : O, O : State>(
|
|||||||
private val inputKlass: KClass<I>,
|
private val inputKlass: KClass<I>,
|
||||||
private val strict: Boolean = false,
|
private val strict: Boolean = false,
|
||||||
private val delegateTo: BehaviourWithFSMStateHandler<I, O>
|
private val delegateTo: BehaviourWithFSMStateHandler<I, O>
|
||||||
) {
|
) : CheckableHandlerHolder<O, O>, BehaviourWithFSMStateHandler<O, O> {
|
||||||
/**
|
/**
|
||||||
* Check ability of [delegateTo] to handle this [state]
|
* Check ability of [delegateTo] to handle this [state]
|
||||||
*
|
*
|
||||||
* @return When [state]::class exactly equals to [inputKlass] will always return true. Otherwise when [strict]
|
* @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
|
* 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 :)
|
* 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<in O>.handleState(
|
override suspend fun BehaviourContextWithFSM<in O>.handleState(state: O): O? = with(delegateTo) {
|
||||||
contextUpdatesFlow: Flow<Update>,
|
@Suppress("UNCHECKED_CAST")
|
||||||
state: O
|
handleState(state as I)
|
||||||
): O? {
|
}
|
||||||
val subscope = scope.LinkedSupervisorScope()
|
|
||||||
return with(copy(scope = subscope, upstreamUpdatesFlow = contextUpdatesFlow)) {
|
override suspend fun StatesMachine<in O>.handleState(state: O): O? = with(delegateTo) {
|
||||||
with(delegateTo) {
|
@Suppress("UNCHECKED_CAST")
|
||||||
handleState(state as I)
|
handleState(state as I)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,11 +37,13 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSM(
|
|||||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
|
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||||
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
|
testServer: Boolean = false,
|
||||||
|
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||||
): TelegramBot = telegramBot(
|
): TelegramBot = telegramBot(
|
||||||
token,
|
token,
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
testServer,
|
||||||
builder
|
builder
|
||||||
).apply {
|
).apply {
|
||||||
buildBehaviourWithFSMAndStartLongPolling(
|
buildBehaviourWithFSMAndStartLongPolling(
|
||||||
@@ -72,12 +74,14 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSMAndStartLongPolling(
|
|||||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
|
||||||
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
|
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
|
||||||
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
|
testServer: Boolean = false,
|
||||||
|
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
|
||||||
): Pair<TelegramBot, Job> {
|
): Pair<TelegramBot, Job> {
|
||||||
return telegramBot(
|
return telegramBot(
|
||||||
token,
|
token,
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
testServer,
|
||||||
builder
|
builder
|
||||||
).let {
|
).let {
|
||||||
it to it.buildBehaviourWithFSMAndStartLongPolling (
|
it to it.buildBehaviourWithFSMAndStartLongPolling (
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.longPolling
|
|||||||
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling
|
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.startGettingOfUpdatesByLongPolling
|
||||||
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
|
import dev.inmo.tgbotapi.updateshandlers.FlowsUpdatesFilter
|
||||||
import dev.inmo.tgbotapi.utils.PreviewFeature
|
import dev.inmo.tgbotapi.utils.PreviewFeature
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.plus
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used in [buildBehaviour] extensions to provide default [CoroutineScope] and allow to avoid all
|
* This function is used in [buildBehaviour] extensions to provide default [CoroutineScope] and allow to avoid all
|
||||||
@@ -25,24 +24,23 @@ expect var defaultCoroutineScopeProvider: () -> CoroutineScope
|
|||||||
* @see [BehaviourContext]
|
* @see [BehaviourContext]
|
||||||
* @see startGettingOfUpdatesByLongPolling
|
* @see startGettingOfUpdatesByLongPolling
|
||||||
*/
|
*/
|
||||||
@PreviewFeature
|
|
||||||
suspend fun TelegramBot.buildBehaviour(
|
suspend fun TelegramBot.buildBehaviour(
|
||||||
flowUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
|
flowUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
|
||||||
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
block: BehaviourContextReceiver<Unit>
|
block: BehaviourContextReceiver<Unit>
|
||||||
) {
|
): BehaviourContext = BehaviourContext(
|
||||||
BehaviourContext(
|
this,
|
||||||
this,
|
scope.let {
|
||||||
scope.let {
|
if (defaultExceptionsHandler == null) {
|
||||||
if (defaultExceptionsHandler == null) {
|
it
|
||||||
it
|
} else {
|
||||||
} else {
|
it + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
||||||
it + ContextSafelyExceptionHandler(defaultExceptionsHandler)
|
}
|
||||||
}
|
},
|
||||||
},
|
flowUpdatesFilter
|
||||||
flowUpdatesFilter
|
).apply {
|
||||||
).block()
|
block()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,20 +51,18 @@ suspend fun TelegramBot.buildBehaviour(
|
|||||||
* @see BehaviourContext
|
* @see BehaviourContext
|
||||||
* @see startGettingOfUpdatesByLongPolling
|
* @see startGettingOfUpdatesByLongPolling
|
||||||
*/
|
*/
|
||||||
@PreviewFeature
|
|
||||||
suspend fun TelegramBot.buildBehaviourWithLongPolling(
|
suspend fun TelegramBot.buildBehaviourWithLongPolling(
|
||||||
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
scope: CoroutineScope = defaultCoroutineScopeProvider(),
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
block: BehaviourContextReceiver<Unit>
|
block: BehaviourContextReceiver<Unit>
|
||||||
) = FlowsUpdatesFilter().let {
|
): Job {
|
||||||
buildBehaviour(
|
val behaviourContext = buildBehaviour(
|
||||||
it,
|
scope = scope,
|
||||||
scope,
|
defaultExceptionsHandler = defaultExceptionsHandler,
|
||||||
defaultExceptionsHandler,
|
block = block
|
||||||
block
|
|
||||||
)
|
)
|
||||||
longPolling(
|
return longPolling(
|
||||||
it,
|
behaviourContext,
|
||||||
scope = scope
|
scope = behaviourContext
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.*
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
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.types.update.abstracts.Update
|
||||||
import dev.inmo.tgbotapi.updateshandlers.*
|
import dev.inmo.tgbotapi.updateshandlers.*
|
||||||
import dev.inmo.tgbotapi.utils.RiskFeature
|
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
@@ -47,12 +47,15 @@ interface BehaviourContext : FlowsUpdatesFilter, TelegramBot, CoroutineScope {
|
|||||||
val flowsUpdatesFilter: FlowsUpdatesFilter
|
val flowsUpdatesFilter: FlowsUpdatesFilter
|
||||||
get() = this
|
get() = this
|
||||||
|
|
||||||
|
val triggersHolder: TriggersHolder
|
||||||
|
|
||||||
fun copy(
|
fun copy(
|
||||||
bot: TelegramBot = this.bot,
|
bot: TelegramBot = this.bot,
|
||||||
scope: CoroutineScope = this.scope,
|
scope: CoroutineScope = this.scope,
|
||||||
broadcastChannelsSize: Int = 100,
|
broadcastChannelsSize: Int = 100,
|
||||||
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
|
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
|
||||||
upstreamUpdatesFlow: Flow<Update>? = null,
|
upstreamUpdatesFlow: Flow<Update>? = null,
|
||||||
|
triggersHolder: TriggersHolder = TriggersHolder(),
|
||||||
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
|
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
|
||||||
): BehaviourContext
|
): BehaviourContext
|
||||||
}
|
}
|
||||||
@@ -63,6 +66,7 @@ class DefaultBehaviourContext(
|
|||||||
broadcastChannelsSize: Int = 100,
|
broadcastChannelsSize: Int = 100,
|
||||||
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
|
onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,
|
||||||
private val upstreamUpdatesFlow: Flow<Update>? = null,
|
private val upstreamUpdatesFlow: Flow<Update>? = null,
|
||||||
|
override val triggersHolder: TriggersHolder = TriggersHolder(),
|
||||||
private val updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
|
private val updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>? = null
|
||||||
) : AbstractFlowsUpdatesFilter(), TelegramBot by bot, CoroutineScope by scope, BehaviourContext {
|
) : AbstractFlowsUpdatesFilter(), TelegramBot by bot, CoroutineScope by scope, BehaviourContext {
|
||||||
|
|
||||||
@@ -80,7 +84,7 @@ class DefaultBehaviourContext(
|
|||||||
} else {
|
} else {
|
||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}.accumulatorFlow(scope)
|
||||||
override val asUpdateReceiver: UpdateReceiver<Update> = additionalUpdatesSharedFlow::emit
|
override val asUpdateReceiver: UpdateReceiver<Update> = additionalUpdatesSharedFlow::emit
|
||||||
|
|
||||||
override fun copy(
|
override fun copy(
|
||||||
@@ -89,34 +93,32 @@ class DefaultBehaviourContext(
|
|||||||
broadcastChannelsSize: Int,
|
broadcastChannelsSize: Int,
|
||||||
onBufferOverflow: BufferOverflow,
|
onBufferOverflow: BufferOverflow,
|
||||||
upstreamUpdatesFlow: Flow<Update>?,
|
upstreamUpdatesFlow: Flow<Update>?,
|
||||||
|
triggersHolder: TriggersHolder,
|
||||||
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
||||||
): BehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter)
|
): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, triggersHolder, updatesFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BehaviourContext(
|
fun BehaviourContext(
|
||||||
bot: TelegramBot,
|
bot: TelegramBot,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter()
|
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
|
||||||
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow)
|
triggersHolder: TriggersHolder = TriggersHolder(),
|
||||||
|
) = DefaultBehaviourContext(bot, scope, upstreamUpdatesFlow = flowsUpdatesFilter.allUpdatesFlow, triggersHolder = triggersHolder)
|
||||||
|
|
||||||
inline fun <T> BehaviourContext(
|
inline fun <T> BehaviourContext(
|
||||||
bot: TelegramBot,
|
bot: TelegramBot,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
|
flowsUpdatesFilter: FlowsUpdatesFilter = FlowsUpdatesFilter(),
|
||||||
|
triggersHolder: TriggersHolder = TriggersHolder(),
|
||||||
crossinline block: BehaviourContext.() -> T
|
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(
|
||||||
* Creates new one [BehaviourContext], adding subsequent [FlowsUpdatesFilter] in case [updatesFilter] is provided and
|
|
||||||
* [CoroutineScope] as new [BehaviourContext.scope]
|
|
||||||
*/
|
|
||||||
suspend fun <T, BC : BehaviourContext> BC.doInSubContextWithUpdatesFilter(
|
|
||||||
updatesFilter: CustomBehaviourContextAndTypeReceiver<BC, Boolean, Update>?,
|
|
||||||
stopOnCompletion: Boolean = true,
|
|
||||||
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
|
|
||||||
scope: CoroutineScope = LinkedSupervisorScope(),
|
scope: CoroutineScope = LinkedSupervisorScope(),
|
||||||
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
|
triggersHolder: TriggersHolder = this.triggersHolder,
|
||||||
): T = copy(
|
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
|
||||||
|
updatesFilter: CustomBehaviourContextAndTypeReceiver<BC, Boolean, Update>? = null,
|
||||||
|
) = copy(
|
||||||
scope = scope,
|
scope = scope,
|
||||||
updatesFilter = updatesFilter ?.let { _ ->
|
updatesFilter = updatesFilter ?.let { _ ->
|
||||||
{
|
{
|
||||||
@@ -125,19 +127,90 @@ suspend fun <T, BC : BehaviourContext> BC.doInSubContextWithUpdatesFilter(
|
|||||||
} ?: true
|
} ?: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
upstreamUpdatesFlow = updatesUpstreamFlow
|
upstreamUpdatesFlow = updatesUpstreamFlow,
|
||||||
).run {
|
triggersHolder = triggersHolder
|
||||||
withContext(coroutineContext) {
|
) 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 <T, BC : BehaviourContext> BC.doInContext(
|
||||||
|
stopOnCompletion: Boolean = false,
|
||||||
|
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
|
||||||
|
): T {
|
||||||
|
return withContext(coroutineContext) {
|
||||||
behaviourContextReceiver().also { if (stopOnCompletion) stop() }
|
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 {
|
||||||
|
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(
|
suspend fun <T> BehaviourContext.doInSubContext(
|
||||||
stopOnCompletion: Boolean = true,
|
stopOnCompletion: Boolean = true,
|
||||||
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
|
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
|
||||||
scope: CoroutineScope = LinkedSupervisorScope(),
|
scope: CoroutineScope = LinkedSupervisorScope(),
|
||||||
|
triggersHolder: TriggersHolder = this.triggersHolder,
|
||||||
behaviourContextReceiver: BehaviourContextReceiver<T>
|
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
|
* This method will cancel ALL subsequent contexts, expectations and waiters
|
||||||
|
|||||||
@@ -30,10 +30,12 @@ suspend fun telegramBotWithBehaviour(
|
|||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
|
testServer: Boolean = false,
|
||||||
block: BehaviourContextReceiver<Unit>
|
block: BehaviourContextReceiver<Unit>
|
||||||
): TelegramBot = telegramBot(
|
): TelegramBot = telegramBot(
|
||||||
token,
|
token,
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
testServer,
|
||||||
builder
|
builder
|
||||||
).apply {
|
).apply {
|
||||||
buildBehaviour(
|
buildBehaviour(
|
||||||
@@ -63,11 +65,13 @@ suspend fun telegramBotWithBehaviourAndLongPolling(
|
|||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
|
||||||
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
|
testServer: Boolean = false,
|
||||||
block: BehaviourContextReceiver<Unit>
|
block: BehaviourContextReceiver<Unit>
|
||||||
): Pair<TelegramBot, Job> {
|
): Pair<TelegramBot, Job> {
|
||||||
return telegramBot(
|
return telegramBot(
|
||||||
token,
|
token,
|
||||||
apiUrl,
|
apiUrl,
|
||||||
|
testServer,
|
||||||
builder
|
builder
|
||||||
).let {
|
).let {
|
||||||
it to it.buildBehaviourWithLongPolling(
|
it to it.buildBehaviourWithLongPolling(
|
||||||
|
|||||||
@@ -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.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
|
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
|||||||
@@ -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.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.*
|
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.*
|
||||||
import dev.inmo.tgbotapi.types.polls.*
|
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
|
||||||
typealias ChosenInlineResultMapper<T> = suspend T.() -> T?
|
typealias ChosenInlineResultMapper<T> = suspend T.() -> T?
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ internal suspend fun <O : MessageContent> BehaviourContext.waitCommonMessage(
|
|||||||
val messages = when (it) {
|
val messages = when (it) {
|
||||||
is SentMediaGroupUpdate -> {
|
is SentMediaGroupUpdate -> {
|
||||||
if (includeMediaGroups) {
|
if (includeMediaGroups) {
|
||||||
it.data.map { it as CommonMessage<MessageContent> }
|
it.data
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,8 @@ internal suspend fun <O : MessageContent> BehaviourContext.waitCommonMessage(
|
|||||||
else -> return@expectFlow emptyList()
|
else -> return@expectFlow emptyList()
|
||||||
}
|
}
|
||||||
messages.mapNotNull { message ->
|
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)) {
|
if (filter == null || filter(asCommonMessage)) {
|
||||||
asCommonMessage.mapper()
|
asCommonMessage.mapper()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.toList
|
|||||||
|
|
||||||
typealias MediaGroupFilter<T> = suspend List<MediaGroupMessage<T>>.() -> Boolean
|
typealias MediaGroupFilter<T> = suspend List<MediaGroupMessage<T>>.() -> Boolean
|
||||||
|
|
||||||
@PreviewFeature
|
|
||||||
internal suspend inline fun <reified T : MediaGroupContent> BehaviourContext.buildMediaGroupWaiter(
|
internal suspend inline fun <reified T : MediaGroupContent> BehaviourContext.buildMediaGroupWaiter(
|
||||||
count: Int = 1,
|
count: Int = 1,
|
||||||
initRequest: Request<*>? = null,
|
initRequest: Request<*>? = null,
|
||||||
|
|||||||
@@ -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.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.types.polls.*
|
import dev.inmo.tgbotapi.types.polls.PollAnswer
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
|
||||||
typealias PollAnswerMapper = suspend PollAnswer.() -> PollAnswer?
|
typealias PollAnswerMapper = suspend PollAnswer.() -> PollAnswer?
|
||||||
@@ -45,14 +45,10 @@ private suspend inline fun BehaviourContext.waitPollAnswers(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (this is PollAnswer) {
|
if (mapper == null) {
|
||||||
if (mapper == null) {
|
this
|
||||||
this
|
|
||||||
} else {
|
|
||||||
mapper(this)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
null
|
mapper(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.types.polls.*
|
import dev.inmo.tgbotapi.types.polls.*
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
|||||||
@@ -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.BehaviourContext
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
||||||
import dev.inmo.tgbotapi.extensions.utils.asPreCheckoutQueryUpdate
|
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.requests.abstracts.Request
|
||||||
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
|
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
|
||||||
import dev.inmo.tgbotapi.types.payments.ShippingQuery
|
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
|
|
||||||
typealias PreCheckoutQueryMapper = suspend PreCheckoutQuery.() -> PreCheckoutQuery?
|
typealias PreCheckoutQueryMapper = suspend PreCheckoutQuery.() -> PreCheckoutQuery?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import dev.inmo.tgbotapi.types.update.abstracts.Update
|
|||||||
/**
|
/**
|
||||||
* Allow only messages which are not [MediaGroupMessage]
|
* 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<*>
|
update !is MediaGroupMessage<*>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
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.*
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilterByUser
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.extensions.utils.asCallbackQueryUpdate
|
||||||
import dev.inmo.tgbotapi.types.queries.callback.*
|
import dev.inmo.tgbotapi.types.queries.callback.*
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
|
||||||
internal suspend inline fun <BC : BehaviourContext, reified T : CallbackQuery> BC.onCallbackQuery(
|
internal suspend inline fun <BC : BehaviourContext, reified T : CallbackQuery> BC.onCallbackQuery(
|
||||||
initialFilter: SimpleFilter<T>? = null,
|
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)
|
(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 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,
|
* @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,
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
|
||||||
) = onCallbackQuery(
|
) = onDataCallbackQueryCounted(
|
||||||
initialFilter,
|
initialFilter,
|
||||||
subcontextUpdatesFilter,
|
subcontextUpdatesFilter,
|
||||||
markerFactory,
|
markerFactory,
|
||||||
@@ -166,7 +211,7 @@ suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
|||||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
|
||||||
) = onCallbackQuery(
|
) = onDataCallbackQueryCounted(
|
||||||
initialFilter,
|
initialFilter,
|
||||||
subcontextUpdatesFilter,
|
subcontextUpdatesFilter,
|
||||||
markerFactory,
|
markerFactory,
|
||||||
@@ -294,7 +339,7 @@ suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
|||||||
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
|
||||||
) = onCallbackQuery(
|
) = onDataCallbackQueryCounted(
|
||||||
initialFilter,
|
initialFilter,
|
||||||
subcontextUpdatesFilter,
|
subcontextUpdatesFilter,
|
||||||
markerFactory,
|
markerFactory,
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
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.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.extensions.utils.asChatJoinRequestUpdate
|
||||||
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
|
import dev.inmo.tgbotapi.types.chat.ChatJoinRequest
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|||||||
@@ -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.*
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.asChosenInlineResultUpdate
|
||||||
import dev.inmo.tgbotapi.extensions.utils.asPollUpdate
|
|
||||||
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.*
|
import dev.inmo.tgbotapi.types.InlineQueries.ChosenInlineResult.*
|
||||||
import dev.inmo.tgbotapi.types.polls.*
|
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|
||||||
internal suspend inline fun <BC : BehaviourContext, reified T : ChosenInlineResult> BC.onChosenInlineResultBase(
|
internal suspend inline fun <BC : BehaviourContext, reified T : ChosenInlineResult> BC.onChosenInlineResultBase(
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
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.*
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
|
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.filters.MessageFilterByChat
|
||||||
@@ -15,8 +17,7 @@ import dev.inmo.tgbotapi.types.message.content.TextContent
|
|||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
|
||||||
|
internal suspend fun <BC : BehaviourContext> BC.commandUncounted(
|
||||||
suspend fun <BC : BehaviourContext> BC.command(
|
|
||||||
commandRegex: Regex,
|
commandRegex: Regex,
|
||||||
requireOnlyCommandInMessage: Boolean = true,
|
requireOnlyCommandInMessage: Boolean = true,
|
||||||
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
|
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
|
||||||
@@ -43,6 +44,35 @@ suspend fun <BC : BehaviourContext> BC.command(
|
|||||||
scenarioReceiver
|
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(
|
suspend fun <BC : BehaviourContext> BC.command(
|
||||||
command: String,
|
command: String,
|
||||||
requireOnlyCommandInMessage: Boolean = true,
|
requireOnlyCommandInMessage: Boolean = true,
|
||||||
@@ -52,22 +82,22 @@ suspend fun <BC : BehaviourContext> BC.command(
|
|||||||
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, CommonMessage<TextContent>>
|
||||||
) = command(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
|
) = command(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
|
||||||
|
|
||||||
suspend inline fun <BC : BehaviourContext> BC.onCommand(
|
suspend fun <BC : BehaviourContext> BC.onCommand(
|
||||||
commandRegex: Regex,
|
commandRegex: Regex,
|
||||||
requireOnlyCommandInMessage: Boolean = true,
|
requireOnlyCommandInMessage: Boolean = true,
|
||||||
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
|
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,
|
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)
|
): Job = command(commandRegex, requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
|
||||||
|
|
||||||
suspend inline fun <BC : BehaviourContext> BC.onCommand(
|
suspend fun <BC : BehaviourContext> BC.onCommand(
|
||||||
command: String,
|
command: String,
|
||||||
requireOnlyCommandInMessage: Boolean = true,
|
requireOnlyCommandInMessage: Boolean = true,
|
||||||
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
|
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,
|
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)
|
): Job = onCommand(command.toRegex(), requireOnlyCommandInMessage, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
|
||||||
|
|
||||||
suspend fun <BC : BehaviourContext> BC.commandWithArgs(
|
suspend fun <BC : BehaviourContext> BC.commandWithArgs(
|
||||||
@@ -104,18 +134,18 @@ suspend fun <BC : BehaviourContext> BC.commandWithArgs(
|
|||||||
scenarioReceiver = scenarioReceiver
|
scenarioReceiver = scenarioReceiver
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend inline fun <BC : BehaviourContext> BC.onCommandWithArgs(
|
suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
|
||||||
commandRegex: Regex,
|
commandRegex: Regex,
|
||||||
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
|
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,
|
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)
|
): Job = commandWithArgs(commandRegex, initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
|
||||||
|
|
||||||
suspend inline fun <BC : BehaviourContext> BC.onCommandWithArgs(
|
suspend fun <BC : BehaviourContext> BC.onCommandWithArgs(
|
||||||
command: String,
|
command: String,
|
||||||
initialFilter: CommonMessageFilter<TextContent>? = CommonMessageFilterExcludeMediaGroups,
|
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,
|
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)
|
): Job = onCommandWithArgs(command.toRegex(), initialFilter, subcontextUpdatesFilter, markerFactory, scenarioReceiver)
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -36,7 +36,6 @@ import dev.inmo.tgbotapi.types.update.abstracts.BaseEditMessageUpdate
|
|||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
import dev.inmo.tgbotapi.utils.PreviewFeature
|
import dev.inmo.tgbotapi.utils.PreviewFeature
|
||||||
|
|
||||||
@PreviewFeature
|
|
||||||
internal suspend inline fun <BC : BehaviourContext, reified T : MessageContent> BC.onEditedContent(
|
internal suspend inline fun <BC : BehaviourContext, reified T : MessageContent> BC.onEditedContent(
|
||||||
initialFilter: CommonMessageFilter<T>? = null,
|
initialFilter: CommonMessageFilter<T>? = null,
|
||||||
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<T>, Update>? = MessageFilterByChat,
|
noinline subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, CommonMessage<T>, Update>? = MessageFilterByChat,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ internal suspend inline fun <BC : BehaviourContext, reified T : ChatEvent> BC.on
|
|||||||
markerFactory: MarkerFactory<in ChatEventMessage<T>, Any> = ByChatMessageMarkerFactory,
|
markerFactory: MarkerFactory<in ChatEventMessage<T>, Any> = ByChatMessageMarkerFactory,
|
||||||
noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, ChatEventMessage<T>>
|
noinline scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, ChatEventMessage<T>>
|
||||||
) = on(markerFactory, initialFilter, subcontextUpdatesFilter, scenarioReceiver) {
|
) = on(markerFactory, initialFilter, subcontextUpdatesFilter, scenarioReceiver) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
(it.asBaseSentMessageUpdate() ?.data ?.asChatEventMessage() ?.takeIf { it.chatEvent is T } as? ChatEventMessage<T>) ?.let(::listOfNotNull)
|
(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>>
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, PrivateEventMessage<WebAppData>>
|
||||||
) = onEvent(
|
) = onEvent(
|
||||||
initialFilter ?.let { { it is PrivateEventMessage<WebAppData> && initialFilter(it) } },
|
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
|
markerFactory
|
||||||
) {
|
) {
|
||||||
if (it is PrivateEventMessage<WebAppData>) {
|
if (it is PrivateEventMessage<WebAppData>) {
|
||||||
|
|||||||
@@ -21,12 +21,9 @@ internal suspend inline fun <BC : BehaviourContext, reified T> BC.on(
|
|||||||
scope,
|
scope,
|
||||||
markerFactory::invoke
|
markerFactory::invoke
|
||||||
) { triggerData ->
|
) { triggerData ->
|
||||||
val scope = LinkedSupervisorScope()
|
createSubContextAndDoWithUpdatesFilter(
|
||||||
doInSubContextWithUpdatesFilter(
|
|
||||||
updatesFilter = subcontextUpdatesFilter ?.toOneType(triggerData),
|
updatesFilter = subcontextUpdatesFilter ?.toOneType(triggerData),
|
||||||
stopOnCompletion = false,
|
stopOnCompletion = false
|
||||||
updatesUpstreamFlow = allUpdatesFlow.accumulatorFlow(scope),
|
|
||||||
scope = scope
|
|
||||||
) {
|
) {
|
||||||
scenarioReceiver(triggerData)
|
scenarioReceiver(triggerData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.*
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.asPollAnswerUpdate
|
||||||
import dev.inmo.tgbotapi.extensions.utils.asPollUpdate
|
import dev.inmo.tgbotapi.types.polls.PollAnswer
|
||||||
import dev.inmo.tgbotapi.types.polls.*
|
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|
||||||
internal suspend inline fun <BC : BehaviourContext> BC.onPollAnswered(
|
internal suspend inline fun <BC : BehaviourContext> BC.onPollAnswered(
|
||||||
|
|||||||
@@ -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.*
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.extensions.utils.asPollUpdate
|
||||||
import dev.inmo.tgbotapi.types.polls.*
|
import dev.inmo.tgbotapi.types.polls.*
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
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.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.asPreCheckoutQueryUpdate
|
||||||
import dev.inmo.tgbotapi.extensions.utils.asShippingQueryUpdate
|
|
||||||
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
|
import dev.inmo.tgbotapi.types.payments.PreCheckoutQuery
|
||||||
import dev.inmo.tgbotapi.types.payments.ShippingQuery
|
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
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.filters.ShippingQueryFilterByUser
|
||||||
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
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.extensions.utils.asShippingQueryUpdate
|
||||||
import dev.inmo.tgbotapi.types.payments.ShippingQuery
|
import dev.inmo.tgbotapi.types.payments.ShippingQuery
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.extensions.behaviour_builder.utils
|
|||||||
fun interface SimpleFilter<in T> {
|
fun interface SimpleFilter<in T> {
|
||||||
suspend operator fun invoke(o: T): Boolean
|
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
|
* @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter
|
||||||
|
|||||||
@@ -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) }
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>()
|
||||||
|
}
|
||||||
@@ -47,10 +47,10 @@ kotlin {
|
|||||||
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version"
|
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_runtime_version"
|
||||||
api "org.jetbrains.kotlinx:kotlinx-serialization-properties:$kotlin_serialisation_runtime_version"
|
api "org.jetbrains.kotlinx:kotlinx-serialization-properties:$kotlin_serialisation_runtime_version"
|
||||||
|
|
||||||
api "com.soywiz.korlibs.klock:klock:$klock_version"
|
api "com.soywiz.korlibs.klock:klock:$korlibs_version"
|
||||||
|
api "com.soywiz.korlibs.krypto:krypto:$korlibs_version"
|
||||||
api "com.benasher44:uuid:$uuid_version"
|
api "com.benasher44:uuid:$uuid_version"
|
||||||
|
|
||||||
api "dev.inmo:micro_utils.crypto:$micro_utils_version"
|
|
||||||
api "dev.inmo:micro_utils.coroutines:$micro_utils_version"
|
api "dev.inmo:micro_utils.coroutines:$micro_utils_version"
|
||||||
api "dev.inmo:micro_utils.serialization.base64:$micro_utils_version"
|
api "dev.inmo:micro_utils.serialization.base64:$micro_utils_version"
|
||||||
api "dev.inmo:micro_utils.serialization.encapsulator:$micro_utils_version"
|
api "dev.inmo:micro_utils.serialization.encapsulator:$micro_utils_version"
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ inline fun telegramBot(
|
|||||||
inline fun telegramBot(
|
inline fun telegramBot(
|
||||||
token: String,
|
token: String,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder)
|
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)
|
||||||
|
|||||||
@@ -125,5 +125,6 @@ inline fun telegramBot(
|
|||||||
inline fun telegramBot(
|
inline fun telegramBot(
|
||||||
token: String,
|
token: String,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl,
|
apiUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
testServer: Boolean = false,
|
||||||
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
|
||||||
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder)
|
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.InlineQ
|
|||||||
import dev.inmo.tgbotapi.types.webapps.query.SentWebAppMessage
|
import dev.inmo.tgbotapi.types.webapps.query.SentWebAppMessage
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param webAppQueryId [dev.inmo.tgbotapi.webapps.WebAppInitData.queryId]
|
||||||
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AnswerWebAppQuery(
|
data class AnswerWebAppQuery(
|
||||||
@SerialName(webAppQueryIdField)
|
@SerialName(webAppQueryIdField)
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ val telegramInlineModeGifPermittedMimeTypes by lazy {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const val tgWebAppStartParamField = "tgWebAppStartParam"
|
||||||
|
|
||||||
const val chatIdField = "chat_id"
|
const val chatIdField = "chat_id"
|
||||||
const val senderChatIdField = "sender_chat_id"
|
const val senderChatIdField = "sender_chat_id"
|
||||||
const val messageIdField = "message_id"
|
const val messageIdField = "message_id"
|
||||||
@@ -389,6 +391,8 @@ const val requireShippingAddressField = "need_shipping_address"
|
|||||||
const val shouldSendPhoneNumberToProviderField = "send_phone_number_to_provider"
|
const val shouldSendPhoneNumberToProviderField = "send_phone_number_to_provider"
|
||||||
const val shouldSendEmailToProviderField = "send_email_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 inputFieldPlaceholderField = "input_field_placeholder"
|
||||||
|
|
||||||
const val priceDependOnShipAddressField = "is_flexible"
|
const val priceDependOnShipAddressField = "is_flexible"
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
package dev.inmo.tgbotapi.types.buttons
|
package dev.inmo.tgbotapi.types.buttons
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.inputFieldPlaceholderField
|
import dev.inmo.tgbotapi.types.*
|
||||||
import dev.inmo.tgbotapi.types.inputFieldPlaceholderLimit
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ReplyKeyboardMarkup(
|
data class ReplyKeyboardMarkup(
|
||||||
val keyboard: Matrix<KeyboardButton>,
|
val keyboard: Matrix<KeyboardButton>,
|
||||||
@SerialName("resize_keyboard")
|
@SerialName(resizeKeyboardField)
|
||||||
val resizeKeyboard: Boolean? = null,
|
val resizeKeyboard: Boolean? = null,
|
||||||
@SerialName("one_time_keyboard")
|
@SerialName(oneTimeKeyboardField)
|
||||||
val oneTimeKeyboard: Boolean? = null,
|
val oneTimeKeyboard: Boolean? = null,
|
||||||
@SerialName(inputFieldPlaceholderField)
|
@SerialName(inputFieldPlaceholderField)
|
||||||
val inputFieldPlaceholder: String? = null,
|
val inputFieldPlaceholder: String? = null,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package dev.inmo.tgbotapi.utils
|
package dev.inmo.tgbotapi.utils
|
||||||
|
|
||||||
import dev.inmo.micro_utils.crypto.hex
|
import com.soywiz.krypto.*
|
||||||
import dev.inmo.micro_utils.crypto.hmacSha256
|
import io.ktor.http.decodeURLQueryComponent
|
||||||
|
import io.ktor.utils.io.core.toByteArray
|
||||||
|
|
||||||
const val telegramBotAPIDefaultUrl = "https://api.telegram.org"
|
const val telegramBotAPIDefaultUrl = "https://api.telegram.org"
|
||||||
|
|
||||||
@@ -19,19 +20,28 @@ private inline val String.withoutLastSlash: String
|
|||||||
|
|
||||||
class TelegramAPIUrlsKeeper(
|
class TelegramAPIUrlsKeeper(
|
||||||
token: String,
|
token: String,
|
||||||
hostUrl: String = telegramBotAPIDefaultUrl
|
hostUrl: String = telegramBotAPIDefaultUrl,
|
||||||
|
urlsSuffixes: String = ""
|
||||||
) {
|
) {
|
||||||
val webAppDataSecretKey by lazy {
|
val webAppDataSecretKeyHash by lazy {
|
||||||
token.hmacSha256("WebAppData")
|
HMAC.hmacSHA256("WebAppData".toByteArray(), token.toByteArray())
|
||||||
}
|
}
|
||||||
|
val webAppDataSecretKey
|
||||||
|
get() = webAppDataSecretKeyHash.hexLower
|
||||||
|
|
||||||
val commonAPIUrl: String
|
val commonAPIUrl: String
|
||||||
val fileBaseUrl: String
|
val fileBaseUrl: String
|
||||||
|
|
||||||
|
constructor(token: String, testServer: Boolean, hostUrl: String = telegramBotAPIDefaultUrl) : this(
|
||||||
|
token,
|
||||||
|
hostUrl,
|
||||||
|
"/test".takeIf { testServer } ?: ""
|
||||||
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val correctedHost = hostUrl.withoutLastSlash
|
val correctedHost = hostUrl.withoutLastSlash
|
||||||
commonAPIUrl = "$correctedHost/bot$token"
|
commonAPIUrl = "$correctedHost/bot$token$urlsSuffixes"
|
||||||
fileBaseUrl = "$correctedHost/file/bot$token"
|
fileBaseUrl = "$correctedHost/file/bot$token$urlsSuffixes"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFileLinkUrl(filePath: String) = "${fileBaseUrl}/$filePath"
|
fun createFileLinkUrl(filePath: String) = "${fileBaseUrl}/$filePath"
|
||||||
@@ -40,5 +50,14 @@ class TelegramAPIUrlsKeeper(
|
|||||||
* @param rawData Data from [dev.inmo.tgbotapi.webapps.WebApp.initData]
|
* @param rawData Data from [dev.inmo.tgbotapi.webapps.WebApp.initData]
|
||||||
* @param hash Data from [dev.inmo.tgbotapi.webapps.WebApp.initDataUnsafe] from the field [dev.inmo.tgbotapi.webapps.WebAppInitData.hash]
|
* @param hash Data from [dev.inmo.tgbotapi.webapps.WebApp.initDataUnsafe] from the field [dev.inmo.tgbotapi.webapps.WebAppInitData.hash]
|
||||||
*/
|
*/
|
||||||
fun checkWebAppLink(rawData: String, hash: String) = rawData.hmacSha256(webAppDataSecretKey).hex() == hash
|
fun checkWebAppLink(rawData: String, hash: String): Boolean {
|
||||||
|
val preparedData = rawData
|
||||||
|
.decodeURLQueryComponent()
|
||||||
|
.split("&")
|
||||||
|
.filterNot { it.startsWith("hash=") }
|
||||||
|
.sorted()
|
||||||
|
.joinToString("\n")
|
||||||
|
|
||||||
|
return HMAC.hmacSHA256(webAppDataSecretKeyHash.bytes, preparedData.toByteArray()).hexLower == hash.lowercase()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package dev.inmo.tgbotapi.utils.extensions
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.types.tgWebAppStartParamField
|
||||||
|
|
||||||
|
fun createWebAppStartParam(value: String) = tgWebAppStartParamField to value
|
||||||
@@ -37,7 +37,7 @@ class InlineKeyboardRowBuilder : RowBuilder<InlineKeyboardButton>()
|
|||||||
* @see InlineKeyboardBuilder.row
|
* @see InlineKeyboardBuilder.row
|
||||||
*/
|
*/
|
||||||
inline fun inlineKeyboard(
|
inline fun inlineKeyboard(
|
||||||
crossinline block: InlineKeyboardBuilder.() -> Unit
|
block: InlineKeyboardBuilder.() -> Unit
|
||||||
) = InlineKeyboardBuilder().apply(block).build()
|
) = InlineKeyboardBuilder().apply(block).build()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,7 +52,7 @@ inline fun inlineKeyboard(
|
|||||||
* @see urlButton
|
* @see urlButton
|
||||||
*/
|
*/
|
||||||
inline fun InlineKeyboardBuilder.row(
|
inline fun InlineKeyboardBuilder.row(
|
||||||
crossinline block: InlineKeyboardRowBuilder.() -> Unit
|
block: InlineKeyboardRowBuilder.() -> Unit
|
||||||
) = add(InlineKeyboardRowBuilder().apply(block).row)
|
) = add(InlineKeyboardRowBuilder().apply(block).row)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ inline fun replyKeyboard(
|
|||||||
oneTimeKeyboard: Boolean? = null,
|
oneTimeKeyboard: Boolean? = null,
|
||||||
inputFieldPlaceholder: String? = null,
|
inputFieldPlaceholder: String? = null,
|
||||||
selective: Boolean? = null,
|
selective: Boolean? = null,
|
||||||
crossinline block: ReplyKeyboardBuilder.() -> Unit
|
block: ReplyKeyboardBuilder.() -> Unit
|
||||||
) = ReplyKeyboardBuilder().apply(block).build(resizeKeyboard, oneTimeKeyboard, inputFieldPlaceholder, selective)
|
) = ReplyKeyboardBuilder().apply(block).build(resizeKeyboard, oneTimeKeyboard, inputFieldPlaceholder, selective)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,7 +56,7 @@ inline fun replyKeyboard(
|
|||||||
* @see requestPollButton
|
* @see requestPollButton
|
||||||
*/
|
*/
|
||||||
inline fun ReplyKeyboardBuilder.row(
|
inline fun ReplyKeyboardBuilder.row(
|
||||||
crossinline block: ReplyKeyboardRowBuilder.() -> Unit
|
block: ReplyKeyboardRowBuilder.() -> Unit
|
||||||
) = add(ReplyKeyboardRowBuilder().apply(block).row)
|
) = add(ReplyKeyboardRowBuilder().apply(block).row)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,3 +110,14 @@ inline fun ReplyKeyboardRowBuilder.webAppButton(
|
|||||||
text: String,
|
text: String,
|
||||||
webApp: WebAppInfo
|
webApp: WebAppInfo
|
||||||
) = add(WebAppKeyboardButton(text, webApp))
|
) = add(WebAppKeyboardButton(text, webApp))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and put [WebAppKeyboardButton]
|
||||||
|
*
|
||||||
|
* @see replyKeyboard
|
||||||
|
* @see ReplyKeyboardBuilder.row
|
||||||
|
*/
|
||||||
|
inline fun ReplyKeyboardRowBuilder.webAppButton(
|
||||||
|
text: String,
|
||||||
|
url: String
|
||||||
|
) = webAppButton(text, WebAppInfo(url))
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.utils.updates.retrieving
|
package dev.inmo.tgbotapi.extensions.utils.updates.retrieving
|
||||||
|
|
||||||
import dev.inmo.micro_utils.coroutines.ExceptionHandler
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
import dev.inmo.micro_utils.coroutines.safely
|
|
||||||
import dev.inmo.tgbotapi.bot.RequestsExecutor
|
import dev.inmo.tgbotapi.bot.RequestsExecutor
|
||||||
import dev.inmo.tgbotapi.extensions.utils.nonstrictJsonFormat
|
import dev.inmo.tgbotapi.extensions.utils.nonstrictJsonFormat
|
||||||
import dev.inmo.tgbotapi.extensions.utils.updates.flowsUpdatesFilter
|
import dev.inmo.tgbotapi.extensions.utils.updates.flowsUpdatesFilter
|
||||||
@@ -10,6 +9,7 @@ import dev.inmo.tgbotapi.types.update.abstracts.Update
|
|||||||
import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy
|
import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy
|
||||||
import dev.inmo.tgbotapi.updateshandlers.*
|
import dev.inmo.tgbotapi.updateshandlers.*
|
||||||
import dev.inmo.tgbotapi.updateshandlers.webhook.WebhookPrivateKeyConfig
|
import dev.inmo.tgbotapi.updateshandlers.webhook.WebhookPrivateKeyConfig
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.server.application.call
|
import io.ktor.server.application.call
|
||||||
import io.ktor.server.engine.*
|
import io.ktor.server.engine.*
|
||||||
import io.ktor.server.request.receiveText
|
import io.ktor.server.request.receiveText
|
||||||
@@ -39,18 +39,22 @@ fun Route.includeWebhookHandlingInRoute(
|
|||||||
) {
|
) {
|
||||||
val transformer = scope.updateHandlerWithMediaGroupsAdaptation(block, mediaGroupsDebounceTimeMillis)
|
val transformer = scope.updateHandlerWithMediaGroupsAdaptation(block, mediaGroupsDebounceTimeMillis)
|
||||||
post {
|
post {
|
||||||
safely(
|
try {
|
||||||
exceptionsHandler ?: {}
|
runCatchingSafely {
|
||||||
) {
|
val asJson = nonstrictJsonFormat.parseToJsonElement(call.receiveText())
|
||||||
val asJson =
|
val update = nonstrictJsonFormat.decodeFromJsonElement(
|
||||||
nonstrictJsonFormat.parseToJsonElement(call.receiveText())
|
UpdateDeserializationStrategy,
|
||||||
val update = nonstrictJsonFormat.decodeFromJsonElement(
|
asJson
|
||||||
UpdateDeserializationStrategy,
|
)
|
||||||
asJson
|
transformer(update)
|
||||||
)
|
}.onSuccess {
|
||||||
transformer(update)
|
call.respond(HttpStatusCode.OK)
|
||||||
|
}.onFailure {
|
||||||
|
call.respond(HttpStatusCode.InternalServerError)
|
||||||
|
}.getOrThrow()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
exceptionsHandler ?.invoke(e)
|
||||||
}
|
}
|
||||||
call.respond("Ok")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +91,7 @@ fun startListenWebhooks(
|
|||||||
privateKeyConfig: WebhookPrivateKeyConfig? = null,
|
privateKeyConfig: WebhookPrivateKeyConfig? = null,
|
||||||
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
||||||
mediaGroupsDebounceTimeMillis: Long = 1000L,
|
mediaGroupsDebounceTimeMillis: Long = 1000L,
|
||||||
|
additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
block: UpdateReceiver<Update>
|
block: UpdateReceiver<Update>
|
||||||
): ApplicationEngine {
|
): ApplicationEngine {
|
||||||
val env = applicationEngineEnvironment {
|
val env = applicationEngineEnvironment {
|
||||||
@@ -98,6 +103,7 @@ fun startListenWebhooks(
|
|||||||
} ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block)
|
} ?: includeWebhookHandlingInRoute(scope, exceptionsHandler, mediaGroupsDebounceTimeMillis, block)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKeyConfig ?.let {
|
privateKeyConfig ?.let {
|
||||||
sslConnector(
|
sslConnector(
|
||||||
privateKeyConfig.keyStore,
|
privateKeyConfig.keyStore,
|
||||||
@@ -113,6 +119,7 @@ fun startListenWebhooks(
|
|||||||
port = listenPort
|
port = listenPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
additionalApplicationEngineEnvironmentConfigurator()
|
||||||
}
|
}
|
||||||
|
|
||||||
return embeddedServer(engineFactory, env).also {
|
return embeddedServer(engineFactory, env).also {
|
||||||
@@ -142,10 +149,11 @@ suspend fun RequestsExecutor.setWebhookInfoAndStartListenWebhooks(
|
|||||||
privateKeyConfig: WebhookPrivateKeyConfig? = null,
|
privateKeyConfig: WebhookPrivateKeyConfig? = null,
|
||||||
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
scope: CoroutineScope = CoroutineScope(Executors.newFixedThreadPool(4).asCoroutineDispatcher()),
|
||||||
mediaGroupsDebounceTimeMillis: Long = 1000L,
|
mediaGroupsDebounceTimeMillis: Long = 1000L,
|
||||||
|
additionalApplicationEngineEnvironmentConfigurator: ApplicationEngineEnvironmentBuilder.() -> Unit = {},
|
||||||
block: UpdateReceiver<Update>
|
block: UpdateReceiver<Update>
|
||||||
): ApplicationEngine = try {
|
): ApplicationEngine = try {
|
||||||
execute(setWebhookRequest)
|
execute(setWebhookRequest)
|
||||||
startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, mediaGroupsDebounceTimeMillis, block)
|
startListenWebhooks(listenPort, engineFactory, exceptionsHandler, listenHost, listenRoute, privateKeyConfig, scope, mediaGroupsDebounceTimeMillis, additionalApplicationEngineEnvironmentConfigurator, block)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ package dev.inmo.tgbotapi.webapps
|
|||||||
|
|
||||||
import dev.inmo.micro_utils.crypto.CryptoJs
|
import dev.inmo.micro_utils.crypto.CryptoJs
|
||||||
|
|
||||||
|
@Deprecated("Useless")
|
||||||
fun CryptoJs.HmacSHA256(text: String, key: String) = this.asDynamic().HmacSHA256(text, key).unsafeCast<String>()
|
fun CryptoJs.HmacSHA256(text: String, key: String) = this.asDynamic().HmacSHA256(text, key).unsafeCast<String>()
|
||||||
|
|
||||||
|
@Deprecated("Useless")
|
||||||
fun CryptoJs.hex(text: String) = this.asDynamic().format.Hex(text).unsafeCast<String>()
|
fun CryptoJs.hex(text: String) = this.asDynamic().format.Hex(text).unsafeCast<String>()
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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 sendDataOrWorkWithQueryId(
|
||||||
|
onSendData: () -> String?,
|
||||||
|
onAnswerWebAppQuery: (WebAppQueryId) -> Unit
|
||||||
|
) {
|
||||||
|
val queryId = webApp.initDataUnsafe.queryId
|
||||||
|
|
||||||
|
if (queryId == null) {
|
||||||
|
webApp.sendData(onSendData() ?: return)
|
||||||
|
} else {
|
||||||
|
onAnswerWebAppQuery(queryId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
) = sendDataOrWorkWithQueryId(onSendData, onAnswerWebAppQuery)
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package dev.inmo.tgbotapi.webapps
|
package dev.inmo.tgbotapi.webapps
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.types.MilliSeconds
|
import dev.inmo.tgbotapi.types.*
|
||||||
import dev.inmo.tgbotapi.types.WebAppQueryId
|
|
||||||
|
|
||||||
external interface WebAppInitData {
|
external interface WebAppInitData {
|
||||||
|
@JsName("query_id")
|
||||||
val queryId: WebAppQueryId?
|
val queryId: WebAppQueryId?
|
||||||
|
|
||||||
val user: WebAppUser?
|
val user: WebAppUser?
|
||||||
|
|||||||
Reference in New Issue
Block a user