Saved Dialogs: Allow deletion (#4243)
This commit is contained in:
parent
8548e96e01
commit
af8aa64d8d
@ -158,7 +158,8 @@ export type UniversalMessage = (
|
||||
& Pick<Partial<GramJs.Message & GramJs.MessageService>, (
|
||||
'out' | 'message' | 'entities' | 'fromId' | 'peerId' | 'fwdFrom' | 'replyTo' | 'replyMarkup' | 'post' |
|
||||
'media' | 'action' | 'views' | 'editDate' | 'editHide' | 'mediaUnread' | 'groupedId' | 'mentioned' | 'viaBotId' |
|
||||
'replies' | 'fromScheduled' | 'postAuthor' | 'noforwards' | 'reactions' | 'forwards' | 'silent' | 'pinned'
|
||||
'replies' | 'fromScheduled' | 'postAuthor' | 'noforwards' | 'reactions' | 'forwards' | 'silent' | 'pinned' |
|
||||
'savedPeerId'
|
||||
)>
|
||||
);
|
||||
|
||||
@ -197,6 +198,8 @@ export function buildApiMessageWithChatId(
|
||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
||||
const hasComments = mtpMessage.replies?.comments;
|
||||
|
||||
const savedPeerId = mtpMessage.savedPeerId && getApiChatIdFromMtpPeer(mtpMessage.savedPeerId);
|
||||
|
||||
return omitUndefined({
|
||||
id: mtpMessage.id,
|
||||
chatId,
|
||||
@ -233,6 +236,7 @@ export function buildApiMessageWithChatId(
|
||||
isProtected,
|
||||
isForwardingAllowed,
|
||||
hasComments,
|
||||
savedPeerId,
|
||||
} satisfies ApiMessage);
|
||||
}
|
||||
|
||||
|
||||
@ -72,8 +72,10 @@ import {
|
||||
} from '../helpers';
|
||||
import localDb from '../localDb';
|
||||
import { scheduleMutedChatUpdate } from '../scheduleUnmute';
|
||||
import { applyState, processUpdate, updateChannelState } from '../updateManager';
|
||||
import { dispatchThreadInfoUpdates } from '../updater';
|
||||
import {
|
||||
applyState, processAffectedHistory, processUpdate, updateChannelState,
|
||||
} from '../updates/updateManager';
|
||||
import { dispatchThreadInfoUpdates } from '../updates/updater';
|
||||
import { invokeRequest, uploadFile } from './client';
|
||||
|
||||
type FullChatData = {
|
||||
@ -1787,7 +1789,7 @@ export async function fetchTopicById({
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteTopic({
|
||||
export async function deleteTopic({
|
||||
chat, topicId,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
@ -1795,12 +1797,18 @@ export function deleteTopic({
|
||||
}) {
|
||||
const { id, accessHash } = chat;
|
||||
|
||||
return invokeRequest(new GramJs.channels.DeleteTopicHistory({
|
||||
const result = await invokeRequest(new GramJs.channels.DeleteTopicHistory({
|
||||
channel: buildInputPeer(id, accessHash),
|
||||
topMsgId: topicId,
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}));
|
||||
|
||||
if (!result) return;
|
||||
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
if (result.offset) {
|
||||
await deleteTopic({ chat, topicId });
|
||||
}
|
||||
}
|
||||
|
||||
export function togglePinnedTopic({
|
||||
|
||||
@ -36,7 +36,7 @@ import {
|
||||
reset as resetUpdatesManager,
|
||||
scheduleGetChannelDifference,
|
||||
updateChannelState,
|
||||
} from '../updateManager';
|
||||
} from '../updates/updateManager';
|
||||
import {
|
||||
onAuthError, onAuthReady, onCurrentUserUpdate, onRequestCode, onRequestPassword, onRequestPhoneNumber,
|
||||
onRequestQrCode, onRequestRegistration, onWebAuthTokenFailed,
|
||||
|
||||
@ -35,6 +35,7 @@ export {
|
||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
||||
deleteSavedHistory,
|
||||
} from './messages';
|
||||
|
||||
export {
|
||||
|
||||
@ -10,7 +10,7 @@ import type { MethodArgs, MethodResponse, Methods } from './types';
|
||||
import { API_THROTTLE_RESET_UPDATES, API_UPDATE_THROTTLE } from '../../../config';
|
||||
import { throttle, throttleWithTickEnd } from '../../../util/schedulers';
|
||||
import { updateFullLocalDb } from '../localDb';
|
||||
import { init as initUpdater } from '../updater';
|
||||
import { init as initUpdater } from '../updates/updater';
|
||||
import { init as initAuth } from './auth';
|
||||
import { init as initBots } from './bots';
|
||||
import { init as initCalls } from './calls';
|
||||
|
||||
@ -80,8 +80,8 @@ import {
|
||||
addMessageToLocalDb,
|
||||
deserializeBytes,
|
||||
} from '../helpers';
|
||||
import { updateChannelState } from '../updateManager';
|
||||
import { dispatchThreadInfoUpdates } from '../updater';
|
||||
import { processAffectedHistory, updateChannelState } from '../updates/updateManager';
|
||||
import { dispatchThreadInfoUpdates } from '../updates/updater';
|
||||
import { requestChatUpdate } from './chats';
|
||||
import { handleGramJsUpdate, invokeRequest, uploadFile } from './client';
|
||||
|
||||
@ -715,10 +715,18 @@ export async function pinMessage({
|
||||
}
|
||||
|
||||
export async function unpinAllMessages({ chat, threadId }: { chat: ApiChat; threadId?: ThreadId }) {
|
||||
await invokeRequest(new GramJs.messages.UnpinAllMessages({
|
||||
const result = await invokeRequest(new GramJs.messages.UnpinAllMessages({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
...(threadId && { topMsgId: Number(threadId) }),
|
||||
}));
|
||||
|
||||
if (!result) return;
|
||||
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
if (result.offset) {
|
||||
await unpinAllMessages({ chat, threadId });
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteMessages({
|
||||
@ -744,6 +752,8 @@ export async function deleteMessages({
|
||||
return;
|
||||
}
|
||||
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
onUpdate({
|
||||
'@type': 'deleteMessages',
|
||||
ids: messageIds,
|
||||
@ -784,9 +794,13 @@ export async function deleteHistory({
|
||||
return;
|
||||
}
|
||||
|
||||
if ('offset' in result && result.offset) {
|
||||
await deleteHistory({ chat, shouldDeleteForAll });
|
||||
return;
|
||||
if ('offset' in result) {
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
if (result.offset) {
|
||||
await deleteHistory({ chat, shouldDeleteForAll });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
@ -795,6 +809,32 @@ export async function deleteHistory({
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteSavedHistory({
|
||||
chat,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.messages.DeleteSavedHistory({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
if (result.offset) {
|
||||
await deleteSavedHistory({ chat });
|
||||
return;
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
'@type': 'deleteSavedHistory',
|
||||
chatId: chat.id,
|
||||
});
|
||||
}
|
||||
|
||||
export async function reportMessages({
|
||||
peer, messageIds, reason, description,
|
||||
}: {
|
||||
@ -862,10 +902,14 @@ export async function markMessageListRead({
|
||||
readMaxId: fixedMaxId,
|
||||
}));
|
||||
} else {
|
||||
await invokeRequest(new GramJs.messages.ReadHistory({
|
||||
const result = await invokeRequest(new GramJs.messages.ReadHistory({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
maxId: fixedMaxId,
|
||||
}));
|
||||
|
||||
if (result) {
|
||||
processAffectedHistory(chat, result);
|
||||
}
|
||||
}
|
||||
|
||||
if (threadId === MAIN_THREAD_ID) {
|
||||
@ -880,7 +924,7 @@ export async function markMessagesRead({
|
||||
}) {
|
||||
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
||||
|
||||
await invokeRequest(
|
||||
const result = await invokeRequest(
|
||||
isChannel
|
||||
? new GramJs.channels.ReadMessageContents({
|
||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||
@ -891,6 +935,14 @@ export async function markMessagesRead({
|
||||
}),
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result !== true) {
|
||||
processAffectedHistory(chat, result);
|
||||
}
|
||||
|
||||
onUpdate({
|
||||
...(isChannel ? {
|
||||
'@type': 'updateChannelMessages',
|
||||
@ -1575,28 +1627,40 @@ export function clickSponsoredMessage({ chat, random }: { chat: ApiChat; random:
|
||||
}));
|
||||
}
|
||||
|
||||
export function readAllMentions({
|
||||
export async function readAllMentions({
|
||||
chat,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.messages.ReadMentions({
|
||||
const result = await invokeRequest(new GramJs.messages.ReadMentions({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}));
|
||||
|
||||
if (!result) return;
|
||||
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
if (result.offset) {
|
||||
await readAllMentions({ chat });
|
||||
}
|
||||
}
|
||||
|
||||
export function readAllReactions({
|
||||
export async function readAllReactions({
|
||||
chat,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
}) {
|
||||
return invokeRequest(new GramJs.messages.ReadReactions({
|
||||
const result = await invokeRequest(new GramJs.messages.ReadReactions({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
}), {
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}));
|
||||
|
||||
if (!result) return;
|
||||
|
||||
processAffectedHistory(chat, result);
|
||||
|
||||
if (result.offset) {
|
||||
await readAllReactions({ chat });
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchUnreadMentions({
|
||||
|
||||
16
src/api/gramjs/updates/UpdatePts.ts
Normal file
16
src/api/gramjs/updates/UpdatePts.ts
Normal file
@ -0,0 +1,16 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
import type { BigInteger } from 'big-integer';
|
||||
|
||||
export class LocalUpdatePts {
|
||||
constructor(public pts: number, public ptsCount: number) {}
|
||||
}
|
||||
|
||||
export class LocalUpdateChannelPts {
|
||||
constructor(public channelId: BigInteger, public pts: number, public ptsCount: number) {}
|
||||
}
|
||||
|
||||
export type UpdatePts = LocalUpdatePts | LocalUpdateChannelPts;
|
||||
|
||||
export function buildLocalUpdatePts(pts: number, ptsCount: number, channelId?: BigInteger) {
|
||||
return channelId ? new LocalUpdateChannelPts(channelId, pts, ptsCount) : new LocalUpdatePts(pts, ptsCount);
|
||||
}
|
||||
@ -1,17 +1,20 @@
|
||||
import { Api as GramJs } from '../../lib/gramjs';
|
||||
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../lib/gramjs/network';
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../../lib/gramjs/network';
|
||||
|
||||
import type { invokeRequest } from './methods/client';
|
||||
import type { ApiChat } from '../../types';
|
||||
import type { invokeRequest } from '../methods/client';
|
||||
import type { Update } from './updater';
|
||||
|
||||
import { DEBUG } from '../../config';
|
||||
import SortedQueue from '../../util/SortedQueue';
|
||||
import { buildApiPeerId } from './apiBuilders/peers';
|
||||
import { buildInputEntity } from './gramjsBuilders';
|
||||
import { addEntitiesToLocalDb } from './helpers';
|
||||
import localDb from './localDb';
|
||||
import { DEBUG } from '../../../config';
|
||||
import SortedQueue from '../../../util/SortedQueue';
|
||||
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||
import { buildInputEntity, buildMtpPeerId } from '../gramjsBuilders';
|
||||
import { addEntitiesToLocalDb } from '../helpers';
|
||||
import localDb from '../localDb';
|
||||
import { dispatchUserAndChatUpdates, sendUpdate, updater } from './updater';
|
||||
|
||||
import { buildLocalUpdatePts, type UpdatePts } from './UpdatePts';
|
||||
|
||||
export type State = {
|
||||
seq: number;
|
||||
date: number;
|
||||
@ -19,7 +22,7 @@ export type State = {
|
||||
qts: number;
|
||||
};
|
||||
type SeqUpdate = (GramJs.Updates | GramJs.UpdatesCombined) & { _isFromDifference?: true };
|
||||
type PtsUpdate = GramJs.TypeUpdate & { pts: number } & { _isFromDifference?: true };
|
||||
type PtsUpdate = ((GramJs.TypeUpdate & { pts: number }) | UpdatePts) & { _isFromDifference?: true };
|
||||
|
||||
const COMMON_BOX_QUEUE_ID = '0';
|
||||
const CHANNEL_DIFFERENCE_LIMIT = 1000;
|
||||
@ -201,10 +204,12 @@ function popPtsQueue(channelId: string) {
|
||||
const pts = update.pts;
|
||||
const ptsCount = getPtsCount(update);
|
||||
|
||||
// Sometimes server sends updates for channels that are opened in other clients. We ignore them
|
||||
if (localPts === undefined) {
|
||||
if (DEBUG) {
|
||||
// Uncomment to debug missing updates
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[UpdateManager] Got pts update without local state', channelId);
|
||||
// console.error('[UpdateManager] Got pts update without local state', channelId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -389,6 +394,16 @@ export function reset() {
|
||||
isInited = false;
|
||||
}
|
||||
|
||||
export function processAffectedHistory(
|
||||
chat: ApiChat, affected: GramJs.messages.AffectedMessages | GramJs.messages.AffectedHistory,
|
||||
) {
|
||||
const isChannel = chat.type === 'chatTypeChannel' || chat.type === 'chatTypeSuperGroup';
|
||||
const channeId = isChannel ? buildMtpPeerId(chat.id, 'channel') : undefined;
|
||||
const update = buildLocalUpdatePts(affected.pts, affected.ptsCount, channeId);
|
||||
|
||||
processUpdate(update);
|
||||
}
|
||||
|
||||
async function loadRemoteState() {
|
||||
const remoteState = await invoke(new GramJs.updates.GetState());
|
||||
if (!remoteState) return;
|
||||
@ -1,21 +1,21 @@
|
||||
import { Api as GramJs, connection } from '../../lib/gramjs';
|
||||
import { Api as GramJs, connection } from '../../../lib/gramjs';
|
||||
|
||||
import type { GroupCallConnectionData } from '../../lib/secret-sauce';
|
||||
import type { GroupCallConnectionData } from '../../../lib/secret-sauce';
|
||||
import type {
|
||||
ApiMessage, ApiMessageExtendedMediaPreview, ApiStory, ApiStorySkipped,
|
||||
ApiUpdate, ApiUpdateConnectionStateType, MediaContent, OnApiUpdate,
|
||||
} from '../types';
|
||||
} from '../../types';
|
||||
|
||||
import { DEBUG, GENERAL_TOPIC_ID } from '../../config';
|
||||
import { compact, omit, pick } from '../../util/iteratees';
|
||||
import { getServerTimeOffset, setServerTimeOffset } from '../../util/serverTime';
|
||||
import { buildApiBotMenuButton } from './apiBuilders/bots';
|
||||
import { DEBUG, GENERAL_TOPIC_ID } from '../../../config';
|
||||
import { compact, omit, pick } from '../../../util/iteratees';
|
||||
import { getServerTimeOffset, setServerTimeOffset } from '../../../util/serverTime';
|
||||
import { buildApiBotMenuButton } from '../apiBuilders/bots';
|
||||
import {
|
||||
buildApiGroupCall,
|
||||
buildApiGroupCallParticipant,
|
||||
buildPhoneCall,
|
||||
getGroupCallId,
|
||||
} from './apiBuilders/calls';
|
||||
} from '../apiBuilders/calls';
|
||||
import {
|
||||
buildApiChatFolder,
|
||||
buildApiChatFromPreview,
|
||||
@ -24,15 +24,15 @@ import {
|
||||
buildChatMember,
|
||||
buildChatMembers,
|
||||
buildChatTypingStatus,
|
||||
} from './apiBuilders/chats';
|
||||
import { buildApiPhoto, buildApiUsernames, buildPrivacyRules } from './apiBuilders/common';
|
||||
import { omitVirtualClassFields } from './apiBuilders/helpers';
|
||||
} from '../apiBuilders/chats';
|
||||
import { buildApiPhoto, buildApiUsernames, buildPrivacyRules } from '../apiBuilders/common';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import {
|
||||
buildApiMessageExtendedMediaPreview,
|
||||
buildMessageMediaContent,
|
||||
buildPoll,
|
||||
buildPollResults,
|
||||
} from './apiBuilders/messageContent';
|
||||
} from '../apiBuilders/messageContent';
|
||||
import {
|
||||
buildApiMessage,
|
||||
buildApiMessageFromNotification,
|
||||
@ -40,28 +40,28 @@ import {
|
||||
buildApiMessageFromShortChat,
|
||||
buildApiThreadInfoFromMessage,
|
||||
buildMessageDraft,
|
||||
} from './apiBuilders/messages';
|
||||
} from '../apiBuilders/messages';
|
||||
import {
|
||||
buildApiNotifyException,
|
||||
buildApiNotifyExceptionTopic,
|
||||
buildPrivacyKey,
|
||||
} from './apiBuilders/misc';
|
||||
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from './apiBuilders/peers';
|
||||
} from '../apiBuilders/misc';
|
||||
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';
|
||||
} from '../apiBuilders/reactions';
|
||||
import { buildApiStealthMode, buildApiStory } from '../apiBuilders/stories';
|
||||
import { buildApiEmojiInteraction, buildStickerSet } from '../apiBuilders/symbols';
|
||||
import {
|
||||
buildApiUser,
|
||||
buildApiUserStatus,
|
||||
} from './apiBuilders/users';
|
||||
} from '../apiBuilders/users';
|
||||
import {
|
||||
buildChatPhotoForLocalDb,
|
||||
buildMessageFromUpdate,
|
||||
isMessageWithMedia,
|
||||
} from './gramjsBuilders';
|
||||
} from '../gramjsBuilders';
|
||||
import {
|
||||
addEntitiesToLocalDb,
|
||||
addMessageToLocalDb,
|
||||
@ -72,13 +72,15 @@ import {
|
||||
resolveMessageApiChatId,
|
||||
serializeBytes,
|
||||
swapLocalInvoiceMedia,
|
||||
} from './helpers';
|
||||
import localDb from './localDb';
|
||||
import { scheduleMutedChatUpdate, scheduleMutedTopicUpdate } from './scheduleUnmute';
|
||||
} from '../helpers';
|
||||
import localDb from '../localDb';
|
||||
import { scheduleMutedChatUpdate, scheduleMutedTopicUpdate } from '../scheduleUnmute';
|
||||
|
||||
import { LocalUpdateChannelPts, LocalUpdatePts, type UpdatePts } from './UpdatePts';
|
||||
|
||||
export type Update = (
|
||||
(GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }
|
||||
) | typeof connection.UpdateConnectionState;
|
||||
) | typeof connection.UpdateConnectionState | UpdatePts;
|
||||
|
||||
const DELETE_MISSING_CHANNEL_MESSAGE_DELAY = 1000;
|
||||
|
||||
@ -1156,6 +1158,8 @@ export function updater(update: Update) {
|
||||
chatId: buildApiPeerId(update.channelId, 'channel'),
|
||||
isEnabled: update.enabled ? true : undefined,
|
||||
});
|
||||
} 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);
|
||||
@ -536,6 +536,7 @@ export interface ApiMessage {
|
||||
};
|
||||
reactions?: ApiReactions;
|
||||
hasComments?: boolean;
|
||||
savedPeerId?: string;
|
||||
}
|
||||
|
||||
export interface ApiReactions {
|
||||
|
||||
@ -315,6 +315,11 @@ export type ApiUpdateDeleteHistory = {
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
export type ApiUpdateDeleteSavedHistory = {
|
||||
'@type': 'deleteSavedHistory';
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
export type ApiUpdateDeleteProfilePhotos = {
|
||||
'@type': 'deleteProfilePhotos';
|
||||
ids: string[];
|
||||
@ -730,7 +735,8 @@ export type ApiUpdate = (
|
||||
ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction |
|
||||
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
|
||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage
|
||||
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||
ApiUpdateDeleteSavedHistory
|
||||
);
|
||||
|
||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||
|
||||
@ -28,6 +28,7 @@ import './DeleteChatModal.scss';
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
chat: ApiChat;
|
||||
isSavedDialog?: boolean;
|
||||
onClose: () => void;
|
||||
onCloseAnimationEnd?: () => void;
|
||||
};
|
||||
@ -47,6 +48,7 @@ type StateProps = {
|
||||
const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
chat,
|
||||
isSavedDialog,
|
||||
isChannel,
|
||||
isPrivateChat,
|
||||
isChatWithSelf,
|
||||
@ -62,6 +64,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
leaveChannel,
|
||||
deleteHistory,
|
||||
deleteSavedHistory,
|
||||
deleteChannel,
|
||||
deleteChatUser,
|
||||
blockUser,
|
||||
@ -74,7 +77,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
||||
|
||||
onClose();
|
||||
}, [deleteHistory, chat.id, onClose]);
|
||||
}, [chat.id, onClose]);
|
||||
|
||||
const handleDeleteAndStop = useCallback(() => {
|
||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
||||
@ -84,7 +87,9 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
}, [chat.id, onClose]);
|
||||
|
||||
const handleDeleteChat = useCallback(() => {
|
||||
if (isPrivateChat) {
|
||||
if (isSavedDialog) {
|
||||
deleteSavedHistory({ chatId: chat.id });
|
||||
} else if (isPrivateChat) {
|
||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
||||
} else if (isBasicGroup) {
|
||||
deleteChatUser({ chatId: chat.id, userId: currentUserId! });
|
||||
@ -103,11 +108,8 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
currentUserId,
|
||||
chat.isCreator,
|
||||
chat.id,
|
||||
isSavedDialog,
|
||||
onClose,
|
||||
deleteHistory,
|
||||
deleteChatUser,
|
||||
leaveChannel,
|
||||
deleteChannel,
|
||||
]);
|
||||
|
||||
const handleLeaveChat = useCallback(() => {
|
||||
@ -133,6 +135,10 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
function renderTitle() {
|
||||
if (isSavedDialog) {
|
||||
return isChatWithSelf ? 'ClearHistoryMyNotesTitle' : 'ClearHistoryTitleSingle2';
|
||||
}
|
||||
|
||||
if (isChannel && !chat.isCreator) {
|
||||
return 'LeaveChannel';
|
||||
}
|
||||
@ -149,6 +155,16 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
function renderContent() {
|
||||
if (isSavedDialog) {
|
||||
return (
|
||||
<p>
|
||||
{renderText(
|
||||
isChatWithSelf ? lang('ClearHistoryMyNotesMessage') : lang('ClearHistoryMessageSingle', chatTitle),
|
||||
['simple_markdown', 'emoji'],
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
if (isChannel && chat.isCreator) {
|
||||
return (
|
||||
<p>
|
||||
@ -165,6 +181,10 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
|
||||
function renderActionText() {
|
||||
if (isSavedDialog) {
|
||||
return 'Delete';
|
||||
}
|
||||
|
||||
if (isChannel && !chat.isCreator) {
|
||||
return 'LeaveChannel';
|
||||
}
|
||||
@ -189,7 +209,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
{renderContent()}
|
||||
<div className="dialog-buttons-column">
|
||||
{isBot && (
|
||||
{isBot && !isSavedDialog && (
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteAndStop}>
|
||||
{lang('DeleteAndStop')}
|
||||
</Button>
|
||||
@ -199,7 +219,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
{contactName ? renderText(lang('ChatList.DeleteForEveryone', contactName)) : lang('DeleteForAll')}
|
||||
</Button>
|
||||
)}
|
||||
{!isPrivateChat && chat.isCreator && (
|
||||
{!isPrivateChat && chat.isCreator && !isSavedDialog && (
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteChat}>
|
||||
{lang('DeleteForAll')}
|
||||
</Button>
|
||||
@ -208,7 +228,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
color="danger"
|
||||
className="confirm-dialog-button"
|
||||
isText
|
||||
onClick={isPrivateChat ? handleDeleteChat : handleLeaveChat}
|
||||
onClick={(isPrivateChat || isSavedDialog) ? handleDeleteChat : handleLeaveChat}
|
||||
>
|
||||
{lang(renderActionText())}
|
||||
</Button>
|
||||
@ -219,12 +239,12 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chat }): StateProps => {
|
||||
(global, { chat, isSavedDialog }): StateProps => {
|
||||
const isPrivateChat = isUserId(chat.id);
|
||||
const isChatWithSelf = selectIsChatWithSelf(global, chat.id);
|
||||
const user = isPrivateChat && selectUser(global, getPrivateChatUserId(chat)!);
|
||||
const isBot = user && isUserBot(user) && !chat.isSupport;
|
||||
const canDeleteForAll = (isPrivateChat && !isChatWithSelf && !isBot);
|
||||
const canDeleteForAll = (isPrivateChat && !isChatWithSelf && !isBot && !isSavedDialog);
|
||||
const contactName = isPrivateChat
|
||||
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
|
||||
: undefined;
|
||||
|
||||
@ -354,6 +354,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
onClose={closeDeleteModal}
|
||||
onCloseAnimationEnd={unmarkRenderDeleteModal}
|
||||
chat={chat}
|
||||
isSavedDialog={isSavedDialog}
|
||||
/>
|
||||
)}
|
||||
{shouldRenderMuteModal && (
|
||||
|
||||
@ -12,6 +12,7 @@ import { ManagementScreens } from '../../types';
|
||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||
import {
|
||||
getHasAdminRight,
|
||||
getIsSavedDialog,
|
||||
isAnonymousForwardsChat,
|
||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||
} from '../../global/helpers';
|
||||
@ -478,6 +479,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
const isDiscussionThread = messageListType === 'thread' && threadId !== MAIN_THREAD_ID;
|
||||
const isRightColumnShown = selectIsRightColumnShown(global, isMobile);
|
||||
|
||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||
|
||||
const isUserBlocked = isPrivate ? selectIsUserBlocked(global, chatId) : false;
|
||||
const canRestartBot = Boolean(bot && isUserBlocked);
|
||||
const canStartBot = !canRestartBot && Boolean(selectIsChatBotNotStarted(global, chatId));
|
||||
@ -489,7 +492,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const canCall = ARE_CALLS_SUPPORTED && isUserId(chat.id) && !isChatWithSelf && !bot && !chat.isSupport
|
||||
&& !isAnonymousForwardsChat(chat.id);
|
||||
const canMute = isMainThread && !isChatWithSelf && !canSubscribe;
|
||||
const canLeave = isMainThread && !canSubscribe;
|
||||
const canLeave = isSavedDialog || (isMainThread && !canSubscribe);
|
||||
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive;
|
||||
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
|
||||
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
getCanDeleteChat,
|
||||
getCanManageTopic,
|
||||
getHasAdminRight,
|
||||
getIsSavedDialog,
|
||||
isChatChannel,
|
||||
isChatGroup,
|
||||
isUserId,
|
||||
@ -119,6 +120,7 @@ type StateProps = {
|
||||
isBlocked?: boolean;
|
||||
isBot?: boolean;
|
||||
isChatWithSelf?: boolean;
|
||||
savedDialog?: ApiChat;
|
||||
};
|
||||
|
||||
const CLOSE_MENU_ANIMATION_DURATION = 200;
|
||||
@ -163,6 +165,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
isBlocked,
|
||||
isBot,
|
||||
isChatWithSelf,
|
||||
savedDialog,
|
||||
onJoinRequestsClick,
|
||||
onSubscribeChannel,
|
||||
onSearchClick,
|
||||
@ -403,6 +406,24 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}, [botCommands, closeMenu, lang, sendBotCommand]);
|
||||
|
||||
const deleteTitle = useMemo(() => {
|
||||
if (!chat) return undefined;
|
||||
|
||||
if (isPrivate || savedDialog) {
|
||||
return lang('DeleteChatUser');
|
||||
}
|
||||
|
||||
if (canDeleteChat) {
|
||||
return lang('GroupInfo.DeleteAndExit');
|
||||
}
|
||||
|
||||
if (isChannel) {
|
||||
return lang('LeaveChannel');
|
||||
}
|
||||
|
||||
return lang('Group.LeaveGroup');
|
||||
}, [canDeleteChat, chat, isChannel, isPrivate, savedDialog, lang]);
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<div className="HeaderMenuContainer">
|
||||
@ -627,9 +648,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
icon="delete"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{lang(isPrivate
|
||||
? 'DeleteChatUser'
|
||||
: (canDeleteChat ? 'GroupInfo.DeleteAndExit' : (isChannel ? 'LeaveChannel' : 'Group.LeaveGroup')))}
|
||||
{deleteTitle}
|
||||
</MenuItem>
|
||||
</>
|
||||
)}
|
||||
@ -638,7 +657,8 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
<DeleteChatModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={closeDeleteModal}
|
||||
chat={chat}
|
||||
chat={savedDialog || chat}
|
||||
isSavedDialog={Boolean(savedDialog)}
|
||||
/>
|
||||
)}
|
||||
{canMute && shouldRenderMuteModal && chat?.id && (
|
||||
@ -694,6 +714,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
// Context menu item should only be displayed if user hid translation panel
|
||||
const canTranslate = selectCanTranslateChat(global, chatId) && fullInfo?.isTranslationDisabled;
|
||||
|
||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||
const savedDialog = isSavedDialog ? selectChat(global, String(threadId)) : undefined;
|
||||
|
||||
return {
|
||||
chat,
|
||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
||||
@ -717,6 +740,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isBlocked: userFullInfo?.isBlocked,
|
||||
isBot: Boolean(chatBot),
|
||||
isChatWithSelf,
|
||||
savedDialog,
|
||||
};
|
||||
},
|
||||
)(HeaderMenuContainer));
|
||||
|
||||
@ -583,7 +583,6 @@ addActionHandler('loadTopChats', (): ActionReturnType => {
|
||||
runThrottledForLoadTopChats(() => {
|
||||
loadChats('active');
|
||||
loadChats('archived');
|
||||
loadChats('saved');
|
||||
});
|
||||
});
|
||||
|
||||
@ -2244,9 +2243,7 @@ addActionHandler('deleteTopic', async (global, actions, payload): Promise<void>
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
const result = await callApi('deleteTopic', { chat, topicId });
|
||||
|
||||
if (!result) return;
|
||||
await callApi('deleteTopic', { chat, topicId });
|
||||
|
||||
global = getGlobal();
|
||||
global = deleteTopic(global, chatId, topicId);
|
||||
|
||||
@ -710,6 +710,22 @@ addActionHandler('deleteHistory', async (global, actions, payload): Promise<void
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('deleteSavedHistory', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, tabId = getCurrentTabId() } = payload!;
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
await callApi('deleteSavedHistory', { chat });
|
||||
|
||||
global = getGlobal();
|
||||
const activeChat = selectCurrentMessageList(global, tabId);
|
||||
if (activeChat && activeChat.threadId === chatId) {
|
||||
actions.openChat({ id: undefined, tabId });
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('reportMessages', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
messageIds, reason, description, tabId = getCurrentTabId(),
|
||||
|
||||
@ -464,6 +464,18 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'deleteSavedHistory': {
|
||||
const { chatId } = update;
|
||||
const currentUserId = global.currentUserId!;
|
||||
global = removeChatFromChatLists(global, chatId, 'saved');
|
||||
setGlobal(global);
|
||||
|
||||
global = getGlobal();
|
||||
deleteThread(global, currentUserId, chatId, actions);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateCommonBoxMessages': {
|
||||
const { ids, messageUpdate } = update;
|
||||
|
||||
@ -925,6 +937,29 @@ function findLastMessage<T extends GlobalState>(global: T, chatId: string, threa
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function deleteThread<T extends GlobalState>(
|
||||
global: T,
|
||||
chatId: string,
|
||||
threadId: ThreadId,
|
||||
actions: RequiredGlobalActions,
|
||||
) {
|
||||
const byId = selectChatMessages(global, chatId);
|
||||
if (!byId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageIds = Object.values(byId).filter((message) => {
|
||||
const messageThreadId = selectThreadIdFromMessage(global, message);
|
||||
return messageThreadId === threadId;
|
||||
}).map((message) => message.id);
|
||||
|
||||
if (!messageIds.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteMessages(global, chatId, messageIds, actions);
|
||||
}
|
||||
|
||||
export function deleteMessages<T extends GlobalState>(
|
||||
global: T, chatId: string | undefined, ids: number[], actions: RequiredGlobalActions,
|
||||
) {
|
||||
@ -942,8 +977,6 @@ export function deleteMessages<T extends GlobalState>(
|
||||
isDeleting: true,
|
||||
});
|
||||
|
||||
global = clearMessageTranslation(global, chatId, id);
|
||||
|
||||
if (chat.topics?.[id]) {
|
||||
global = deleteTopic(global, chatId, id);
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ import {
|
||||
selectViewportIds,
|
||||
} from '../selectors';
|
||||
import { updateTabState } from './tabs';
|
||||
import { clearMessageTranslation } from './translations';
|
||||
|
||||
type MessageStoreSections = {
|
||||
byId: Record<number, ApiMessage>;
|
||||
@ -261,10 +262,12 @@ export function deleteChatMessages<T extends GlobalState>(
|
||||
const message = byId[messageId];
|
||||
if (!message) return;
|
||||
const threadId = selectThreadIdFromMessage(global, message);
|
||||
if (!threadId || threadId === MAIN_THREAD_ID) return;
|
||||
if (!threadId) return;
|
||||
const threadMessages = updatedThreads.get(threadId) || [];
|
||||
threadMessages.push(messageId);
|
||||
updatedThreads.set(threadId, threadMessages);
|
||||
|
||||
global = clearMessageTranslation(global, chatId, messageId);
|
||||
});
|
||||
|
||||
const deletedForwardedPosts = Object.values(pickTruthy(byId, messageIds)).filter(
|
||||
@ -297,15 +300,11 @@ export function deleteChatMessages<T extends GlobalState>(
|
||||
}
|
||||
|
||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||
let viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||
if (!viewportIds) return;
|
||||
|
||||
messageIds.forEach((messageId) => {
|
||||
if (viewportIds?.includes(messageId)) {
|
||||
viewportIds = viewportIds.filter((id) => id !== messageId);
|
||||
}
|
||||
});
|
||||
|
||||
global = replaceTabThreadParam(global, chatId, threadId, 'viewportIds', viewportIds, tabId);
|
||||
const newViewportIds = excludeSortedArray(viewportIds, messageIds);
|
||||
global = replaceTabThreadParam(global, chatId, threadId, 'viewportIds', newViewportIds, tabId);
|
||||
});
|
||||
|
||||
global = replaceThreadParam(global, chatId, threadId, 'listedIds', listedIds);
|
||||
|
||||
@ -508,7 +508,12 @@ export function selectCanDeleteTopic<T extends GlobalState>(global: T, chatId: s
|
||||
export function selectSavedDialogIdFromMessage<T extends GlobalState>(
|
||||
global: T, message: ApiMessage,
|
||||
): string | undefined {
|
||||
const { chatId, senderId, forwardInfo } = message;
|
||||
const {
|
||||
chatId, senderId, forwardInfo, savedPeerId,
|
||||
} = message;
|
||||
|
||||
if (savedPeerId) return savedPeerId;
|
||||
|
||||
if (chatId !== global.currentUserId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -1385,6 +1385,9 @@ export interface ActionPayloads {
|
||||
chatId: string;
|
||||
shouldDeleteForAll?: boolean;
|
||||
} & WithTabId;
|
||||
deleteSavedHistory: {
|
||||
chatId: string;
|
||||
} & WithTabId;
|
||||
loadSponsoredMessages: {
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
@ -45,6 +45,24 @@ const useChatContextActions = ({
|
||||
const { isSelf } = user || {};
|
||||
const isServiceNotifications = user?.id === SERVICE_NOTIFICATIONS_USER_ID;
|
||||
|
||||
const deleteTitle = useMemo(() => {
|
||||
if (!chat) return undefined;
|
||||
|
||||
if (isUserId(chat.id) || isSavedDialog) {
|
||||
return lang('DeleteChatUser');
|
||||
}
|
||||
|
||||
if (getCanDeleteChat(chat)) {
|
||||
return lang('DeleteChat');
|
||||
}
|
||||
|
||||
if (isChatChannel(chat)) {
|
||||
return lang('LeaveChannel');
|
||||
}
|
||||
|
||||
return lang('Group.LeaveGroup');
|
||||
}, [chat, isSavedDialog, lang]);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!chat) {
|
||||
return undefined;
|
||||
@ -91,8 +109,15 @@ const useChatContextActions = ({
|
||||
handler: togglePinned,
|
||||
};
|
||||
|
||||
const actionDelete = {
|
||||
title: deleteTitle,
|
||||
icon: 'delete',
|
||||
destructive: true,
|
||||
handler: handleDelete,
|
||||
};
|
||||
|
||||
if (isSavedDialog) {
|
||||
return compact([actionOpenInNewTab, actionPin]) as MenuItemContextAction[];
|
||||
return compact([actionOpenInNewTab, actionPin, actionDelete]) as MenuItemContextAction[];
|
||||
}
|
||||
|
||||
const actionAddToFolder = canChangeFolder ? {
|
||||
@ -133,17 +158,6 @@ const useChatContextActions = ({
|
||||
? { title: lang('ReportPeer.Report'), icon: 'flag', handler: handleReport }
|
||||
: undefined;
|
||||
|
||||
const actionDelete = {
|
||||
title: isUserId(chat.id)
|
||||
? lang('Delete')
|
||||
: lang(getCanDeleteChat(chat)
|
||||
? 'DeleteChat'
|
||||
: (isChatChannel(chat) ? 'LeaveChannel' : 'Group.LeaveGroup')),
|
||||
icon: 'delete',
|
||||
destructive: true,
|
||||
handler: handleDelete,
|
||||
};
|
||||
|
||||
const isInFolder = folderId !== undefined;
|
||||
|
||||
return compact([
|
||||
@ -159,7 +173,7 @@ const useChatContextActions = ({
|
||||
]) as MenuItemContextAction[];
|
||||
}, [
|
||||
chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted, currentUserId,
|
||||
handleDelete, handleMute, handleReport, folderId, isSelf, isServiceNotifications, isSavedDialog,
|
||||
handleDelete, handleMute, handleReport, folderId, isSelf, isServiceNotifications, isSavedDialog, deleteTitle,
|
||||
]);
|
||||
};
|
||||
|
||||
|
||||
@ -1404,6 +1404,7 @@ messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp;
|
||||
messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult;
|
||||
messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
|
||||
messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
||||
messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
|
||||
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
|
||||
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
||||
updates.getState#edd4882a = updates.State;
|
||||
|
||||
@ -284,6 +284,7 @@
|
||||
"messages.getUnreadMentions",
|
||||
"messages.getSavedDialogs",
|
||||
"messages.getSavedHistory",
|
||||
"messages.deleteSavedHistory",
|
||||
"messages.getPinnedSavedDialogs",
|
||||
"messages.toggleSavedDialogPin",
|
||||
"help.getPremiumPromo",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user