1
0
mirror of https://github.com/InsanusMokrassar/TelegramBotAPI.git synced 2024-06-29 04:47:48 +00:00

Merge pull request #124 from sleshJdev/feature/download-pathed-file-extension

Extended PathedFile to get it as file/stream
This commit is contained in:
InsanusMokrassar 2020-08-23 21:44:53 +06:00 committed by GitHub
commit 0472e35752
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 204 additions and 82 deletions

View File

@ -21,6 +21,21 @@
* All deprecations from previous versions were removed * All deprecations from previous versions were removed
* `TelegramBotAPI-core`: * `TelegramBotAPI-core`:
* Typealias `TelegramBot` was added * 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<MediaGroupMessage>`
-> `List<CommonMessage<MediaGroupContent>>`
* `forwardInfo`
* `replyTo`
* `chat`
* `createResend` (several extensions)
## 0.27.0 ## 0.27.0

View File

@ -1,13 +1,15 @@
package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.statement.HttpStatement import kotlinx.serialization.json.Json
interface KtorCallFactory { interface KtorCallFactory {
suspend fun <T: Any> prepareCall( suspend fun <T: Any> makeCall(
client: HttpClient, client: HttpClient,
baseUrl: String, urlsKeeper: TelegramAPIUrlsKeeper,
request: Request<T> request: Request<T>,
) : HttpStatement? jsonFormatter: Json
): T?
} }

View File

@ -1,21 +1,17 @@
package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor
import com.github.insanusmokrassar.TelegramBotAPI.bot.BaseRequestsExecutor import com.github.insanusmokrassar.TelegramBotAPI.bot.BaseRequestsExecutor
import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base.MultipartRequestCallFactory import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base.*
import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base.SimpleRequestCallFactory
import com.github.insanusmokrassar.TelegramBotAPI.bot.exceptions.newRequestException 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.EmptyLimiter
import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters.RequestLimiter import com.github.insanusmokrassar.TelegramBotAPI.bot.settings.limiters.RequestLimiter
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request
import com.github.insanusmokrassar.TelegramBotAPI.types.Response import com.github.insanusmokrassar.TelegramBotAPI.types.Response
import com.github.insanusmokrassar.TelegramBotAPI.types.RetryAfterError
import com.github.insanusmokrassar.TelegramBotAPI.utils.* import com.github.insanusmokrassar.TelegramBotAPI.utils.*
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.receive
import io.ktor.client.features.* import io.ktor.client.features.*
import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.HttpStatement
import io.ktor.client.statement.readText import io.ktor.client.statement.readText
import kotlinx.coroutines.delay
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
class KtorRequestsExecutor( class KtorRequestsExecutor(
@ -28,7 +24,7 @@ class KtorRequestsExecutor(
) : BaseRequestsExecutor(telegramAPIUrlsKeeper) { ) : BaseRequestsExecutor(telegramAPIUrlsKeeper) {
private val callsFactories: List<KtorCallFactory> = callsFactories.run { private val callsFactories: List<KtorCallFactory> = callsFactories.run {
if (!excludeDefaultFactories) { if (!excludeDefaultFactories) {
asSequence().plus(SimpleRequestCallFactory()).plus(MultipartRequestCallFactory()).toList() this + listOf(SimpleRequestCallFactory, MultipartRequestCallFactory, DownloadFileRequestCallFactory)
} else { } else {
this this
} }
@ -57,39 +53,20 @@ class KtorRequestsExecutor(
} }
) { ) {
requestsLimiter.limit { requestsLimiter.limit {
var statement: HttpStatement? = null var result: T? = null
for (factory in callsFactories) { for (potentialFactory in callsFactories) {
statement = factory.prepareCall( result = potentialFactory.makeCall(
client, client,
telegramAPIUrlsKeeper.commonAPIUrl, telegramAPIUrlsKeeper,
request request,
jsonFormatter
) )
if (statement != null) { if (result != null) {
break break
} }
} }
val response = statement?.execute() ?: throw IllegalArgumentException("Can't execute request: $request") result ?: error("Can't execute request: $request")
val content = response.receive<String>()
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"
)
})
} }
} }
} }

View File

@ -1,37 +1,42 @@
package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base
import com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.KtorCallFactory 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.GetUpdates
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.Request 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.HttpClient
import io.ktor.client.call.receive
import io.ktor.client.features.timeout import io.ktor.client.features.timeout
import io.ktor.client.request.* 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.ContentType
import io.ktor.http.HttpMethod import kotlinx.coroutines.delay
import kotlinx.serialization.json.Json
import kotlin.collections.set import kotlin.collections.set
abstract class AbstractRequestCallFactory : KtorCallFactory { abstract class AbstractRequestCallFactory : KtorCallFactory {
private val methodsCache: MutableMap<String, String> = mutableMapOf() private val methodsCache: MutableMap<String, String> = mutableMapOf()
override suspend fun <T : Any> prepareCall( override suspend fun <T : Any> makeCall(
client: HttpClient, client: HttpClient,
baseUrl: String, urlsKeeper: TelegramAPIUrlsKeeper,
request: Request<T> request: Request<T>,
): HttpStatement? { jsonFormatter: Json
val preparedBody = prepareCallBody(client, baseUrl, request) ?: return null ): T? {
val preparedBody = prepareCallBody(client, urlsKeeper, request) ?: return null
return HttpStatement( client.post<HttpResponse> {
HttpRequestBuilder().apply {
url( url(
methodsCache[request.method()] ?: "$baseUrl/${request.method()}".also { methodsCache[request.method()] ?: "${urlsKeeper.commonAPIUrl}/${request.method()}".also {
methodsCache[request.method()] = it methodsCache[request.method()] = it
} }
) )
method = HttpMethod.Post
accept(ContentType.Application.Json) accept(ContentType.Application.Json)
if (request is GetUpdates) { if (request is GetUpdates) {
request.timeout ?.times(1000L) ?.let { customTimeoutMillis -> request.timeout?.times(1000L)?.let { customTimeoutMillis ->
if (customTimeoutMillis > 0) { if (customTimeoutMillis > 0) {
timeout { timeout {
requestTimeoutMillis = customTimeoutMillis requestTimeoutMillis = customTimeoutMillis
@ -42,14 +47,33 @@ abstract class AbstractRequestCallFactory : KtorCallFactory {
} }
body = preparedBody body = preparedBody
}, }.let { response ->
client val content = response.receive<String>()
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 <T : Any> prepareCallBody( protected abstract fun <T : Any> prepareCallBody(
client: HttpClient, client: HttpClient,
baseUrl: String, urlsKeeper: TelegramAPIUrlsKeeper,
request: Request<T> request: Request<T>
): Any? ): Any?
} }

View File

@ -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 <T : Any> makeCall(
client: HttpClient,
urlsKeeper: TelegramAPIUrlsKeeper,
request: Request<T>,
jsonFormatter: Json
): T? = (request as? DownloadFile) ?.let {
val fullUrl = "${urlsKeeper.fileBaseUrl}/${it.filePath}"
return handleSafely {
@Suppress("UNCHECKED_CAST")
client.get<ByteArray>(fullUrl) as T // always ByteArray
}
}
}

View File

@ -1,6 +1,7 @@
package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.* import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import com.github.insanusmokrassar.TelegramBotAPI.utils.mapWithCommonValues import com.github.insanusmokrassar.TelegramBotAPI.utils.mapWithCommonValues
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.forms.MultiPartFormDataContent 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.Headers
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
class MultipartRequestCallFactory : AbstractRequestCallFactory() { object MultipartRequestCallFactory : AbstractRequestCallFactory() {
override fun <T : Any> prepareCallBody( override fun <T : Any> prepareCallBody(
client: HttpClient, client: HttpClient,
baseUrl: String, urlsKeeper: TelegramAPIUrlsKeeper,
request: Request<T> request: Request<T>
): Any? = (request as? MultipartRequest) ?.let { castedRequest -> ): Any? = (request as? MultipartRequest) ?.let { castedRequest ->
MultiPartFormDataContent( MultiPartFormDataContent(

View File

@ -1,14 +1,15 @@
package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base package com.github.insanusmokrassar.TelegramBotAPI.bot.Ktor.base
import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.* import com.github.insanusmokrassar.TelegramBotAPI.requests.abstracts.*
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.http.ContentType import io.ktor.http.ContentType
import io.ktor.http.content.TextContent import io.ktor.http.content.TextContent
class SimpleRequestCallFactory : AbstractRequestCallFactory() { object SimpleRequestCallFactory : AbstractRequestCallFactory() {
override fun <T : Any> prepareCallBody( override fun <T : Any> prepareCallBody(
client: HttpClient, client: HttpClient,
baseUrl: String, urlsKeeper: TelegramAPIUrlsKeeper,
request: Request<T> request: Request<T>
): Any? = (request as? SimpleRequest<T>) ?.let { _ -> ): Any? = (request as? SimpleRequest<T>) ?.let { _ ->
val content = request.json().toString() val content = request.json().toString()

View File

@ -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<ByteArray> {
override fun method(): String = filePath
override val resultDeserializer: DeserializationStrategy<ByteArray>
get() = ByteArraySerializer()
}

View File

@ -24,13 +24,12 @@ data class InputMediaVideo(
override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this) override fun serialize(format: StringFormat): String = format.encodeToString(serializer(), this)
@Transient
override val arguments: JsonElement = buildArguments(serializer())
@SerialName(mediaField) @SerialName(mediaField)
val media: String = when (file) { val media: String = when (file) {
is FileId -> file.fileId is FileId -> file.fileId
is MultipartFile -> file.fileId.toInputMediaFileAttachmentName() is MultipartFile -> file.fileId.toInputMediaFileAttachmentName()
} }
@Transient
override val arguments: JsonElement = buildArguments(serializer())
} }

View File

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

View File

@ -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.*
import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat import com.github.insanusmokrassar.TelegramBotAPI.types.chat.abstracts.Chat
import com.github.insanusmokrassar.TelegramBotAPI.types.message.ForwardInfo 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.*
import com.github.insanusmokrassar.TelegramBotAPI.types.message.abstracts.Message import com.github.insanusmokrassar.TelegramBotAPI.types.message.content.abstracts.MediaGroupContent
import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.SentMediaGroupUpdate import com.github.insanusmokrassar.TelegramBotAPI.types.update.MediaGroupUpdates.SentMediaGroupUpdate
val List<MediaGroupMessage>.forwardInfo: ForwardInfo? val List<CommonMessage<MediaGroupContent>>.forwardInfo: ForwardInfo?
get() = firstOrNull() ?.forwardInfo get() = firstOrNull() ?.forwardInfo
val List<MediaGroupMessage>.replyTo: Message? val List<CommonMessage<MediaGroupContent>>.replyTo: Message?
get() = firstOrNull() ?.replyTo get() = firstOrNull() ?.replyTo
val List<MediaGroupMessage>.chat: Chat? val List<CommonMessage<MediaGroupContent>>.chat: Chat?
get() = firstOrNull() ?.chat get() = firstOrNull() ?.chat
val List<MediaGroupMessage>.mediaGroupId: MediaGroupIdentifier? val List<MediaGroupMessage>.mediaGroupId: MediaGroupIdentifier?
get() = firstOrNull() ?.mediaGroupId get() = firstOrNull() ?.mediaGroupId
@ -26,7 +26,7 @@ val SentMediaGroupUpdate.chat: Chat
val SentMediaGroupUpdate.mediaGroupId: MediaGroupIdentifier val SentMediaGroupUpdate.mediaGroupId: MediaGroupIdentifier
get() = data.mediaGroupId!! get() = data.mediaGroupId!!
fun List<MediaGroupMessage>.createResend( fun List<CommonMessage<MediaGroupContent>>.createResend(
chatId: ChatId, chatId: ChatId,
disableNotification: Boolean = false, disableNotification: Boolean = false,
replyTo: MessageIdentifier? = null replyTo: MessageIdentifier? = null
@ -37,7 +37,7 @@ fun List<MediaGroupMessage>.createResend(
replyTo replyTo
) )
fun List<MediaGroupMessage>.createResend( fun List<CommonMessage<MediaGroupContent>>.createResend(
chat: Chat, chat: Chat,
disableNotification: Boolean = false, disableNotification: Boolean = false,
replyTo: MessageIdentifier? = null replyTo: MessageIdentifier? = null

View File

@ -0,0 +1,29 @@
package com.github.insanusmokrassar.TelegramBotAPI.types.files
import com.github.insanusmokrassar.TelegramBotAPI.utils.TelegramAPIUrlsKeeper
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream
import java.net.URL
fun PathedFile.asStream(
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
): InputStream = URL(this.fullUrl(telegramAPIUrlsKeeper)).openStream()
fun PathedFile.asFile(
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper,
dest: File = File.createTempFile(this.fileUniqueId, this.filename),
defaultBufferSize: Int = DEFAULT_BUFFER_SIZE
): File {
this.asStream(telegramAPIUrlsKeeper).use { input ->
FileOutputStream(dest).use { out ->
input.copyTo(out, defaultBufferSize)
}
}
return dest
}
fun PathedFile.asBytes(
telegramAPIUrlsKeeper: TelegramAPIUrlsKeeper
): ByteArray = this.asStream(telegramAPIUrlsKeeper)
.use { input -> input.readBytes() }