mirror of
https://github.com/InsanusMokrassar/TelegramBotAPI.git
synced 2024-11-26 03:58:44 +00:00
commit
1ce2526401
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,5 +1,19 @@
|
|||||||
# TelegramBotAPI changelog
|
# TelegramBotAPI changelog
|
||||||
|
|
||||||
|
## 0.30.7
|
||||||
|
|
||||||
|
* `Common`:
|
||||||
|
* `Version`:
|
||||||
|
* `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
|
||||||
|
* 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
|
## 0.30.6
|
||||||
|
|
||||||
* `Core`
|
* `Core`
|
||||||
|
@ -12,12 +12,12 @@ klock_version=1.12.1
|
|||||||
uuid_version=0.2.2
|
uuid_version=0.2.2
|
||||||
ktor_version=1.4.2
|
ktor_version=1.4.2
|
||||||
|
|
||||||
micro_utils_version=0.4.0
|
micro_utils_version=0.4.1
|
||||||
|
|
||||||
javax_activation_version=1.1.1
|
javax_activation_version=1.1.1
|
||||||
|
|
||||||
library_group=dev.inmo
|
library_group=dev.inmo
|
||||||
library_version=0.30.6
|
library_version=0.30.7
|
||||||
|
|
||||||
gradle_bintray_plugin_version=1.8.5
|
gradle_bintray_plugin_version=1.8.5
|
||||||
github_release_plugin_version=2.2.12
|
github_release_plugin_version=2.2.12
|
||||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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
|
||||||
|
@ -3,6 +3,7 @@ package dev.inmo.tgbotapi.bot.Ktor
|
|||||||
import dev.inmo.micro_utils.coroutines.safely
|
import dev.inmo.micro_utils.coroutines.safely
|
||||||
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
import dev.inmo.tgbotapi.bot.BaseRequestsExecutor
|
||||||
import dev.inmo.tgbotapi.bot.Ktor.base.*
|
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.exceptions.newRequestException
|
||||||
import dev.inmo.tgbotapi.bot.settings.limiters.*
|
import dev.inmo.tgbotapi.bot.settings.limiters.*
|
||||||
import dev.inmo.tgbotapi.requests.abstracts.Request
|
import dev.inmo.tgbotapi.requests.abstracts.Request
|
||||||
@ -13,6 +14,33 @@ import io.ktor.client.features.*
|
|||||||
import io.ktor.client.statement.readText
|
import io.ktor.client.statement.readText
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
|
class KtorRequestsExecutorBuilder(
|
||||||
|
var telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
|
||||||
|
) {
|
||||||
|
var client: HttpClient = HttpClient()
|
||||||
|
var callsFactories: List<KtorCallFactory> = 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()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
class KtorRequestsExecutor(
|
||||||
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
|
||||||
client: HttpClient = HttpClient(),
|
client: HttpClient = HttpClient(),
|
||||||
|
@ -1,67 +1,43 @@
|
|||||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
package dev.inmo.tgbotapi.bot.settings.limiters
|
||||||
|
|
||||||
import com.soywiz.klock.DateTime
|
import com.soywiz.klock.DateTime
|
||||||
|
import dev.inmo.micro_utils.coroutines.*
|
||||||
|
import dev.inmo.tgbotapi.types.MilliSeconds
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
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()
|
private fun now(): Long = DateTime.nowUnixLong()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
class CommonLimiter(
|
class CommonLimiter(
|
||||||
private val lockCount: Int = 10,
|
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 {
|
) : RequestLimiter {
|
||||||
private var doLimit: Boolean = false
|
private val quotaSemaphore = Semaphore(lockCount)
|
||||||
|
private val counterRegeneratorJob = scope.launch {
|
||||||
private val counterChannel = Channel<Unit>(Channel.UNLIMITED)
|
val regenDelay: MilliSeconds = (regenTime.toDouble() / lockCount).roundToLong()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (now > removeLimitTime) {
|
|
||||||
limitManagementJob = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val quoterChannel = Channel<Unit>(Channel.CONFLATED)
|
|
||||||
private val tickerJob = scope.launch {
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
quoterChannel.send(Unit)
|
delay(regenDelay)
|
||||||
delay(1000L)
|
if (quotaSemaphore.availablePermits < lockCount) {
|
||||||
|
try {
|
||||||
|
quotaSemaphore.release()
|
||||||
|
} catch (_: IllegalStateException) {
|
||||||
|
// Skip IllegalStateException due to the fact that this exception may happens in release method
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <T> limit(block: suspend () -> T): T {
|
override suspend fun <T> limit(block: suspend () -> T): T {
|
||||||
counterChannel.send(Unit)
|
quotaSemaphore.acquire()
|
||||||
return if (!doLimit) {
|
return block()
|
||||||
block()
|
|
||||||
} else {
|
|
||||||
quoterChannel.receive()
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package dev.inmo.tgbotapi.bot.settings.limiters
|
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.*
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@ -9,42 +11,35 @@ import kotlin.math.pow
|
|||||||
|
|
||||||
private sealed class RequestEvent
|
private sealed class RequestEvent
|
||||||
private class AddRequest(
|
private class AddRequest(
|
||||||
val continuation: Continuation<Long>
|
val continuation: Continuation<MilliSeconds>
|
||||||
) : RequestEvent()
|
) : RequestEvent()
|
||||||
private object CompleteRequest : RequestEvent()
|
private object CompleteRequest : RequestEvent()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PowLimiter(
|
data class PowLimiter(
|
||||||
private val minAwaitTime: Long = 0L,
|
private val minAwaitTime: MilliSeconds = 0L,
|
||||||
private val maxAwaitTime: Long = 10000L,
|
private val maxAwaitTime: MilliSeconds = 10000L,
|
||||||
private val powValue: Double = 4.0,
|
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 {
|
) : RequestLimiter {
|
||||||
@Transient
|
|
||||||
private val scope = CoroutineScope(Dispatchers.Default)
|
|
||||||
@Transient
|
|
||||||
private val eventsChannel = Channel<RequestEvent>(Channel.UNLIMITED)
|
|
||||||
@Transient
|
@Transient
|
||||||
private val awaitTimeRange = minAwaitTime .. maxAwaitTime
|
private val awaitTimeRange = minAwaitTime .. maxAwaitTime
|
||||||
|
@Transient
|
||||||
init {
|
private val eventsChannel = let {
|
||||||
scope.launch {
|
var requestsInWork = 0.0
|
||||||
var requestsInWork: Double = 0.0
|
scope.actor<RequestEvent> {
|
||||||
for (event in eventsChannel) {
|
when (it) {
|
||||||
when (event) {
|
|
||||||
is AddRequest -> {
|
is AddRequest -> {
|
||||||
val awaitTime = (((requestsInWork.pow(powValue) * powK) * 1000L).toLong())
|
val awaitTime = (requestsInWork.pow(powValue) * powK).toLong()
|
||||||
requestsInWork++
|
requestsInWork++
|
||||||
|
|
||||||
event.continuation.resume(
|
it.continuation.resume(
|
||||||
if (awaitTime in awaitTimeRange) {
|
when {
|
||||||
awaitTime
|
awaitTime in awaitTimeRange -> awaitTime
|
||||||
} else {
|
awaitTime < awaitTimeRange.first -> awaitTimeRange.first
|
||||||
if (awaitTime < minAwaitTime) {
|
else -> awaitTimeRange.last
|
||||||
minAwaitTime
|
|
||||||
} else {
|
|
||||||
maxAwaitTime
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -52,19 +47,24 @@ data class PowLimiter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun <T> limit(
|
private suspend inline fun <T> withDelay(
|
||||||
block: suspend () -> T
|
crossinline block: suspend () -> T
|
||||||
): T {
|
): T {
|
||||||
val delayMillis = suspendCoroutine<Long> {
|
val delayMillis = suspendCoroutine<Long> {
|
||||||
scope.launch { eventsChannel.send(AddRequest(it)) }
|
scope.launch { eventsChannel.send(AddRequest(it)) }
|
||||||
}
|
}
|
||||||
delay(delayMillis)
|
delay(delayMillis)
|
||||||
return try {
|
return try {
|
||||||
block()
|
safely { block() }
|
||||||
} finally {
|
} finally {
|
||||||
eventsChannel.send(CompleteRequest)
|
eventsChannel.send(CompleteRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> limit(
|
||||||
|
block: suspend () -> T
|
||||||
|
): T {
|
||||||
|
return withDelay(block)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,28 @@ package dev.inmo.tgbotapi.utils
|
|||||||
|
|
||||||
const val telegramBotAPIDefaultUrl = "https://api.telegram.org"
|
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(
|
class TelegramAPIUrlsKeeper(
|
||||||
token: String,
|
token: String,
|
||||||
hostUrl: String = telegramBotAPIDefaultUrl
|
hostUrl: String = telegramBotAPIDefaultUrl
|
||||||
) {
|
) {
|
||||||
val commonAPIUrl = "$hostUrl/bot$token"
|
val commonAPIUrl: String
|
||||||
val fileBaseUrl = "$hostUrl/file/bot$token"
|
val fileBaseUrl: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
val correctedHost = hostUrl.withoutLastSlash
|
||||||
|
commonAPIUrl = "$correctedHost/bot$token"
|
||||||
|
fileBaseUrl = "$correctedHost/file/bot$token"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package dev.inmo.tgbotapi.extensions.api
|
package dev.inmo.tgbotapi.extensions.api
|
||||||
|
|
||||||
import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutor
|
import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutorBuilder
|
||||||
import dev.inmo.tgbotapi.bot.TelegramBot
|
import dev.inmo.tgbotapi.bot.TelegramBot
|
||||||
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper
|
||||||
import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl
|
import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl
|
||||||
@ -11,11 +11,11 @@ import io.ktor.client.engine.*
|
|||||||
/**
|
/**
|
||||||
* Allows to create bot using bot [urlsKeeper]
|
* Allows to create bot using bot [urlsKeeper]
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Replaced in core", ReplaceWith("telegramBot", "dev.inmo.tgbotapi.bot.Ktor.telegramBot"))
|
||||||
fun telegramBot(
|
fun telegramBot(
|
||||||
urlsKeeper: TelegramAPIUrlsKeeper
|
urlsKeeper: TelegramAPIUrlsKeeper
|
||||||
): TelegramBot = KtorRequestsExecutor(
|
): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(
|
||||||
urlsKeeper,
|
urlsKeeper
|
||||||
HttpClient()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,10 +24,9 @@ fun telegramBot(
|
|||||||
fun telegramBot(
|
fun telegramBot(
|
||||||
urlsKeeper: TelegramAPIUrlsKeeper,
|
urlsKeeper: TelegramAPIUrlsKeeper,
|
||||||
client: HttpClient
|
client: HttpClient
|
||||||
): TelegramBot = KtorRequestsExecutor(
|
): TelegramBot = dev.inmo.tgbotapi.bot.Ktor.telegramBot(urlsKeeper) {
|
||||||
urlsKeeper,
|
this.client = client
|
||||||
client
|
}
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to create bot using bot [urlsKeeper] and specify [HttpClientEngineFactory] by passing [clientFactory] param and optionally
|
* Allows to create bot using bot [urlsKeeper] and specify [HttpClientEngineFactory] by passing [clientFactory] param and optionally
|
||||||
@ -73,11 +72,12 @@ inline fun telegramBot(
|
|||||||
/**
|
/**
|
||||||
* Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client]
|
* 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")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun telegramBot(
|
inline fun telegramBot(
|
||||||
token: String,
|
token: String,
|
||||||
apiUrl: String = telegramBotAPIDefaultUrl
|
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]
|
* Allows to create bot using bot [token], [apiUrl] (for custom api servers) and already prepared [client]
|
||||||
|
Loading…
Reference in New Issue
Block a user