From 2dadeb7eb7f65c6d0073b5194dbe871619de8034 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 12:50:54 +0600 Subject: [PATCH 1/7] start 0.30.7 --- CHANGELOG.md | 2 ++ gradle.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b92c1537..f122451911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # TelegramBotAPI changelog +## 0.30.7 + ## 0.30.6 * `Core` diff --git a/gradle.properties b/gradle.properties index f8fd51fe9e..8eec2df16d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ micro_utils_version=0.4.0 javax_activation_version=1.1.1 library_group=dev.inmo -library_version=0.30.6 +library_version=0.30.7 gradle_bintray_plugin_version=1.8.5 github_release_plugin_version=2.2.12 From ec74111a9dc003c87d09c5bd0448fcf26f1fbc44 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 12:51:55 +0600 Subject: [PATCH 2/7] update micro_utils version --- CHANGELOG.md | 4 ++++ gradle.properties | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f122451911..ce05d40c82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.30.7 +* `Common`: + * `Version`: + * `MicroUtils`: `0.4.0` -> `0.4.1` + ## 0.30.6 * `Core` diff --git a/gradle.properties b/gradle.properties index 8eec2df16d..2c4cd08bc5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ klock_version=1.12.1 uuid_version=0.2.2 ktor_version=1.4.2 -micro_utils_version=0.4.0 +micro_utils_version=0.4.1 javax_activation_version=1.1.1 From bc1b7c3f259e9d81ad03f96c2b85a8c678d0967c Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 13:18:11 +0600 Subject: [PATCH 3/7] fix ending of host url in TelegramAPIUrlsKeeper --- CHANGELOG.md | 2 ++ .../tgbotapi/utils/TelegramAPIUrlsKeeper.kt | 22 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce05d40c82..5b5223e811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ * `Common`: * `Version`: * `MicroUtils`: `0.4.0` -> `0.4.1` +* `Core`: + * `TelegramAPIUrlsKeeper` will fix ending of host url since this version ## 0.30.6 diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/TelegramAPIUrlsKeeper.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/TelegramAPIUrlsKeeper.kt index 1e8770804d..100781d435 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/TelegramAPIUrlsKeeper.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/TelegramAPIUrlsKeeper.kt @@ -2,10 +2,28 @@ package dev.inmo.tgbotapi.utils const val telegramBotAPIDefaultUrl = "https://api.telegram.org" +private inline val String.withoutLastSlash: String + get() { + var correctedUrl = this + while (true) { + val withoutSuffix = correctedUrl.removeSuffix("/") + if (withoutSuffix == correctedUrl) { + return correctedUrl + } + correctedUrl = withoutSuffix + } + } + class TelegramAPIUrlsKeeper( token: String, hostUrl: String = telegramBotAPIDefaultUrl ) { - val commonAPIUrl = "$hostUrl/bot$token" - val fileBaseUrl = "$hostUrl/file/bot$token" + val commonAPIUrl: String + val fileBaseUrl: String + + init { + val correctedHost = hostUrl.withoutLastSlash + commonAPIUrl = "$correctedHost/bot$token" + fileBaseUrl = "$correctedHost/file/bot$token" + } } From f8cccc3e17482b5fcf7a64127d47c07cf2996d4a Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 14:41:53 +0600 Subject: [PATCH 4/7] update gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8d8e8abe86..19869d6658 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip From 0d19952ba78bcd9e1893f745809625b8e087a8e7 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 15:48:58 +0600 Subject: [PATCH 5/7] PowLimiter and CommonLimiter rewriting (#210) --- CHANGELOG.md | 1 + .../bot/settings/limiters/CommonLimiter.kt | 72 +++++++------------ .../bot/settings/limiters/PowLimiter.kt | 66 ++++++++--------- 3 files changed, 58 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b5223e811..f82db78a7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * `MicroUtils`: `0.4.0` -> `0.4.1` * `Core`: * `TelegramAPIUrlsKeeper` will fix ending of host url since this version + * New mechanisms in`PowLimiter` and `CommonLimiter` has been added ## 0.30.6 diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/CommonLimiter.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/CommonLimiter.kt index 4910440856..d87b007544 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/CommonLimiter.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/CommonLimiter.kt @@ -1,67 +1,43 @@ package dev.inmo.tgbotapi.bot.settings.limiters import com.soywiz.klock.DateTime +import dev.inmo.micro_utils.coroutines.* +import dev.inmo.tgbotapi.types.MilliSeconds import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.sync.Semaphore +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import kotlin.coroutines.Continuation +import kotlin.math.roundToLong private fun now(): Long = DateTime.nowUnixLong() +@Serializable class CommonLimiter( private val lockCount: Int = 10, - private val regenTime: Long = 20 * 1000L // 20 seconds for full regen of opportunity to send message + private val regenTime: MilliSeconds = 15 * 1000, // 15 seconds for full regen of opportunity to send message + @Transient + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) : RequestLimiter { - private var doLimit: Boolean = false - - private val counterChannel = Channel(Channel.UNLIMITED) - private val scope = CoroutineScope(Dispatchers.Default) - private val counterJob = scope.launch { - var wasLastSecond = 0 - var lastCountTime = now() - var limitManagementJob: Job? = null - var removeLimitTime: Long = lastCountTime - for (counter in counterChannel) { - val now = now() - if (now - lastCountTime > 1000) { - lastCountTime = now - wasLastSecond = 1 - } else { - wasLastSecond++ - } - if (wasLastSecond >= lockCount) { - removeLimitTime = now + regenTime - if (limitManagementJob == null) { - limitManagementJob = launch { - doLimit = true - var internalNow = now() - while (internalNow < removeLimitTime) { - delay(removeLimitTime - internalNow) - internalNow = now() - } - doLimit = false - } + private val quotaSemaphore = Semaphore(lockCount) + private val counterRegeneratorJob = scope.launch { + val regenDelay: MilliSeconds = (regenTime.toDouble() / lockCount).roundToLong() + while (isActive) { + delay(regenDelay) + if (quotaSemaphore.availablePermits < lockCount) { + try { + quotaSemaphore.release() + } catch (_: IllegalStateException) { + // Skip IllegalStateException due to the fact that this exception may happens in release method } } - if (now > removeLimitTime) { - limitManagementJob = null - } - } - } - - private val quoterChannel = Channel(Channel.CONFLATED) - private val tickerJob = scope.launch { - while (isActive) { - quoterChannel.send(Unit) - delay(1000L) } } override suspend fun limit(block: suspend () -> T): T { - counterChannel.send(Unit) - return if (!doLimit) { - block() - } else { - quoterChannel.receive() - block() - } + quotaSemaphore.acquire() + return block() } } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/PowLimiter.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/PowLimiter.kt index 910cc03988..2eb0a87fa9 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/PowLimiter.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/settings/limiters/PowLimiter.kt @@ -1,5 +1,7 @@ package dev.inmo.tgbotapi.bot.settings.limiters +import dev.inmo.micro_utils.coroutines.* +import dev.inmo.tgbotapi.types.MilliSeconds import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.serialization.Serializable @@ -9,62 +11,60 @@ import kotlin.math.pow private sealed class RequestEvent private class AddRequest( - val continuation: Continuation + val continuation: Continuation ) : RequestEvent() private object CompleteRequest : RequestEvent() @Serializable data class PowLimiter( - private val minAwaitTime: Long = 0L, - private val maxAwaitTime: Long = 10000L, + private val minAwaitTime: MilliSeconds = 0L, + private val maxAwaitTime: MilliSeconds = 10000L, private val powValue: Double = 4.0, - private val powK: Double = 0.0016 + private val powK: Double = 1.6, + @Transient + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) ) : RequestLimiter { - @Transient - private val scope = CoroutineScope(Dispatchers.Default) - @Transient - private val eventsChannel = Channel(Channel.UNLIMITED) @Transient private val awaitTimeRange = minAwaitTime .. maxAwaitTime + @Transient + private val eventsChannel = let { + var requestsInWork = 0.0 + scope.actor { + when (it) { + is AddRequest -> { + val awaitTime = (requestsInWork.pow(powValue) * powK).toLong() + requestsInWork++ - init { - scope.launch { - var requestsInWork: Double = 0.0 - for (event in eventsChannel) { - when (event) { - is AddRequest -> { - val awaitTime = (((requestsInWork.pow(powValue) * powK) * 1000L).toLong()) - requestsInWork++ - - event.continuation.resume( - if (awaitTime in awaitTimeRange) { - awaitTime - } else { - if (awaitTime < minAwaitTime) { - minAwaitTime - } else { - maxAwaitTime - } - } - ) - } - is CompleteRequest -> requestsInWork-- + it.continuation.resume( + when { + awaitTime in awaitTimeRange -> awaitTime + awaitTime < awaitTimeRange.first -> awaitTimeRange.first + else -> awaitTimeRange.last + } + ) } + is CompleteRequest -> requestsInWork-- } } } - override suspend fun limit( - block: suspend () -> T + private suspend inline fun withDelay( + crossinline block: suspend () -> T ): T { val delayMillis = suspendCoroutine { scope.launch { eventsChannel.send(AddRequest(it)) } } delay(delayMillis) return try { - block() + safely { block() } } finally { eventsChannel.send(CompleteRequest) } } + + override suspend fun limit( + block: suspend () -> T + ): T { + return withDelay(block) + } } From 657e9aa770c86f23b3996911fd85562fede1455f Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 16:06:25 +0600 Subject: [PATCH 6/7] add KtorRequestsExecutorBuilder --- CHANGELOG.md | 5 +++++ .../tgbotapi/bot/Ktor/KtorRequestsExecutor.kt | 18 ++++++++++++++++++ .../tgbotapi/extensions/api/BotExtensions.kt | 14 ++++++-------- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f82db78a7c..2202ea5103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ * `Core`: * `TelegramAPIUrlsKeeper` will fix ending of host url since this version * New mechanisms in`PowLimiter` and `CommonLimiter` has been added + * New builder `KtorRequestsExecutorBuilder` + * New function `telegramBot` +* `Utils`: + * Simple function `telegramBot(TelegramAPIUrlsKeeper)` has been deprecated with replacement by almost the same + function in `Core` ## 0.30.6 diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt index 4c5ffe6922..cdaf835e84 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt @@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.bot.Ktor 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.TelegramBot import dev.inmo.tgbotapi.bot.exceptions.newRequestException import dev.inmo.tgbotapi.bot.settings.limiters.* import dev.inmo.tgbotapi.requests.abstracts.Request @@ -13,6 +14,23 @@ import io.ktor.client.features.* import io.ktor.client.statement.readText import kotlinx.serialization.json.Json +class KtorRequestsExecutorBuilder( + var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper +) { + var client: HttpClient = HttpClient() + var callsFactories: List = emptyList() + var excludeDefaultFactories: Boolean = false + var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter() + var jsonFormatter: Json = nonstrictJsonFormat + + fun build() = KtorRequestsExecutor(telegramAPIUrlsKeeper, client, callsFactories, excludeDefaultFactories, requestsLimiter, jsonFormatter) +} + +inline fun telegramBot( + telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, + crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {} +): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build() + class KtorRequestsExecutor( telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, client: HttpClient = HttpClient(), diff --git a/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt b/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt index c931181091..606018acbb 100644 --- a/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt +++ b/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt @@ -1,6 +1,5 @@ package dev.inmo.tgbotapi.extensions.api -import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutor import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl @@ -11,11 +10,11 @@ import io.ktor.client.engine.* /** * Allows to create bot using bot [urlsKeeper] */ +@Deprecated("Replaced in core", ReplaceWith("telegramBot", "dev.inmo.tgbotapi.bot.Ktor.telegramBot")) fun telegramBot( urlsKeeper: TelegramAPIUrlsKeeper -): TelegramBot = KtorRequestsExecutor( - urlsKeeper, - HttpClient() +): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot( + urlsKeeper ) /** @@ -24,10 +23,9 @@ fun telegramBot( fun telegramBot( urlsKeeper: TelegramAPIUrlsKeeper, client: HttpClient -): TelegramBot = KtorRequestsExecutor( - urlsKeeper, - client -) +): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(urlsKeeper) { + this.client = client +} /** * Allows to create bot using bot [urlsKeeper] and specify [HttpClientEngineFactory] by passing [clientFactory] param and optionally From 74c480b07edadf287d54d1e6b64ca8d7f74905ff Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 17 Nov 2020 16:18:58 +0600 Subject: [PATCH 7/7] one new telegramBot function --- .../dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt | 10 ++++++++++ .../dev/inmo/tgbotapi/extensions/api/BotExtensions.kt | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt index cdaf835e84..db18339ec3 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/Ktor/KtorRequestsExecutor.kt @@ -31,6 +31,16 @@ inline fun telegramBot( crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {} ): TelegramBot = KtorRequestsExecutorBuilder(telegramAPIUrlsKeeper).apply(builder).build() +/** + * Shortcut for [telegramBot] + */ +@Suppress("NOTHING_TO_INLINE") +inline fun telegramBot( + token: String, + apiUrl: String = telegramBotAPIDefaultUrl, + crossinline builder: KtorRequestsExecutorBuilder.() -> Unit = {} +): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl), builder) + class KtorRequestsExecutor( telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper, client: HttpClient = HttpClient(), diff --git a/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt b/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt index 606018acbb..18c1c2bcfb 100644 --- a/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt +++ b/tgbotapi.extensions.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/BotExtensions.kt @@ -1,5 +1,6 @@ package dev.inmo.tgbotapi.extensions.api +import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutorBuilder import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl @@ -71,11 +72,12 @@ inline fun telegramBot( /** * Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client] */ +@Deprecated("Replaced in core", ReplaceWith("telegramBot", "dev.inmo.tgbotapi.bot.Ktor.telegramBot")) @Suppress("NOTHING_TO_INLINE") inline fun telegramBot( token: String, apiUrl: String = telegramBotAPIDefaultUrl -): TelegramBot = telegramBot(TelegramAPIUrlsKeeper(token, apiUrl)) +): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(token, apiUrl) /** * Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client]