1099 lines
36 KiB
TypeScript
1099 lines
36 KiB
TypeScript
import { Api as GramJs, type Update } from '../../../lib/gramjs';
|
|
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../../lib/gramjs/network';
|
|
|
|
import type { GroupCallConnectionData } from '../../../lib/secret-sauce';
|
|
import type {
|
|
ApiMessage, ApiPoll, ApiStory, ApiStorySkipped,
|
|
ApiUpdateConnectionStateType,
|
|
} from '../../types';
|
|
|
|
import { DEBUG, GENERAL_TOPIC_ID } from '../../../config';
|
|
import {
|
|
omit, pick,
|
|
} from '../../../util/iteratees';
|
|
import { getServerTimeOffset, setServerTimeOffset } from '../../../util/serverTime';
|
|
import { buildApiBotMenuButton } from '../apiBuilders/bots';
|
|
import {
|
|
buildApiGroupCall,
|
|
buildApiGroupCallParticipant,
|
|
buildPhoneCall,
|
|
getGroupCallId,
|
|
} from '../apiBuilders/calls';
|
|
import {
|
|
buildApiChatFolder,
|
|
buildApiChatFromPreview,
|
|
buildApiChatSettings,
|
|
buildChatMember,
|
|
buildChatMembers,
|
|
buildChatTypingStatus,
|
|
} from '../apiBuilders/chats';
|
|
import {
|
|
buildApiPhoto, buildApiUsernames, buildPrivacyRules,
|
|
} from '../apiBuilders/common';
|
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
|
import {
|
|
buildApiMessageExtendedMediaPreview,
|
|
buildBoughtMediaContent,
|
|
buildPoll,
|
|
buildPollFromMedia,
|
|
buildPollResults,
|
|
} from '../apiBuilders/messageContent';
|
|
import {
|
|
buildApiMessage,
|
|
buildApiMessageFromNotification,
|
|
buildApiMessageFromShort,
|
|
buildApiMessageFromShortChat,
|
|
buildApiQuickReply,
|
|
buildMessageDraft,
|
|
} from '../apiBuilders/messages';
|
|
import {
|
|
buildApiNotifyException,
|
|
buildApiNotifyExceptionTopic,
|
|
buildLangStrings,
|
|
buildPrivacyKey,
|
|
} from '../apiBuilders/misc';
|
|
import { buildApiStarsAmount } from '../apiBuilders/payments';
|
|
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
|
import {
|
|
buildApiReaction,
|
|
buildMessageReactions,
|
|
} from '../apiBuilders/reactions';
|
|
import { buildApiStealthMode, buildApiStory } from '../apiBuilders/stories';
|
|
import { buildApiEmojiInteraction, buildStickerSet } from '../apiBuilders/symbols';
|
|
import {
|
|
buildApiUser,
|
|
buildApiUserStatus,
|
|
} from '../apiBuilders/users';
|
|
import {
|
|
buildChatPhotoForLocalDb,
|
|
buildMessageFromUpdate,
|
|
} from '../gramjsBuilders';
|
|
import {
|
|
addPhotoToLocalDb,
|
|
addStoryToLocalDb,
|
|
isChatFolder,
|
|
log,
|
|
resolveMessageApiChatId,
|
|
serializeBytes,
|
|
} from '../helpers';
|
|
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';
|
|
|
|
const sentMessageIds = new Set();
|
|
|
|
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: ApiPoll | 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 && buildPollFromMedia(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': sentMessageIds.has(message.id) ? 'updateScheduledMessage' : 'newScheduledMessage',
|
|
id: message.id,
|
|
chatId: message.chatId,
|
|
message,
|
|
poll,
|
|
});
|
|
} else {
|
|
// We don't have preview for action or 'via bot' messages, so `newMessage` update here is required
|
|
const hasLocalCopy = sentMessageIds.has(message.id) && !message.viaBotId && !message.content.action;
|
|
sendApiUpdate({
|
|
'@type': hasLocalCopy ? 'updateMessage' : 'newMessage',
|
|
id: message.id,
|
|
chatId: message.chatId,
|
|
message,
|
|
shouldForceReply,
|
|
poll,
|
|
});
|
|
}
|
|
|
|
// 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) {
|
|
// 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
|
|
))) {
|
|
sendApiUpdate({
|
|
'@type': 'updateChat',
|
|
id: message.chatId,
|
|
chat: {
|
|
isForbidden: true,
|
|
isNotJoined: true,
|
|
},
|
|
});
|
|
}
|
|
} 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)
|
|
))) {
|
|
sendApiUpdate({
|
|
'@type': 'updateChatJoin',
|
|
id: message.chatId,
|
|
});
|
|
}
|
|
} else if (action instanceof GramJs.MessageActionGroupCall) {
|
|
if (!action.duration && action.call) {
|
|
sendApiUpdate({
|
|
'@type': 'updateGroupCallChatId',
|
|
chatId: message.chatId,
|
|
call: {
|
|
id: action.call.id.toString(),
|
|
accessHash: action.call.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;
|
|
|
|
sendApiUpdate({
|
|
'@type': 'updateQuickReplyMessage',
|
|
id: message.id,
|
|
message,
|
|
});
|
|
} 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']);
|
|
|
|
const poll = mtpMessage instanceof GramJs.Message && mtpMessage.media
|
|
? buildPollFromMedia(mtpMessage.media) : undefined;
|
|
|
|
sendApiUpdate({
|
|
'@type': 'updateMessage',
|
|
id: message.id,
|
|
chatId: message.chatId,
|
|
message,
|
|
poll,
|
|
});
|
|
} else if (update instanceof GramJs.UpdateMessageReactions) {
|
|
sendApiUpdate({
|
|
'@type': 'updateMessageReactions',
|
|
id: update.msgId,
|
|
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,
|
|
},
|
|
});
|
|
} 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.UpdateMessageID || update instanceof GramJs.UpdateShortSentMessage) {
|
|
sentMessageIds.add(update.id);
|
|
} 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;
|
|
if (poll) {
|
|
const apiPoll = buildPoll(poll, results);
|
|
|
|
sendApiUpdate({
|
|
'@type': 'updateMessagePoll',
|
|
pollId: String(pollId),
|
|
pollUpdate: apiPoll,
|
|
});
|
|
} else {
|
|
const pollResults = buildPollResults(results);
|
|
sendApiUpdate({
|
|
'@type': 'updateMessagePoll',
|
|
pollId: String(pollId),
|
|
pollUpdate: { 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': 'updateChatInbox',
|
|
id: getApiChatIdFromMtpPeer(update.peer),
|
|
chat: {
|
|
lastReadInboxMessageId: update.maxId,
|
|
unreadCount: update.stillUnreadCount,
|
|
},
|
|
});
|
|
} else if (update instanceof GramJs.UpdateReadHistoryOutbox) {
|
|
sendApiUpdate({
|
|
'@type': 'updateChat',
|
|
id: getApiChatIdFromMtpPeer(update.peer),
|
|
chat: {
|
|
lastReadOutboxMessageId: update.maxId,
|
|
},
|
|
});
|
|
} else if (update instanceof GramJs.UpdateReadChannelInbox) {
|
|
sendApiUpdate({
|
|
'@type': 'updateChat',
|
|
id: buildApiPeerId(update.channelId, 'channel'),
|
|
chat: {
|
|
lastReadInboxMessageId: update.maxId,
|
|
unreadCount: update.stillUnreadCount,
|
|
},
|
|
});
|
|
} else if (update instanceof GramJs.UpdateReadChannelOutbox) {
|
|
sendApiUpdate({
|
|
'@type': 'updateChat',
|
|
id: buildApiPeerId(update.channelId, 'channel'),
|
|
chat: {
|
|
lastReadOutboxMessageId: update.maxId,
|
|
},
|
|
});
|
|
} else if (update instanceof GramJs.UpdateReadChannelDiscussionInbox) {
|
|
sendApiUpdate({
|
|
'@type': 'updateThreadInfo',
|
|
threadInfo: {
|
|
chatId: buildApiPeerId(update.channelId, 'channel'),
|
|
threadId: update.topMsgId,
|
|
lastReadInboxMessageId: update.readMaxId,
|
|
},
|
|
});
|
|
} else if (update instanceof GramJs.UpdateReadChannelDiscussionOutbox) {
|
|
sendApiUpdate({
|
|
'@type': 'updateChat',
|
|
id: buildApiPeerId(update.channelId, 'channel'),
|
|
chat: {
|
|
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
|
|
? 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.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.UpdateNotifySettings
|
|
&& update.peer instanceof GramJs.NotifyPeer
|
|
) {
|
|
const payload = buildApiNotifyException(update.notifySettings, update.peer.peer);
|
|
scheduleMutedChatUpdate(payload.chatId, payload.muteUntil, sendApiUpdate);
|
|
sendApiUpdate({
|
|
'@type': 'updateNotifyExceptions',
|
|
...payload,
|
|
});
|
|
} else if (
|
|
update instanceof GramJs.UpdateNotifySettings
|
|
&& update.peer instanceof GramJs.NotifyForumTopic
|
|
) {
|
|
const payload = buildApiNotifyExceptionTopic(
|
|
update.notifySettings, update.peer.peer, update.peer.topMsgId,
|
|
);
|
|
scheduleMutedTopicUpdate(payload.chatId, payload.topicId, payload.muteUntil, sendApiUpdate);
|
|
sendApiUpdate({
|
|
'@type': 'updateTopicNotifyExceptions',
|
|
...payload,
|
|
});
|
|
} 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) {
|
|
sendApiUpdate({
|
|
'@type': 'updateStartEmojiInteraction',
|
|
id,
|
|
emoji: update.action.emoticon,
|
|
messageId: update.action.msgId,
|
|
interaction: buildApiEmojiInteraction(JSON.parse(update.action.interaction.data)),
|
|
});
|
|
} else {
|
|
sendApiUpdate({
|
|
'@type': 'updateChatTypingStatus',
|
|
id,
|
|
typingStatus: buildChatTypingStatus(update),
|
|
});
|
|
}
|
|
} else if (update instanceof GramJs.UpdateChannelUserTyping) {
|
|
const id = buildApiPeerId(update.channelId, 'channel');
|
|
|
|
sendApiUpdate({
|
|
'@type': 'updateChatTypingStatus',
|
|
id,
|
|
threadId: update.topMsgId,
|
|
typingStatus: buildChatTypingStatus(update),
|
|
});
|
|
} 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) {
|
|
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': 'updateChat',
|
|
id: getApiChatIdFromMtpPeer(update.peer.peer),
|
|
chat: {
|
|
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) {
|
|
// 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) => {
|
|
sendApiUpdate({
|
|
'@type': 'deleteContact',
|
|
id: buildApiPeerId(user.id, 'user'),
|
|
});
|
|
});
|
|
|
|
_entities
|
|
.filter((e) => e instanceof GramJs.User && e.contact)
|
|
.map(buildApiUser)
|
|
.forEach((user) => {
|
|
if (!user) {
|
|
return;
|
|
}
|
|
|
|
sendApiUpdate({
|
|
'@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;
|
|
}
|
|
|
|
sendApiUpdate({
|
|
'@type': 'updateNotifySettings',
|
|
peerType,
|
|
isSilent: Boolean(silent
|
|
|| (typeof muteUntil === 'number' && Date.now() + getServerTimeOffset() * 1000 < muteUntil * 1000)),
|
|
shouldShowPreviews: Boolean(showPreviews),
|
|
});
|
|
} 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) {
|
|
sendApiUpdate({
|
|
'@type': 'updateGroupCallParticipants',
|
|
groupCallId: getGroupCallId(update.call),
|
|
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.UpdateBotMenuButton) {
|
|
const {
|
|
botId,
|
|
button,
|
|
} = update;
|
|
|
|
const id = buildApiPeerId(botId, 'user');
|
|
|
|
sendApiUpdate({
|
|
'@type': 'updateBotMenuButton',
|
|
botId: id,
|
|
button: buildApiBotMenuButton(button),
|
|
});
|
|
} 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.UpdateChannelPinnedTopic) {
|
|
sendApiUpdate({
|
|
'@type': 'updatePinnedTopic',
|
|
chatId: buildApiPeerId(update.channelId, 'channel'),
|
|
topicId: update.topicId,
|
|
isPinned: Boolean(update.pinned),
|
|
});
|
|
} else if (update instanceof GramJs.UpdateChannelPinnedTopics) {
|
|
sendApiUpdate({
|
|
'@type': 'updatePinnedTopicsOrder',
|
|
chatId: buildApiPeerId(update.channelId, 'channel'),
|
|
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) {
|
|
sendApiUpdate({
|
|
'@type': 'updateStarsBalance',
|
|
balance: buildApiStarsAmount(update.balance),
|
|
});
|
|
} else if (update instanceof GramJs.UpdatePaidReactionPrivacy) {
|
|
sendApiUpdate({
|
|
'@type': 'updatePaidReactionPrivacy',
|
|
isPrivate: 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 (DEBUG) {
|
|
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
|
log('UNEXPECTED UPDATE', params);
|
|
}
|
|
}
|