Merge pull request #10 from InsanusMokrassar/0.9.0

0.9.0
This commit is contained in:
InsanusMokrassar 2019-01-26 13:18:16 +08:00 committed by GitHub
commit 2dda103d51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 197 additions and 72 deletions

View File

@ -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`)

View File

@ -1,4 +1,4 @@
project.version = "0.8.5"
project.version = "0.9.0"
project.group = "com.github.insanusmokrassar"
buildscript {

View File

@ -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

View File

@ -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"
}

View File

@ -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()
)
}

View File

@ -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<KtorCallFactory> = 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 <T : Any> execute(request: Request<T>): 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"
)
}
}
}
}

View File

@ -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()
}
}
}

View File

@ -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)

View File

@ -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
)

View File

@ -0,0 +1,5 @@
package com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters
object EmptyLimiter : RequestLimiter {
override suspend fun <T> limit(block: suspend () -> T): T = block()
}

View File

@ -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<Long>
) : 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<RequestEvent>(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 <T> limit(
block: suspend () -> T
): T {
val delayMillis = suspendCoroutine<Long> {
scope.launch { eventsChannel.send(AddRequest(it)) }
}
delay(delayMillis)
return try {
block()
} finally {
eventsChannel.send(CompleteRequest)
}
}
}

View File

@ -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 <T> limit(block: suspend () -> T): T
}

View File

@ -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<User> {
override fun method(): String = "getMe"
override fun resultSerializer(): KSerializer<User> = User.serializer()