mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2025-12-29 17:39:22 +00:00
Compare commits
42 Commits
6cbe313e42
...
0.38.22
| Author | SHA1 | Date | |
|---|---|---|---|
| 233c789054 | |||
| 52f405c3b1 | |||
| 199856acce | |||
| f36d642ec8 | |||
| aa6e5b2284 | |||
| 32b83ac687 | |||
| 90e0b1ac81 | |||
| 1b5b3af45b | |||
| 7ff6412ec5 | |||
| bd23d3fbdb | |||
| 08147fc33b | |||
| ba4c153659 | |||
| 3c3607d817 | |||
| d0add888c4 | |||
| 3e3adab46b | |||
| 89d13de307 | |||
| 32451f4e1c | |||
| ad50f41d1e | |||
| 8c9cd9df67 | |||
| 8e7f7a03c8 | |||
| 484e09374d | |||
|
|
619c82bb32 | ||
| 33fb75a5eb | |||
| a52f31f4c9 | |||
| 682f696866 | |||
| 87fff2e5d0 | |||
| 92b4ba2ff0 | |||
| 9014cdbc99 | |||
| 37317a1055 | |||
| 60c21002e1 | |||
| 78a7a3546a | |||
| 4799617ced | |||
| c70484076d | |||
| 0c92e6eeb4 | |||
| efd1c8f83a | |||
| 7e57a0e4e0 | |||
| 6b977e67d0 | |||
| ab5937449c | |||
| 61d3131bf2 | |||
| 048f244449 | |||
| 6ebf4ff652 | |||
| b4c41d7dd8 |
56
CHANGELOG.md
56
CHANGELOG.md
@@ -1,5 +1,61 @@
|
|||||||
# TelegramBotAPI changelog
|
# TelegramBotAPI changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
* `Core`:
|
||||||
|
* Add `BotCommandScopeChat` as new `BotCommandScope` (fix of [#574](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/574))
|
||||||
|
* `BotCommandScope` companion got several properties and functions for more useful scope creation
|
||||||
|
|
||||||
|
## 0.38.16
|
||||||
|
|
||||||
|
* `Core`:
|
||||||
|
* `TelegramAPIUrlsKeeper` now have two new things: properties `webAppDataSecretKey` and fun `checkWebAppLink`
|
||||||
|
|
||||||
|
## 0.38.15
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* `Version`:
|
||||||
|
* `MicroUtils`: `0.9.20` -> `0.9.24`
|
||||||
|
* `Core`:
|
||||||
|
* Fixes in `MessageContent#serializationModule`
|
||||||
|
* `BehaviourBuilder`:
|
||||||
|
* Add triggers for `DataCallbackQuery` and subtypes with regex checking of data
|
||||||
|
|
||||||
## 0.38.14
|
## 0.38.14
|
||||||
|
|
||||||
__This update contains including of [Telegram Bot API 6.0](https://core.telegram.org/bots/api-changelog#april-16-2022)__
|
__This update contains including of [Telegram Bot API 6.0](https://core.telegram.org/bots/api-changelog#april-16-2022)__
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ klock_version=2.7.0
|
|||||||
uuid_version=0.4.0
|
uuid_version=0.4.0
|
||||||
ktor_version=1.6.8
|
ktor_version=1.6.8
|
||||||
|
|
||||||
micro_utils_version=0.9.20
|
micro_utils_version=0.9.24
|
||||||
|
|
||||||
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.10
|
dokka_version=1.6.10
|
||||||
|
|
||||||
library_group=dev.inmo
|
library_group=dev.inmo
|
||||||
library_version=0.38.14
|
library_version=0.38.22
|
||||||
|
|
||||||
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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import dev.inmo.micro_utils.coroutines.accumulatorFlow
|
|||||||
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.jvm.JvmName
|
||||||
|
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
|
||||||
@@ -26,10 +28,31 @@ interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T
|
|||||||
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
|
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
|
||||||
): T? {
|
): T? {
|
||||||
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
|
||||||
handleState(contextUpdatesFlow, state)
|
doInSubContext(updatesUpstreamFlow = contextUpdatesFlow) {
|
||||||
|
handleState(state)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add NON STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Non strict means that
|
||||||
|
* for input [State] will be used [KClass.isInstance] and any inheritor of [kClass] will pass this requirement
|
||||||
|
*
|
||||||
|
* @see BehaviourWithFSMStateHandlerHolder
|
||||||
|
* @see onStateOrSubstate
|
||||||
|
*/
|
||||||
|
fun <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,
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
@@ -48,6 +71,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
|
||||||
@@ -58,6 +103,9 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
|||||||
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, Flow<Update>>()
|
||||||
|
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
|
||||||
|
private var actualHandlersList = additionalHandlers + handlers
|
||||||
|
|
||||||
private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) {
|
private fun getContextUpdatesFlow(context: Any) = updatesFlows.getOrPut(context) {
|
||||||
allUpdatesFlow.accumulatorFlow(scope)
|
allUpdatesFlow.accumulatorFlow(scope)
|
||||||
}
|
}
|
||||||
@@ -65,12 +113,17 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
|||||||
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(
|
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(
|
||||||
state,
|
state,
|
||||||
allUpdatesFlow,
|
allUpdatesFlow,
|
||||||
handlers
|
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 = launchStateHandling(state, getContextUpdatesFlow(state.context), actualHandlersList)
|
||||||
if (newState != null) {
|
if (newState != null) {
|
||||||
statesManager.update(state, newState)
|
statesManager.update(state, newState)
|
||||||
} else {
|
} else {
|
||||||
@@ -94,6 +147,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)
|
||||||
@@ -106,7 +179,7 @@ class DefaultBehaviourContextWithFSM<T : State>(
|
|||||||
onBufferOverflow: BufferOverflow,
|
onBufferOverflow: BufferOverflow,
|
||||||
upstreamUpdatesFlow: Flow<Update>?,
|
upstreamUpdatesFlow: Flow<Update>?,
|
||||||
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, 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,8 +85,8 @@ 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
|
@PreviewFeature
|
||||||
suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
|
||||||
@@ -156,17 +94,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 = 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 +114,16 @@ 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
|
@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,
|
||||||
|
|||||||
@@ -34,20 +34,11 @@ class BehaviourWithFSMStateHandlerHolder<I : O, O : 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(
|
suspend fun BehaviourContextWithFSM<in O>.handleState(
|
||||||
contextUpdatesFlow: Flow<Update>,
|
|
||||||
state: O
|
state: O
|
||||||
): O? {
|
): O? = with(delegateTo) {
|
||||||
val subscope = scope.LinkedSupervisorScope()
|
handleState(state as I)
|
||||||
return with(copy(scope = subscope, upstreamUpdatesFlow = contextUpdatesFlow)) {
|
|
||||||
with(delegateTo) {
|
|
||||||
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 (
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ internal inline fun <BC, T, I1, I2> CustomBehaviourContextAndTwoTypesReceiver<BC
|
|||||||
): CustomBehaviourContextAndTypeReceiver<BC, T, I2> = { invoke(this, i1, it) }
|
): CustomBehaviourContextAndTypeReceiver<BC, T, I2> = { invoke(this, i1, it) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains all necessary tools for work with bots and especially for [buildBehaviour]
|
* This class contains all necessary tools for work with bots and especially [buildBehaviour]
|
||||||
*
|
*
|
||||||
* @see DefaultBehaviourContext
|
* @see DefaultBehaviourContext
|
||||||
*/
|
*/
|
||||||
@@ -90,7 +90,7 @@ class DefaultBehaviourContext(
|
|||||||
onBufferOverflow: BufferOverflow,
|
onBufferOverflow: BufferOverflow,
|
||||||
upstreamUpdatesFlow: Flow<Update>?,
|
upstreamUpdatesFlow: Flow<Update>?,
|
||||||
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
|
||||||
): BehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter)
|
): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BehaviourContext(
|
fun BehaviourContext(
|
||||||
@@ -116,19 +116,20 @@ suspend fun <T, BC : BehaviourContext> BC.doInSubContextWithUpdatesFilter(
|
|||||||
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
|
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
|
||||||
scope: CoroutineScope = LinkedSupervisorScope(),
|
scope: CoroutineScope = LinkedSupervisorScope(),
|
||||||
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
|
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
|
||||||
): T = copy(
|
): T {
|
||||||
scope = scope,
|
val newContext = copy(
|
||||||
updatesFilter = updatesFilter ?.let { _ ->
|
scope = scope,
|
||||||
{
|
updatesFilter = updatesFilter ?.let { _ ->
|
||||||
(this as? BC) ?.run {
|
{
|
||||||
updatesFilter(it)
|
(this as? BC) ?.run {
|
||||||
} ?: true
|
updatesFilter(it)
|
||||||
}
|
} ?: true
|
||||||
},
|
}
|
||||||
upstreamUpdatesFlow = updatesUpstreamFlow
|
},
|
||||||
).run {
|
upstreamUpdatesFlow = updatesUpstreamFlow
|
||||||
withContext(coroutineContext) {
|
) as BC
|
||||||
behaviourContextReceiver().also { if (stopOnCompletion) stop() }
|
return withContext(currentCoroutineContext()) {
|
||||||
|
newContext.behaviourContextReceiver().also { if (stopOnCompletion) newContext.stop() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CallbackQueryFilte
|
|||||||
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.ByUserCallbackQueryMarkerFactory
|
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.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
|
||||||
|
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.CallbackQuery.*
|
import dev.inmo.tgbotapi.types.CallbackQuery.*
|
||||||
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
@@ -44,6 +45,62 @@ suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
|
|||||||
scenarioReceiver
|
scenarioReceiver
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dataRegex Will be used with [initialFilter] as [initialFilter] for upstream [onDataCallbackQuery] to filter
|
||||||
|
* [DataCallbackQuery] with [DataCallbackQuery.data] [String.matches] to [dataRegex]
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
|
||||||
|
dataRegex: Regex,
|
||||||
|
initialFilter: SimpleFilter<DataCallbackQuery>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
|
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
|
||||||
|
) = onDataCallbackQuery(
|
||||||
|
initialFilter = initialFilter + {
|
||||||
|
it.data.matches(dataRegex)
|
||||||
|
},
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
markerFactory,
|
||||||
|
scenarioReceiver
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data Will be converted to [Regex] via its constructor and pass it to upstream [onDataCallbackQuery]
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onDataCallbackQuery(
|
||||||
|
data: String,
|
||||||
|
initialFilter: SimpleFilter<DataCallbackQuery>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, DataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
|
markerFactory: MarkerFactory<in DataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, DataCallbackQuery>
|
||||||
|
) = onDataCallbackQuery(
|
||||||
|
Regex(data),
|
||||||
|
initialFilter,
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
markerFactory,
|
||||||
|
scenarioReceiver
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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,
|
||||||
@@ -116,6 +173,62 @@ suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
|||||||
scenarioReceiver
|
scenarioReceiver
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dataRegex Will be used with [initialFilter] as [initialFilter] for upstream [onInlineMessageIdDataCallbackQuery]
|
||||||
|
* to filter [InlineMessageIdDataCallbackQuery] with [InlineMessageIdDataCallbackQuery.data] [String.matches] to [dataRegex]
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
||||||
|
dataRegex: Regex,
|
||||||
|
initialFilter: SimpleFilter<InlineMessageIdDataCallbackQuery>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
|
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
|
||||||
|
) = onInlineMessageIdDataCallbackQuery(
|
||||||
|
initialFilter = initialFilter + {
|
||||||
|
it.data.matches(dataRegex)
|
||||||
|
},
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
markerFactory,
|
||||||
|
scenarioReceiver
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data Will be converted to [Regex] via its constructor and pass it to upstream [onInlineMessageIdDataCallbackQuery]
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onInlineMessageIdDataCallbackQuery(
|
||||||
|
data: String,
|
||||||
|
initialFilter: SimpleFilter<InlineMessageIdDataCallbackQuery>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, InlineMessageIdDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
|
markerFactory: MarkerFactory<in InlineMessageIdDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, InlineMessageIdDataCallbackQuery>
|
||||||
|
) = onInlineMessageIdDataCallbackQuery(
|
||||||
|
Regex(data),
|
||||||
|
initialFilter,
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
markerFactory,
|
||||||
|
scenarioReceiver
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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,
|
||||||
@@ -188,6 +301,62 @@ suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
|||||||
scenarioReceiver
|
scenarioReceiver
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param dataRegex Will be used with [initialFilter] as [initialFilter] for upstream [onMessageDataCallbackQuery] to filter
|
||||||
|
* [MessageDataCallbackQuery] with [MessageDataCallbackQuery.data] [String.matches] to [dataRegex]
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
||||||
|
dataRegex: Regex,
|
||||||
|
initialFilter: SimpleFilter<MessageDataCallbackQuery>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
|
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
|
||||||
|
) = onMessageDataCallbackQuery(
|
||||||
|
initialFilter = initialFilter + {
|
||||||
|
it.data.matches(dataRegex)
|
||||||
|
},
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
markerFactory,
|
||||||
|
scenarioReceiver
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data Will be converted to [Regex] via its constructor and pass it to upstream [onMessageDataCallbackQuery]
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onMessageDataCallbackQuery(
|
||||||
|
data: String,
|
||||||
|
initialFilter: SimpleFilter<MessageDataCallbackQuery>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, MessageDataCallbackQuery, Update>? = CallbackQueryFilterByUser,
|
||||||
|
markerFactory: MarkerFactory<in MessageDataCallbackQuery, Any> = ByUserCallbackQueryMarkerFactory,
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, MessageDataCallbackQuery>
|
||||||
|
) = onMessageDataCallbackQuery(
|
||||||
|
Regex(data),
|
||||||
|
initialFilter,
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
markerFactory,
|
||||||
|
scenarioReceiver
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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,
|
||||||
|
|||||||
@@ -28,16 +28,20 @@ fun <T> SimpleFilter<T>.listNone() = SimpleFilter<Iterable<T>> {
|
|||||||
/**
|
/**
|
||||||
* Makes an AND (&&) operation between [this] and [other]
|
* Makes an AND (&&) operation between [this] and [other]
|
||||||
*/
|
*/
|
||||||
operator fun <T> SimpleFilter<T>.times(other: SimpleFilter<T>): SimpleFilter<T> = {
|
operator fun <T> SimpleFilter<T>?.times(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let {
|
||||||
this(it) && other(it)
|
{
|
||||||
}
|
this(it) && other(it)
|
||||||
|
}
|
||||||
|
} ?: other
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes an OR (||) operation between [this] and [other]
|
* Makes an OR (||) operation between [this] and [other]
|
||||||
*/
|
*/
|
||||||
operator fun <T> SimpleFilter<T>.plus(other: SimpleFilter<T>): SimpleFilter<T> = {
|
operator fun <T> SimpleFilter<T>?.plus(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let {
|
||||||
this(it) || other(it)
|
{
|
||||||
}
|
this(it) || other(it)
|
||||||
|
}
|
||||||
|
} ?: other
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse results of [this]
|
* Reverse results of [this]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -22,11 +22,14 @@ private class SurrogateBotCommandScope(
|
|||||||
BotCommandScopeAllGroupChats.type -> BotCommandScopeAllGroupChats
|
BotCommandScopeAllGroupChats.type -> BotCommandScopeAllGroupChats
|
||||||
BotCommandScopeAllChatAdministrators.type -> BotCommandScopeAllChatAdministrators
|
BotCommandScopeAllChatAdministrators.type -> BotCommandScopeAllChatAdministrators
|
||||||
BotCommandScopeChatAdministrators.type -> BotCommandScopeChatAdministrators(
|
BotCommandScopeChatAdministrators.type -> BotCommandScopeChatAdministrators(
|
||||||
chatId ?: error("chat_administrators type must have $chatIdField field, but have no")
|
chatId ?: error("${BotCommandScopeChatAdministrators.type} type must have $chatIdField field, but have no")
|
||||||
)
|
)
|
||||||
BotCommandScopeChatMember.type -> BotCommandScopeChatMember(
|
BotCommandScopeChatMember.type -> BotCommandScopeChatMember(
|
||||||
chatId ?: error("chat_administrators type must have $chatIdField field, but have no"),
|
chatId ?: error("${BotCommandScopeChatMember.type} type must have $chatIdField field, but have no"),
|
||||||
userId ?: error("chat_administrators type must have $userIdField field, but have no")
|
userId ?: error("${BotCommandScopeChatMember.type} type must have $userIdField field, but have no")
|
||||||
|
)
|
||||||
|
BotCommandScopeChat.type -> BotCommandScopeChat(
|
||||||
|
chatId ?: error("${BotCommandScopeChat.type} type must have $chatIdField field, but have no")
|
||||||
)
|
)
|
||||||
else -> UnknownBotCommandScope(type)
|
else -> UnknownBotCommandScope(type)
|
||||||
}
|
}
|
||||||
@@ -40,6 +43,7 @@ private class SurrogateBotCommandScope(
|
|||||||
BotCommandScopeAllChatAdministrators -> SurrogateBotCommandScope(scope.type)
|
BotCommandScopeAllChatAdministrators -> SurrogateBotCommandScope(scope.type)
|
||||||
is BotCommandScopeChatAdministrators -> SurrogateBotCommandScope(scope.type, scope.chatId)
|
is BotCommandScopeChatAdministrators -> SurrogateBotCommandScope(scope.type, scope.chatId)
|
||||||
is BotCommandScopeChatMember -> SurrogateBotCommandScope(scope.type, scope.chatId, scope.userId)
|
is BotCommandScopeChatMember -> SurrogateBotCommandScope(scope.type, scope.chatId, scope.userId)
|
||||||
|
is BotCommandScopeChat -> SurrogateBotCommandScope(scope.type, scope.chatId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +51,16 @@ private class SurrogateBotCommandScope(
|
|||||||
@Serializable(BotCommandScopeSerializer::class)
|
@Serializable(BotCommandScopeSerializer::class)
|
||||||
sealed interface BotCommandScope {
|
sealed interface BotCommandScope {
|
||||||
val type: String
|
val type: String
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val Default = BotCommandScopeDefault
|
||||||
|
val AllPrivateChats = BotCommandScopeAllPrivateChats
|
||||||
|
val AllGroupChats = BotCommandScopeAllGroupChats
|
||||||
|
val AllChatAdministrators = BotCommandScopeAllChatAdministrators
|
||||||
|
fun ChatAdministrators(chatId: ChatIdentifier) = BotCommandScopeChatAdministrators(chatId)
|
||||||
|
fun Chat(chatId: ChatIdentifier) = BotCommandScopeChat(chatId)
|
||||||
|
fun ChatMember(chatId: ChatIdentifier, userId: UserId) = BotCommandScopeChatMember(chatId, userId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@@ -94,6 +108,17 @@ data class BotCommandScopeChatAdministrators(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class BotCommandScopeChat(
|
||||||
|
override val chatId: ChatIdentifier
|
||||||
|
) : ChatBotCommandScope {
|
||||||
|
@Required
|
||||||
|
override val type: String = BotCommandScopeChat.type
|
||||||
|
companion object {
|
||||||
|
const val type = "chat"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class BotCommandScopeChatMember(
|
data class BotCommandScopeChatMember(
|
||||||
override val chatId: ChatIdentifier,
|
override val chatId: ChatIdentifier,
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ interface MessageContent: ResendableContent {
|
|||||||
subclass(DiceContent::class)
|
subclass(DiceContent::class)
|
||||||
subclass(TextContent::class)
|
subclass(TextContent::class)
|
||||||
|
|
||||||
subclass(LiveLocationContent::class)
|
subclass(LocationContent::class, LocationContentSerializer)
|
||||||
subclass(StaticLocationContent::class)
|
|
||||||
|
|
||||||
subclass(PhotoContent::class)
|
subclass(PhotoContent::class)
|
||||||
subclass(VideoContent::class)
|
subclass(VideoContent::class)
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package dev.inmo.tgbotapi.utils
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.crypto.SourceBytes
|
||||||
|
import dev.inmo.micro_utils.crypto.SourceString
|
||||||
|
|
||||||
|
internal expect fun SourceString.hmacSha256(key: String): String
|
||||||
|
private val HEX_ARRAY = "0123456789abcdef".toCharArray()
|
||||||
|
|
||||||
|
internal fun SourceBytes.hex(): String {
|
||||||
|
val hexChars = CharArray(size * 2)
|
||||||
|
for (j in indices) {
|
||||||
|
val v: Int = this[j].toInt() and 0xFF
|
||||||
|
hexChars[j * 2] = HEX_ARRAY[v ushr 4]
|
||||||
|
hexChars[j * 2 + 1] = HEX_ARRAY[v and 0x0F]
|
||||||
|
}
|
||||||
|
return hexChars.concatToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun SourceString.hex(): String = encodeToByteArray().hex()
|
||||||
@@ -16,16 +16,33 @@ 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 {
|
||||||
|
token.hmacSha256("WebAppData")
|
||||||
|
}
|
||||||
|
|
||||||
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"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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]
|
||||||
|
*/
|
||||||
|
fun checkWebAppLink(rawData: String, hash: String) = rawData.hmacSha256(webAppDataSecretKey).hex() == hash
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package dev.inmo.tgbotapi.utils.extensions
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.types.tgWebAppStartParamField
|
||||||
|
|
||||||
|
fun createWebAppStartParam(value: String) = tgWebAppStartParamField to value
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package dev.inmo.tgbotapi.utils
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.crypto.CryptoJS
|
||||||
|
import dev.inmo.micro_utils.crypto.SourceString
|
||||||
|
|
||||||
|
actual fun SourceString.hmacSha256(key: String) = CryptoJS.asDynamic().HmacSHA256(this, key).toString().unsafeCast<String>()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package dev.inmo.tgbotapi.utils
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.crypto.SourceString
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
actual fun SourceString.hmacSha256(key: String): String {
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
|
||||||
|
val secretKey = SecretKeySpec(key.toByteArray(), "HmacSHA256")
|
||||||
|
mac.init(secretKey)
|
||||||
|
|
||||||
|
return mac.doFinal(toByteArray()).hex()
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
kotlin {
|
||||||
|
jvm {
|
||||||
|
compilations.main {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "1.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
js(IR) {
|
js(IR) {
|
||||||
browser()
|
browser()
|
||||||
nodejs()
|
nodejs()
|
||||||
|
|||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -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,6 +1,7 @@
|
|||||||
package dev.inmo.tgbotapi.webapps
|
package dev.inmo.tgbotapi.webapps
|
||||||
|
|
||||||
import dev.inmo.micro_utils.crypto.CryptoJS
|
import dev.inmo.micro_utils.crypto.CryptoJS
|
||||||
|
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||||
|
|
||||||
external class WebApp {
|
external class WebApp {
|
||||||
val initData: String
|
val initData: String
|
||||||
@@ -76,6 +77,7 @@ fun WebApp.onMainButtonClicked(eventHandler: EventHandler) = onEvent(EventType.M
|
|||||||
*/
|
*/
|
||||||
fun WebApp.onViewportChanged(eventHandler: ViewportChangedEventHandler) = onEvent(EventType.ViewportChanged, eventHandler)
|
fun WebApp.onViewportChanged(eventHandler: ViewportChangedEventHandler) = onEvent(EventType.ViewportChanged, eventHandler)
|
||||||
|
|
||||||
fun WebApp.isInitDataSafe(botToken: String) = CryptoJS.hex(
|
fun WebApp.isInitDataSafe(botToken: String) = TelegramAPIUrlsKeeper(botToken).checkWebAppLink(
|
||||||
CryptoJS.HmacSHA256(botToken, "WebAppData")
|
initData,
|
||||||
) == initDataUnsafe.hash
|
initDataUnsafe.hash
|
||||||
|
)
|
||||||
|
|||||||
@@ -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