Path: blob/main/SignalServiceKit/Messages/Interactions/PinnedMessages/TSInfoMessage+PinnedMessage.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
public import LibSignalClient
@objc
public class PersistablePinnedMessageItem: NSObject, NSCopying, NSSecureCoding {
let pinnedMessageAuthorAci: Aci
let originalMessageAuthorAci: Aci
let timestamp: Int64
public init(pinnedMessageAuthorAci: Aci, originalMessageAuthorAci: Aci, timestamp: Int64) {
self.pinnedMessageAuthorAci = pinnedMessageAuthorAci
self.originalMessageAuthorAci = originalMessageAuthorAci
self.timestamp = timestamp
}
// MARK: - NSCopying
public func copy(with zone: NSZone? = nil) -> Any {
self
}
// MARK: - NSSecureCoding
public static var supportsSecureCoding: Bool { true }
public func encode(with coder: NSCoder) {
coder.encode(pinnedMessageAuthorAci.serviceIdBinary, forKey: "pinnedMessageAuthor")
coder.encode(originalMessageAuthorAci.serviceIdBinary, forKey: "originalMessageAuthor")
coder.encode(timestamp, forKey: "timestamp")
}
public required init?(coder: NSCoder) {
guard
let pinnedMessageAuthorServiceIdBinary = coder.decodeObject(of: NSData.self, forKey: "pinnedMessageAuthor") as Data?,
let originalMessageAuthorServiceIdBinary = coder.decodeObject(of: NSData.self, forKey: "originalMessageAuthor") as Data?,
let _pinnedMessageAuthorAci = try? Aci.parseFrom(serviceIdBinary: pinnedMessageAuthorServiceIdBinary),
let _originalMessageAuthorAci = try? Aci.parseFrom(serviceIdBinary: originalMessageAuthorServiceIdBinary)
else {
return nil
}
self.pinnedMessageAuthorAci = _pinnedMessageAuthorAci
self.originalMessageAuthorAci = _originalMessageAuthorAci
self.timestamp = coder.decodeInt64(forKey: "timestamp")
}
}
extension TSInfoMessage {
public func pinnedMessageUniqueId(threadUniqueId: String, transaction: DBReadTransaction) -> String? {
guard let pinnedMessageItem: PersistablePinnedMessageItem = infoMessageValue(forKey: .pinnedMessage) else {
return nil
}
guard let localAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction)?.aci else {
Logger.error("User is not registered")
return nil
}
guard
let message = try? DependenciesBridge.shared.interactionStore.fetchMessage(
timestamp: UInt64(pinnedMessageItem.timestamp),
incomingMessageAuthor: localAci == pinnedMessageItem.originalMessageAuthorAci ? nil : pinnedMessageItem.originalMessageAuthorAci,
threadUniqueId: threadUniqueId,
transaction: transaction,
)
else {
Logger.error("Can't find target message")
return nil
}
if message.editState == .pastRevision {
// Pin targeted an old edit revision, fetch the latest
// version to ensure the view button goes to the right place.
if
let latestEdit = DependenciesBridge.shared.editMessageStore.findMessage(
fromEdit: message,
tx: transaction,
)
{
return latestEdit.uniqueId
}
Logger.error("Can't find past revision")
return nil
}
return message.uniqueId
}
func pinnedMessageDescription(transaction: DBReadTransaction) -> String? {
guard let pinnedMessageItem: PersistablePinnedMessageItem = infoMessageValue(forKey: .pinnedMessage) else {
return nil
}
guard let localAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction)?.aci else {
Logger.error("Can't find local ACI")
return nil
}
if localAci == pinnedMessageItem.pinnedMessageAuthorAci {
return OWSLocalizedString(
"PINNED_MESSAGE_CHAT_EVENT_SELF",
comment: "Shown when the local user pins a message.",
)
}
let displayName = SSKEnvironment.shared.contactManagerRef.displayName(
for: SignalServiceAddress(pinnedMessageItem.pinnedMessageAuthorAci),
tx: transaction,
)
let formatString = OWSLocalizedString(
"PINNED_MESSAGE_CHAT_EVENT_OTHER",
comment: "Shown when another user pins a message. Embeds {{ another user }}.",
)
return String.nonPluralLocalizedStringWithFormat(formatString, displayName.resolvedValue())
}
}