mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-12-23 00:57:13 +00:00
commit
c5b7c4e1f5
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,19 @@
|
||||
# TelegramBotAPI changelog
|
||||
|
||||
## 0.30.3
|
||||
|
||||
* `Common`:
|
||||
* `Version`:
|
||||
* `MicroUtils`: `0.3.0` -> `0.3.1`
|
||||
* `Core`:
|
||||
* New type of requests exceptions `TooMuchRequestsException`. In fact it will be rare case when you will get this
|
||||
exception
|
||||
* `EmptyLimiter` has been renamed to `ExceptionsOnlyLimiter` and currently will stop requests after
|
||||
`TooMuchRequestsException` happen until retry time is actual
|
||||
* Now `ExceptionsOnlyLimiter` (previously `EmptyLimiter`) is a class
|
||||
* `AbstractRequestCallFactory` currently will not look at the response and wait if it have `RetryAfter` error. New
|
||||
behaviour aimed on delegating of this work to `RequestsLimiter`
|
||||
|
||||
## 0.30.2
|
||||
|
||||
* `Common`:
|
||||
|
@ -12,12 +12,12 @@ klock_version=1.12.1
|
||||
uuid_version=0.2.2
|
||||
ktor_version=1.4.2
|
||||
|
||||
micro_utils_version=0.3.0
|
||||
micro_utils_version=0.3.1
|
||||
|
||||
javax_activation_version=1.1.1
|
||||
|
||||
library_group=dev.inmo
|
||||
library_version=0.30.2
|
||||
library_version=0.30.3
|
||||
|
||||
gradle_bintray_plugin_version=1.8.5
|
||||
github_release_plugin_version=2.2.12
|
||||
|
@ -4,8 +4,7 @@ import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||
import dev.inmo.tgbotapi.bot.Ktor.base.*
|
||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.EmptyLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter
|
||||
import dev.inmo.tgbotapi.bot.settings.limiters.*
|
||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||
import dev.inmo.tgbotapi.types.Response
|
||||
import dev.inmo.tgbotapi.utils.*
|
||||
@ -19,7 +18,7 @@ class KtorRequestsExecutor(
|
||||
client: HttpClient = HttpClient(),
|
||||
callsFactories: List<KtorCallFactory> = emptyList(),
|
||||
excludeDefaultFactories: Boolean = false,
|
||||
private val requestsLimiter: RequestLimiter = EmptyLimiter,
|
||||
private val requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter(),
|
||||
private val jsonFormatter: Json = nonstrictJsonFormat
|
||||
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
|
||||
private val callsFactories: List<KtorCallFactory> = callsFactories.run {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package dev.inmo.tgbotapi.bot.Ktor.base
|
||||
|
||||
import dev.inmo.micro_utils.coroutines.safely
|
||||
import dev.inmo.tgbotapi.bot.Ktor.KtorCallFactory
|
||||
import dev.inmo.tgbotapi.bot.exceptions.newRequestException
|
||||
import dev.inmo.tgbotapi.requests.GetUpdates
|
||||
@ -51,23 +52,17 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
|
||||
val content = response.receive<String>()
|
||||
val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content)
|
||||
|
||||
return (responseObject.result?.let {
|
||||
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
||||
} ?: responseObject.parameters?.let {
|
||||
val error = it.error
|
||||
if (error is RetryAfterError) {
|
||||
delay(error.leftToRetry)
|
||||
makeCall(client, urlsKeeper, request, jsonFormatter)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: response.let {
|
||||
throw newRequestException(
|
||||
responseObject,
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
})
|
||||
return safely {
|
||||
(responseObject.result?.let {
|
||||
jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it)
|
||||
} ?: response.let {
|
||||
throw newRequestException(
|
||||
responseObject,
|
||||
content,
|
||||
"Can't get result object from $content"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.inmo.tgbotapi.bot.exceptions
|
||||
|
||||
import dev.inmo.tgbotapi.types.Response
|
||||
import com.soywiz.klock.DateTime
|
||||
import dev.inmo.tgbotapi.types.*
|
||||
import io.ktor.utils.io.errors.IOException
|
||||
|
||||
fun newRequestException(
|
||||
@ -16,6 +17,13 @@ fun newRequestException(
|
||||
description == "Unauthorized" -> UnauthorizedException(response, plainAnswer, message, cause)
|
||||
description.contains("PHOTO_INVALID_DIMENSIONS") -> InvalidPhotoDimensionsException(response, plainAnswer, message, cause)
|
||||
description.contains("wrong file identifier") -> WrongFileIdentifierException(response, plainAnswer, message, cause)
|
||||
description.contains("Too Many Requests") -> TooMuchRequestsException(
|
||||
(response.parameters ?.error as? RetryAfterError) ?: RetryAfterError(60, DateTime.now().unixMillisLong),
|
||||
response,
|
||||
plainAnswer,
|
||||
message,
|
||||
cause
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
} ?: CommonRequestException(response, plainAnswer, message, cause)
|
||||
@ -49,3 +57,6 @@ class InvalidPhotoDimensionsException(response: Response, plainAnswer: String, m
|
||||
|
||||
class WrongFileIdentifierException(response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
||||
RequestException(response, plainAnswer, message, cause)
|
||||
|
||||
class TooMuchRequestsException(val retryAfter: RetryAfterError, response: Response, plainAnswer: String, message: String?, cause: Throwable?) :
|
||||
RequestException(response, plainAnswer, message, cause)
|
||||
|
@ -1,5 +0,0 @@
|
||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||
|
||||
object EmptyLimiter : RequestLimiter {
|
||||
override suspend fun <T> limit(block: suspend () -> T): T = block()
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
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.*
|
||||
import io.ktor.client.features.ClientRequestException
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||
while (true) {
|
||||
lockState.first { !it }
|
||||
val result = safely({
|
||||
when (it) {
|
||||
is TooMuchRequestsException -> {
|
||||
lock(it.retryAfter.leftToRetry)
|
||||
Result.failure(it)
|
||||
}
|
||||
is ClientRequestException -> {
|
||||
if (it.response.status == HttpStatusCode.TooManyRequests) {
|
||||
lock(defaultTooManyRequestsDelay)
|
||||
} else {
|
||||
throw it
|
||||
}
|
||||
Result.failure(it)
|
||||
}
|
||||
else -> throw it
|
||||
}
|
||||
}) {
|
||||
Result.success(block())
|
||||
}
|
||||
if (result.isSuccess) {
|
||||
return result.getOrNull()!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Renamed", ReplaceWith("ExceptionsOnlyLimiter", "dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter"))
|
||||
typealias EmptyLimiter = ExceptionsOnlyLimiter
|
@ -28,6 +28,7 @@ typealias GooglePlaceId = String
|
||||
typealias GooglePlaceType = String
|
||||
|
||||
typealias Seconds = Int
|
||||
typealias MilliSeconds = Long
|
||||
typealias LongSeconds = Long
|
||||
|
||||
typealias Meters = Float
|
||||
|
@ -5,7 +5,7 @@ import com.soywiz.klock.DateTime
|
||||
sealed class RequestError
|
||||
|
||||
data class RetryAfterError(
|
||||
val seconds: Long,
|
||||
val seconds: Seconds,
|
||||
val startCountingMillis: Long
|
||||
) : RequestError() {
|
||||
val canContinue = (seconds * 1000L) + startCountingMillis
|
||||
|
@ -8,7 +8,7 @@ data class ResponseParametersRaw(
|
||||
@SerialName("migrate_to_chat_id")
|
||||
private val migrateToChatId: ChatId? = null,
|
||||
@SerialName("retry_after")
|
||||
private val retryAfter: Long? = null
|
||||
private val retryAfter: Seconds? = null
|
||||
) {
|
||||
@Transient
|
||||
private val createTime: Long = DateTime.now().unixMillisLong
|
||||
|
Loading…
Reference in New Issue
Block a user