From ce090a8a3c03ca4764f004dbcc809fd9ff258a01 Mon Sep 17 00:00:00 2001 From: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:16:09 -0700 Subject: [PATCH] Import/export view-once messages --- .github/workflows/ci.yml | 2 +- protos/Backups.proto | 7 +++++++ ts/services/backups/export.ts | 34 ++++++++++++++++++++++++++++++++-- ts/services/backups/import.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b374c8a64..5b93dbe33c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,7 +194,7 @@ jobs: uses: actions/checkout@v4 with: repository: 'signalapp/Signal-Message-Backup-Tests' - ref: '996daf691d4162ce854845dc883c62adb1a3fe55' + ref: 'c84390838f65c6c0927c9bbcc3a240f986fc4a80' path: 'backup-integration-tests' - run: xvfb-run --auto-servernum npm run test-electron diff --git a/protos/Backups.proto b/protos/Backups.proto index 03bcaf9480..4facb2cfa0 100644 --- a/protos/Backups.proto +++ b/protos/Backups.proto @@ -342,6 +342,7 @@ message ChatItem { ChatUpdateMessage updateMessage = 15; PaymentNotification paymentNotification = 16; GiftBadge giftBadge = 17; + ViewOnceMessage viewOnceMessage = 18; } } @@ -470,6 +471,12 @@ message GiftBadge { State state = 2; } +message ViewOnceMessage { + // Will be null for viewed messages + MessageAttachment attachment = 1; + repeated Reaction reactions = 2; +} + message ContactAttachment { message Name { optional string givenName = 1; diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index df128c7a97..b96cf5be4b 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -77,6 +77,7 @@ import { isNormalBubble, isPhoneNumberDiscovery, isProfileChange, + isTapToView, isUniversalTimerNotification, isUnsupportedMessage, isVerifiedChange, @@ -1060,7 +1061,12 @@ export class BackupExportStream extends Readable { } const { contact, sticker } = message; - if (message.isErased) { + if (isTapToView(message)) { + result.viewOnceMessage = await this.toViewOnceMessage({ + message, + backupLevel, + }); + } else if (message.isErased) { result.remoteDeletedMessage = {}; } else if (messageHasPaymentEvent(message)) { const { payment } = message; @@ -2265,7 +2271,7 @@ export class BackupExportStream extends Readable { serverTimestamp != null ? getSafeLongFromTimestamp(serverTimestamp) : null, - read: readStatus === ReadStatus.Read, + read: readStatus === ReadStatus.Read || readStatus === ReadStatus.Viewed, sealedSender: unidentifiedDeliveryReceived === true, }; } @@ -2443,6 +2449,30 @@ export class BackupExportStream extends Readable { }; } + private async toViewOnceMessage({ + message, + backupLevel, + }: { + message: Pick< + MessageAttributesType, + 'attachments' | 'received_at' | 'reactions' + >; + backupLevel: BackupLevel; + }): Promise { + const attachment = message.attachments?.at(0); + return { + attachment: + attachment == null + ? null + : await this.processMessageAttachment({ + attachment, + backupLevel, + messageReceivedAt: message.received_at, + }), + reactions: this.getMessageReactions(message), + }; + } + private async toChatItemRevisions( parent: Backups.IChatItem, message: MessageAttributesType, diff --git a/ts/services/backups/import.ts b/ts/services/backups/import.ts index 6e8c600ba4..0b2464508d 100644 --- a/ts/services/backups/import.ts +++ b/ts/services/backups/import.ts @@ -1303,6 +1303,11 @@ export class BackupImportStream extends Writable { ...attributes, ...(await this.fromStandardMessage(item.standardMessage, chatConvo.id)), }; + } else if (item.viewOnceMessage) { + attributes = { + ...attributes, + ...(await this.fromViewOnceMessage(item.viewOnceMessage)), + }; } else { const result = await this.fromNonBubbleChatItem(item, { aboutMe, @@ -1566,6 +1571,27 @@ export class BackupImportStream extends Writable { }; } + private async fromViewOnceMessage({ + attachment, + reactions, + }: Backups.IViewOnceMessage): Promise> { + return { + ...(attachment + ? { + attachments: [ + convertBackupMessageAttachmentToAttachment(attachment), + ].filter(isNotNil), + } + : { + attachments: undefined, + readStatus: ReadStatus.Viewed, + isErased: true, + }), + reactions: this.fromReactions(reactions), + isViewOnce: true, + }; + } + private async fromRevisions( mainMessage: MessageAttributesType, revisions: ReadonlyArray