TelegramPWA/src/api/gramjs/updates/mtpUpdateHandler.ts
2026-05-05 18:30:19 +02:00

1185 lines
39 KiB
TypeScript

import { Api as GramJs, type Update } from '../../../lib/gramjs';
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../../lib/gramjs/network';
import type { GroupCallConnectionData } from '../../../lib/vibecalls';
import {
type ApiMessage,
type ApiMessagePoll,
type ApiStory,
type ApiStorySkipped,
type ApiUpdateConnectionStateType,
type ApiWebPage,
MAIN_THREAD_ID,
} from '../../types';
import { DEBUG, GENERAL_TOPIC_ID } from '../../../config';
import {
omit, omitUndefined, pick,
} from '../../../util/iteratees';
import { getServerTimeOffset, setServerTimeOffset } from '../../../util/serverTime';
import { buildApiBotCommand, buildApiBotMenuButton } from '../apiBuilders/bots';
import {
buildApiGroupCall,
buildApiGroupCallParticipant,
buildPhoneCall,
getGroupCallId,
} from '../apiBuilders/calls';
import {
buildApiChatFolder,
buildApiChatFromPreview,
buildChatMember,
buildChatMembers,
buildChatTypingStatus,
} from '../apiBuilders/chats';
import {
buildApiFormattedText,
buildApiMessageEntity,
buildApiPhoto, buildApiUsernames, buildPrivacyRules,
} from '../apiBuilders/common';
import { buildApiStarGiftAuctionUserState, buildApiTypeStarGiftAuctionState } from '../apiBuilders/gifts';
import { omitVirtualClassFields } from '../apiBuilders/helpers';
import {
buildApiMessageExtendedMediaPreview,
buildBoughtMediaContent,
buildMessagePollFromMedia,
buildPoll,
buildPollResults,
buildWebPage,
buildWebPageFromMedia,
} from '../apiBuilders/messageContent';
import {
buildApiMessage,
buildApiMessageFromNotification,
buildApiMessageFromShort,
buildApiMessageFromShortChat,
buildApiQuickReply,
buildMessageDraft,
} from '../apiBuilders/messages';
import {
buildLangStrings,
buildPrivacyKey,
} from '../apiBuilders/misc';
import { buildApiCurrencyAmount } from '../apiBuilders/payments';
import {
buildApiEmojiStatus,
buildApiPeerId,
buildApiPeerNotifySettings,
getApiChatIdFromMtpPeer,
} from '../apiBuilders/peers';
import {
buildApiPaidReactionPrivacy,
buildApiReaction,
buildMessageReactions,
} from '../apiBuilders/reactions';
import { buildApiStealthMode, buildApiStory } from '../apiBuilders/stories';
import { buildApiEmojiInteraction, buildStickerSet } from '../apiBuilders/symbols';
import {
buildApiPeerSettings,
buildApiUserStatus,
} from '../apiBuilders/users';
import {
buildChatPhotoForLocalDb,
buildMessageFromUpdate,
} from '../gramjsBuilders';
import {
addPhotoToLocalDb,
addStoryToLocalDb,
} from '../helpers/localDb';
import {
isChatFolder,
log,
resolveMessageApiChatId,
serializeBytes,
} from '../helpers/misc';
import localDb from '../localDb';
import { scheduleMutedChatUpdate, scheduleMutedTopicUpdate } from '../scheduleUnmute';
import { sendApiUpdate } from './apiUpdateEmitter';
import { processMessageAndUpdateThreadInfo } from './entityProcessor';
import LocalUpdatePremiumFloodWait from './UpdatePremiumFloodWait';
import { LocalUpdateChannelPts, LocalUpdatePts } from './UpdatePts';
export function updater(update: Update) {
if (update instanceof UpdateServerTimeOffset) {
setServerTimeOffset(update.timeOffset);
sendApiUpdate({
'@type': 'updateServerTimeOffset',
serverTimeOffset: update.timeOffset,
});
} else if (update instanceof UpdateConnectionState) {
let connectionState: ApiUpdateConnectionStateType;
switch (update.state) {
case UpdateConnectionState.disconnected:
connectionState = 'connectionStateConnecting';
break;
case UpdateConnectionState.broken:
connectionState = 'connectionStateBroken';
break;
case UpdateConnectionState.connected:
default:
connectionState = 'connectionStateReady';
break;
}
sendApiUpdate({
'@type': 'updateConnectionState',
connectionState,
});
// Messages
} else if (
update instanceof GramJs.UpdateNewMessage
|| update instanceof GramJs.UpdateNewScheduledMessage
|| update instanceof GramJs.UpdateNewChannelMessage
|| update instanceof GramJs.UpdateShortChatMessage
|| update instanceof GramJs.UpdateShortMessage
) {
let message: ApiMessage | undefined;
let poll: ApiMessagePoll | undefined;
let webPage: ApiWebPage | undefined;
let shouldForceReply: boolean | undefined;
if (update instanceof GramJs.UpdateShortChatMessage) {
message = buildApiMessageFromShortChat(update);
} else if (update instanceof GramJs.UpdateShortMessage) {
message = buildApiMessageFromShort(update);
} else {
const mtpMessage = update.message;
// TODO Remove if proven not reproducing
if (mtpMessage instanceof GramJs.MessageEmpty) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('Unexpected update:', update.className, update);
}
return;
}
processMessageAndUpdateThreadInfo(mtpMessage);
message = buildApiMessage(mtpMessage)!;
if (mtpMessage instanceof GramJs.Message) {
poll = mtpMessage.media && buildMessagePollFromMedia(mtpMessage.media);
webPage = mtpMessage.media && buildWebPageFromMedia(mtpMessage.media);
}
shouldForceReply = 'replyMarkup' in update.message
&& update.message?.replyMarkup instanceof GramJs.ReplyKeyboardForceReply
&& (!update.message.replyMarkup.selective || message.isMentioned);
}
if (update instanceof GramJs.UpdateNewScheduledMessage) {
sendApiUpdate({
'@type': 'updateScheduledMessage',
id: message.id,
chatId: message.chatId,
message,
poll,
webPage,
isFromNew: true,
isFull: true,
});
} else {
sendApiUpdate({
'@type': 'updateMessage',
id: message.id,
chatId: message.chatId,
message,
shouldForceReply,
poll,
webPage,
isFromNew: true,
isFull: true,
});
}
// Some updates to a Chat/Channel don't have a dedicated update class.
// We can get info on some updates from Service Messages.
if (update.message instanceof GramJs.MessageService) {
const { action } = update.message;
if (action instanceof GramJs.MessageActionChatEditTitle) {
sendApiUpdate({
'@type': 'updateChat',
id: message.chatId,
chat: {
title: action.title,
},
});
} else if (action instanceof GramJs.MessageActionChatEditPhoto) {
const apiPhoto = action.photo instanceof GramJs.Photo && buildApiPhoto(action.photo);
if (!apiPhoto) return;
const photo = buildChatPhotoForLocalDb(action.photo);
const localDbChatId = resolveMessageApiChatId(update.message)!;
if (localDb.chats[localDbChatId]) {
localDb.chats[localDbChatId].photo = photo;
}
addPhotoToLocalDb(action.photo);
sendApiUpdate({
'@type': 'updateNewProfilePhoto',
peerId: message.chatId,
photo: apiPhoto,
});
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
const localDbChatId = resolveMessageApiChatId(update.message)!;
if (localDb.chats[localDbChatId]) {
localDb.chats[localDbChatId].photo = new GramJs.ChatPhotoEmpty();
}
sendApiUpdate({
'@type': 'updateDeleteProfilePhoto',
peerId: message.chatId,
});
} else if (action instanceof GramJs.MessageActionChatDeleteUser) {
if (update._entities && update._entities.some((e): e is GramJs.User => (
e instanceof GramJs.User && Boolean(e.self) && e.id === action.userId
))) {
sendApiUpdate({
'@type': 'updateChat',
id: message.chatId,
chat: {
isForbidden: true,
isNotJoined: true,
},
});
}
} else if (action instanceof GramJs.MessageActionChatJoinedByLink) {
const { fromId } = update.message;
if (fromId instanceof GramJs.PeerUser && update._entities?.some((e): e is GramJs.User => (
e instanceof GramJs.User && Boolean(e.self) && e.id === fromId.userId
))) {
sendApiUpdate({
'@type': 'updateChatJoin',
id: message.chatId,
});
}
} else if (action instanceof GramJs.MessageActionChatAddUser) {
if (update._entities && update._entities.some((e): e is GramJs.User => (
e instanceof GramJs.User && Boolean(e.self) && action.users.includes(e.id)
))) {
sendApiUpdate({
'@type': 'updateChatJoin',
id: message.chatId,
});
}
} else if (action instanceof GramJs.MessageActionGroupCall) {
const groupCall = action.call instanceof GramJs.InputGroupCall ? action.call : undefined;
if (!action.duration && groupCall) {
sendApiUpdate({
'@type': 'updateGroupCallChatId',
chatId: message.chatId,
call: {
id: groupCall.id.toString(),
accessHash: groupCall.accessHash.toString(),
},
});
}
} else if (action instanceof GramJs.MessageActionTopicEdit) {
const replyTo = update.message.replyTo instanceof GramJs.MessageReplyHeader
? update.message.replyTo
: undefined;
const {
replyToMsgId, replyToTopId, forumTopic: isTopicReply,
} = replyTo || {};
const topicId = !isTopicReply ? GENERAL_TOPIC_ID : replyToTopId || replyToMsgId || GENERAL_TOPIC_ID;
sendApiUpdate({
'@type': 'updateTopic',
chatId: getApiChatIdFromMtpPeer(update.message.peerId),
topicId,
});
} else if (action instanceof GramJs.MessageActionTopicCreate) {
sendApiUpdate({
'@type': 'updateTopics',
chatId: getApiChatIdFromMtpPeer(update.message.peerId),
});
}
}
} else if (update instanceof GramJs.UpdateQuickReplyMessage) {
const message = buildApiMessage(update.message);
if (!message) return;
const poll = update.message instanceof GramJs.Message && update.message.media
? buildMessagePollFromMedia(update.message.media) : undefined;
const webPage = update.message instanceof GramJs.Message && update.message.media
? buildWebPageFromMedia(update.message.media) : undefined;
sendApiUpdate({
'@type': 'updateQuickReplyMessage',
id: message.id,
message,
poll,
webPage,
});
} else if (update instanceof GramJs.UpdateDeleteQuickReplyMessages) {
sendApiUpdate({
'@type': 'deleteQuickReplyMessages',
quickReplyId: update.shortcutId,
messageIds: update.messages,
});
} else if (update instanceof GramJs.UpdateQuickReplies) {
const quickReplies = update.quickReplies.map(buildApiQuickReply);
sendApiUpdate({
'@type': 'updateQuickReplies',
quickReplies,
});
} else if (update instanceof GramJs.UpdateNewQuickReply) {
const quickReply = buildApiQuickReply(update.quickReply);
sendApiUpdate({
'@type': 'updateQuickReplies',
quickReplies: [quickReply],
});
} else if (update instanceof GramJs.UpdateDeleteQuickReply) {
sendApiUpdate({
'@type': 'deleteQuickReply',
quickReplyId: update.shortcutId,
});
} else if (
update instanceof GramJs.UpdateEditMessage
|| update instanceof GramJs.UpdateEditChannelMessage
) {
const mtpMessage = update.message;
// TODO Remove if proven not reproducing
if (mtpMessage instanceof GramJs.MessageEmpty) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('Unexpected update:', update.className, update);
}
return;
}
processMessageAndUpdateThreadInfo(mtpMessage);
// Workaround for a weird server behavior when own message is marked as incoming
const message = omit(buildApiMessage(mtpMessage)!, ['isOutgoing']) as ApiMessage;
const poll = mtpMessage instanceof GramJs.Message && mtpMessage.media
? buildMessagePollFromMedia(mtpMessage.media) : undefined;
const webPage = mtpMessage instanceof GramJs.Message && mtpMessage.media
? buildWebPageFromMedia(mtpMessage.media) : undefined;
sendApiUpdate({
'@type': 'updateMessage',
id: message.id,
chatId: message.chatId,
message,
poll,
webPage,
isFull: true,
});
} else if (update instanceof GramJs.UpdateMessageReactions) {
sendApiUpdate({
'@type': 'updateMessageReactions',
id: update.msgId,
threadId: update.topMsgId,
chatId: getApiChatIdFromMtpPeer(update.peer),
reactions: buildMessageReactions(update.reactions),
});
} else if (update instanceof GramJs.UpdateMessageExtendedMedia) {
const chatId = getApiChatIdFromMtpPeer(update.peer);
const isBought = update.extendedMedia[0] instanceof GramJs.MessageExtendedMedia;
if (isBought) {
const boughtMedia = buildBoughtMediaContent(update.extendedMedia);
if (!boughtMedia?.length) return;
sendApiUpdate({
'@type': 'updateMessageExtendedMedia',
id: update.msgId,
chatId,
isBought,
extendedMedia: boughtMedia,
});
return;
}
const previewMedia = !isBought ? update.extendedMedia
.filter((m): m is GramJs.MessageExtendedMediaPreview => m instanceof GramJs.MessageExtendedMediaPreview)
.map((m) => buildApiMessageExtendedMediaPreview(m))
.filter(Boolean) : undefined;
if (!previewMedia?.length) return;
sendApiUpdate({
'@type': 'updateMessageExtendedMedia',
id: update.msgId,
chatId,
extendedMedia: previewMedia,
});
} else if (update instanceof GramJs.UpdateDeleteMessages) {
sendApiUpdate({
'@type': 'deleteMessages',
ids: update.messages,
});
} else if (update instanceof GramJs.UpdateDeleteScheduledMessages) {
sendApiUpdate({
'@type': 'deleteScheduledMessages',
ids: update.messages,
newIds: update.sentMessages,
chatId: getApiChatIdFromMtpPeer(update.peer),
});
} else if (update instanceof GramJs.UpdateDeleteChannelMessages) {
const chatId = buildApiPeerId(update.channelId, 'channel');
sendApiUpdate({
'@type': 'deleteMessages',
ids: update.messages,
chatId,
});
} else if (update instanceof GramJs.UpdateServiceNotification) {
if (update.popup) {
sendApiUpdate({
'@type': 'error',
error: {
message: update.message,
entities: update.entities.map(buildApiMessageEntity),
},
});
} else {
const currentDate = Date.now() / 1000 + getServerTimeOffset();
const message = buildApiMessageFromNotification(update, currentDate);
processMessageAndUpdateThreadInfo(buildMessageFromUpdate(message.id, message.chatId, update));
sendApiUpdate({
'@type': 'updateServiceNotification',
message,
});
}
} else if (update instanceof GramJs.UpdateReadMessagesContents) {
sendApiUpdate({
'@type': 'updateCommonBoxMessages',
ids: update.messages,
messageUpdate: {
hasUnreadMention: false,
isMediaUnread: false,
},
});
} else if (update instanceof GramJs.UpdateChannelReadMessagesContents) {
sendApiUpdate({
'@type': 'updateChannelMessages',
channelId: buildApiPeerId(update.channelId, 'channel'),
ids: update.messages,
messageUpdate: {
hasUnreadMention: false,
isMediaUnread: false,
},
});
} else if (update instanceof GramJs.UpdateMessagePoll) {
const { pollId, poll, results } = update;
const apiPoll = poll && buildPoll(poll);
const pollResults = buildPollResults(results);
sendApiUpdate({
'@type': 'updateMessagePoll',
pollId: pollId.toString(),
pollUpdate: omitUndefined({ summary: apiPoll, results: pollResults }),
});
} else if (update instanceof GramJs.UpdateMessagePollVote) {
sendApiUpdate({
'@type': 'updateMessagePollVote',
pollId: String(update.pollId),
peerId: getApiChatIdFromMtpPeer(update.peer),
options: update.options.map(serializeBytes),
});
} else if (update instanceof GramJs.UpdateChannelMessageViews) {
sendApiUpdate({
'@type': 'updateMessage',
chatId: buildApiPeerId(update.channelId, 'channel'),
id: update.id,
message: { viewsCount: update.views },
});
} else if (update instanceof GramJs.UpdateChannelMessageForwards) {
sendApiUpdate({
'@type': 'updateMessage',
chatId: buildApiPeerId(update.channelId, 'channel'),
id: update.id,
message: { forwardsCount: update.forwards },
});
// Chats
} else if (update instanceof GramJs.UpdateReadHistoryInbox) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: getApiChatIdFromMtpPeer(update.peer),
threadId: update.topMsgId || MAIN_THREAD_ID,
readState: {
lastReadInboxMessageId: update.maxId,
unreadCount: update.stillUnreadCount,
hasUnreadMark: undefined,
},
});
} else if (update instanceof GramJs.UpdateReadHistoryOutbox) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: getApiChatIdFromMtpPeer(update.peer),
threadId: MAIN_THREAD_ID,
readState: {
lastReadOutboxMessageId: update.maxId,
},
});
} else if (update instanceof GramJs.UpdateReadChannelInbox) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: buildApiPeerId(update.channelId, 'channel'),
threadId: MAIN_THREAD_ID,
readState: {
lastReadInboxMessageId: update.maxId,
unreadCount: update.stillUnreadCount,
hasUnreadMark: undefined,
},
});
} else if (update instanceof GramJs.UpdateReadChannelOutbox) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: buildApiPeerId(update.channelId, 'channel'),
threadId: MAIN_THREAD_ID,
readState: {
lastReadOutboxMessageId: update.maxId,
},
});
} else if (update instanceof GramJs.UpdateReadChannelDiscussionInbox) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: buildApiPeerId(update.channelId, 'channel'),
threadId: update.topMsgId,
readState: {
lastReadInboxMessageId: update.readMaxId,
},
});
} else if (update instanceof GramJs.UpdateReadChannelDiscussionOutbox) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: buildApiPeerId(update.channelId, 'channel'),
threadId: update.topMsgId,
readState: {
lastReadOutboxMessageId: update.readMaxId,
},
});
} else if (
update instanceof GramJs.UpdateDialogPinned
&& update.peer instanceof GramJs.DialogPeer
) {
sendApiUpdate({
'@type': 'updateChatPinned',
id: getApiChatIdFromMtpPeer(update.peer.peer),
isPinned: update.pinned || false,
});
} else if (update instanceof GramJs.UpdatePinnedDialogs) {
const ids = update.order?.filter(
(dp): dp is GramJs.DialogPeer => dp instanceof GramJs.DialogPeer)
.map((dp) => getApiChatIdFromMtpPeer(dp.peer));
sendApiUpdate({
'@type': 'updatePinnedChatIds',
ids,
folderId: update.folderId || undefined,
});
} else if (
update instanceof GramJs.UpdateSavedDialogPinned
&& update.peer instanceof GramJs.DialogPeer
) {
sendApiUpdate({
'@type': 'updateSavedDialogPinned',
id: getApiChatIdFromMtpPeer(update.peer.peer),
isPinned: update.pinned || false,
});
} else if (update instanceof GramJs.UpdatePinnedSavedDialogs) {
const ids = update.order
? update.order
.filter((dp): dp is GramJs.DialogPeer => dp instanceof GramJs.DialogPeer)
.map((dp) => getApiChatIdFromMtpPeer(dp.peer))
: [];
sendApiUpdate({
'@type': 'updatePinnedSavedDialogIds',
ids,
});
} else if (update instanceof GramJs.UpdateFolderPeers) {
update.folderPeers.forEach((folderPeer) => {
const { folderId, peer } = folderPeer;
sendApiUpdate({
'@type': 'updateChatListType',
id: getApiChatIdFromMtpPeer(peer),
folderId,
});
});
} else if (update instanceof GramJs.UpdateDialogFilter) {
const { id, filter } = update;
const folder = isChatFolder(filter) ? buildApiChatFolder(filter) : undefined;
sendApiUpdate({
'@type': 'updateChatFolder',
id,
folder,
});
} else if (update instanceof GramJs.UpdateDialogFilterOrder) {
sendApiUpdate({
'@type': 'updateChatFoldersOrder',
orderedIds: update.order,
});
} else if (update instanceof GramJs.UpdateChatParticipants) {
const replacedMembers = buildChatMembers(update.participants);
sendApiUpdate({
'@type': 'updateChatMembers',
id: buildApiPeerId(update.participants.chatId, 'chat'),
replacedMembers,
});
} else if (update instanceof GramJs.UpdateChatParticipantAdd) {
const addedMember = buildChatMember(
pick(update, ['userId', 'inviterId', 'date']) as GramJs.ChatParticipant,
);
sendApiUpdate({
'@type': 'updateChatMembers',
id: buildApiPeerId(update.chatId, 'chat'),
addedMember,
});
} else if (update instanceof GramJs.UpdateChatParticipantDelete) {
sendApiUpdate({
'@type': 'updateChatMembers',
id: buildApiPeerId(update.chatId, 'chat'),
deletedMemberId: buildApiPeerId(update.userId, 'user'),
});
} else if (update instanceof GramJs.UpdateChatParticipantRank) {
sendApiUpdate({
'@type': 'updateChatParticipantRank',
id: buildApiPeerId(update.chatId, 'chat'),
userId: buildApiPeerId(update.userId, 'user'),
rank: update.rank,
});
} else if (
update instanceof GramJs.UpdatePinnedMessages
|| update instanceof GramJs.UpdatePinnedChannelMessages
) {
const chatId = update instanceof GramJs.UpdatePinnedMessages
? getApiChatIdFromMtpPeer(update.peer)
: buildApiPeerId(update.channelId, 'channel');
sendApiUpdate({
'@type': 'updatePinnedIds',
chatId,
messageIds: update.messages,
isPinned: update.pinned,
});
} else if (
update instanceof GramJs.UpdateUserTyping
|| update instanceof GramJs.UpdateChatUserTyping
) {
const chatId = update instanceof GramJs.UpdateUserTyping
? buildApiPeerId(update.userId, 'user')
: buildApiPeerId(update.chatId, 'chat');
const peerId = update instanceof GramJs.UpdateUserTyping
? buildApiPeerId(update.userId, 'user')
: getApiChatIdFromMtpPeer(update.fromId);
const threadId = update instanceof GramJs.UpdateUserTyping ? update.topMsgId : undefined;
if (update.action instanceof GramJs.SendMessageEmojiInteraction) {
sendApiUpdate({
'@type': 'updateStartEmojiInteraction',
id: chatId,
emoji: update.action.emoticon,
messageId: update.action.msgId,
interaction: buildApiEmojiInteraction(JSON.parse(update.action.interaction.data)),
});
} else if (update.action instanceof GramJs.SendMessageTextDraftAction) {
sendApiUpdate({
'@type': 'updateChatTypingDraft',
chatId,
id: update.action.randomId.toString(),
threadId,
text: buildApiFormattedText(update.action.text),
});
} else {
sendApiUpdate({
'@type': 'updateChatTypingStatus',
id: chatId,
peerId,
threadId,
typingStatus: buildChatTypingStatus(update),
});
}
} else if (update instanceof GramJs.UpdateChannelUserTyping) {
const id = buildApiPeerId(update.channelId, 'channel');
const peerId = getApiChatIdFromMtpPeer(update.fromId);
sendApiUpdate({
'@type': 'updateChatTypingStatus',
id,
peerId,
threadId: update.topMsgId,
typingStatus: buildChatTypingStatus(update),
});
} else if (update instanceof GramJs.UpdateChannel) {
const { _entities } = update;
if (!_entities) {
return;
}
const channel = _entities.find((e): e is GramJs.Channel | GramJs.ChannelForbidden => (
e instanceof GramJs.Channel || e instanceof GramJs.ChannelForbidden
));
if (channel instanceof GramJs.Channel) {
const chat = buildApiChatFromPreview(channel);
if (chat) {
sendApiUpdate({
'@type': 'updateChat',
id: chat.id,
chat,
});
sendApiUpdate({
'@type': chat.isNotJoined ? 'updateChatLeave' : 'updateChatJoin',
id: buildApiPeerId(update.channelId, 'channel'),
});
}
} else if (channel instanceof GramJs.ChannelForbidden) {
const chatId = buildApiPeerId(update.channelId, 'channel');
sendApiUpdate({
'@type': 'updateChat',
id: chatId,
chat: {
isRestricted: true,
},
});
sendApiUpdate({
'@type': 'updateChatLeave',
id: chatId,
});
} else if (_entities.length === 0) {
// The link to the discussion group may have been changed.
// No corresponding update available at this moment https://core.telegram.org/type/Updates
sendApiUpdate({
'@type': 'resetMessages',
id: buildApiPeerId(update.channelId, 'channel'),
});
}
} else if (
update instanceof GramJs.UpdateDialogUnreadMark
&& update.peer instanceof GramJs.DialogPeer
) {
sendApiUpdate({
'@type': 'updateThreadReadState',
chatId: getApiChatIdFromMtpPeer(update.peer.peer),
threadId: MAIN_THREAD_ID,
readState: {
hasUnreadMark: update.unread,
},
});
} else if (update instanceof GramJs.UpdateChatDefaultBannedRights) {
sendApiUpdate({
'@type': 'updateChat',
id: getApiChatIdFromMtpPeer(update.peer),
chat: {
defaultBannedRights: omitVirtualClassFields(update.defaultBannedRights),
},
});
// Users
} else if (update instanceof GramJs.UpdateUserStatus) {
sendApiUpdate({
'@type': 'updateUserStatus',
userId: buildApiPeerId(update.userId, 'user'),
status: buildApiUserStatus(update.status),
});
} else if (update instanceof GramJs.UpdateUser) {
sendApiUpdate({
'@type': 'updateRequestUserUpdate',
id: buildApiPeerId(update.userId, 'user'),
});
} else if (update instanceof GramJs.UpdateUserEmojiStatus) {
const emojiStatus = buildApiEmojiStatus(update.emojiStatus);
sendApiUpdate({
'@type': 'updateUserEmojiStatus',
userId: buildApiPeerId(update.userId, 'user'),
emojiStatus,
});
} else if (update instanceof GramJs.UpdateUserName) {
const apiUserId = buildApiPeerId(update.userId, 'user');
const updatedUser = localDb.users[apiUserId];
const user = updatedUser?.mutualContact && !updatedUser.self
? pick(update, [])
: pick(update, ['firstName', 'lastName']);
const usernames = buildApiUsernames(update);
sendApiUpdate({
'@type': 'updateUser',
id: apiUserId,
user: {
...user,
usernames,
},
});
} else if (update instanceof GramJs.UpdateUserPhone) {
const { userId, phone } = update;
sendApiUpdate({
'@type': 'updateUser',
id: buildApiPeerId(userId, 'user'),
user: { phoneNumber: phone },
});
} else if (update instanceof GramJs.UpdatePeerSettings) {
const { peer, settings } = update;
const peerId = getApiChatIdFromMtpPeer(peer);
const apiSettings = buildApiPeerSettings(settings);
sendApiUpdate({
'@type': 'updatePeerSettings',
id: peerId,
settings: apiSettings,
});
} else if (update instanceof GramJs.UpdateNotifySettings) {
const {
notifySettings,
peer: notifyPeer,
} = update;
const className = notifyPeer.className;
const settings = buildApiPeerNotifySettings(notifySettings);
if (notifyPeer instanceof GramJs.NotifyPeer) {
const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer);
if (settings.mutedUntil) {
scheduleMutedChatUpdate(peerId, settings.mutedUntil, sendApiUpdate);
}
sendApiUpdate({
'@type': 'updateChatNotifySettings',
chatId: peerId,
settings,
});
return;
}
if (notifyPeer instanceof GramJs.NotifyForumTopic) {
const peerId = getApiChatIdFromMtpPeer(notifyPeer.peer);
if (settings.mutedUntil) {
scheduleMutedTopicUpdate(peerId, notifyPeer.topMsgId, settings.mutedUntil, sendApiUpdate);
}
sendApiUpdate({
'@type': 'updateTopicNotifySettings',
chatId: peerId,
topicId: notifyPeer.topMsgId,
settings,
});
return;
}
const peerType = className === 'NotifyUsers'
? 'users'
: (className === 'NotifyChats'
? 'groups'
: (className === 'NotifyBroadcasts'
? 'channels'
: undefined
)
);
if (!peerType) {
return;
}
sendApiUpdate({
'@type': 'updateDefaultNotifySettings',
peerType,
settings,
});
} else if (update instanceof GramJs.UpdatePeerBlocked) {
sendApiUpdate({
'@type': 'updatePeerBlocked',
id: getApiChatIdFromMtpPeer(update.peerId),
isBlocked: update.blocked,
isBlockedFromStories: update.blockedMyStoriesFrom,
});
} else if (update instanceof GramJs.UpdatePrivacy) {
const key = buildPrivacyKey(update.key);
if (key) {
sendApiUpdate({
'@type': 'updatePrivacy',
key,
rules: buildPrivacyRules(update.rules),
});
}
// Misc
} else if (update instanceof GramJs.UpdateDraftMessage) {
sendApiUpdate({
'@type': 'draftMessage',
chatId: getApiChatIdFromMtpPeer(update.peer),
threadId: update.topMsgId,
draft: buildMessageDraft(update.draft),
});
} else if (update instanceof GramJs.UpdateContactsReset) {
sendApiUpdate({ '@type': 'updateResetContactList' });
} else if (update instanceof GramJs.UpdateFavedStickers) {
sendApiUpdate({ '@type': 'updateFavoriteStickers' });
} else if (update instanceof GramJs.UpdateRecentStickers) {
sendApiUpdate({ '@type': 'updateRecentStickers' });
} else if (update instanceof GramJs.UpdateRecentReactions) {
sendApiUpdate({ '@type': 'updateRecentReactions' });
} else if (update instanceof GramJs.UpdateSavedReactionTags) {
sendApiUpdate({ '@type': 'updateSavedReactionTags' });
} else if (update instanceof GramJs.UpdateMoveStickerSetToTop) {
if (!update.masks) {
sendApiUpdate({
'@type': 'updateMoveStickerSetToTop',
isCustomEmoji: update.emojis,
id: update.stickerset.toString(),
});
}
} else if (update instanceof GramJs.UpdateStickerSets) {
sendApiUpdate({ '@type': 'updateStickerSets' });
} else if (update instanceof GramJs.UpdateStickerSetsOrder) {
if (!update.masks) {
sendApiUpdate({
'@type': 'updateStickerSetsOrder',
order: update.order.map((n) => n.toString()),
isCustomEmoji: update.emojis,
});
}
} else if (update instanceof GramJs.UpdateNewStickerSet) {
if (update.stickerset instanceof GramJs.messages.StickerSet) {
const stickerSet = buildStickerSet(update.stickerset.set);
sendApiUpdate({
'@type': 'updateStickerSet',
id: stickerSet.id,
stickerSet,
});
}
} else if (update instanceof GramJs.UpdateSavedGifs) {
sendApiUpdate({ '@type': 'updateSavedGifs' });
} else if (update instanceof GramJs.UpdateGroupCall) {
sendApiUpdate({
'@type': 'updateGroupCall',
call: buildApiGroupCall(update.call),
});
} else if (update instanceof GramJs.UpdateGroupCallConnection) {
sendApiUpdate({
'@type': 'updateGroupCallConnection',
data: JSON.parse(update.params.data) as GroupCallConnectionData,
presentation: Boolean(update.presentation),
});
} else if (update instanceof GramJs.UpdateGroupCallParticipants) {
const groupCallId = getGroupCallId(update.call);
if (groupCallId) {
sendApiUpdate({
'@type': 'updateGroupCallParticipants',
groupCallId,
participants: update.participants.map(buildApiGroupCallParticipant),
});
}
} else if (update instanceof GramJs.UpdatePendingJoinRequests) {
sendApiUpdate({
'@type': 'updatePendingJoinRequests',
chatId: getApiChatIdFromMtpPeer(update.peer),
recentRequesterIds: update.recentRequesters.map((id) => buildApiPeerId(id, 'user')),
requestsPending: update.requestsPending,
});
} else if (update instanceof GramJs.UpdatePhoneCall) {
sendApiUpdate({
'@type': 'updatePhoneCall',
call: buildPhoneCall(update.phoneCall),
});
} else if (update instanceof GramJs.UpdatePhoneCallSignalingData) {
sendApiUpdate({
'@type': 'updatePhoneCallSignalingData',
callId: update.phoneCallId.toString(),
data: Array.from(update.data),
});
} else if (update instanceof GramJs.UpdateWebViewResultSent) {
const { queryId } = update;
sendApiUpdate({
'@type': 'updateWebViewResultSent',
queryId: queryId.toString(),
});
} else if (update instanceof GramJs.UpdateWebPage || update instanceof GramJs.UpdateChannelWebPage) {
const webPage = buildWebPage(update.webpage);
if (webPage) {
sendApiUpdate({
'@type': 'updateWebPage',
webPage,
});
}
} else if (update instanceof GramJs.UpdateBotMenuButton) {
const {
botId,
button,
} = update;
const id = buildApiPeerId(botId, 'user');
sendApiUpdate({
'@type': 'updateBotMenuButton',
botId: id,
button: buildApiBotMenuButton(button),
});
} else if (update instanceof GramJs.UpdateBotCommands) {
const {
botId,
commands,
} = update;
const id = buildApiPeerId(botId, 'user');
const commandsArray = commands.map((command) => buildApiBotCommand(id, command));
sendApiUpdate({
'@type': 'updateBotCommands',
botId: id,
commands: commandsArray.length ? commandsArray : undefined,
});
} else if (update instanceof GramJs.UpdateTranscribedAudio) {
sendApiUpdate({
'@type': 'updateTranscribedAudio',
transcriptionId: update.transcriptionId.toString(),
text: update.text,
isPending: update.pending,
});
} else if (update instanceof GramJs.UpdateConfig) {
sendApiUpdate({ '@type': 'updateConfig' });
} else if (update instanceof GramJs.UpdatePinnedForumTopic) {
sendApiUpdate({
'@type': 'updatePinnedTopic',
chatId: getApiChatIdFromMtpPeer(update.peer),
topicId: update.topicId,
isPinned: Boolean(update.pinned),
});
} else if (update instanceof GramJs.UpdatePinnedForumTopics) {
sendApiUpdate({
'@type': 'updatePinnedTopicsOrder',
chatId: getApiChatIdFromMtpPeer(update.peer),
order: update.order || [],
});
} else if (update instanceof GramJs.UpdateRecentEmojiStatuses) {
sendApiUpdate({ '@type': 'updateRecentEmojiStatuses' });
} else if (update instanceof GramJs.UpdateStory) {
const { story } = update;
const peerId = getApiChatIdFromMtpPeer(update.peer);
const apiStory = buildApiStory(peerId, story) as ApiStory | ApiStorySkipped;
addStoryToLocalDb(story, peerId); // Add after building to prevent repair info overwrite
if (story instanceof GramJs.StoryItemDeleted) {
sendApiUpdate({
'@type': 'deleteStory',
peerId,
storyId: story.id,
});
} else {
sendApiUpdate({
'@type': 'updateStory',
peerId,
story: apiStory,
});
}
} else if (update instanceof GramJs.UpdateReadStories) {
sendApiUpdate({
'@type': 'updateReadStories',
peerId: getApiChatIdFromMtpPeer(update.peer),
lastReadId: update.maxId,
});
} else if (update instanceof GramJs.UpdateSentStoryReaction) {
const reaction = buildApiReaction(update.reaction);
sendApiUpdate({
'@type': 'updateSentStoryReaction',
peerId: getApiChatIdFromMtpPeer(update.peer),
storyId: update.storyId,
reaction,
});
} else if (update instanceof GramJs.UpdateStoriesStealthMode) {
sendApiUpdate({
'@type': 'updateStealthMode',
stealthMode: buildApiStealthMode(update.stealthMode),
});
} else if (update instanceof GramJs.UpdateAttachMenuBots) {
sendApiUpdate({
'@type': 'updateAttachMenuBots',
});
} else if (update instanceof GramJs.UpdateNewAuthorization) {
sendApiUpdate({
'@type': 'updateNewAuthorization',
hash: update.hash.toString(),
date: update.date,
device: update.device,
location: update.location,
isUnconfirmed: update.unconfirmed,
});
} else if (update instanceof GramJs.UpdateChannelViewForumAsMessages) {
sendApiUpdate({
'@type': 'updateViewForumAsMessages',
chatId: buildApiPeerId(update.channelId, 'channel'),
isEnabled: update.enabled ? true : undefined,
});
} else if (update instanceof GramJs.UpdateStarsBalance) {
const balance = buildApiCurrencyAmount(update.balance);
if (!balance) {
return;
}
sendApiUpdate({
'@type': 'updateStarsBalance',
balance,
});
} else if (update instanceof GramJs.UpdateStarGiftAuctionState) {
const state = buildApiTypeStarGiftAuctionState(update.state);
if (!state) {
return;
}
sendApiUpdate({
'@type': 'updateStarGiftAuctionState',
giftId: update.giftId.toString(),
state,
});
} else if (update instanceof GramJs.UpdateStarGiftAuctionUserState) {
sendApiUpdate({
'@type': 'updateStarGiftAuctionUserState',
giftId: update.giftId.toString(),
userState: buildApiStarGiftAuctionUserState(update.userState),
});
} else if (update instanceof GramJs.UpdateStarGiftCraftFail) {
sendApiUpdate({
'@type': 'updateStarGiftCraftFail',
});
} else if (update instanceof GramJs.UpdatePaidReactionPrivacy) {
sendApiUpdate({
'@type': 'updatePaidReactionPrivacy',
private: buildApiPaidReactionPrivacy(update.private),
});
} else if (update instanceof GramJs.UpdateLangPackTooLong) {
sendApiUpdate({
'@type': 'updateLangPackTooLong',
langCode: update.langCode,
});
} else if (update instanceof GramJs.UpdateLangPack) {
const { strings, keysToRemove } = buildLangStrings(update.difference.strings);
sendApiUpdate({
'@type': 'updateLangPack',
version: update.difference.version,
strings,
keysToRemove,
});
} else if (update instanceof LocalUpdatePremiumFloodWait) { // Local updates
sendApiUpdate({
'@type': 'updatePremiumFloodWait',
isUpload: update.isUpload,
});
} else if (update instanceof LocalUpdatePts || update instanceof LocalUpdateChannelPts) {
// Do nothing, handled on the manager side
} else if (update instanceof GramJs.UpdateMessageID || update instanceof GramJs.UpdateShortSentMessage) {
// Do nothing, handled when sending the message
} else if (DEBUG) {
const params = typeof update === 'object' && 'className' in update ? update.className : update;
log('UNEXPECTED UPDATE', params);
}
}