From 768d00eedded03e7434059ca55d8cb846383f7f0 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 23 Aug 2020 21:43:58 +0600 Subject: [PATCH] total rework (by accident) --- CHANGELOG.md | 15 ++++ .../bot/Ktor/KtorCallFactory.kt | 12 +-- .../bot/Ktor/KtorRequestsExecutor.kt | 43 +++------- .../Ktor/base/AbstractRequestCallFactory.kt | 80 ++++++++++++------- .../base/DownloadFileRequestCallFactory.kt | 29 +++++++ .../Ktor/base/MultipartRequestCallFactory.kt | 6 +- .../bot/Ktor/base/SimpleRequestCallFactory.kt | 5 +- .../TelegramBotAPI/requests/DownloadFile.kt | 14 ++++ .../types/InputMedia/InputMediaVideo.kt | 7 +- .../extensions/api/DownloadFile.kt | 32 ++++++++ .../utils/shortcuts/MediaGroupsShortcuts.kt | 14 ++-- 11 files changed, 175 insertions(+), 82 deletions(-) create mode 100644 TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/DownloadFileRequestCallFactory.kt create mode 100644 TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/DownloadFile.kt create mode 100644 TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/DownloadFile.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c5519ab5..42fbeafba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,21 @@ * All deprecations from previous versions were removed * `TelegramBotAPI-core`: * Typealias `TelegramBot` was added + * Fully rebuilt `KtorCallFactory` interface to be able to handle custom answers from telegram bot api system + * New implementation of `KtorCallFactory` was added: `DownloadFileRequestCallFactory` + * `DownloadFile` request was added + * All included `KtorCallFactory` realizations (except of abstract) now are objects: + * `MultipartRequestCallFactory` + * `SimpleRequestCallFactory` +* `TelegramBotAPI-extensions-api`: + * Extensions `TelegramBot#downloadFile` were added +* `TelegramBotAPI-extensions-utils`: + * All extensions for media groups (except of `mediaGroupId`) have changed their context: `List` + -> `List>` + * `forwardInfo` + * `replyTo` + * `chat` + * `createResend` (several extensions) ## 0.27.0 diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorCallFactory.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorCallFactory.kt index 81f162173c..7b7b33e32b 100644 --- a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorCallFactory.kt +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorCallFactory.kt @@ -1,13 +1,15 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request +import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper import io.ktor.client.HttpClient -import io.ktor.client.statement.HttpStatement +import kotlinx.serialization.json.Json interface KtorCallFactory { - suspend fun prepareCall( + suspend fun makeCall( client: HttpClient, - baseUrl: String, - request: Request - ) : HttpStatement? + urlsKeeper: TelegramAPIUrlsKeeper, + request: Request, + jsonFormatter: Json + ): T? } diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt index 28bfff56d4..4c48ebc69b 100644 --- a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/KtorRequestsExecutor.kt @@ -1,21 +1,17 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor 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.Ktor.base.* import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.newRequestException 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.Response -import com.github.insanusmokrassar.TelegramBotAPI.types.RetryAfterError import com.github.insanusmokrassar.TelegramBotAPI.utils.* import io.ktor.client.HttpClient -import io.ktor.client.call.receive import io.ktor.client.features.* import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.readText -import kotlinx.coroutines.delay import kotlinx.serialization.json.Json class KtorRequestsExecutor( @@ -28,7 +24,7 @@ class KtorRequestsExecutor( ) : BaseRequestsExecutor(telegramAPIUrlsKeeper) { private val callsFactories: List = callsFactories.run { if (!excludeDefaultFactories) { - asSequence().plus(SimpleRequestCallFactory()).plus(MultipartRequestCallFactory()).toList() + this + listOf(SimpleRequestCallFactory, MultipartRequestCallFactory, DownloadFileRequestCallFactory) } else { this } @@ -57,39 +53,20 @@ class KtorRequestsExecutor( } ) { requestsLimiter.limit { - var statement: HttpStatement? = null - for (factory in callsFactories) { - statement = factory.prepareCall( + var result: T? = null + for (potentialFactory in callsFactories) { + result = potentialFactory.makeCall( client, - telegramAPIUrlsKeeper.commonAPIUrl, - request + telegramAPIUrlsKeeper, + request, + jsonFormatter ) - if (statement != null) { + if (result != null) { break } } - val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request") - val content = response.receive() - val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content) - - (responseObject.result?.let { - jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it) - } ?: responseObject.parameters?.let { - val error = it.error - if (error is RetryAfterError) { - delay(error.leftToRetry) - execute(request) - } else { - null - } - } ?: response.let { - throw newRequestException( - responseObject, - content, - "Can't get result object from $content" - ) - }) + result ?: error("Can't execute request: $request") } } } diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt index b7808a6784..1aff3d8a98 100644 --- a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/AbstractRequestCallFactory.kt @@ -1,55 +1,79 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorCallFactory +import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.newRequestException import com.github.insanusmokrassar.TelegramBotAPI.requests.GetUpdates import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request +import com.github.insanusmokrassar.TelegramBotAPI.types.Response +import com.github.insanusmokrassar.TelegramBotAPI.types.RetryAfterError +import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper import io.ktor.client.HttpClient +import io.ktor.client.call.receive import io.ktor.client.features.timeout import io.ktor.client.request.* -import io.ktor.client.statement.HttpStatement +import io.ktor.client.statement.HttpResponse import io.ktor.http.ContentType -import io.ktor.http.HttpMethod +import kotlinx.coroutines.delay +import kotlinx.serialization.json.Json import kotlin.collections.set abstract class AbstractRequestCallFactory : KtorCallFactory { private val methodsCache: MutableMap = mutableMapOf() - override suspend fun prepareCall( + override suspend fun makeCall( client: HttpClient, - baseUrl: String, - request: Request - ): HttpStatement? { - val preparedBody = prepareCallBody(client, baseUrl, request) ?: return null + urlsKeeper: TelegramAPIUrlsKeeper, + request: Request, + jsonFormatter: Json + ): T? { + val preparedBody = prepareCallBody(client, urlsKeeper, request) ?: return null - return HttpStatement( - HttpRequestBuilder().apply { - url( - methodsCache[request.method()] ?: "$baseUrl/${request.method()}".also { - methodsCache[request.method()] = it - } - ) - method = HttpMethod.Post - accept(ContentType.Application.Json) + client.post { + url( + methodsCache[request.method()] ?: "${urlsKeeper.commonAPIUrl}/${request.method()}".also { + methodsCache[request.method()] = it + } + ) + accept(ContentType.Application.Json) - if (request is GetUpdates) { - request.timeout ?.times(1000L) ?.let { customTimeoutMillis -> - if (customTimeoutMillis > 0) { - timeout { - requestTimeoutMillis = customTimeoutMillis - socketTimeoutMillis = customTimeoutMillis - } + if (request is GetUpdates) { + request.timeout?.times(1000L)?.let { customTimeoutMillis -> + if (customTimeoutMillis > 0) { + timeout { + requestTimeoutMillis = customTimeoutMillis + socketTimeoutMillis = customTimeoutMillis } } } + } - body = preparedBody - }, - client - ) + body = preparedBody + }.let { response -> + val content = response.receive() + val responseObject = jsonFormatter.decodeFromString(Response.serializer(), content) + + return (responseObject.result?.let { + jsonFormatter.decodeFromJsonElement(request.resultDeserializer, it) + } ?: responseObject.parameters?.let { + val error = it.error + if (error is RetryAfterError) { + delay(error.leftToRetry) + makeCall(client, urlsKeeper, request, jsonFormatter) + } else { + null + } + } ?: response.let { + throw newRequestException( + responseObject, + content, + "Can't get result object from $content" + ) + }) + } } protected abstract fun prepareCallBody( client: HttpClient, - baseUrl: String, + urlsKeeper: TelegramAPIUrlsKeeper, request: Request ): Any? } \ No newline at end of file diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/DownloadFileRequestCallFactory.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/DownloadFileRequestCallFactory.kt new file mode 100644 index 0000000000..31ac3f7c11 --- /dev/null +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/DownloadFileRequestCallFactory.kt @@ -0,0 +1,29 @@ +package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base + +import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorCallFactory +import com.github.insanusmokrassar.TelegramBotAPI.bot.RequestsExecutor +import com.github.insanusmokrassar.TelegramBotAPI.requests.DownloadFile +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request +import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper +import com.github.insanusmokrassar.TelegramBotAPI.utils.handleSafely +import io.ktor.client.HttpClient +import io.ktor.client.request.* +import io.ktor.client.statement.* +import io.ktor.http.HttpMethod +import kotlinx.serialization.json.Json + +object DownloadFileRequestCallFactory : KtorCallFactory { + override suspend fun makeCall( + client: HttpClient, + urlsKeeper: TelegramAPIUrlsKeeper, + request: Request, + jsonFormatter: Json + ): T? = (request as? DownloadFile) ?.let { + val fullUrl = "${urlsKeeper.fileBaseUrl}/${it.filePath}" + + return handleSafely { + @Suppress("UNCHECKED_CAST") + client.get(fullUrl) as T // always ByteArray + } + } +} diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/MultipartRequestCallFactory.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/MultipartRequestCallFactory.kt index fb5b7a7203..6ec311d1f4 100644 --- a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/MultipartRequestCallFactory.kt +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/MultipartRequestCallFactory.kt @@ -1,6 +1,7 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.* +import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper import com.github.insanusmokrassar.TelegramBotAPI.utils.mapWithCommonValues import io.ktor.client.HttpClient import io.ktor.client.request.forms.MultiPartFormDataContent @@ -8,11 +9,10 @@ import io.ktor.client.request.forms.formData import io.ktor.http.Headers import io.ktor.http.HttpHeaders -class MultipartRequestCallFactory : AbstractRequestCallFactory() { - +object MultipartRequestCallFactory : AbstractRequestCallFactory() { override fun prepareCallBody( client: HttpClient, - baseUrl: String, + urlsKeeper: TelegramAPIUrlsKeeper, request: Request ): Any? = (request as? MultipartRequest) ?.let { castedRequest -> MultiPartFormDataContent( diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/SimpleRequestCallFactory.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/SimpleRequestCallFactory.kt index b0e0982c20..4b21b32879 100644 --- a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/SimpleRequestCallFactory.kt +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/bot/Ktor/base/SimpleRequestCallFactory.kt @@ -1,14 +1,15 @@ package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.* +import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper import io.ktor.client.HttpClient import io.ktor.http.ContentType import io.ktor.http.content.TextContent -class SimpleRequestCallFactory : AbstractRequestCallFactory() { +object SimpleRequestCallFactory : AbstractRequestCallFactory() { override fun prepareCallBody( client: HttpClient, - baseUrl: String, + urlsKeeper: TelegramAPIUrlsKeeper, request: Request ): Any? = (request as? SimpleRequest) ?.let { _ -> val content = request.json().toString() diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/DownloadFile.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/DownloadFile.kt new file mode 100644 index 0000000000..28787e39f0 --- /dev/null +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/requests/DownloadFile.kt @@ -0,0 +1,14 @@ +package com.github.insanusmokrassar.TelegramBotAPI.requests + +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.builtins.ByteArraySerializer + +class DownloadFile( + val filePath: String +) : Request { + override fun method(): String = filePath + + override val resultDeserializer: DeserializationStrategy + get() = ByteArraySerializer() +} diff --git a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/InputMedia/InputMediaVideo.kt b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/InputMedia/InputMediaVideo.kt index 7158dc3597..c89573979d 100644 --- a/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/InputMedia/InputMediaVideo.kt +++ b/TelegramBotAPI-core/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/types/InputMedia/InputMediaVideo.kt @@ -24,13 +24,12 @@ data class InputMediaVideo( override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this) - - @Transient - override val arguments: JsonElement = buildArguments(serializer()) - @SerialName(mediaField) val media: String = when (file) { is FileId -> file.fileId is MultipartFile -> file.fileId.toInputMediaFileAttachmentName() } + + @Transient + override val arguments: JsonElement = buildArguments(serializer()) } diff --git a/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/DownloadFile.kt b/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/DownloadFile.kt new file mode 100644 index 0000000000..2f9e6debf9 --- /dev/null +++ b/TelegramBotAPI-extensions-api/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/api/DownloadFile.kt @@ -0,0 +1,32 @@ +package com.github.insanusmokrassar.TelegramBotAPI.extensions.api + +import com.github.insanusmokrassar.TelegramBotAPI.bot.TelegramBot +import com.github.insanusmokrassar.TelegramBotAPI.extensions.api.get.getFileAdditionalInfo +import com.github.insanusmokrassar.TelegramBotAPI.requests.DownloadFile +import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.FileId +import com.github.insanusmokrassar.TelegramBotAPI.types.files.PathedFile +import com.github.insanusmokrassar.TelegramBotAPI.types.files.abstracts.TelegramMediaFile + +suspend fun TelegramBot.downloadFile( + filePath: String +): ByteArray = execute( + DownloadFile(filePath) +) + +suspend fun TelegramBot.downloadFile( + pathedFile: PathedFile +): ByteArray = execute( + DownloadFile(pathedFile.filePath) +) + +suspend fun TelegramBot.downloadFile( + fileId: FileId +): ByteArray = downloadFile( + getFileAdditionalInfo(fileId) +) + +suspend fun TelegramBot.downloadFile( + file: TelegramMediaFile +): ByteArray = downloadFile( + getFileAdditionalInfo(file) +) diff --git a/TelegramBotAPI-extensions-utils/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/utils/shortcuts/MediaGroupsShortcuts.kt b/TelegramBotAPI-extensions-utils/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/utils/shortcuts/MediaGroupsShortcuts.kt index 208a7904b9..89756971df 100644 --- a/TelegramBotAPI-extensions-utils/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/utils/shortcuts/MediaGroupsShortcuts.kt +++ b/TelegramBotAPI-extensions-utils/src/commonMain/kotlin/com/github/insanusmokrassar/TelegramBotAPI/extensions/utils/shortcuts/MediaGroupsShortcuts.kt @@ -4,15 +4,15 @@ import com.github.insanusmokrassar.TelegramBotAPI.requests.send.media.SendMediaG import com.github.insanusmokrassar.TelegramBotAPI.types.* import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat import com.github.insanusmokrassar.TelegramBotAPI.types.message.ForwardInfo -import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.MediaGroupMessage -import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Message +import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.* +import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.MediaGroupContent import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.SentMediaGroupUpdate -val List.forwardInfo: ForwardInfo? +val List>.forwardInfo: ForwardInfo? get() = firstOrNull() ?.forwardInfo -val List.replyTo: Message? +val List>.replyTo: Message? get() = firstOrNull() ?.replyTo -val List.chat: Chat? +val List>.chat: Chat? get() = firstOrNull() ?.chat val List.mediaGroupId: MediaGroupIdentifier? get() = firstOrNull() ?.mediaGroupId @@ -26,7 +26,7 @@ val SentMediaGroupUpdate.chat: Chat val SentMediaGroupUpdate.mediaGroupId: MediaGroupIdentifier get() = data.mediaGroupId!! -fun List.createResend( +fun List>.createResend( chatId: ChatId, disableNotification: Boolean = false, replyTo: MessageIdentifier? = null @@ -37,7 +37,7 @@ fun List.createResend( replyTo ) -fun List.createResend( +fun List>.createResend( chat: Chat, disableNotification: Boolean = false, replyTo: MessageIdentifier? = null