TelegramPWA/src/api/gramjs/updater.ts

910 lines
28 KiB
TypeScript

import { GroupCallConnectionData } from '../../lib/secret-sauce';
import { Api as GramJs, connection } from '../../lib/gramjs';
import { ApiMessage, ApiUpdateConnectionStateType, OnApiUpdate } from '../types';
import { pick } from '../../util/iteratees';
import {
buildApiMessage,
buildApiMessageFromShort,
buildApiMessageFromShortChat,
buildMessageMediaContent,
buildMessageTextContent,
buildPoll,
buildPollResults,
buildApiMessageFromNotification,
buildMessageDraft,
buildMessageReactions,
} from './apiBuilders/messages';
import {
buildChatMember,
buildChatMembers,
buildChatTypingStatus,
buildAvatarHash,
buildApiChatFromPreview,
buildApiChatFolder,
buildApiChatSettings,
} from './apiBuilders/chats';
import { buildApiUser, buildApiUserStatus } from './apiBuilders/users';
import {
buildMessageFromUpdate,
isMessageWithMedia,
buildChatPhotoForLocalDb,
} from './gramjsBuilders';
import localDb from './localDb';
import { omitVirtualClassFields } from './apiBuilders/helpers';
import { DEBUG } from '../../config';
import {
addMessageToLocalDb,
addEntitiesWithPhotosToLocalDb,
addPhotoToLocalDb,
resolveMessageApiChatId,
serializeBytes,
} from './helpers';
import { buildApiNotifyException, buildPrivacyKey, buildPrivacyRules } from './apiBuilders/misc';
import { buildApiPhoto } from './apiBuilders/common';
import {
buildApiGroupCall,
buildApiGroupCallParticipant,
getGroupCallId,
} from './apiBuilders/calls';
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './apiBuilders/peers';
import { buildApiEmojiInteraction } from './apiBuilders/symbols';
type Update = (
(GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }
) | typeof connection.UpdateConnectionState;
const DELETE_MISSING_CHANNEL_MESSAGE_DELAY = 1000;
let onUpdate: OnApiUpdate;
export function init(_onUpdate: OnApiUpdate) {
onUpdate = _onUpdate;
}
const sentMessageIds = new Set();
let serverTimeOffset = 0;
// Workaround for a situation when an incorrect update comes with an undefined property `adminRights`
let shouldIgnoreNextChannelUpdate = false;
const IGNORE_NEXT_CHANNEL_UPDATE_TIMEOUT = 2000;
function dispatchUserAndChatUpdates(entities: (GramJs.TypeUser | GramJs.TypeChat)[]) {
entities
.filter((e) => e instanceof GramJs.User)
.map(buildApiUser)
.forEach((user) => {
if (!user) {
return;
}
onUpdate({
'@type': 'updateUser',
id: user.id,
user,
});
});
entities
.filter((e) => e instanceof GramJs.Chat || e instanceof GramJs.Channel)
.map((e) => buildApiChatFromPreview(e))
.forEach((chat) => {
if (!chat) {
return;
}
onUpdate({
'@type': 'updateChat',
id: chat.id,
chat,
});
});
}
export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
if (update instanceof connection.UpdateServerTimeOffset) {
serverTimeOffset = update.timeOffset;
} else if (update instanceof connection.UpdateConnectionState) {
let connectionState: ApiUpdateConnectionStateType;
switch (update.state) {
case connection.UpdateConnectionState.disconnected:
connectionState = 'connectionStateConnecting';
break;
case connection.UpdateConnectionState.broken:
connectionState = 'connectionStateBroken';
break;
case connection.UpdateConnectionState.connected:
default:
connectionState = 'connectionStateReady';
break;
}
onUpdate({
'@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 shouldForceReply: boolean | undefined;
if (update instanceof GramJs.UpdateShortChatMessage) {
message = buildApiMessageFromShortChat(update);
} else if (update instanceof GramJs.UpdateShortMessage) {
message = buildApiMessageFromShort(update);
} else {
// TODO Remove if proven not reproducing
if (update.message instanceof GramJs.MessageEmpty) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('Unexpected update:', update.className, update);
}
return;
}
if (update.message instanceof GramJs.Message && isMessageWithMedia(update.message)) {
addMessageToLocalDb(update.message);
}
message = buildApiMessage(update.message)!;
shouldForceReply = 'replyMarkup' in update.message
&& update.message?.replyMarkup instanceof GramJs.ReplyKeyboardForceReply
&& (!update.message.replyMarkup.selective || message.isMentioned);
}
// eslint-disable-next-line no-underscore-dangle
const entities = update._entities;
if (entities) {
addEntitiesWithPhotosToLocalDb(entities);
dispatchUserAndChatUpdates(entities);
}
if (update instanceof GramJs.UpdateNewScheduledMessage) {
onUpdate({
'@type': sentMessageIds.has(message.id) ? 'updateScheduledMessage' : 'newScheduledMessage',
id: message.id,
chatId: message.chatId,
message,
});
} else {
onUpdate({
'@type': sentMessageIds.has(message.id) ? 'updateMessage' : 'newMessage',
id: message.id,
chatId: message.chatId,
message,
shouldForceReply,
});
}
// 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.MessageActionPaymentSent) {
onUpdate({
'@type': 'updatePaymentStateCompleted',
});
} else if (action instanceof GramJs.MessageActionChatEditTitle) {
onUpdate({
'@type': 'updateChat',
id: message.chatId,
chat: {
title: action.title,
},
});
} else if (action instanceof GramJs.MessageActionChatEditPhoto) {
const photo = buildChatPhotoForLocalDb(action.photo);
const avatarHash = buildAvatarHash(photo);
const localDbChatId = resolveMessageApiChatId(update.message)!;
if (localDb.chats[localDbChatId]) {
localDb.chats[localDbChatId].photo = photo;
}
addPhotoToLocalDb(action.photo);
if (avatarHash) {
onUpdate({
'@type': 'updateChat',
id: message.chatId,
chat: {
avatarHash,
},
...(action.photo instanceof GramJs.Photo && { newProfilePhoto: buildApiPhoto(action.photo) }),
});
}
} else if (action instanceof GramJs.MessageActionChatDeletePhoto) {
const localDbChatId = resolveMessageApiChatId(update.message)!;
if (localDb.chats[localDbChatId]) {
localDb.chats[localDbChatId].photo = new GramJs.ChatPhotoEmpty();
}
onUpdate({
'@type': 'updateChat',
id: message.chatId,
chat: { avatarHash: undefined },
});
} else if (action instanceof GramJs.MessageActionChatDeleteUser) {
// eslint-disable-next-line no-underscore-dangle
if (update._entities && update._entities.some((e): e is GramJs.User => (
e instanceof GramJs.User && Boolean(e.self) && e.id === action.userId
))) {
onUpdate({
'@type': 'updateChat',
id: message.chatId,
chat: {
isRestricted: true,
},
});
onUpdate({
'@type': 'updateChatLeave',
id: message.chatId,
});
}
} else if (action instanceof GramJs.MessageActionChatAddUser) {
// eslint-disable-next-line no-underscore-dangle
if (update._entities && update._entities.some((e): e is GramJs.User => (
e instanceof GramJs.User && Boolean(e.self) && action.users.includes(e.id)
))) {
onUpdate({
'@type': 'updateChatJoin',
id: message.chatId,
});
}
} else if (action instanceof GramJs.MessageActionGroupCall) {
if (!action.duration && action.call) {
onUpdate({
'@type': 'updateGroupCallChatId',
chatId: message.chatId,
call: {
id: action.call.id.toString(),
accessHash: action.call.accessHash.toString(),
},
});
}
}
}
} else if (
update instanceof GramJs.UpdateEditMessage
|| update instanceof GramJs.UpdateEditChannelMessage
) {
// TODO Remove if proven not reproducing
if (update.message instanceof GramJs.MessageEmpty) {
if (DEBUG) {
// eslint-disable-next-line no-console
console.error('Unexpected update:', update.className, update);
}
return;
}
if (update.message instanceof GramJs.Message && isMessageWithMedia(update.message)) {
addMessageToLocalDb(update.message);
}
const message = buildApiMessage(update.message)!;
onUpdate({
'@type': 'updateMessage',
id: message.id,
chatId: message.chatId,
message,
});
} else if (update instanceof GramJs.UpdateMessageReactions) {
onUpdate({
'@type': 'updateMessageReactions',
id: update.msgId,
chatId: getApiChatIdFromMtpPeer(update.peer),
reactions: buildMessageReactions(update.reactions),
});
} else if (update instanceof GramJs.UpdateDeleteMessages) {
onUpdate({
'@type': 'deleteMessages',
ids: update.messages,
});
} else if (update instanceof GramJs.UpdateDeleteScheduledMessages) {
onUpdate({
'@type': 'deleteScheduledMessages',
ids: update.messages,
chatId: getApiChatIdFromMtpPeer(update.peer),
});
} else if (update instanceof GramJs.UpdateDeleteChannelMessages) {
const chatId = buildApiPeerId(update.channelId, 'channel');
const ids = update.messages;
const existingIds = ids.filter((id) => localDb.messages[`${chatId}-${id}`]);
const missingIds = ids.filter((id) => !localDb.messages[`${chatId}-${id}`]);
const profilePhotoIds = ids.map((id) => {
const message = localDb.messages[`${chatId}-${id}`];
return message && message instanceof GramJs.MessageService && 'photo' in message.action
? String(message.action.photo.id)
: undefined;
}).filter<string>(Boolean as any);
if (existingIds.length) {
onUpdate({
'@type': 'deleteMessages',
ids: existingIds,
chatId,
});
}
if (profilePhotoIds.length) {
onUpdate({
'@type': 'deleteProfilePhotos',
ids: profilePhotoIds,
chatId,
});
}
// For some reason delete message update sometimes comes before new message update
if (missingIds.length) {
setTimeout(() => {
onUpdate({
'@type': 'deleteMessages',
ids: missingIds,
chatId,
});
}, DELETE_MISSING_CHANNEL_MESSAGE_DELAY);
}
} else if (update instanceof GramJs.UpdateServiceNotification) {
if (update.popup) {
onUpdate({
'@type': 'error',
error: {
message: update.message,
},
});
} else {
const currentDate = Date.now() / 1000 + serverTimeOffset;
const message = buildApiMessageFromNotification(update, currentDate);
if (isMessageWithMedia(update)) {
addMessageToLocalDb(buildMessageFromUpdate(message.id, message.chatId, update));
}
onUpdate({
'@type': 'updateServiceNotification',
message,
});
}
} else if ((
originRequest instanceof GramJs.messages.SendMessage
|| originRequest instanceof GramJs.messages.SendMedia
|| originRequest instanceof GramJs.messages.SendMultiMedia
|| originRequest instanceof GramJs.messages.ForwardMessages
) && (
update instanceof GramJs.UpdateMessageID
|| update instanceof GramJs.UpdateShortSentMessage
)) {
let randomId;
if ('randomId' in update) {
randomId = update.randomId;
} else if ('randomId' in originRequest) {
randomId = originRequest.randomId;
}
const localMessage = randomId && localDb.localMessages[String(randomId)];
if (!localMessage) {
throw new Error('Local message not found');
}
let newContent: ApiMessage['content'] | undefined;
if (update instanceof GramJs.UpdateShortSentMessage) {
if (localMessage.content.text && update.entities) {
newContent = {
text: buildMessageTextContent(localMessage.content.text.text, update.entities),
};
}
if (update.media) {
newContent = {
...newContent,
...buildMessageMediaContent(update.media),
};
}
const mtpMessage = buildMessageFromUpdate(update.id, localMessage.chatId, update);
if (isMessageWithMedia(mtpMessage)) {
addMessageToLocalDb(mtpMessage);
}
}
sentMessageIds.add(update.id);
// Edge case for "Send When Online"
const isAlreadySent = 'date' in update && update.date * 1000 < Date.now() + serverTimeOffset * 1000;
onUpdate({
'@type': localMessage.isScheduled && !isAlreadySent
? 'updateScheduledMessageSendSucceeded'
: 'updateMessageSendSucceeded',
chatId: localMessage.chatId,
localId: localMessage.id,
message: {
...localMessage,
...(newContent && {
content: {
...localMessage.content,
...newContent,
},
}),
id: update.id,
sendingState: undefined,
...('date' in update && { date: update.date }),
},
});
} else if (update instanceof GramJs.UpdateReadMessagesContents) {
onUpdate({
'@type': 'updateCommonBoxMessages',
ids: update.messages,
messageUpdate: {
hasUnreadMention: false,
isMediaUnread: false,
},
});
} else if (update instanceof GramJs.UpdateChannelReadMessagesContents) {
onUpdate({
'@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;
if (poll) {
const apiPoll = buildPoll(poll, results);
onUpdate({
'@type': 'updateMessagePoll',
pollId: String(pollId),
pollUpdate: apiPoll,
});
} else {
const pollResults = buildPollResults(results);
onUpdate({
'@type': 'updateMessagePoll',
pollId: String(pollId),
pollUpdate: { results: pollResults },
});
}
} else if (update instanceof GramJs.UpdateMessagePollVote) {
onUpdate({
'@type': 'updateMessagePollVote',
pollId: String(update.pollId),
userId: buildApiPeerId(update.userId, 'user'),
options: update.options.map(serializeBytes),
});
} else if (update instanceof GramJs.UpdateChannelMessageViews) {
onUpdate({
'@type': 'updateMessage',
chatId: buildApiPeerId(update.channelId, 'channel'),
id: update.id,
message: { views: update.views },
});
// Chats
} else if (update instanceof GramJs.UpdateReadHistoryInbox) {
onUpdate({
'@type': 'updateChatInbox',
id: getApiChatIdFromMtpPeer(update.peer),
chat: {
lastReadInboxMessageId: update.maxId,
unreadCount: update.stillUnreadCount,
},
});
} else if (update instanceof GramJs.UpdateReadHistoryOutbox) {
onUpdate({
'@type': 'updateChat',
id: getApiChatIdFromMtpPeer(update.peer),
chat: {
lastReadOutboxMessageId: update.maxId,
},
});
} else if (update instanceof GramJs.UpdateReadChannelInbox) {
onUpdate({
'@type': 'updateChat',
id: buildApiPeerId(update.channelId, 'channel'),
chat: {
lastReadInboxMessageId: update.maxId,
unreadCount: update.stillUnreadCount,
},
});
} else if (update instanceof GramJs.UpdateReadChannelOutbox) {
onUpdate({
'@type': 'updateChat',
id: buildApiPeerId(update.channelId, 'channel'),
chat: {
lastReadOutboxMessageId: update.maxId,
},
});
} else if (
update instanceof GramJs.UpdateDialogPinned
&& update.peer instanceof GramJs.DialogPeer
) {
onUpdate({
'@type': 'updateChatPinned',
id: getApiChatIdFromMtpPeer(update.peer.peer),
isPinned: update.pinned || false,
});
} else if (update instanceof GramJs.UpdatePinnedDialogs) {
const ids = update.order
? update.order
.filter((dp): dp is GramJs.DialogPeer => dp instanceof GramJs.DialogPeer)
.map((dp) => getApiChatIdFromMtpPeer(dp.peer))
: [];
onUpdate({
'@type': 'updatePinnedChatIds',
ids,
folderId: update.folderId || undefined,
});
} else if (update instanceof GramJs.UpdateFolderPeers) {
update.folderPeers.forEach((folderPeer) => {
const { folderId, peer } = folderPeer;
onUpdate({
'@type': 'updateChatListType',
id: getApiChatIdFromMtpPeer(peer),
folderId,
});
});
} else if (update instanceof GramJs.UpdateDialogFilter) {
const { id, filter } = update;
const folder = filter ? buildApiChatFolder(filter) : undefined;
onUpdate({
'@type': 'updateChatFolder',
id,
folder,
});
} else if (update instanceof GramJs.UpdateDialogFilterOrder) {
onUpdate({
'@type': 'updateChatFoldersOrder',
orderedIds: update.order,
});
} else if (update instanceof GramJs.UpdateChatParticipants) {
const replacedMembers = buildChatMembers(update.participants);
onUpdate({
'@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,
);
onUpdate({
'@type': 'updateChatMembers',
id: buildApiPeerId(update.chatId, 'chat'),
addedMember,
});
} else if (update instanceof GramJs.UpdateChatParticipantDelete) {
onUpdate({
'@type': 'updateChatMembers',
id: buildApiPeerId(update.chatId, 'chat'),
deletedMemberId: buildApiPeerId(update.userId, 'user'),
});
} else if (
update instanceof GramJs.UpdatePinnedMessages
|| update instanceof GramJs.UpdatePinnedChannelMessages
) {
const chatId = update instanceof GramJs.UpdatePinnedMessages
? getApiChatIdFromMtpPeer(update.peer)
: buildApiPeerId(update.channelId, 'channel');
onUpdate({
'@type': 'updatePinnedIds',
chatId,
messageIds: update.messages,
isPinned: update.pinned,
});
} else if (
update instanceof GramJs.UpdateNotifySettings
&& update.peer instanceof GramJs.NotifyPeer
) {
onUpdate({
'@type': 'updateNotifyExceptions',
...buildApiNotifyException(update.notifySettings, update.peer.peer, serverTimeOffset),
});
} else if (
update instanceof GramJs.UpdateUserTyping
|| update instanceof GramJs.UpdateChatUserTyping
) {
const id = update instanceof GramJs.UpdateUserTyping
? buildApiPeerId(update.userId, 'user')
: buildApiPeerId(update.chatId, 'chat');
if (update.action instanceof GramJs.SendMessageEmojiInteraction) {
onUpdate({
'@type': 'updateStartEmojiInteraction',
id,
emoji: update.action.emoticon,
messageId: update.action.msgId,
interaction: buildApiEmojiInteraction(JSON.parse(update.action.interaction.data)),
});
} else {
onUpdate({
'@type': 'updateChatTypingStatus',
id,
typingStatus: buildChatTypingStatus(update, serverTimeOffset),
});
}
} else if (update instanceof GramJs.UpdateChannelUserTyping) {
const id = buildApiPeerId(update.channelId, 'channel');
onUpdate({
'@type': 'updateChatTypingStatus',
id,
typingStatus: buildChatTypingStatus(update, serverTimeOffset),
});
} else if (update instanceof GramJs.UpdateChannel) {
// eslint-disable-next-line @typescript-eslint/naming-convention
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) {
if (shouldIgnoreNextChannelUpdate) {
shouldIgnoreNextChannelUpdate = false;
return;
}
if (originRequest instanceof GramJs.messages.ToggleNoForwards) {
shouldIgnoreNextChannelUpdate = true;
setTimeout(() => { shouldIgnoreNextChannelUpdate = false; }, IGNORE_NEXT_CHANNEL_UPDATE_TIMEOUT);
}
const chat = buildApiChatFromPreview(channel);
if (chat) {
onUpdate({
'@type': 'updateChat',
id: chat.id,
chat,
});
onUpdate({
'@type': chat.isNotJoined ? 'updateChatLeave' : 'updateChatJoin',
id: buildApiPeerId(update.channelId, 'channel'),
});
}
} else if (channel instanceof GramJs.ChannelForbidden) {
const chatId = buildApiPeerId(update.channelId, 'channel');
onUpdate({
'@type': 'updateChat',
id: chatId,
chat: {
isRestricted: true,
},
});
onUpdate({
'@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
onUpdate({
'@type': 'resetMessages',
id: buildApiPeerId(update.channelId, 'channel'),
});
}
} else if (
update instanceof GramJs.UpdateDialogUnreadMark
&& update.peer instanceof GramJs.DialogPeer
) {
onUpdate({
'@type': 'updateChat',
id: getApiChatIdFromMtpPeer(update.peer.peer),
chat: {
hasUnreadMark: update.unread,
},
});
} else if (update instanceof GramJs.UpdateChatDefaultBannedRights) {
onUpdate({
'@type': 'updateChat',
id: getApiChatIdFromMtpPeer(update.peer),
chat: {
defaultBannedRights: omitVirtualClassFields(update.defaultBannedRights),
},
});
// Users
} else if (update instanceof GramJs.UpdateUserStatus) {
onUpdate({
'@type': 'updateUserStatus',
userId: buildApiPeerId(update.userId, 'user'),
status: buildApiUserStatus(update.status),
});
} 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, ['username'])
: pick(update, ['firstName', 'lastName', 'username']);
onUpdate({
'@type': 'updateUser',
id: apiUserId,
user,
});
} else if (update instanceof GramJs.UpdateUserPhoto) {
const { userId, photo } = update;
const apiUserId = buildApiPeerId(userId, 'user');
const avatarHash = buildAvatarHash(photo);
if (localDb.users[apiUserId]) {
localDb.users[apiUserId].photo = photo;
}
onUpdate({
'@type': 'updateUser',
id: apiUserId,
user: { avatarHash },
});
} else if (update instanceof GramJs.UpdateUserPhone) {
const { userId, phone } = update;
onUpdate({
'@type': 'updateUser',
id: buildApiPeerId(userId, 'user'),
user: { phoneNumber: phone },
});
} else if (update instanceof GramJs.UpdatePeerSettings) {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { _entities, settings } = update;
if (!_entities) {
return;
}
if (_entities?.length) {
_entities
.filter((e) => e instanceof GramJs.User && !e.contact)
.forEach((user) => {
onUpdate({
'@type': 'deleteContact',
id: buildApiPeerId(user.id, 'user'),
});
});
_entities
.filter((e) => e instanceof GramJs.User && e.contact)
.map(buildApiUser)
.forEach((user) => {
if (!user) {
return;
}
onUpdate({
'@type': 'updateUser',
id: user.id,
user: {
...user,
...(settings && { settings: buildApiChatSettings(settings) }),
},
});
});
}
// Settings
} else if (update instanceof GramJs.UpdateNotifySettings) {
const {
notifySettings: {
showPreviews, silent, muteUntil,
},
peer: { className },
} = update;
const peerType = className === 'NotifyUsers'
? 'contact'
: (className === 'NotifyChats'
? 'group'
: (className === 'NotifyBroadcasts'
? 'broadcast'
: undefined
)
);
if (!peerType) {
return;
}
onUpdate({
'@type': 'updateNotifySettings',
peerType,
isSilent: Boolean(silent
|| (typeof muteUntil === 'number' && Date.now() + serverTimeOffset * 1000 < muteUntil * 1000)),
shouldShowPreviews: Boolean(showPreviews),
});
} else if (update instanceof GramJs.UpdatePeerBlocked) {
onUpdate({
'@type': 'updatePeerBlocked',
id: getApiChatIdFromMtpPeer(update.peerId),
isBlocked: update.blocked,
});
} else if (update instanceof GramJs.UpdatePrivacy) {
const key = buildPrivacyKey(update.key);
if (key) {
onUpdate({
'@type': 'updatePrivacy',
key,
rules: buildPrivacyRules(update.rules),
});
}
// Misc
} else if (update instanceof GramJs.UpdateDraftMessage) {
onUpdate({
'@type': 'draftMessage',
chatId: getApiChatIdFromMtpPeer(update.peer),
...buildMessageDraft(update.draft),
});
} else if (update instanceof GramJs.UpdateContactsReset) {
onUpdate({ '@type': 'updateResetContactList' });
} else if (update instanceof GramJs.UpdateFavedStickers) {
onUpdate({ '@type': 'updateFavoriteStickers' });
} else if (update instanceof GramJs.UpdateGroupCall) {
onUpdate({
'@type': 'updateGroupCall',
call: buildApiGroupCall(update.call),
});
} else if (update instanceof GramJs.UpdateGroupCallConnection) {
onUpdate({
'@type': 'updateGroupCallConnection',
data: JSON.parse(update.params.data) as GroupCallConnectionData,
presentation: Boolean(update.presentation),
});
} else if (update instanceof GramJs.UpdateGroupCallParticipants) {
// eslint-disable-next-line no-underscore-dangle
const entities = update._entities;
if (entities) {
addEntitiesWithPhotosToLocalDb(entities);
dispatchUserAndChatUpdates(entities);
}
onUpdate({
'@type': 'updateGroupCallParticipants',
groupCallId: getGroupCallId(update.call),
participants: update.participants.map(buildApiGroupCallParticipant),
});
} else if (update instanceof GramJs.UpdatePendingJoinRequests) {
// eslint-disable-next-line no-underscore-dangle
const entities = update._entities;
if (entities) {
addEntitiesWithPhotosToLocalDb(entities);
dispatchUserAndChatUpdates(entities);
}
onUpdate({
'@type': 'updatePendingJoinRequests',
chatId: getApiChatIdFromMtpPeer(update.peer),
recentRequesterIds: update.recentRequesters.map((id) => buildApiPeerId(id, 'user')),
requestsPending: update.requestsPending,
});
} else if (DEBUG) {
const params = typeof update === 'object' && 'className' in update ? update.className : update;
// eslint-disable-next-line no-console
console.warn('[GramJs/updater] Unexpected update:', params);
}
}