Fix roundtripping of voice notes with body

This commit is contained in:
Fedor Indutny
2025-04-16 14:27:47 -07:00
committed by GitHub
parent c9c3d24fd9
commit 0b5f0df1ca
2 changed files with 50 additions and 14 deletions

View File

@@ -38,7 +38,6 @@ import type {
ConversationAttributesType,
MessageAttributesType,
QuotedAttachmentType,
QuotedMessageType,
} from '../../model-types.d';
import { drop } from '../../util/drop';
import { isNotNil } from '../../util/isNotNil';
@@ -2195,14 +2194,13 @@ export class BackupExportStream extends Readable {
}
async #toQuote({
quote,
message,
backupLevel,
messageReceivedAt,
}: {
quote?: QuotedMessageType;
message: Pick<MessageAttributesType, 'quote' | 'received_at' | 'body'>;
backupLevel: BackupLevel;
messageReceivedAt: number;
}): Promise<Backups.IQuote | null> {
const { quote } = message;
if (!quote) {
return null;
}
@@ -2259,7 +2257,7 @@ export class BackupExportStream extends Readable {
? await this.#processMessageAttachment({
attachment: attachment.thumbnail,
backupLevel,
messageReceivedAt,
message,
})
: undefined,
};
@@ -2287,11 +2285,17 @@ export class BackupExportStream extends Readable {
}
#getMessageAttachmentFlag(
message: Pick<MessageAttributesType, 'body'>,
attachment: AttachmentType
): Backups.MessageAttachment.Flag {
const flag = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
// eslint-disable-next-line no-bitwise
if (((attachment.flags || 0) & flag) === flag) {
// Legacy data support for iOS
if (message.body) {
return Backups.MessageAttachment.Flag.NONE;
}
return Backups.MessageAttachment.Flag.VOICE_MESSAGE;
}
if (isGIF([attachment])) {
@@ -2311,22 +2315,22 @@ export class BackupExportStream extends Readable {
async #processMessageAttachment({
attachment,
backupLevel,
messageReceivedAt,
message,
}: {
attachment: AttachmentType;
backupLevel: BackupLevel;
messageReceivedAt: number;
message: Pick<MessageAttributesType, 'quote' | 'received_at' | 'body'>;
}): Promise<Backups.MessageAttachment> {
const { clientUuid } = attachment;
const filePointer = await this.#processAttachment({
attachment,
backupLevel,
messageReceivedAt,
messageReceivedAt: message.received_at,
});
return new Backups.MessageAttachment({
pointer: filePointer,
flag: this.#getMessageAttachmentFlag(attachment),
flag: this.#getMessageAttachmentFlag(message, attachment),
wasDownloaded: isDownloaded(attachment),
clientUuid: clientUuid ? uuidToBytes(clientUuid) : undefined,
});
@@ -2550,9 +2554,8 @@ export class BackupExportStream extends Readable {
}): Promise<Backups.IStandardMessage> {
return {
quote: await this.#toQuote({
quote: message.quote,
message,
backupLevel,
messageReceivedAt: message.received_at,
}),
attachments: message.attachments?.length
? await Promise.all(
@@ -2560,7 +2563,7 @@ export class BackupExportStream extends Readable {
return this.#processMessageAttachment({
attachment,
backupLevel,
messageReceivedAt: message.received_at,
message,
});
})
)
@@ -2667,7 +2670,7 @@ export class BackupExportStream extends Readable {
: await this.#processMessageAttachment({
attachment,
backupLevel,
messageReceivedAt: message.received_at,
message,
}),
reactions: this.#getMessageReactions(message),
};

View File

@@ -355,6 +355,39 @@ describe('backup/attachments', () => {
{ backupLevel: BackupLevel.Paid }
);
});
it('drops voice message flag when body is present', async () => {
const attachment = composeAttachment(1);
attachment.contentType = AUDIO_MP3;
attachment.flags = SignalService.AttachmentPointer.Flags.VOICE_MESSAGE;
strictAssert(isVoiceMessage(attachment), 'it is a voice attachment');
strictAssert(attachment.digest, 'digest exists');
await asymmetricRoundtripHarness(
[
composeMessage(1, {
body: 'hello',
attachments: [attachment],
}),
],
[
composeMessage(1, {
body: 'hello',
hasAttachments: true,
attachments: [
{
...omit(attachment, NON_ROUNDTRIPPED_BACKUP_LOCATOR_FIELDS),
flags: undefined,
backupLocator: {
mediaName: digestToMediaName(attachment.digest),
},
},
],
}),
],
{ backupLevel: BackupLevel.Paid }
);
});
});
describe('Preview attachments', () => {