166 lines
6.9 KiB
Swift
166 lines
6.9 KiB
Swift
//
|
|
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
public import LibSignalClient
|
|
|
|
public class OutgoingStorySentMessageTranscript: OWSOutgoingSyncMessage {
|
|
// Exposed to ObjC and made optional for MTLModel serialization
|
|
|
|
@objc
|
|
private var storyEncodedRecipientStates: Data?
|
|
|
|
@objc
|
|
private var storyMessageUniqueId: String?
|
|
|
|
@objc
|
|
private var isRecipientUpdate: NSNumber!
|
|
|
|
public init(localThread: TSThread, timestamp: UInt64, recipientStates: [ServiceId: StoryRecipientState], transaction: SDSAnyReadTransaction) {
|
|
// We need to store the encoded data rather than just the uniqueId
|
|
// of the story message as the story message will have been deleted
|
|
// by the time we're sending this transcript.
|
|
self.storyEncodedRecipientStates = Self.encodeRecipientStates(recipientStates)
|
|
self.isRecipientUpdate = NSNumber(value: true)
|
|
super.init(timestamp: timestamp, thread: localThread, transaction: transaction)
|
|
}
|
|
|
|
public init(localThread: TSThread, storyMessage: StoryMessage, transaction: SDSAnyReadTransaction) {
|
|
self.storyMessageUniqueId = storyMessage.uniqueId
|
|
self.isRecipientUpdate = NSNumber(value: false)
|
|
super.init(timestamp: storyMessage.timestamp, thread: localThread, transaction: transaction)
|
|
}
|
|
|
|
private static func encodeRecipientStates(_ recipientStates: [ServiceId: StoryRecipientState]) -> Data? {
|
|
return try? JSONEncoder().encode(recipientStates.mapKeys(injectiveTransform: { $0.codableUppercaseString }))
|
|
}
|
|
|
|
private static func decodeRecipientStates(_ encodedRecipientStates: Data?) -> [ServiceId: StoryRecipientState]? {
|
|
guard let encodedRecipientStates else {
|
|
return nil
|
|
}
|
|
return (try? JSONDecoder().decode(
|
|
[ServiceIdUppercaseString: StoryRecipientState].self, from: encodedRecipientStates
|
|
))?.mapKeys(injectiveTransform: { $0.wrappedValue })
|
|
}
|
|
|
|
public override var isUrgent: Bool { false }
|
|
|
|
private func storyMessage(transaction: SDSAnyReadTransaction) -> StoryMessage? {
|
|
guard let storyMessageUniqueId = storyMessageUniqueId else { return nil }
|
|
return StoryMessage.anyFetch(uniqueId: storyMessageUniqueId, transaction: transaction)
|
|
}
|
|
|
|
public override func syncMessageBuilder(transaction: SDSAnyReadTransaction) -> SSKProtoSyncMessageBuilder? {
|
|
let sentBuilder = SSKProtoSyncMessageSent.builder()
|
|
sentBuilder.setTimestamp(timestamp)
|
|
sentBuilder.setIsRecipientUpdate(isRecipientUpdate.boolValue)
|
|
|
|
if let storyMessage = storyMessage(transaction: transaction) {
|
|
if !isRecipientUpdate.boolValue {
|
|
guard let storyMessageProto = storyMessageProto(for: storyMessage, transaction: transaction) else {
|
|
owsFailDebug("Failed to build sync proto for story message with timestamp \(storyMessage.timestamp)")
|
|
return nil
|
|
}
|
|
sentBuilder.setStoryMessage(storyMessageProto)
|
|
}
|
|
|
|
guard case .outgoing(let recipientStates) = storyMessage.manifest else {
|
|
owsFailDebug("Unexpected type for story message sync with timestamp \(storyMessage.timestamp)")
|
|
return nil
|
|
}
|
|
|
|
applyRecipientStates(recipientStates, sentBuilder: sentBuilder)
|
|
} else if let recipientStates = Self.decodeRecipientStates(storyEncodedRecipientStates) {
|
|
applyRecipientStates(recipientStates, sentBuilder: sentBuilder)
|
|
} else {
|
|
owsFailDebug("Missing recipient states")
|
|
return nil
|
|
}
|
|
|
|
do {
|
|
let sentProto = try sentBuilder.build()
|
|
|
|
let builder = SSKProtoSyncMessage.builder()
|
|
builder.setSent(sentProto)
|
|
return builder
|
|
} catch {
|
|
owsFailDebug("failed to build proto \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func applyRecipientStates(_ recipientStates: [ServiceId: StoryRecipientState], sentBuilder: SSKProtoSyncMessageSentBuilder) {
|
|
for (serviceId, state) in recipientStates {
|
|
let builder = SSKProtoSyncMessageSentStoryMessageRecipient.builder()
|
|
builder.setDestinationServiceID(serviceId.serviceIdString)
|
|
builder.setDistributionListIds(state.contexts.map { $0.uuidString })
|
|
builder.setIsAllowedToReply(state.allowsReplies)
|
|
sentBuilder.addStoryMessageRecipients(builder.buildInfallibly())
|
|
}
|
|
}
|
|
|
|
private func storyMessageProto(for storyMessage: StoryMessage, transaction: SDSAnyReadTransaction) -> SSKProtoStoryMessage? {
|
|
let builder = SSKProtoStoryMessage.builder()
|
|
|
|
switch storyMessage.attachment {
|
|
case .media:
|
|
guard
|
|
let storyMessageRowId = storyMessage.id,
|
|
let attachment = DependenciesBridge.shared.attachmentStore.fetchFirstReferencedAttachment(
|
|
for: .storyMessageMedia(storyMessageRowId: storyMessageRowId),
|
|
tx: transaction.asV2Read
|
|
),
|
|
let pointer = attachment.attachment.asTransitTierPointer()
|
|
else {
|
|
owsFailDebug("Missing attachment for outgoing story message")
|
|
return nil
|
|
}
|
|
let attachmentProto = DependenciesBridge.shared.attachmentManager.buildProtoForSending(
|
|
from: attachment.reference,
|
|
pointer: pointer
|
|
)
|
|
builder.setFileAttachment(attachmentProto)
|
|
if let storyMediaCaption = attachment.reference.storyMediaCaption {
|
|
builder.setBodyRanges(storyMediaCaption.toProtoBodyRanges())
|
|
}
|
|
case .text(let attachment):
|
|
guard let attachmentProto = try? attachment.buildProto(
|
|
parentStoryMessage: storyMessage,
|
|
bodyRangeHandler: builder.setBodyRanges(_:),
|
|
transaction: transaction
|
|
) else {
|
|
owsFailDebug("Missing attachment for outgoing story message")
|
|
return nil
|
|
}
|
|
builder.setTextAttachment(attachmentProto)
|
|
}
|
|
|
|
builder.setAllowsReplies(true)
|
|
|
|
do {
|
|
if let groupId = storyMessage.groupId,
|
|
let groupThread = TSGroupThread.fetch(groupId: groupId, transaction: transaction),
|
|
let groupModel = groupThread.groupModel as? TSGroupModelV2 {
|
|
builder.setGroup(try GroupsV2Protos.buildGroupContextProto(groupModel: groupModel, groupChangeProtoData: nil))
|
|
}
|
|
|
|
return try builder.build()
|
|
} catch {
|
|
owsFailDebug("failed to build protobuf: \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// MARK: - MTLModel
|
|
|
|
public required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
}
|
|
|
|
public required init(dictionary: [String: Any]) throws {
|
|
try super.init(dictionary: dictionary)
|
|
}
|
|
}
|