2023-01-03 19:55:46 +00:00
|
|
|
// Copyright 2020 Signal Messenger, LLC
|
2021-06-17 17:15:10 +00:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
/* eslint-disable max-classes-per-file */
|
|
|
|
|
|
|
|
import { Collection, Model } from 'backbone';
|
2022-07-11 18:35:55 +00:00
|
|
|
import type { ConversationModel } from '../models/conversations';
|
2021-10-26 19:15:33 +00:00
|
|
|
import type { MessageModel } from '../models/messages';
|
2022-07-11 18:35:55 +00:00
|
|
|
import type {
|
|
|
|
MessageAttributesType,
|
|
|
|
ReactionAttributesType,
|
|
|
|
} from '../model-types.d';
|
2022-11-22 18:43:43 +00:00
|
|
|
import * as Errors from '../types/errors';
|
2022-07-11 18:35:55 +00:00
|
|
|
import * as log from '../logging/log';
|
2022-05-10 19:02:21 +00:00
|
|
|
import { getContactId, getContact } from '../messages/helpers';
|
2022-07-11 18:35:55 +00:00
|
|
|
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
|
2022-05-10 19:02:21 +00:00
|
|
|
import { isOutgoing, isStory } from '../state/selectors/message';
|
2023-01-11 22:54:06 +00:00
|
|
|
import { strictAssert } from '../util/assert';
|
2023-05-16 17:37:12 +00:00
|
|
|
import { getMessageSentTimestampSet } from '../util/getMessageSentTimestampSet';
|
2021-06-17 17:15:10 +00:00
|
|
|
|
2021-07-28 21:37:09 +00:00
|
|
|
export class ReactionModel extends Model<ReactionAttributesType> {}
|
2021-06-17 17:15:10 +00:00
|
|
|
|
|
|
|
let singleton: Reactions | undefined;
|
|
|
|
|
2021-10-13 16:29:15 +00:00
|
|
|
export class Reactions extends Collection<ReactionModel> {
|
2021-06-17 17:15:10 +00:00
|
|
|
static getSingleton(): Reactions {
|
|
|
|
if (!singleton) {
|
|
|
|
singleton = new Reactions();
|
|
|
|
}
|
|
|
|
|
|
|
|
return singleton;
|
|
|
|
}
|
|
|
|
|
|
|
|
forMessage(message: MessageModel): Array<ReactionModel> {
|
2023-05-16 17:37:12 +00:00
|
|
|
const sentTimestamps = getMessageSentTimestampSet(message.attributes);
|
2021-06-17 17:15:10 +00:00
|
|
|
if (isOutgoing(message.attributes)) {
|
2023-05-16 17:37:12 +00:00
|
|
|
const outgoingReactions = this.filter(item =>
|
|
|
|
sentTimestamps.has(item.get('targetTimestamp'))
|
2021-06-17 17:15:10 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
if (outgoingReactions.length > 0) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info('Found early reaction for outgoing message');
|
2021-06-17 17:15:10 +00:00
|
|
|
this.remove(outgoingReactions);
|
|
|
|
return outgoingReactions;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-10 22:51:54 +00:00
|
|
|
const senderId = getContactId(message.attributes);
|
2021-06-17 17:15:10 +00:00
|
|
|
const reactionsBySource = this.filter(re => {
|
2022-08-09 21:39:00 +00:00
|
|
|
const targetSender = window.ConversationController.lookupOrCreate({
|
2021-06-17 17:15:10 +00:00
|
|
|
uuid: re.get('targetAuthorUuid'),
|
2022-12-03 01:05:27 +00:00
|
|
|
reason: 'Reactions.forMessage',
|
2021-06-17 17:15:10 +00:00
|
|
|
});
|
|
|
|
const targetTimestamp = re.get('targetTimestamp');
|
2023-05-16 17:37:12 +00:00
|
|
|
return (
|
|
|
|
targetSender?.id === senderId && sentTimestamps.has(targetTimestamp)
|
|
|
|
);
|
2021-06-17 17:15:10 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
if (reactionsBySource.length > 0) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info('Found early reaction for message');
|
2021-06-17 17:15:10 +00:00
|
|
|
this.remove(reactionsBySource);
|
|
|
|
return reactionsBySource;
|
|
|
|
}
|
|
|
|
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2022-07-11 18:35:55 +00:00
|
|
|
private async findMessage(
|
|
|
|
targetTimestamp: number,
|
|
|
|
targetConversationId: string
|
|
|
|
): Promise<MessageAttributesType | undefined> {
|
|
|
|
const messages = await window.Signal.Data.getMessagesBySentAt(
|
|
|
|
targetTimestamp
|
|
|
|
);
|
|
|
|
|
|
|
|
return messages.find(m => {
|
|
|
|
const contact = getContact(m);
|
|
|
|
|
|
|
|
if (!contact) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const mcid = contact.get('id');
|
|
|
|
return mcid === targetConversationId;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-01-11 22:54:06 +00:00
|
|
|
async onReaction(reaction: ReactionModel): Promise<void> {
|
2021-06-17 17:15:10 +00:00
|
|
|
try {
|
|
|
|
// The conversation the target message was in; we have to find it in the database
|
|
|
|
// to to figure that out.
|
2022-08-09 21:39:00 +00:00
|
|
|
const targetAuthorConversation =
|
|
|
|
window.ConversationController.lookupOrCreate({
|
2021-06-17 17:15:10 +00:00
|
|
|
uuid: reaction.get('targetAuthorUuid'),
|
2022-12-03 01:05:27 +00:00
|
|
|
reason: 'Reactions.onReaction',
|
2021-11-11 22:43:05 +00:00
|
|
|
});
|
2022-08-09 21:39:00 +00:00
|
|
|
const targetConversationId = targetAuthorConversation?.id;
|
2021-06-17 17:15:10 +00:00
|
|
|
if (!targetConversationId) {
|
|
|
|
throw new Error(
|
2022-08-09 21:39:00 +00:00
|
|
|
'onReaction: No conversationId returned from lookupOrCreate!'
|
2021-06-17 17:15:10 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-11 22:54:06 +00:00
|
|
|
const generatedMessage = reaction.get('storyReactionMessage');
|
|
|
|
strictAssert(
|
|
|
|
generatedMessage,
|
|
|
|
'Story reactions must provide storyReactionMessage'
|
|
|
|
);
|
2022-07-11 18:35:55 +00:00
|
|
|
const fromConversation = window.ConversationController.get(
|
|
|
|
generatedMessage.get('conversationId')
|
|
|
|
);
|
|
|
|
|
|
|
|
let targetConversation: ConversationModel | undefined | null;
|
|
|
|
|
|
|
|
const targetMessageCheck = await this.findMessage(
|
|
|
|
reaction.get('targetTimestamp'),
|
|
|
|
targetConversationId
|
|
|
|
);
|
|
|
|
if (!targetMessageCheck) {
|
|
|
|
log.info(
|
|
|
|
'No message for reaction',
|
2023-01-11 22:54:06 +00:00
|
|
|
reaction.get('timestamp'),
|
|
|
|
'targeting',
|
2022-07-11 18:35:55 +00:00
|
|
|
reaction.get('targetAuthorUuid'),
|
2021-11-11 22:43:05 +00:00
|
|
|
reaction.get('targetTimestamp')
|
|
|
|
);
|
2022-07-11 18:35:55 +00:00
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
fromConversation &&
|
|
|
|
isStory(targetMessageCheck) &&
|
|
|
|
isDirectConversation(fromConversation.attributes) &&
|
|
|
|
!isMe(fromConversation.attributes)
|
|
|
|
) {
|
|
|
|
targetConversation = fromConversation;
|
|
|
|
} else {
|
|
|
|
targetConversation =
|
|
|
|
await window.ConversationController.getConversationForTargetMessage(
|
|
|
|
targetConversationId,
|
|
|
|
reaction.get('targetTimestamp')
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-17 17:15:10 +00:00
|
|
|
if (!targetConversation) {
|
2021-09-17 18:27:53 +00:00
|
|
|
log.info(
|
2021-06-17 17:15:10 +00:00
|
|
|
'No target conversation for reaction',
|
|
|
|
reaction.get('targetAuthorUuid'),
|
|
|
|
reaction.get('targetTimestamp')
|
|
|
|
);
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
// awaiting is safe since `onReaction` is never called from inside the queue
|
2021-10-29 23:19:44 +00:00
|
|
|
await targetConversation.queueJob('Reactions.onReaction', async () => {
|
|
|
|
log.info('Handling reaction for', reaction.get('targetTimestamp'));
|
|
|
|
|
2022-07-11 18:35:55 +00:00
|
|
|
// Thanks TS.
|
|
|
|
if (!targetConversation) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-29 23:19:44 +00:00
|
|
|
// Message is fetched inside the conversation queue so we have the
|
|
|
|
// most recent data
|
2022-07-11 18:35:55 +00:00
|
|
|
const targetMessage = await this.findMessage(
|
|
|
|
reaction.get('targetTimestamp'),
|
|
|
|
targetConversationId
|
|
|
|
);
|
2021-10-29 23:19:44 +00:00
|
|
|
|
|
|
|
if (!targetMessage) {
|
|
|
|
return;
|
2021-06-17 17:15:10 +00:00
|
|
|
}
|
2021-10-29 23:19:44 +00:00
|
|
|
|
|
|
|
const message = window.MessageController.register(
|
|
|
|
targetMessage.id,
|
|
|
|
targetMessage
|
|
|
|
);
|
|
|
|
|
2022-05-10 19:02:21 +00:00
|
|
|
// Use the generated message in ts/background.ts to create a message
|
2023-01-01 11:41:40 +00:00
|
|
|
// if the reaction is targeted at a story.
|
2023-01-11 22:54:06 +00:00
|
|
|
if (!isStory(targetMessage)) {
|
|
|
|
await message.handleReaction(reaction);
|
|
|
|
} else {
|
|
|
|
await generatedMessage.handleReaction(reaction, {
|
|
|
|
storyMessage: targetMessage,
|
2022-08-15 18:47:45 +00:00
|
|
|
});
|
2022-05-10 19:02:21 +00:00
|
|
|
}
|
|
|
|
|
2021-10-29 23:19:44 +00:00
|
|
|
this.remove(reaction);
|
|
|
|
});
|
2021-06-17 17:15:10 +00:00
|
|
|
} catch (error) {
|
2022-11-22 18:43:43 +00:00
|
|
|
log.error('Reactions.onReaction error:', Errors.toLogFormat(error));
|
2021-06-17 17:15:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|