Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Messages/Interactions/TSInfoMessage+GroupUpdates+PersistableGroupUpdateItem.swift
1 views
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import Foundation
public import LibSignalClient

extension TSInfoMessage {
    @objc(TSInfoMessageUpdateMessages)
    public class LegacyPersistableGroupUpdateItemsWrapper: NSObject, NSCopying, NSSecureCoding {
        public let updateItems: [LegacyPersistableGroupUpdateItem]

        public init(_ updateItems: [LegacyPersistableGroupUpdateItem]) {
            self.updateItems = updateItems
        }

        // MARK: NSCopying

        public func copy(with _: NSZone? = nil) -> Any {
            self
        }

        // MARK: NSSecureCoding

        public static var supportsSecureCoding: Bool { true }

        private static let messagesKey = "messagesKey"

        public func encode(with coder: NSCoder) {
            let jsonEncoder = JSONEncoder()
            do {
                let messagesData = try jsonEncoder.encode(updateItems)
                coder.encode(messagesData, forKey: Self.messagesKey)
            } catch let error {
                owsFailDebug("Failed to encode updateItems data: \(error)")
                return
            }
        }

        public required init?(coder: NSCoder) {
            guard
                let updateItemsData = coder.decodeObject(
                    of: NSData.self,
                    forKey: Self.messagesKey,
                ) as Data?
            else {
                owsFailDebug("Failed to decode updateItems data")
                return nil
            }

            let jsonDecoder = JSONDecoder()
            do {
                updateItems = try jsonDecoder.decode(
                    [LegacyPersistableGroupUpdateItem].self,
                    from: updateItemsData,
                )
            } catch let error {
                owsFailDebug("Failed to decode updateItems data: \(error)")
                return nil
            }

            super.init()
        }
    }

    @objc(TSInfoMessageUpdateMessagesV2)
    public class PersistableGroupUpdateItemsWrapper: NSObject, NSCopying, NSSecureCoding {
        public let updateItems: [PersistableGroupUpdateItem]

        public init(_ updateItems: [PersistableGroupUpdateItem]) {
            self.updateItems = updateItems
        }

        // MARK: NSCopying

        public func copy(with _: NSZone? = nil) -> Any {
            self
        }

        // MARK: NSSecureCoding

        public static var supportsSecureCoding: Bool { true }

        private static let messagesKey = "messagesKey"

        public func encode(with coder: NSCoder) {
            let jsonEncoder = JSONEncoder()
            do {
                let messagesData = try jsonEncoder.encode(updateItems)
                coder.encode(messagesData, forKey: Self.messagesKey)
            } catch let error {
                owsFailDebug("Failed to encode updateItems data: \(error)")
                return
            }
        }

        public required init?(coder: NSCoder) {
            guard
                let updateItemsData = coder.decodeObject(
                    of: NSData.self,
                    forKey: Self.messagesKey,
                ) as Data?
            else {
                owsFailDebug("Failed to decode updateItems data")
                return nil
            }

            let jsonDecoder = JSONDecoder()
            do {
                updateItems = try jsonDecoder.decode(
                    [PersistableGroupUpdateItem].self,
                    from: updateItemsData,
                )
            } catch let error {
                owsFailDebug("Failed to decode updateItems data: \(error)")
                return nil
            }

            super.init()
        }
    }
}

// MARK: -

extension TSInfoMessage {

    public enum LegacyPersistableGroupUpdateItem: Codable {
        enum CodingKeys: String, CodingKey {
            case sequenceOfInviteLinkRequestAndCancels
            case invitedPniPromotedToFullMemberAci
            case inviteRemoved
        }

        case sequenceOfInviteLinkRequestAndCancels(count: UInt, isTail: Bool)
        case invitedPniPromotedToFullMemberAci(pni: PniUuid, aci: AciUuid)
        case inviteRemoved(invitee: ServiceIdUppercaseString<ServiceId>, wasLocalUser: Bool)

        func toNewItem(
            updater: GroupUpdateSource,
            oldGroupModel: TSGroupModel?,
            localIdentifiers: LocalIdentifiers,
        ) -> PersistableGroupUpdateItem? {
            switch self {
            case .sequenceOfInviteLinkRequestAndCancels(let count, let isTail):
                switch updater {
                case .unknown, .legacyE164, .rejectedInviteToPni, .localUser:
                    owsFailDebug("How did we get one of these without a valid updater? that should be impossible")
                    return nil
                case .aci(let aci):
                    return .sequenceOfInviteLinkRequestAndCancels(
                        requester: aci.codableUuid,
                        count: count,
                        isTail: isTail,
                    )
                }

            case .invitedPniPromotedToFullMemberAci(let pni, let aci):
                return .invitedPniPromotedToFullMemberAci(
                    newMember: aci,
                    inviter: oldGroupModel?.groupMembership.addedByAci(
                        forInvitedMember: SignalServiceAddress(pni.wrappedValue),
                    )?.codableUuid,
                )

            case .inviteRemoved(let invitee, let wasLocalUser):
                let remover: ServiceId
                var wasRejectedInvite = false
                switch updater {
                case .aci(let aci):
                    remover = aci
                case .rejectedInviteToPni(let pni):
                    remover = pni
                    wasRejectedInvite = true
                case .localUser(let originalSource):
                    switch originalSource {
                    case .aci(let aci):
                        remover = aci
                    case .rejectedInviteToPni(let pni):
                        remover = pni
                        wasRejectedInvite = true
                    case .unknown, .legacyE164, .localUser:
                        owsFailDebug("Invalid!")
                        return nil
                    }
                case .unknown, .legacyE164:
                    owsFailDebug("Only acis or pnis can remove an invite")
                    return nil
                }

                let inviterAci = oldGroupModel?.groupMembership.addedByAci(
                    forInvitedMember: SignalServiceAddress(invitee.wrappedValue),
                )

                if wasLocalUser {
                    if wasRejectedInvite || localIdentifiers.contains(serviceId: remover) {
                        // Local user invite that was rejected.
                        if let inviterAci {
                            return .localUserDeclinedInviteFromInviter(
                                inviterAci: inviterAci.codableUuid,
                            )
                        } else {
                            return .localUserDeclinedInviteFromUnknownUser
                        }
                    } else {
                        // Local user invite that was removed by another user.
                        if let removerAci = remover as? Aci {
                            return .localUserInviteRevoked(revokerAci: removerAci.codableUuid)
                        } else {
                            return .localUserInviteRevokedByUnknownUser
                        }
                    }
                } else {
                    if wasRejectedInvite || invitee.wrappedValue == remover {
                        // Other user rejected an invite.
                        if let inviterAci {
                            if inviterAci == localIdentifiers.aci {
                                return .otherUserDeclinedInviteFromLocalUser(invitee: invitee)
                            } else {
                                return .otherUserDeclinedInviteFromInviter(
                                    invitee: invitee,
                                    inviterAci: inviterAci.codableUuid,
                                )
                            }
                        } else {
                            return .otherUserDeclinedInviteFromUnknownUser(invitee: invitee)
                        }
                    } else {
                        // Other user's invite was revoked.
                        if let removerAci = remover as? Aci {
                            if removerAci == localIdentifiers.aci {
                                return .otherUserInviteRevokedByLocalUser(invitee: invitee)
                            } else {
                                return .unnamedUserInvitesWereRevokedByOtherUser(
                                    updaterAci: removerAci.codableUuid,
                                    count: 1,
                                )
                            }
                        } else {
                            return .unnamedUserInvitesWereRevokedByUnknownUser(count: 1)
                        }
                    }
                }
            }
        }
    }

    public enum PersistableGroupUpdateItem: Codable {
        enum CodingKeys: String, CodingKey {
            case sequenceOfInviteLinkRequestAndCancels
            case invitedPniPromotedToFullMemberAci
            case genericUpdateByLocalUser
            case genericUpdateByOtherUser
            case genericUpdateByUnknownUser
            case createdByLocalUser
            case createdByOtherUser
            case createdByUnknownUser
            case inviteFriendsToNewlyCreatedGroup
            case wasMigrated
            case localUserInvitedAfterMigration
            case otherUsersInvitedAfterMigration
            case otherUsersDroppedAfterMigration
            case nameChangedByLocalUser
            case nameChangedByOtherUser
            case nameChangedByUnknownUser
            case nameRemovedByLocalUser
            case nameRemovedByOtherUser
            case nameRemovedByUnknownUser
            case avatarChangedByLocalUser
            case avatarChangedByOtherUser
            case avatarChangedByUnknownUser
            case avatarRemovedByLocalUser
            case avatarRemovedByOtherUser
            case avatarRemovedByUnknownUser
            case descriptionChangedByLocalUser
            case descriptionChangedByOtherUser
            case descriptionChangedByUnknownUser
            case descriptionRemovedByLocalUser
            case descriptionRemovedByOtherUser
            case descriptionRemovedByUnknownUser
            case membersAccessChangedByLocalUser
            case membersAccessChangedByOtherUser
            case membersAccessChangedByUnknownUser
            case attributesAccessChangedByLocalUser
            case attributesAccessChangedByOtherUser
            case attributesAccessChangedByUnknownUser
            case memberLabelsAccessChangedByLocalUser
            case memberLabelsAccessChangedByOtherUser
            case memberLabelsAccessChangedByUnknownUser
            case announcementOnlyEnabledByLocalUser
            case announcementOnlyEnabledByOtherUser
            case announcementOnlyEnabledByUnknownUser
            case announcementOnlyDisabledByLocalUser
            case announcementOnlyDisabledByOtherUser
            case announcementOnlyDisabledByUnknownUser
            case localUserWasGrantedAdministratorByLocalUser
            case localUserWasGrantedAdministratorByOtherUser
            case localUserWasGrantedAdministratorByUnknownUser
            case otherUserWasGrantedAdministratorByLocalUser
            case otherUserWasGrantedAdministratorByOtherUser
            case otherUserWasGrantedAdministratorByUnknownUser
            case localUserWasRevokedAdministratorByLocalUser
            case localUserWasRevokedAdministratorByOtherUser
            case localUserWasRevokedAdministratorByUnknownUser
            case otherUserWasRevokedAdministratorByLocalUser
            case otherUserWasRevokedAdministratorByOtherUser
            case otherUserWasRevokedAdministratorByUnknownUser
            case localUserLeft
            case localUserRemoved
            case localUserRemovedByUnknownUser
            case otherUserLeft
            case otherUserRemovedByLocalUser
            case otherUserRemoved
            case otherUserRemovedByUnknownUser
            case localUserWasInvitedByLocalUser
            case localUserWasInvitedByOtherUser
            case localUserWasInvitedByUnknownUser
            case otherUserWasInvitedByLocalUser
            case unnamedUsersWereInvitedByLocalUser
            case unnamedUsersWereInvitedByOtherUser
            case unnamedUsersWereInvitedByUnknownUser
            case localUserAcceptedInviteFromInviter
            case localUserAcceptedInviteFromUnknownUser
            case otherUserAcceptedInviteFromLocalUser
            case otherUserAcceptedInviteFromInviter
            case otherUserAcceptedInviteFromUnknownUser
            case localUserJoined
            case otherUserJoined
            case localUserAddedByLocalUser
            case localUserAddedByOtherUser
            case localUserAddedByUnknownUser
            case otherUserAddedByLocalUser
            case otherUserAddedByOtherUser
            case otherUserAddedByUnknownUser
            case localUserDeclinedInviteFromInviter
            case localUserDeclinedInviteFromUnknownUser
            case otherUserDeclinedInviteFromLocalUser
            case otherUserDeclinedInviteFromInviter
            case otherUserDeclinedInviteFromUnknownUser
            case unnamedUserDeclinedInviteFromInviter
            case unnamedUserDeclinedInviteFromUnknownUser
            case localUserInviteRevoked
            case localUserInviteRevokedByUnknownUser
            case otherUserInviteRevokedByLocalUser
            case unnamedUserInvitesWereRevokedByLocalUser
            case unnamedUserInvitesWereRevokedByOtherUser
            case unnamedUserInvitesWereRevokedByUnknownUser
            case localUserRequestedToJoin
            case otherUserRequestedToJoin
            case localUserRequestApproved
            case localUserRequestApprovedByUnknownUser
            case otherUserRequestApprovedByLocalUser
            case otherUserRequestApproved
            case otherUserRequestApprovedByUnknownUser
            case localUserRequestCanceledByLocalUser
            case localUserRequestRejectedByUnknownUser
            case otherUserRequestRejectedByLocalUser
            case otherUserRequestRejectedByOtherUser
            case otherUserRequestCanceledByOtherUser
            case otherUserRequestRejectedByUnknownUser
            case disappearingMessagesEnabledByLocalUser
            case disappearingMessagesEnabledByOtherUser
            case disappearingMessagesEnabledByUnknownUser
            case disappearingMessagesDisabledByLocalUser
            case disappearingMessagesDisabledByOtherUser
            case disappearingMessagesDisabledByUnknownUser
            case inviteLinkResetByLocalUser
            case inviteLinkResetByOtherUser
            case inviteLinkResetByUnknownUser
            case inviteLinkEnabledWithoutApprovalByLocalUser
            case inviteLinkEnabledWithoutApprovalByOtherUser
            case inviteLinkEnabledWithoutApprovalByUnknownUser
            case inviteLinkEnabledWithApprovalByLocalUser
            case inviteLinkEnabledWithApprovalByOtherUser
            case inviteLinkEnabledWithApprovalByUnknownUser
            case inviteLinkDisabledByLocalUser
            case inviteLinkDisabledByOtherUser
            case inviteLinkDisabledByUnknownUser
            case inviteLinkApprovalDisabledByLocalUser
            case inviteLinkApprovalDisabledByOtherUser
            case inviteLinkApprovalDisabledByUnknownUser
            case inviteLinkApprovalEnabledByLocalUser
            case inviteLinkApprovalEnabledByOtherUser
            case inviteLinkApprovalEnabledByUnknownUser
            case localUserJoinedViaInviteLink
            case otherUserJoinedViaInviteLink
            case groupTerminatedByLocalUser
            case groupTerminatedByOtherUser
            case groupTerminatedByUnknownUser
        }

        /// Represents a sequence of "request to join" and "canceled request to
        /// join" from the same user.
        ///
        /// - Note
        /// We do not use this case when the requestor is the local user; we show these
        /// as separate ``localUserRequestedToJoin`` and
        /// ``localUserRequestCanceledByLocalUser``.
        case sequenceOfInviteLinkRequestAndCancels(requester: AciUuid, count: UInt, isTail: Bool)

        /// Someone was invited to the group by their PNI, and in accepting the
        /// invite "promoted" their membership from the invited PNI to their
        /// "full member" ACI.
        ///
        /// - Note
        /// The user in question may be another user, or the local user.
        case invitedPniPromotedToFullMemberAci(newMember: AciUuid, inviter: AciUuid?)

        case genericUpdateByLocalUser
        case genericUpdateByOtherUser(updaterAci: AciUuid)
        case genericUpdateByUnknownUser

        case createdByLocalUser
        case createdByOtherUser(updaterAci: AciUuid)
        case createdByUnknownUser

        case inviteFriendsToNewlyCreatedGroup

        /// The group was migrated from gv1 to gv2
        case wasMigrated
        /// As part of a gv1->gv2 migration, the user who did the migration could'nt add the local user
        /// and invited them instead (because the migrating user lacked the local user's profile key).
        /// We have never generated these locally, but they may be present in backups from other clients.
        case localUserInvitedAfterMigration
        /// As part of a gv1->gv2 migration, gv1 members whose acis were known but profile keys
        /// were not known were invited to the new gv2 group.
        /// We have never generated these locally, but they may be present in backups from other clients.
        case otherUsersInvitedAfterMigration(count: UInt)
        /// As part of a gv1->gv2 migration, gv1 members whose acis were not known were removed
        /// from the group.
        /// We have never generated these locally, but they may be present in backups from other clients.
        case otherUsersDroppedAfterMigration(count: UInt)

        case nameChangedByLocalUser(newGroupName: String)
        case nameChangedByOtherUser(updaterAci: AciUuid, newGroupName: String)
        case nameChangedByUnknownUser(newGroupName: String)

        case nameRemovedByLocalUser
        case nameRemovedByOtherUser(updaterAci: AciUuid)
        case nameRemovedByUnknownUser

        case avatarChangedByLocalUser
        case avatarChangedByOtherUser(updaterAci: AciUuid)
        case avatarChangedByUnknownUser

        case avatarRemovedByLocalUser
        case avatarRemovedByOtherUser(updaterAci: AciUuid)
        case avatarRemovedByUnknownUser

        case descriptionChangedByLocalUser(newDescription: String)
        case descriptionChangedByOtherUser(updaterAci: AciUuid, newDescription: String)
        case descriptionChangedByUnknownUser(newDescription: String)

        case descriptionRemovedByLocalUser
        case descriptionRemovedByOtherUser(updaterAci: AciUuid)
        case descriptionRemovedByUnknownUser

        case membersAccessChangedByLocalUser(newAccess: GroupV2Access)
        case membersAccessChangedByOtherUser(updaterAci: AciUuid, newAccess: GroupV2Access)
        case membersAccessChangedByUnknownUser(newAccess: GroupV2Access)

        case attributesAccessChangedByLocalUser(newAccess: GroupV2Access)
        case attributesAccessChangedByOtherUser(updaterAci: AciUuid, newAccess: GroupV2Access)
        case attributesAccessChangedByUnknownUser(newAccess: GroupV2Access)

        case memberLabelsAccessChangedByLocalUser(newAccess: GroupV2Access)
        case memberLabelsAccessChangedByOtherUser(updaterAci: AciUuid, newAccess: GroupV2Access)
        case memberLabelsAccessChangedByUnknownUser(newAccess: GroupV2Access)

        case announcementOnlyEnabledByLocalUser
        case announcementOnlyEnabledByOtherUser(updaterAci: AciUuid)
        case announcementOnlyEnabledByUnknownUser

        case announcementOnlyDisabledByLocalUser
        case announcementOnlyDisabledByOtherUser(updaterAci: AciUuid)
        case announcementOnlyDisabledByUnknownUser

        case localUserWasGrantedAdministratorByLocalUser
        case localUserWasGrantedAdministratorByOtherUser(updaterAci: AciUuid)
        case localUserWasGrantedAdministratorByUnknownUser

        case otherUserWasGrantedAdministratorByLocalUser(userAci: AciUuid)
        case otherUserWasGrantedAdministratorByOtherUser(updaterAci: AciUuid, userAci: AciUuid)
        case otherUserWasGrantedAdministratorByUnknownUser(userAci: AciUuid)

        case localUserWasRevokedAdministratorByLocalUser
        case localUserWasRevokedAdministratorByOtherUser(updaterAci: AciUuid)
        case localUserWasRevokedAdministratorByUnknownUser

        case otherUserWasRevokedAdministratorByLocalUser(userAci: AciUuid)
        case otherUserWasRevokedAdministratorByOtherUser(updaterAci: AciUuid, userAci: AciUuid)
        case otherUserWasRevokedAdministratorByUnknownUser(userAci: AciUuid)

        case localUserLeft
        case localUserRemoved(removerAci: AciUuid)
        case localUserRemovedByUnknownUser

        case otherUserLeft(userAci: AciUuid)
        case otherUserRemovedByLocalUser(userAci: AciUuid)
        case otherUserRemoved(removerAci: AciUuid, userAci: AciUuid)
        case otherUserRemovedByUnknownUser(userAci: AciUuid)

        case localUserWasInvitedByLocalUser
        case localUserWasInvitedByOtherUser(updaterAci: AciUuid)
        case localUserWasInvitedByUnknownUser

        case otherUserWasInvitedByLocalUser(inviteeServiceId: ServiceIdUppercaseString<ServiceId>)

        case unnamedUsersWereInvitedByLocalUser(count: UInt)
        case unnamedUsersWereInvitedByOtherUser(updaterAci: AciUuid, count: UInt)
        case unnamedUsersWereInvitedByUnknownUser(count: UInt)

        case localUserAcceptedInviteFromInviter(inviterAci: AciUuid)
        case localUserAcceptedInviteFromUnknownUser
        case otherUserAcceptedInviteFromLocalUser(userAci: AciUuid)
        case otherUserAcceptedInviteFromInviter(userAci: AciUuid, inviterAci: AciUuid)
        case otherUserAcceptedInviteFromUnknownUser(userAci: AciUuid)

        case localUserJoined
        case otherUserJoined(userAci: AciUuid)

        case localUserAddedByLocalUser
        case localUserAddedByOtherUser(updaterAci: AciUuid)
        case localUserAddedByUnknownUser

        case otherUserAddedByLocalUser(userAci: AciUuid)
        case otherUserAddedByOtherUser(updaterAci: AciUuid, userAci: AciUuid)
        case otherUserAddedByUnknownUser(userAci: AciUuid)

        case localUserDeclinedInviteFromInviter(inviterAci: AciUuid)
        case localUserDeclinedInviteFromUnknownUser
        case otherUserDeclinedInviteFromLocalUser(invitee: ServiceIdUppercaseString<ServiceId>)
        case otherUserDeclinedInviteFromInviter(invitee: ServiceIdUppercaseString<ServiceId>, inviterAci: AciUuid)
        case otherUserDeclinedInviteFromUnknownUser(invitee: ServiceIdUppercaseString<ServiceId>)
        case unnamedUserDeclinedInviteFromInviter(inviterAci: AciUuid)
        case unnamedUserDeclinedInviteFromUnknownUser

        case localUserInviteRevoked(revokerAci: AciUuid)
        case localUserInviteRevokedByUnknownUser
        // For a single invite we revoked we keep the invitee.
        // For many, or if someone else revoked, we just keep the number.
        case otherUserInviteRevokedByLocalUser(invitee: ServiceIdUppercaseString<ServiceId>)
        case unnamedUserInvitesWereRevokedByLocalUser(count: UInt)
        case unnamedUserInvitesWereRevokedByOtherUser(updaterAci: AciUuid, count: UInt)
        case unnamedUserInvitesWereRevokedByUnknownUser(count: UInt)

        case localUserRequestedToJoin
        case otherUserRequestedToJoin(userAci: AciUuid)

        case localUserRequestApproved(approverAci: AciUuid)
        case localUserRequestApprovedByUnknownUser
        case otherUserRequestApprovedByLocalUser(userAci: AciUuid)
        case otherUserRequestApproved(userAci: AciUuid, approverAci: AciUuid)
        case otherUserRequestApprovedByUnknownUser(userAci: AciUuid)

        case localUserRequestCanceledByLocalUser
        case localUserRequestRejectedByUnknownUser

        case otherUserRequestRejectedByLocalUser(requesterAci: AciUuid)
        case otherUserRequestRejectedByOtherUser(updaterAci: AciUuid, requesterAci: AciUuid)
        case otherUserRequestCanceledByOtherUser(requesterAci: AciUuid)
        case otherUserRequestRejectedByUnknownUser(requesterAci: AciUuid)

        case disappearingMessagesEnabledByLocalUser(durationMs: UInt64)
        case disappearingMessagesEnabledByOtherUser(updaterAci: AciUuid, durationMs: UInt64)
        case disappearingMessagesEnabledByUnknownUser(durationMs: UInt64)

        case disappearingMessagesDisabledByLocalUser
        case disappearingMessagesDisabledByOtherUser(updaterAci: AciUuid)
        case disappearingMessagesDisabledByUnknownUser

        case inviteLinkResetByLocalUser
        case inviteLinkResetByOtherUser(updaterAci: AciUuid)
        case inviteLinkResetByUnknownUser

        case inviteLinkEnabledWithoutApprovalByLocalUser
        case inviteLinkEnabledWithoutApprovalByOtherUser(updaterAci: AciUuid)
        case inviteLinkEnabledWithoutApprovalByUnknownUser

        case inviteLinkEnabledWithApprovalByLocalUser
        case inviteLinkEnabledWithApprovalByOtherUser(updaterAci: AciUuid)
        case inviteLinkEnabledWithApprovalByUnknownUser

        case inviteLinkDisabledByLocalUser
        case inviteLinkDisabledByOtherUser(updaterAci: AciUuid)
        case inviteLinkDisabledByUnknownUser

        case inviteLinkApprovalDisabledByLocalUser
        case inviteLinkApprovalDisabledByOtherUser(updaterAci: AciUuid)
        case inviteLinkApprovalDisabledByUnknownUser

        case inviteLinkApprovalEnabledByLocalUser
        case inviteLinkApprovalEnabledByOtherUser(updaterAci: AciUuid)
        case inviteLinkApprovalEnabledByUnknownUser

        case localUserJoinedViaInviteLink
        case otherUserJoinedViaInviteLink(userAci: AciUuid)

        case groupTerminatedByLocalUser
        case groupTerminatedByOtherUser(updaterAci: AciUuid)
        case groupTerminatedByUnknownUser
    }
}

extension TSInfoMessage.PersistableGroupUpdateItem {
    public var aciForSpamReporting: AciUuid? {
        switch self {
        case
            .genericUpdateByOtherUser(let updaterAci),
            .createdByOtherUser(let updaterAci),
            .nameChangedByOtherUser(let updaterAci, _),
            .nameRemovedByOtherUser(let updaterAci),
            .avatarChangedByOtherUser(let updaterAci),
            .avatarRemovedByOtherUser(let updaterAci),
            .descriptionChangedByOtherUser(let updaterAci, _),
            .descriptionRemovedByOtherUser(let updaterAci),
            .membersAccessChangedByOtherUser(let updaterAci, _),
            .attributesAccessChangedByOtherUser(let updaterAci, _),
            .announcementOnlyEnabledByOtherUser(let updaterAci),
            .announcementOnlyDisabledByOtherUser(let updaterAci),
            .localUserWasGrantedAdministratorByOtherUser(let updaterAci),
            .otherUserWasGrantedAdministratorByLocalUser(let updaterAci),
            .otherUserWasGrantedAdministratorByOtherUser(let updaterAci, _),
            .otherUserWasGrantedAdministratorByUnknownUser(let updaterAci),
            .localUserWasRevokedAdministratorByOtherUser(let updaterAci),
            .otherUserWasRevokedAdministratorByOtherUser(let updaterAci, _),
            .localUserRemoved(let updaterAci),
            .otherUserRemoved(let updaterAci, _),
            .localUserWasInvitedByOtherUser(let updaterAci),
            .unnamedUsersWereInvitedByOtherUser(let updaterAci, _),
            .localUserAcceptedInviteFromInviter(let updaterAci),
            .otherUserAcceptedInviteFromInviter(let updaterAci, _),
            .localUserAddedByOtherUser(let updaterAci),
            .otherUserAddedByOtherUser(let updaterAci, _),
            .localUserDeclinedInviteFromInviter(let updaterAci),
            .otherUserDeclinedInviteFromInviter(_, let updaterAci),
            .unnamedUserDeclinedInviteFromInviter(let updaterAci),
            .unnamedUserInvitesWereRevokedByOtherUser(let updaterAci, _),
            .otherUserRequestApproved(_, let updaterAci),
            .otherUserRequestRejectedByOtherUser(let updaterAci, _),
            .disappearingMessagesEnabledByOtherUser(let updaterAci, _),
            .disappearingMessagesDisabledByOtherUser(let updaterAci),
            .inviteLinkResetByOtherUser(let updaterAci),
            .inviteLinkEnabledWithoutApprovalByOtherUser(let updaterAci),
            .inviteLinkEnabledWithApprovalByOtherUser(let updaterAci),
            .inviteLinkDisabledByOtherUser(let updaterAci),
            .inviteLinkApprovalDisabledByOtherUser(let updaterAci),
            .inviteLinkApprovalEnabledByOtherUser(let updaterAci),
            .memberLabelsAccessChangedByOtherUser(let updaterAci, _),
            .groupTerminatedByOtherUser(let updaterAci):
            return updaterAci
        case
            .sequenceOfInviteLinkRequestAndCancels,
            .invitedPniPromotedToFullMemberAci,
            .genericUpdateByLocalUser,
            .genericUpdateByUnknownUser,
            .createdByLocalUser,
            .createdByUnknownUser,
            .inviteFriendsToNewlyCreatedGroup,
            .wasMigrated,
            .localUserInvitedAfterMigration,
            .otherUsersInvitedAfterMigration,
            .otherUsersDroppedAfterMigration,
            .nameChangedByLocalUser,
            .nameChangedByUnknownUser,
            .nameRemovedByLocalUser,
            .nameRemovedByUnknownUser,
            .avatarChangedByLocalUser,
            .avatarChangedByUnknownUser,
            .avatarRemovedByLocalUser,
            .avatarRemovedByUnknownUser,
            .descriptionChangedByLocalUser,
            .descriptionChangedByUnknownUser,
            .descriptionRemovedByLocalUser,
            .descriptionRemovedByUnknownUser,
            .membersAccessChangedByLocalUser,
            .membersAccessChangedByUnknownUser,
            .attributesAccessChangedByLocalUser,
            .attributesAccessChangedByUnknownUser,
            .announcementOnlyEnabledByLocalUser,
            .announcementOnlyEnabledByUnknownUser,
            .announcementOnlyDisabledByLocalUser,
            .announcementOnlyDisabledByUnknownUser,
            .localUserWasGrantedAdministratorByLocalUser,
            .localUserWasGrantedAdministratorByUnknownUser,
            .localUserWasRevokedAdministratorByLocalUser,
            .localUserWasRevokedAdministratorByUnknownUser,
            .otherUserWasRevokedAdministratorByLocalUser,
            .otherUserWasRevokedAdministratorByUnknownUser,
            .localUserLeft,
            .localUserRemovedByUnknownUser,
            .otherUserLeft,
            .otherUserRemovedByLocalUser,
            .otherUserRemovedByUnknownUser,
            .localUserWasInvitedByLocalUser,
            .localUserWasInvitedByUnknownUser,
            .otherUserWasInvitedByLocalUser,
            .unnamedUsersWereInvitedByLocalUser,
            .unnamedUsersWereInvitedByUnknownUser,
            .localUserAcceptedInviteFromUnknownUser,
            .otherUserAcceptedInviteFromLocalUser,
            .otherUserAcceptedInviteFromUnknownUser,
            .localUserJoined,
            .otherUserJoined,
            .localUserAddedByLocalUser,
            .localUserAddedByUnknownUser,
            .otherUserAddedByLocalUser,
            .otherUserAddedByUnknownUser,
            .localUserDeclinedInviteFromUnknownUser,
            .otherUserDeclinedInviteFromLocalUser,
            .otherUserDeclinedInviteFromUnknownUser,
            .unnamedUserDeclinedInviteFromUnknownUser,
            .localUserInviteRevoked,
            .localUserInviteRevokedByUnknownUser,
            .otherUserInviteRevokedByLocalUser,
            .unnamedUserInvitesWereRevokedByLocalUser,
            .unnamedUserInvitesWereRevokedByUnknownUser,
            .localUserRequestedToJoin,
            .otherUserRequestedToJoin,
            .localUserRequestApproved,
            .localUserRequestApprovedByUnknownUser,
            .otherUserRequestApprovedByLocalUser,
            .otherUserRequestApprovedByUnknownUser,
            .localUserRequestCanceledByLocalUser,
            .localUserRequestRejectedByUnknownUser,
            .otherUserRequestRejectedByLocalUser,
            .otherUserRequestRejectedByUnknownUser,
            .otherUserRequestCanceledByOtherUser,
            .disappearingMessagesEnabledByLocalUser,
            .disappearingMessagesEnabledByUnknownUser,
            .disappearingMessagesDisabledByLocalUser,
            .disappearingMessagesDisabledByUnknownUser,
            .inviteLinkResetByLocalUser,
            .inviteLinkResetByUnknownUser,
            .inviteLinkEnabledWithoutApprovalByLocalUser,
            .inviteLinkEnabledWithoutApprovalByUnknownUser,
            .inviteLinkEnabledWithApprovalByLocalUser,
            .inviteLinkEnabledWithApprovalByUnknownUser,
            .inviteLinkDisabledByLocalUser,
            .inviteLinkDisabledByUnknownUser,
            .inviteLinkApprovalEnabledByLocalUser,
            .inviteLinkApprovalEnabledByUnknownUser,
            .localUserJoinedViaInviteLink,
            .inviteLinkApprovalDisabledByUnknownUser,
            .otherUserJoinedViaInviteLink,
            .inviteLinkApprovalDisabledByLocalUser,
            .memberLabelsAccessChangedByLocalUser,
            .memberLabelsAccessChangedByUnknownUser,
            .groupTerminatedByLocalUser,
            .groupTerminatedByUnknownUser:
            return nil
        }
    }
}