// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { pick } from 'lodash'; import type { MessageAttributesType } from '../model-types.d'; import type { StoryDataType } from '../state/ducks/stories'; import * as durations from '../util/durations'; import * as log from '../logging/log'; import dataInterface from '../sql/Client'; import { getAttachmentsForMessage } from '../state/selectors/message'; import { isNotNil } from '../util/isNotNil'; import { strictAssert } from '../util/assert'; import { dropNull } from '../util/dropNull'; let storyData: Array | undefined; export async function loadStories(): Promise { storyData = await dataInterface.getOlderStories({}); await repairUnexpiredStories(); } export function getStoryDataFromMessageAttributes( message: MessageAttributesType ): StoryDataType | undefined { const { attachments, deletedForEveryone } = message; const unresolvedAttachment = attachments ? attachments[0] : undefined; if (!unresolvedAttachment && !deletedForEveryone) { log.warn( `getStoryDataFromMessageAttributes: ${message.id} does not have an attachment` ); return; } const [attachment] = unresolvedAttachment && unresolvedAttachment.path ? getAttachmentsForMessage(message) : [unresolvedAttachment]; return { attachment, messageId: message.id, ...pick(message, [ 'canReplyToStory', 'conversationId', 'deletedForEveryone', 'reactions', 'readAt', 'readStatus', 'sendStateByConversationId', 'source', 'sourceUuid', 'storyDistributionListId', 'timestamp', 'type', ]), expireTimer: message.expireTimer, expirationStartTimestamp: dropNull(message.expirationStartTimestamp), }; } export function getStoriesForRedux(): Array { strictAssert(storyData, 'storyData has not been loaded'); const stories = storyData .map(getStoryDataFromMessageAttributes) .filter(isNotNil); storyData = undefined; return stories; } async function repairUnexpiredStories(): Promise { strictAssert(storyData, 'Could not load stories'); const DAY_AS_SECONDS = durations.DAY / 1000; const storiesWithExpiry = storyData .filter( story => !story.expirationStartTimestamp || !story.expireTimer || story.expireTimer > DAY_AS_SECONDS ) .map(story => ({ ...story, expirationStartTimestamp: Math.min(story.timestamp, Date.now()), expireTimer: Math.min( Math.floor((story.timestamp + durations.DAY - Date.now()) / 1000), DAY_AS_SECONDS ), })); if (!storiesWithExpiry.length) { return; } log.info( 'repairUnexpiredStories: repairing number of stories', storiesWithExpiry.length ); await Promise.all( storiesWithExpiry.map(messageAttributes => { return window.Signal.Data.saveMessage(messageAttributes, { ourUuid: window.textsecure.storage.user.getCheckedUuid().toString(), }); }) ); }