diff --git a/CHANGELOG.md b/CHANGELOG.md index db171d0018..b4f52bd608 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # TelegramBotAPI changelog +## 18.2.3 + +* `Core`: + * Add default middleware `ExceptionsThrottlerTelegramBotMiddleware` + * Make `TelegramBotMiddlewaresPipelinesHandler` to be default `TelegramBotPipelinesHandler` + * Make `DefaultKtorRequestsExecutor` now uses `runCatching` instead of `runCatchingSafely` + * `onRequestResultPresented` lambda now accepts non-nullable `result` + ## 18.2.2 * `Version`: diff --git a/gradle.properties b/gradle.properties index f5eaaed081..1a59b3fbcd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ kotlin.incremental=true kotlin.incremental.js=true library_group=dev.inmo -library_version=18.2.2 +library_version=18.2.3 diff --git a/tgbotapi.core/api/tgbotapi.core.api b/tgbotapi.core/api/tgbotapi.core.api index af39ae2f2b..0003b914a7 100644 --- a/tgbotapi.core/api/tgbotapi.core.api +++ b/tgbotapi.core/api/tgbotapi.core.api @@ -426,11 +426,12 @@ public final class dev/inmo/tgbotapi/bot/ktor/base/SimpleRequestCallFactory : de public synthetic fun (Ldev/inmo/kslog/common/KSLog;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } -public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware : dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler { +public class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware : dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler { public static final field Companion Ldev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware$Companion; public fun ()V - public fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;)V - public synthetic fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Ljava/lang/String;)V + public synthetic fun (Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function5;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function4;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getId ()Ljava/lang/String; public fun onAfterCallFactoryMakeCall (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -459,6 +460,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareB public final fun doOnRequestResultAbsent (Lkotlin/jvm/functions/Function3;)V public final fun doOnRequestResultPresented (Lkotlin/jvm/functions/Function5;)V public final fun doOnRequestReturnResult (Lkotlin/jvm/functions/Function4;)V + public final fun getId ()Ljava/lang/String; public final fun getOnAfterCallFactoryMakeCall ()Lkotlin/jvm/functions/Function4; public final fun getOnBeforeCallFactoryMakeCall ()Lkotlin/jvm/functions/Function3; public final fun getOnBeforeSearchCallFactory ()Lkotlin/jvm/functions/Function3; @@ -466,6 +468,7 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareB public final fun getOnRequestResultAbsent ()Lkotlin/jvm/functions/Function3; public final fun getOnRequestResultPresented ()Lkotlin/jvm/functions/Function5; public final fun getOnRequestReturnResult ()Lkotlin/jvm/functions/Function4; + public final fun setId (Ljava/lang/String;)V public final fun setOnAfterCallFactoryMakeCall (Lkotlin/jvm/functions/Function4;)V public final fun setOnBeforeCallFactoryMakeCall (Lkotlin/jvm/functions/Function3;)V public final fun setOnBeforeSearchCallFactory (Lkotlin/jvm/functions/Function3;)V @@ -481,7 +484,9 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareB public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler : dev/inmo/tgbotapi/bot/ktor/TelegramBotPipelinesHandler { public static final field Companion Ldev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler$Companion; + public fun ()V public fun (Ljava/util/List;)V + public synthetic fun (Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun onAfterCallFactoryMakeCall (Ljava/lang/Object;Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeCallFactoryMakeCall (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ldev/inmo/tgbotapi/bot/ktor/KtorCallFactory;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onBeforeSearchCallFactory (Ldev/inmo/tgbotapi/requests/abstracts/Request;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -502,6 +507,13 @@ public final class dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewares public final fun build (Lkotlin/jvm/functions/Function1;)Ldev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler; } +public final class dev/inmo/tgbotapi/bot/ktor/middlewares/builtins/ExceptionsThrottlerTelegramBotMiddleware { + public static final field INSTANCE Ldev/inmo/tgbotapi/bot/ktor/middlewares/builtins/ExceptionsThrottlerTelegramBotMiddleware; + public static final field id Ljava/lang/String; + public final fun invoke-HG0u8IE (FJ)Ldev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware; + public static synthetic fun invoke-HG0u8IE$default (Ldev/inmo/tgbotapi/bot/ktor/middlewares/builtins/ExceptionsThrottlerTelegramBotMiddleware;FJILjava/lang/Object;)Ldev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware; +} + public final class dev/inmo/tgbotapi/bot/multiserver/SimpleMultiServerRequestsExecutor : dev/inmo/tgbotapi/bot/RequestsExecutor { public static final field Companion Ldev/inmo/tgbotapi/bot/multiserver/SimpleMultiServerRequestsExecutor$Companion; public fun (Ljava/util/List;Lkotlin/jvm/functions/Function4;Lkotlin/jvm/functions/Function0;)V 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 ddba388517..c23d52d6af 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 @@ -2,6 +2,7 @@ package dev.inmo.tgbotapi.bot.ktor import dev.inmo.kslog.common.KSLog import dev.inmo.tgbotapi.bot.BaseRequestsExecutor +import dev.inmo.tgbotapi.bot.ktor.middlewares.TelegramBotMiddlewaresPipelinesHandler import dev.inmo.tgbotapi.bot.settings.limiters.ExceptionsOnlyLimiter import dev.inmo.tgbotapi.bot.settings.limiters.RequestLimiter import dev.inmo.tgbotapi.requests.abstracts.Request @@ -39,7 +40,7 @@ fun KtorRequestsExecutor( excludeDefaultFactories: Boolean = false, requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter, jsonFormatter: Json = nonstrictJsonFormat, - pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotPipelinesHandler, + pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotMiddlewaresPipelinesHandler(), logger: KSLog = DefaultKTgBotAPIKSLog, ) = KtorRequestsExecutor( telegramAPIUrlsKeeper = telegramAPIUrlsKeeper, diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/KtorRequestsExecutorFactories.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/KtorRequestsExecutorFactories.kt index 8b46427622..768d99fcb6 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/KtorRequestsExecutorFactories.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/KtorRequestsExecutorFactories.kt @@ -27,7 +27,7 @@ class KtorRequestsExecutorBuilder( var requestsLimiter: RequestLimiter = ExceptionsOnlyLimiter var jsonFormatter: Json = nonstrictJsonFormat var logger: KSLog = DefaultKTgBotAPIKSLog - var pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotPipelinesHandler + var pipelineStepsHolder: TelegramBotPipelinesHandler = TelegramBotMiddlewaresPipelinesHandler() fun includeMiddlewares(block: TelegramBotMiddlewaresPipelinesHandler.Builder.() -> Unit) { pipelineStepsHolder = TelegramBotMiddlewaresPipelinesHandler.build(block) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt index e3966257f3..bd29dc2ee6 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/base/DefaultKtorRequestsExecutor.kt @@ -47,7 +47,7 @@ class DefaultKtorRequestsExecutor internal constructor( } override suspend fun execute(request: Request): T { - return runCatchingSafely { + return runCatching { logger.v { "Start request $request" } pipelineStepsHolder.onBeforeSearchCallFactory(request, callsFactories) requestsLimiter.limit(request) { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt index 981742d6c7..a4221a362f 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddleware.kt @@ -1,5 +1,6 @@ package dev.inmo.tgbotapi.bot.ktor.middlewares +import com.benasher44.uuid.uuid4 import dev.inmo.micro_utils.common.Warning import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler @@ -22,14 +23,15 @@ import dev.inmo.tgbotapi.requests.abstracts.Request * Non-null result of lambda will be used as the result of request handling */ @Warning("This API is experimental and subject of changes") -class TelegramBotMiddleware( +open class TelegramBotMiddleware( internal val onRequestException: (suspend (request: Request<*>, t: Throwable?) -> Any?)? = null, internal val onBeforeSearchCallFactory: (suspend (request: Request<*>, callsFactories: List) -> Unit)? = null, internal val onBeforeCallFactoryMakeCall: (suspend (request: Request<*>, potentialFactory: KtorCallFactory) -> Unit)? = null, internal val onAfterCallFactoryMakeCall: (suspend (result: Any?, request: Request<*>, potentialFactory: KtorCallFactory) -> Any?)? = null, - internal val onRequestResultPresented: (suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?)? = null, + internal val onRequestResultPresented: (suspend (result: Any, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?)? = null, internal val onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List) -> Any?)? = null, internal val onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List) -> Result?)? = null, + val id: String = uuid4().toString() ) : TelegramBotPipelinesHandler { object ResultAbsence : Throwable() override suspend fun onRequestException(request: Request, t: Throwable): T? { diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt index 94c9bdd875..83c64bb8ac 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewareBuilder.kt @@ -1,5 +1,6 @@ package dev.inmo.tgbotapi.bot.ktor.middlewares +import com.benasher44.uuid.uuid4 import dev.inmo.micro_utils.common.Warning import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler @@ -11,9 +12,10 @@ class TelegramBotMiddlewareBuilder { var onBeforeSearchCallFactory: (suspend (request: Request<*>, callsFactories: List) -> Unit)? = null var onBeforeCallFactoryMakeCall: (suspend (request: Request<*>, potentialFactory: KtorCallFactory) -> Unit)? = null var onAfterCallFactoryMakeCall: (suspend (result: Any?, request: Request<*>, potentialFactory: KtorCallFactory) -> Any?)? = null - var onRequestResultPresented: (suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?)? = null + var onRequestResultPresented: (suspend (result: Any, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?)? = null var onRequestResultAbsent: (suspend (request: Request<*>, callsFactories: List) -> Any?)? = null var onRequestReturnResult: (suspend (result: Result<*>, request: Request<*>, callsFactories: List) -> Result?)? = null + var id: String = uuid4().toString() /** * Useful way to set [onRequestException] @@ -42,7 +44,7 @@ class TelegramBotMiddlewareBuilder { /** * Useful way to set [onRequestResultPresented] */ - fun doOnRequestResultPresented(block: suspend (result: Any?, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?) { + fun doOnRequestResultPresented(block: suspend (result: Any, request: Request<*>, resultCallFactory: KtorCallFactory, callsFactories: List) -> Any?) { onRequestResultPresented = block } /** @@ -67,7 +69,8 @@ class TelegramBotMiddlewareBuilder { onAfterCallFactoryMakeCall = onAfterCallFactoryMakeCall, onRequestResultPresented = onRequestResultPresented, onRequestResultAbsent = onRequestResultAbsent, - onRequestReturnResult = onRequestReturnResult + onRequestReturnResult = onRequestReturnResult, + id = id ) } @@ -82,6 +85,7 @@ class TelegramBotMiddlewareBuilder { onRequestResultPresented = middleware.onRequestResultPresented onRequestResultAbsent = middleware.onRequestResultAbsent onRequestReturnResult = middleware.onRequestReturnResult + id = middleware.id additionalSetup() }.build() } diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt index fbcc976d2f..37818510d1 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/TelegramBotMiddlewaresPipelinesHandler.kt @@ -3,11 +3,12 @@ package dev.inmo.tgbotapi.bot.ktor.middlewares import dev.inmo.micro_utils.common.Warning import dev.inmo.tgbotapi.bot.ktor.KtorCallFactory import dev.inmo.tgbotapi.bot.ktor.TelegramBotPipelinesHandler +import dev.inmo.tgbotapi.bot.ktor.middlewares.builtins.ExceptionsThrottlerTelegramBotMiddleware import dev.inmo.tgbotapi.requests.abstracts.Request @Warning("This API is experimental and subject of changes") class TelegramBotMiddlewaresPipelinesHandler( - private val middlewares: List + private val middlewares: List = emptyList() ) : TelegramBotPipelinesHandler { override suspend fun onRequestException(request: Request, t: Throwable): T? { return middlewares.firstNotNullOfOrNull { @@ -72,6 +73,7 @@ class TelegramBotMiddlewaresPipelinesHandler( @Warning("This API is experimental and subject of changes") class Builder { + @Warning("This API is experimental and subject of changes") val middlewares = mutableListOf() @Warning("This API is experimental and subject of changes") diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/builtins/ExceptionsThrottlerTelegramBotMiddleware.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/builtins/ExceptionsThrottlerTelegramBotMiddleware.kt new file mode 100644 index 0000000000..e450912bfb --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/ktor/middlewares/builtins/ExceptionsThrottlerTelegramBotMiddleware.kt @@ -0,0 +1,59 @@ +package dev.inmo.tgbotapi.bot.ktor.middlewares.builtins + +import dev.inmo.tgbotapi.bot.ktor.middlewares.TelegramBotMiddleware +import dev.inmo.tgbotapi.requests.abstracts.Request +import korlibs.time.milliseconds +import kotlinx.coroutines.delay +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.reflect.KClass +import kotlin.time.Duration + +/** + * @see invoke + */ +object ExceptionsThrottlerTelegramBotMiddleware { + const val id: String = "ExceptionsThrottlerTelegramBotMiddleware" + + /** + * Creates [TelegramBotMiddleware] and configures it with next parameters: + * + * * [TelegramBotMiddleware.onRequestException] will throttle after exception if exception has happened before + * * [TelegramBotMiddleware.onRequestReturnResult] will clear state of all exceptions happened with the [Request] if its + * handling has been completed successfully + */ + operator fun invoke( + exceptionDurationMultiplier: Float = 2f, + initialExceptionDuration: Duration = 125.milliseconds, + ): TelegramBotMiddleware = TelegramBotMiddleware.build { + val exceptionsTimeouts = mutableMapOf, Duration>() + val latestExceptionsRequestsTypes = mutableMapOf, MutableSet>>() + val mutex = Mutex() + onRequestException = onRequestException@{ request, t -> + t ?: return@onRequestException null + val kclass = t::class + val toSleep = mutex.withLock { + val latestDuration = exceptionsTimeouts[kclass] + exceptionsTimeouts[kclass] = latestDuration ?.times(exceptionDurationMultiplier.toDouble()) ?: initialExceptionDuration + latestExceptionsRequestsTypes.getOrPut(request::class) { mutableSetOf() }.add(kclass) + latestDuration + } + toSleep ?.let { + delay(it) + } + null + } + onRequestReturnResult = onRequestReturnResult@{ result, request, _ -> + if (result.isSuccess) { + mutex.withLock { + val exceptionKClass = latestExceptionsRequestsTypes.remove(request::class) ?: return@withLock + exceptionKClass.forEach { + exceptionsTimeouts.remove(it) + } + } + } + null + } + id = ExceptionsThrottlerTelegramBotMiddleware.id + } +}