TelegramPWA/src/components/middle/message/ActionMessageText.tsx
2025-06-18 17:43:33 +02:00

779 lines
27 KiB
TypeScript

import { memo, type TeactNode } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type { ApiChat, ApiMessage, ApiPeer } from '../../../api/types';
import {
GENERAL_TOPIC_ID,
SERVICE_NOTIFICATIONS_USER_ID,
TME_LINK_PREFIX,
} from '../../../config';
import {
getMainUsername,
getMessageInvoice, getMessageText, isChatChannel,
} from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers';
import { getMessageReplyInfo } from '../../../global/helpers/replies';
import {
selectChat,
selectChatMessage,
selectPeer,
selectSender,
selectThreadIdFromMessage,
selectTopic,
} from '../../../global/selectors';
import { ensureProtocol } from '../../../util/browser/url';
import { formatDateTimeToString, formatShortDuration } from '../../../util/dates/dateFormat';
import { formatCurrency } from '../../../util/formatCurrency';
import { formatStarsAsText } from '../../../util/localization/format';
import { conjuctionWithNodes } from '../../../util/localization/utils';
import renderText from '../../common/helpers/renderText';
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
import {
getCallMessageKey,
getPinnedMediaValue,
renderMessageLink,
renderPeerLink,
translateWithYou,
} from './helpers/messageActions';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import CustomEmoji from '../../common/CustomEmoji';
import TopicDefaultIcon from '../../common/TopicDefaultIcon';
import Link from '../../ui/Link';
import styles from './ActionMessage.module.scss';
type OwnProps = {
message: ApiMessage;
isInsideTopic?: boolean;
asPreview?: boolean;
};
type StateProps = {
currentUserId?: string;
sender?: ApiPeer;
replyMessage?: ApiMessage;
chat?: ApiChat;
};
const NBSP = '\u00A0';
const DEFAULT_TOPIC_ICON_ID = '0';
const UNSUPPORTED_LANG_KEY = 'ActionUnsupported';
const ActionMessageText = ({
message,
currentUserId,
sender,
chat,
replyMessage,
isInsideTopic,
asPreview,
}: OwnProps & StateProps) => {
const {
openThread,
openTelegramLink,
openUrl,
} = getActions();
const { chatId, isOutgoing } = message;
const action = message.content.action!;
const lang = useLang();
function renderStrong(text: TeactNode) {
if (asPreview) return text;
return <span className={styles.strong}>{text}</span>;
}
const renderActionText = useLastCallback(() => {
const global = getGlobal();
const isChannel = chat && isChatChannel(chat);
const isServiceNotificationsChat = chatId === SERVICE_NOTIFICATIONS_USER_ID;
const isSavedMessages = chatId === currentUserId;
const senderTitle = sender && getPeerTitle(lang, sender);
const chatTitle = chat && getPeerTitle(lang, chat);
const userFallbackText = lang('ActionFallbackUser');
const chatFallbackText = lang('ActionFallbackChat');
const channelFallbackText = lang('ActionFallbackChannel');
const senderLink = renderPeerLink(sender?.id, senderTitle || userFallbackText, asPreview);
const chatLink = renderPeerLink(chat?.id, chatTitle || chatFallbackText, asPreview);
switch (action.type) {
case 'pinMessage': {
if (replyMessage) {
const formattedText = getMessageText(replyMessage);
if (formattedText) {
const textLink = renderMessageLink(
replyMessage,
renderTextWithEntities({
text: formattedText.text,
entities: formattedText.entities,
asPreview: true,
}),
asPreview,
);
return translateWithYou(
lang, 'ActionPinnedText', isOutgoing, { text: textLink, from: senderLink },
);
}
const mediaValue = getPinnedMediaValue(lang, replyMessage);
if (mediaValue) {
const messageLink = renderMessageLink(replyMessage, mediaValue, asPreview);
return translateWithYou(
lang, 'ActionPinnedMedia', isOutgoing, { from: senderLink, media: messageLink },
);
}
}
return translateWithYou(
lang,
'ActionPinnedNotFound',
isOutgoing,
{ from: senderLink },
);
}
case 'gameScore': {
const { score } = action;
const gameTitle = replyMessage?.content.game?.title;
const gameLink = gameTitle && renderMessageLink(replyMessage, renderText(gameTitle), asPreview);
if (gameLink) {
return translateWithYou(
lang,
'ActionGameScore',
isOutgoing,
{ from: senderLink, count: score, game: gameLink },
{ pluralValue: score },
);
}
return translateWithYou(
lang, 'ActionGameScoreNoGame', isOutgoing, { from: senderLink, count: score }, { pluralValue: score },
);
}
case 'chatJoinedByLink':
return lang('ActionUserJoinedByLink', { from: senderLink }, { withNodes: true });
case 'chatJoinedByRequest':
return translateWithYou(lang, 'ActionJoinedByRequest', isOutgoing, { from: senderLink });
case 'channelJoined': {
const { isViaRequest, inviterId } = action;
const inviter = inviterId ? selectPeer(global, inviterId) : undefined;
if (inviter && inviterId !== currentUserId) {
const inviterLink = renderPeerLink(inviterId, getPeerTitle(lang, inviter) || userFallbackText, asPreview);
return lang('ActionAddYou', { from: inviterLink }, { withNodes: true });
}
return lang(isViaRequest ? 'ActionChannelJoinedByRequestChannelYou' : 'ActionChannelJoinedYou');
}
case 'chatEditTitle': {
const { title } = action;
if (isChannel) return lang('ActionChangedTitleChannel', { title });
return translateWithYou(lang, 'ActionChangedTitle', isOutgoing, { title, from: senderLink });
}
case 'chatDeletePhoto':
return isChannel ? lang('ActionRemovedPhotoChannel')
: translateWithYou(lang, 'ActionRemovedPhoto', isOutgoing, { from: senderLink });
case 'chatEditPhoto':
return isChannel ? lang('ActionChangedPhotoChannel')
: translateWithYou(lang, 'ActionChangedPhoto', isOutgoing, { from: senderLink });
case 'chatCreate': {
const { title } = action;
return lang('ActionCreatedChat', { title, from: senderLink }, { withNodes: true });
}
case 'channelCreate': {
const { title } = action;
return isChannel ? lang('ActionCreatedChannel')
: translateWithYou(lang, 'ActionCreatedChat', isOutgoing, { title, from: senderLink });
}
case 'chatMigrateTo': {
const { channelId } = action;
const channel = selectChat(global, channelId)!;
const channelLink = renderPeerLink(channelId, getPeerTitle(lang, channel)!, asPreview);
return lang('ActionMigratedTo', { chat: channelLink }, { withNodes: true });
}
case 'channelMigrateFrom': {
const { chatId: originalChatId, title } = action;
const originalChatLink = renderPeerLink(originalChatId, title || chatFallbackText, asPreview);
return lang('ActionMigratedFrom', { chat: originalChatLink }, { withNodes: true });
}
case 'topicCreate': {
const { title, iconColor, iconEmojiId } = action;
const topicId = selectThreadIdFromMessage(global, message);
const topicLink = (
<Link
className={styles.topicLink}
onClick={() => openThread({ chatId, threadId: topicId })}
>
{iconEmojiId ? <CustomEmoji documentId={iconEmojiId} isSelectable />
: <TopicDefaultIcon topicId={topicId} title={title} iconColor={iconColor} />}
{NBSP}
{renderText(title)}
</Link>
);
return lang('ActionTopicCreated', { topic: topicLink }, { withNodes: true });
}
case 'topicEdit': {
const {
iconEmojiId, isClosed, isHidden, title,
} = action;
const topicId = selectThreadIdFromMessage(global, message);
const currentTopic = selectTopic(global, chatId, topicId);
const topicLink = (
<Link
className={styles.topicLink}
onClick={() => openThread({ chatId, threadId: topicId })}
>
{iconEmojiId && iconEmojiId !== DEFAULT_TOPIC_ICON_ID
? <CustomEmoji documentId={iconEmojiId} isSelectable />
: (
<TopicDefaultIcon
topicId={topicId}
title={title || currentTopic?.title || lang('ActionTopicPlaceholder')}
iconColor={currentTopic?.iconColor}
/>
)}
{topicId !== GENERAL_TOPIC_ID && NBSP}
{renderText(title || currentTopic?.title || lang('ActionTopicPlaceholder'))}
</Link>
);
const topicPlaceholderLink = (
<Link
className={styles.topicLink}
onClick={() => openThread({ chatId, threadId: topicId })}
>
{lang('ActionTopicPlaceholder')}
</Link>
);
if (isClosed !== undefined) {
if (isInsideTopic) {
return lang(isClosed ? 'ActionTopicClosedInside' : 'ActionTopicReopenedInside');
}
return lang(
isClosed ? 'ActionTopicClosed' : 'ActionTopicReopened',
{ from: senderLink, topic: topicLink },
{ withNodes: true },
);
}
if (isHidden !== undefined) {
if (isInsideTopic) {
return lang(isHidden ? 'ActionTopicHiddenInside' : 'ActionTopicUnhiddenInside');
}
return lang(
isHidden ? 'ActionTopicHidden' : 'ActionTopicUnhidden',
{ topic: topicLink },
{ withNodes: true },
);
}
if (title && iconEmojiId) {
return lang(
'ActionTopicIconAndRenamed',
{
from: senderLink,
link: topicPlaceholderLink,
topic: topicLink,
},
{ withNodes: true },
);
}
if (title === undefined) {
if (!iconEmojiId || iconEmojiId === DEFAULT_TOPIC_ICON_ID) {
return lang(
'ActionTopicIconRemoved', { from: senderLink, link: topicPlaceholderLink }, { withNodes: true },
);
}
return lang(
'ActionTopicIconChanged',
{
from: senderLink,
link: topicPlaceholderLink,
emoji: <CustomEmoji documentId={iconEmojiId} loopLimit={2} />,
},
{ withNodes: true },
);
}
return lang('ActionTopicRenamed', { from: senderLink, link: topicPlaceholderLink, title }, { withNodes: true });
}
case 'boostApply':
return translateWithYou(
lang,
'ActionBoostApply',
isOutgoing,
{ from: senderLink, count: action.boosts },
{ pluralValue: action.boosts },
);
case 'chatAddUser': {
const { userIds } = action;
if (sender?.id === userIds[0]) {
return translateWithYou(lang, 'ActionUserJoined', isOutgoing, { from: senderLink });
}
if (userIds.length === 1) {
const user = selectPeer(global, userIds[0]);
const userTitle = (user && getPeerTitle(lang, user)) || userFallbackText;
const userLink = renderPeerLink(user?.id, userTitle, asPreview);
return translateWithYou(lang, 'ActionAddUser', isOutgoing, { from: senderLink, user: userLink });
}
const users = userIds.map((userId) => selectPeer(global, userId)).filter(Boolean);
if (!users.length) {
return translateWithYou(lang, 'ActionAddUser', isOutgoing, { from: senderLink, user: userFallbackText });
}
const userLinks = users.map((user) => (
renderPeerLink(user.id, getPeerTitle(lang, user) || userFallbackText, asPreview)
));
return translateWithYou(
lang, 'ActionAddUsersMany', isOutgoing, { from: senderLink, users: conjuctionWithNodes(lang, userLinks) },
);
}
case 'chatDeleteUser': {
const { userId } = action;
if (sender?.id === userId) {
return translateWithYou(lang, 'ActionUserLeft', isOutgoing, { from: senderLink });
}
const user = selectPeer(global, userId);
const userTitle = (user && getPeerTitle(lang, user)) || userFallbackText;
const userLink = renderPeerLink(user?.id, userTitle, asPreview);
return translateWithYou(lang, 'ActionKickUser', isOutgoing, { from: senderLink, user: userLink });
}
case 'botAllowed': {
const {
app, domain, isAttachMenu, isFromRequest,
} = action;
if (isAttachMenu) return lang('ActionAttachMenuBotAllowed');
if (isFromRequest) return lang('ActionWebappBotAllowed');
if (app) {
const senderUsername = sender && getMainUsername(sender);
const link = senderUsername && `${TME_LINK_PREFIX + senderUsername}/${app.shortName}`;
const appLink = link
? <Link onClick={() => openTelegramLink({ url: link })}>{app.title}</Link>
: lang('ActionBotAppPlaceholder');
return lang('ActionBotAllowedFromApp', { app: appLink }, { withNodes: true });
}
if (!domain) return lang(UNSUPPORTED_LANG_KEY);
const url = ensureProtocol(domain);
const link = <Link onClick={() => openUrl({ url })}>{domain}</Link>;
return lang('ActionBotAllowedFromDomain', { domain: link }, { withNodes: true });
}
case 'giveawayLaunch': {
const { stars } = action;
if (stars) {
return lang(
isChannel ? 'ActionGiveawayStarsStarted' : 'ActionGiveawayStarsStartedGroup',
{ from: senderLink, amount: renderStrong(formatStarsAsText(lang, stars)) },
{ withNodes: true },
);
}
return lang(
isChannel ? 'ActionGiveawayStarted' : 'ActionGiveawayStartedGroup',
{ from: senderLink },
{ withNodes: true },
);
}
case 'giveawayResults': {
const { winnersCount, isStars, unclaimedCount } = action;
if (!winnersCount) return lang('ActionGiveawayResultsNone');
if (unclaimedCount) {
return lang(isStars ? 'ActionGiveawayResultsStarsSome' : 'ActionGiveawayResultsSome');
}
return lang(
isStars ? 'ActionGiveawayResultsStars' : 'ActionGiveawayResults',
{ count: winnersCount },
{ pluralValue: winnersCount },
);
}
case 'giftStars':
case 'giftPremium': {
const {
amount, currency, cryptoAmount, cryptoCurrency,
} = action;
const price = formatCurrency(lang, amount, currency, { asFontIcon: true });
const cryptoPrice = cryptoAmount ? formatCurrency(lang, cryptoAmount, cryptoCurrency!) : undefined;
const cost = cryptoPrice ? lang('ActionCostCrypto', { price, cryptoPrice }, { withNodes: true }) : price;
if (isServiceNotificationsChat) {
return lang('ActionGiftTextCostAnonymous', { cost }, { withNodes: true });
}
return translateWithYou(
lang, 'ActionGiftTextCost', isOutgoing, { from: senderLink, cost: renderStrong(cost) },
);
}
case 'prizeStars':
case 'giftCode': {
return translateWithYou(lang, 'ActionGiftTextUnknown', isOutgoing, undefined);
}
case 'groupCall': {
const { duration } = action;
const durationText = duration ? formatShortDuration(lang, duration) : undefined;
if (durationText) {
if (isChannel) {
return lang('ActionGroupCallFinishedChannel', { duration: durationText });
}
return lang(
'ActionGroupCallFinishedGroup', { from: senderLink, duration: durationText }, { withNodes: true },
);
}
if (isChannel) return lang('ActionGroupCallStartedChannel');
return lang('ActionGroupCallStartedGroup', { from: senderLink }, { withNodes: true });
}
case 'groupCallScheduled': {
const { scheduleDate } = action;
const formattedDate = formatDateTimeToString(scheduleDate * 1000, lang.code, true);
if (isChannel) return lang('ActionGroupCallScheduledChannel', { date: formattedDate });
return lang('ActionGroupCallScheduledGroup', { from: senderLink, date: formattedDate }, { withNodes: true });
}
case 'inviteToGroupCall': {
const { userIds } = action;
if (userIds.length === 1) {
const user = selectPeer(global, userIds[0]);
const userTitle = (user && getPeerTitle(lang, user)) || userFallbackText;
const userLink = renderPeerLink(user?.id, userTitle, asPreview);
return translateWithYou(lang, 'ActionVideoInvited', isOutgoing, { from: senderLink, user: userLink });
}
const users = userIds.map((userId) => selectPeer(global, userId)).filter(Boolean);
if (!users.length) {
return translateWithYou(
lang, 'ActionVideoInvited', isOutgoing, { from: senderLink, user: userFallbackText },
);
}
const userLinks = users.map((user) => (
renderPeerLink(user.id, getPeerTitle(lang, user) || userFallbackText, asPreview)
));
return translateWithYou(
lang, 'ActionVideoInvitedMany', isOutgoing, { from: senderLink, users: conjuctionWithNodes(lang, userLinks) },
);
}
case 'paymentSent': {
const {
currency, totalAmount, isRecurringInit, isRecurringUsed,
} = action;
const cost = renderStrong(formatCurrency(lang, totalAmount, currency, { asFontIcon: true }));
const invoice = replyMessage && getMessageInvoice(replyMessage);
const invoiceTitle = invoice?.title;
if (isRecurringUsed) {
return lang('ActionPaymentUsedRecurring', { amount: cost }, { withNodes: true });
}
if (!invoiceTitle) {
if (isRecurringInit) {
return lang('ActionPaymentInitRecurring', { amount: cost, user: chatLink }, { withNodes: true });
}
return lang('ActionPaymentDone', { amount: cost, user: chatLink }, { withNodes: true });
}
if (isRecurringInit) {
return lang(
'ActionPaymentInitRecurringFor',
{ amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage!, invoiceTitle, asPreview) },
{ withNodes: true },
);
}
return lang(
'ActionPaymentDoneFor',
{ amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage!, invoiceTitle, asPreview) },
{ withNodes: true },
);
}
case 'paymentRefunded': {
const { currency, totalAmount, peerId } = action;
const peer = selectPeer(global, peerId);
const peerTitle = (peer && getPeerTitle(lang, peer)) || userFallbackText;
const peerLink = renderPeerLink(peer?.id, peerTitle, asPreview);
const amount = formatCurrency(lang, totalAmount, currency, { asFontIcon: true });
return lang('ActionPaymentRefunded', { peer: peerLink, amount }, { withNodes: true });
}
case 'starGift': {
const {
gift, alreadyPaidUpgradeStars, peerId, savedId, fromId,
} = action;
const isToChannel = Boolean(peerId && savedId);
const fromPeer = fromId ? selectPeer(global, fromId) : sender;
const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText;
const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview);
const starsAmount = gift.stars + (alreadyPaidUpgradeStars || 0);
const cost = renderStrong(formatStarsAsText(lang, starsAmount));
if (isToChannel) {
const channelPeer = selectPeer(global, peerId!);
const isYou = fromPeer?.id === currentUserId;
const channelTitle = (channelPeer && getPeerTitle(lang, channelPeer)) || channelFallbackText;
const channelLink = renderPeerLink(peerId, channelTitle, asPreview);
return translateWithYou(
lang, 'ActionStarGiftSentChannel', isYou, { user: fromLink, channel: channelLink, cost },
);
}
if (isServiceNotificationsChat) {
return lang('ActionStarGiftReceivedAnonymous', { cost }, { withNodes: true });
}
if (isSavedMessages) {
return lang('ActionStarGiftSelfBought', { cost }, { withNodes: true });
}
if (isOutgoing) {
return lang('ActionStarGiftSent', { cost }, { withNodes: true });
}
return lang('ActionStarGiftReceived', { user: senderLink, cost }, { withNodes: true });
}
case 'starGiftUnique': {
const {
isTransferred, isUpgrade, savedId, peerId, fromId, resaleStars, gift,
} = action;
const isToChannel = Boolean(peerId && savedId);
const fromPeer = fromId ? selectPeer(global, fromId) : sender;
const fromTitle = (fromPeer && getPeerTitle(lang, fromPeer)) || userFallbackText;
const fromLink = renderPeerLink(fromPeer?.id, fromTitle, asPreview);
if (resaleStars) {
return lang(
isOutgoing
? 'ApiMessageMessageActionResaleStarGiftUniqueOutgoing'
: 'ApiMessageMessageActionResaleStarGiftUniqueIncoming',
{
gift: lang('GiftUnique', { title: gift.title, number: gift.number }),
stars: renderStrong(formatStarsAsText(lang, resaleStars)),
},
{ withNodes: true },
);
}
if (isToChannel) {
const channelPeer = selectPeer(global, peerId!);
const isYou = fromPeer?.id === currentUserId;
const isAnonymous = fromPeer?.id === SERVICE_NOTIFICATIONS_USER_ID;
const channelTitle = (channelPeer && getPeerTitle(lang, channelPeer)) || channelFallbackText;
const channelLink = renderPeerLink(peerId, channelTitle, asPreview);
if (isUpgrade) {
return translateWithYou(
lang, 'ActionStarGiftUpgradedChannel', isYou, { user: fromLink, channel: channelLink },
);
}
if (isTransferred) {
if (isAnonymous) {
return lang('ActionStarGiftTransferredUnknownChannel', { channel: channelLink }, { withNodes: true });
}
return translateWithYou(
lang, 'ActionStarGiftTransferredChannel', isYou, { user: fromLink, channel: channelLink },
);
}
}
if (isSavedMessages) {
if (isUpgrade) return lang('ActionStarGiftUpgradedSelf');
if (isTransferred) return lang('ActionStarGiftTransferredSelf');
}
if (isUpgrade) {
if (isOutgoing) {
return lang('ActionStarGiftUpgradedMine', { user: chatLink }, { withNodes: true });
}
if (isSavedMessages) {
return lang('ActionStarGiftUpgradedSelf');
}
return lang('ActionStarGiftUpgradedUser', { user: senderLink }, { withNodes: true });
}
if (isTransferred) {
if (sender?.id === SERVICE_NOTIFICATIONS_USER_ID) {
return lang('ActionStarGiftTransferredUnknown');
}
if (isSavedMessages) {
return lang('ActionStarGiftTransferredSelf');
}
if (isOutgoing) {
return lang('ActionStarGiftTransferredMine', { user: chatLink }, { withNodes: true });
}
return lang('ActionStarGiftTransferred', { user: senderLink }, { withNodes: true });
}
if (isOutgoing) {
return lang('ActionGiftUniqueSent');
}
return lang('ActionGiftUniqueReceived', { user: senderLink }, { withNodes: true });
}
case 'suggestProfilePhoto': {
const actionPeer = (isOutgoing ? chat : sender)!;
const actionPeerLink = renderPeerLink(
actionPeer.id, getPeerTitle(lang, actionPeer) || userFallbackText, asPreview,
);
return translateWithYou(lang, 'ActionSuggestedPhoto', isOutgoing, { user: actionPeerLink });
}
case 'webViewDataSent':
return lang('ActionWebviewDataDone', { text: action.text });
case 'expired': {
const { isRoundVideo, isVoice } = action;
if (isVoice) return lang('ActionExpiredVoice');
if (isRoundVideo) return lang('ActionExpiredVideo');
return lang(UNSUPPORTED_LANG_KEY);
}
case 'historyClear':
return lang('ActionHistoryCleared');
case 'screenshotTaken':
return translateWithYou(lang, 'ActionScreenshotTaken', isOutgoing, { from: senderLink });
case 'contactSignUp':
return lang('ActionUserRegistered', { from: senderLink }, { withNodes: true });
case 'customAction':
return action.message;
case 'paidMessagesPrice': {
const { stars, isAllowedInChannel } = action;
if (stars === 0) {
if (isChannel) {
return lang(
isAllowedInChannel ? 'ActionMessageChannelFree' : 'ActionMessageChannelDisabled',
{ peer: chatLink },
{ withNodes: true },
);
}
return translateWithYou(lang, 'ActionPaidMessagePriceFree', isOutgoing, { peer: senderLink });
}
return translateWithYou(lang, 'ActionPaidMessagePrice', isOutgoing, {
peer: senderLink,
amount: formatStarsAsText(lang, stars),
}, { withMarkdown: true });
}
case 'paidMessagesRefunded': {
const { stars } = action;
const user = selectPeer(global, chatId);
const userTitle = (user && getPeerTitle(lang, user)) || userFallbackText;
const key = isOutgoing
? 'ApiMessageActionPaidMessagesRefundedOutgoing'
: 'ApiMessageActionPaidMessagesRefundedIncoming';
return lang(key, {
stars: formatStarsAsText(lang, stars),
user: renderPeerLink(user?.id, userTitle),
}, { withNodes: true, withMarkdown: true });
}
case 'phoneCall': // Rendered as a regular message, but considered an action for the summary
return lang(getCallMessageKey(action, isOutgoing));
default:
return lang(UNSUPPORTED_LANG_KEY);
}
});
return renderActionText();
};
export default memo(withGlobal<OwnProps>(
(global, { message }): StateProps => {
const chat = selectChat(global, message.chatId);
const sender = selectSender(global, message);
const { replyToMsgId, replyToPeerId } = getMessageReplyInfo(message) || {};
const replyMessage = replyToMsgId
? selectChatMessage(global, replyToPeerId || message.chatId, replyToMsgId) : undefined;
return {
currentUserId: global.currentUserId,
replyMessage,
chat,
sender,
};
},
)(ActionMessageText));