1283 lines
39 KiB
TypeScript
1283 lines
39 KiB
TypeScript
import { Api as GramJs } from '../../../lib/gramjs';
|
|
|
|
import type { ApiDraft } from '../../../global/types';
|
|
import type {
|
|
ApiAction,
|
|
ApiAttachment,
|
|
ApiChat,
|
|
ApiContact,
|
|
ApiFactCheck,
|
|
ApiFormattedText,
|
|
ApiGroupCall,
|
|
ApiInputMessageReplyInfo,
|
|
ApiInputReplyInfo,
|
|
ApiKeyboardButton,
|
|
ApiMessage,
|
|
ApiMessageActionStarGift,
|
|
ApiMessageEntity,
|
|
ApiMessageForwardInfo,
|
|
ApiMessageReportResult,
|
|
ApiNewPoll,
|
|
ApiPeer,
|
|
ApiPhoto,
|
|
ApiPoll,
|
|
ApiQuickReply,
|
|
ApiReplyInfo,
|
|
ApiReplyKeyboard,
|
|
ApiSponsoredMessage,
|
|
ApiSticker,
|
|
ApiStory,
|
|
ApiStorySkipped,
|
|
ApiThreadInfo,
|
|
ApiVideo,
|
|
MediaContent,
|
|
PhoneCallAction,
|
|
} from '../../types';
|
|
import {
|
|
ApiMessageEntityTypes, MAIN_THREAD_ID,
|
|
} from '../../types';
|
|
|
|
import {
|
|
DELETED_COMMENTS_CHANNEL_ID,
|
|
SERVICE_NOTIFICATIONS_USER_ID,
|
|
SPONSORED_MESSAGE_CACHE_MS,
|
|
STARS_CURRENCY_CODE,
|
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
|
SUPPORTED_PHOTO_CONTENT_TYPES,
|
|
SUPPORTED_VIDEO_CONTENT_TYPES,
|
|
} from '../../../config';
|
|
import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnlyCountForMessage';
|
|
import { omitUndefined, pick } from '../../../util/iteratees';
|
|
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
|
import { interpolateArray } from '../../../util/waveform';
|
|
import { buildPeer } from '../gramjsBuilders';
|
|
import {
|
|
addPhotoToLocalDb,
|
|
type MediaRepairContext,
|
|
resolveMessageApiChatId,
|
|
serializeBytes,
|
|
} from '../helpers';
|
|
import { buildApiCallDiscardReason } from './calls';
|
|
import {
|
|
buildApiFormattedText,
|
|
buildApiPhoto,
|
|
} from './common';
|
|
import { buildMessageContent, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
|
import { buildApiStarGift } from './payments';
|
|
import { buildApiPeerColor, buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
|
import { buildMessageReactions } from './reactions';
|
|
|
|
const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
|
|
|
|
const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp';
|
|
const INPUT_WAVEFORM_LENGTH = 63;
|
|
const MIN_SCHEDULED_PERIOD = 10;
|
|
|
|
let localMessageCounter = 0;
|
|
function getNextLocalMessageId(lastMessageId = 0) {
|
|
return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT);
|
|
}
|
|
|
|
let currentUserId!: string;
|
|
|
|
export function setMessageBuilderCurrentUserId(_currentUserId: string) {
|
|
currentUserId = _currentUserId;
|
|
}
|
|
|
|
export function buildApiSponsoredMessage(
|
|
mtpMessage: GramJs.SponsoredMessage, chatId: string,
|
|
): ApiSponsoredMessage | undefined {
|
|
const {
|
|
message, entities, randomId, recommended, sponsorInfo, additionalInfo, buttonText, canReport, title, url, color,
|
|
} = mtpMessage;
|
|
|
|
let photo: ApiPhoto | undefined;
|
|
if (mtpMessage.photo instanceof GramJs.Photo) {
|
|
addPhotoToLocalDb(mtpMessage.photo);
|
|
photo = buildApiPhoto(mtpMessage.photo);
|
|
}
|
|
|
|
let media: MediaContent | undefined;
|
|
if (mtpMessage.media) {
|
|
media = buildMessageMediaContent(mtpMessage.media);
|
|
}
|
|
|
|
return {
|
|
chatId,
|
|
randomId: serializeBytes(randomId),
|
|
expiresAt: Math.round(Date.now() / 1000) + SPONSORED_MESSAGE_CACHE_MS,
|
|
isRecommended: recommended,
|
|
sponsorInfo,
|
|
additionalInfo,
|
|
buttonText,
|
|
canReport,
|
|
title,
|
|
url,
|
|
peerColor: color && buildApiPeerColor(color),
|
|
photo,
|
|
content: {
|
|
...media,
|
|
text: buildMessageTextContent(message, entities),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function buildApiMessage(mtpMessage: GramJs.TypeMessage): ApiMessage | undefined {
|
|
const chatId = resolveMessageApiChatId(mtpMessage);
|
|
if (
|
|
!chatId
|
|
|| !(mtpMessage instanceof GramJs.Message || mtpMessage instanceof GramJs.MessageService)) {
|
|
return undefined;
|
|
}
|
|
|
|
return buildApiMessageWithChatId(chatId, mtpMessage);
|
|
}
|
|
|
|
export function buildApiMessageFromShort(mtpMessage: GramJs.UpdateShortMessage): ApiMessage {
|
|
const chatId = buildApiPeerId(mtpMessage.userId, 'user');
|
|
|
|
return buildApiMessageWithChatId(chatId, {
|
|
...mtpMessage,
|
|
fromId: buildPeer(mtpMessage.out ? currentUserId : buildApiPeerId(mtpMessage.userId, 'user')),
|
|
peerId: buildPeer(mtpMessage.out ? buildApiPeerId(mtpMessage.userId, 'user') : currentUserId),
|
|
});
|
|
}
|
|
|
|
export function buildApiMessageFromShortChat(mtpMessage: GramJs.UpdateShortChatMessage): ApiMessage {
|
|
const chatId = buildApiPeerId(mtpMessage.chatId, 'chat');
|
|
|
|
return buildApiMessageWithChatId(chatId, {
|
|
...mtpMessage,
|
|
fromId: buildPeer(buildApiPeerId(mtpMessage.fromId, 'user')),
|
|
peerId: buildPeer(buildApiPeerId(mtpMessage.chatId, 'chat')),
|
|
});
|
|
}
|
|
|
|
export function buildApiMessageFromNotification(
|
|
notification: GramJs.UpdateServiceNotification,
|
|
currentDate: number,
|
|
): ApiMessage {
|
|
const localId = getNextLocalMessageId(currentDate);
|
|
const content = buildMessageContent(notification);
|
|
|
|
return {
|
|
id: localId,
|
|
chatId: SERVICE_NOTIFICATIONS_USER_ID,
|
|
date: notification.inboxDate || currentDate,
|
|
content,
|
|
isOutgoing: false,
|
|
};
|
|
}
|
|
|
|
export type UniversalMessage = (
|
|
Pick<GramJs.Message & GramJs.MessageService, ('id' | 'date' | 'peerId')>
|
|
& Partial<GramJs.Message & GramJs.MessageService>
|
|
);
|
|
|
|
export function buildApiMessageWithChatId(
|
|
chatId: string,
|
|
mtpMessage: UniversalMessage,
|
|
): ApiMessage {
|
|
const fromId = mtpMessage.fromId ? getApiChatIdFromMtpPeer(mtpMessage.fromId) : undefined;
|
|
const peerId = mtpMessage.peerId ? getApiChatIdFromMtpPeer(mtpMessage.peerId) : undefined;
|
|
|
|
const isChatWithSelf = !fromId && chatId === currentUserId;
|
|
const forwardInfo = mtpMessage.fwdFrom && buildApiMessageForwardInfo(mtpMessage.fwdFrom, isChatWithSelf);
|
|
|
|
const isSavedOutgoing = Boolean(!forwardInfo || forwardInfo.fromId === currentUserId || forwardInfo.isSavedOutgoing);
|
|
|
|
const isOutgoing = !isChatWithSelf ? Boolean(mtpMessage.out && !mtpMessage.post)
|
|
: isSavedOutgoing;
|
|
const content = buildMessageContent(mtpMessage);
|
|
const action = mtpMessage.action
|
|
&& buildAction(mtpMessage.action, fromId, peerId, Boolean(mtpMessage.post), isOutgoing);
|
|
if (action) {
|
|
content.action = action;
|
|
}
|
|
const isScheduled = mtpMessage.date > getServerTime() + MIN_SCHEDULED_PERIOD;
|
|
|
|
const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice
|
|
&& Boolean(mtpMessage.media.extendedMedia);
|
|
|
|
const isEdited = Boolean(mtpMessage.editDate) && !mtpMessage.editHide;
|
|
const {
|
|
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
|
} = buildReplyButtons(mtpMessage, isInvoiceMedia) || {};
|
|
const { mediaUnread: isMediaUnread, postAuthor } = mtpMessage;
|
|
const groupedId = mtpMessage.groupedId && String(mtpMessage.groupedId);
|
|
const isInAlbum = Boolean(groupedId) && !(content.document || content.audio || content.sticker);
|
|
const shouldHideKeyboardButtons = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide;
|
|
const isHideKeyboardSelective = mtpMessage.replyMarkup instanceof GramJs.ReplyKeyboardHide
|
|
&& mtpMessage.replyMarkup.selective;
|
|
const isProtected = mtpMessage.noforwards || isInvoiceMedia;
|
|
const isForwardingAllowed = !mtpMessage.noforwards;
|
|
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
|
const hasComments = mtpMessage.replies?.comments;
|
|
const senderBoosts = mtpMessage.fromBoostsApplied;
|
|
const factCheck = mtpMessage.factcheck && buildApiFactCheck(mtpMessage.factcheck);
|
|
|
|
const isInvertedMedia = mtpMessage.invertMedia;
|
|
|
|
const savedPeerId = mtpMessage.savedPeerId && getApiChatIdFromMtpPeer(mtpMessage.savedPeerId);
|
|
|
|
return omitUndefined<ApiMessage>({
|
|
id: mtpMessage.id,
|
|
chatId,
|
|
isOutgoing,
|
|
content,
|
|
date: mtpMessage.date,
|
|
senderId: fromId,
|
|
viewsCount: mtpMessage.views,
|
|
forwardsCount: mtpMessage.forwards,
|
|
isScheduled,
|
|
isFromScheduled: mtpMessage.fromScheduled,
|
|
isSilent: mtpMessage.silent,
|
|
isPinned: mtpMessage.pinned,
|
|
reactions: mtpMessage.reactions && buildMessageReactions(mtpMessage.reactions),
|
|
emojiOnlyCount,
|
|
...(mtpMessage.replyTo && { replyInfo: buildApiReplyInfo(mtpMessage.replyTo, mtpMessage) }),
|
|
forwardInfo,
|
|
isEdited,
|
|
editDate: mtpMessage.editDate,
|
|
isMediaUnread,
|
|
hasUnreadMention: mtpMessage.mentioned && isMediaUnread,
|
|
isMentioned: mtpMessage.mentioned,
|
|
...(groupedId && {
|
|
groupedId,
|
|
isInAlbum,
|
|
}),
|
|
inlineButtons,
|
|
...(keyboardButtons && {
|
|
keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
|
}),
|
|
...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons, isHideKeyboardSelective }),
|
|
...(mtpMessage.viaBotId && { viaBotId: buildApiPeerId(mtpMessage.viaBotId, 'user') }),
|
|
postAuthorTitle: postAuthor,
|
|
isProtected,
|
|
isForwardingAllowed,
|
|
hasComments,
|
|
savedPeerId,
|
|
senderBoosts,
|
|
viaBusinessBotId: mtpMessage.viaBusinessBotId?.toString(),
|
|
factCheck,
|
|
effectId: mtpMessage.effect?.toString(),
|
|
isInvertedMedia,
|
|
});
|
|
}
|
|
|
|
export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | undefined {
|
|
if (draft instanceof GramJs.DraftMessageEmpty) {
|
|
return undefined;
|
|
}
|
|
|
|
const {
|
|
message, entities, replyTo, date, effect,
|
|
} = draft;
|
|
|
|
const replyInfo = replyTo instanceof GramJs.InputReplyToMessage ? {
|
|
type: 'message',
|
|
replyToMsgId: replyTo.replyToMsgId,
|
|
replyToTopId: replyTo.topMsgId,
|
|
replyToPeerId: replyTo.replyToPeerId && getApiChatIdFromMtpPeer(replyTo.replyToPeerId),
|
|
quoteText: replyTo.quoteText ? buildMessageTextContent(replyTo.quoteText, replyTo.quoteEntities) : undefined,
|
|
} satisfies ApiInputMessageReplyInfo : undefined;
|
|
|
|
return {
|
|
text: message ? buildMessageTextContent(message, entities) : undefined,
|
|
replyInfo,
|
|
date,
|
|
effectId: effect?.toString(),
|
|
};
|
|
}
|
|
|
|
function buildApiMessageForwardInfo(fwdFrom: GramJs.MessageFwdHeader, isChatWithSelf = false): ApiMessageForwardInfo {
|
|
const savedFromPeerId = fwdFrom.savedFromPeer && getApiChatIdFromMtpPeer(fwdFrom.savedFromPeer);
|
|
const fromId = fwdFrom.fromId && getApiChatIdFromMtpPeer(fwdFrom.fromId);
|
|
|
|
return {
|
|
date: fwdFrom.date,
|
|
savedDate: fwdFrom.savedDate,
|
|
isImported: fwdFrom.imported,
|
|
isChannelPost: Boolean(fwdFrom.channelPost),
|
|
channelPostId: fwdFrom.channelPost,
|
|
isLinkedChannelPost: Boolean(fwdFrom.channelPost && savedFromPeerId === fromId
|
|
&& fwdFrom.savedFromMsgId === fwdFrom.channelPost && !isChatWithSelf),
|
|
savedFromPeerId,
|
|
isSavedOutgoing: fwdFrom.savedOut,
|
|
fromId,
|
|
fromChatId: fromId || savedFromPeerId,
|
|
fromMessageId: fwdFrom.savedFromMsgId || fwdFrom.channelPost,
|
|
hiddenUserName: fwdFrom.fromName,
|
|
postAuthorTitle: fwdFrom.postAuthor,
|
|
};
|
|
}
|
|
|
|
function buildApiReplyInfo(
|
|
replyHeader: GramJs.TypeMessageReplyHeader, context?: MediaRepairContext,
|
|
): ApiReplyInfo | undefined {
|
|
if (replyHeader instanceof GramJs.MessageReplyStoryHeader) {
|
|
return {
|
|
type: 'story',
|
|
peerId: getApiChatIdFromMtpPeer(replyHeader.peer),
|
|
storyId: replyHeader.storyId,
|
|
};
|
|
}
|
|
|
|
if (replyHeader instanceof GramJs.MessageReplyHeader) {
|
|
const {
|
|
replyFrom,
|
|
replyToMsgId,
|
|
replyToTopId,
|
|
replyMedia,
|
|
replyToPeerId,
|
|
forumTopic,
|
|
quote,
|
|
quoteText,
|
|
quoteEntities,
|
|
} = replyHeader;
|
|
|
|
return {
|
|
type: 'message',
|
|
replyToMsgId,
|
|
replyToTopId,
|
|
isForumTopic: forumTopic,
|
|
replyFrom: replyFrom && buildApiMessageForwardInfo(replyFrom),
|
|
replyToPeerId: replyToPeerId && getApiChatIdFromMtpPeer(replyToPeerId),
|
|
replyMedia: replyMedia && buildMessageMediaContent(replyMedia, context),
|
|
isQuote: quote,
|
|
quoteText: quoteText ? buildMessageTextContent(quoteText, quoteEntities) : undefined,
|
|
};
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
export function buildApiFactCheck(factCheck: GramJs.FactCheck): ApiFactCheck {
|
|
return {
|
|
shouldFetch: factCheck.needCheck,
|
|
hash: factCheck.hash.toString(),
|
|
text: factCheck.text && buildApiFormattedText(factCheck.text),
|
|
countryCode: factCheck.country,
|
|
};
|
|
}
|
|
|
|
function buildApiMessageActionStarGift(action: GramJs.MessageActionStarGift) : ApiMessageActionStarGift {
|
|
const {
|
|
nameHidden, saved, converted, gift, message, convertStars,
|
|
} = action;
|
|
|
|
return {
|
|
isNameHidden: Boolean(nameHidden),
|
|
isSaved: Boolean(saved),
|
|
isConverted: Boolean(converted),
|
|
gift: buildApiStarGift(gift),
|
|
message: message && buildApiFormattedText(message),
|
|
starsToConvert: convertStars?.toJSNumber(),
|
|
};
|
|
}
|
|
|
|
function buildAction(
|
|
action: GramJs.TypeMessageAction,
|
|
senderId: string | undefined,
|
|
targetPeerId: string | undefined,
|
|
isChannelPost: boolean,
|
|
isOutgoing: boolean,
|
|
): ApiAction | undefined {
|
|
if (action instanceof GramJs.MessageActionEmpty) {
|
|
return undefined;
|
|
}
|
|
|
|
let phoneCall: PhoneCallAction | undefined;
|
|
let call: Partial<ApiGroupCall> | undefined;
|
|
let amount: number | undefined;
|
|
let stars: number | undefined;
|
|
let starGift: ApiMessageActionStarGift | undefined;
|
|
let currency: string | undefined;
|
|
let giftCryptoInfo: {
|
|
currency: string;
|
|
amount: number;
|
|
} | undefined;
|
|
let text: string;
|
|
const translationValues: string[] = [];
|
|
let type: ApiAction['type'] = 'other';
|
|
let photo: ApiPhoto | undefined;
|
|
let score: number | undefined;
|
|
let months: number | undefined;
|
|
let topicEmojiIconId: string | undefined;
|
|
let isTopicAction: boolean | undefined;
|
|
let slug: string | undefined;
|
|
let isGiveaway: boolean | undefined;
|
|
let isUnclaimed: boolean | undefined;
|
|
let pluralValue: number | undefined;
|
|
let transactionId: string | undefined;
|
|
let message: ApiFormattedText | undefined;
|
|
|
|
let targetUserIds = 'users' in action
|
|
? action.users && action.users.map((id) => buildApiPeerId(id, 'user'))
|
|
: ('userId' in action && [buildApiPeerId(action.userId, 'user')]) || [];
|
|
|
|
let targetChatId;
|
|
if (action instanceof GramJs.MessageActionChatCreate) {
|
|
text = 'Notification.CreatedChatWithTitle';
|
|
translationValues.push('%action_origin%', action.title);
|
|
type = 'chatCreate';
|
|
} else if (action instanceof GramJs.MessageActionChatEditTitle) {
|
|
if (isChannelPost) {
|
|
text = 'Channel.MessageTitleUpdated';
|
|
translationValues.push(action.title);
|
|
} else {
|
|
text = 'Notification.ChangedGroupName';
|
|
translationValues.push('%action_origin%', action.title);
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionChatEditPhoto) {
|
|
if (isChannelPost) {
|
|
text = 'Channel.MessagePhotoUpdated';
|
|
} else {
|
|
text = 'Notification.ChangedGroupPhoto';
|
|
translationValues.push('%action_origin%');
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
|
|
if (isChannelPost) {
|
|
text = 'Channel.MessagePhotoRemoved';
|
|
} else {
|
|
text = 'Group.MessagePhotoRemoved';
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionChatAddUser) {
|
|
if (!senderId || targetUserIds.includes(senderId)) {
|
|
text = 'Notification.JoinedChat';
|
|
translationValues.push('%target_user%');
|
|
} else {
|
|
text = 'Notification.Invited';
|
|
translationValues.push('%action_origin%', '%target_user%');
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionChatDeleteUser) {
|
|
if (!senderId || targetUserIds.includes(senderId)) {
|
|
text = 'Notification.LeftChat';
|
|
translationValues.push('%target_user%');
|
|
} else {
|
|
text = 'Notification.Kicked';
|
|
translationValues.push('%action_origin%', '%target_user%');
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionChatJoinedByLink) {
|
|
text = 'Notification.JoinedGroupByLink';
|
|
translationValues.push('%action_origin%');
|
|
} else if (action instanceof GramJs.MessageActionChannelCreate) {
|
|
text = 'Notification.CreatedChannel';
|
|
} else if (action instanceof GramJs.MessageActionChatMigrateTo) {
|
|
targetChatId = getApiChatIdFromMtpPeer(action);
|
|
text = 'Migrated to %target_chat%';
|
|
translationValues.push('%target_chat%');
|
|
} else if (action instanceof GramJs.MessageActionChannelMigrateFrom) {
|
|
targetChatId = getApiChatIdFromMtpPeer(action);
|
|
text = 'Migrated from %target_chat%';
|
|
translationValues.push('%target_chat%');
|
|
} else if (action instanceof GramJs.MessageActionPinMessage) {
|
|
text = 'Chat.Service.Group.UpdatedPinnedMessage1';
|
|
translationValues.push('%action_origin%', '%message%');
|
|
} else if (action instanceof GramJs.MessageActionHistoryClear) {
|
|
text = 'HistoryCleared';
|
|
type = 'historyClear';
|
|
} else if (action instanceof GramJs.MessageActionPhoneCall) {
|
|
const withDuration = Boolean(action.duration);
|
|
text = [
|
|
withDuration ? 'ChatList.Service' : 'Chat',
|
|
action.video ? 'VideoCall' : 'Call',
|
|
isOutgoing ? (withDuration ? 'outgoing' : 'Outgoing') : (withDuration ? 'incoming' : 'Incoming'),
|
|
].join('.');
|
|
|
|
if (withDuration) {
|
|
const mins = Math.max(Math.round(action.duration! / 60), 1);
|
|
translationValues.push(`${mins} min${mins > 1 ? 's' : ''}`);
|
|
}
|
|
|
|
phoneCall = {
|
|
isOutgoing,
|
|
isVideo: action.video,
|
|
duration: action.duration,
|
|
reason: buildApiCallDiscardReason(action.reason),
|
|
};
|
|
} else if (action instanceof GramJs.MessageActionInviteToGroupCall) {
|
|
text = 'Notification.VoiceChatInvitation';
|
|
call = {
|
|
id: action.call.id.toString(),
|
|
accessHash: action.call.accessHash.toString(),
|
|
};
|
|
translationValues.push('%action_origin%', '%target_user%');
|
|
} else if (action instanceof GramJs.MessageActionContactSignUp) {
|
|
text = 'Notification.Joined';
|
|
translationValues.push('%action_origin%');
|
|
type = 'contactSignUp';
|
|
} else if (action instanceof GramJs.MessageActionPaymentSent) {
|
|
amount = Number(action.totalAmount);
|
|
currency = action.currency;
|
|
text = 'PaymentSuccessfullyPaid';
|
|
type = 'receipt';
|
|
if (targetPeerId) {
|
|
targetUserIds.push(targetPeerId);
|
|
}
|
|
translationValues.push('%payment_amount%', '%target_user%', '%product%');
|
|
} else if (action instanceof GramJs.MessageActionGroupCall) {
|
|
if (action.duration) {
|
|
const mins = Math.max(Math.round(action.duration / 60), 1);
|
|
text = 'Notification.VoiceChatEnded';
|
|
translationValues.push(`${mins} min${mins > 1 ? 's' : ''}`);
|
|
} else {
|
|
text = 'Notification.VoiceChatStartedChannel';
|
|
call = {
|
|
id: action.call.id.toString(),
|
|
accessHash: action.call.accessHash.toString(),
|
|
};
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionBotAllowed) {
|
|
if (action.domain) {
|
|
text = 'ActionBotAllowed';
|
|
translationValues.push(action.domain);
|
|
} else if (action.fromRequest) {
|
|
text = 'lng_action_webapp_bot_allowed';
|
|
} else {
|
|
text = 'ActionAttachMenuBotAllowed';
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionCustomAction) {
|
|
text = action.message;
|
|
} else if (action instanceof GramJs.MessageActionChatJoinedByRequest) {
|
|
text = 'ChatService.UserJoinedGroupByRequest';
|
|
translationValues.push('%action_origin%');
|
|
} else if (action instanceof GramJs.MessageActionGameScore) {
|
|
text = senderId === currentUserId ? 'ActionYouScoredInGame' : 'ActionUserScoredInGame';
|
|
translationValues.push('%score%');
|
|
score = action.score;
|
|
} else if (action instanceof GramJs.MessageActionWebViewDataSent) {
|
|
text = 'Notification.WebAppSentData';
|
|
translationValues.push(action.text);
|
|
} else if (action instanceof GramJs.MessageActionGiftPremium) {
|
|
type = 'giftPremium';
|
|
text = isOutgoing ? 'ActionGiftOutbound' : 'ActionGiftInbound';
|
|
if (isOutgoing) {
|
|
translationValues.push('%gift_payment_amount%');
|
|
} else {
|
|
translationValues.push('%action_origin%', '%gift_payment_amount%');
|
|
}
|
|
if (action.message) {
|
|
message = buildApiFormattedText(action.message);
|
|
}
|
|
if (targetPeerId) {
|
|
targetUserIds.push(targetPeerId);
|
|
}
|
|
currency = action.currency;
|
|
if (action.cryptoCurrency) {
|
|
giftCryptoInfo = {
|
|
currency: action.cryptoCurrency,
|
|
amount: action.cryptoAmount!.toJSNumber(),
|
|
};
|
|
}
|
|
amount = action.amount.toJSNumber();
|
|
months = action.months;
|
|
} else if (action instanceof GramJs.MessageActionTopicCreate) {
|
|
text = 'TopicWasCreatedAction';
|
|
type = 'topicCreate';
|
|
translationValues.push(action.title);
|
|
} else if (action instanceof GramJs.MessageActionTopicEdit) {
|
|
if (action.closed !== undefined) {
|
|
text = action.closed ? 'TopicWasClosedAction' : 'TopicWasReopenedAction';
|
|
translationValues.push('%action_origin%', '%action_topic%');
|
|
} else if (action.hidden !== undefined) {
|
|
text = action.hidden ? 'TopicHidden2' : 'TopicShown';
|
|
} else if (action.title) {
|
|
text = 'TopicRenamedTo';
|
|
translationValues.push('%action_origin%', action.title);
|
|
} else if (action.iconEmojiId) {
|
|
text = 'TopicWasIconChangedToAction';
|
|
translationValues.push('%action_origin%', '%action_topic_icon%');
|
|
topicEmojiIconId = action.iconEmojiId.toString();
|
|
} else {
|
|
text = 'ChatList.UnsupportedMessage';
|
|
}
|
|
isTopicAction = true;
|
|
} else if (action instanceof GramJs.MessageActionSuggestProfilePhoto) {
|
|
const isVideo = action.photo instanceof GramJs.Photo && action.photo.videoSizes?.length;
|
|
text = senderId === currentUserId
|
|
? (isVideo ? 'ActionSuggestVideoFromYouDescription' : 'ActionSuggestPhotoFromYouDescription')
|
|
: (isVideo ? 'ActionSuggestVideoToYouDescription' : 'ActionSuggestPhotoToYouDescription');
|
|
type = 'suggestProfilePhoto';
|
|
translationValues.push('%target_user%');
|
|
|
|
if (targetPeerId) targetUserIds.push(targetPeerId);
|
|
} else if (action instanceof GramJs.MessageActionGiveawayLaunch) {
|
|
text = 'BoostingGiveawayJustStarted';
|
|
translationValues.push('%action_origin%');
|
|
} else if (action instanceof GramJs.MessageActionGiftCode) {
|
|
type = 'giftCode';
|
|
text = isOutgoing ? 'ActionGiftOutbound' : 'BoostingReceivedGiftNoName';
|
|
slug = action.slug;
|
|
months = action.months;
|
|
amount = action.amount?.toJSNumber();
|
|
isGiveaway = Boolean(action.viaGiveaway);
|
|
isUnclaimed = Boolean(action.unclaimed);
|
|
if (isOutgoing) {
|
|
translationValues.push('%gift_payment_amount%');
|
|
}
|
|
if (action.message) {
|
|
message = buildApiFormattedText(action.message);
|
|
}
|
|
|
|
currency = action.currency;
|
|
if (action.cryptoCurrency) {
|
|
giftCryptoInfo = {
|
|
currency: action.cryptoCurrency,
|
|
amount: action.cryptoAmount!.toJSNumber(),
|
|
};
|
|
}
|
|
if (action.boostPeer) {
|
|
targetChatId = getApiChatIdFromMtpPeer(action.boostPeer);
|
|
}
|
|
if (targetPeerId) {
|
|
targetUserIds.push(targetPeerId);
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionGiveawayResults) {
|
|
if (!action.winnersCount) {
|
|
text = 'lng_action_giveaway_results_none';
|
|
} else if (action.unclaimedCount) {
|
|
text = 'lng_action_giveaway_results_some';
|
|
} else {
|
|
text = 'BoostingGiveawayServiceWinnersSelected';
|
|
translationValues.push('%amount%');
|
|
amount = action.winnersCount;
|
|
pluralValue = action.winnersCount;
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionPrizeStars) {
|
|
type = 'prizeStars';
|
|
isUnclaimed = Boolean(action.unclaimed);
|
|
if (action.boostPeer) {
|
|
targetChatId = getApiChatIdFromMtpPeer(action.boostPeer);
|
|
}
|
|
text = 'Notification.StarsPrize';
|
|
stars = action.stars.toJSNumber();
|
|
transactionId = action.transactionId;
|
|
} else if (action instanceof GramJs.MessageActionBoostApply) {
|
|
type = 'chatBoost';
|
|
if (action.boosts === 1) {
|
|
text = senderId === currentUserId ? 'BoostingBoostsGroupByYouServiceMsg' : 'BoostingBoostsGroupByUserServiceMsg';
|
|
translationValues.push('%action_origin%');
|
|
} else {
|
|
text = senderId === currentUserId ? 'BoostingBoostsGroupByYouServiceMsgCount'
|
|
: 'BoostingBoostsGroupByUserServiceMsgCount';
|
|
translationValues.push(action.boosts.toString());
|
|
if (senderId !== currentUserId) {
|
|
translationValues.unshift('%action_origin%');
|
|
}
|
|
pluralValue = action.boosts;
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionPaymentRefunded) {
|
|
text = 'ActionRefunded';
|
|
amount = Number(action.totalAmount);
|
|
currency = action.currency;
|
|
} else if (action instanceof GramJs.MessageActionRequestedPeer) {
|
|
text = 'ActionRequestedPeer';
|
|
if (action.peers) {
|
|
targetUserIds = action.peers?.map((peer) => getApiChatIdFromMtpPeer(peer));
|
|
}
|
|
if (targetPeerId) {
|
|
translationValues.unshift('%action_origin%');
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionGiftStars) {
|
|
type = 'giftStars';
|
|
text = isOutgoing ? 'ActionGiftOutbound' : targetPeerId ? 'ActionGiftInbound' : 'BoostingReceivedGiftNoName';
|
|
if (isOutgoing) {
|
|
translationValues.push('%gift_payment_amount%');
|
|
} else {
|
|
translationValues.push('%action_origin%', '%gift_payment_amount%');
|
|
}
|
|
if (targetPeerId) {
|
|
targetUserIds.push(targetPeerId);
|
|
targetChatId = targetPeerId;
|
|
}
|
|
|
|
if (action.cryptoCurrency) {
|
|
giftCryptoInfo = {
|
|
currency: action.cryptoCurrency,
|
|
amount: action.cryptoAmount!.toJSNumber(),
|
|
};
|
|
}
|
|
|
|
currency = action.currency;
|
|
amount = action.amount.toJSNumber();
|
|
stars = action.stars.toJSNumber();
|
|
transactionId = action.transactionId;
|
|
} else if (action instanceof GramJs.MessageActionStarGift) {
|
|
type = 'starGift';
|
|
if (isOutgoing) {
|
|
text = 'ActionGiftOutbound';
|
|
translationValues.push('%gift_payment_amount%');
|
|
} else {
|
|
text = 'ActionGiftInbound';
|
|
translationValues.push('%action_origin%', '%gift_payment_amount%');
|
|
}
|
|
|
|
if (targetPeerId) {
|
|
targetUserIds.push(targetPeerId);
|
|
targetChatId = targetPeerId;
|
|
}
|
|
|
|
amount = action.gift.stars.toJSNumber();
|
|
currency = STARS_CURRENCY_CODE;
|
|
starGift = buildApiMessageActionStarGift(action);
|
|
} else {
|
|
text = 'ChatList.UnsupportedMessage';
|
|
}
|
|
|
|
if ('photo' in action && action.photo instanceof GramJs.Photo) {
|
|
addPhotoToLocalDb(action.photo);
|
|
photo = buildApiPhoto(action.photo);
|
|
}
|
|
|
|
return {
|
|
mediaType: 'action',
|
|
text,
|
|
type,
|
|
targetUserIds,
|
|
targetChatId,
|
|
photo, // TODO Only used internally now, will be used for the UI in future
|
|
amount,
|
|
stars,
|
|
starGift,
|
|
currency,
|
|
giftCryptoInfo,
|
|
isGiveaway,
|
|
slug,
|
|
translationValues,
|
|
call,
|
|
phoneCall,
|
|
score,
|
|
months,
|
|
topicEmojiIconId,
|
|
isTopicAction,
|
|
isUnclaimed,
|
|
pluralValue,
|
|
transactionId,
|
|
message,
|
|
};
|
|
}
|
|
|
|
function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: boolean): ApiReplyKeyboard | undefined {
|
|
const { replyMarkup, media } = message;
|
|
|
|
if (!(replyMarkup instanceof GramJs.ReplyKeyboardMarkup || replyMarkup instanceof GramJs.ReplyInlineMarkup)) {
|
|
return undefined;
|
|
}
|
|
|
|
const markup = replyMarkup.rows.map(({ buttons }) => {
|
|
return buttons.map((button): ApiKeyboardButton | undefined => {
|
|
const { text } = button;
|
|
|
|
if (button instanceof GramJs.KeyboardButton) {
|
|
return {
|
|
type: 'command',
|
|
text,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonUrl) {
|
|
if (button.url.includes('?startgroup=')) {
|
|
return {
|
|
type: 'unsupported',
|
|
text,
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: 'url',
|
|
text,
|
|
url: button.url,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonCallback) {
|
|
if (button.requiresPassword) {
|
|
return {
|
|
type: 'unsupported',
|
|
text,
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: 'callback',
|
|
text,
|
|
data: serializeBytes(button.data),
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonRequestPoll) {
|
|
return {
|
|
type: 'requestPoll',
|
|
text,
|
|
isQuiz: button.quiz,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonRequestPhone) {
|
|
return {
|
|
type: 'requestPhone',
|
|
text,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonBuy) {
|
|
if (media instanceof GramJs.MessageMediaInvoice && media.receiptMsgId) {
|
|
return {
|
|
type: 'receipt',
|
|
receiptMessageId: media.receiptMsgId,
|
|
};
|
|
}
|
|
if (shouldSkipBuyButton) return undefined;
|
|
return {
|
|
type: 'buy',
|
|
text,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonGame) {
|
|
return {
|
|
type: 'game',
|
|
text,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonSwitchInline) {
|
|
return {
|
|
type: 'switchBotInline',
|
|
text,
|
|
query: button.query,
|
|
isSamePeer: button.samePeer,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonUserProfile) {
|
|
return {
|
|
type: 'userProfile',
|
|
text,
|
|
userId: button.userId.toString(),
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonSimpleWebView) {
|
|
return {
|
|
type: 'simpleWebView',
|
|
text,
|
|
url: button.url,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonWebView) {
|
|
return {
|
|
type: 'webView',
|
|
text,
|
|
url: button.url,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonUrlAuth) {
|
|
return {
|
|
type: 'urlAuth',
|
|
text,
|
|
url: button.url,
|
|
buttonId: button.buttonId,
|
|
};
|
|
}
|
|
|
|
if (button instanceof GramJs.KeyboardButtonCopy) {
|
|
return {
|
|
type: 'copy',
|
|
text,
|
|
copyText: button.copyText,
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: 'unsupported',
|
|
text,
|
|
};
|
|
}).filter(Boolean);
|
|
});
|
|
|
|
if (markup.every((row) => !row.length)) return undefined;
|
|
|
|
return {
|
|
[replyMarkup instanceof GramJs.ReplyKeyboardMarkup ? 'keyboardButtons' : 'inlineButtons']: markup,
|
|
...(replyMarkup instanceof GramJs.ReplyKeyboardMarkup && {
|
|
keyboardPlaceholder: replyMarkup.placeholder,
|
|
isKeyboardSingleUse: replyMarkup.singleUse,
|
|
isKeyboardSelective: replyMarkup.selective,
|
|
}),
|
|
};
|
|
}
|
|
|
|
function buildNewPoll(poll: ApiNewPoll, localId: number): ApiPoll {
|
|
return {
|
|
mediaType: 'poll',
|
|
id: String(localId),
|
|
summary: pick(poll.summary, ['question', 'answers']),
|
|
results: {},
|
|
};
|
|
}
|
|
|
|
export function buildLocalMessage(
|
|
chat: ApiChat,
|
|
lastMessageId?: number,
|
|
text?: string,
|
|
entities?: ApiMessageEntity[],
|
|
replyInfo?: ApiInputReplyInfo,
|
|
attachment?: ApiAttachment,
|
|
sticker?: ApiSticker,
|
|
gif?: ApiVideo,
|
|
poll?: ApiNewPoll,
|
|
contact?: ApiContact,
|
|
groupedId?: string,
|
|
scheduledAt?: number,
|
|
sendAs?: ApiPeer,
|
|
story?: ApiStory | ApiStorySkipped,
|
|
isInvertedMedia?: true,
|
|
effectId?: string,
|
|
) {
|
|
const localId = getNextLocalMessageId(lastMessageId);
|
|
const media = attachment && buildUploadingMedia(attachment);
|
|
const isChannel = chat.type === 'chatTypeChannel';
|
|
|
|
const resultReplyInfo = replyInfo && buildReplyInfo(replyInfo, chat.isForum);
|
|
|
|
const localPoll = poll && buildNewPoll(poll, localId);
|
|
|
|
const message = {
|
|
id: localId,
|
|
chatId: chat.id,
|
|
content: omitUndefined({
|
|
text: text ? {
|
|
text,
|
|
entities,
|
|
} : undefined,
|
|
...media,
|
|
sticker,
|
|
video: gif || media?.video,
|
|
contact,
|
|
storyData: story && { mediaType: 'storyData', ...story },
|
|
pollId: localPoll?.id,
|
|
}),
|
|
date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(),
|
|
isOutgoing: !isChannel,
|
|
senderId: sendAs?.id || currentUserId,
|
|
replyInfo: resultReplyInfo,
|
|
...(groupedId && {
|
|
groupedId,
|
|
...(media && (media.photo || media.video) && { isInAlbum: true }),
|
|
}),
|
|
...(scheduledAt && { isScheduled: true }),
|
|
isForwardingAllowed: true,
|
|
isInvertedMedia,
|
|
effectId,
|
|
} satisfies ApiMessage;
|
|
|
|
const emojiOnlyCount = getEmojiOnlyCountForMessage(message.content, message.groupedId);
|
|
|
|
const finalMessage = {
|
|
...message,
|
|
...(emojiOnlyCount && { emojiOnlyCount }),
|
|
};
|
|
|
|
return {
|
|
message: finalMessage,
|
|
poll: localPoll,
|
|
};
|
|
}
|
|
|
|
export function buildLocalForwardedMessage({
|
|
toChat,
|
|
toThreadId,
|
|
message,
|
|
scheduledAt,
|
|
noAuthors,
|
|
noCaptions,
|
|
isCurrentUserPremium,
|
|
lastMessageId,
|
|
sendAs,
|
|
}: {
|
|
toChat: ApiChat;
|
|
toThreadId?: number;
|
|
message: ApiMessage;
|
|
scheduledAt?: number;
|
|
noAuthors?: boolean;
|
|
noCaptions?: boolean;
|
|
isCurrentUserPremium?: boolean;
|
|
lastMessageId?: number;
|
|
sendAs?: ApiPeer;
|
|
}): ApiMessage {
|
|
const localId = getNextLocalMessageId(lastMessageId);
|
|
const {
|
|
content,
|
|
chatId: fromChatId,
|
|
id: fromMessageId,
|
|
senderId,
|
|
groupedId,
|
|
isInAlbum,
|
|
isInvertedMedia,
|
|
} = message;
|
|
|
|
const isAudio = content.audio;
|
|
const asIncomingInChatWithSelf = (
|
|
toChat.id === currentUserId && (fromChatId !== toChat.id || message.forwardInfo) && !isAudio
|
|
);
|
|
const shouldHideText = Object.keys(content).length > 1 && content.text && noCaptions;
|
|
const shouldDropCustomEmoji = !isCurrentUserPremium;
|
|
const strippedText = content.text?.entities && shouldDropCustomEmoji ? {
|
|
text: content.text.text,
|
|
entities: content.text.entities.filter((entity) => entity.type !== ApiMessageEntityTypes.CustomEmoji),
|
|
} : content.text;
|
|
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
|
|
|
const updatedContent = {
|
|
...content,
|
|
text: !shouldHideText ? strippedText : undefined,
|
|
};
|
|
|
|
// TODO Prepare reply info between forwarded messages locally, to prevent height jumps
|
|
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
|
const replyInfo: ApiReplyInfo | undefined = toThreadId && !isToMainThread ? {
|
|
type: 'message',
|
|
replyToMsgId: toThreadId,
|
|
replyToTopId: toThreadId,
|
|
isForumTopic: toChat.isForum || undefined,
|
|
} : undefined;
|
|
|
|
return {
|
|
id: localId,
|
|
chatId: toChat.id,
|
|
content: updatedContent,
|
|
date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(),
|
|
isOutgoing: !asIncomingInChatWithSelf && toChat.type !== 'chatTypeChannel',
|
|
senderId: sendAs?.id || currentUserId,
|
|
sendingState: 'messageSendingStatePending',
|
|
groupedId,
|
|
isInAlbum,
|
|
isForwardingAllowed: true,
|
|
replyInfo,
|
|
isInvertedMedia,
|
|
...(toThreadId && toChat?.isForum && { isTopicReply: true }),
|
|
|
|
...(emojiOnlyCount && { emojiOnlyCount }),
|
|
// Forward info doesn't get added when user forwards own messages and when forwarding audio
|
|
...(message.chatId !== currentUserId && !isAudio && !noAuthors && {
|
|
forwardInfo: {
|
|
date: message.forwardInfo?.date || message.date,
|
|
savedDate: message.date,
|
|
isChannelPost: false,
|
|
fromChatId,
|
|
fromMessageId,
|
|
fromId: senderId,
|
|
savedFromPeerId: message.chatId,
|
|
},
|
|
}),
|
|
...(message.chatId === currentUserId && !noAuthors && { forwardInfo: message.forwardInfo }),
|
|
...(scheduledAt && { isScheduled: true }),
|
|
};
|
|
}
|
|
|
|
function buildReplyInfo(inputInfo: ApiInputReplyInfo, isForum?: boolean): ApiReplyInfo {
|
|
if (inputInfo.type === 'story') {
|
|
return {
|
|
type: 'story',
|
|
peerId: inputInfo.peerId,
|
|
storyId: inputInfo.storyId,
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: 'message',
|
|
replyToMsgId: inputInfo.replyToMsgId,
|
|
replyToTopId: inputInfo.replyToTopId,
|
|
replyToPeerId: inputInfo.replyToPeerId,
|
|
quoteText: inputInfo.quoteText,
|
|
isForumTopic: isForum && inputInfo.replyToTopId ? true : undefined,
|
|
...(Boolean(inputInfo.quoteText) && { isQuote: true }),
|
|
};
|
|
}
|
|
|
|
export function buildUploadingMedia(
|
|
attachment: ApiAttachment,
|
|
): MediaContent {
|
|
const {
|
|
filename: fileName,
|
|
blobUrl,
|
|
previewBlobUrl,
|
|
mimeType,
|
|
size,
|
|
audio,
|
|
shouldSendAsFile,
|
|
shouldSendAsSpoiler,
|
|
ttlSeconds,
|
|
} = attachment;
|
|
|
|
if (!shouldSendAsFile) {
|
|
if (attachment.quick) {
|
|
// TODO Handle GIF as video, but support playback in <video>
|
|
if (SUPPORTED_PHOTO_CONTENT_TYPES.has(mimeType)) {
|
|
const { width, height } = attachment.quick;
|
|
return {
|
|
photo: {
|
|
mediaType: 'photo',
|
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
|
sizes: [],
|
|
thumbnail: { width, height, dataUri: previewBlobUrl || blobUrl },
|
|
blobUrl,
|
|
date: Math.round(Date.now() / 1000),
|
|
isSpoiler: shouldSendAsSpoiler,
|
|
},
|
|
};
|
|
}
|
|
if (SUPPORTED_VIDEO_CONTENT_TYPES.has(mimeType)) {
|
|
const { width, height, duration } = attachment.quick;
|
|
return {
|
|
video: {
|
|
mediaType: 'video',
|
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
|
mimeType,
|
|
duration: duration || 0,
|
|
fileName,
|
|
width,
|
|
height,
|
|
blobUrl,
|
|
...(previewBlobUrl && { thumbnail: { width, height, dataUri: previewBlobUrl } }),
|
|
size,
|
|
isSpoiler: shouldSendAsSpoiler,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
if (attachment.voice) {
|
|
const { duration, waveform } = attachment.voice;
|
|
const { data: inputWaveform } = interpolateArray(waveform, INPUT_WAVEFORM_LENGTH);
|
|
return {
|
|
voice: {
|
|
mediaType: 'voice',
|
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
|
duration,
|
|
waveform: inputWaveform,
|
|
size,
|
|
},
|
|
ttlSeconds,
|
|
};
|
|
}
|
|
if (SUPPORTED_AUDIO_CONTENT_TYPES.has(mimeType)) {
|
|
const { duration, performer, title } = audio || {};
|
|
return {
|
|
audio: {
|
|
mediaType: 'audio',
|
|
id: LOCAL_MEDIA_UPLOADING_TEMP_ID,
|
|
mimeType,
|
|
fileName,
|
|
size,
|
|
duration: duration || 0,
|
|
title,
|
|
performer,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
return {
|
|
document: {
|
|
mediaType: 'document',
|
|
mimeType,
|
|
fileName,
|
|
size,
|
|
...(previewBlobUrl && { previewBlobUrl }),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function buildApiThreadInfoFromMessage(
|
|
mtpMessage: GramJs.TypeMessage,
|
|
): ApiThreadInfo | undefined {
|
|
const chatId = resolveMessageApiChatId(mtpMessage);
|
|
if (
|
|
!chatId
|
|
|| !(mtpMessage instanceof GramJs.Message)
|
|
|| !mtpMessage.replies) {
|
|
return undefined;
|
|
}
|
|
|
|
return buildApiThreadInfo(mtpMessage.replies, mtpMessage.id, chatId);
|
|
}
|
|
|
|
export function buildApiThreadInfo(
|
|
messageReplies: GramJs.TypeMessageReplies, messageId: number, chatId: string,
|
|
): ApiThreadInfo | undefined {
|
|
const {
|
|
channelId, replies, maxId, readMaxId, recentRepliers, comments,
|
|
} = messageReplies;
|
|
|
|
const apiChannelId = channelId ? buildApiPeerId(channelId, 'channel') : undefined;
|
|
if (apiChannelId === DELETED_COMMENTS_CHANNEL_ID) {
|
|
return undefined;
|
|
}
|
|
|
|
const baseThreadInfo = {
|
|
messagesCount: replies,
|
|
...(maxId && { lastMessageId: maxId }),
|
|
...(readMaxId && { lastReadMessageId: readMaxId }),
|
|
...(recentRepliers && { recentReplierIds: recentRepliers.map(getApiChatIdFromMtpPeer) }),
|
|
};
|
|
|
|
if (comments) {
|
|
return {
|
|
...baseThreadInfo,
|
|
isCommentsInfo: true,
|
|
chatId: apiChannelId!,
|
|
originChannelId: chatId,
|
|
originMessageId: messageId,
|
|
};
|
|
}
|
|
|
|
return {
|
|
...baseThreadInfo,
|
|
isCommentsInfo: false,
|
|
chatId,
|
|
threadId: messageId,
|
|
};
|
|
}
|
|
|
|
export function buildApiQuickReply(reply: GramJs.TypeQuickReply): ApiQuickReply {
|
|
const { shortcutId, shortcut, topMessage } = reply;
|
|
return {
|
|
id: shortcutId,
|
|
shortcut,
|
|
topMessageId: topMessage,
|
|
};
|
|
}
|
|
|
|
export function buildApiReportResult(
|
|
result: GramJs.TypeReportResult,
|
|
): ApiMessageReportResult {
|
|
if (result instanceof GramJs.ReportResultReported) {
|
|
return {
|
|
type: 'reported',
|
|
};
|
|
}
|
|
|
|
if (result instanceof GramJs.ReportResultAddComment) {
|
|
return {
|
|
type: 'comment',
|
|
isOptional: result.optional,
|
|
option: serializeBytes(result.option),
|
|
};
|
|
}
|
|
|
|
const title = result.title;
|
|
const options = result.options.map((option) => ({
|
|
text: option.text,
|
|
option: serializeBytes(option.option),
|
|
}));
|
|
|
|
return {
|
|
type: 'selectOption',
|
|
title,
|
|
options,
|
|
};
|
|
}
|