468 lines
13 KiB
TypeScript
468 lines
13 KiB
TypeScript
// Copyright 2022 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import type { RawBodyRange } from '../types/BodyRange';
|
|
import type { MessageAttributesType } from '../model-types.d';
|
|
import type { ReplacementValuesType } from '../types/I18N';
|
|
import * as Attachment from '../types/Attachment';
|
|
import * as EmbeddedContact from '../types/EmbeddedContact';
|
|
import * as GroupChange from '../groupChange';
|
|
import * as MIME from '../types/MIME';
|
|
import * as Stickers from '../types/Stickers';
|
|
import * as expirationTimer from './expirationTimer';
|
|
import * as log from '../logging/log';
|
|
import { GiftBadgeStates } from '../components/conversation/Message';
|
|
import { dropNull } from './dropNull';
|
|
import { getCallHistorySelector } from '../state/selectors/callHistory';
|
|
import { getCallSelector, getActiveCall } from '../state/selectors/calling';
|
|
import { getCallingNotificationText } from './callingNotification';
|
|
import {
|
|
getConversationSelector,
|
|
getSelectedMessageIds,
|
|
getTargetedMessage,
|
|
} from '../state/selectors/conversations';
|
|
import { getStringForConversationMerge } from './getStringForConversationMerge';
|
|
import { getStringForProfileChange } from './getStringForProfileChange';
|
|
import { getTitleNoDefault, getNumber } from './getTitle';
|
|
import { findAndFormatContact } from './findAndFormatContact';
|
|
import { isMe } from './whatTypeOfConversation';
|
|
import { strictAssert } from './assert';
|
|
import {
|
|
getPropsForCallHistory,
|
|
hasErrors,
|
|
isCallHistory,
|
|
isChatSessionRefreshed,
|
|
isDeliveryIssue,
|
|
isEndSession,
|
|
isExpirationTimerUpdate,
|
|
isGroupUpdate,
|
|
isGroupV1Migration,
|
|
isGroupV2Change,
|
|
isIncoming,
|
|
isKeyChange,
|
|
isOutgoing,
|
|
isProfileChange,
|
|
isTapToView,
|
|
isUnsupportedMessage,
|
|
isConversationMerge,
|
|
} from '../state/selectors/message';
|
|
import {
|
|
getContact,
|
|
messageHasPaymentEvent,
|
|
getPaymentEventNotificationText,
|
|
} from '../messages/helpers';
|
|
|
|
function getNameForNumber(e164: string): string {
|
|
const conversation = window.ConversationController.get(e164);
|
|
if (!conversation) {
|
|
return e164;
|
|
}
|
|
return conversation.getTitle();
|
|
}
|
|
|
|
export function getNotificationDataForMessage(
|
|
attributes: MessageAttributesType
|
|
): {
|
|
bodyRanges?: ReadonlyArray<RawBodyRange>;
|
|
emoji?: string;
|
|
text: string;
|
|
} {
|
|
if (isDeliveryIssue(attributes)) {
|
|
return {
|
|
emoji: '⚠️',
|
|
text: window.i18n('icu:DeliveryIssue--preview'),
|
|
};
|
|
}
|
|
|
|
if (isConversationMerge(attributes)) {
|
|
const conversation = window.ConversationController.get(
|
|
attributes.conversationId
|
|
);
|
|
strictAssert(
|
|
conversation,
|
|
'getNotificationData/isConversationMerge/conversation'
|
|
);
|
|
strictAssert(
|
|
attributes.conversationMerge,
|
|
'getNotificationData/isConversationMerge/conversationMerge'
|
|
);
|
|
|
|
return {
|
|
text: getStringForConversationMerge({
|
|
obsoleteConversationTitle: getTitleNoDefault(
|
|
attributes.conversationMerge.renderInfo
|
|
),
|
|
obsoleteConversationNumber: getNumber(
|
|
attributes.conversationMerge.renderInfo
|
|
),
|
|
conversationTitle: conversation.getTitle(),
|
|
i18n: window.i18n,
|
|
}),
|
|
};
|
|
}
|
|
|
|
if (isChatSessionRefreshed(attributes)) {
|
|
return {
|
|
emoji: '🔁',
|
|
text: window.i18n('icu:ChatRefresh--notification'),
|
|
};
|
|
}
|
|
|
|
if (isUnsupportedMessage(attributes)) {
|
|
return {
|
|
text: window.i18n('icu:message--getDescription--unsupported-message'),
|
|
};
|
|
}
|
|
|
|
if (isGroupV1Migration(attributes)) {
|
|
return {
|
|
text: window.i18n('icu:GroupV1--Migration--was-upgraded'),
|
|
};
|
|
}
|
|
|
|
if (isProfileChange(attributes)) {
|
|
const { profileChange: change, changedId } = attributes;
|
|
const changedContact = findAndFormatContact(changedId);
|
|
if (!change) {
|
|
throw new Error('getNotificationData: profileChange was missing!');
|
|
}
|
|
|
|
return {
|
|
text: getStringForProfileChange(change, changedContact, window.i18n),
|
|
};
|
|
}
|
|
|
|
if (isGroupV2Change(attributes)) {
|
|
const { groupV2Change: change } = attributes;
|
|
strictAssert(
|
|
change,
|
|
'getNotificationData: isGroupV2Change true, but no groupV2Change!'
|
|
);
|
|
|
|
const changes = GroupChange.renderChange<string>(change, {
|
|
i18n: window.i18n,
|
|
ourAci: window.textsecure.storage.user.getCheckedAci(),
|
|
ourPni: window.textsecure.storage.user.getCheckedPni(),
|
|
renderContact: (conversationId: string) => {
|
|
const conversation = window.ConversationController.get(conversationId);
|
|
return conversation
|
|
? conversation.getTitle()
|
|
: window.i18n('icu:unknownContact');
|
|
},
|
|
renderString: (
|
|
key: string,
|
|
_i18n: unknown,
|
|
components: ReplacementValuesType<string | number> | undefined
|
|
) => {
|
|
// eslint-disable-next-line local-rules/valid-i18n-keys
|
|
return window.i18n(key, components);
|
|
},
|
|
});
|
|
|
|
return { text: changes.map(({ text }) => text).join(' ') };
|
|
}
|
|
|
|
if (messageHasPaymentEvent(attributes)) {
|
|
const sender = findAndFormatContact(attributes.sourceServiceId);
|
|
const conversation = findAndFormatContact(attributes.conversationId);
|
|
return {
|
|
text: getPaymentEventNotificationText(
|
|
attributes.payment,
|
|
sender.title,
|
|
conversation.title,
|
|
sender.isMe,
|
|
window.i18n
|
|
),
|
|
emoji: '💳',
|
|
};
|
|
}
|
|
|
|
const { attachments = [] } = attributes;
|
|
|
|
if (isTapToView(attributes)) {
|
|
if (attributes.isErased) {
|
|
return {
|
|
text: window.i18n('icu:message--getDescription--disappearing-media'),
|
|
};
|
|
}
|
|
|
|
if (Attachment.isImage(attachments)) {
|
|
return {
|
|
text: window.i18n('icu:message--getDescription--disappearing-photo'),
|
|
emoji: '📷',
|
|
};
|
|
}
|
|
if (Attachment.isVideo(attachments)) {
|
|
return {
|
|
text: window.i18n('icu:message--getDescription--disappearing-video'),
|
|
emoji: '🎥',
|
|
};
|
|
}
|
|
// There should be an image or video attachment, but we have a fallback just in
|
|
// case.
|
|
return { text: window.i18n('icu:mediaMessage'), emoji: '📎' };
|
|
}
|
|
|
|
if (isGroupUpdate(attributes)) {
|
|
const { group_update: groupUpdate } = attributes;
|
|
const fromContact = getContact(attributes);
|
|
const messages = [];
|
|
if (!groupUpdate) {
|
|
throw new Error('getNotificationData: Missing group_update');
|
|
}
|
|
|
|
if (groupUpdate.left === 'You') {
|
|
return { text: window.i18n('icu:youLeftTheGroup') };
|
|
}
|
|
if (groupUpdate.left) {
|
|
return {
|
|
text: window.i18n('icu:leftTheGroup', {
|
|
name: getNameForNumber(groupUpdate.left),
|
|
}),
|
|
};
|
|
}
|
|
|
|
if (!fromContact) {
|
|
return { text: '' };
|
|
}
|
|
|
|
if (isMe(fromContact.attributes)) {
|
|
messages.push(window.i18n('icu:youUpdatedTheGroup'));
|
|
} else {
|
|
messages.push(
|
|
window.i18n('icu:updatedTheGroup', {
|
|
name: fromContact.getTitle(),
|
|
})
|
|
);
|
|
}
|
|
|
|
if (groupUpdate.joined && groupUpdate.joined.length) {
|
|
const joinedContacts = groupUpdate.joined.map(item =>
|
|
window.ConversationController.getOrCreate(item, 'private')
|
|
);
|
|
const joinedWithoutMe = joinedContacts.filter(
|
|
contact => !isMe(contact.attributes)
|
|
);
|
|
|
|
if (joinedContacts.length > 1) {
|
|
messages.push(
|
|
window.i18n('icu:multipleJoinedTheGroup', {
|
|
names: joinedWithoutMe
|
|
.map(contact => contact.getTitle())
|
|
.join(', '),
|
|
})
|
|
);
|
|
|
|
if (joinedWithoutMe.length < joinedContacts.length) {
|
|
messages.push(window.i18n('icu:youJoinedTheGroup'));
|
|
}
|
|
} else {
|
|
const joinedContact = window.ConversationController.getOrCreate(
|
|
groupUpdate.joined[0],
|
|
'private'
|
|
);
|
|
if (isMe(joinedContact.attributes)) {
|
|
messages.push(window.i18n('icu:youJoinedTheGroup'));
|
|
} else {
|
|
messages.push(
|
|
window.i18n('icu:joinedTheGroup', {
|
|
name: joinedContacts[0].getTitle(),
|
|
})
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (groupUpdate.name) {
|
|
messages.push(
|
|
window.i18n('icu:titleIsNow', {
|
|
name: groupUpdate.name,
|
|
})
|
|
);
|
|
}
|
|
if (groupUpdate.avatarUpdated) {
|
|
messages.push(window.i18n('icu:updatedGroupAvatar'));
|
|
}
|
|
|
|
return { text: messages.join(' ') };
|
|
}
|
|
if (isEndSession(attributes)) {
|
|
return { text: window.i18n('icu:sessionEnded') };
|
|
}
|
|
if (isIncoming(attributes) && hasErrors(attributes)) {
|
|
return { text: window.i18n('icu:incomingError') };
|
|
}
|
|
|
|
const { body: untrimmedBody = '', bodyRanges = [] } = attributes;
|
|
const body = untrimmedBody.trim();
|
|
|
|
if (attachments.length) {
|
|
// This should never happen but we want to be extra-careful.
|
|
const attachment = attachments[0] || {};
|
|
const { contentType } = attachment;
|
|
|
|
const tooBigAttachmentCount = attachments.filter(
|
|
item => item.wasTooBig
|
|
).length;
|
|
if (tooBigAttachmentCount === attachments.length) {
|
|
return {
|
|
emoji: '📎',
|
|
text: window.i18n('icu:message--attachmentTooBig--one'),
|
|
};
|
|
}
|
|
|
|
if (contentType === MIME.IMAGE_GIF || Attachment.isGIF(attachments)) {
|
|
return {
|
|
bodyRanges,
|
|
emoji: '🎡',
|
|
text: body || window.i18n('icu:message--getNotificationText--gif'),
|
|
};
|
|
}
|
|
if (Attachment.isImage(attachments)) {
|
|
return {
|
|
bodyRanges,
|
|
emoji: '📷',
|
|
text: body || window.i18n('icu:message--getNotificationText--photo'),
|
|
};
|
|
}
|
|
if (Attachment.isVideo(attachments)) {
|
|
return {
|
|
bodyRanges,
|
|
emoji: '🎥',
|
|
text: body || window.i18n('icu:message--getNotificationText--video'),
|
|
};
|
|
}
|
|
if (Attachment.isVoiceMessage(attachment)) {
|
|
return {
|
|
bodyRanges,
|
|
emoji: '🎤',
|
|
text:
|
|
body ||
|
|
window.i18n('icu:message--getNotificationText--voice-message'),
|
|
};
|
|
}
|
|
if (Attachment.isAudio(attachments)) {
|
|
return {
|
|
bodyRanges,
|
|
emoji: '🔈',
|
|
text:
|
|
body ||
|
|
window.i18n('icu:message--getNotificationText--audio-message'),
|
|
};
|
|
}
|
|
|
|
return {
|
|
bodyRanges,
|
|
text: body || window.i18n('icu:message--getNotificationText--file'),
|
|
emoji: '📎',
|
|
};
|
|
}
|
|
|
|
const { sticker: stickerData } = attributes;
|
|
if (stickerData) {
|
|
const emoji =
|
|
Stickers.getSticker(stickerData.packId, stickerData.stickerId)?.emoji ||
|
|
stickerData?.emoji;
|
|
|
|
if (!emoji) {
|
|
log.warn('Unable to get emoji for sticker');
|
|
}
|
|
return {
|
|
text: window.i18n('icu:message--getNotificationText--stickers'),
|
|
emoji: dropNull(emoji),
|
|
};
|
|
}
|
|
|
|
if (isCallHistory(attributes)) {
|
|
const state = window.reduxStore.getState();
|
|
const callingNotification = getPropsForCallHistory(attributes, {
|
|
callSelector: getCallSelector(state),
|
|
activeCall: getActiveCall(state),
|
|
callHistorySelector: getCallHistorySelector(state),
|
|
conversationSelector: getConversationSelector(state),
|
|
selectedMessageIds: getSelectedMessageIds(state),
|
|
targetedMessageId: getTargetedMessage(state)?.id,
|
|
});
|
|
if (callingNotification) {
|
|
const text = getCallingNotificationText(callingNotification, window.i18n);
|
|
if (text != null) {
|
|
return {
|
|
text,
|
|
};
|
|
}
|
|
}
|
|
|
|
log.error("This call history message doesn't have valid call history");
|
|
}
|
|
if (isExpirationTimerUpdate(attributes)) {
|
|
const { expireTimer } = attributes.expirationTimerUpdate ?? {};
|
|
if (!expireTimer) {
|
|
return { text: window.i18n('icu:disappearingMessagesDisabled') };
|
|
}
|
|
|
|
return {
|
|
text: window.i18n('icu:timerSetTo', {
|
|
time: expirationTimer.format(window.i18n, expireTimer),
|
|
}),
|
|
};
|
|
}
|
|
|
|
if (isKeyChange(attributes)) {
|
|
const { key_changed: identifier } = attributes;
|
|
const conversation = window.ConversationController.get(identifier);
|
|
return {
|
|
text: window.i18n('icu:safetyNumberChangedGroup', {
|
|
name: conversation ? conversation.getTitle() : '',
|
|
}),
|
|
};
|
|
}
|
|
const { contact: contacts } = attributes;
|
|
if (contacts && contacts.length) {
|
|
return {
|
|
text:
|
|
EmbeddedContact.getName(contacts[0]) ||
|
|
window.i18n('icu:unknownContact'),
|
|
emoji: '👤',
|
|
};
|
|
}
|
|
|
|
const { giftBadge } = attributes;
|
|
if (giftBadge) {
|
|
const emoji = '✨';
|
|
|
|
if (isOutgoing(attributes)) {
|
|
const toContact = window.ConversationController.get(
|
|
attributes.conversationId
|
|
);
|
|
const recipient =
|
|
toContact?.getTitle() ?? window.i18n('icu:unknownContact');
|
|
return {
|
|
emoji,
|
|
text: window.i18n('icu:message--donation--preview--sent', {
|
|
recipient,
|
|
}),
|
|
};
|
|
}
|
|
|
|
const fromContact = getContact(attributes);
|
|
const sender = fromContact?.getTitle() ?? window.i18n('icu:unknownContact');
|
|
return {
|
|
emoji,
|
|
text:
|
|
giftBadge.state === GiftBadgeStates.Unopened
|
|
? window.i18n('icu:message--donation--preview--unopened', {
|
|
sender,
|
|
})
|
|
: window.i18n('icu:message--donation--preview--redeemed'),
|
|
};
|
|
}
|
|
|
|
if (body) {
|
|
return {
|
|
text: body,
|
|
bodyRanges,
|
|
};
|
|
}
|
|
|
|
return { text: '' };
|
|
}
|