diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/multiserver/SimpleMultiServerRequestsExecutor.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/multiserver/SimpleMultiServerRequestsExecutor.kt new file mode 100644 index 0000000000..f4ae41b0dd --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/bot/multiserver/SimpleMultiServerRequestsExecutor.kt @@ -0,0 +1,210 @@ +package dev.inmo.tgbotapi.bot.multiserver + +import dev.inmo.tgbotapi.bot.Ktor.KtorRequestsExecutorBuilder +import dev.inmo.tgbotapi.bot.Ktor.telegramBot +import dev.inmo.tgbotapi.bot.RequestsExecutor +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.abstracts.Request +import dev.inmo.tgbotapi.utils.TelegramAPIUrlsKeeper +import dev.inmo.tgbotapi.utils.telegramBotAPIDefaultUrl +import kotlinx.coroutines.* + +/** + * This type of [RequestsExecutor] (aka [TelegramBot]) has been created to aggregate several bots under the hood and make + * requests, for example, with changing of api url + * + * @param bots Bots which will be used to [execute] [Request]s + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one [execute]. By default, will select next + * [TelegramBot] when called (or first when current index is last or -1) + * @param onClose This method will be called inside of [close] method. By default, will close all [bots] + */ +class SimpleMultiServerRequestsExecutor( + private val bots: List, + private val botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + private val onClose: () -> Unit = { + bots.forEach(TelegramBot::close) + } +) : RequestsExecutor { + override suspend fun execute(request: Request): T { + var currentBot = bots.botSelector(-1, null) + while (currentCoroutineContext().isActive) { + val i = bots.indexOf(currentBot) + runCatching { + currentBot.execute(request) + }.onSuccess { + return it + }.onFailure { + currentBot = bots.botSelector(i, it) + } + } + error("Coroutine has been terminated") + } + + override fun close() { + onClose() + } + + companion object { + /** + * @param keepers Will be used to create result [bots] + * @param builder Will be called with each item from [keepers] in process of bots creation + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one [execute]. By default, will select next + * [TelegramBot] when called (or first when current index is last or -1) + * @param onClose This method will be called inside of [close] method. By default, will close all [bots] + */ + inline operator fun invoke( + keepers: Iterable, + crossinline builder: KtorRequestsExecutorBuilder.(TelegramAPIUrlsKeeper) -> Unit = {}, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: ((List) -> Unit)? = null + ): SimpleMultiServerRequestsExecutor { + val bots = keepers.map { telegramBot(it) { builder(it) } } + return if (onClose == null) { + SimpleMultiServerRequestsExecutor(bots, botSelector) + } else { + SimpleMultiServerRequestsExecutor(bots, botSelector) { + onClose(bots) + } + } + } + + /** + * @param tokens Will be used to create result [bots] + * @param builder Will be called with each item from [tokens] in process of bots creation + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one [execute]. By default, will select next + * [TelegramBot] when called (or first when current index is last or -1) + * @param onClose This method will be called inside of [close] method. By default, will close all [bots] + */ + inline operator fun invoke( + tokens: Iterable, + crossinline builder: KtorRequestsExecutorBuilder.(String) -> Unit = {}, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: ((List) -> Unit)? = null + ): SimpleMultiServerRequestsExecutor { + val bots = tokens.map { telegramBot(it) { builder(it) } } + return if (onClose == null) { + SimpleMultiServerRequestsExecutor(bots, botSelector) + } else { + SimpleMultiServerRequestsExecutor(bots, botSelector) { + onClose(bots) + } + } + } + + /** + * @param tokens [Pair]s with first parameter as a token and second parameter api url. Will be used to create + * result [bots] + * @param builder Will be called with each item from [tokens] in process of bots creation + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one [execute]. By default, will select next + * [TelegramBot] when called (or first when current index is last or -1) + * @param onClose This method will be called inside of [close] method. By default, will close all [bots] + */ + inline operator fun invoke( + tokens: Iterable>, + crossinline builder: KtorRequestsExecutorBuilder.(Pair) -> Unit = {}, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: ((List) -> Unit)? = null + ): SimpleMultiServerRequestsExecutor { + val bots = tokens.map { telegramBot(it.first, it.second) { builder(it) } } + return if (onClose == null) { + SimpleMultiServerRequestsExecutor(bots, botSelector) + } else { + SimpleMultiServerRequestsExecutor(bots, botSelector) { + onClose(bots) + } + } + } + } +} + + +/** + * Creates [SimpleMultiServerRequestsExecutor] + * + * @param bots Bots which will be used to [SimpleMultiServerRequestsExecutor.execute] [Request]s + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one + * [SimpleMultiServerRequestsExecutor.execute]. By default, will select next [TelegramBot] when called (or first when + * current index is last or -1) + * @param onClose This method will be called inside of [SimpleMultiServerRequestsExecutor.close] method. By default, will close all [bots] + */ +@Suppress("NOTHING_TO_INLINE") +inline fun telegramBot( + bots: List, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: () -> Unit = { + bots.forEach(TelegramBot::close) + } +): TelegramBot = SimpleMultiServerRequestsExecutor(bots, botSelector, onClose) + +/** + * @param keepers Will be used to create result [SimpleMultiServerRequestsExecutor.bots] + * @param builder Will be called with each item from [keepers] in process of bots creation + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one + * [SimpleMultiServerRequestsExecutor.execute]. By default, will select next [TelegramBot] when called (or first when + * current index is last or -1) + * @param onClose This method will be called inside of [SimpleMultiServerRequestsExecutor.close] method. By default, + * will close all [SimpleMultiServerRequestsExecutor.bots] + */ +inline fun telegramBot( + keepers: Iterable, + crossinline builder: KtorRequestsExecutorBuilder.(TelegramAPIUrlsKeeper) -> Unit = {}, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: ((List) -> Unit)? = null +): TelegramBot = SimpleMultiServerRequestsExecutor(keepers, builder, botSelector, onClose) + +/** + * @param tokens Will be used to create result [SimpleMultiServerRequestsExecutor.bots] + * @param builder Will be called with each item from [tokens] in process of bots creation + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one + * [SimpleMultiServerRequestsExecutor.execute]. By default, will select next [TelegramBot] when called (or first when + * current index is last or -1) + * @param onClose This method will be called inside of [SimpleMultiServerRequestsExecutor.close] method. By default, + * will close all [SimpleMultiServerRequestsExecutor.bots] + */ +inline fun telegramBot( + tokens: Iterable, + crossinline builder: KtorRequestsExecutorBuilder.(String) -> Unit = {}, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: ((List) -> Unit)? = null +): TelegramBot = SimpleMultiServerRequestsExecutor(tokens, builder, botSelector, onClose) + +/** + * @param tokens [Pair]s with first parameter as a token and second parameter api url. Will be used to create + * result [SimpleMultiServerRequestsExecutor.bots] + * @param builder Will be called with each item from [tokens] in process of bots creation + * @param botSelector It is strategy by which the bot is selected for the execution. This lambda will receive **-1** + * when request execution just started and this call is first in context of one + * [SimpleMultiServerRequestsExecutor.execute]. By default, will select next + * [TelegramBot] when called (or first when current index is last or -1) + * @param onClose This method will be called inside of [SimpleMultiServerRequestsExecutor.close] method. By default, + * will close all [SimpleMultiServerRequestsExecutor.bots] + */ +inline fun telegramBot( + tokens: Iterable>, + crossinline builder: KtorRequestsExecutorBuilder.(Pair) -> Unit = {}, + noinline botSelector: suspend List.(currentBotIndex: Int, t: Throwable?) -> TelegramBot = { i, _ -> + getOrElse(i + 1) { first() } + }, + noinline onClose: ((List) -> Unit)? = null +): TelegramBot = SimpleMultiServerRequestsExecutor(tokens, builder, botSelector, onClose)