// // Copyright (c) 2021 Open Whisper Systems. All rights reserved. // import Foundation import SignalMessaging public class AttachmentMultisend: Dependencies { public class func sendApprovedMedia(conversations: [ConversationItem], approvalMessageBody: MessageBody?, approvedAttachments: [SignalAttachment]) -> Promise<[TSThread]> { return firstly(on: ThreadUtil.enqueueSendQueue) { // Duplicate attachments per conversation let conversationAttachments: [(ConversationItem, [SignalAttachment])] = try conversations.map { conversation in return (conversation, try approvedAttachments.map { try $0.cloneAttachment() }) } // We only upload one set of attachments, and then copy the upload details into // each conversation before sending. let attachmentsToUpload: [OutgoingAttachmentInfo] = approvedAttachments.map { attachment in return OutgoingAttachmentInfo(dataSource: attachment.dataSource, contentType: attachment.mimeType, sourceFilename: attachment.filenameOrDefault, caption: attachment.captionText, albumMessageId: nil, isBorderless: attachment.isBorderless, isLoopingVideo: attachment.isLoopingVideo) } var threads: [TSThread] = [] self.databaseStorage.write { transaction in var messages: [TSOutgoingMessage] = [] for (conversation, attachments) in conversationAttachments { guard let thread = conversation.thread(transaction: transaction) else { owsFailDebug("Missing thread for conversation") continue } // If this thread has a pending message request, treat it as accepted. ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: thread, transaction: transaction) let message = try! ThreadUtil.createUnsentMessage(body: approvalMessageBody, mediaAttachments: attachments, thread: thread, quotedReplyModel: nil, linkPreviewDraft: nil, transaction: transaction) messages.append(message) threads.append(thread) thread.donateSendMessageIntent(transaction: transaction) } // map of attachments we'll upload to their copies in each recipient thread var attachmentIdMap: [String: [String]] = [:] let correspondingAttachmentIds = transpose(messages.map { $0.attachmentIds }) for (index, attachmentInfo) in attachmentsToUpload.enumerated() { do { let attachmentToUpload = try attachmentInfo.asStreamConsumingDataSource(withIsVoiceMessage: false) attachmentToUpload.anyInsert(transaction: transaction) attachmentIdMap[attachmentToUpload.uniqueId] = correspondingAttachmentIds[index] } catch { owsFailDebug("error: \(error)") } } self.broadcastMediaMessageJobQueue.add(attachmentIdMap: attachmentIdMap, transaction: transaction) } return threads } } public class func sendApprovedMediaFromShareExtension( conversations: [ConversationItem], approvalMessageBody: MessageBody?, approvedAttachments: [SignalAttachment], messagesReadyToSend: (([TSOutgoingMessage]) -> Void)? = nil ) -> Promise<[TSThread]> { return firstly(on: .sharedUserInitiated) { () -> (Promise<[TSThread]>) in // Duplicate attachments per conversation let conversationAttachments: [(ConversationItem, [SignalAttachment])] = try conversations.map { conversation in return (conversation, try approvedAttachments.map { try $0.cloneAttachment() }) } // We only upload one set of attachments, and then copy the upload details into // each conversation before sending. let attachmentsToUpload: [OutgoingAttachmentInfo] = approvedAttachments.map { attachment in return OutgoingAttachmentInfo(dataSource: attachment.dataSource, contentType: attachment.mimeType, sourceFilename: attachment.filenameOrDefault, caption: attachment.captionText, albumMessageId: nil, isBorderless: attachment.isBorderless, isLoopingVideo: attachment.isLoopingVideo) } var threads: [TSThread] = [] var attachmentIdMap: [String: [String]] = [:] var messages: [TSOutgoingMessage] = [] self.databaseStorage.write { transaction in for (conversation, attachments) in conversationAttachments { guard let thread = conversation.thread(transaction: transaction) else { owsFailDebug("Missing thread for conversation") continue } // If this thread has a pending message request, treat it as accepted. ThreadUtil.addThreadToProfileWhitelistIfEmptyOrPendingRequestAndSetDefaultTimer(thread: thread, transaction: transaction) let message = try! ThreadUtil.createUnsentMessage(body: approvalMessageBody, mediaAttachments: attachments, thread: thread, quotedReplyModel: nil, linkPreviewDraft: nil, transaction: transaction) messages.append(message) threads.append(thread) thread.donateSendMessageIntent(transaction: transaction) } // map of attachments we'll upload to their copies in each recipient thread let correspondingAttachmentIds = transpose(messages.map { $0.attachmentIds }) for (index, attachmentInfo) in attachmentsToUpload.enumerated() { do { let attachmentToUpload = try attachmentInfo.asStreamConsumingDataSource(withIsVoiceMessage: false) attachmentToUpload.anyInsert(transaction: transaction) attachmentIdMap[attachmentToUpload.uniqueId] = correspondingAttachmentIds[index] } catch { owsFailDebug("error: \(error)") } } } messagesReadyToSend?(messages) let outgoingMessages = try BroadcastMediaUploader.upload(attachmentIdMap: attachmentIdMap) var messageSendPromises = [Promise]() databaseStorage.write { transaction in for message in outgoingMessages { messageSendPromises.append(ThreadUtil.enqueueMessagePromise( message: message, isHighPriority: true, transaction: transaction )) } } return Promise.when(fulfilled: messageSendPromises).map { threads } } } }