diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index a8515d74a..5f71d712d 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -811,11 +811,20 @@ export class ConversationModel extends Backbone.Model { networkTimestamp ); + const attachmentsWithVoiceMessage = attachments + ? attachments.map(attachment => { + if (attachment.isVoiceMessage) { + return { ...attachment, flags: SignalService.AttachmentPointer.Flags.VOICE_MESSAGE }; + } + return attachment; + }) + : undefined; + const messageModel = await this.addSingleOutgoingMessage({ body, quote: isEmpty(quote) ? undefined : quote, preview, - attachments, + attachments: attachmentsWithVoiceMessage, sent_at: networkTimestamp, // overridden later, but we need one to have the sorting done in the UI even when the sending is pending expirationType: DisappearingMessages.changeToDisappearingMessageType( this, diff --git a/ts/models/message.ts b/ts/models/message.ts index 93af19a90..2d90d2266 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -727,11 +727,14 @@ export class MessageModel extends Backbone.Model { thumbnail, fileName, caption, + isVoiceMessage: isVoiceMessageFromDb, } = attachment; const isVoiceMessageBool = + !!isVoiceMessageFromDb || // eslint-disable-next-line no-bitwise - Boolean(flags && flags & SignalService.AttachmentPointer.Flags.VOICE_MESSAGE) || false; + !!(flags && flags & SignalService.AttachmentPointer.Flags.VOICE_MESSAGE) || + false; return { id, diff --git a/ts/node/sql.ts b/ts/node/sql.ts index 7a6032045..92a7dfa65 100644 --- a/ts/node/sql.ts +++ b/ts/node/sql.ts @@ -22,6 +22,7 @@ import { last, map, omit, + some, uniq, } from 'lodash'; @@ -1107,7 +1108,7 @@ async function getAllMessagesWithAttachmentsInConversationSentBefore( .all({ conversationId, beforeMs: deleteAttachBeforeSeconds * 1000 }); const messages = map(rows, row => jsonToObject(row.json)); const messagesWithAttachments = messages.filter(m => { - return getExternalFilesForMessage(m, false).some(a => !isEmpty(a) && isString(a)); // when we remove an attachment, we set the path to '' so it should be excluded here + return hasUserVisibleAttachments(m); }); return messagesWithAttachments; } @@ -2092,7 +2093,7 @@ function getMessagesWithFileAttachments(conversationId: string, limit: number) { return map(rows, row => jsonToObject(row.json)); } -function getExternalFilesForMessage(message: any, includePreview = true) { +function getExternalFilesForMessage(message: any) { const { attachments, quote, preview } = message; const files: Array = []; @@ -2110,7 +2111,6 @@ function getExternalFilesForMessage(message: any, includePreview = true) { files.push(screenshot.path); } }); - if (quote && quote.attachments && quote.attachments.length) { forEach(quote.attachments, attachment => { const { thumbnail } = attachment; @@ -2121,7 +2121,7 @@ function getExternalFilesForMessage(message: any, includePreview = true) { }); } - if (includePreview && preview && preview.length) { + if (preview && preview.length) { forEach(preview, item => { const { image } = item; @@ -2134,6 +2134,30 @@ function getExternalFilesForMessage(message: any, includePreview = true) { return files; } +/** + * This looks like `getExternalFilesForMessage`, but it does not include some type of attachments not visible from the right panel. + * It should only be used when we look for messages to mark as deleted when an admin + * triggers a "delete messages with attachments since". + * Note: quoted attachments are referencing the original message, so we don't need to include them here. + * Note: previews are not considered user visible (because not visible from the right panel), + * so we don't need to include them here + * Note: voice messages are not considered user visible (because not visible from the right panel), + */ +function hasUserVisibleAttachments(message: any) { + const { attachments } = message; + + return some(attachments, attachment => { + const { path: file, flags, thumbnail, screenshot } = attachment; + + return ( + // eslint-disable-next-line no-bitwise + (file && !(flags & SignalService.AttachmentPointer.Flags.VOICE_MESSAGE)) || + thumbnail?.path || + screenshot?.path + ); + }); +} + function getExternalFilesForConversation( conversationAvatar: | string