| 
									
										
										
										
											2023-03-27 19:48:57 -04:00
										 |  |  | // Copyright 2023 Signal Messenger, LLC
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import type { AttachmentType, ThumbnailType } from '../types/Attachment'; | 
					
						
							|  |  |  | import type { | 
					
						
							|  |  |  |   MessageAttributesType, | 
					
						
							|  |  |  |   QuotedMessageType, | 
					
						
							|  |  |  | } from '../model-types.d'; | 
					
						
							|  |  |  | import type { MIMEType } from '../types/MIME'; | 
					
						
							|  |  |  | import type { LinkPreviewType } from '../types/message/LinkPreviews'; | 
					
						
							|  |  |  | import type { StickerType } from '../types/Stickers'; | 
					
						
							|  |  |  | import { IMAGE_JPEG, IMAGE_GIF } from '../types/MIME'; | 
					
						
							|  |  |  | import { getContact } from '../messages/helpers'; | 
					
						
							|  |  |  | import { getQuoteBodyText } from './getQuoteBodyText'; | 
					
						
							|  |  |  | import { isGIF } from '../types/Attachment'; | 
					
						
							|  |  |  | import { isGiftBadge, isTapToView } from '../state/selectors/message'; | 
					
						
							| 
									
										
										
										
											2023-05-16 10:37:12 -07:00
										 |  |  | import * as log from '../logging/log'; | 
					
						
							| 
									
										
										
										
											2023-03-27 19:48:57 -04:00
										 |  |  | import { map, take, collect } from './iterables'; | 
					
						
							|  |  |  | import { strictAssert } from './assert'; | 
					
						
							| 
									
										
										
										
											2023-05-16 10:37:12 -07:00
										 |  |  | import { getMessageSentTimestamp } from './getMessageSentTimestamp'; | 
					
						
							| 
									
										
										
										
											2023-03-27 19:48:57 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | export async function makeQuote( | 
					
						
							|  |  |  |   quotedMessage: MessageAttributesType | 
					
						
							|  |  |  | ): Promise<QuotedMessageType> { | 
					
						
							|  |  |  |   const contact = getContact(quotedMessage); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   strictAssert(contact, 'makeQuote: no contact'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const { | 
					
						
							|  |  |  |     attachments, | 
					
						
							|  |  |  |     bodyRanges, | 
					
						
							|  |  |  |     id: messageId, | 
					
						
							|  |  |  |     payment, | 
					
						
							|  |  |  |     preview, | 
					
						
							|  |  |  |     sticker, | 
					
						
							|  |  |  |   } = quotedMessage; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-16 10:37:12 -07:00
										 |  |  |   const quoteId = getMessageSentTimestamp(quotedMessage, { log }); | 
					
						
							| 
									
										
										
										
											2023-03-27 19:48:57 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |   return { | 
					
						
							| 
									
										
										
										
											2023-08-16 22:54:39 +02:00
										 |  |  |     authorAci: contact.getCheckedAci('makeQuote'), | 
					
						
							| 
									
										
										
										
											2023-03-27 19:48:57 -04:00
										 |  |  |     attachments: isTapToView(quotedMessage) | 
					
						
							|  |  |  |       ? [{ contentType: IMAGE_JPEG, fileName: null }] | 
					
						
							|  |  |  |       : await getQuoteAttachment(attachments, preview, sticker), | 
					
						
							|  |  |  |     payment, | 
					
						
							|  |  |  |     bodyRanges, | 
					
						
							|  |  |  |     id: quoteId, | 
					
						
							|  |  |  |     isViewOnce: isTapToView(quotedMessage), | 
					
						
							|  |  |  |     isGiftBadge: isGiftBadge(quotedMessage), | 
					
						
							|  |  |  |     messageId, | 
					
						
							|  |  |  |     referencedMessageNotFound: false, | 
					
						
							|  |  |  |     text: getQuoteBodyText(quotedMessage, quoteId), | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export async function getQuoteAttachment( | 
					
						
							|  |  |  |   attachments?: Array<AttachmentType>, | 
					
						
							|  |  |  |   preview?: Array<LinkPreviewType>, | 
					
						
							|  |  |  |   sticker?: StickerType | 
					
						
							|  |  |  | ): Promise< | 
					
						
							|  |  |  |   Array<{ | 
					
						
							|  |  |  |     contentType: MIMEType; | 
					
						
							| 
									
										
										
										
											2023-04-20 12:31:59 -04:00
										 |  |  |     fileName?: string | null; | 
					
						
							|  |  |  |     thumbnail?: ThumbnailType | null; | 
					
						
							| 
									
										
										
										
											2023-03-27 19:48:57 -04:00
										 |  |  |   }> | 
					
						
							|  |  |  | > { | 
					
						
							|  |  |  |   const { getAbsoluteAttachmentPath, loadAttachmentData } = | 
					
						
							|  |  |  |     window.Signal.Migrations; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (attachments && attachments.length) { | 
					
						
							|  |  |  |     const attachmentsToUse = Array.from(take(attachments, 1)); | 
					
						
							|  |  |  |     const isGIFQuote = isGIF(attachmentsToUse); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Promise.all( | 
					
						
							|  |  |  |       map(attachmentsToUse, async attachment => { | 
					
						
							|  |  |  |         const { path, fileName, thumbnail, contentType } = attachment; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (!path) { | 
					
						
							|  |  |  |           return { | 
					
						
							|  |  |  |             contentType: isGIFQuote ? IMAGE_GIF : contentType, | 
					
						
							|  |  |  |             // Our protos library complains about this field being undefined, so we
 | 
					
						
							|  |  |  |             //   force it to null
 | 
					
						
							|  |  |  |             fileName: fileName || null, | 
					
						
							|  |  |  |             thumbnail: null, | 
					
						
							|  |  |  |           }; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           contentType: isGIFQuote ? IMAGE_GIF : contentType, | 
					
						
							|  |  |  |           // Our protos library complains about this field being undefined, so we force
 | 
					
						
							|  |  |  |           //   it to null
 | 
					
						
							|  |  |  |           fileName: fileName || null, | 
					
						
							|  |  |  |           thumbnail: thumbnail | 
					
						
							|  |  |  |             ? { | 
					
						
							|  |  |  |                 ...(await loadAttachmentData(thumbnail)), | 
					
						
							|  |  |  |                 objectUrl: thumbnail.path | 
					
						
							|  |  |  |                   ? getAbsoluteAttachmentPath(thumbnail.path) | 
					
						
							|  |  |  |                   : undefined, | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             : null, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (preview && preview.length) { | 
					
						
							|  |  |  |     const previewImages = collect(preview, prev => prev.image); | 
					
						
							|  |  |  |     const previewImagesToUse = take(previewImages, 1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return Promise.all( | 
					
						
							|  |  |  |       map(previewImagesToUse, async image => { | 
					
						
							|  |  |  |         const { contentType } = image; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |           contentType, | 
					
						
							|  |  |  |           // Our protos library complains about this field being undefined, so we
 | 
					
						
							|  |  |  |           //   force it to null
 | 
					
						
							|  |  |  |           fileName: null, | 
					
						
							|  |  |  |           thumbnail: image | 
					
						
							|  |  |  |             ? { | 
					
						
							|  |  |  |                 ...(await loadAttachmentData(image)), | 
					
						
							|  |  |  |                 objectUrl: image.path | 
					
						
							|  |  |  |                   ? getAbsoluteAttachmentPath(image.path) | 
					
						
							|  |  |  |                   : undefined, | 
					
						
							|  |  |  |               } | 
					
						
							|  |  |  |             : null, | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (sticker && sticker.data && sticker.data.path) { | 
					
						
							|  |  |  |     const { path, contentType } = sticker.data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return [ | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         contentType, | 
					
						
							|  |  |  |         // Our protos library complains about this field being undefined, so we
 | 
					
						
							|  |  |  |         //   force it to null
 | 
					
						
							|  |  |  |         fileName: null, | 
					
						
							|  |  |  |         thumbnail: { | 
					
						
							|  |  |  |           ...(await loadAttachmentData(sticker.data)), | 
					
						
							|  |  |  |           objectUrl: path ? getAbsoluteAttachmentPath(path) : undefined, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return []; | 
					
						
							|  |  |  | } |