From 88a89ff1e7730b707e22f0a49ed8a40321696b5e Mon Sep 17 00:00:00 2001
From: InsanusMokrassar <ovsyannikov.alexey95@gmail.com>
Date: Sat, 14 Jan 2023 22:37:56 +0600
Subject: [PATCH] add resender module

---
 gradle.properties                             |   2 +-
 resender/build.gradle                         |  17 +++
 .../src/commonMain/kotlin/MessageMetaInfo.kt  |  18 +++
 .../src/commonMain/kotlin/MessagesResender.kt | 144 ++++++++++++++++++
 settings.gradle                               |   2 +
 5 files changed, 182 insertions(+), 1 deletion(-)
 create mode 100644 resender/build.gradle
 create mode 100644 resender/src/commonMain/kotlin/MessageMetaInfo.kt
 create mode 100644 resender/src/commonMain/kotlin/MessagesResender.kt

diff --git a/gradle.properties b/gradle.properties
index 9883b90..ae49d66 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -13,7 +13,7 @@ kotlin_serialisation_core_version=1.4.1
 
 github_release_plugin_version=2.4.1
 
-tgbotapi_version=5.0.0
+tgbotapi_version=5.0.1-branch_5.0.1-build1361
 micro_utils_version=0.16.4
 exposed_version=0.41.1
 plagubot_version=3.3.0
diff --git a/resender/build.gradle b/resender/build.gradle
new file mode 100644
index 0000000..9a85f4f
--- /dev/null
+++ b/resender/build.gradle
@@ -0,0 +1,17 @@
+plugins {
+    id "org.jetbrains.kotlin.multiplatform"
+    id "org.jetbrains.kotlin.plugin.serialization"
+}
+
+apply from: "$mppJavaWithJsProjectPath"
+
+kotlin {
+    sourceSets {
+        commonMain {
+            dependencies {
+                api "dev.inmo:tgbotapi.core:$tgbotapi_version"
+            }
+        }
+    }
+}
+
diff --git a/resender/src/commonMain/kotlin/MessageMetaInfo.kt b/resender/src/commonMain/kotlin/MessageMetaInfo.kt
new file mode 100644
index 0000000..f844c63
--- /dev/null
+++ b/resender/src/commonMain/kotlin/MessageMetaInfo.kt
@@ -0,0 +1,18 @@
+package dev.inmo.tgbotapi.libraries.resender
+
+import dev.inmo.tgbotapi.types.IdChatIdentifier
+import dev.inmo.tgbotapi.types.MessageId
+import dev.inmo.tgbotapi.types.message.abstracts.Message
+import dev.inmo.tgbotapi.types.message.abstracts.PossiblyMediaGroupMessage
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class MessageMetaInfo(
+    val chatId: IdChatIdentifier,
+    val messageId: MessageId,
+    val group: String? = null
+)
+
+operator fun MessageMetaInfo.Companion.invoke(
+    message: Message
+) = MessageMetaInfo(message.chat.id, message.messageId, (message as? PossiblyMediaGroupMessage<*>) ?.mediaGroupId)
diff --git a/resender/src/commonMain/kotlin/MessagesResender.kt b/resender/src/commonMain/kotlin/MessagesResender.kt
new file mode 100644
index 0000000..27c6444
--- /dev/null
+++ b/resender/src/commonMain/kotlin/MessagesResender.kt
@@ -0,0 +1,144 @@
+package dev.inmo.tgbotapi.libraries.resender
+
+import dev.inmo.tgbotapi.bot.TelegramBot
+import dev.inmo.tgbotapi.requests.ForwardMessage
+import dev.inmo.tgbotapi.requests.send.CopyMessage
+import dev.inmo.tgbotapi.requests.send.media.SendMediaGroup
+import dev.inmo.tgbotapi.types.ChatIdentifier
+import dev.inmo.tgbotapi.types.IdChatIdentifier
+import dev.inmo.tgbotapi.types.mediaCountInMediaGroup
+import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
+import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
+
+class MessagesResender(
+    private val bot: TelegramBot,
+    private val cacheChatId: ChatIdentifier
+) {
+    suspend fun resend(
+        targetChatId: IdChatIdentifier,
+        messagesInfo: List<MessageMetaInfo>
+    ): List<Pair<MessageMetaInfo, MessageMetaInfo>> {
+        val orders = messagesInfo.mapIndexed { i, messageInfo -> messageInfo to i }.toMap()
+        val sortedMessagesContents = messagesInfo.groupBy { it.group }.flatMap { (group, list) ->
+            if (group == null) {
+                list.map {
+                    orders.getValue(it) to listOf(it)
+                }
+            } else {
+                listOf(orders.getValue(list.first()) to list)
+            }
+        }.sortedBy { it.first }
+
+        return sortedMessagesContents.flatMap { (_, contents) ->
+            val result = mutableListOf<Pair<MessageMetaInfo, MessageMetaInfo>>()
+
+            when {
+                contents.size == 1 -> {
+                    val messageInfo = contents.first()
+                    runCatching {
+                        MessageMetaInfo(
+                            targetChatId,
+                            bot.execute(
+                                CopyMessage(
+                                    targetChatId,
+                                    fromChatId = messageInfo.chatId,
+                                    messageId = messageInfo.messageId
+                                )
+                            )
+                        )
+                    }.onFailure { _ ->
+                        runCatching {
+                            bot.execute(
+                                ForwardMessage(
+                                    toChatId = targetChatId,
+                                    fromChatId = messageInfo.chatId,
+                                    messageId = messageInfo.messageId
+                                )
+                            )
+                        }.onSuccess {
+                            MessageMetaInfo(
+                                targetChatId,
+                                bot.execute(
+                                    CopyMessage(
+                                        targetChatId,
+                                        fromChatId = it.chat.id,
+                                        messageId = it.messageId
+                                    )
+                                )
+                            )
+                        }
+                    }.getOrNull() ?.let {
+                        messageInfo to it
+                    }
+                }
+                else -> {
+                    val resultContents = contents.mapNotNull {
+                        it to (
+                            bot.execute(
+                                ForwardMessage(
+                                    toChatId = cacheChatId,
+                                    fromChatId = it.chatId,
+                                    messageId = it.messageId
+                                )
+                            ) as? ContentMessage<*> ?: return@mapNotNull null)
+                    }.mapNotNull { (src, forwardedMessage) ->
+                        val forwardedMessageAsMediaPartMessage = forwardedMessage.takeIf {
+                            it.content is MediaGroupPartContent
+                        } ?.let {
+                            it as ContentMessage<MediaGroupPartContent>
+                        }
+                        src to (forwardedMessageAsMediaPartMessage ?: null.also { _ ->
+                            result.add(
+                                src to MessageMetaInfo(
+                                    targetChatId,
+                                    bot.execute(
+                                        CopyMessage(
+                                            targetChatId,
+                                            fromChatId = forwardedMessage.chat.id,
+                                            messageId = forwardedMessage.messageId
+                                        )
+                                    )
+                                )
+                            )
+                        } ?: return@mapNotNull null)
+                    }
+
+                    resultContents.singleOrNull() ?.also { (src, it) ->
+                        result.add(
+                            src to MessageMetaInfo(
+                                targetChatId,
+                                bot.execute(
+                                    CopyMessage(
+                                        targetChatId,
+                                        it.chat.id,
+                                        it.messageId
+                                    )
+                                )
+                            )
+                        )
+                    } ?: resultContents.chunked(mediaCountInMediaGroup.last).forEach {
+                        bot.execute(
+                            SendMediaGroup<MediaGroupPartContent>(
+                                targetChatId,
+                                it.map { it.second.content.toMediaGroupMemberTelegramMedia() }
+                            )
+                        ).content.group.mapIndexed { i, partWrapper ->
+                            it.getOrNull(i) ?.let {
+                                result.add(
+                                    it.first to MessageMetaInfo(
+                                        partWrapper.sourceMessage.chat.id,
+                                        partWrapper.sourceMessage.messageId,
+                                        partWrapper.sourceMessage.mediaGroupId
+                                    )
+                                )
+                            }
+                        }
+                    }
+                }
+            }
+
+            result.toList()
+        }
+
+    }
+}
diff --git a/settings.gradle b/settings.gradle
index ccfc849..feb9455 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -7,6 +7,8 @@ String[] includes = [
 
     ":cache:content:common",
     ":cache:content:micro_utils",
+
+    ":resender",
 ]