Drop story replies from group timeline

This commit is contained in:
Josh Perez 2022-04-20 19:33:38 -04:00 committed by GitHub
parent e5ba00b798
commit 774246b6e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 358 additions and 33 deletions

View file

@ -85,6 +85,7 @@ import * as universalExpireTimer from '../util/universalExpireTimer';
import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions'; import type { GroupNameCollisionsWithIdsByTitle } from '../util/groupMemberNameCollisions';
import { import {
isDirectConversation, isDirectConversation,
isGroup,
isGroupV1, isGroupV1,
isGroupV2, isGroupV2,
isMe, isMe,
@ -1375,10 +1376,16 @@ export class ConversationModel extends window.Backbone
// Clear typing indicator for a given contact if we receive a message from them // Clear typing indicator for a given contact if we receive a message from them
this.clearContactTypingTimer(typingToken); this.clearContactTypingTimer(typingToken);
if (!isStory(message.attributes)) { // If it's a group story reply or a story message, we don't want to update
this.addSingleMessage(message); // the last message or add new messages to redux.
const isGroupStoryReply =
isGroup(this.attributes) && message.get('storyId');
if (isGroupStoryReply || isStory(message.attributes)) {
return;
} }
this.addSingleMessage(message);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.debouncedUpdateLastMessage!(); this.debouncedUpdateLastMessage!();
} }
@ -1486,7 +1493,11 @@ export class ConversationModel extends window.Backbone
} }
} }
const metrics = await getMessageMetricsForConversation(conversationId); const metrics = await getMessageMetricsForConversation(
conversationId,
undefined,
isGroup(this.attributes)
);
// If this is a message request that has not yet been accepted, we always show the // If this is a message request that has not yet been accepted, we always show the
// oldest messages, to ensure that the ConversationHero is shown. We don't want to // oldest messages, to ensure that the ConversationHero is shown. We don't want to
@ -1505,6 +1516,7 @@ export class ConversationModel extends window.Backbone
} }
const messages = await getOlderMessagesByConversation(conversationId, { const messages = await getOlderMessagesByConversation(conversationId, {
isGroup: isGroup(this.attributes),
limit: MESSAGE_LOAD_CHUNK_SIZE, limit: MESSAGE_LOAD_CHUNK_SIZE,
}); });
@ -1556,6 +1568,7 @@ export class ConversationModel extends window.Backbone
const receivedAt = message.received_at; const receivedAt = message.received_at;
const sentAt = message.sent_at; const sentAt = message.sent_at;
const models = await getOlderMessagesByConversation(conversationId, { const models = await getOlderMessagesByConversation(conversationId, {
isGroup: isGroup(this.attributes),
receivedAt, receivedAt,
sentAt, sentAt,
messageId: oldestMessageId, messageId: oldestMessageId,
@ -1609,6 +1622,7 @@ export class ConversationModel extends window.Backbone
const receivedAt = message.received_at; const receivedAt = message.received_at;
const sentAt = message.sent_at; const sentAt = message.sent_at;
const models = await getNewerMessagesByConversation(conversationId, { const models = await getNewerMessagesByConversation(conversationId, {
isGroup: isGroup(this.attributes),
receivedAt, receivedAt,
sentAt, sentAt,
limit: MESSAGE_LOAD_CHUNK_SIZE, limit: MESSAGE_LOAD_CHUNK_SIZE,
@ -2047,6 +2061,7 @@ export class ConversationModel extends window.Backbone
messages = await window.Signal.Data.getOlderMessagesByConversation( messages = await window.Signal.Data.getOlderMessagesByConversation(
this.get('id'), this.get('id'),
{ {
isGroup: isGroup(this.attributes),
limit: 100, limit: 100,
receivedAt: first ? first.received_at : undefined, receivedAt: first ? first.received_at : undefined,
sentAt: first ? first.sent_at : undefined, sentAt: first ? first.sent_at : undefined,
@ -4186,6 +4201,7 @@ export class ConversationModel extends window.Backbone
const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString(); const ourUuid = window.textsecure.storage.user.getCheckedUuid().toString();
const stats = await window.Signal.Data.getConversationMessageStats({ const stats = await window.Signal.Data.getConversationMessageStats({
conversationId, conversationId,
isGroup: isGroup(this.attributes),
ourUuid, ourUuid,
}); });

View file

@ -2542,9 +2542,12 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
} }
const conversationTimestamp = conversation.get('timestamp'); const conversationTimestamp = conversation.get('timestamp');
const isGroupStoryReply =
isGroup(conversation.attributes) && message.get('storyId');
if ( if (
!conversationTimestamp || !isGroupStoryReply &&
message.get('sent_at') > conversationTimestamp (!conversationTimestamp ||
message.get('sent_at') > conversationTimestamp)
) { ) {
conversation.set({ conversation.set({
lastMessage: message.getNotificationText(), lastMessage: message.getNotificationText(),
@ -2626,7 +2629,10 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const isFirstRun = false; const isFirstRun = false;
await this.modifyTargetMessage(conversation, isFirstRun); await this.modifyTargetMessage(conversation, isFirstRun);
if (isMessageUnread(this.attributes)) { const isGroupStoryReply =
isGroup(conversation.attributes) && this.get('storyId');
if (isMessageUnread(this.attributes) && !isGroupStoryReply) {
await conversation.notify(this); await conversation.notify(this);
} }
@ -2722,6 +2728,9 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
const viewSyncs = ViewSyncs.getSingleton().forMessage(message); const viewSyncs = ViewSyncs.getSingleton().forMessage(message);
const isGroupStoryReply =
isGroup(conversation.attributes) && message.get('storyId');
if (readSyncs.length !== 0 || viewSyncs.length !== 0) { if (readSyncs.length !== 0 || viewSyncs.length !== 0) {
const markReadAt = Math.min( const markReadAt = Math.min(
Date.now(), Date.now(),
@ -2758,7 +2767,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
this.pendingMarkRead ?? Date.now(), this.pendingMarkRead ?? Date.now(),
markReadAt markReadAt
); );
} else if (isFirstRun) { } else if (isFirstRun && !isGroupStoryReply) {
conversation.set({ conversation.set({
unreadCount: (conversation.get('unreadCount') || 0) + 1, unreadCount: (conversation.get('unreadCount') || 0) + 1,
isArchived: false, isArchived: false,

View file

@ -1177,6 +1177,7 @@ async function getTotalUnreadForConversation(
async function getUnreadByConversationAndMarkRead(options: { async function getUnreadByConversationAndMarkRead(options: {
conversationId: string; conversationId: string;
isGroup?: boolean;
newestUnreadAt: number; newestUnreadAt: number;
readAt?: number; readAt?: number;
storyId?: UUIDStringType; storyId?: UUIDStringType;
@ -1228,12 +1229,14 @@ function handleMessageJSON(
async function getOlderMessagesByConversation( async function getOlderMessagesByConversation(
conversationId: string, conversationId: string,
{ {
isGroup,
limit = 100, limit = 100,
messageId, messageId,
receivedAt = Number.MAX_VALUE, receivedAt = Number.MAX_VALUE,
sentAt = Number.MAX_VALUE, sentAt = Number.MAX_VALUE,
storyId, storyId,
}: { }: {
isGroup?: boolean;
limit?: number; limit?: number;
messageId?: string; messageId?: string;
receivedAt?: number; receivedAt?: number;
@ -1244,6 +1247,7 @@ async function getOlderMessagesByConversation(
const messages = await channels.getOlderMessagesByConversation( const messages = await channels.getOlderMessagesByConversation(
conversationId, conversationId,
{ {
isGroup,
limit, limit,
receivedAt, receivedAt,
sentAt, sentAt,
@ -1267,11 +1271,13 @@ async function getOlderStories(options: {
async function getNewerMessagesByConversation( async function getNewerMessagesByConversation(
conversationId: string, conversationId: string,
{ {
isGroup,
limit = 100, limit = 100,
receivedAt = 0, receivedAt = 0,
sentAt = 0, sentAt = 0,
storyId, storyId,
}: { }: {
isGroup?: boolean;
limit?: number; limit?: number;
receivedAt?: number; receivedAt?: number;
sentAt?: number; sentAt?: number;
@ -1281,6 +1287,7 @@ async function getNewerMessagesByConversation(
const messages = await channels.getNewerMessagesByConversation( const messages = await channels.getNewerMessagesByConversation(
conversationId, conversationId,
{ {
isGroup,
limit, limit,
receivedAt, receivedAt,
sentAt, sentAt,
@ -1292,14 +1299,17 @@ async function getNewerMessagesByConversation(
} }
async function getConversationMessageStats({ async function getConversationMessageStats({
conversationId, conversationId,
isGroup,
ourUuid, ourUuid,
}: { }: {
conversationId: string; conversationId: string;
isGroup?: boolean;
ourUuid: UUIDStringType; ourUuid: UUIDStringType;
}): Promise<ConversationMessageStatsType> { }): Promise<ConversationMessageStatsType> {
const { preview, activity, hasUserInitiatedMessages } = const { preview, activity, hasUserInitiatedMessages } =
await channels.getConversationMessageStats({ await channels.getConversationMessageStats({
conversationId, conversationId,
isGroup,
ourUuid, ourUuid,
}); });
@ -1318,11 +1328,13 @@ async function getLastConversationMessage({
} }
async function getMessageMetricsForConversation( async function getMessageMetricsForConversation(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
) { ) {
const result = await channels.getMessageMetricsForConversation( const result = await channels.getMessageMetricsForConversation(
conversationId, conversationId,
storyId storyId,
isGroup
); );
return result; return result;

View file

@ -388,6 +388,7 @@ export type DataInterface = {
) => Promise<number>; ) => Promise<number>;
getUnreadByConversationAndMarkRead: (options: { getUnreadByConversationAndMarkRead: (options: {
conversationId: string; conversationId: string;
isGroup?: boolean;
newestUnreadAt: number; newestUnreadAt: number;
readAt?: number; readAt?: number;
storyId?: UUIDStringType; storyId?: UUIDStringType;
@ -448,11 +449,13 @@ export type DataInterface = {
// getNewerMessagesByConversation is JSON on server, full message on Client // getNewerMessagesByConversation is JSON on server, full message on Client
getMessageMetricsForConversation: ( getMessageMetricsForConversation: (
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
) => Promise<ConversationMetricsType>; ) => Promise<ConversationMetricsType>;
// getConversationRangeCenteredOnMessage is JSON on server, full message on client // getConversationRangeCenteredOnMessage is JSON on server, full message on client
getConversationMessageStats: (options: { getConversationMessageStats: (options: {
conversationId: string; conversationId: string;
isGroup?: boolean;
ourUuid: UUIDStringType; ourUuid: UUIDStringType;
}) => Promise<ConversationMessageStatsType>; }) => Promise<ConversationMessageStatsType>;
getLastConversationMessage(options: { getLastConversationMessage(options: {
@ -613,6 +616,7 @@ export type ServerInterface = DataInterface & {
getOlderMessagesByConversation: ( getOlderMessagesByConversation: (
conversationId: string, conversationId: string,
options?: { options?: {
isGroup?: boolean;
limit?: number; limit?: number;
messageId?: string; messageId?: string;
receivedAt?: number; receivedAt?: number;
@ -623,6 +627,7 @@ export type ServerInterface = DataInterface & {
getNewerMessagesByConversation: ( getNewerMessagesByConversation: (
conversationId: string, conversationId: string,
options?: { options?: {
isGroup?: boolean;
limit?: number; limit?: number;
receivedAt?: number; receivedAt?: number;
sentAt?: number; sentAt?: number;
@ -683,6 +688,7 @@ export type ClientInterface = DataInterface & {
getOlderMessagesByConversation: ( getOlderMessagesByConversation: (
conversationId: string, conversationId: string,
options: { options: {
isGroup?: boolean;
limit?: number; limit?: number;
messageId?: string; messageId?: string;
receivedAt?: number; receivedAt?: number;
@ -693,6 +699,7 @@ export type ClientInterface = DataInterface & {
getNewerMessagesByConversation: ( getNewerMessagesByConversation: (
conversationId: string, conversationId: string,
options: { options: {
isGroup?: boolean;
limit?: number; limit?: number;
receivedAt?: number; receivedAt?: number;
sentAt?: number; sentAt?: number;

View file

@ -2030,8 +2030,11 @@ async function getMessageBySender({
return jsonToObject(rows[0].json); return jsonToObject(rows[0].json);
} }
export function _storyIdPredicate(storyId: string | undefined): string { export function _storyIdPredicate(
if (storyId === undefined) { storyId: string | undefined,
isGroup?: boolean
): string {
if (!isGroup && storyId === undefined) {
// We could use 'TRUE' here, but it is better to require `$storyId` // We could use 'TRUE' here, but it is better to require `$storyId`
// parameter // parameter
return '$storyId IS NULL'; return '$storyId IS NULL';
@ -2042,11 +2045,13 @@ export function _storyIdPredicate(storyId: string | undefined): string {
async function getUnreadByConversationAndMarkRead({ async function getUnreadByConversationAndMarkRead({
conversationId, conversationId,
isGroup,
newestUnreadAt, newestUnreadAt,
storyId, storyId,
readAt, readAt,
}: { }: {
conversationId: string; conversationId: string;
isGroup?: boolean;
newestUnreadAt: number; newestUnreadAt: number;
storyId?: UUIDStringType; storyId?: UUIDStringType;
readAt?: number; readAt?: number;
@ -2070,7 +2075,7 @@ async function getUnreadByConversationAndMarkRead({
) AND ) AND
expireTimer > 0 AND expireTimer > 0 AND
conversationId = $conversationId AND conversationId = $conversationId AND
(${_storyIdPredicate(storyId)}) AND (${_storyIdPredicate(storyId, isGroup)}) AND
received_at <= $newestUnreadAt; received_at <= $newestUnreadAt;
` `
).run({ ).run({
@ -2089,7 +2094,7 @@ async function getUnreadByConversationAndMarkRead({
WHERE WHERE
readStatus = ${ReadStatus.Unread} AND readStatus = ${ReadStatus.Unread} AND
conversationId = $conversationId AND conversationId = $conversationId AND
(${_storyIdPredicate(storyId)}) AND (${_storyIdPredicate(storyId, isGroup)}) AND
received_at <= $newestUnreadAt received_at <= $newestUnreadAt
ORDER BY received_at DESC, sent_at DESC; ORDER BY received_at DESC, sent_at DESC;
` `
@ -2109,7 +2114,7 @@ async function getUnreadByConversationAndMarkRead({
WHERE WHERE
readStatus = ${ReadStatus.Unread} AND readStatus = ${ReadStatus.Unread} AND
conversationId = $conversationId AND conversationId = $conversationId AND
(${_storyIdPredicate(storyId)}) AND (${_storyIdPredicate(storyId, isGroup)}) AND
received_at <= $newestUnreadAt; received_at <= $newestUnreadAt;
` `
).run({ ).run({
@ -2310,6 +2315,7 @@ async function _removeAllReactions(): Promise<void> {
async function getOlderMessagesByConversation( async function getOlderMessagesByConversation(
conversationId: string, conversationId: string,
options?: { options?: {
isGroup?: boolean;
limit?: number; limit?: number;
messageId?: string; messageId?: string;
receivedAt?: number; receivedAt?: number;
@ -2322,12 +2328,14 @@ async function getOlderMessagesByConversation(
function getOlderMessagesByConversationSync( function getOlderMessagesByConversationSync(
conversationId: string, conversationId: string,
{ {
isGroup,
limit = 100, limit = 100,
messageId, messageId,
receivedAt = Number.MAX_VALUE, receivedAt = Number.MAX_VALUE,
sentAt = Number.MAX_VALUE, sentAt = Number.MAX_VALUE,
storyId, storyId,
}: { }: {
isGroup?: boolean;
limit?: number; limit?: number;
messageId?: string; messageId?: string;
receivedAt?: number; receivedAt?: number;
@ -2344,7 +2352,7 @@ function getOlderMessagesByConversationSync(
conversationId = $conversationId AND conversationId = $conversationId AND
($messageId IS NULL OR id IS NOT $messageId) AND ($messageId IS NULL OR id IS NOT $messageId) AND
isStory IS 0 AND isStory IS 0 AND
(${_storyIdPredicate(storyId)}) AND (${_storyIdPredicate(storyId, isGroup)}) AND
( (
(received_at = $received_at AND sent_at < $sent_at) OR (received_at = $received_at AND sent_at < $sent_at) OR
received_at < $received_at received_at < $received_at
@ -2419,11 +2427,13 @@ async function getNewerMessagesByConversation(
function getNewerMessagesByConversationSync( function getNewerMessagesByConversationSync(
conversationId: string, conversationId: string,
{ {
isGroup,
limit = 100, limit = 100,
receivedAt = 0, receivedAt = 0,
sentAt = 0, sentAt = 0,
storyId, storyId,
}: { }: {
isGroup?: boolean;
limit?: number; limit?: number;
receivedAt?: number; receivedAt?: number;
sentAt?: number; sentAt?: number;
@ -2437,7 +2447,7 @@ function getNewerMessagesByConversationSync(
SELECT json FROM messages WHERE SELECT json FROM messages WHERE
conversationId = $conversationId AND conversationId = $conversationId AND
isStory IS 0 AND isStory IS 0 AND
(${_storyIdPredicate(storyId)}) AND (${_storyIdPredicate(storyId, isGroup)}) AND
( (
(received_at = $received_at AND sent_at > $sent_at) OR (received_at = $received_at AND sent_at > $sent_at) OR
received_at > $received_at received_at > $received_at
@ -2458,7 +2468,8 @@ function getNewerMessagesByConversationSync(
} }
function getOldestMessageForConversation( function getOldestMessageForConversation(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
): MessageMetricsType | undefined { ): MessageMetricsType | undefined {
const db = getInstance(); const db = getInstance();
const row = db const row = db
@ -2467,7 +2478,7 @@ function getOldestMessageForConversation(
SELECT * FROM messages WHERE SELECT * FROM messages WHERE
conversationId = $conversationId AND conversationId = $conversationId AND
isStory IS 0 AND isStory IS 0 AND
(${_storyIdPredicate(storyId)}) (${_storyIdPredicate(storyId, isGroup)})
ORDER BY received_at ASC, sent_at ASC ORDER BY received_at ASC, sent_at ASC
LIMIT 1; LIMIT 1;
` `
@ -2485,7 +2496,8 @@ function getOldestMessageForConversation(
} }
function getNewestMessageForConversation( function getNewestMessageForConversation(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
): MessageMetricsType | undefined { ): MessageMetricsType | undefined {
const db = getInstance(); const db = getInstance();
const row = db const row = db
@ -2494,7 +2506,7 @@ function getNewestMessageForConversation(
SELECT * FROM messages WHERE SELECT * FROM messages WHERE
conversationId = $conversationId AND conversationId = $conversationId AND
isStory IS 0 AND isStory IS 0 AND
(${_storyIdPredicate(storyId)}) (${_storyIdPredicate(storyId, isGroup)})
ORDER BY received_at DESC, sent_at DESC ORDER BY received_at DESC, sent_at DESC
LIMIT 1; LIMIT 1;
` `
@ -2513,9 +2525,11 @@ function getNewestMessageForConversation(
function getLastConversationActivity({ function getLastConversationActivity({
conversationId, conversationId,
isGroup,
ourUuid, ourUuid,
}: { }: {
conversationId: string; conversationId: string;
isGroup?: boolean;
ourUuid: UUIDStringType; ourUuid: UUIDStringType;
}): MessageType | undefined { }): MessageType | undefined {
const db = getInstance(); const db = getInstance();
@ -2527,6 +2541,7 @@ function getLastConversationActivity({
conversationId = $conversationId AND conversationId = $conversationId AND
shouldAffectActivity IS 1 AND shouldAffectActivity IS 1 AND
isTimerChangeFromSync IS 0 AND isTimerChangeFromSync IS 0 AND
${isGroup ? 'storyId IS NULL AND' : ''}
isGroupLeaveEventFromOther IS 0 isGroupLeaveEventFromOther IS 0
ORDER BY received_at DESC, sent_at DESC ORDER BY received_at DESC, sent_at DESC
LIMIT 1; LIMIT 1;
@ -2544,8 +2559,10 @@ function getLastConversationActivity({
} }
function getLastConversationPreview({ function getLastConversationPreview({
conversationId, conversationId,
isGroup,
}: { }: {
conversationId: string; conversationId: string;
isGroup?: boolean;
}): MessageType | undefined { }): MessageType | undefined {
const db = getInstance(); const db = getInstance();
const row = prepare( const row = prepare(
@ -2556,6 +2573,7 @@ function getLastConversationPreview({
conversationId = $conversationId AND conversationId = $conversationId AND
shouldAffectPreview IS 1 AND shouldAffectPreview IS 1 AND
isGroupLeaveEventFromOther IS 0 AND isGroupLeaveEventFromOther IS 0 AND
${isGroup ? 'storyId IS NULL AND' : ''}
( (
expiresAt IS NULL expiresAt IS NULL
OR OR
@ -2578,9 +2596,11 @@ function getLastConversationPreview({
async function getConversationMessageStats({ async function getConversationMessageStats({
conversationId, conversationId,
isGroup,
ourUuid, ourUuid,
}: { }: {
conversationId: string; conversationId: string;
isGroup?: boolean;
ourUuid: UUIDStringType; ourUuid: UUIDStringType;
}): Promise<ConversationMessageStatsType> { }): Promise<ConversationMessageStatsType> {
const db = getInstance(); const db = getInstance();
@ -2589,9 +2609,10 @@ async function getConversationMessageStats({
return { return {
activity: getLastConversationActivity({ activity: getLastConversationActivity({
conversationId, conversationId,
isGroup,
ourUuid, ourUuid,
}), }),
preview: getLastConversationPreview({ conversationId }), preview: getLastConversationPreview({ conversationId, isGroup }),
hasUserInitiatedMessages: hasUserInitiatedMessages(conversationId), hasUserInitiatedMessages: hasUserInitiatedMessages(conversationId),
}; };
})(); })();
@ -2625,7 +2646,8 @@ async function getLastConversationMessage({
function getOldestUnreadMessageForConversation( function getOldestUnreadMessageForConversation(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
): MessageMetricsType | undefined { ): MessageMetricsType | undefined {
const db = getInstance(); const db = getInstance();
const row = db const row = db
@ -2635,7 +2657,7 @@ function getOldestUnreadMessageForConversation(
conversationId = $conversationId AND conversationId = $conversationId AND
readStatus = ${ReadStatus.Unread} AND readStatus = ${ReadStatus.Unread} AND
isStory IS 0 AND isStory IS 0 AND
(${_storyIdPredicate(storyId)}) (${_storyIdPredicate(storyId, isGroup)})
ORDER BY received_at ASC, sent_at ASC ORDER BY received_at ASC, sent_at ASC
LIMIT 1; LIMIT 1;
` `
@ -2660,7 +2682,8 @@ async function getTotalUnreadForConversation(
} }
function getTotalUnreadForConversationSync( function getTotalUnreadForConversationSync(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
): number { ): number {
const db = getInstance(); const db = getInstance();
const row = db const row = db
@ -2672,7 +2695,7 @@ function getTotalUnreadForConversationSync(
conversationId = $conversationId AND conversationId = $conversationId AND
readStatus = ${ReadStatus.Unread} AND readStatus = ${ReadStatus.Unread} AND
isStory IS 0 AND isStory IS 0 AND
(${_storyIdPredicate(storyId)}) (${_storyIdPredicate(storyId, isGroup)})
` `
) )
.get({ .get({
@ -2689,23 +2712,35 @@ function getTotalUnreadForConversationSync(
async function getMessageMetricsForConversation( async function getMessageMetricsForConversation(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
): Promise<ConversationMetricsType> { ): Promise<ConversationMetricsType> {
return getMessageMetricsForConversationSync(conversationId, storyId); return getMessageMetricsForConversationSync(conversationId, storyId, isGroup);
} }
function getMessageMetricsForConversationSync( function getMessageMetricsForConversationSync(
conversationId: string, conversationId: string,
storyId?: UUIDStringType storyId?: UUIDStringType,
isGroup?: boolean
): ConversationMetricsType { ): ConversationMetricsType {
const oldest = getOldestMessageForConversation(conversationId, storyId); const oldest = getOldestMessageForConversation(
const newest = getNewestMessageForConversation(conversationId, storyId); conversationId,
storyId,
isGroup
);
const newest = getNewestMessageForConversation(
conversationId,
storyId,
isGroup
);
const oldestUnread = getOldestUnreadMessageForConversation( const oldestUnread = getOldestUnreadMessageForConversation(
conversationId, conversationId,
storyId storyId,
isGroup
); );
const totalUnread = getTotalUnreadForConversationSync( const totalUnread = getTotalUnreadForConversationSync(
conversationId, conversationId,
storyId storyId,
isGroup
); );
return { return {

View file

@ -79,6 +79,7 @@ import { useBoundActions } from '../../hooks/useBoundActions';
import type { NoopActionType } from './noop'; import type { NoopActionType } from './noop';
import { conversationJobQueue } from '../../jobs/conversationJobQueue'; import { conversationJobQueue } from '../../jobs/conversationJobQueue';
import type { TimelineMessageLoadingState } from '../../util/timelineUtil'; import type { TimelineMessageLoadingState } from '../../util/timelineUtil';
import { isGroup } from '../../util/whatTypeOfConversation';
// State // State
@ -2453,6 +2454,12 @@ export function reducer(
return state; return state;
} }
const conversationAttrs = state.conversationLookup[conversationId];
const isGroupStoryReply = isGroup(conversationAttrs) && data.storyId;
if (isGroupStoryReply) {
return state;
}
return { return {
...state, ...state,
messagesLookup: { messagesLookup: {

View file

@ -77,6 +77,60 @@ describe('sql/conversationSummary', () => {
assert.isTrue(messages.hasUserInitiatedMessages); assert.isTrue(messages.hasUserInitiatedMessages);
}); });
it('returns the latest message in current conversation excluding group story replies', async () => {
assert.lengthOf(await _getAllMessages(), 0);
const now = Date.now();
const conversationId = getUuid();
const ourUuid = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: now + 1,
received_at: now + 1,
timestamp: now + 1,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: now + 2,
received_at: now + 2,
timestamp: now + 2,
storyId: getUuid(),
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId,
sent_at: now + 3,
received_at: now + 3,
timestamp: now + 3,
storyId: getUuid(),
};
await saveMessages([message1, message2, message3], {
forceSave: true,
ourUuid,
});
assert.lengthOf(await _getAllMessages(), 3);
const messages = await getConversationMessageStats({
conversationId,
isGroup: true,
ourUuid,
});
assert.strictEqual(messages.activity?.body, message1.body, 'activity');
assert.strictEqual(messages.preview?.body, message1.body, 'preview');
assert.isTrue(messages.hasUserInitiatedMessages);
});
it('preview excludes several message types, allows type = NULL', async () => { it('preview excludes several message types, allows type = NULL', async () => {
assert.lengthOf(await _getAllMessages(), 0); assert.lengthOf(await _getAllMessages(), 0);

View file

@ -719,4 +719,80 @@ describe('sql/markRead', () => {
'should be reaction5' 'should be reaction5'
); );
}); });
it('does not include group story replies', async () => {
assert.lengthOf(await _getAllMessages(), 0);
const start = Date.now();
const readAt = start + 20;
const conversationId = getUuid();
const storyId = getUuid();
const ourUuid = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'story',
conversationId,
sent_at: start + 1,
received_at: start + 1,
timestamp: start + 1,
readStatus: ReadStatus.Read,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'incoming',
conversationId,
sent_at: start + 2,
received_at: start + 2,
timestamp: start + 2,
readStatus: ReadStatus.Unread,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'incoming',
conversationId,
sent_at: start + 3,
received_at: start + 3,
timestamp: start + 3,
readStatus: ReadStatus.Unread,
};
const message4: MessageAttributesType = {
id: getUuid(),
body: 'message 4',
type: 'incoming',
conversationId,
sent_at: start + 4,
received_at: start + 4,
timestamp: start + 4,
readStatus: ReadStatus.Unread,
storyId,
};
await saveMessages([message1, message2, message3, message4], {
forceSave: true,
ourUuid,
});
assert.lengthOf(await _getAllMessages(), 4);
const markedRead = await getUnreadByConversationAndMarkRead({
conversationId,
isGroup: true,
newestUnreadAt: message4.received_at,
readAt,
});
assert.lengthOf(markedRead, 1, '1 message marked read');
// Sorted in descending order
assert.strictEqual(
markedRead[0].id,
message3.id,
'first should be message3'
);
});
}); });

View file

@ -155,6 +155,59 @@ describe('sql/timelineFetches', () => {
assert.strictEqual(messages[0].id, message2.id); assert.strictEqual(messages[0].id, message2.id);
}); });
it('returns N most recent messages excluding group story replies', async () => {
assert.lengthOf(await _getAllMessages(), 0);
const now = Date.now();
const conversationId = getUuid();
const storyId = getUuid();
const ourUuid = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'story',
type: 'incoming',
conversationId,
sent_at: now - 20,
received_at: now - 20,
timestamp: now - 20,
storyId,
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'story reply 1',
type: 'outgoing',
conversationId,
sent_at: now - 10,
received_at: now - 10,
timestamp: now - 10,
storyId,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'normal message',
type: 'outgoing',
conversationId,
sent_at: now,
received_at: now,
timestamp: now,
};
await saveMessages([message1, message2, message3], {
forceSave: true,
ourUuid,
});
assert.lengthOf(await _getAllMessages(), 3);
const messages = await getOlderMessagesByConversation(conversationId, {
isGroup: true,
limit: 5,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages[0].id, message3.id);
});
it('returns N messages older than provided received_at', async () => { it('returns N messages older than provided received_at', async () => {
assert.lengthOf(await _getAllMessages(), 0); assert.lengthOf(await _getAllMessages(), 0);
@ -493,6 +546,60 @@ describe('sql/timelineFetches', () => {
assert.strictEqual(messages[0].id, message3.id); assert.strictEqual(messages[0].id, message3.id);
}); });
it('returns N messages excluding group story replies', async () => {
assert.lengthOf(await _getAllMessages(), 0);
const target = Date.now();
const conversationId = getUuid();
const ourUuid = getUuid();
const message1: MessageAttributesType = {
id: getUuid(),
body: 'message 1',
type: 'outgoing',
conversationId,
sent_at: target - 10,
received_at: target - 10,
timestamp: target - 10,
storyId: getUuid(),
};
const message2: MessageAttributesType = {
id: getUuid(),
body: 'message 2',
type: 'outgoing',
conversationId,
sent_at: target + 20,
received_at: target + 20,
timestamp: target + 20,
};
const message3: MessageAttributesType = {
id: getUuid(),
body: 'message 3',
type: 'outgoing',
conversationId,
sent_at: target + 10,
received_at: target + 10,
timestamp: target + 10,
storyId: getUuid(),
};
await saveMessages([message1, message2, message3], {
forceSave: true,
ourUuid,
});
assert.lengthOf(await _getAllMessages(), 3);
const messages = await getNewerMessagesByConversation(conversationId, {
isGroup: true,
limit: 5,
receivedAt: target,
sentAt: target,
});
assert.lengthOf(messages, 1);
assert.strictEqual(messages[0].id, message2.id);
});
it('returns N newer messages with same received_at, greater sent_at', async () => { it('returns N newer messages with same received_at, greater sent_at', async () => {
assert.lengthOf(await _getAllMessages(), 0); assert.lengthOf(await _getAllMessages(), 0);

View file

@ -6,6 +6,7 @@ import { hasErrors } from '../state/selectors/message';
import { readReceiptsJobQueue } from '../jobs/readReceiptsJobQueue'; import { readReceiptsJobQueue } from '../jobs/readReceiptsJobQueue';
import { readSyncJobQueue } from '../jobs/readSyncJobQueue'; import { readSyncJobQueue } from '../jobs/readSyncJobQueue';
import { notificationService } from '../services/notifications'; import { notificationService } from '../services/notifications';
import { isGroup } from './whatTypeOfConversation';
import * as log from '../logging/log'; import * as log from '../logging/log';
export async function markConversationRead( export async function markConversationRead(
@ -22,6 +23,7 @@ export async function markConversationRead(
conversationId, conversationId,
newestUnreadAt, newestUnreadAt,
readAt: options.readAt, readAt: options.readAt,
isGroup: isGroup(conversationAttrs),
}), }),
window.Signal.Data.getUnreadReactionsAndMarkRead({ window.Signal.Data.getUnreadReactionsAndMarkRead({
conversationId, conversationId,