// // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Foundation import SignalServiceKit extension AttachmentMultisend { public struct Destination { public let conversationItem: ConversationItem public let thread: TSThread // Message bodies are re-generated _per destination_, // as mentions must be hydrated based on participants. public let messageBody: ValidatedTSMessageBody? } public static func prepareForSending( _ messageBody: MessageBody?, to conversations: [ConversationItem], db: SDSDatabaseStorage, attachmentValidator: TSResourceContentValidator ) throws -> [Destination] { // If the message body has no mentions, we can "hydrate" once across all threads // and share it. We only need to re-generate per-thread if there are mentions. let canShareMessageBody = !(messageBody?.ranges.hasMentions ?? false) struct PreDestination { let conversationItem: ConversationItem let thread: TSThread let messageBody: HydratedMessageBody? } let preDestinations: [PreDestination] = try db.write { tx in return try conversations.map { conversation in guard let thread = conversation.getOrCreateThread(transaction: tx) else { throw OWSAssertionError("Missing thread for conversation") } let hydratedMessageBody: HydratedMessageBody? if canShareMessageBody { // Don't set per-destination message bodies. hydratedMessageBody = nil } else { hydratedMessageBody = messageBody?.forForwarding(to: thread, transaction: tx.unwrapGrdbRead) } return .init( conversationItem: conversation, thread: thread, messageBody: hydratedMessageBody ) } } guard !canShareMessageBody else { // We only prepare the single shared body. let validatedMessageBody: ValidatedTSMessageBody? if let messageBody { validatedMessageBody = try attachmentValidator.prepareOversizeTextIfNeeded( from: messageBody ) } else { validatedMessageBody = nil } return preDestinations.map { .init( conversationItem: $0.conversationItem, thread: $0.thread, messageBody: validatedMessageBody ) } } // Prepare the message body per-thread. var destinations = [Destination]() for preDestination in preDestinations { guard let hydratedMessageBody = preDestination.messageBody else { destinations.append(.init( conversationItem: preDestination.conversationItem, thread: preDestination.thread, messageBody: nil )) continue } let validatedMessageBody = try attachmentValidator.prepareOversizeTextIfNeeded( from: hydratedMessageBody.asMessageBodyForForwarding() ) destinations.append(.init( conversationItem: preDestination.conversationItem, thread: preDestination.thread, messageBody: validatedMessageBody )) } return destinations } }