mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-26 03:58:44 +00:00
commit
ae8a461e9d
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,19 @@
|
|||||||
# TelegramBotAPI changelog
|
# TelegramBotAPI changelog
|
||||||
|
|
||||||
|
## 3.2.0
|
||||||
|
|
||||||
|
**Since this update, `RequestsExecutor#execute` may throw only `BotException`. In case you wish to handle some exceptions from `execute` you must catch `BotException` and handle its `cause`**
|
||||||
|
|
||||||
|
* `Versions`:
|
||||||
|
* `Serialization`: `1.4.0-RC` -> `1.4.0`
|
||||||
|
* `MicroUtils`: `0.12.1` -> `0.12.4`
|
||||||
|
* `Core`:
|
||||||
|
* `SetWebhook#allowedUpdates` now is `ALL_UPDATES_LIST` by default instead of `null`
|
||||||
|
* `API`:
|
||||||
|
* Extension `TelegramBot#setWebhook` parameter `allowedUpdates` now is `ALL_UPDATES_LIST` by default instead of `null`
|
||||||
|
* `Utils`:
|
||||||
|
* All related to long polling extensions parameters `allowedUpdates` now are `ALL_UPDATES_LIST` by default instead of `null`
|
||||||
|
|
||||||
## 3.1.1
|
## 3.1.1
|
||||||
|
|
||||||
* `Common`:
|
* `Common`:
|
||||||
|
@ -6,4 +6,4 @@ kotlin.incremental=true
|
|||||||
kotlin.incremental.js=true
|
kotlin.incremental.js=true
|
||||||
|
|
||||||
library_group=dev.inmo
|
library_group=dev.inmo
|
||||||
library_version=3.1.1
|
library_version=3.2.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
|
||||||
kotlin = "1.7.10"
|
kotlin = "1.7.10"
|
||||||
kotlin-serialization = "1.4.0-RC"
|
kotlin-serialization = "1.4.0"
|
||||||
kotlin-coroutines = "1.6.4"
|
kotlin-coroutines = "1.6.4"
|
||||||
|
|
||||||
javax-activation = "1.1.1"
|
javax-activation = "1.1.1"
|
||||||
@ -13,7 +13,7 @@ ktor = "2.1.0"
|
|||||||
ksp = "1.7.10-1.0.6"
|
ksp = "1.7.10-1.0.6"
|
||||||
kotlin-poet = "1.12.0"
|
kotlin-poet = "1.12.0"
|
||||||
|
|
||||||
microutils = "0.12.1"
|
microutils = "0.12.4"
|
||||||
|
|
||||||
github-release-plugin = "2.4.1"
|
github-release-plugin = "2.4.1"
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import dev.inmo.tgbotapi.bot.TelegramBot
|
|||||||
import dev.inmo.tgbotapi.requests.abstracts.FileId
|
import dev.inmo.tgbotapi.requests.abstracts.FileId
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
|
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
|
||||||
import dev.inmo.tgbotapi.requests.webhook.SetWebhook
|
import dev.inmo.tgbotapi.requests.webhook.SetWebhook
|
||||||
|
import dev.inmo.tgbotapi.types.ALL_UPDATES_LIST
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this method to send information about webhook (like [url])
|
* Use this method to send information about webhook (like [url])
|
||||||
@ -12,7 +13,7 @@ suspend fun TelegramBot.setWebhookInfo(
|
|||||||
url: String,
|
url: String,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
) = execute(
|
) = execute(
|
||||||
@ -29,7 +30,7 @@ suspend fun TelegramBot.setWebhookInfo(
|
|||||||
certificate: FileId,
|
certificate: FileId,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
) = execute(
|
) = execute(
|
||||||
@ -46,7 +47,7 @@ suspend fun TelegramBot.setWebhookInfo(
|
|||||||
certificate: MultipartFile,
|
certificate: MultipartFile,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
) = execute(
|
) = execute(
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.doWithRegistration
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.*
|
||||||
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||||
|
import dev.inmo.tgbotapi.types.message.textsources.BotCommandTextSource
|
||||||
|
import dev.inmo.tgbotapi.types.message.textsources.TextSource
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will filter all the messages and include required commands with [commandRegex].
|
||||||
|
*
|
||||||
|
* * In case you wish to get only the commands at the start of message, use [requireCommandAtStart]
|
||||||
|
* * In case you wish to exclude messages with more than one command, you may use [requireSingleCommand]
|
||||||
|
* * In case you wish to exclude messages with commands params, you may use [requireCommandsWithoutParams]
|
||||||
|
*/
|
||||||
|
suspend fun BehaviourContext.waitCommandMessage(
|
||||||
|
commandRegex: Regex,
|
||||||
|
initRequest: Request<*>? = null,
|
||||||
|
errorFactory: NullableRequestBuilder<*> = { null }
|
||||||
|
) = channelFlow {
|
||||||
|
triggersHolder.handleableCommandsHolder.doWithRegistration(
|
||||||
|
commandRegex
|
||||||
|
) {
|
||||||
|
waitTextMessage(initRequest, errorFactory).filter {
|
||||||
|
it.content.textSources.any { it.botCommandTextSourceOrNull() ?.command ?.matches(commandRegex) == true }
|
||||||
|
}.collect {
|
||||||
|
send(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun BehaviourContext.waitCommandMessage(
|
||||||
|
command: String,
|
||||||
|
initRequest: Request<*>? = null,
|
||||||
|
errorFactory: NullableRequestBuilder<*> = { null }
|
||||||
|
) = waitCommandMessage(Regex(command), initRequest, errorFactory)
|
||||||
|
|
||||||
|
fun Flow<CommonMessage<TextContent>>.requireCommandAtStart() = filter {
|
||||||
|
it.content.textSources.firstOrNull() is BotCommandTextSource
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subsequent [Flow] will retrieve only messages with ONE [BotCommandTextSource]. It does not guarantee that this
|
||||||
|
* [BotCommandTextSource] will be at the start of the message
|
||||||
|
*
|
||||||
|
* @see requireCommandAtStart
|
||||||
|
*/
|
||||||
|
fun Flow<CommonMessage<TextContent>>.requireSingleCommand() = filter {
|
||||||
|
var count = 0
|
||||||
|
|
||||||
|
it.content.textSources.forEach {
|
||||||
|
if (it is BotCommandTextSource) {
|
||||||
|
count++
|
||||||
|
if (count > 1) {
|
||||||
|
return@filter false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
count == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subsequent [Flow] will retrieve only messages without [TextContent.textSources] which are not [BotCommandTextSource]
|
||||||
|
*/
|
||||||
|
fun Flow<CommonMessage<TextContent>>.requireCommandsWithoutParams() = filter {
|
||||||
|
it.content.textSources.none { it !is BotCommandTextSource }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map the commands with their arguments and source messages
|
||||||
|
*/
|
||||||
|
fun Flow<CommonMessage<TextContent>>.commandsWithParams(): Flow<Pair<CommonMessage<TextContent>, List<Pair<BotCommandTextSource, Array<TextSource>>>>> = mapNotNull {
|
||||||
|
var currentCommandTextSource: BotCommandTextSource? = null
|
||||||
|
val currentArgs = mutableListOf<TextSource>()
|
||||||
|
val result = mutableListOf<Pair<BotCommandTextSource, Array<TextSource>>>()
|
||||||
|
|
||||||
|
fun addCurrentCommandToResult() {
|
||||||
|
currentCommandTextSource ?.let {
|
||||||
|
result.add(it to currentArgs.toTypedArray())
|
||||||
|
currentArgs.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it.content.textSources.forEach {
|
||||||
|
it.ifBotCommandTextSource {
|
||||||
|
addCurrentCommandToResult()
|
||||||
|
currentCommandTextSource = it
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
currentArgs.add(it)
|
||||||
|
}
|
||||||
|
addCurrentCommandToResult()
|
||||||
|
|
||||||
|
result.toList().takeIf { it.isNotEmpty() } ?.let { result ->
|
||||||
|
it to result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat [commandsWithParams]. Each [Pair] of [BotCommandTextSource] and its [Array] of arg text sources will
|
||||||
|
* be associated with its source message
|
||||||
|
*/
|
||||||
|
fun Flow<CommonMessage<TextContent>>.flattenCommandsWithParams() = commandsWithParams().flatMapConcat { (message, commandsWithParams) ->
|
||||||
|
commandsWithParams.map {
|
||||||
|
message to it
|
||||||
|
}.asFlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use [flattenCommandsWithParams] and filter out the commands which do not [matches] to [commandRegex]
|
||||||
|
*/
|
||||||
|
fun Flow<CommonMessage<TextContent>>.commandParams(commandRegex: Regex) = flattenCommandsWithParams().filter { (_, commandWithParams) ->
|
||||||
|
commandWithParams.first.command.matches(commandRegex)
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.expectations
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.regularTextSourceOrNull
|
||||||
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
|
import dev.inmo.tgbotapi.types.message.abstracts.CommonMessage
|
||||||
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||||
|
import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
|
||||||
|
suspend fun BehaviourContext.waitDeepLinks(
|
||||||
|
initRequest: Request<*>? = null,
|
||||||
|
errorFactory: NullableRequestBuilder<*> = { null },
|
||||||
|
): Flow<Pair<CommonMessage<TextContent>, String>> = waitCommandMessage(
|
||||||
|
"start",
|
||||||
|
initRequest,
|
||||||
|
errorFactory
|
||||||
|
)
|
||||||
|
.requireSingleCommand()
|
||||||
|
.requireCommandAtStart()
|
||||||
|
.flattenCommandsWithParams().mapNotNull {
|
||||||
|
it.first to (it.second.second.singleOrNull() ?.regularTextSourceOrNull() ?.source ?.removePrefix(" ") ?: return@mapNotNull null)
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.*
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDeepLinks
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.CommonMessageFilterExcludeMediaGroups
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.MessageFilterByChat
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatMessageMarkerFactory
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory
|
||||||
|
import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.times
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.*
|
||||||
|
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams
|
||||||
|
import dev.inmo.tgbotapi.types.message.content.TextContent
|
||||||
|
import dev.inmo.tgbotapi.types.message.content.TextMessage
|
||||||
|
import dev.inmo.tgbotapi.types.message.textsources.RegularTextSource
|
||||||
|
import dev.inmo.tgbotapi.types.update.abstracts.Update
|
||||||
|
import io.ktor.http.decodeURLQueryComponent
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
|
||||||
|
private val startRegex = Regex("start")
|
||||||
|
suspend fun <BC : BehaviourContext> BC.onDeepLink(
|
||||||
|
initialFilter: SimpleFilter<Pair<TextMessage, String>>? = null,
|
||||||
|
subcontextUpdatesFilter: CustomBehaviourContextAndTwoTypesReceiver<BC, Boolean, Pair<TextMessage, String>, Update> = { (message, _), update -> MessageFilterByChat(this, message, update) },
|
||||||
|
markerFactory: MarkerFactory<Pair<TextMessage, String>, Any> = MarkerFactory { (message, _) -> ByChatMessageMarkerFactory(message) },
|
||||||
|
scenarioReceiver: CustomBehaviourContextAndTypeReceiver<BC, Unit, Pair<TextMessage, String>>
|
||||||
|
): Job = on(
|
||||||
|
markerFactory,
|
||||||
|
SimpleFilter<Pair<TextMessage, String>> { (message, _) ->
|
||||||
|
message.content.textSources.size == 2
|
||||||
|
&& message.content.textSources.firstOrNull() ?.asBotCommandTextSource() ?.command == "start"
|
||||||
|
&& message.content.textSources.getOrNull(1) is RegularTextSource
|
||||||
|
} * initialFilter,
|
||||||
|
subcontextUpdatesFilter,
|
||||||
|
scenarioReceiver,
|
||||||
|
) {
|
||||||
|
(it.messageUpdateOrNull()) ?.data ?.commonMessageOrNull() ?.withContentOrNull<TextContent>() ?.let { message ->
|
||||||
|
message to message.content.textSources[1].source.removePrefix(" ").decodeURLQueryComponent()
|
||||||
|
} ?.let(::listOfNotNull)
|
||||||
|
}.also {
|
||||||
|
triggersHolder.handleableCommandsHolder.registerHandleable(startRegex)
|
||||||
|
it.invokeOnCompletion {
|
||||||
|
this@onDeepLink.launchSafelyWithoutExceptions { triggersHolder.handleableCommandsHolder.unregisterHandleable(startRegex) }
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ fun interface SimpleFilter<in T> {
|
|||||||
suspend operator fun invoke(o: T): Boolean
|
suspend operator fun invoke(o: T): Boolean
|
||||||
}
|
}
|
||||||
val TrueSimpleFilter = SimpleFilter<Any?> { true }
|
val TrueSimpleFilter = SimpleFilter<Any?> { true }
|
||||||
|
val FalseSimpleFilter = SimpleFilter<Any?> { false }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter
|
* @return [SimpleFilter] which will return true in case when all the items in incoming data passed [this] filter
|
||||||
@ -28,21 +29,29 @@ fun <T> SimpleFilter<T>.listNone() = SimpleFilter<Iterable<T>> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes an AND (&&) operation between [this] and [other]
|
* Makes an AND (&&) operation between [this] and [other]
|
||||||
|
*
|
||||||
|
* * When both arguments are null, [TrueSimpleFilter] will be returned
|
||||||
*/
|
*/
|
||||||
operator fun <T> SimpleFilter<T>?.times(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let {
|
infix operator fun <T> SimpleFilter<T>?.times(other: SimpleFilter<T>?): SimpleFilter<T> = this ?.let {
|
||||||
SimpleFilter {
|
other ?.let {
|
||||||
this(it) && other(it)
|
SimpleFilter {
|
||||||
}
|
this(it) && other(it)
|
||||||
} ?: other
|
}
|
||||||
|
} ?: it
|
||||||
|
} ?: other ?: TrueSimpleFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes an OR (||) operation between [this] and [other]
|
* Makes an OR (||) operation between [this] and [other]
|
||||||
|
*
|
||||||
|
* * When both arguments are null, [TrueSimpleFilter] will be returned
|
||||||
*/
|
*/
|
||||||
operator fun <T> SimpleFilter<T>?.plus(other: SimpleFilter<T>): SimpleFilter<T> = this ?.let {
|
infix operator fun <T> SimpleFilter<T>?.plus(other: SimpleFilter<T>?): SimpleFilter<T> = this ?.let {
|
||||||
SimpleFilter {
|
other ?.let {
|
||||||
this(it) || other(it)
|
SimpleFilter {
|
||||||
}
|
this(it) || other(it)
|
||||||
} ?: other
|
}
|
||||||
|
} ?: it
|
||||||
|
} ?: other ?: TrueSimpleFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reverse results of [this]
|
* Reverse results of [this]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
|
package dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
||||||
@ -7,6 +8,7 @@ open class HandleableTriggersHolder<T>(
|
|||||||
preset: List<T> = emptyList()
|
preset: List<T> = emptyList()
|
||||||
) {
|
) {
|
||||||
protected val commandsMutex = Mutex()
|
protected val commandsMutex = Mutex()
|
||||||
|
protected val handleableCounts = mutableMapOf<T, Int>()
|
||||||
protected val _handleable = mutableListOf<T>().also {
|
protected val _handleable = mutableListOf<T>().also {
|
||||||
it.addAll(preset)
|
it.addAll(preset)
|
||||||
}
|
}
|
||||||
@ -16,12 +18,31 @@ open class HandleableTriggersHolder<T>(
|
|||||||
suspend fun registerHandleable(data: T) {
|
suspend fun registerHandleable(data: T) {
|
||||||
commandsMutex.withLock {
|
commandsMutex.withLock {
|
||||||
_handleable.add(data)
|
_handleable.add(data)
|
||||||
|
handleableCounts[data] = (handleableCounts[data] ?: 0) + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unregisterHandleable(data: T) {
|
suspend fun unregisterHandleable(data: T) {
|
||||||
commandsMutex.withLock {
|
commandsMutex.withLock {
|
||||||
_handleable.remove(data)
|
val newHandleableCount = (handleableCounts[data] ?: 0) - 1
|
||||||
|
if (newHandleableCount > 0) {
|
||||||
|
handleableCounts[data] = newHandleableCount
|
||||||
|
} else {
|
||||||
|
handleableCounts.remove(data)
|
||||||
|
_handleable.remove(data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun <T, R> HandleableTriggersHolder<T>.doWithRegistration(
|
||||||
|
data: T,
|
||||||
|
block: suspend () -> R
|
||||||
|
): R {
|
||||||
|
registerHandleable(data)
|
||||||
|
val result = runCatchingSafely {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
unregisterHandleable(data)
|
||||||
|
return result.getOrThrow()
|
||||||
|
}
|
||||||
|
@ -35,13 +35,18 @@ fun newRequestException(
|
|||||||
}
|
}
|
||||||
} ?: CommonRequestException(response, plainAnswer, message, cause)
|
} ?: CommonRequestException(response, plainAnswer, message, cause)
|
||||||
|
|
||||||
|
sealed class BotException(message: String = "Something went wrong", cause: Throwable? = null) : IOException(message, cause)
|
||||||
|
|
||||||
|
class CommonBotException(message: String = "Something went wrong", cause: Throwable? = null) : BotException(message, cause)
|
||||||
|
|
||||||
sealed class RequestException constructor(
|
sealed class RequestException constructor(
|
||||||
val response: Response,
|
val response: Response,
|
||||||
val plainAnswer: String,
|
val plainAnswer: String,
|
||||||
message: String? = null,
|
message: String? = null,
|
||||||
override val cause: Throwable? = null
|
cause: Throwable? = null
|
||||||
) : IOException(
|
) : BotException(
|
||||||
message ?: "Something went wrong"
|
message ?: "Something went wrong",
|
||||||
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
class CommonRequestException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
class CommonRequestException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package dev.inmo.tgbotapi.bot.ktor
|
package dev.inmo.tgbotapi.bot.ktor
|
||||||
|
|
||||||
|
import dev.inmo.micro_utils.coroutines.runCatchingSafely
|
||||||
import dev.inmo.micro_utils.coroutines.safely
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
import dev.inmo.tgbotapi.bot.exceptions.*
|
||||||
import dev.inmo.tgbotapi.bot.ktor.base.*
|
import dev.inmo.tgbotapi.bot.ktor.base.*
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||||
@ -48,12 +49,36 @@ class KtorRequestsExecutor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <T : Any> execute(request: Request<T>): T {
|
override suspend fun <T : Any> execute(request: Request<T>): T {
|
||||||
return runCatching {
|
return runCatchingSafely {
|
||||||
safely(
|
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
||||||
{ e ->
|
requestsLimiter.limit {
|
||||||
pipelineStepsHolder.onRequestException(request, e) ?.let { return@safely it }
|
var result: T? = null
|
||||||
|
lateinit var factoryHandledRequest: KtorCallFactory
|
||||||
|
for (potentialFactory in callsFactories) {
|
||||||
|
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
||||||
|
result = potentialFactory.makeCall(
|
||||||
|
client,
|
||||||
|
telegramAPIUrlsKeeper,
|
||||||
|
request,
|
||||||
|
jsonFormatter
|
||||||
|
)
|
||||||
|
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
||||||
|
if (result != null) {
|
||||||
|
factoryHandledRequest = potentialFactory
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
throw if (e is ClientRequestException) {
|
result ?.let {
|
||||||
|
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
||||||
|
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
||||||
|
}
|
||||||
|
}.let {
|
||||||
|
val result = it.exceptionOrNull() ?.let { e ->
|
||||||
|
pipelineStepsHolder.onRequestException(request, e) ?.let { return@let it }
|
||||||
|
|
||||||
|
if (e is ClientRequestException) {
|
||||||
|
val exceptionResult = runCatchingSafely {
|
||||||
val content = e.response.bodyAsText()
|
val content = e.response.bodyAsText()
|
||||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||||
newRequestException(
|
newRequestException(
|
||||||
@ -61,38 +86,15 @@ class KtorRequestsExecutor(
|
|||||||
content,
|
content,
|
||||||
"Can't get result object from $content"
|
"Can't get result object from $content"
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
e
|
|
||||||
}
|
}
|
||||||
|
exceptionResult.exceptionOrNull() ?.let {
|
||||||
|
CommonBotException(cause = e)
|
||||||
|
} ?: exceptionResult.getOrThrow()
|
||||||
|
} else {
|
||||||
|
CommonBotException(cause = e)
|
||||||
}
|
}
|
||||||
) {
|
} ?.let { Result.failure(it) } ?: it
|
||||||
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
|
pipelineStepsHolder.onRequestReturnResult(result, request, callsFactories)
|
||||||
requestsLimiter.limit {
|
|
||||||
var result: T? = null
|
|
||||||
lateinit var factoryHandledRequest: KtorCallFactory
|
|
||||||
for (potentialFactory in callsFactories) {
|
|
||||||
pipelineStepsHolder.onBeforeCallFactoryMakeCall(request, potentialFactory)
|
|
||||||
result = potentialFactory.makeCall(
|
|
||||||
client,
|
|
||||||
telegramAPIUrlsKeeper,
|
|
||||||
request,
|
|
||||||
jsonFormatter
|
|
||||||
)
|
|
||||||
result = pipelineStepsHolder.onAfterCallFactoryMakeCall(result, request, potentialFactory)
|
|
||||||
if (result != null) {
|
|
||||||
factoryHandledRequest = potentialFactory
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result ?.let {
|
|
||||||
pipelineStepsHolder.onRequestResultPresented(it, request, factoryHandledRequest, callsFactories)
|
|
||||||
} ?: pipelineStepsHolder.onRequestResultAbsent(request, callsFactories) ?: error("Can't execute request: $request")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.let {
|
|
||||||
pipelineStepsHolder.onRequestReturnResult(it, request, callsFactories)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ class MultipartSetWebhookRequest(
|
|||||||
certificate: MultipartFile,
|
certificate: MultipartFile,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
) : SetWebhookRequest(), MultipartRequest<Boolean> by MultipartRequestImpl(
|
) : SetWebhookRequest(), MultipartRequest<Boolean> by MultipartRequestImpl(
|
||||||
@ -40,7 +40,7 @@ fun SetWebhook(
|
|||||||
certificate: MultipartFile,
|
certificate: MultipartFile,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
): MultipartSetWebhookRequest = MultipartSetWebhookRequest(
|
): MultipartSetWebhookRequest = MultipartSetWebhookRequest(
|
||||||
@ -58,7 +58,7 @@ fun SetWebhook(
|
|||||||
certificate: FileId,
|
certificate: FileId,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
): SetWebhook = SetWebhook(
|
): SetWebhook = SetWebhook(
|
||||||
@ -84,7 +84,7 @@ fun SetWebhook(
|
|||||||
certificate: InputFile,
|
certificate: InputFile,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
) = when (certificate) {
|
) = when (certificate) {
|
||||||
@ -104,7 +104,7 @@ fun SetWebhook(
|
|||||||
url: String,
|
url: String,
|
||||||
ipAddress: String? = null,
|
ipAddress: String? = null,
|
||||||
maxAllowedConnections: Int? = null,
|
maxAllowedConnections: Int? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
dropPendingUpdates: Boolean? = null,
|
dropPendingUpdates: Boolean? = null,
|
||||||
secretToken: String? = null
|
secretToken: String? = null
|
||||||
) = SetWebhook(
|
) = SetWebhook(
|
||||||
@ -135,7 +135,7 @@ data class SetWebhook internal constructor(
|
|||||||
@SerialName(maxAllowedConnectionsField)
|
@SerialName(maxAllowedConnectionsField)
|
||||||
val maxAllowedConnections: Int? = null,
|
val maxAllowedConnections: Int? = null,
|
||||||
@SerialName(allowedUpdatesField)
|
@SerialName(allowedUpdatesField)
|
||||||
val allowedUpdates: List<String>? = null,
|
val allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
@SerialName(dropPendingUpdatesField)
|
@SerialName(dropPendingUpdatesField)
|
||||||
val dropPendingUpdates: Boolean? = null,
|
val dropPendingUpdates: Boolean? = null,
|
||||||
@SerialName(secretTokenField)
|
@SerialName(secretTokenField)
|
||||||
|
@ -81,6 +81,8 @@ sealed interface StickerType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val usernameRegex = Regex("@[\\w\\d_]+")
|
||||||
|
|
||||||
val degreesLimit = 1 .. 360
|
val degreesLimit = 1 .. 360
|
||||||
val horizontalAccuracyLimit = 0F .. 1500F
|
val horizontalAccuracyLimit = 0F .. 1500F
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package dev.inmo.tgbotapi.types.message.textsources
|
package dev.inmo.tgbotapi.types.message.textsources
|
||||||
|
|
||||||
|
import dev.inmo.tgbotapi.types.usernameRegex
|
||||||
|
import dev.inmo.tgbotapi.types.Username
|
||||||
import dev.inmo.tgbotapi.utils.RiskFeature
|
import dev.inmo.tgbotapi.utils.RiskFeature
|
||||||
import dev.inmo.tgbotapi.utils.internal.*
|
import dev.inmo.tgbotapi.utils.internal.*
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -16,6 +18,9 @@ data class BotCommandTextSource @RiskFeature(DirectInvocationOfTextSourceConstru
|
|||||||
val command: String by lazy {
|
val command: String by lazy {
|
||||||
commandRegex.find(source) ?.value ?.substring(1) ?: source.substring(1)// skip first symbol like "/" or "!"
|
commandRegex.find(source) ?.value ?.substring(1) ?: source.substring(1)// skip first symbol like "/" or "!"
|
||||||
}
|
}
|
||||||
|
val username: Username? by lazy {
|
||||||
|
Username(usernameRegex.find(source) ?.value ?: return@lazy null)
|
||||||
|
}
|
||||||
|
|
||||||
override val markdown: String by lazy { source.commandMarkdown() }
|
override val markdown: String by lazy { source.commandMarkdown() }
|
||||||
override val markdownV2: String by lazy { source.commandMarkdownV2() }
|
override val markdownV2: String by lazy { source.commandMarkdownV2() }
|
||||||
|
@ -30,7 +30,7 @@ fun <T> Flow<Iterable<T>>.flatten(): Flow<T> = flow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T, R> Flow<T>.flatMap(mapper: (T) -> Iterable<R>): Flow<R> = flow {
|
fun <T, R> Flow<T>.flatMap(mapper: suspend (T) -> Iterable<R>): Flow<R> = flow {
|
||||||
collect {
|
collect {
|
||||||
mapper(it).forEach {
|
mapper(it).forEach {
|
||||||
emit(it)
|
emit(it)
|
||||||
|
@ -4,6 +4,7 @@ import dev.inmo.tgbotapi.types.*
|
|||||||
import dev.inmo.tgbotapi.types.chat.*
|
import dev.inmo.tgbotapi.types.chat.*
|
||||||
import dev.inmo.tgbotapi.types.message.abstracts.Message
|
import dev.inmo.tgbotapi.types.message.abstracts.Message
|
||||||
import dev.inmo.tgbotapi.types.message.textsources.link
|
import dev.inmo.tgbotapi.types.message.textsources.link
|
||||||
|
import io.ktor.http.encodeURLQueryComponent
|
||||||
|
|
||||||
|
|
||||||
fun makeUsernameLink(username: String) = "$internalLinkBeginning/$username"
|
fun makeUsernameLink(username: String) = "$internalLinkBeginning/$username"
|
||||||
@ -17,11 +18,11 @@ inline val Username.deepLinkPrefix
|
|||||||
inline val Username.startattachPrefix
|
inline val Username.startattachPrefix
|
||||||
get() = makeUsernameStartattachPrefix(usernameWithoutAt)
|
get() = makeUsernameStartattachPrefix(usernameWithoutAt)
|
||||||
inline fun makeLink(username: Username) = username.link
|
inline fun makeLink(username: Username) = username.link
|
||||||
inline fun makeTelegramDeepLink(username: String, startParameter: String) = "${makeUsernameDeepLinkPrefix(username)}$startParameter"
|
inline fun makeTelegramDeepLink(username: String, startParameter: String) = "${makeUsernameDeepLinkPrefix(username)}$startParameter".encodeURLQueryComponent()
|
||||||
inline fun makeTelegramStartattach(username: String, data: String? = null) = makeUsernameStartattachLink(username, data)
|
inline fun makeTelegramStartattach(username: String, data: String? = null) = makeUsernameStartattachLink(username, data)
|
||||||
inline fun makeDeepLink(username: Username, startParameter: String) = "${username.deepLinkPrefix}$startParameter"
|
inline fun makeDeepLink(username: Username, startParameter: String) = makeTelegramDeepLink(username.usernameWithoutAt, startParameter)
|
||||||
inline fun makeTelegramDeepLink(username: Username, startParameter: String) = makeDeepLink(username, startParameter)
|
inline fun makeTelegramDeepLink(username: Username, startParameter: String) = makeDeepLink(username, startParameter)
|
||||||
inline fun makeTelegramStartattach(username: Username, data: String? = null) = makeTelegramStartattach(username.username, data)
|
inline fun makeTelegramStartattach(username: Username, data: String? = null) = makeTelegramStartattach(username.usernameWithoutAt, data)
|
||||||
|
|
||||||
fun makeLinkToMessage(
|
fun makeLinkToMessage(
|
||||||
username: String,
|
username: String,
|
||||||
|
@ -21,7 +21,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
fun TelegramBot.longPollingFlow(
|
fun TelegramBot.longPollingFlow(
|
||||||
timeoutSeconds: Seconds = 30,
|
timeoutSeconds: Seconds = 30,
|
||||||
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
|
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
): Flow<Update> = channelFlow {
|
): Flow<Update> = channelFlow {
|
||||||
var lastUpdateIdentifier: UpdateIdentifier? = null
|
var lastUpdateIdentifier: UpdateIdentifier? = null
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ fun TelegramBot.startGettingOfUpdatesByLongPolling(
|
|||||||
timeoutSeconds: Seconds = 30,
|
timeoutSeconds: Seconds = 30,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
|
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
updatesReceiver: UpdateReceiver<Update>
|
updatesReceiver: UpdateReceiver<Update>
|
||||||
): Job = longPollingFlow(timeoutSeconds, exceptionsHandler, allowedUpdates).subscribeSafely(
|
): Job = longPollingFlow(timeoutSeconds, exceptionsHandler, allowedUpdates).subscribeSafely(
|
||||||
scope,
|
scope,
|
||||||
@ -97,7 +97,7 @@ fun TelegramBot.createAccumulatedUpdatesRetrieverFlow(
|
|||||||
avoidInlineQueries: Boolean = false,
|
avoidInlineQueries: Boolean = false,
|
||||||
avoidCallbackQueries: Boolean = false,
|
avoidCallbackQueries: Boolean = false,
|
||||||
exceptionsHandler: ExceptionHandler<Unit>? = null,
|
exceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
allowedUpdates: List<String>? = null
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST
|
||||||
): Flow<Update> = longPollingFlow(
|
): Flow<Update> = longPollingFlow(
|
||||||
timeoutSeconds = 0,
|
timeoutSeconds = 0,
|
||||||
exceptionsHandler = {
|
exceptionsHandler = {
|
||||||
@ -117,7 +117,7 @@ fun TelegramBot.retrieveAccumulatedUpdates(
|
|||||||
avoidCallbackQueries: Boolean = false,
|
avoidCallbackQueries: Boolean = false,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
|
exceptionsHandler: (ExceptionHandler<Unit>)? = null,
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
updatesReceiver: UpdateReceiver<Update>
|
updatesReceiver: UpdateReceiver<Update>
|
||||||
): Job = createAccumulatedUpdatesRetrieverFlow(
|
): Job = createAccumulatedUpdatesRetrieverFlow(
|
||||||
avoidInlineQueries,
|
avoidInlineQueries,
|
||||||
@ -149,7 +149,7 @@ suspend fun TelegramBot.flushAccumulatedUpdates(
|
|||||||
avoidInlineQueries: Boolean = false,
|
avoidInlineQueries: Boolean = false,
|
||||||
avoidCallbackQueries: Boolean = false,
|
avoidCallbackQueries: Boolean = false,
|
||||||
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
|
||||||
allowedUpdates: List<String>? = null,
|
allowedUpdates: List<String>? = ALL_UPDATES_LIST,
|
||||||
exceptionsHandler: ExceptionHandler<Unit>? = null,
|
exceptionsHandler: ExceptionHandler<Unit>? = null,
|
||||||
updatesReceiver: UpdateReceiver<Update> = {}
|
updatesReceiver: UpdateReceiver<Update> = {}
|
||||||
) = retrieveAccumulatedUpdates(
|
) = retrieveAccumulatedUpdates(
|
||||||
|
Loading…
Reference in New Issue
Block a user