Settings: Support deleting profile photos (#2133)
This commit is contained in:
parent
2b37128066
commit
6138d1a5f7
@ -54,7 +54,7 @@ export {
|
||||
|
||||
export {
|
||||
updateProfile, checkUsername, updateUsername, fetchBlockedContacts, blockContact, unblockContact,
|
||||
updateProfilePhoto, uploadProfilePhoto, fetchWallpapers, uploadWallpaper,
|
||||
updateProfilePhoto, uploadProfilePhoto, deleteProfilePhoto, fetchWallpapers, uploadWallpaper,
|
||||
fetchAuthorizations, terminateAuthorization, terminateAllAuthorizations,
|
||||
fetchWebAuthorizations, terminateWebAuthorization, terminateAllWebAuthorizations,
|
||||
fetchNotificationExceptions, fetchNotificationSettings, updateContactSignUpNotification, updateNotificationSettings,
|
||||
|
||||
@ -6,7 +6,7 @@ import type {
|
||||
ApiError,
|
||||
ApiLangString,
|
||||
ApiLanguage,
|
||||
ApiNotifyException,
|
||||
ApiNotifyException, ApiPhoto,
|
||||
} from '../../types';
|
||||
import type { ApiPrivacyKey, InputPrivacyRules, LangCode } from '../../../types';
|
||||
|
||||
@ -26,7 +26,9 @@ import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
import { buildAppConfig } from '../apiBuilders/appConfig';
|
||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||
import { buildInputEntity, buildInputPeer, buildInputPrivacyKey } from '../gramjsBuilders';
|
||||
import {
|
||||
buildInputEntity, buildInputPeer, buildInputPrivacyKey, buildInputPhoto,
|
||||
} from '../gramjsBuilders';
|
||||
import { getClient, invokeRequest, uploadFile } from './client';
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
@ -79,6 +81,16 @@ export async function uploadProfilePhoto(file: File) {
|
||||
}));
|
||||
}
|
||||
|
||||
export async function deleteProfilePhoto(photo: ApiPhoto) {
|
||||
const photoId = buildInputPhoto(photo);
|
||||
if (!photoId) return false;
|
||||
const isDeleted = await invokeRequest(new GramJs.photos.DeletePhotos({ id: [photoId] }), true);
|
||||
if (isDeleted) {
|
||||
delete localDb.photos[photo.id];
|
||||
}
|
||||
return isDeleted;
|
||||
}
|
||||
|
||||
export async function fetchWallpapers() {
|
||||
const result = await invokeRequest(new GramJs.account.GetWallPapers({ hash: BigInt('0') }));
|
||||
|
||||
|
||||
@ -127,7 +127,7 @@ const Avatar: FC<OwnProps> = ({
|
||||
|
||||
const userId = user?.id;
|
||||
useEffect(() => {
|
||||
if (shouldShowVideo && !profilePhoto) {
|
||||
if (userId && shouldShowVideo && !profilePhoto) {
|
||||
loadFullUser({ userId });
|
||||
}
|
||||
}, [loadFullUser, profilePhoto, userId, shouldShowVideo]);
|
||||
|
||||
@ -29,7 +29,8 @@ export type OwnProps = {
|
||||
isSchedule: boolean;
|
||||
message: ApiMessage;
|
||||
album?: IAlbum;
|
||||
onClose: () => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
onConfirm?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
@ -48,6 +49,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
||||
contactName,
|
||||
willDeleteForCurrentUserOnly,
|
||||
willDeleteForAll,
|
||||
onConfirm,
|
||||
onClose,
|
||||
}) => {
|
||||
const {
|
||||
@ -56,14 +58,16 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const handleDeleteMessageForAll = useCallback(() => {
|
||||
onConfirm?.();
|
||||
const messageIds = album?.messages
|
||||
? album.messages.map(({ id }) => id)
|
||||
: [message.id];
|
||||
deleteMessages({ messageIds, shouldDeleteForAll: true });
|
||||
onClose();
|
||||
}, [deleteMessages, message.id, onClose, album]);
|
||||
}, [onConfirm, album, message.id, deleteMessages, onClose]);
|
||||
|
||||
const handleDeleteMessageForSelf = useCallback(() => {
|
||||
onConfirm?.();
|
||||
const messageIds = album?.messages
|
||||
? album.messages.map(({ id }) => id)
|
||||
: [message.id];
|
||||
@ -76,7 +80,7 @@ const DeleteMessageModal: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
}
|
||||
onClose();
|
||||
}, [album, message.id, isSchedule, onClose, deleteScheduledMessages, deleteMessages]);
|
||||
}, [onConfirm, album, message.id, isSchedule, onClose, deleteScheduledMessages, deleteMessages]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
|
||||
54
src/components/common/DeleteProfilePhotoModal.tsx
Normal file
54
src/components/common/DeleteProfilePhotoModal.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { useCallback, memo } from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import type { ApiPhoto } from '../../api/types';
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import Button from '../ui/Button';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
photo: ApiPhoto;
|
||||
profileId: string;
|
||||
onConfirm?: NoneToVoidFunction;
|
||||
onClose: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
const DeleteProfilePhotoModal: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
photo,
|
||||
profileId,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}) => {
|
||||
const {
|
||||
deleteProfilePhoto,
|
||||
} = getActions();
|
||||
|
||||
const handleDeletePhoto = useCallback(() => {
|
||||
onConfirm?.();
|
||||
deleteProfilePhoto({ photo, profileId });
|
||||
onClose();
|
||||
}, [onConfirm, deleteProfilePhoto, photo, profileId, onClose]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
onEnter={handleDeletePhoto}
|
||||
className="delete"
|
||||
title="Are you sure?"
|
||||
>
|
||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeletePhoto}>
|
||||
{lang('Preview.DeletePhoto')}
|
||||
</Button>
|
||||
<Button className="confirm-dialog-button" isText onClick={onClose}>{lang('Cancel')}</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DeleteProfilePhotoModal);
|
||||
@ -59,6 +59,7 @@ type StateProps = {
|
||||
mediaId?: number;
|
||||
senderId?: string;
|
||||
isChatWithSelf?: boolean;
|
||||
canDeleteMedia?: boolean;
|
||||
origin?: MediaViewerOrigin;
|
||||
avatarOwner?: ApiChat | ApiUser;
|
||||
message?: ApiMessage;
|
||||
@ -77,6 +78,7 @@ const MediaViewer: FC<StateProps> = ({
|
||||
mediaId,
|
||||
senderId,
|
||||
isChatWithSelf,
|
||||
canDeleteMedia,
|
||||
origin,
|
||||
avatarOwner,
|
||||
message,
|
||||
@ -145,6 +147,12 @@ const MediaViewer: FC<StateProps> = ({
|
||||
animationKey.current = selectedMediaIndex;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && !mediaIds.length) {
|
||||
closeMediaViewer();
|
||||
}
|
||||
}, [isOpen, closeMediaViewer, mediaIds.length]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return undefined;
|
||||
@ -279,12 +287,23 @@ const MediaViewer: FC<StateProps> = ({
|
||||
return undefined;
|
||||
}, [mediaIds]);
|
||||
|
||||
const handleBeforeDelete = useCallback(() => {
|
||||
if (mediaIds.length <= 1) {
|
||||
handleClose();
|
||||
return;
|
||||
}
|
||||
let index = mediaId ? mediaIds.indexOf(mediaId) : -1;
|
||||
// Before deleting, select previous media or the first one
|
||||
index = index > 0 ? index - 1 : 0;
|
||||
selectMedia(mediaIds[index]);
|
||||
}, [handleClose, mediaId, mediaIds, selectMedia]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
function renderSenderInfo() {
|
||||
return avatarOwner ? (
|
||||
<SenderInfo
|
||||
key={avatarOwner.id}
|
||||
key={mediaId}
|
||||
chatId={avatarOwner.id}
|
||||
isAvatar
|
||||
/>
|
||||
@ -324,8 +343,12 @@ const MediaViewer: FC<StateProps> = ({
|
||||
mediaData={fullMediaBlobUrl || previewBlobUrl}
|
||||
isVideo={isVideo}
|
||||
message={message}
|
||||
canDeleteAvatar={canDeleteMedia && !!avatarPhoto}
|
||||
avatarPhoto={avatarPhoto}
|
||||
avatarOwnerId={avatarOwner?.id}
|
||||
fileName={fileName}
|
||||
canReport={canReport}
|
||||
onBeforeDelete={handleBeforeDelete}
|
||||
onReport={openReportModal}
|
||||
onCloseMediaViewer={handleClose}
|
||||
onForward={handleForward}
|
||||
@ -377,8 +400,7 @@ export default memo(withGlobal(
|
||||
animationLevel,
|
||||
} = global.settings.byKey;
|
||||
|
||||
const { shouldSkipHistoryAnimations } = global;
|
||||
|
||||
const { shouldSkipHistoryAnimations, currentUserId } = global;
|
||||
let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);
|
||||
|
||||
if (origin === MediaViewerOrigin.SearchResult) {
|
||||
@ -405,14 +427,20 @@ export default memo(withGlobal(
|
||||
}
|
||||
|
||||
if (avatarOwnerId) {
|
||||
const sender = selectUser(global, avatarOwnerId) || selectChat(global, avatarOwnerId);
|
||||
const user = selectUser(global, avatarOwnerId);
|
||||
const chat = selectChat(global, avatarOwnerId);
|
||||
let canDeleteMedia = false;
|
||||
if (user) canDeleteMedia = avatarOwnerId === currentUserId;
|
||||
// TODO Support deleting chat photos
|
||||
// if (chat) canDeleteMedia = isChatAdmin(chat);
|
||||
isChatWithSelf = selectIsChatWithSelf(global, avatarOwnerId);
|
||||
|
||||
return {
|
||||
mediaId,
|
||||
senderId: avatarOwnerId,
|
||||
avatarOwner: sender,
|
||||
avatarOwner: user || chat,
|
||||
isChatWithSelf,
|
||||
canDeleteMedia,
|
||||
animationLevel,
|
||||
origin,
|
||||
shouldSkipHistoryAnimations,
|
||||
|
||||
@ -6,7 +6,9 @@ import React, {
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { ApiMessage } from '../../api/types';
|
||||
import type {
|
||||
ApiMessage, ApiPhoto,
|
||||
} from '../../api/types';
|
||||
import type { MessageListType } from '../../global/types';
|
||||
import type { MenuItemProps } from '../ui/MenuItem';
|
||||
|
||||
@ -29,6 +31,7 @@ import DropdownMenu from '../ui/DropdownMenu';
|
||||
import MenuItem from '../ui/MenuItem';
|
||||
import ProgressSpinner from '../ui/ProgressSpinner';
|
||||
import DeleteMessageModal from '../common/DeleteMessageModal';
|
||||
import DeleteProfilePhotoModal from '../common/DeleteProfilePhotoModal';
|
||||
|
||||
import './MediaViewerActions.scss';
|
||||
|
||||
@ -45,9 +48,13 @@ type OwnProps = {
|
||||
isVideo: boolean;
|
||||
zoomLevelChange: number;
|
||||
message?: ApiMessage;
|
||||
canDeleteAvatar?: boolean;
|
||||
avatarPhoto?: ApiPhoto;
|
||||
avatarOwnerId?: string;
|
||||
fileName?: string;
|
||||
canReport?: boolean;
|
||||
onReport: NoneToVoidFunction;
|
||||
onBeforeDelete: NoneToVoidFunction;
|
||||
onCloseMediaViewer: NoneToVoidFunction;
|
||||
onForward: NoneToVoidFunction;
|
||||
setZoomLevelChange: (change: number) => void;
|
||||
@ -57,6 +64,8 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
mediaData,
|
||||
isVideo,
|
||||
message,
|
||||
avatarPhoto,
|
||||
avatarOwnerId,
|
||||
fileName,
|
||||
isChatProtected,
|
||||
isDownloading,
|
||||
@ -67,6 +76,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
messageListType,
|
||||
onReport,
|
||||
onCloseMediaViewer,
|
||||
onBeforeDelete,
|
||||
onForward,
|
||||
setZoomLevelChange,
|
||||
}) => {
|
||||
@ -118,6 +128,28 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
}, []);
|
||||
|
||||
function renderDeleteModals() {
|
||||
return message
|
||||
? (
|
||||
<DeleteMessageModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
isSchedule={messageListType === 'scheduled'}
|
||||
onClose={closeDeleteModal}
|
||||
onConfirm={onBeforeDelete}
|
||||
message={message}
|
||||
/>
|
||||
)
|
||||
: (avatarOwnerId && avatarPhoto) ? (
|
||||
<DeleteProfilePhotoModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
onClose={closeDeleteModal}
|
||||
onConfirm={onBeforeDelete}
|
||||
profileId={avatarOwnerId}
|
||||
photo={avatarPhoto}
|
||||
/>
|
||||
) : undefined;
|
||||
}
|
||||
|
||||
function renderDownloadButton() {
|
||||
if (isProtected) {
|
||||
return undefined;
|
||||
@ -218,14 +250,7 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
))}
|
||||
</DropdownMenu>
|
||||
{isDownloading && <ProgressSpinner progress={downloadProgress} size="s" noCross />}
|
||||
{message && canDelete && (
|
||||
<DeleteMessageModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
isSchedule={messageListType === 'scheduled'}
|
||||
onClose={closeDeleteModal}
|
||||
message={message}
|
||||
/>
|
||||
)}
|
||||
{canDelete && renderDeleteModals()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -293,26 +318,21 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<i className="icon-close" />
|
||||
</Button>
|
||||
{message && canDelete && (
|
||||
<DeleteMessageModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
isSchedule={messageListType === 'scheduled'}
|
||||
onClose={closeDeleteModal}
|
||||
message={message}
|
||||
/>
|
||||
)}
|
||||
{canDelete && renderDeleteModals()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { message }): StateProps => {
|
||||
(global, { message, canDeleteAvatar }): StateProps => {
|
||||
const currentMessageList = selectCurrentMessageList(global);
|
||||
const { threadId } = selectCurrentMessageList(global) || {};
|
||||
const isDownloading = message ? selectIsDownloading(global, message) : false;
|
||||
const isProtected = selectIsMessageProtected(global, message);
|
||||
const isChatProtected = message && selectIsChatProtected(global, message?.chatId);
|
||||
const { canDelete } = (threadId && message && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||
const { canDelete: canDeleteMessage } = (threadId
|
||||
&& message && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||
const canDelete = canDeleteMessage || canDeleteAvatar;
|
||||
const messageListType = currentMessageList?.type;
|
||||
|
||||
return {
|
||||
|
||||
@ -45,11 +45,11 @@ const AvatarEditable: FC<OwnProps> = ({
|
||||
setSelectedFile(undefined);
|
||||
onChange(croppedImg);
|
||||
|
||||
if (croppedBlobUrl) {
|
||||
if (croppedBlobUrl && croppedBlobUrl !== currentAvatarBlobUrl) {
|
||||
URL.revokeObjectURL(croppedBlobUrl);
|
||||
}
|
||||
setCroppedBlobUrl(URL.createObjectURL(croppedImg));
|
||||
}, [croppedBlobUrl, onChange]);
|
||||
}, [croppedBlobUrl, currentAvatarBlobUrl, onChange]);
|
||||
|
||||
const handleModalClose = useCallback(() => {
|
||||
setSelectedFile(undefined);
|
||||
|
||||
@ -39,7 +39,10 @@ addActionHandler('updateProfile', async (global, actions, payload) => {
|
||||
});
|
||||
|
||||
if (photo) {
|
||||
await callApi('updateProfilePhoto', photo);
|
||||
const result = await callApi('updateProfilePhoto', photo);
|
||||
if (result) {
|
||||
actions.loadProfilePhotos({ profileId: currentUserId });
|
||||
}
|
||||
}
|
||||
|
||||
if (firstName || lastName || about) {
|
||||
@ -80,6 +83,18 @@ addActionHandler('updateProfile', async (global, actions, payload) => {
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('deleteProfilePhoto', async (global, actions, payload) => {
|
||||
const { photo, profileId } = payload;
|
||||
const result = await callApi('deleteProfilePhoto', photo);
|
||||
if (!result) return;
|
||||
if (isUserId(profileId)) {
|
||||
actions.loadFullUser({ userId: profileId });
|
||||
} else {
|
||||
actions.loadFullChat({ chatId: profileId });
|
||||
}
|
||||
actions.loadProfilePhotos({ profileId });
|
||||
});
|
||||
|
||||
addActionHandler('checkUsername', async (global, actions, payload) => {
|
||||
const { username } = payload!;
|
||||
|
||||
|
||||
@ -736,18 +736,19 @@ export interface ActionPayloads {
|
||||
type?: MessageListType;
|
||||
shouldReplaceHistory?: boolean;
|
||||
};
|
||||
|
||||
loadFullChat: {
|
||||
chatId: string;
|
||||
force?: boolean;
|
||||
};
|
||||
openChatWithDraft: {
|
||||
chatId?: string;
|
||||
text: string;
|
||||
};
|
||||
resetOpenChatWithDraft: never;
|
||||
|
||||
toggleJoinToSend: {
|
||||
chatId: string;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
|
||||
toggleJoinRequest: {
|
||||
chatId: string;
|
||||
isEnabled: boolean;
|
||||
@ -857,6 +858,9 @@ export interface ActionPayloads {
|
||||
};
|
||||
|
||||
// Users
|
||||
loadFullUser: {
|
||||
userId: string;
|
||||
};
|
||||
openAddContactDialog: {
|
||||
userId?: string;
|
||||
};
|
||||
@ -874,6 +878,13 @@ export interface ActionPayloads {
|
||||
isMuted?: boolean;
|
||||
shouldSharePhoneNumber?: boolean;
|
||||
};
|
||||
loadProfilePhotos: {
|
||||
profileId: string;
|
||||
};
|
||||
deleteProfilePhoto: {
|
||||
profileId: string;
|
||||
photo: ApiPhoto;
|
||||
};
|
||||
|
||||
// Forwards
|
||||
openForwardMenu: {
|
||||
@ -1222,11 +1233,11 @@ export type NonTypedActionNames = (
|
||||
// chats
|
||||
'preloadTopChatMessages' | 'loadAllChats' | 'openChatWithInfo' | 'openLinkedChat' |
|
||||
'openSupportChat' | 'focusMessageInComments' | 'openChatByPhoneNumber' |
|
||||
'loadChatSettings' | 'loadFullChat' | 'loadTopChats' | 'requestChatUpdate' | 'updateChatMutedState' |
|
||||
'loadChatSettings' | 'loadTopChats' | 'requestChatUpdate' | 'updateChatMutedState' |
|
||||
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
|
||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
||||
'updateChat' | 'toggleSignatures' | 'loadGroupsForDiscussion' | 'linkDiscussionGroup' | 'unlinkDiscussionGroup' |
|
||||
'loadProfilePhotos' | 'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' | 'setChatEnabledReactions' |
|
||||
'loadMoreMembers' | 'setActiveChatFolder' | 'openNextChat' | 'setChatEnabledReactions' |
|
||||
'addChatMembers' | 'deleteChatMember' | 'openPreviousChat' | 'editChatFolders' | 'toggleIsProtected' |
|
||||
// messages
|
||||
'loadViewportMessages' | 'selectMessage' | 'sendMessage' | 'cancelSendingMessage' | 'pinMessage' | 'deleteMessages' |
|
||||
@ -1261,7 +1272,7 @@ export type NonTypedActionNames = (
|
||||
'togglePreHistoryHidden' | 'updateChatDefaultBannedRights' | 'updateChatMemberBannedRights' | 'updateChatAdmin' |
|
||||
'acceptInviteConfirmation' |
|
||||
// users
|
||||
'loadFullUser' | 'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' |
|
||||
'loadNearestCountry' | 'loadTopUsers' | 'loadContactList' |
|
||||
'loadCurrentUser' | 'updateProfile' | 'checkUsername' |
|
||||
'deleteContact' | 'loadUser' | 'setUserSearchQuery' | 'loadCommonChats' | 'reportSpam' |
|
||||
// chat creation
|
||||
|
||||
@ -1233,6 +1233,7 @@ updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||
photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo;
|
||||
photos.deletePhotos#87cf7f2f id:Vector<InputPhoto> = Vector<long>;
|
||||
photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos;
|
||||
upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
|
||||
upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File;
|
||||
|
||||
@ -164,6 +164,7 @@
|
||||
"updates.getChannelDifference",
|
||||
"photos.uploadProfilePhoto",
|
||||
"photos.getUserPhotos",
|
||||
"photos.deletePhotos",
|
||||
"upload.saveFilePart",
|
||||
"upload.getFile",
|
||||
"upload.saveBigFilePart",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user