From c025a027c6d990c962ebc9875e5edba9b5487435 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Wed, 3 Jan 2024 16:06:54 +0600 Subject: [PATCH] add support of copy/forward/delete messages --- .../tgbotapi/extensions/api/DeleteMessages.kt | 67 ++++++++ .../extensions/api/ForwardMessages.kt | 156 ++++++++++++++++++ .../extensions/api/send/CopyMessages.kt | 84 ++++++++++ .../abstracts/types/MessagesAction.kt | 7 + .../inmo/tgbotapi/requests/DeleteMessages.kt | 29 ++++ .../inmo/tgbotapi/requests/ForwardMessages.kt | 85 ++++++++++ .../tgbotapi/requests/send/CopyMessages.kt | 91 ++++++++++ .../kotlin/dev/inmo/tgbotapi/types/Common.kt | 6 + 8 files changed, 525 insertions(+) create mode 100644 tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/DeleteMessages.kt create mode 100644 tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/ForwardMessages.kt create mode 100644 tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/send/CopyMessages.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessagesAction.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/DeleteMessages.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/ForwardMessages.kt create mode 100644 tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/CopyMessages.kt diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/DeleteMessages.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/DeleteMessages.kt new file mode 100644 index 0000000000..406af2bae1 --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/DeleteMessages.kt @@ -0,0 +1,67 @@ +package dev.inmo.tgbotapi.extensions.api + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.DeleteMessage +import dev.inmo.tgbotapi.requests.DeleteMessages +import dev.inmo.tgbotapi.requests.ForwardMessages +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.message.abstracts.Message + +suspend fun TelegramBot.deleteMessages( + chatId: ChatIdentifier, + messageIds: List +) = messageIds.chunked(deleteMessagesLimit.last).map { + execute( + DeleteMessages( + chatId = chatId, + messageIds = it + ) + ) +}.all { it } + +suspend fun TelegramBot.deleteMessages( + chatId: ChatIdentifier, + messageIds: Array +) = deleteMessages( + chatId = chatId, + messageIds = messageIds.toList() +) + +suspend fun TelegramBot.deleteMessages( + chatId: ChatIdentifier, + firstMessageId: MessageId, + vararg messageIds: MessageId +) = deleteMessages( + chatId = chatId, + messageIds = (listOf(firstMessageId) + messageIds.toList()) +) + +suspend fun TelegramBot.deleteMessages( + messages: List +) = messages.groupBy { it.chat }.map { (chat, messages) -> + deleteMessages( + chatId = chat.id, + messageIds = messages.map { it.messageId } + ) +}.all { it } + +suspend fun TelegramBot.delete( + chatId: ChatIdentifier, + messageIds: List +) = deleteMessages(chatId = chatId, messageIds = messageIds) + +suspend fun TelegramBot.delete( + chatId: ChatIdentifier, + messageIds: Array +) = deleteMessages(chatId = chatId, messageIds = messageIds) + +suspend fun TelegramBot.delete( + chatId: ChatIdentifier, + firstMessageId: MessageId, + secondMessageId: MessageId, + vararg messageIds: MessageId +) = deleteMessages(chatId = chatId, messageIds = (listOf(firstMessageId, secondMessageId) + messageIds.toList())) + +suspend fun TelegramBot.delete( + messages: List +) = deleteMessages(messages) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/ForwardMessages.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/ForwardMessages.kt new file mode 100644 index 0000000000..894b2ca1da --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/ForwardMessages.kt @@ -0,0 +1,156 @@ +package dev.inmo.tgbotapi.extensions.api + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.ForwardMessages +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.message.abstracts.Message + +suspend fun TelegramBot.forwardMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: List, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = messageIds.chunked(forwardMessagesLimit.last).flatMap { + execute( + ForwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = it, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption + ) + ) +} + +suspend fun TelegramBot.forwardMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: Array, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = forwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = messageIds.toList(), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.forwardMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + firstMessageId: MessageId, + vararg messageIds: MessageId, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = forwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = (listOf(firstMessageId) + messageIds.toList()), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.forwardMessages( + toChatId: ChatIdentifier, + messages: List, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = messages.groupBy { it.chat }.flatMap { (chat, messages) -> + forwardMessages( + toChatId = toChatId, + fromChatId = chat.id, + messageIds = messages.map { it.messageId }, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption + ) +} + +suspend fun TelegramBot.forward( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: List, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = forwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = messageIds, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.forward( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: Array, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = forwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = messageIds, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.forward( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + firstMessageId: MessageId, + vararg messageIds: MessageId, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = forwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + firstMessageId = firstMessageId, + messageIds = *messageIds, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.forward( + toChatId: ChatIdentifier, + messages: List, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = forwardMessages( + toChatId = toChatId, + messages = messages, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) diff --git a/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/send/CopyMessages.kt b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/send/CopyMessages.kt new file mode 100644 index 0000000000..cfca65c3ff --- /dev/null +++ b/tgbotapi.api/src/commonMain/kotlin/dev/inmo/tgbotapi/extensions/api/send/CopyMessages.kt @@ -0,0 +1,84 @@ +package dev.inmo.tgbotapi.extensions.api.send + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.requests.send.CopyMessages +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.message.abstracts.Message + +suspend fun TelegramBot.copyMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: List, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = messageIds.chunked(copyMessagesLimit.last).flatMap { + execute( + CopyMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = it, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption + ) + ) +} + +suspend fun TelegramBot.copyMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: Array, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = copyMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = messageIds.toList(), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.copyMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + firstMessageId: MessageId, + vararg messageIds: MessageId, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = copyMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = (listOf(firstMessageId) + messageIds.toList()), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +suspend fun TelegramBot.copyMessages( + toChatId: ChatIdentifier, + messages: List, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = messages.groupBy { it.chat }.flatMap { (chat, messages) -> + copyMessages( + toChatId = toChatId, + fromChatId = chat.id, + messageIds = messages.map { it.messageId }, + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption + ) +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessagesAction.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessagesAction.kt new file mode 100644 index 0000000000..51b8dcca07 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/abstracts/types/MessagesAction.kt @@ -0,0 +1,7 @@ +package dev.inmo.tgbotapi.abstracts.types + +import dev.inmo.tgbotapi.types.MessageId + +interface MessagesAction: ChatRequest { + val messageIds: List +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/DeleteMessages.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/DeleteMessages.kt new file mode 100644 index 0000000000..155bd0b73a --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/DeleteMessages.kt @@ -0,0 +1,29 @@ +package dev.inmo.tgbotapi.requests + +import dev.inmo.tgbotapi.abstracts.types.MessageAction +import dev.inmo.tgbotapi.abstracts.types.MessagesAction +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.serializer + +@Serializable +data class DeleteMessages( + @SerialName(chatIdField) + override val chatId: ChatIdentifier, + @SerialName(messageIdsField) + override val messageIds: List +) : SimpleRequest, MessagesAction { + override fun method(): String = "deleteMessages" + + init { + require(messageIds.size in deleteMessagesLimit) { + "Messages count for deleteMessages must be in $deleteMessagesLimit range" + } + } + + override val resultDeserializer: DeserializationStrategy + get() = Boolean.serializer() + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/ForwardMessages.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/ForwardMessages.kt new file mode 100644 index 0000000000..5985aa8396 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/ForwardMessages.kt @@ -0,0 +1,85 @@ +package dev.inmo.tgbotapi.requests + +import dev.inmo.tgbotapi.abstracts.types.DisableNotification +import dev.inmo.tgbotapi.abstracts.types.MessagesAction +import dev.inmo.tgbotapi.abstracts.types.ProtectContent +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.requests.send.abstracts.OptionallyMessageThreadRequest +import dev.inmo.tgbotapi.types.* +import kotlinx.serialization.* +import kotlinx.serialization.builtins.ListSerializer + +fun ForwardMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: Array, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = ForwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = messageIds.toList(), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +fun ForwardMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageId: MessageId, + vararg messageIds: MessageId, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = ForwardMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = (listOf(messageId) + messageIds.toList()), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +@Serializable +data class ForwardMessages ( + @SerialName(chatIdField) + val toChatId: ChatIdentifier, + @SerialName(fromChatIdField) + val fromChatId: ChatIdentifier, + @SerialName(messageIdsField) + override val messageIds: List, + @SerialName(messageThreadIdField) + override val threadId: MessageThreadId? = toChatId.threadId, + @SerialName(disableNotificationField) + override val disableNotification: Boolean = false, + @SerialName(protectContentField) + override val protectContent: Boolean = false, + @SerialName(removeCaptionField) + private val removeCaption: Boolean = false +): SimpleRequest>, + MessagesAction, + ProtectContent, + OptionallyMessageThreadRequest, + DisableNotification { + override val chatId: ChatIdentifier + get() = fromChatId + + init { + require(messageIds.size in forwardMessagesLimit) { + "Messages count for forwardMessages must be in $forwardMessagesLimit range" + } + } + + override fun method(): String = "forwardMessages" + + override val resultDeserializer: DeserializationStrategy> + get() = ListSerializer(MessageIdSerializer) + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/CopyMessages.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/CopyMessages.kt new file mode 100644 index 0000000000..38e5c296d2 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/requests/send/CopyMessages.kt @@ -0,0 +1,91 @@ +package dev.inmo.tgbotapi.requests.send + +import dev.inmo.tgbotapi.abstracts.types.DisableNotification +import dev.inmo.tgbotapi.abstracts.types.MessagesAction +import dev.inmo.tgbotapi.abstracts.types.ProtectContent +import dev.inmo.tgbotapi.requests.abstracts.SimpleRequest +import dev.inmo.tgbotapi.requests.send.abstracts.OptionallyMessageThreadRequest +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.message.textsources.TextSource +import dev.inmo.tgbotapi.types.message.ParseMode +import dev.inmo.tgbotapi.types.buttons.KeyboardMarkup +import dev.inmo.tgbotapi.types.message.* +import dev.inmo.tgbotapi.types.message.toRawMessageEntities +import dev.inmo.tgbotapi.utils.extensions.makeString +import kotlinx.serialization.* +import kotlinx.serialization.builtins.ListSerializer + +fun CopyMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageIds: Array, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = CopyMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = messageIds.toList(), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +fun CopyMessages( + toChatId: ChatIdentifier, + fromChatId: ChatIdentifier, + messageId: MessageId, + vararg messageIds: MessageId, + threadId: MessageThreadId? = toChatId.threadId, + disableNotification: Boolean = false, + protectContent: Boolean = false, + removeCaption: Boolean = false +) = CopyMessages( + toChatId = toChatId, + fromChatId = fromChatId, + messageIds = (listOf(messageId) + messageIds.toList()), + threadId = threadId, + disableNotification = disableNotification, + protectContent = protectContent, + removeCaption = removeCaption +) + +@Serializable +data class CopyMessages ( + @SerialName(chatIdField) + val toChatId: ChatIdentifier, + @SerialName(fromChatIdField) + val fromChatId: ChatIdentifier, + @SerialName(messageIdsField) + override val messageIds: List, + @SerialName(messageThreadIdField) + override val threadId: MessageThreadId? = toChatId.threadId, + @SerialName(disableNotificationField) + override val disableNotification: Boolean = false, + @SerialName(protectContentField) + override val protectContent: Boolean = false, + @SerialName(removeCaptionField) + private val removeCaption: Boolean = false +): SimpleRequest>, + MessagesAction, + ProtectContent, + OptionallyMessageThreadRequest, + DisableNotification { + override val chatId: ChatIdentifier + get() = fromChatId + + init { + require(messageIds.size in copyMessagesLimit) { + "Messages count for copyMessages must be in $copyMessagesLimit range" + } + } + + override fun method(): String = "copyMessages" + + override val resultDeserializer: DeserializationStrategy> + get() = ListSerializer(MessageIdSerializer) + override val requestSerializer: SerializationStrategy<*> + get() = serializer() +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt index b6e7dd2c83..3d55e4d95e 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/Common.kt @@ -175,6 +175,10 @@ val keywordsInStickerLimit = 0 .. 20 val stickerKeywordLengthLimit = 0 .. 64 +val forwardMessagesLimit = 1 .. 100 +val copyMessagesLimit = forwardMessagesLimit +val deleteMessagesLimit = forwardMessagesLimit + const val botActionActualityTime: Seconds = 5 val cloudStorageKeyLimit = 1 .. 128 @@ -196,6 +200,7 @@ const val tgWebAppStartParamField = "tgWebAppStartParam" const val chatIdField = "chat_id" const val senderChatIdField = "sender_chat_id" const val messageIdField = "message_id" +const val messageIdsField = "message_ids" const val actorChatField = "actor_chat" const val messageThreadIdField = "message_thread_id" const val mediaGroupIdField = "media_group_id" @@ -205,6 +210,7 @@ const val disableWebPagePreviewField = "disable_web_page_preview" const val linkPreviewOptionsField = "link_preview_options" const val disableNotificationField = "disable_notification" const val protectContentField = "protect_content" +const val removeCaptionField = "remove_caption" const val replyToMessageIdField = "reply_to_message_id" const val allowSendingWithoutReplyField = "allow_sending_without_reply" const val replyMarkupField = "reply_markup"