diff --git a/CHANGELOG b/CHANGELOG index 58d0d932f2..39438fbf05 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,3 +42,12 @@ * Add extension `String#toMarkdown` * Fix of inserting of text when create Markdown-adapted text from text and text entities * Fix default realisation of MessageEntity#asMarkdownSource + +### 0.9.0 + +* Old extension `OkHttpClient.Builder#useWith` now deprecated and must be replaced by the same in +`com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor` package +* Replace `ProxySettings` data class in `settings` package, deprecate old link +* `BaseRequestsExecutor` now have no it's own scope +* Add `RequestLimiter` and base realisations +* Now `KtorRequestsExecutor` can receive as one of parameters `RequestLimiter` (by default - `EmptyLimiter`) diff --git a/build.gradle b/build.gradle index 679e579617..4b60baaa21 100644 --- a/build.gradle +++ b/build.gradle @@ -1,4 +1,4 @@ -project.version = "0.8.5" +project.version = "0.9.0" project.group = "com.github.insanusmokrassar" buildscript { diff --git a/gradle.properties b/gradle.properties index dc6da056e3..209f0390f7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,6 +3,6 @@ kotlin_version=1.3.11 kotlin_coroutines_version=1.1.0 kotlin_serialisation_runtime_version=0.9.1 joda_time_version=2.10.1 -ktor_version=1.0.1 +ktor_version=1.1.1 gradle_bintray_plugin_version=1.8.4 diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/BaseRequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/BaseRequestsExecutor.kt new file mode 100644 index 0000000000..abaf662aa4 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/BaseRequestsExecutor.kt @@ -0,0 +1,8 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot + +abstract class BaseRequestsExecutor( + token: String, + hostUrl: String = "https://api.telegram.org" +) : RequestsExecutor { + protected val baseUrl: String = "$hostUrl/bot$token" +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/DefaultRequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/DefaultRequestsExecutor.kt deleted file mode 100644 index dd1ac11d42..0000000000 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/DefaultRequestsExecutor.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.github.insanusmokrassar.TelegramBotAPI.bot - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.asCoroutineDispatcher -import java.util.concurrent.Executors - -abstract class BaseRequestsExecutor( - token: String, - hostUrl: String = "https://api.telegram.org" -) : RequestsExecutor { - protected val baseUrl: String = "$hostUrl/bot$token" - - protected val scope: CoroutineScope = CoroutineScope( - Executors.newSingleThreadExecutor().asCoroutineDispatcher() - ) -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt index 5b3ec01f38..7660088d98 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt @@ -4,6 +4,8 @@ import com.github.insanusmokrassar.TelegramBotAPI.bot.BaseRequestsExecutor import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base.MultipartRequestCallFactory import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base.SimpleRequestCallFactory import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestException +import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters.EmptyLimiter +import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters.RequestLimiter import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request import com.github.insanusmokrassar.TelegramBotAPI.types.ResponseParameters import io.ktor.client.HttpClient @@ -19,7 +21,8 @@ class KtorRequestsExecutor( private val client: HttpClient = HttpClient(OkHttp), hostUrl: String = "https://api.telegram.org", callsFactories: List = emptyList(), - excludeDefaultFactories: Boolean = false + excludeDefaultFactories: Boolean = false, + private val requestsLimiter: RequestLimiter = EmptyLimiter ) : BaseRequestsExecutor(token, hostUrl) { constructor( token: String, @@ -40,30 +43,32 @@ class KtorRequestsExecutor( } override suspend fun execute(request: Request): T { - var call: HttpClientCall? = null - for (factory in callsFactories) { - call = factory.prepareCall( - client, - baseUrl, - request - ) - if (call != null) { - break + return requestsLimiter.limit { + var call: HttpClientCall? = null + for (factory in callsFactories) { + call = factory.prepareCall( + client, + baseUrl, + request + ) + if (call != null) { + break + } } - } - if (call == null) { - throw IllegalArgumentException("Can't execute request: $request") - } - val content = call.response.content.toByteArray().toString(Charset.defaultCharset()) - val responseObject = JSON.parse( - ResponseParameters.serializer(request.resultSerializer()), - content - ) - return responseObject.result ?: call.let { - throw RequestException( - responseObject, - "Can't get result object" + if (call == null) { + throw IllegalArgumentException("Can't execute request: $request") + } + val content = call.response.content.toByteArray().toString(Charset.defaultCharset()) + val responseObject = JSON.parse( + ResponseParameters.serializer(request.resultSerializer()), + content ) + responseObject.result ?: call.let { + throw RequestException( + responseObject, + "Can't get result object" + ) + } } } } diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/ProxySettings.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/ProxySettings.kt new file mode 100644 index 0000000000..745c4ecdf8 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/ProxySettings.kt @@ -0,0 +1,32 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor + +import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.ProxySettings +import io.ktor.http.HttpHeaders +import okhttp3.Credentials +import okhttp3.OkHttpClient +import java.net.InetSocketAddress +import java.net.Proxy + +fun OkHttpClient.Builder.useWith(proxySettings: ProxySettings) { + proxy( + Proxy( + Proxy.Type.SOCKS, + InetSocketAddress( + proxySettings.host, + proxySettings.port + ) + ) + ) + proxySettings.password ?.let { + password -> + proxyAuthenticator { + _, response -> + response.request().newBuilder().apply { + addHeader( + HttpHeaders.ProxyAuthorization, + Credentials.basic(proxySettings.username ?: "", password) + ) + }.build() + } + } +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/ProxySettings.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/ProxySettings.kt index 1023292e76..12e3ab9e71 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/ProxySettings.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/ProxySettings.kt @@ -1,37 +1,18 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot -import okhttp3.Credentials +import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.useWith +import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.ProxySettings import okhttp3.OkHttpClient -import java.net.InetSocketAddress -import java.net.Proxy -data class ProxySettings( - val host: String = "localhost", - val port: Int = 1080, - val username: String? = null, - val password: String? = null +@Deprecated( + "Replaced in settings package", + ReplaceWith("ProxySettings", "com.github.insanusmokrassar.TelegramBotAPI.bot.settings.ProxySettings") ) +typealias ProxySettings = com.github.insanusmokrassar.TelegramBotAPI.bot.settings.ProxySettings -fun OkHttpClient.Builder.useWith(proxySettings: ProxySettings) { - proxy( - Proxy( - Proxy.Type.SOCKS, - InetSocketAddress( - proxySettings.host, - proxySettings.port - ) - ) - ) - proxySettings.password ?.let { - password -> - proxyAuthenticator { - _, response -> - response.request().newBuilder().apply { - addHeader( - "Proxy-Authorization", - Credentials.basic(proxySettings.username ?: "", password) - ) - }.build() - } - } -} + +@Deprecated( + "Replaced in Ktor package", + ReplaceWith("useWith", "com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.useWith") +) +fun OkHttpClient.Builder.useWith(proxySettings: ProxySettings) = useWith(proxySettings) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/ProxySettings.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/ProxySettings.kt new file mode 100644 index 0000000000..19dfdec44d --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/ProxySettings.kt @@ -0,0 +1,16 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.settings + +import kotlinx.serialization.Optional +import kotlinx.serialization.Serializable + +@Serializable +data class ProxySettings( + @Optional + val host: String = "localhost", + @Optional + val port: Int = 1080, + @Optional + val username: String? = null, + @Optional + val password: String? = null +) diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/EmptyLimiter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/EmptyLimiter.kt new file mode 100644 index 0000000000..c0a24f1885 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/EmptyLimiter.kt @@ -0,0 +1,5 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters + +object EmptyLimiter : RequestLimiter { + override suspend fun limit(block: suspend () -> T): T = block() +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/PowLimiter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/PowLimiter.kt new file mode 100644 index 0000000000..da2acc46bf --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/PowLimiter.kt @@ -0,0 +1,75 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.serialization.* +import java.util.concurrent.Executors +import kotlin.coroutines.* + +private sealed class RequestEvent +private class AddRequest( + val continuation: Continuation +) : RequestEvent() +private object CompleteRequest : RequestEvent() + +@Serializable +data class PowLimiter( + @Optional + private val minAwaitTime: Long = 0L, + @Optional + private val maxAwaitTime: Long = 10000L, + @Optional + private val powValue: Double = 4.0, + @Optional + private val powK: Double = 0.0016 +) : RequestLimiter { + @Transient + private val scope = CoroutineScope( + Executors.newFixedThreadPool(3).asCoroutineDispatcher() + ) + @Transient + private val eventsChannel = Channel(Channel.UNLIMITED) + @Transient + private val awaitTimeRange = minAwaitTime .. maxAwaitTime + + init { + scope.launch { + var requestsInWork: Double = 0.0 + for (event in eventsChannel) { + when (event) { + is AddRequest -> { + val awaitTime = ((Math.pow(requestsInWork, powValue) * powK) * 1000L).toLong() + requestsInWork++ + + event.continuation.resume( + if (awaitTime in awaitTimeRange) { + awaitTime + } else { + if (awaitTime < minAwaitTime) { + minAwaitTime + } else { + maxAwaitTime + } + } + ) + } + is CompleteRequest -> requestsInWork-- + } + } + } + } + + override suspend fun limit( + block: suspend () -> T + ): T { + val delayMillis = suspendCoroutine { + scope.launch { eventsChannel.send(AddRequest(it)) } + } + delay(delayMillis) + return try { + block() + } finally { + eventsChannel.send(CompleteRequest) + } + } +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/RequestLimiter.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/RequestLimiter.kt new file mode 100644 index 0000000000..8abc707151 --- /dev/null +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/settings/limiters/RequestLimiter.kt @@ -0,0 +1,8 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters + +interface RequestLimiter { + /** + * Use limit for working of block (like delay between or after, for example) + */ + suspend fun limit(block: suspend () -> T): T +} diff --git a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt index d32001e0dd..ae0b4eb48d 100644 --- a/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt +++ b/src/main/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/GetMe.kt @@ -3,7 +3,9 @@ package com.github.insanusmokrassar.TelegramBotAPI.requests import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.SimpleRequest import com.github.insanusmokrassar.TelegramBotAPI.types.User import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +@Serializable class GetMe : SimpleRequest { override fun method(): String = "getMe" override fun resultSerializer(): KSerializer = User.serializer()