//
// Copyright 2014 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

syntax = "proto3";

package BackupProto;

message BackupProto {
  message BackupInfo {
    uint64 version = 1;
    uint64 backupTimeMs = 2;
  }

  message Frame {
    oneof item {
      AccountData account = 1;
      Recipient recipient = 2;
      Chat chat = 3;
      ChatItem chatItem = 4;
      StickerPack stickerPack = 5;
    }
  }

  message AccountData {
    enum PhoneNumberSharingMode {
      UNKNOWN = 0;
      EVERYBODY = 1;
      NOBODY = 2;
    }

    message UsernameLink {
      enum Color {
        UNKNOWN = 0;
        BLUE = 1;
        WHITE = 2;
        GREY = 3;
        OLIVE = 4;
        GREEN = 5;
        ORANGE = 6;
        PINK = 7;
        PURPLE = 8;
      }

      bytes entropy = 1;  // 32 bytes of entropy used for encryption
      bytes serverId = 2; // 16 bytes of encoded UUID provided by the server
      optional Color color = 3;
    }

    message AccountSettings {
      bool readReceipts = 1;
      bool sealedSenderIndicators = 2;
      bool typingIndicators = 3;
      bool linkPreviews = 4;
      bool notDiscoverableByPhoneNumber = 5;
      bool preferContactAvatars = 6;
      uint32 universalExpireTimer = 7; // 0 means no universal expire timer.
      repeated string preferredReactionEmoji = 8;
      bool displayBadgesOnProfile = 9;
      bool keepMutedChatsArchived = 10;
      bool hasSetMyStoriesPrivacy = 11;
      bool hasViewedOnboardingStory = 12;
      bool storiesDisabled = 13;
      optional bool storyViewReceiptsEnabled = 14;
      bool hasSeenGroupStoryEducationSheet = 15;
      bool hasCompletedUsernameOnboarding = 16;
      optional PhoneNumberSharingMode phoneNumberSharingMode = 17;
    }

    bytes profileKey = 1;
    optional string username = 2;
    UsernameLink usernameLink = 3;
    string givenName = 4;
    string familyName = 5;
    string avatarUrlPath = 6;
    bytes subscriberId = 7;
    string subscriberCurrencyCode = 8;
    bool subscriptionManuallyCancelled = 9;
    AccountSettings accountSettings = 10;
  }

  message Recipient {
    uint64 id = 1; // generated id for reference only within this file
    oneof destination {
      Contact contact = 2;
      Group group = 3;
      DistributionListItem distributionList = 4;
      SelfRecipient selfRecipient = 5;
      ReleaseNotes releaseNotes = 6;
    }
  }

  message Contact {
    enum Registered {
      UNKNOWN = 0;
      REGISTERED = 1;
      NOT_REGISTERED = 2;
    }

    optional bytes aci = 1; // should be 16 bytes
    optional bytes pni = 2; // should be 16 bytes
    optional string username = 3;
    optional uint64 e164 = 4;
    bool blocked = 5;
    bool hidden = 6;
    optional Registered registered = 7;
    uint64 unregisteredTimestamp = 8;
    optional bytes profileKey = 9;
    bool profileSharing = 10;
    optional string profileGivenName = 11;
    optional string profileFamilyName = 12;
    bool hideStory = 13;
  }

  message Group {
    enum StorySendMode {
      DEFAULT = 0;
      DISABLED = 1;
      ENABLED = 2;
    }

    bytes masterKey = 1;
    bool whitelisted = 2;
    bool hideStory = 3;
    optional StorySendMode storySendMode = 4;
    GroupSnapshot snapshot = 5;

    // A copy of the static Group state defined in `Groups.proto`, with all
    // plaintext fields (contrasted with that proto's encrypted fields).
    message GroupSnapshot {
      bytes publicKey = 1;
      GroupAttributeBlob title = 2;
      GroupAttributeBlob descriptionText = 11;
      string avatarUrl = 3;
      GroupAttributeBlob disappearingMessagesTimer = 4;
      AccessControl accessControl = 5;
      uint32 version = 6;
      repeated Member members = 7;
      repeated MemberPendingProfileKey membersPendingProfileKey = 8;
      repeated MemberPendingAdminApproval membersPendingAdminApproval = 9;
      bytes inviteLinkPassword = 10;
      bool announcementsOnly = 12;
      repeated MemberBanned membersBanned = 13;
    }

    message GroupAttributeBlob {
      oneof content {
        string title = 1;
        bytes  avatar = 2;
        uint32 disappearingMessagesDuration = 3;
        string descriptionText = 4;
      }
    }

    message Member {
      enum Role {
        UNKNOWN = 0;
        DEFAULT = 1;
        ADMINISTRATOR = 2;
      }

      bytes userId = 1;
      Role role = 2;
      bytes profileKey = 3;
      reserved /*presentation*/ 4; // The field is deprecated in the context of static group state
      uint32 joinedAtVersion = 5;
    }

    message MemberPendingProfileKey {
      Member member = 1;
      bytes addedByUserId = 2;
      uint64 timestamp = 3;
    }

    message MemberPendingAdminApproval {
      bytes userId = 1;
      bytes profileKey = 2;
      reserved /*presentation*/ 3; // The field is deprecated in the context of static group state
      uint64 timestamp = 4;
    }

    message MemberBanned {
      bytes userId = 1;
      uint64 timestamp = 2;
    }

    message AccessControl {
      enum AccessRequired {
        UNKNOWN = 0;
        ANY = 1;
        MEMBER = 2;
        ADMINISTRATOR = 3;
        UNSATISFIABLE = 4;
      }

      AccessRequired attributes = 1;
      AccessRequired members = 2;
      AccessRequired addFromInviteLink = 3;
    }
  }

  message SelfRecipient {}

  message ReleaseNotes {}

  message Chat {
    uint64 id = 1; // generated id for reference only within this file
    uint64 recipientId = 2;
    bool archived = 3;
    uint32 pinnedOrder = 4; // 0 = unpinned, otherwise chat is considered pinned and will be displayed in ascending order
    uint64 expirationTimerMs = 5; // 0 = no expire timer.
    uint64 muteUntilMs = 6;
    bool markedUnread = 7;
    bool dontNotifyForMentionsIfMuted = 8;
    optional FilePointer wallpaper = 9;
  }

  message DistributionListItem {
    bytes distributionId = 1; // distribution list ids are uuids
    oneof item {
      uint64 deletionTimestamp = 2;
      DistributionList distributionList = 3;
    }
  }

  message DistributionList {
    enum PrivacyMode {
      UNKNOWN = 0;
      ONLY_WITH = 1;
      ALL_EXCEPT = 2;
      ALL = 3;
    }

    string name = 1;
    bool allowReplies = 2;
    PrivacyMode privacyMode = 3;
    repeated uint64 memberRecipientIds = 4; // generated recipient id
  }

  message Identity {
    bytes serviceId = 1;
    bytes identityKey = 2;
    uint64 timestamp = 3;
    bool firstUse = 4;
    bool verified = 5;
    bool nonblockingApproval = 6;
  }

  message ChatItem {
    message IncomingMessageDetails {
      uint64 dateReceived = 1;
      uint64 dateServerSent = 2;
      bool read = 3;
      bool sealedSender = 4;
    }

    message OutgoingMessageDetails {
      repeated SendStatus sendStatus = 1;
    }

    message DirectionlessMessageDetails {
    }

    uint64 chatId = 1;   // conversation id
    uint64 authorId = 2; // recipient id
    uint64 dateSent = 3;
    optional uint64 expireStartDate = 4; // timestamp of when expiration timer started ticking down
    optional uint64 expiresInMs = 5; // how long timer of message is (ms)
    repeated ChatItem revisions = 6; // ordered from oldest to newest
    bool sms = 7;

    oneof directionalDetails {
      IncomingMessageDetails incoming = 8;
      OutgoingMessageDetails outgoing = 9;
      DirectionlessMessageDetails directionless = 10;
    }

    oneof item {
      StandardMessage standardMessage = 11;
      ContactMessage contactMessage = 12;
      StickerMessage stickerMessage = 13;
      RemoteDeletedMessage remoteDeletedMessage = 14;
      ChatUpdateMessage updateMessage = 15;
      PaymentNotification paymentNotification = 16;
    }
  }

  message SendStatus {
    enum Status {
      UNKNOWN = 0;
      FAILED = 1;
      PENDING = 2;
      SENT = 3;
      DELIVERED = 4;
      READ = 5;
      VIEWED = 6;
      SKIPPED = 7; // e.g. user in group was blocked, so we skipped sending to them
    }
    uint64 recipientId = 1;
    optional Status deliveryStatus = 2;
    bool networkFailure = 3;
    bool identityKeyMismatch = 4;
    bool sealedSender = 5;
    uint64 lastStatusUpdateTimestamp = 6; // the time the status was last updated -- if from a receipt, it should be the sentTime of the receipt
  }

  message Text {
    string body = 1;
    repeated BodyRange bodyRanges = 2;
  }

  message StandardMessage {
    optional Quote quote = 1;
    optional Text text = 2;
    repeated FilePointer attachments = 3;
    repeated LinkPreview linkPreview = 4;
    optional FilePointer longText = 5;
    repeated Reaction reactions = 6;
  }

  message ContactMessage {
    repeated ContactAttachment contact = 1;
    repeated Reaction reactions = 2;
  }

  message PaymentNotification {
    message TransactionDetails {
      message MobileCoinTxoIdentification { // Used to map to payments on the ledger
        repeated bytes publicKey = 1; // for received transactions
        repeated bytes keyImages = 2; // for sent transactions
      }

      message FailedTransaction { // Failed payments can't be synced from the ledger
        enum FailureReason {
          GENERIC = 0;
          NETWORK = 1;
          INSUFFICIENT_FUNDS = 2;
        }
        FailureReason reason = 1;
      }

      message Transaction {
        enum Status {
          INITIAL = 0;
          SUBMITTED = 1;
          SUCCESSFUL = 2;
        }
        Status status = 1;

        // This identification is used to map the payment table to the ledger
        // and is likely required otherwise we may have issues reconciling with
        // the ledger
        MobileCoinTxoIdentification mobileCoinIdentification = 2;
        optional uint64 timestamp = 3;
        optional uint64 blockIndex = 4;
        optional uint64 blockTimestamp = 5;
        optional bytes transaction = 6; // mobile coin blobs
        optional bytes receipt = 7; // mobile coin blobs
      }

      oneof payment {
        Transaction transaction = 1;
        FailedTransaction failedTransaction = 2;
      }
    }

    optional string amountMob = 1; // stored as a decimal string, e.g. 1.00001
    optional string feeMob = 2; // stored as a decimal string, e.g. 1.00001
    optional string note = 3;
    TransactionDetails transactionDetails = 4;

  }

  message ContactAttachment {
    message Name {
      optional string givenName = 1;
      optional string familyName = 2;
      optional string prefix = 3;
      optional string suffix = 4;
      optional string middleName = 5;
      optional string displayName = 6;
    }

    message Phone {
      enum Type {
        UNKNOWN = 0;
        HOME = 1;
        MOBILE = 2;
        WORK = 3;
        CUSTOM = 4;
      }

      optional string value = 1;
      optional Type type = 2;
      optional string label = 3;
    }

    message Email {
      enum Type {
        UNKNOWN = 0;
        HOME = 1;
        MOBILE = 2;
        WORK = 3;
        CUSTOM = 4;
      }

      optional string value = 1;
      optional Type type = 2;
      optional string label = 3;
    }

    message PostalAddress {
      enum Type {
        UNKNOWN = 0;
        HOME = 1;
        WORK = 2;
        CUSTOM = 3;
      }

      optional Type type = 1;
      optional string label = 2;
      optional string street = 3;
      optional string pobox = 4;
      optional string neighborhood = 5;
      optional string city = 6;
      optional string region = 7;
      optional string postcode = 8;
      optional string country = 9;
    }

    message Avatar {
      FilePointer avatar = 1;
    }

    optional Name name = 1;
    repeated Phone number = 3;
    repeated Email email = 4;
    repeated PostalAddress address = 5;
    optional Avatar avatar = 6;
    optional string organization = 7;
  }

  message DocumentMessage {
    Text text = 1;
    FilePointer document = 2;
    repeated Reaction reactions = 3;
  }

  message VoiceMessage {
    optional Quote quote = 1;
    FilePointer audio = 2;
    repeated Reaction reactions = 3;
  }

  message StickerMessage {
    Sticker sticker = 1;
    repeated Reaction reactions = 2;
  }

  // Tombstone for remote delete
  message RemoteDeletedMessage {}

  message Sticker {
    bytes packId = 1;
    bytes packKey = 2;
    uint32 stickerId = 3;
    optional string emoji = 4;
  }

  message LinkPreview {
    string url = 1;
    optional string title = 2;
    optional FilePointer image = 3;
    optional string description = 4;
    optional uint64 date = 5;
  }

  message FilePointer {
    message BackupLocator {
      string mediaName = 1;
      uint32 cdnNumber = 2;
    }

    message AttachmentLocator {
      string cdnKey = 1;
      uint32 cdnNumber = 2;
      uint64 uploadTimestamp = 3;
    }

    message LegacyAttachmentLocator {
      fixed64 cdnId = 1;
    }

    // An attachment that was backed up without being downloaded.
    // Its MediaName should be generated as “{sender_aci}_{cdn_attachment_key}”,
    // but should eventually transition to a BackupLocator with mediaName
    // being the content hash once it is downloaded.
    message UndownloadedBackupLocator {
      bytes senderAci = 1;
      string cdnKey = 2;
      uint32 cdnNumber = 3;
    }

    enum Flags {
      VOICE_MESSAGE = 0;
      BORDERLESS = 1;
      GIF = 2;
    }

    oneof locator {
      BackupLocator backupLocator = 1;
      AttachmentLocator attachmentLocator= 2;
      LegacyAttachmentLocator legacyAttachmentLocator = 3;
      UndownloadedBackupLocator undownloadedBackupLocator = 4;
    }

    optional bytes key = 5;
    optional string contentType = 6;
    // Size of fullsize decrypted media blob in bytes.
    // Can be ignored if unset/unavailable.
    optional uint32 size = 7;
    optional bytes incrementalMac = 8;
    optional bytes incrementalMacChunkSize = 9;
    optional string fileName = 10;
    optional uint32 flags = 11;
    optional uint32 width = 12;
    optional uint32 height = 13;
    optional string caption = 14;
    optional string blurHash = 15;
  }

  message Quote {
    enum Type {
      UNKNOWN = 0;
      NORMAL = 1;
      GIFTBADGE = 2;
    }

    message QuotedAttachment {
      optional string contentType = 1;
      optional string fileName = 2;
      optional FilePointer thumbnail = 3;
    }

    optional uint64 targetSentTimestamp = 1; // null if the target message could not be found at time of quote insert
    uint64 authorId = 2;
    optional string text = 3;
    repeated QuotedAttachment attachments = 4;
    repeated BodyRange bodyRanges = 5;
    optional Type type = 6;
  }

  message BodyRange {
    enum Style {
      NONE = 0;
      BOLD = 1;
      ITALIC = 2;
      SPOILER = 3;
      STRIKETHROUGH = 4;
      MONOSPACE = 5;
    }

    optional uint32 start = 1;
    optional uint32 length = 2;

    oneof associatedValue {
      bytes mentionAci = 3;
      Style style = 4;
    }
  }

  message Reaction {
    string emoji = 1;
    uint64 authorId = 2;
    uint64 sentTimestamp = 3;
    optional uint64 receivedTimestamp = 4;
    uint64 sortOrder = 5; // A higher sort order means that a reaction is more recent
  }

  message ChatUpdateMessage {
    oneof update {
      SimpleChatUpdate simpleUpdate = 1;
      GroupChangeChatUpdate groupChange = 2;
      ExpirationTimerChatUpdate expirationTimerChange = 3;
      ProfileChangeChatUpdate profileChange = 4;
      ThreadMergeChatUpdate threadMerge = 5;
      SessionSwitchoverChatUpdate sessionSwitchover = 6;
      IndividualCall individualCall = 7;
      GroupCall groupCall = 8;
    }
  }

  message IndividualCall {
    enum Type {
      UNKNOWN_TYPE = 0;
      AUDIO_CALL = 1;
      VIDEO_CALL = 2;
    }

    enum Direction {
      UNKNOWN_DIRECTION = 0;
      INCOMING = 1;
      OUTGOING = 2;
    }

    enum State {
      UNKNOWN_STATE = 0;
      ACCEPTED = 1;
      NOT_ACCEPTED = 2;
      // An incoming call that is no longer ongoing, which we neither accepted
      // not actively declined. For example, it expired, was canceled by the
      // sender, or was rejected due to being in another call.
      MISSED = 3;
      // We auto-declined an incoming call due to a notification profile.
      MISSED_NOTIFICATION_PROFILE = 4;
    }

    optional uint64 callId = 1;
    Type type = 2;
    Direction direction = 3;
    State state = 4;
    uint64 startedCallTimestamp = 5;
  }

  message GroupCall {
    enum State {
      UNKNOWN_STATE = 0;
      // A group call was started without ringing.
      GENERIC = 1;
      // We joined a group call that was started without ringing.
      JOINED = 2;
      // An incoming group call is actively ringing.
      RINGING = 3;
      // We accepted an incoming group ring.
      ACCEPTED = 4;
      // We declined an incoming group ring.
      DECLINED = 5;
      // We missed an incoming group ring, for example because it expired.
      MISSED = 6;
      // We auto-declined an incoming group ring due to a notification profile.
      MISSED_NOTIFICATION_PROFILE = 7;
      // An outgoing ring was started. We don't track any state for outgoing rings
      // beyond that they started.
      OUTGOING_RING = 8;
    }

    optional uint64 callId = 1;
    State state = 2;
    optional uint64 ringerRecipientId = 3;
    optional uint64 startedCallRecipientId = 4;
    uint64 startedCallTimestamp = 5;
    // The time the call ended. 0 indicates an unknown time.
    uint64 endedCallTimestamp = 6;
  }

  message SimpleChatUpdate {
    enum Type {
      UNKNOWN = 0;
      JOINED_SIGNAL = 1;
      IDENTITY_UPDATE = 2;
      IDENTITY_VERIFIED = 3;
      IDENTITY_DEFAULT = 4; // marking as unverified
      CHANGE_NUMBER = 5;
      BOOST_REQUEST = 6;
      END_SESSION = 7;
      CHAT_SESSION_REFRESH = 8;
      BAD_DECRYPT = 9;
      PAYMENTS_ACTIVATED = 10;
      PAYMENT_ACTIVATION_REQUEST = 11;
    }

    optional Type type = 1;
  }

  message GroupDescriptionChatUpdate {
    string newDescription = 1;
  }

  // For 1:1 chat updates only.
  // For group thread updates use GroupExpirationTimerUpdate.
  message ExpirationTimerChatUpdate {
    uint32 expiresInMs = 1; // 0 means the expiration timer was disabled
  }

  message ProfileChangeChatUpdate {
    string previousName = 1;
    string newName = 2;
  }

  message ThreadMergeChatUpdate {
    uint64 previousE164 = 1;
  }

  message SessionSwitchoverChatUpdate {
    uint64 e164 = 1;
  }

  message GroupChangeChatUpdate {
    message Update {
      // Note: group expiration timer changes are represented as ExpirationTimerChatUpdate.
      oneof update {
        GenericGroupUpdate genericGroupUpdate = 1;
        GroupCreationUpdate groupCreationUpdate = 2;
        GroupNameUpdate groupNameUpdate = 3;
        GroupAvatarUpdate groupAvatarUpdate = 4;
        GroupDescriptionUpdate groupDescriptionUpdate = 5;
        GroupMembershipAccessLevelChangeUpdate groupMembershipAccessLevelChangeUpdate = 6;
        GroupAttributesAccessLevelChangeUpdate groupAttributesAccessLevelChangeUpdate = 7;
        GroupAnnouncementOnlyChangeUpdate groupAnnouncementOnlyChangeUpdate = 8;
        GroupAdminStatusUpdate groupAdminStatusUpdate = 9;
        GroupMemberLeftUpdate groupMemberLeftUpdate = 10;
        GroupMemberRemovedUpdate groupMemberRemovedUpdate = 11;
        SelfInvitedToGroupUpdate selfInvitedToGroupUpdate = 12;
        SelfInvitedOtherUserToGroupUpdate selfInvitedOtherUserToGroupUpdate = 13;
        GroupUnknownInviteeUpdate groupUnknownInviteeUpdate = 14;
        GroupInvitationAcceptedUpdate groupInvitationAcceptedUpdate = 15;
        GroupInvitationDeclinedUpdate groupInvitationDeclinedUpdate = 16;
        GroupMemberJoinedUpdate groupMemberJoinedUpdate = 17;
        GroupMemberAddedUpdate groupMemberAddedUpdate = 18;
        GroupSelfInvitationRevokedUpdate groupSelfInvitationRevokedUpdate = 19;
        GroupInvitationRevokedUpdate groupInvitationRevokedUpdate = 20;
        GroupJoinRequestUpdate groupJoinRequestUpdate = 21;
        GroupJoinRequestApprovalUpdate groupJoinRequestApprovalUpdate = 22;
        GroupJoinRequestCanceledUpdate groupJoinRequestCanceledUpdate = 23;
        GroupInviteLinkResetUpdate groupInviteLinkResetUpdate = 24;
        GroupInviteLinkEnabledUpdate groupInviteLinkEnabledUpdate = 25;
        GroupInviteLinkAdminApprovalUpdate groupInviteLinkAdminApprovalUpdate = 26;
        GroupInviteLinkDisabledUpdate groupInviteLinkDisabledUpdate = 27;
        GroupMemberJoinedByLinkUpdate groupMemberJoinedByLinkUpdate = 28;
        GroupV2MigrationUpdate groupV2MigrationUpdate = 29;
        GroupV2MigrationSelfInvitedUpdate groupV2MigrationSelfInvitedUpdate = 30;
        GroupV2MigrationInvitedMembersUpdate groupV2MigrationInvitedMembersUpdate = 31;
        GroupV2MigrationDroppedMembersUpdate groupV2MigrationDroppedMembersUpdate = 32;
        GroupSequenceOfRequestsAndCancelsUpdate groupSequenceOfRequestsAndCancelsUpdate = 33;
        GroupExpirationTimerUpdate groupExpirationTimerUpdate = 34;
      }
    }

    // Must be one or more; all updates batched together came from
    // a single batched group state update.
    repeated Update updates = 1;
  }

  message GenericGroupUpdate {
    optional bytes updaterAci = 1;
  }

  message GroupCreationUpdate {
    optional bytes updaterAci = 1;
  }

  message GroupNameUpdate {
    optional bytes updaterAci = 1;
    // Null value means the group name was removed.
    optional string newGroupName = 2;
  }

  message GroupAvatarUpdate {
    optional bytes updaterAci = 1;
    bool wasRemoved = 2;
  }

  message GroupDescriptionUpdate {
    optional bytes updaterAci = 1;
    // Null value means the group description was removed.
    optional string newDescription = 2;
  }

  enum GroupV2AccessLevel {
    UNKNOWN = 0;
    ANY = 1;
    MEMBER = 2;
    ADMINISTRATOR = 3;
    UNSATISFIABLE = 4;
  }

  message GroupMembershipAccessLevelChangeUpdate {
    optional bytes updaterAci = 1;
    optional GroupV2AccessLevel accessLevel = 2;
  }

  message GroupAttributesAccessLevelChangeUpdate {
    optional bytes updaterAci = 1;
    optional GroupV2AccessLevel accessLevel = 2;
  }

  message GroupAnnouncementOnlyChangeUpdate {
    optional bytes updaterAci = 1;
    bool isAnnouncementOnly = 2;
  }

  message GroupAdminStatusUpdate {
    optional bytes updaterAci = 1;
    // The aci who had admin status granted or revoked.
    bytes memberAci = 2;
    bool wasAdminStatusGranted = 3;
  }

  message GroupMemberLeftUpdate {
    bytes aci = 1;
  }

  message GroupMemberRemovedUpdate {
    optional bytes removerAci = 1;
    bytes removedAci = 2;
  }

  message SelfInvitedToGroupUpdate {
    optional bytes inviterAci = 1;
  }

  message SelfInvitedOtherUserToGroupUpdate {
    // If no invitee id available, use GroupUnknownInviteeUpdate
    bytes inviteeServiceId = 1;
  }

  message GroupUnknownInviteeUpdate {
    // Can be the self user.
    optional bytes inviterAci = 1;
    uint32 inviteeCount = 2;
  }

  message GroupInvitationAcceptedUpdate {
    optional bytes inviterAci = 1;
    bytes newMemberAci = 2;
  }

  message GroupInvitationDeclinedUpdate {
    optional bytes inviterAci = 1;
    // Note: if invited by pni, just set inviteeAci to nil.
    optional bytes inviteeAci = 2;
  }

  message GroupMemberJoinedUpdate {
    bytes newMemberAci = 1;
  }

  message GroupMemberAddedUpdate {
    optional bytes updaterAci = 1;
    bytes newMemberAci = 2;
    bool hadOpenInvitation = 3;
    // If hadOpenInvitation is true, optionally include aci of the inviter.
    optional bytes inviterAci = 4;
  }

  // An invitation to self was revoked.
  message GroupSelfInvitationRevokedUpdate {
    optional bytes revokerAci = 1;
  }

  // These invitees should never be the local user.
  // Use GroupSelfInvitationRevokedUpdate in those cases.
  // The inviter or updater can be the local user.
  message GroupInvitationRevokedUpdate {
    message Invitee {
      optional bytes inviterAci = 1;
      // Prefer to use aci over pni. No need to set
      // pni if aci is set. Both can be missing.
      optional bytes inviteeAci = 2;
      optional bytes inviteePni = 3;
    }

    // The member that revoked the invite(s), not the inviter!
    // Assumed to be an admin (at the time, may no longer be an
    // admin or even a member).
    optional bytes updaterAci = 1;
    repeated Invitee invitees = 2;
  }

  message GroupJoinRequestUpdate {
    bytes requestorAci = 1;
  }

  message GroupJoinRequestApprovalUpdate {
    bytes requestorAci = 1;
    // The aci that approved or rejected the request.
    optional bytes updaterAci = 2;
    bool wasApproved = 3;
  }

  message GroupJoinRequestCanceledUpdate {
    bytes requestorAci = 1;
  }

  // A single requestor has requested to join and cancelled
  // their request repeatedly with no other updates in between.
  // The last action encompassed by this update is always a
  // cancellation; if there was another open request immediately
  // after, it will be a separate GroupJoinRequestUpdate, either
  // in the same frame or in a subsequent frame.
  message GroupSequenceOfRequestsAndCancelsUpdate {
    bytes requestorAci = 1;
    uint32 count = 2;
  }

  message GroupInviteLinkResetUpdate {
    optional bytes updaterAci = 1;
  }

  message GroupInviteLinkEnabledUpdate {
    optional bytes updaterAci = 1;
    bool linkRequiresAdminApproval = 2;
  }

  message GroupInviteLinkAdminApprovalUpdate {
    optional bytes updaterAci = 1;
    bool linkRequiresAdminApproval = 2;
  }

  message GroupInviteLinkDisabledUpdate {
    optional bytes updaterAci = 1;
  }

  message GroupMemberJoinedByLinkUpdate {
    bytes newMemberAci = 1;
  }

  // A gv1->gv2 migration occurred.
  message GroupV2MigrationUpdate  {}

  // Another user migrated gv1->gv2 but was unable to add
  // the local user and invited them instead.
  message GroupV2MigrationSelfInvitedUpdate {}

  // The local user migrated gv1->gv2 but was unable to
  // add some members and invited them instead.
  // (Happens if we don't have the invitee's profile key)
  message GroupV2MigrationInvitedMembersUpdate {
    uint32 invitedMembersCount = 1;
  }

  // The local user migrated gv1->gv2 but was unable to
  // add or invite some members and dropped them instead.
  // (Happens for e164 members where we don't have an aci).
  message GroupV2MigrationDroppedMembersUpdate {
    uint32 droppedMembersCount = 1;
  }

  // For 1:1 timer updates, use ExpirationTimerChatUpdate.
  message GroupExpirationTimerUpdate {
    uint32 expiresInMs = 1; // 0 means the expiration timer was disabled.
    optional bytes updaterAci = 2;
  }

  message StickerPack {
    bytes id = 1;
    bytes key = 2;
    string title = 3;
    string author = 4;
    repeated StickerPackSticker stickers = 5; // First one should be cover sticker.
  }

  message StickerPackSticker {
    FilePointer data = 1;
    string emoji = 2;
  }
}
