1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-12-04 13:26:57 +00:00

Compare commits

..

31 Commits

Author SHA1 Message Date
f36d642ec8 sendDataOrWorkWithQueryId 2022-05-04 13:05:54 +06:00
aa6e5b2284 fix of WebAppInitData#queryId js name 2022-05-04 12:58:06 +06:00
32b83ac687 start 0.38.21 2022-05-04 12:51:14 +06:00
90e0b1ac81 Merge pull request #580 from InsanusMokrassar/0.38.20
0.38.20
2022-05-04 11:11:53 +06:00
1b5b3af45b add handleResult 2022-05-04 11:01:48 +06:00
7ff6412ec5 TelegramBot#answerWebAppQuery 2022-05-04 10:51:38 +06:00
bd23d3fbdb hotfixes 2022-05-03 23:12:03 +06:00
08147fc33b start 0.38.20 2022-05-03 23:11:02 +06:00
ba4c153659 Merge pull request #579 from InsanusMokrassar/0.38.19
0.38.19
2022-05-03 14:36:22 +06:00
3c3607d817 fill changelog 2022-05-03 14:32:29 +06:00
d0add888c4 now the handlers from the upstream lists are not modified in the whole fsm behaviour context 2022-05-03 14:28:03 +06:00
3e3adab46b complete fix of issue with subcontexts 2022-05-03 13:58:13 +06:00
89d13de307 deprecation of BehaviourContextWithFSMBuilder 2022-05-03 10:38:48 +06:00
32451f4e1c start 0.38.19 2022-05-03 09:44:39 +06:00
ad50f41d1e Merge pull request #578 from InsanusMokrassar/0.38.18
0.38.18
2022-05-02 13:49:24 +06:00
8c9cd9df67 fix of #577 2022-05-02 13:21:43 +06:00
8e7f7a03c8 note about xzima help 2022-05-02 12:44:20 +06:00
484e09374d small refactor 2022-05-02 12:29:30 +06:00
Alex
619c82bb32 fix: new BehaviourContext ignored 2022-05-02 12:29:21 +06:00
33fb75a5eb start 0.38.18 2022-05-02 12:28:39 +06:00
a52f31f4c9 Merge pull request #575 from InsanusMokrassar/0.38.17
0.38.17
2022-05-01 17:23:33 +06:00
682f696866 fill changelog 2022-05-01 17:23:12 +06:00
87fff2e5d0 add BotCommandScope helper extensions 2022-05-01 17:18:44 +06:00
92b4ba2ff0 fix of #574 2022-05-01 16:50:03 +06:00
9014cdbc99 start 0.38.17 2022-05-01 16:41:47 +06:00
37317a1055 Merge pull request #573 from InsanusMokrassar/0.38.16
0.38.16
2022-04-29 23:28:12 +06:00
60c21002e1 fixes 2022-04-29 19:50:02 +06:00
78a7a3546a remove checking of webapp data from webapp module due to its existing in core 2022-04-29 19:18:22 +06:00
4799617ced "TelegramAPIUrlsKeeper" now have two new things: properties "webAppDataSecretKey" and fun "checkWebAppLink" 2022-04-29 19:11:38 +06:00
c70484076d start 0.38.16 2022-04-29 18:56:38 +06:00
0c92e6eeb4 Merge pull request #570 from InsanusMokrassar/0.38.15
0.38.15
2022-04-26 13:45:09 +06:00
22 changed files with 330 additions and 136 deletions

View File

@@ -1,5 +1,45 @@
# TelegramBotAPI changelog
## 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`:

View File

@@ -20,6 +20,6 @@ javax_activation_version=1.1.1
dokka_version=1.6.10
library_group=dev.inmo
library_version=0.38.15
library_version=0.38.21
github_release_plugin_version=2.3.7

View File

@@ -40,8 +40,9 @@ data class BotBuilder internal constructor(
fun buildBot(
token: String,
apiUrl: String = telegramBotAPIDefaultUrl,
testServer: Boolean = false,
block: BotBuilder.() -> Unit
) = telegramBot(
TelegramAPIUrlsKeeper(token, apiUrl),
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
BotBuilder().apply(block).createHttpClient()
)

View File

@@ -66,17 +66,19 @@ inline fun telegramBot(
inline fun telegramBot(
token: String,
apiUrl: String = telegramBotAPIDefaultUrl,
testServer: Boolean = false,
client: HttpClient = HttpClient()
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), client)
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), client)
@Suppress("NOTHING_TO_INLINE")
inline fun <T: HttpClientEngineConfig> telegramBot(
token: String,
clientFactory: HttpClientEngineFactory<T>,
apiUrl: String = telegramBotAPIDefaultUrl,
testServer: Boolean = false,
noinline clientConfig: HttpClientConfig<T>.() -> Unit = {}
) = telegramBot(
TelegramAPIUrlsKeeper(token, apiUrl),
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
clientFactory,
clientConfig
)
@@ -90,9 +92,10 @@ inline fun telegramBot(
token: String,
clientEngine: HttpClientEngine,
apiUrl: String = telegramBotAPIDefaultUrl,
testServer: Boolean = false,
noinline clientConfig: HttpClientConfig<*>.() -> Unit = {}
) = telegramBot(
TelegramAPIUrlsKeeper(token, apiUrl),
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
clientEngine,
clientConfig
)
@@ -105,8 +108,9 @@ inline fun telegramBot(
inline fun telegramBot(
token: String,
apiUrl: String = telegramBotAPIDefaultUrl,
testServer: Boolean = false,
noinline clientConfig: HttpClientConfig<*>.() -> Unit
) = telegramBot(
TelegramAPIUrlsKeeper(token, apiUrl),
TelegramAPIUrlsKeeper(token, testServer, apiUrl),
clientConfig
)

View File

@@ -9,6 +9,8 @@ import dev.inmo.micro_utils.coroutines.accumulatorFlow
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import kotlin.jvm.JvmName
import kotlin.reflect.KClass
/**
* Interface which combine [BehaviourContext] and [StatesMachine]. Subcontext of triggers and states contexts must have
@@ -26,10 +28,31 @@ interface BehaviourContextWithFSM<T : State> : BehaviourContext, StatesMachine<T
handlers: List<BehaviourWithFSMStateHandlerHolder<*, T>>
): T? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(contextUpdatesFlow, state)
doInSubContext(updatesUpstreamFlow = contextUpdatesFlow) {
handleState(state)
}
}
}
/**
* Add NON STRICT [handler] to list of available in future [BehaviourContextWithFSM]. Non strict means that
* for input [State] will be used [KClass.isInstance] and any inheritor of [kClass] will pass this requirement
*
* @see BehaviourWithFSMStateHandlerHolder
* @see onStateOrSubstate
*/
fun <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(
bot: TelegramBot,
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
* [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>>
) : BehaviourContext by behaviourContext, BehaviourContextWithFSM<T> {
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) {
allUpdatesFlow.accumulatorFlow(scope)
}
@@ -65,12 +113,17 @@ class DefaultBehaviourContextWithFSM<T : State>(
override suspend fun StatesMachine<in T>.handleState(state: T): T? = launchStateHandling(
state,
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 {
val statePerformer: suspend (T) -> Unit = { state: T ->
val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), handlers)
val newState = launchStateHandling(state, getContextUpdatesFlow(state.context), actualHandlersList)
if (newState != null) {
statesManager.update(state, newState)
} else {
@@ -94,6 +147,26 @@ class DefaultBehaviourContextWithFSM<T : State>(
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) {
statesManager.startChain(state)
@@ -106,7 +179,7 @@ class DefaultBehaviourContextWithFSM<T : State>(
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
): BehaviourContextWithFSM<T> = BehaviourContextWithFSM(
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
behaviourContext.copy(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter),
handlers,
statesManager

View File

@@ -14,70 +14,8 @@ import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlin.reflect.KClass
class BehaviourContextWithFSMBuilder<T : State> internal constructor(
private val resultBehaviourContext: 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
}
@Deprecated("Will be removed soon")
typealias BehaviourContextWithFSMBuilder<T> = BehaviourContextWithFSM<T>
/**
* Creates [BehaviourContextWithFSM] via creating of [DefaultBehaviourContext] with [this] as [TelegramBot],
@@ -94,17 +32,17 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
scope: CoroutineScope = defaultCoroutineScopeProvider(),
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
): BehaviourContextWithFSM<T> = BehaviourContextWithFSMBuilder(
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
DefaultBehaviourContext(
this,
defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope,
upstreamUpdatesFlow = upstreamUpdatesFlow
),
statesManager,
presetHandlers
).apply { block() }.build()
presetHandlers,
statesManager
).apply { block() }
/**
* Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates
@@ -116,9 +54,9 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
scope: CoroutineScope = defaultCoroutineScopeProvider(),
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
): Pair<BehaviourContextWithFSM<T>, Job> = buildBehaviourWithFSM(
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
): Pair<DefaultBehaviourContextWithFSM<T>, Job> = buildBehaviourWithFSM(
upstreamUpdatesFlow,
scope,
defaultExceptionsHandler,
@@ -147,8 +85,8 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
* @see BehaviourContext
* @see BehaviourContextWithFSM
* @see longPolling
* @see BehaviourContextWithFSMBuilder.strictlyOn
* @see BehaviourContextWithFSMBuilder.onStateOrSubstate
* @see BehaviourContextWithFSM.strictlyOn
* @see BehaviourContextWithFSM.onStateOrSubstate
*/
@PreviewFeature
suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
@@ -156,17 +94,17 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
scope: CoroutineScope = defaultCoroutineScopeProvider(),
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
): BehaviourContextWithFSM<T> = BehaviourContextWithFSMBuilder(
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
): DefaultBehaviourContextWithFSM<T> = BehaviourContextWithFSM(
DefaultBehaviourContext(
this,
defaultExceptionsHandler ?.let { scope + ContextSafelyExceptionHandler(it) } ?: scope,
upstreamUpdatesFlow = flowUpdatesFilter.allUpdatesFlow
),
statesManager,
presetHandlers
).apply { block() }.build()
presetHandlers,
statesManager
).apply { block() }
/**
* Use [buildBehaviourWithFSM] to create [BehaviourContextWithFSM] and launch getting of updates
@@ -176,16 +114,16 @@ suspend fun <T : State> TelegramBot.buildBehaviourWithFSM(
* @see buildBehaviourWithFSMAndStartLongPolling
* @see BehaviourContext
* @see longPolling
* @see BehaviourContextWithFSMBuilder.strictlyOn
* @see BehaviourContextWithFSMBuilder.onStateOrSubstate
* @see BehaviourContextWithFSM.strictlyOn
* @see BehaviourContextWithFSM.onStateOrSubstate
*/
@PreviewFeature
suspend fun <T : State> TelegramBot.buildBehaviourWithFSMAndStartLongPolling(
scope: CoroutineScope = defaultCoroutineScopeProvider(),
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
) = FlowsUpdatesFilter().let {
buildBehaviourWithFSM(
it,

View File

@@ -34,20 +34,11 @@ class BehaviourWithFSMStateHandlerHolder<I : O, O : 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(
contextUpdatesFlow: Flow<Update>,
state: O
): O? {
val subscope = scope.LinkedSupervisorScope()
return with(copy(scope = subscope, upstreamUpdatesFlow = contextUpdatesFlow)) {
with(delegateTo) {
handleState(state as I)
}
}
): O? = with(delegateTo) {
handleState(state as I)
}
}

View File

@@ -37,11 +37,13 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSM(
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
testServer: Boolean = false,
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
): TelegramBot = telegramBot(
token,
apiUrl,
testServer,
builder
).apply {
buildBehaviourWithFSMAndStartLongPolling(
@@ -72,12 +74,14 @@ suspend fun <T : State> telegramBotWithBehaviourAndFSMAndStartLongPolling(
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
statesManager: StatesManager<T> = DefaultStatesManager(InMemoryDefaultStatesManagerRepo()),
presetHandlers: MutableList<BehaviourWithFSMStateHandlerHolder<*, T>> = mutableListOf(),
block: CustomBehaviourContextReceiver<BehaviourContextWithFSMBuilder<T>, Unit>
presetHandlers: List<BehaviourWithFSMStateHandlerHolder<*, T>> = listOf(),
testServer: Boolean = false,
block: CustomBehaviourContextReceiver<DefaultBehaviourContextWithFSM<T>, Unit>
): Pair<TelegramBot, Job> {
return telegramBot(
token,
apiUrl,
testServer,
builder
).let {
it to it.buildBehaviourWithFSMAndStartLongPolling (

View File

@@ -90,7 +90,7 @@ class DefaultBehaviourContext(
onBufferOverflow: BufferOverflow,
upstreamUpdatesFlow: Flow<Update>?,
updatesFilter: BehaviourContextAndTypeReceiver<Boolean, Update>?
): BehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter)
): DefaultBehaviourContext = DefaultBehaviourContext(bot, scope, broadcastChannelsSize, onBufferOverflow, upstreamUpdatesFlow, updatesFilter)
}
fun BehaviourContext(
@@ -116,19 +116,20 @@ suspend fun <T, BC : BehaviourContext> BC.doInSubContextWithUpdatesFilter(
updatesUpstreamFlow: Flow<Update> = allUpdatesFlow,
scope: CoroutineScope = LinkedSupervisorScope(),
behaviourContextReceiver: CustomBehaviourContextReceiver<BC, T>
): T = copy(
scope = scope,
updatesFilter = updatesFilter ?.let { _ ->
{
(this as? BC) ?.run {
updatesFilter(it)
} ?: true
}
},
upstreamUpdatesFlow = updatesUpstreamFlow
).run {
withContext(coroutineContext) {
behaviourContextReceiver().also { if (stopOnCompletion) stop() }
): T {
val newContext = copy(
scope = scope,
updatesFilter = updatesFilter ?.let { _ ->
{
(this as? BC) ?.run {
updatesFilter(it)
} ?: true
}
},
upstreamUpdatesFlow = updatesUpstreamFlow
) as BC
return withContext(currentCoroutineContext()) {
newContext.behaviourContextReceiver().also { if (stopOnCompletion) newContext.stop() }
}
}

View File

@@ -30,10 +30,12 @@ suspend fun telegramBotWithBehaviour(
apiUrl: String = telegramBotAPIDefaultUrl,
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
testServer: Boolean = false,
block: BehaviourContextReceiver<Unit>
): TelegramBot = telegramBot(
token,
apiUrl,
testServer,
builder
).apply {
buildBehaviour(
@@ -63,11 +65,13 @@ suspend fun telegramBotWithBehaviourAndLongPolling(
apiUrl: String = telegramBotAPIDefaultUrl,
builder: KtorRequestsExecutorBuilder.() -> Unit = {},
defaultExceptionsHandler: ExceptionHandler<Unit>? = null,
testServer: Boolean = false,
block: BehaviourContextReceiver<Unit>
): Pair<TelegramBot, Job> {
return telegramBot(
token,
apiUrl,
testServer,
builder
).let {
it to it.buildBehaviourWithLongPolling(

View File

@@ -125,5 +125,6 @@ inline fun telegramBot(
inline fun telegramBot(
token: String,
apiUrl: String = telegramBotAPIDefaultUrl,
testServer: Boolean = false,
builder: KtorRequestsExecutorBuilder.() -> Unit = {}
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder)
): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, testServer, apiUrl), builder)

View File

@@ -6,6 +6,9 @@ import dev.inmo.tgbotapi.types.InlineQueries.InlineQueryResult.abstracts.InlineQ
import dev.inmo.tgbotapi.types.webapps.query.SentWebAppMessage
import kotlinx.serialization.*
/**
* @param webAppQueryId [dev.inmo.tgbotapi.webapps.WebAppInitData.queryId]
*/
@Serializable
data class AnswerWebAppQuery(
@SerialName(webAppQueryIdField)

View File

@@ -22,11 +22,14 @@ private class SurrogateBotCommandScope(
BotCommandScopeAllGroupChats.type -> BotCommandScopeAllGroupChats
BotCommandScopeAllChatAdministrators.type -> BotCommandScopeAllChatAdministrators
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(
chatId ?: error("chat_administrators type must have $chatIdField field, but have no"),
userId ?: error("chat_administrators type must have $userIdField field, but have no")
chatId ?: error("${BotCommandScopeChatMember.type} type must have $chatIdField 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)
}
@@ -40,6 +43,7 @@ private class SurrogateBotCommandScope(
BotCommandScopeAllChatAdministrators -> SurrogateBotCommandScope(scope.type)
is BotCommandScopeChatAdministrators -> SurrogateBotCommandScope(scope.type, scope.chatId)
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)
sealed interface BotCommandScope {
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
@@ -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
data class BotCommandScopeChatMember(
override val chatId: ChatIdentifier,

View File

@@ -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()

View File

@@ -16,16 +16,33 @@ private inline val String.withoutLastSlash: String
class TelegramAPIUrlsKeeper(
token: String,
hostUrl: String = telegramBotAPIDefaultUrl
hostUrl: String = telegramBotAPIDefaultUrl,
urlsSuffixes: String = ""
) {
val webAppDataSecretKey by lazy {
token.hmacSha256("WebAppData")
}
val commonAPIUrl: String
val fileBaseUrl: String
constructor(token: String, testServer: Boolean, hostUrl: String = telegramBotAPIDefaultUrl) : this(
token,
hostUrl,
"/test".takeIf { testServer } ?: ""
)
init {
val correctedHost = hostUrl.withoutLastSlash
commonAPIUrl = "$correctedHost/bot$token"
fileBaseUrl = "$correctedHost/file/bot$token"
commonAPIUrl = "$correctedHost/bot$token$urlsSuffixes"
fileBaseUrl = "$correctedHost/file/bot$token$urlsSuffixes"
}
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
}

View File

@@ -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>()

View File

@@ -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()
}

View File

@@ -27,6 +27,13 @@ repositories {
}
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js(IR) {
browser()
nodejs()

View File

@@ -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))
}

View File

@@ -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)

View File

@@ -1,6 +1,7 @@
package dev.inmo.tgbotapi.webapps
import dev.inmo.micro_utils.crypto.CryptoJS
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
external class WebApp {
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.isInitDataSafe(botToken: String) = CryptoJS.hex(
CryptoJS.HmacSHA256(botToken, "WebAppData")
) == initDataUnsafe.hash
fun WebApp.isInitDataSafe(botToken: String) = TelegramAPIUrlsKeeper(botToken).checkWebAppLink(
initData,
initDataUnsafe.hash
)

View File

@@ -1,9 +1,9 @@
package dev.inmo.tgbotapi.webapps
import dev.inmo.tgbotapi.types.MilliSeconds
import dev.inmo.tgbotapi.types.WebAppQueryId
import dev.inmo.tgbotapi.types.*
external interface WebAppInitData {
@JsName("query_id")
val queryId: WebAppQueryId?
val user: WebAppUser?