diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 12fe622c56..f5379f8392 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.re microutils-serialization-base64 = { module = "dev.inmo:micro_utils.serialization.base64", version.ref = "microutils" } microutils-serialization-encapsulator = { module = "dev.inmo:micro_utils.serialization.encapsulator", version.ref = "microutils" } microutils-serialization-typedSerializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" } +microutils-serialization-mapper = { module = "dev.inmo:micro_utils.serialization.mapper", version.ref = "microutils" } microutils-languageCodes = { module = "dev.inmo:micro_utils.language_codes", version.ref = "microutils" } microutils-ktor-common = { module = "dev.inmo:micro_utils.ktor.common", version.ref = "microutils" } microutils-fsm-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" } diff --git a/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToFile.kt b/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToFile.kt index 05ed333073..1174d1162d 100644 --- a/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToFile.kt +++ b/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToFile.kt @@ -9,6 +9,7 @@ import dev.inmo.tgbotapi.types.files.TelegramMediaFile import dev.inmo.tgbotapi.types.message.content.MediaContent import io.ktor.util.cio.use import io.ktor.util.cio.writeChannel +import io.ktor.utils.io.copyAndClose import io.ktor.utils.io.copyTo import kotlinx.coroutines.job import java.io.File @@ -25,7 +26,7 @@ suspend fun TelegramBot.downloadFile( doOutsideOfCoroutine { destFile.createNewFile() } destFile.writeChannel(coroutineContext.job).use { - readChannel.copyTo(this) + readChannel.copyAndClose(this) } return destFile diff --git a/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToTempFile.kt b/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToTempFile.kt index 299e9d1ddd..5f8f6fd75f 100644 --- a/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToTempFile.kt +++ b/tgbotapi.api/src/jvmMain/kotlin/dev/inmo/tgbotapi/extensions/api/files/DownloadFileToTempFile.kt @@ -30,11 +30,17 @@ suspend fun TelegramBot.downloadFileToTemp( suspend fun TelegramBot.downloadFileToTemp( pathedFile: PathedFile -) = downloadFileToTemp( +): File = downloadFileToTemp( pathedFile.filePath -).apply { - runCatching { - renameTo(File(parentFile, "$nameWithoutExtension.${pathedFile.fileName.fileExtension}")) +).run { + val newFile = File(parentFile, "$nameWithoutExtension.${pathedFile.fileName.fileExtension}") + val success = runCatching { + renameTo(newFile) + }.getOrElse { false } + if (success) { + newFile + } else { + this@run } } diff --git a/tgbotapi.core/build.gradle b/tgbotapi.core/build.gradle index dac5a707a4..e8448b7c47 100644 --- a/tgbotapi.core/build.gradle +++ b/tgbotapi.core/build.gradle @@ -26,6 +26,7 @@ kotlin { api libs.microutils.serialization.base64 api libs.microutils.serialization.encapsulator api libs.microutils.serialization.typedSerializer + api libs.microutils.serialization.mapper api libs.microutils.ktor.common api libs.microutils.languageCodes diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/abstracts/InputFile.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/abstracts/InputFile.kt index afd6df3640..67b126c235 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/abstracts/InputFile.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/abstracts/InputFile.kt @@ -36,8 +36,10 @@ sealed class InputFile { } } +internal const val attachPrefix = "attach://" + internal inline val InputFile.attachFileId - get() = "attach://$fileId" + get() = "$attachPrefix$fileId" internal inline val InputFile.fileIdToSend get() = when (this) { is FileId -> fileId @@ -60,8 +62,8 @@ fun String.toInputFile() = FileId(this) @RiskFeature object InputFileSerializer : KSerializer { override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(FileId::class.toString(), PrimitiveKind.STRING) - override fun serialize(encoder: Encoder, value: InputFile) = encoder.encodeString(value.fileId) - override fun deserialize(decoder: Decoder): FileId = FileId(decoder.decodeString()) + override fun serialize(encoder: Encoder, value: InputFile) = encoder.encodeString(value.fileIdToSend) + override fun deserialize(decoder: Decoder): FileId = FileId(decoder.decodeString().removePrefix(attachPrefix)) } // TODO:: add checks for files size diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAnimation.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAnimation.kt index ae61f5c46d..d1a51fed56 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAnimation.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAnimation.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -22,7 +23,7 @@ import kotlinx.serialization.* fun SendAnimation( chatId: ChatIdentifier, animation: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, text: String? = null, parseMode: ParseMode? = null, spoilered: Boolean = false, @@ -36,15 +37,13 @@ fun SendAnimation( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val animationAsFileId = (animation as? FileId) ?.fileId val animationAsFile = animation as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendAnimationData( chatId, - animationAsFileId, - thumbAsFileId, + animation, + thumbnail ?.fileId, text, parseMode, null, @@ -63,9 +62,9 @@ fun SendAnimation( return if (animationAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendAnimationFiles(animationAsFile, thumbAsFile) + listOfNotNull(animationAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -73,7 +72,7 @@ fun SendAnimation( fun SendAnimation( chatId: ChatIdentifier, animation: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, entities: TextSourcesList, spoilered: Boolean = false, duration: Long? = null, @@ -86,15 +85,13 @@ fun SendAnimation( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val animationAsFileId = (animation as? FileId) ?.fileId val animationAsFile = animation as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendAnimationData( chatId, - animationAsFileId, - thumbAsFileId, + animation, + thumbnail ?.fileId, entities.makeString(), null, entities.toRawMessageEntities(), @@ -113,9 +110,9 @@ fun SendAnimation( return if (animationAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendAnimationFiles(animationAsFile, thumbAsFile) + listOfNotNull(animationAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -128,7 +125,7 @@ data class SendAnimationData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(animationField) - val animation: String? = null, + val animation: InputFile, @SerialName(thumbnailField) override val thumbnail: String? = null, @SerialName(captionField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAudio.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAudio.kt index dcb1d268f5..b2399f9fed 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAudio.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendAudio.kt @@ -2,6 +2,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.abstracts.Performerable import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -23,7 +24,7 @@ import kotlinx.serialization.* fun SendAudio( chatId: ChatIdentifier, audio: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, text: String? = null, parseMode: ParseMode? = null, duration: Long? = null, @@ -36,15 +37,13 @@ fun SendAudio( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val audioAsFileId = (audio as? FileId) ?.fileId val audioAsFile = audio as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendAudioData( chatId, - audioAsFileId, - thumbAsFileId, + audio, + thumbnail ?.fileId, text, parseMode, null, @@ -62,9 +61,9 @@ fun SendAudio( return if (audioAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendAudioFiles(audioAsFile, thumbAsFile) + listOfNotNull(audioAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -72,7 +71,7 @@ fun SendAudio( fun SendAudio( chatId: ChatIdentifier, audio: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, entities: List, duration: Long? = null, performer: String? = null, @@ -84,15 +83,13 @@ fun SendAudio( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val audioAsFileId = (audio as? FileId) ?.fileId val audioAsFile = audio as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendAudioData( chatId, - audioAsFileId, - thumbAsFileId, + audio, + thumbnail ?.fileId, entities.makeString(), null, entities.toRawMessageEntities(), @@ -110,9 +107,9 @@ fun SendAudio( return if (audioAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendAudioFiles(audioAsFile, thumbAsFile) + listOfNotNull(audioAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -125,7 +122,7 @@ data class SendAudioData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(audioField) - val audio: String? = null, + val audio: InputFile, @SerialName(thumbnailField) override val thumbnail: String? = null, @SerialName(captionField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendDocument.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendDocument.kt index 8ff04c4aca..98bc374dac 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendDocument.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendDocument.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -31,7 +32,7 @@ import kotlinx.serialization.* fun SendDocument( chatId: ChatIdentifier, document: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, text: String? = null, parseMode: ParseMode? = null, threadId: MessageThreadId? = chatId.threadId, @@ -42,15 +43,13 @@ fun SendDocument( replyMarkup: KeyboardMarkup? = null, disableContentTypeDetection: Boolean? = null ): Request> { - val documentAsFileId = (document as? FileId) ?.fileId val documentAsFile = document as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendDocumentData( chatId, - documentAsFileId, - thumbAsFileId, + document, + thumbnail ?.fileId, text, parseMode, null, @@ -66,9 +65,9 @@ fun SendDocument( return if (documentAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendDocumentFiles(documentAsFile, thumbAsFile) + listOfNotNull(documentAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -85,7 +84,7 @@ fun SendDocument( fun SendDocument( chatId: ChatIdentifier, document: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, entities: TextSourcesList, threadId: MessageThreadId? = chatId.threadId, disableNotification: Boolean = false, @@ -95,15 +94,13 @@ fun SendDocument( replyMarkup: KeyboardMarkup? = null, disableContentTypeDetection: Boolean? = null ): Request> { - val documentAsFileId = (document as? FileId) ?.fileId val documentAsFile = document as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendDocumentData( chatId, - documentAsFileId, - thumbAsFileId, + document, + thumbnail ?.fileId, entities.makeString(), null, entities.toRawMessageEntities(), @@ -119,9 +116,9 @@ fun SendDocument( return if (documentAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendDocumentFiles(documentAsFile, thumbAsFile) + listOfNotNull(documentAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -143,7 +140,7 @@ data class SendDocumentData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(documentField) - val document: String? = null, + val document: InputFile, @SerialName(thumbnailField) override val thumbnail: String? = null, @SerialName(captionField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendMediaGroup.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendMediaGroup.kt index afb914b770..ff2ace3d9e 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendMediaGroup.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendMediaGroup.kt @@ -2,10 +2,12 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.MultipartFile import dev.inmo.tgbotapi.requests.abstracts.Request +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.SendMessageRequest import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.media.* +import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.PossiblySentViaBotCommonMessage import dev.inmo.tgbotapi.types.message.abstracts.TelegramBotAPIMessageDeserializeOnlySerializerClass import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent @@ -37,7 +39,7 @@ fun SendMediaGroup( protectContent: Boolean = false, replyToMessageId: MessageId? = null, allowSendingWithoutReply: Boolean? = null -): Request>> { +): Request>> { if (media.size !in mediaCountInMediaGroup) { throwRangeError("Count of members in media group", mediaCountInMediaGroup, media.size) } @@ -66,11 +68,11 @@ fun SendMediaGroup( return (if (files.isEmpty()) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendMediaGroupFiles(files) + files.associateBy { it.fileId } ) - }) as Request>> + }) as Request>> } /** diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendPhoto.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendPhoto.kt index 0c54d063e5..d030d66cba 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendPhoto.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendPhoto.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -33,7 +34,7 @@ fun SendPhoto( ): Request> { val data = SendPhotoData( chatId, - (photo as? FileId) ?.fileId, + photo, text, parseMode, null, @@ -45,12 +46,14 @@ fun SendPhoto( allowSendingWithoutReply, replyMarkup ) - return data.photo ?.let { + return if (photo is MultipartFile) { + CommonMultipartFileRequest( + data, + listOf(photo).associateBy { it.fileId } + ) + } else { data - } ?: MultipartRequestImpl( - data, - SendPhotoFiles(photo as MultipartFile) - ) + } } fun SendPhoto( @@ -67,7 +70,7 @@ fun SendPhoto( ): Request> { val data = SendPhotoData( chatId, - (photo as? FileId)?.fileId, + photo, entities.makeString(), null, entities.toRawMessageEntities(), @@ -79,12 +82,15 @@ fun SendPhoto( allowSendingWithoutReply, replyMarkup ) - return data.photo ?.let { + + return if (photo is MultipartFile) { + CommonMultipartFileRequest( + data, + listOf(photo).associateBy { it.fileId } + ) + } else { data - } ?: MultipartRequestImpl( - data, - SendPhotoFiles(photo as MultipartFile) - ) + } } private val commonResultDeserializer: DeserializationStrategy> @@ -95,7 +101,7 @@ data class SendPhotoData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(photoField) - val photo: String? = null, + val photo: InputFile, @SerialName(captionField) override val text: String? = null, @SerialName(parseModeField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendSticker.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendSticker.kt index 3de84286d7..46897671d6 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendSticker.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendSticker.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.ReplyingMarkupSendMessageRequest import dev.inmo.tgbotapi.requests.send.abstracts.SendMessageRequest import dev.inmo.tgbotapi.types.* @@ -28,7 +29,7 @@ fun SendSticker( replyMarkup: KeyboardMarkup? = null ): Request> = SendStickerByFileId( chatId, - sticker as? FileId, + sticker, threadId, disableNotification, protectContent, @@ -37,7 +38,10 @@ fun SendSticker( replyMarkup ).let { when (sticker) { - is MultipartFile -> SendStickerByFile(it, sticker, emoji) + is MultipartFile -> CommonMultipartFileRequest( + it, + listOf(sticker).associateBy { it.fileId } + ) is FileId -> it } } @@ -50,7 +54,7 @@ data class SendStickerByFileId internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(stickerField) - val sticker: FileId? = null, + val sticker: InputFile, @SerialName(messageThreadIdField) override val threadId: MessageThreadId? = chatId.threadId, @SerialName(disableNotificationField) @@ -70,20 +74,3 @@ data class SendStickerByFileId internal constructor( override val requestSerializer: SerializationStrategy<*> get() = serializer() } - -data class SendStickerByFile internal constructor( - @Transient - private val sendStickerByFileId: SendStickerByFileId, - val sticker: MultipartFile, - val emoji: String? -) : MultipartRequest>, Request> by sendStickerByFileId { - override val mediaMap: Map = mapOf(stickerField to sticker) - override val paramsJson: JsonObject - get() { - return JsonObject( - mapOfNotNull( - emojiField to emoji ?.let { JsonPrimitive(it) } - ) + sendStickerByFileId.toJsonWithoutNulls(SendStickerByFileId.serializer()) - ) - } -} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideo.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideo.kt index e427c4f10a..815e784969 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideo.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideo.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -22,7 +23,7 @@ import kotlinx.serialization.* fun SendVideo( chatId: ChatIdentifier, video: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, text: String? = null, parseMode: ParseMode? = null, spoilered: Boolean = false, @@ -37,15 +38,13 @@ fun SendVideo( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val videoAsFileId = (video as? FileId) ?.fileId val videoAsFile = video as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendVideoData( chatId, - videoAsFileId, - thumbAsFileId, + video, + thumbnail ?.fileId, text, parseMode, null, @@ -65,9 +64,9 @@ fun SendVideo( return if (videoAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendVideoFiles(videoAsFile, thumbAsFile) + listOfNotNull(videoAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -75,7 +74,7 @@ fun SendVideo( fun SendVideo( chatId: ChatIdentifier, video: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, entities: TextSourcesList, spoilered: Boolean = false, duration: Long? = null, @@ -89,15 +88,13 @@ fun SendVideo( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val videoAsFileId = (video as? FileId) ?.fileId val videoAsFile = video as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendVideoData( chatId, - videoAsFileId, - thumbAsFileId, + video, + thumbnail ?.fileId, entities.makeString(), null, entities.toRawMessageEntities(), @@ -117,9 +114,9 @@ fun SendVideo( return if (videoAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendVideoFiles(videoAsFile, thumbAsFile) + listOfNotNull(videoAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -132,7 +129,7 @@ data class SendVideoData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(videoField) - val video: String? = null, + val video: InputFile, @SerialName(thumbnailField) override val thumbnail: String? = null, @SerialName(captionField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideoNote.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideoNote.kt index 914e50eafa..0c840feed0 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideoNote.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVideoNote.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -14,7 +15,7 @@ import kotlinx.serialization.* fun SendVideoNote( chatId: ChatIdentifier, videoNote: InputFile, - thumb: InputFile? = null, + thumbnail: InputFile? = null, duration: Long? = null, size: Int? = null, // in documentation - length (size of video side) threadId: MessageThreadId? = chatId.threadId, @@ -24,15 +25,13 @@ fun SendVideoNote( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val videoNoteAsFileId = (videoNote as? FileId) ?.fileId val videoNoteAsFile = videoNote as? MultipartFile - val thumbAsFileId = (thumb as? FileId) ?.fileId - val thumbAsFile = thumb as? MultipartFile + val thumbAsFile = thumbnail as? MultipartFile val data = SendVideoNoteData( chatId, - videoNoteAsFileId, - thumbAsFileId, + videoNote, + thumbnail ?.fileId, duration, size, threadId, @@ -46,9 +45,9 @@ fun SendVideoNote( return if (videoNoteAsFile == null && thumbAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendVideoNoteFiles(videoNoteAsFile, thumbAsFile) + listOfNotNull(videoNoteAsFile, thumbAsFile).associateBy { it.fileId } ) } } @@ -61,7 +60,7 @@ data class SendVideoNoteData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(videoNoteField) - val videoNote: String? = null, + val videoNote: InputFile, @SerialName(thumbnailField) override val thumbnail: String? = null, @SerialName(durationField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVoice.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVoice.kt index 0b914246fb..1bad25a36a 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVoice.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/media/SendVoice.kt @@ -1,6 +1,7 @@ package dev.inmo.tgbotapi.requests.send.media import dev.inmo.tgbotapi.requests.abstracts.* +import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.send.abstracts.* import dev.inmo.tgbotapi.requests.send.media.base.* import dev.inmo.tgbotapi.types.* @@ -32,12 +33,11 @@ fun SendVoice( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val voiceAsFileId = (voice as? FileId) ?.fileId val voiceAsFile = voice as? MultipartFile val data = SendVoiceData( chatId, - voiceAsFileId, + voice, text, parseMode, null, @@ -53,9 +53,9 @@ fun SendVoice( return if (voiceAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendVoiceFiles(voiceAsFile) + listOfNotNull(voiceAsFile).associateBy { it.fileId } ) } } @@ -72,12 +72,11 @@ fun SendVoice( allowSendingWithoutReply: Boolean? = null, replyMarkup: KeyboardMarkup? = null ): Request> { - val voiceAsFileId = (voice as? FileId) ?.fileId val voiceAsFile = voice as? MultipartFile val data = SendVoiceData( chatId, - voiceAsFileId, + voice, entities.makeString(), null, entities.toRawMessageEntities(), @@ -93,9 +92,9 @@ fun SendVoice( return if (voiceAsFile == null) { data } else { - MultipartRequestImpl( + CommonMultipartFileRequest( data, - SendVoiceFiles(voiceAsFile) + listOfNotNull(voiceAsFile).associateBy { it.fileId } ) } } @@ -108,7 +107,7 @@ data class SendVoiceData internal constructor( @SerialName(chatIdField) override val chatId: ChatIdentifier, @SerialName(voiceField) - val voice: String? = null, + val voice: InputFile, @SerialName(captionField) override val text: String? = null, @SerialName(parseModeField) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/CreateNewStickerSet.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/CreateNewStickerSet.kt index 7895e7ef93..5cf4afa885 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/CreateNewStickerSet.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/CreateNewStickerSet.kt @@ -1,11 +1,15 @@ package dev.inmo.tgbotapi.requests.stickers +import dev.inmo.micro_utils.serialization.mapper.MapperSerializer import dev.inmo.tgbotapi.requests.abstracts.* import dev.inmo.tgbotapi.requests.common.CommonMultipartFileRequest import dev.inmo.tgbotapi.requests.stickers.abstracts.CreateStickerSetAction import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.stickers.MaskPosition import kotlinx.serialization.* +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder /** * Will create one of [CreateNewStickerSet] types based on the first element of [stickers] @@ -26,18 +30,30 @@ fun CreateNewStickerSet( is InputSticker.WithKeywords.CustomEmoji -> CreateNewStickerSet.CustomEmoji(userId, name, title, stickersFormat, stickers.filterIsInstance(), needsRepainting) is InputSticker.WithKeywords.Regular -> CreateNewStickerSet.Regular(userId, name, title, stickersFormat, stickers.filterIsInstance()) } - val multipartParts = stickers.mapNotNull { (it.sticker as? MultipartFile) } + val multipartParts = stickers.mapNotNull { + (it.sticker as? MultipartFile) + } return if (multipartParts.isNotEmpty()) { - CommonMultipartFileRequest( - data, - multipartParts.associateBy { it.fileId } - ) + when (data) { // cratch for exact determining of common multipart data type + is CreateNewStickerSet.CustomEmoji -> CommonMultipartFileRequest( + data, + multipartParts.associateBy { it.fileId } + ) + is CreateNewStickerSet.Mask -> CommonMultipartFileRequest( + data, + multipartParts.associateBy { it.fileId } + ) + is CreateNewStickerSet.Regular -> CommonMultipartFileRequest( + data, + multipartParts.associateBy { it.fileId } + ) + } } else { data } } -@Serializable +@Serializable(CreateNewStickerSetSerializer::class) sealed interface CreateNewStickerSet : CreateStickerSetAction { val stickerType: StickerType val stickers: List @@ -101,4 +117,70 @@ sealed interface CreateNewStickerSet : CreateStickerSetAction { override val stickerType: StickerType get() = StickerType.CustomEmoji } + + @Serializable + data class SurrogateCreateNewSticker internal constructor( + @SerialName(userIdField) + override val userId: UserId, + @SerialName(nameField) + override val name: String, + @SerialName(titleField) + override val title: String, + @SerialName(stickerFormatField) + val stickersFormat: StickerFormat, + @SerialName(stickersField) + val stickers: List, + @SerialName(stickerTypeField) + val stickerType: StickerType, + @SerialName(needsRepaintingField) + val needsRepainting: Boolean? = null + ) : CreateStickerSetAction { + override val requestSerializer: SerializationStrategy<*> + get() = CreateNewStickerSet.serializer() + + override fun method(): String = "createNewStickerSet" + } } + +object CreateNewStickerSetSerializer : KSerializer, + MapperSerializer( + CreateNewStickerSet.SurrogateCreateNewSticker.serializer(), + { + CreateNewStickerSet.SurrogateCreateNewSticker( + it.userId, + it.name, + it.title, + it.stickersFormat, + it.stickers, + it.stickerType, + (it as? CreateNewStickerSet.CustomEmoji)?.needsRepainting + ) + }, + { + when (it.stickerType) { + StickerType.CustomEmoji -> CreateNewStickerSet.CustomEmoji( + it.userId, + it.name, + it.title, + it.stickersFormat, + it.stickers.filterIsInstance(), + it.needsRepainting + ) + StickerType.Mask -> CreateNewStickerSet.Mask( + it.userId, + it.name, + it.title, + it.stickersFormat, + it.stickers.filterIsInstance(), + ) + StickerType.Regular -> CreateNewStickerSet.Regular( + it.userId, + it.name, + it.title, + it.stickersFormat, + it.stickers.filterIsInstance(), + ) + is StickerType.Unknown -> error("Unable to create new sticker set due to error in type format: ${it.stickerType}") + } + } + ) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/InputSticker.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/InputSticker.kt index 9633b555a7..874fff6676 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/InputSticker.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/stickers/InputSticker.kt @@ -1,17 +1,20 @@ package dev.inmo.tgbotapi.requests.stickers +import dev.inmo.micro_utils.serialization.mapper.MapperSerializer import dev.inmo.tgbotapi.requests.abstracts.InputFile +import dev.inmo.tgbotapi.types.StickerType import dev.inmo.tgbotapi.types.emojiListField import dev.inmo.tgbotapi.types.keywordsField import dev.inmo.tgbotapi.types.maskPositionField import dev.inmo.tgbotapi.types.stickerField import dev.inmo.tgbotapi.types.stickers.MaskPosition import dev.inmo.tgbotapi.utils.internal.ClassCastsIncluded +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @ClassCastsIncluded -@Serializable +@Serializable(InputStickerSerializer::class) sealed interface InputSticker { val sticker: InputFile val emojisList: List @@ -51,3 +54,69 @@ sealed interface InputSticker { ) : WithKeywords } } + +object InputStickerSerializer : KSerializer, MapperSerializer( + SurrogateInputSticker.serializer(), + { + when (it) { + is InputSticker.Mask -> SurrogateInputSticker( + it.sticker, + it.emojisList, + emptyList(), + it.maskPosition, + StickerType.Mask + ) + is InputSticker.WithKeywords.CustomEmoji -> SurrogateInputSticker( + it.sticker, + it.emojisList, + it.keywords, + null, + StickerType.CustomEmoji + ) + is InputSticker.WithKeywords.Regular -> SurrogateInputSticker( + it.sticker, + it.emojisList, + it.keywords, + null, + StickerType.Regular + ) + } + }, + { + when (it.internalType) { + StickerType.CustomEmoji -> InputSticker.WithKeywords.CustomEmoji( + it.sticker, + it.emojisList, + it.keywords + ) + StickerType.Mask -> InputSticker.Mask( + it.sticker, + it.emojisList, + it.maskPosition + ) + StickerType.Regular -> InputSticker.WithKeywords.Regular( + it.sticker, + it.emojisList, + it.keywords + ) + is StickerType.Unknown -> InputSticker.WithKeywords.Regular( + it.sticker, + it.emojisList, + it.keywords + ) + } + }, +) { + @Serializable + data class SurrogateInputSticker internal constructor( + @SerialName(stickerField) + val sticker: InputFile, + @SerialName(emojiListField) + val emojisList: List, + @SerialName(keywordsField) + val keywords: List = emptyList(), + @SerialName(maskPositionField) + val maskPosition: MaskPosition? = null, + internal val internalType: StickerType = StickerType.Unknown() + ) +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/Abstracts.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/Abstracts.kt index 89b9ad92d0..9d21571e14 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/Abstracts.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/Abstracts.kt @@ -118,6 +118,18 @@ sealed interface MediaCollectionContent: MessageContent, M sealed interface MediaContent: MessageContent { val media: TelegramMediaFile fun asTelegramMedia(): TelegramMedia + + override fun createResend( + chatId: ChatIdentifier, + messageThreadId: MessageThreadId?, + disableNotification: Boolean, + protectContent: Boolean, + replyToMessageId: MessageId?, + allowSendingWithoutReply: Boolean?, + replyMarkup: KeyboardMarkup? + ): Request> { + TODO("Not yet implemented") + } } sealed interface SpoilerableMediaContent : MediaContent, SpoilerableData diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/MediaGroupContent.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/MediaGroupContent.kt index 8af2a53a34..1b0c05cb53 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/MediaGroupContent.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/message/content/MediaGroupContent.kt @@ -9,6 +9,7 @@ import dev.inmo.tgbotapi.types.MessageThreadId import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup import dev.inmo.tgbotapi.types.files.TelegramMediaFile import dev.inmo.tgbotapi.types.media.TelegramMedia +import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.textsources.TextSource import kotlinx.serialization.Serializable @@ -38,7 +39,7 @@ data class MediaGroupContent( replyToMessageId: MessageId?, allowSendingWithoutReply: Boolean?, replyMarkup: KeyboardMarkup? - ): Request = SendMediaGroup( + ): Request>> = SendMediaGroup( chatId, group.map { it.content.toMediaGroupMemberTelegramMedia() }, threadId,