1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2025-11-16 12:00:18 +00:00

Compare commits

..

24 Commits
4.2.1 ... 4.2.4

Author SHA1 Message Date
2d6709baf4 Update CHANGELOG.md 2022-12-30 19:34:08 +06:00
b50ad30176 fixes in updateHandlerWithMediaGroupsAdaptation 2022-12-30 09:13:53 +06:00
887c51f83a Update BehaviourContextWithFSM.kt 2022-12-29 08:12:02 +06:00
c193b512d9 Update BehaviourContextWithFSM.kt 2022-12-28 22:59:10 +06:00
ee055873e6 fixes and improvements 2022-12-28 22:48:49 +06:00
6db6209c88 Fixes in DefaultBehaviourContextWithFSM 2022-12-28 22:41:27 +06:00
e92563e85f start 4.2.4 2022-12-28 22:26:35 +06:00
03f2f0e25b Merge pull request #699 from InsanusMokrassar/4.2.3
4.2.3
2022-12-28 09:16:00 +06:00
4197e13c54 Update CHANGELOG.md 2022-12-28 09:12:54 +06:00
e09ea9a9b4 upfill changelog 2022-12-28 09:11:49 +06:00
2a32654d57 update miroutils and fill changelog 2022-12-28 09:05:32 +06:00
0fff553ce1 Update KtorRequestsExecutor.kt 2022-12-27 22:31:42 +06:00
33a1701f5b Update ExceptionsOnlyLimiter.kt 2022-12-27 22:30:47 +06:00
f9a9f958ba Update ExceptionsOnlyLimiter.kt 2022-12-27 22:25:26 +06:00
f686be0271 Update ExceptionsOnlyLimiter.kt 2022-12-27 22:22:03 +06:00
91307f3ebf improvements in ExceptionsOnlyLimiter 2022-12-26 21:37:48 +06:00
8e64205f53 several improvements in requests limiters 2022-12-26 20:49:29 +06:00
5434df1f02 Merge pull request #695 from InsanusMokrassar/4.2.2
4.2.2
2022-12-18 10:05:17 +06:00
e56199ac9f Update CHANGELOG.md 2022-12-18 10:01:46 +06:00
3e199c6944 Update libs.versions.toml 2022-12-18 10:01:25 +06:00
b43d9aefb9 fix of #694 2022-12-13 10:52:56 +06:00
fe133bbde0 update microutils 2022-12-13 10:49:18 +06:00
332fe95adf start 4.2.2 2022-12-13 10:48:38 +06:00
1f416d4a28 Merge pull request #693 from InsanusMokrassar/4.2.1
4.2.1
2022-12-08 10:50:07 +06:00
11 changed files with 99 additions and 73 deletions

View File

@@ -1,5 +1,26 @@
# TelegramBotAPI changelog
## 4.2.4
* `Core`:
* Fixes in webhook parts adapter
* `BehaviourBuilderWithFSM`:
* Fixes in `DefaultBehaviourContextWithFSM`
## 4.2.3
* `Versions`:
* `MicroUtils`: `0.16.2` -> `0.16.4`
* `Core`:
* Simplify default `RequestsLimiter` (`ExceptionsOnlyLimiter`) (thanks to [@y9san9](https://github.com/y9san9) for help)
## 4.2.2
* `Versions`:
* `MicroUtils`: `0.16.0` -> `0.16.2`
* `Core`:
* Fix of [#694](https://github.com/InsanusMokrassar/TelegramBotAPI/issues/694): add opportunity to create `ChatId` and `ChatIdWithThreadId` from `IdChatIdentifier`
## 4.2.1
* `Versions`:

View File

@@ -6,4 +6,4 @@ kotlin.incremental=true
kotlin.incremental.js=true
library_group=dev.inmo
library_version=4.2.1
library_version=4.2.4

View File

@@ -13,7 +13,7 @@ ktor = "2.2.1"
ksp = "1.7.22-1.0.8"
kotlin-poet = "1.12.0"
microutils = "0.16.0"
microutils = "0.16.4"
github-release-plugin = "2.4.1"
dokka = "1.7.20"

View File

@@ -10,6 +10,8 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.handlers_registrar.T
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.reflect.KClass
/**
@@ -133,6 +135,9 @@ class DefaultBehaviourContextWithFSM<T : State>(
private val additionalHandlers = mutableListOf<BehaviourWithFSMStateHandlerHolder<*, T>>()
private var actualHandlersList = additionalHandlers + handlers
protected val statesJobs = mutableMapOf<T, Job>()
protected val statesJobsMutex = Mutex()
override suspend fun launchStateHandling(state: T, handlers: List<CheckableHandlerHolder<in T, T>>): T? {
return launchStateHandling(state, handlers, onStateHandlingErrorHandler)
}
@@ -164,21 +169,49 @@ class DefaultBehaviourContextWithFSM<T : State>(
statesManager.endChain(state)
}
}
fun Job.enableRemoveOnCompletion(state: T) {
invokeOnCompletion {
launchSafelyWithoutExceptions {
statesJobsMutex.withLock {
if (this@enableRemoveOnCompletion === statesJobs[state]) {
statesJobs.remove(state)
}
}
}
}
}
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it) }
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
statesJobs[it] = launch { statePerformer(it) }.apply { enableRemoveOnCompletion(it) }
}
}
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) {
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
}
updatesFlows.remove(it.context)
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) { (old, new) ->
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(old) ?.cancel() }
runCatchingSafely { statesJobs.remove(new) ?.cancel() }
statesJobs[new] = launch { statePerformer(new) }.apply { enableRemoveOnCompletion(new) }
}
if (old.context != new.context) {
updatesFlows.remove(old.context)
}
launch { statePerformer(new) }
}
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) {
updatesFlows.remove(it.context)
}
statesManager.getActiveStates().forEach {
launch { statePerformer(it) }
statesJobsMutex.withLock {
runCatchingSafely { statesJobs.remove(it) ?.cancel() }
statesJobs[it] = launch { statePerformer(it) }.apply { enableRemoveOnCompletion(it) }
}
}
}
/**

View File

@@ -30,7 +30,7 @@ class KtorRequestsExecutor(
client: HttpClient = HttpClient(),
callsFactories: List<KtorCallFactory> = emptyList(),
excludeDefaultFactories: Boolean = false,
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter,
private val jsonFormatter: Json = nonstrictJsonFormat,
private val pipelineStepsHolder: KtorPipelineStepsHolder = KtorPipelineStepsHolder
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
@@ -51,7 +51,7 @@ class KtorRequestsExecutor(
override suspend fun <T : Any> execute(request: Request<T>): T {
return runCatchingSafely {
pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories)
requestsLimiter.limit {
requestsLimiter.limit(request) {
var result: T? = null
lateinit var factoryHandledRequest: KtorCallFactory
for (potentialFactory in callsFactories) {
@@ -111,7 +111,7 @@ class KtorRequestsExecutorBuilder(
var client: HttpClient = HttpClient()
var callsFactories: List<KtorCallFactory> = emptyList()
var excludeDefaultFactories: Boolean = false
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter()
var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter
var jsonFormatter: Json = nonstrictJsonFormat
fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter)

View File

@@ -1,66 +1,18 @@
package dev.inmo.tgbotapi.bot.settings.limiters
import dev.inmo.micro_utils.coroutines.safely
import dev.inmo.tgbotapi.bot.exceptions.TooMuchRequestsException
import dev.inmo.tgbotapi.types.MilliSeconds
import dev.inmo.tgbotapi.types.RetryAfterError
import io.ktor.client.plugins.ClientRequestException
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
/**
* This limiter will limit requests only after getting a [RetryAfterError] or [ClientRequestException] with
* [HttpStatusCode.TooManyRequests] status code. Important thing is that in case if some of block has been blocked, all
* the others will wait until it will be possible to be called
*
* @param defaultTooManyRequestsDelay This parameter will be used in case of getting [ClientRequestException] with
* [HttpStatusCode.TooManyRequests] as a parameter for delay like it would be [TooMuchRequestsException]. The reason of
* it is that in [ClientRequestException] there is no information about required delay between requests
* Simple limiter which will lock any request when [TooMuchRequestsExceptions] is thrown and rerun request after lock time
*/
class ExceptionsOnlyLimiter(
private val defaultTooManyRequestsDelay: MilliSeconds = 1000L
) : RequestLimiter {
private val lockState = MutableStateFlow(false)
private suspend fun lock(timeMillis: MilliSeconds) {
try {
safely {
lockState.emit(true)
delay(timeMillis)
}
} finally {
lockState.emit(false)
}
}
object ExceptionsOnlyLimiter : RequestLimiter {
override suspend fun <T> limit(block: suspend () -> T): T {
while (true) {
lockState.first { !it }
var throwable: Throwable? = null
val result = safely({
throwable = when (it) {
is TooMuchRequestsException -> {
lock(it.retryAfter.leftToRetry)
it
}
is ClientRequestException -> {
if (it.response.status == HttpStatusCode.TooManyRequests) {
lock(defaultTooManyRequestsDelay)
} else {
throw it
}
it
}
else -> throw it
}
null
}) {
block()
}
if (throwable == null) {
return result!!
}
return try {
block()
} catch (e: TooMuchRequestsException) {
delay(e.retryAfter.leftToRetry)
limit(block)
}
}
}

View File

@@ -0,0 +1,5 @@
package dev.inmo.tgbotapi.bot.settings.limiters
object NoLimitsLimiter : RequestLimiter {
override suspend fun <T> limit(block: suspend () -> T): T = block()
}

View File

@@ -1,8 +1,12 @@
package dev.inmo.tgbotapi.bot.settings.limiters
import dev.inmo.tgbotapi.requests.abstracts.Request
interface RequestLimiter {
/**
* Use limit for working of block (like delay between or after, for example)
*/
suspend fun <T> limit(block: suspend () -> T): T
suspend fun <T : Any> limit(request: Request<T>, block: suspend () -> T) = limit(block)
}

View File

@@ -54,6 +54,13 @@ value class ChatIdWithThreadId(val chatIdWithThreadId: Pair<Identifier, MessageT
val ChatIdentifier.threadId: MessageThreadId?
get() = (this as? IdChatIdentifier) ?.threadId
fun IdChatIdentifier.toChatId() = when (this) {
is ChatId -> this
is ChatIdWithThreadId -> ChatId(chatId)
}
fun IdChatIdentifier.toChatWithThreadId(threadId: MessageThreadId) = IdChatIdentifier(chatId, threadId)
/**
* https://core.telegram.org/bots/api#formatting-options
*/

View File

@@ -1,5 +1,7 @@
package dev.inmo.tgbotapi.extensions.utils.updates.retrieving
import dev.inmo.micro_utils.coroutines.launchSafely
import dev.inmo.micro_utils.coroutines.launchSafelyWithoutExceptions
import dev.inmo.tgbotapi.extensions.utils.updates.convertWithMediaGroupUpdates
import dev.inmo.tgbotapi.types.message.abstracts.PossiblyMediaGroupMessage
import dev.inmo.tgbotapi.types.update.abstracts.BaseMessageUpdate
@@ -29,15 +31,18 @@ fun CoroutineScope.updateHandlerWithMediaGroupsAdaptation(
)
launch {
launch {
launchSafelyWithoutExceptions {
for (update in updatesChannel) {
when (val data = update.data) {
is PossiblyMediaGroupMessage<*> -> mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate)
val data = update.data
when {
data is PossiblyMediaGroupMessage<*> && data.mediaGroupId != null -> {
mediaGroupChannel.send("${data.mediaGroupId}${update::class.simpleName}" to update as BaseMessageUpdate)
}
else -> output(update)
}
}
}
launch {
launchSafelyWithoutExceptions {
for ((_, mediaGroup) in mediaGroupAccumulatedChannel) {
mediaGroup.convertWithMediaGroupUpdates().forEach {
output(it)

View File

@@ -41,10 +41,9 @@ fun Route.includeWebhookHandlingInRoute(
post {
try {
runCatchingSafely {
val asJson = nonstrictJsonFormat.parseToJsonElement(call.receiveText())
val update = nonstrictJsonFormat.decodeFromJsonElement(
val update = nonstrictJsonFormat.decodeFromString(
UpdateDeserializationStrategy,
asJson
call.receiveText()
)
transformer(update)
}.onSuccess {