// Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only syntax = "proto3"; package signalbackups; option java_package = "org.thoughtcrime.securesms.backup.v2.proto"; message BackupInfo { uint64 version = 1; uint64 backupTimeMs = 2; } // Frames must follow in the following ordering rules: // // 1. There is exactly one AccountData and it is the first frame. // 2. A frame referenced by ID must come before the referencing frame. // e.g. a Recipient must come before any Chat referencing it. // 3. All ChatItems must appear in global Chat rendering order. // (The order in which they were received by the client.) // // Recipients, Chats, StickerPacks, and AdHocCalls can be in any order. // (But must respect rule 2.) // For example, Chats may all be together at the beginning, // or may each immediately precede its first ChatItem. message Frame { oneof item { AccountData account = 1; Recipient recipient = 2; Chat chat = 3; ChatItem chatItem = 4; StickerPack stickerPack = 5; AdHocCall adHocCall = 6; } } 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 Color color = 3; } message AccountSettings { bool readReceipts = 1; bool sealedSenderIndicators = 2; bool typingIndicators = 3; bool linkPreviews = 4; bool notDiscoverableByPhoneNumber = 5; bool preferContactAvatars = 6; uint32 universalExpireTimerSeconds = 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; PhoneNumberSharingMode phoneNumberSharingMode = 17; ChatStyle defaultChatStyle = 18; repeated ChatStyle.CustomChatColor customChatColors = 19; } message SubscriberData { bytes subscriberId = 1; string currencyCode = 2; bool manuallyCancelled = 3; } bytes profileKey = 1; optional string username = 2; UsernameLink usernameLink = 3; string givenName = 4; string familyName = 5; string avatarUrlPath = 6; SubscriberData donationSubscriberData = 7; SubscriberData backupsSubscriberData = 8; AccountSettings accountSettings = 9; } message Recipient { uint64 id = 1; // generated id for reference only within this file oneof destination { Contact contact = 2; Group group = 3; DistributionListItem distributionList = 4; Self self = 5; ReleaseNotes releaseNotes = 6; CallLink callLink = 7; } } message Contact { message Registered { } message NotRegistered { uint64 unregisteredTimestamp = 1; } enum Visibility { VISIBLE = 0; HIDDEN = 1; HIDDEN_MESSAGE_REQUEST = 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; Visibility visibility = 6; oneof registration { Registered registered = 7; NotRegistered notRegistered = 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; StorySendMode storySendMode = 4; GroupSnapshot snapshot = 5; // These are simply plaintext copies of the groups proto from Groups.proto. // They should be kept completely in-sync with Groups.proto. // These exist to allow us to have the latest snapshot of a group during restoration without having to hit the network. // We would use Groups.proto if we could, but we want a plaintext version to improve export readability. // For documentation, defer to Groups.proto. The only name change is Group -> GroupSnapshot to avoid the naming conflict. message GroupSnapshot { reserved /*publicKey*/ 1; // The field is deprecated in the context of static group state GroupAttributeBlob title = 2; GroupAttributeBlob description = 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 announcements_only = 12; repeated MemberBanned members_banned = 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 Self {} 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; ChatStyle style = 9; uint32 expireTimerVersion = 10; } /** * Call Links have some associated data including a call, but unlike other recipients * are not tied to threads because they do not have messages associated with them. * * note: * - room id can be derived from the root key * - the presence of an admin key means this user is a call admin */ message CallLink { enum Restrictions { UNKNOWN = 0; NONE = 1; ADMIN_APPROVAL = 2; } bytes rootKey = 1; optional bytes adminKey = 2; // Only present if the user is an admin string name = 3; Restrictions restrictions = 4; uint64 expirationMs = 5; } message AdHocCall { enum State { UNKNOWN_STATE = 0; GENERIC = 1; } uint64 callId = 1; // Refers to a Recipient with the `callLink` field set uint64 recipientId = 2; State state = 3; uint64 callTimestamp = 4; } 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 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; uint64 expireStartDate = 4; // timestamp of when expiration timer started ticking down 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; GiftBadge giftBadge = 17; } } message SendStatus { message Pending {} message Sent { bool sealedSender = 1; } message Delivered { bool sealedSender = 1; } message Read { bool sealedSender = 1; } message Viewed { bool sealedSender = 1; } // e.g. user in group was blocked, so we skipped sending to them message Skipped {} message Failed { enum FailureReason { UNKNOWN = 0; // A valid value -- could indicate a crash or lack of information NETWORK = 1; IDENTITY_KEY_MISMATCH = 2; } FailureReason reason = 1; } uint64 recipientId = 1; uint64 timestamp = 2; // the time the status was last updated -- if from a receipt, it should be the sentTime of the receipt oneof deliveryStatus { Pending pending = 3; Sent sent = 4; Delivered delivered = 5; Read read = 6; Viewed viewed = 7; Skipped skipped = 8; Failed failed = 9; } } message Text { string body = 1; repeated BodyRange bodyRanges = 2; } message StandardMessage { optional Quote quote = 1; optional Text text = 2; repeated MessageAttachment 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 GiftBadge { enum State { UNOPENED = 0; OPENED = 1; REDEEMED = 2; FAILED = 3; } bytes receiptCredentialPresentation = 1; State state = 2; } 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; } optional Name name = 1; repeated Phone number = 3; repeated Email email = 4; repeated PostalAddress address = 5; optional FilePointer avatar = 6; optional string organization = 7; } 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; // Stickers are uploaded to be sent as attachments; we also // back them up as normal attachments when they are in messages. // DO NOT treat this as the definitive source of a sticker in // an installed StickerPack that shares the same packId. FilePointer data = 5; } message LinkPreview { string url = 1; optional string title = 2; optional FilePointer image = 3; optional string description = 4; optional uint64 date = 5; } // A FilePointer on a message that has additional // metadata that applies only to message attachments. message MessageAttachment { // Similar to SignalService.AttachmentPointer.Flags, // but explicitly mutually exclusive. Note the different raw values // (non-zero starting values are not supported in proto3.) enum Flag { NONE = 0; VOICE_MESSAGE = 1; BORDERLESS = 2; GIF = 3; } FilePointer pointer = 1; Flag flag = 2; bool wasDownloaded = 3; // Cross-client identifier for this attachment among all attachments on the // owning message. See: SignalService.AttachmentPointer.clientUuid. optional bytes clientUuid = 4; } message FilePointer { // References attachments in the backup (media) storage tier. message BackupLocator { string mediaName = 1; // If present, the cdn number of the succesful upload. // If empty/0, may still have been uploaded, and clients // can discover the cdn number via the list endpoint. optional uint32 cdnNumber = 2; bytes key = 3; bytes digest = 4; uint32 size = 5; // Fallback in case backup tier upload failed. optional string transitCdnKey = 6; optional uint32 transitCdnNumber = 7; } // References attachments in the transit storage tier. // May be downloaded or not when the backup is generated; // primarily for free-tier users who cannot copy the // attachments to the backup (media) storage tier. message AttachmentLocator { string cdnKey = 1; uint32 cdnNumber = 2; uint64 uploadTimestamp = 3; bytes key = 4; bytes digest = 5; uint32 size = 6; } // References attachments that are invalid in such a way where download // cannot be attempted. Could range from missing digests to missing // CDN keys or anything else that makes download attempts impossible. // This serves as a 'tombstone' so that the UX can show that an attachment // did exist, but for whatever reason it's not retrievable. message InvalidAttachmentLocator { } oneof locator { BackupLocator backupLocator = 1; AttachmentLocator attachmentLocator= 2; InvalidAttachmentLocator invalidAttachmentLocator = 3; } optional string contentType = 4; optional bytes incrementalMac = 5; optional uint32 incrementalMacChunkSize = 6; optional string fileName = 7; optional uint32 width = 8; optional uint32 height = 9; optional string caption = 10; optional string blurHash = 11; } message Quote { enum Type { UNKNOWN = 0; NORMAL = 1; GIFTBADGE = 2; } message QuotedAttachment { optional string contentType = 1; optional string fileName = 2; optional MessageAttachment 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; 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 because some clients may not track this data optional uint64 receivedTimestamp = 4; // A higher sort order means that a reaction is more recent. Some clients may export this as // incrementing numbers (e.g. 1, 2, 3), others as timestamps. uint64 sortOrder = 5; } 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; LearnedProfileChatUpdate learnedProfileChange = 9; } } 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; bool read = 6; } 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; bool read = 7; } message SimpleChatUpdate { enum Type { UNKNOWN = 0; JOINED_SIGNAL = 1; IDENTITY_UPDATE = 2; IDENTITY_VERIFIED = 3; IDENTITY_DEFAULT = 4; // marking as unverified CHANGE_NUMBER = 5; RELEASE_CHANNEL_DONATION_REQUEST = 6; END_SESSION = 7; CHAT_SESSION_REFRESH = 8; BAD_DECRYPT = 9; PAYMENTS_ACTIVATED = 10; PAYMENT_ACTIVATION_REQUEST = 11; UNSUPPORTED_PROTOCOL_MESSAGE = 12; REPORTED_SPAM = 13; } Type type = 1; } // For 1:1 chat updates only. // For group thread updates use GroupExpirationTimerUpdate. message ExpirationTimerChatUpdate { uint64 expiresInMs = 1; // 0 means the expiration timer was disabled } message ProfileChangeChatUpdate { string previousName = 1; string newName = 2; } message LearnedProfileChatUpdate { oneof previousName { uint64 e164 = 1; string username = 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; GroupV2AccessLevel accessLevel = 2; } message GroupAttributesAccessLevelChangeUpdate { optional bytes updaterAci = 1; 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 { uint64 expiresInMs = 1; // 0 means the expiration timer was disabled optional bytes updaterAci = 2; } message StickerPack { bytes packId = 1; bytes packKey = 2; } message ChatStyle { message Gradient { uint32 angle = 1; // degrees repeated fixed32 colors = 2; repeated float positions = 3; // percent from 0 to 1 } message CustomChatColor { uint64 id = 1; oneof color { fixed32 solid = 2; Gradient gradient = 3; } } message AutomaticBubbleColor { } enum WallpaperPreset { UNKNOWN_WALLPAPER_PRESET = 0; SOLID_BLUSH = 1; SOLID_COPPER = 2; SOLID_DUST = 3; SOLID_CELADON = 4; SOLID_RAINFOREST = 5; SOLID_PACIFIC = 6; SOLID_FROST = 7; SOLID_NAVY = 8; SOLID_LILAC = 9; SOLID_PINK = 10; SOLID_EGGPLANT = 11; SOLID_SILVER = 12; GRADIENT_SUNSET = 13; GRADIENT_NOIR = 14; GRADIENT_HEATMAP = 15; GRADIENT_AQUA = 16; GRADIENT_IRIDESCENT = 17; GRADIENT_MONSTERA = 18; GRADIENT_BLISS = 19; GRADIENT_SKY = 20; GRADIENT_PEACH = 21; } enum BubbleColorPreset { UNKNOWN_BUBBLE_COLOR_PRESET = 0; SOLID_ULTRAMARINE = 1; SOLID_CRIMSON = 2; SOLID_VERMILION = 3; SOLID_BURLAP = 4; SOLID_FOREST = 5; SOLID_WINTERGREEN = 6; SOLID_TEAL = 7; SOLID_BLUE = 8; SOLID_INDIGO = 9; SOLID_VIOLET = 10; SOLID_PLUM = 11; SOLID_TAUPE = 12; SOLID_STEEL = 13; GRADIENT_EMBER = 14; GRADIENT_MIDNIGHT = 15; GRADIENT_INFRARED = 16; GRADIENT_LAGOON = 17; GRADIENT_FLUORESCENT = 18; GRADIENT_BASIL = 19; GRADIENT_SUBLIME = 20; GRADIENT_SEA = 21; GRADIENT_TANGERINE = 22; } oneof wallpaper { WallpaperPreset wallpaperPreset = 1; FilePointer wallpaperPhoto = 2; } oneof bubbleColor { // Bubble setting is automatically determined based on the wallpaper setting, // or `SOLID_ULTRAMARINE` for `noWallpaper` AutomaticBubbleColor autoBubbleColor = 3; BubbleColorPreset bubbleColorPreset = 4; // See AccountSettings.customChatColors uint64 customColorId = 5; } bool dimWallpaperInDarkMode = 7; }