Support "Report Peer" and "Report Profile Photo" (#126)

This commit is contained in:
Vadym Milichev 2022-04-09 02:23:35 +03:00 committed by GitHub
parent a7e2695f2e
commit 91c2015052
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 321 additions and 28 deletions

View File

@ -11,7 +11,9 @@ import {
ApiGroupCall, ApiGroupCall,
ApiMessageEntity, ApiMessageEntity,
ApiMessageEntityTypes, ApiMessageEntityTypes,
ApiNewPoll, ApiPhoneCall, ApiNewPoll,
ApiPhoto,
ApiPhoneCall,
ApiReportReason, ApiReportReason,
ApiSendMessageAction, ApiSendMessageAction,
ApiSticker, ApiSticker,
@ -343,6 +345,20 @@ export function buildChatPhotoForLocalDb(photo: GramJs.TypePhoto) {
}); });
} }
export function buildInputPhoto(photo: ApiPhoto) {
const localPhoto = localDb.photos[photo?.id];
if (!localPhoto) {
return undefined;
}
return new GramJs.InputPhoto(pick(localPhoto, [
'id',
'accessHash',
'fileReference',
]));
}
export function buildInputContact({ export function buildInputContact({
phone, phone,
firstName, firstName,

View File

@ -0,0 +1,43 @@
import {
ApiChat, ApiPhoto, ApiReportReason, ApiUser,
} from '../../types';
import { invokeRequest } from './client';
import { Api as GramJs } from '../../../lib/gramjs';
import { buildInputPeer, buildInputReportReason, buildInputPhoto } from '../gramjsBuilders';
export async function reportPeer({
peer,
reason,
description,
}: {
peer: ApiChat | ApiUser; reason: ApiReportReason; description?: string;
}) {
const result = await invokeRequest(new GramJs.account.ReportPeer({
peer: buildInputPeer(peer.id, peer.accessHash),
reason: buildInputReportReason(reason),
message: description,
}));
return result;
}
export async function reportProfilePhoto({
peer,
photo,
reason,
description,
}: {
peer: ApiChat | ApiUser; photo: ApiPhoto; reason: ApiReportReason; description?: string;
}) {
const photoId = buildInputPhoto(photo);
if (!photoId) return undefined;
const result = await invokeRequest(new GramJs.account.ReportProfilePhoto({
peer: buildInputPeer(peer.id, peer.accessHash),
photoId,
reason: buildInputReportReason(reason),
message: description,
}));
return result;
}

View File

@ -2,6 +2,10 @@ export {
destroy, disconnect, downloadMedia, fetchCurrentUser, repairFileReference, destroy, disconnect, downloadMedia, fetchCurrentUser, repairFileReference,
} from './client'; } from './client';
export {
reportPeer, reportProfilePhoto,
} from './account';
export { export {
provideAuthPhoneNumber, provideAuthCode, provideAuthPassword, provideAuthRegistration, restartAuth, restartAuthWithQr, provideAuthPhoneNumber, provideAuthCode, provideAuthPassword, provideAuthRegistration, restartAuth, restartAuthWithQr,
} from './auth'; } from './auth';

View File

@ -1,11 +1,11 @@
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import React, { import React, {
FC, memo, useCallback, useState, FC, memo, useCallback, useMemo, useState,
} from '../../lib/teact/teact'; } from '../../lib/teact/teact';
import { getActions } from '../../global'; import { getActions } from '../../global';
import { ApiReportReason } from '../../api/types'; import { ApiPhoto, ApiReportReason } from '../../api/types';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -16,17 +16,27 @@ import InputText from '../ui/InputText';
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
subject?: 'peer' | 'messages' | 'media';
chatId?: string;
photo?: ApiPhoto;
messageIds?: number[]; messageIds?: number[];
onClose: () => void; onClose: () => void;
onCloseAnimationEnd?: () => void;
}; };
const ReportMessageModal: FC<OwnProps> = ({ const ReportModal: FC<OwnProps> = ({
isOpen, isOpen,
subject = 'messages',
chatId,
photo,
messageIds, messageIds,
onClose, onClose,
onCloseAnimationEnd,
}) => { }) => {
const { const {
reportMessages, reportMessages,
reportPeer,
reportProfilePhoto,
exitMessageSelectMode, exitMessageSelectMode,
} = getActions(); } = getActions();
@ -34,10 +44,34 @@ const ReportMessageModal: FC<OwnProps> = ({
const [description, setDescription] = useState(''); const [description, setDescription] = useState('');
const handleReport = useCallback(() => { const handleReport = useCallback(() => {
reportMessages({ messageIds, reason: selectedReason, description }); switch (subject) {
exitMessageSelectMode(); case 'messages':
reportMessages({ messageIds, reason: selectedReason, description });
exitMessageSelectMode();
break;
case 'peer':
reportPeer({ chatId, reason: selectedReason, description });
break;
case 'media':
reportProfilePhoto({
chatId, photo, reason: selectedReason, description,
});
break;
}
onClose(); onClose();
}, [description, exitMessageSelectMode, messageIds, onClose, reportMessages, selectedReason]); }, [
description,
exitMessageSelectMode,
messageIds,
photo,
onClose,
reportMessages,
selectedReason,
chatId,
reportProfilePhoto,
reportPeer,
subject,
]);
const handleSelectReason = useCallback((value: string) => { const handleSelectReason = useCallback((value: string) => {
setSelectedReason(value as ApiReportReason); setSelectedReason(value as ApiReportReason);
@ -49,7 +83,7 @@ const ReportMessageModal: FC<OwnProps> = ({
const lang = useLang(); const lang = useLang();
const REPORT_OPTIONS: { value: ApiReportReason; label: string }[] = [ const REPORT_OPTIONS: { value: ApiReportReason; label: string }[] = useMemo(() => [
{ value: 'spam', label: lang('lng_report_reason_spam') }, { value: 'spam', label: lang('lng_report_reason_spam') },
{ value: 'violence', label: lang('lng_report_reason_violence') }, { value: 'violence', label: lang('lng_report_reason_violence') },
{ value: 'pornography', label: lang('lng_report_reason_pornography') }, { value: 'pornography', label: lang('lng_report_reason_pornography') },
@ -58,19 +92,28 @@ const ReportMessageModal: FC<OwnProps> = ({
{ value: 'illegalDrugs', label: 'Illegal Drugs' }, { value: 'illegalDrugs', label: 'Illegal Drugs' },
{ value: 'personalDetails', label: 'Personal Details' }, { value: 'personalDetails', label: 'Personal Details' },
{ value: 'other', label: lang('lng_report_reason_other') }, { value: 'other', label: lang('lng_report_reason_other') },
]; ], [lang]);
if (!messageIds) { if (
(subject === 'messages' && !messageIds)
|| (subject === 'peer' && !chatId)
|| (subject === 'media' && (!chatId || !photo))
) {
return undefined; return undefined;
} }
const title = subject === 'messages'
? lang('lng_report_message_title')
: lang('ReportPeer.Report');
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
onEnter={isOpen ? handleReport : undefined} onEnter={isOpen ? handleReport : undefined}
onCloseAnimationEnd={onCloseAnimationEnd}
className="report" className="report"
title={lang('lng_report_message_title')} title={title}
> >
<RadioGroup <RadioGroup
name="report-message" name="report-message"
@ -91,4 +134,4 @@ const ReportMessageModal: FC<OwnProps> = ({
); );
}; };
export default memo(ReportMessageModal); export default memo(ReportModal);

View File

@ -50,6 +50,7 @@ import ListItem from '../../ui/ListItem';
import Badge from './Badge'; import Badge from './Badge';
import ChatFolderModal from '../ChatFolderModal.async'; import ChatFolderModal from '../ChatFolderModal.async';
import ChatCallStatus from './ChatCallStatus'; import ChatCallStatus from './ChatCallStatus';
import ReportModal from '../../common/ReportModal';
import FakeIcon from '../../common/FakeIcon'; import FakeIcon from '../../common/FakeIcon';
import './Chat.scss'; import './Chat.scss';
@ -116,8 +117,10 @@ const Chat: FC<OwnProps & StateProps> = ({
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag(); const [isChatFolderModalOpen, openChatFolderModal, closeChatFolderModal] = useFlag();
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag(); const [shouldRenderDeleteModal, markRenderDeleteModal, unmarkRenderDeleteModal] = useFlag();
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag(); const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
const [shouldRenderReportModal, markRenderReportModal, unmarkRenderReportModal] = useFlag();
const { lastMessage, typingStatus } = chat || {}; const { lastMessage, typingStatus } = chat || {};
const isAction = lastMessage && isActionMessage(lastMessage); const isAction = lastMessage && isActionMessage(lastMessage);
@ -190,21 +193,27 @@ const Chat: FC<OwnProps & StateProps> = ({
focusLastMessage, focusLastMessage,
]); ]);
function handleDelete() { const handleDelete = useCallback(() => {
markRenderDeleteModal(); markRenderDeleteModal();
openDeleteModal(); openDeleteModal();
} }, [markRenderDeleteModal, openDeleteModal]);
function handleChatFolderChange() { const handleChatFolderChange = useCallback(() => {
markRenderChatFolderModal(); markRenderChatFolderModal();
openChatFolderModal(); openChatFolderModal();
} }, [markRenderChatFolderModal, openChatFolderModal]);
const handleReport = useCallback(() => {
markRenderReportModal();
openReportModal();
}, [markRenderReportModal, openReportModal]);
const contextActions = useChatContextActions({ const contextActions = useChatContextActions({
chat, chat,
user, user,
handleDelete, handleDelete,
handleChatFolderChange, handleChatFolderChange,
handleReport,
folderId, folderId,
isPinned, isPinned,
isMuted, isMuted,
@ -330,6 +339,15 @@ const Chat: FC<OwnProps & StateProps> = ({
chatId={chatId} chatId={chatId}
/> />
)} )}
{shouldRenderReportModal && (
<ReportModal
isOpen={isReportModalOpen}
onClose={closeReportModal}
onCloseAnimationEnd={unmarkRenderReportModal}
chatId={chatId}
subject="peer"
/>
)}
</ListItem> </ListItem>
); );
}; };

View File

@ -16,6 +16,7 @@ import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck'
import useHistoryBack from '../../hooks/useHistoryBack'; import useHistoryBack from '../../hooks/useHistoryBack';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
import useMedia from '../../hooks/useMedia'; import useMedia from '../../hooks/useMedia';
import useFlag from '../../hooks/useFlag';
import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress'; import useMediaWithLoadProgress from '../../hooks/useMediaWithLoadProgress';
import usePrevious from '../../hooks/usePrevious'; import usePrevious from '../../hooks/usePrevious';
import { import {
@ -40,6 +41,7 @@ import {
selectChatMessage, selectChatMessage,
selectChatMessages, selectChatMessages,
selectCurrentMediaSearch, selectCurrentMediaSearch,
selectIsChatWithSelf,
selectListedIds, selectListedIds,
selectOutlyingIds, selectOutlyingIds,
selectScheduledMessage, selectScheduledMessage,
@ -63,6 +65,7 @@ import PanZoom from './PanZoom';
import SenderInfo from './SenderInfo'; import SenderInfo from './SenderInfo';
import SlideTransition from './SlideTransition'; import SlideTransition from './SlideTransition';
import ZoomControls from './ZoomControls'; import ZoomControls from './ZoomControls';
import ReportModal from '../common/ReportModal';
import './MediaViewer.scss'; import './MediaViewer.scss';
@ -71,6 +74,7 @@ type StateProps = {
threadId?: number; threadId?: number;
messageId?: number; messageId?: number;
senderId?: string; senderId?: string;
isChatWithSelf?: boolean;
origin?: MediaViewerOrigin; origin?: MediaViewerOrigin;
avatarOwner?: ApiChat | ApiUser; avatarOwner?: ApiChat | ApiUser;
profilePhotoIndex?: number; profilePhotoIndex?: number;
@ -87,6 +91,7 @@ const MediaViewer: FC<StateProps> = ({
threadId, threadId,
messageId, messageId,
senderId, senderId,
isChatWithSelf,
origin, origin,
avatarOwner, avatarOwner,
profilePhotoIndex, profilePhotoIndex,
@ -144,6 +149,7 @@ const MediaViewer: FC<StateProps> = ({
const isGhostAnimation = animationLevel === 2; const isGhostAnimation = animationLevel === 2;
/* Controls */ /* Controls */
const [isReportModalOpen, openReportModal, closeReportModal] = useFlag();
const [canPanZoomWrap, setCanPanZoomWrap] = useState(false); const [canPanZoomWrap, setCanPanZoomWrap] = useState(false);
const [isZoomed, setIsZoomed] = useState<boolean>(false); const [isZoomed, setIsZoomed] = useState<boolean>(false);
const [zoomLevel, setZoomLevel] = useState<number>(1); const [zoomLevel, setZoomLevel] = useState<number>(1);
@ -186,6 +192,8 @@ const MediaViewer: FC<StateProps> = ({
undefined, undefined,
isGhostAnimation && ANIMATION_DURATION, isGhostAnimation && ANIMATION_DURATION,
); );
const avatarPhoto = avatarOwner?.photos?.[profilePhotoIndex!];
const canReport = !!avatarPhoto && profilePhotoIndex! > 0 && !isChatWithSelf;
const localBlobUrl = (photo || video) ? (photo || video)!.blobUrl : undefined; const localBlobUrl = (photo || video) ? (photo || video)!.blobUrl : undefined;
let bestImageData = (!isVideo && (localBlobUrl || fullMediaBlobUrl)) || previewBlobUrl || pictogramBlobUrl; let bestImageData = (!isVideo && (localBlobUrl || fullMediaBlobUrl)) || previewBlobUrl || pictogramBlobUrl;
@ -483,11 +491,20 @@ const MediaViewer: FC<StateProps> = ({
isZoomed={isZoomed} isZoomed={isZoomed}
message={message} message={message}
fileName={fileName} fileName={fileName}
canReport={canReport}
onReport={openReportModal}
onCloseMediaViewer={close} onCloseMediaViewer={close}
onForward={handleForward} onForward={handleForward}
onZoomToggle={handleZoomToggle} onZoomToggle={handleZoomToggle}
isAvatar={isAvatar} isAvatar={isAvatar}
/> />
<ReportModal
isOpen={isReportModalOpen}
onClose={closeReportModal}
subject="media"
photo={avatarPhoto}
chatId={avatarOwner?.id}
/>
</div> </div>
<PanZoom <PanZoom
noWrap={!canPanZoomWrap} noWrap={!canPanZoomWrap}
@ -565,6 +582,8 @@ export default memo(withGlobal(
animationLevel, animationLevel,
} = global.settings.byKey; } = global.settings.byKey;
let isChatWithSelf = !!chatId && selectIsChatWithSelf(global, chatId);
if (origin === MediaViewerOrigin.SearchResult) { if (origin === MediaViewerOrigin.SearchResult) {
if (!(chatId && messageId)) { if (!(chatId && messageId)) {
return { animationLevel }; return { animationLevel };
@ -579,6 +598,7 @@ export default memo(withGlobal(
chatId, chatId,
messageId, messageId,
senderId: message.senderId, senderId: message.senderId,
isChatWithSelf,
origin, origin,
message, message,
animationLevel, animationLevel,
@ -587,11 +607,13 @@ export default memo(withGlobal(
if (avatarOwnerId) { if (avatarOwnerId) {
const sender = selectUser(global, avatarOwnerId) || selectChat(global, avatarOwnerId); const sender = selectUser(global, avatarOwnerId) || selectChat(global, avatarOwnerId);
isChatWithSelf = selectIsChatWithSelf(global, avatarOwnerId);
return { return {
messageId: -1, messageId: -1,
senderId: avatarOwnerId, senderId: avatarOwnerId,
avatarOwner: sender, avatarOwner: sender,
isChatWithSelf,
profilePhotoIndex: profilePhotoIndex || 0, profilePhotoIndex: profilePhotoIndex || 0,
animationLevel, animationLevel,
origin, origin,
@ -635,6 +657,7 @@ export default memo(withGlobal(
threadId, threadId,
messageId, messageId,
senderId: message.senderId, senderId: message.senderId,
isChatWithSelf,
origin, origin,
message, message,
chatMessages, chatMessages,

View File

@ -33,6 +33,8 @@ type OwnProps = {
message?: ApiMessage; message?: ApiMessage;
fileName?: string; fileName?: string;
isAvatar?: boolean; isAvatar?: boolean;
canReport?: boolean;
onReport: NoneToVoidFunction;
onCloseMediaViewer: NoneToVoidFunction; onCloseMediaViewer: NoneToVoidFunction;
onForward: NoneToVoidFunction; onForward: NoneToVoidFunction;
onZoomToggle: NoneToVoidFunction; onZoomToggle: NoneToVoidFunction;
@ -47,6 +49,8 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
isAvatar, isAvatar,
isDownloading, isDownloading,
isProtected, isProtected,
canReport,
onReport,
onCloseMediaViewer, onCloseMediaViewer,
onForward, onForward,
onZoomToggle, onZoomToggle,
@ -154,6 +158,14 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
{lang('AccActionDownload')} {lang('AccActionDownload')}
</MenuItem> </MenuItem>
)} )}
{canReport && (
<MenuItem
icon="flag"
onClick={onReport}
>
{lang('ReportPeer.Report')}
</MenuItem>
)}
</DropdownMenu> </DropdownMenu>
{isDownloading && <ProgressSpinner progress={downloadProgress} size="s" noCross />} {isDownloading && <ProgressSpinner progress={downloadProgress} size="s" noCross />}
</div> </div>
@ -183,6 +195,17 @@ const MediaViewerActions: FC<OwnProps & StateProps> = ({
> >
<i className={isZoomed ? 'icon-zoom-out' : 'icon-zoom-in'} /> <i className={isZoomed ? 'icon-zoom-out' : 'icon-zoom-in'} />
</Button> </Button>
{canReport && (
<Button
round
size="smaller"
color="translucent-white"
ariaLabel={lang(isVideo ? 'PeerInfo.ReportProfileVideo' : 'PeerInfo.ReportProfilePhoto')}
onClick={onReport}
>
<i className="icon-flag" />
</Button>
)}
<Button <Button
round round
size="smaller" size="smaller"

View File

@ -13,7 +13,7 @@ import {
selectChat, selectNotifySettings, selectNotifyExceptions, selectUser, selectChatBot, selectChat, selectNotifySettings, selectNotifyExceptions, selectUser, selectChatBot,
} from '../../global/selectors'; } from '../../global/selectors';
import { import {
isUserId, getCanDeleteChat, selectIsChatMuted, getCanAddContact, isUserId, getCanDeleteChat, selectIsChatMuted, getCanAddContact, isChatChannel, isChatGroup,
} from '../../global/helpers'; } from '../../global/helpers';
import useShowTransition from '../../hooks/useShowTransition'; import useShowTransition from '../../hooks/useShowTransition';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -22,6 +22,7 @@ import Portal from '../ui/Portal';
import Menu from '../ui/Menu'; import Menu from '../ui/Menu';
import MenuItem from '../ui/MenuItem'; import MenuItem from '../ui/MenuItem';
import DeleteChatModal from '../common/DeleteChatModal'; import DeleteChatModal from '../common/DeleteChatModal';
import ReportModal from '../common/ReportModal';
import './HeaderMenuContainer.scss'; import './HeaderMenuContainer.scss';
@ -69,6 +70,7 @@ type StateProps = {
isPrivate?: boolean; isPrivate?: boolean;
isMuted?: boolean; isMuted?: boolean;
canAddContact?: boolean; canAddContact?: boolean;
canReportChat?: boolean;
canDeleteChat?: boolean; canDeleteChat?: boolean;
hasLinkedChat?: boolean; hasLinkedChat?: boolean;
}; };
@ -93,6 +95,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
chat, chat,
isPrivate, isPrivate,
isMuted, isMuted,
canReportChat,
canDeleteChat, canDeleteChat,
hasLinkedChat, hasLinkedChat,
canAddContact, canAddContact,
@ -115,10 +118,21 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
} = getActions(); } = getActions();
const [isMenuOpen, setIsMenuOpen] = useState(true); const [isMenuOpen, setIsMenuOpen] = useState(true);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
const { x, y } = anchor; const { x, y } = anchor;
useShowTransition(isOpen, onCloseAnimationEnd, undefined, false); useShowTransition(isOpen, onCloseAnimationEnd, undefined, false);
const handleReport = useCallback(() => {
setIsMenuOpen(false);
setIsReportModalOpen(true);
}, []);
const closeReportModal = useCallback(() => {
setIsReportModalOpen(false);
onClose();
}, [onClose]);
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
setIsMenuOpen(false); setIsMenuOpen(false);
setIsDeleteModalOpen(true); setIsDeleteModalOpen(true);
@ -334,6 +348,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
{lang('Statistics')} {lang('Statistics')}
</MenuItem> </MenuItem>
)} )}
{canReportChat && (
<MenuItem
icon="flag"
onClick={handleReport}
>
{lang('ReportPeer.Report')}
</MenuItem>
)}
{botButtons} {botButtons}
{canLeave && ( {canLeave && (
<MenuItem <MenuItem
@ -354,6 +376,14 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
chat={chat} chat={chat}
/> />
)} )}
{canReportChat && chat?.id && (
<ReportModal
isOpen={isReportModalOpen}
onClose={closeReportModal}
subject="peer"
chatId={chat.id}
/>
)}
</div> </div>
</Portal> </Portal>
); );
@ -368,6 +398,7 @@ export default memo(withGlobal<OwnProps>(
const isPrivate = isUserId(chat.id); const isPrivate = isUserId(chat.id);
const user = isPrivate ? selectUser(global, chatId) : undefined; const user = isPrivate ? selectUser(global, chatId) : undefined;
const canAddContact = user && getCanAddContact(user); const canAddContact = user && getCanAddContact(user);
const canReportChat = isChatChannel(chat) || isChatGroup(chat) || (user && !user.isSelf);
const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined; const chatBot = chatId !== REPLIES_USER_ID ? selectChatBot(global, chatId) : undefined;
@ -376,6 +407,7 @@ export default memo(withGlobal<OwnProps>(
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)), isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
isPrivate, isPrivate,
canAddContact, canAddContact,
canReportChat,
canDeleteChat: getCanDeleteChat(chat), canDeleteChat: getCanDeleteChat(chat),
hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId), hasLinkedChat: Boolean(chat?.fullInfo?.linkedChatId),
botCommands: chatBot?.fullInfo?.botCommands, botCommands: chatBot?.fullInfo?.botCommands,

View File

@ -23,7 +23,7 @@ import useCopySelectedMessages from './hooks/useCopySelectedMessages';
import Button from '../ui/Button'; import Button from '../ui/Button';
import DeleteSelectedMessageModal from './DeleteSelectedMessageModal'; import DeleteSelectedMessageModal from './DeleteSelectedMessageModal';
import ReportMessageModal from '../common/ReportMessageModal'; import ReportModal from '../common/ReportModal';
import './MessageSelectToolbar.scss'; import './MessageSelectToolbar.scss';
@ -157,7 +157,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
isSchedule={isSchedule} isSchedule={isSchedule}
onClose={closeDeleteModal} onClose={closeDeleteModal}
/> />
<ReportMessageModal <ReportModal
isOpen={isReportModalOpen} isOpen={isReportModalOpen}
onClose={closeReportModal} onClose={closeReportModal}
messageIds={selectedMessageIds} messageIds={selectedMessageIds}

View File

@ -25,7 +25,7 @@ import useFlag from '../../../hooks/useFlag';
import { REM } from '../../common/helpers/mediaDimensions'; import { REM } from '../../common/helpers/mediaDimensions';
import DeleteMessageModal from '../../common/DeleteMessageModal'; import DeleteMessageModal from '../../common/DeleteMessageModal';
import ReportMessageModal from '../../common/ReportMessageModal'; import ReportModal from '../../common/ReportModal';
import PinMessageModal from '../../common/PinMessageModal'; import PinMessageModal from '../../common/PinMessageModal';
import MessageContextMenu from './MessageContextMenu'; import MessageContextMenu from './MessageContextMenu';
import CalendarModal from '../../common/CalendarModal'; import CalendarModal from '../../common/CalendarModal';
@ -396,7 +396,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
album={album} album={album}
message={message} message={message}
/> />
<ReportMessageModal <ReportModal
isOpen={isReportModalOpen} isOpen={isReportModalOpen}
onClose={closeReportModal} onClose={closeReportModal}
messageIds={reportMessageIds} messageIds={reportMessageIds}

View File

@ -19,6 +19,7 @@ import './api/globalSearch';
import './api/localSearch'; import './api/localSearch';
import './api/management'; import './api/management';
import './api/sync'; import './api/sync';
import './api/accounts';
import './api/users'; import './api/users';
import './api/bots'; import './api/bots';
import './api/settings'; import './api/settings';

View File

@ -0,0 +1,62 @@
import { addActionHandler } from '../../index';
import { selectChat } from '../../selectors';
import { callApi } from '../../../api/gramjs';
import { getTranslation } from '../../../util/langProvider';
addActionHandler('reportPeer', async (global, actions, payload) => {
const {
chatId,
reason,
description,
} = payload;
if (!chatId) {
return;
}
const chat = selectChat(global, chatId)!;
if (!chat) {
return;
}
const result = await callApi('reportPeer', {
peer: chat,
reason,
description,
});
actions.showNotification({
message: result
? getTranslation('ReportPeer.AlertSuccess')
: 'An error occurred while submitting your report. Please, try again later.',
});
});
addActionHandler('reportProfilePhoto', async (global, actions, payload) => {
const {
chatId,
reason,
description,
photo,
} = payload;
if (!chatId) {
return;
}
const chat = selectChat(global, chatId)!;
if (!chat || !photo) {
return;
}
const result = await callApi('reportProfilePhoto', {
peer: chat,
photo,
reason,
description,
});
actions.showNotification({
message: result
? getTranslation('ReportPeer.AlertSuccess')
: 'An error occurred while submitting your report. Please, try again later.',
});
});

View File

@ -67,6 +67,7 @@ import {
} from '../../selectors'; } from '../../selectors';
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers'; import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
import { isServiceNotificationMessage } from '../../helpers'; import { isServiceNotificationMessage } from '../../helpers';
import { getTranslation } from '../../../util/langProvider';
const uploadProgressCallbacks = new Map<number, ApiOnProgress>(); const uploadProgressCallbacks = new Map<number, ApiOnProgress>();
@ -460,8 +461,8 @@ addActionHandler('reportMessages', async (global, actions, payload) => {
actions.showNotification({ actions.showNotification({
message: result message: result
? 'Thank you! Your report will be reviewed by our team.' ? getTranslation('ReportPeer.AlertSuccess')
: 'Error occured while submiting report. Please, try again later.', : 'An error occurred while submitting your report. Please, try again later.',
}); });
}); });

View File

@ -30,6 +30,8 @@ import {
ApiGroupStatistics, ApiGroupStatistics,
ApiPaymentFormNativeParams, ApiPaymentFormNativeParams,
ApiUpdate, ApiUpdate,
ApiReportReason,
ApiPhoto,
ApiKeyboardButton, ApiKeyboardButton,
ApiPhoneCall, ApiPhoneCall,
} from '../api/types'; } from '../api/types';
@ -552,6 +554,19 @@ export interface ActionPayloads {
signOut: { forceInitApi?: boolean } | undefined; signOut: { forceInitApi?: boolean } | undefined;
apiUpdate: ApiUpdate; apiUpdate: ApiUpdate;
// Accounts
reportPeer: {
chatId?: string;
reason: ApiReportReason;
description: string;
};
reportProfilePhoto: {
chatId?: string;
reason: ApiReportReason;
description: string;
photo?: ApiPhoto;
};
// Chats // Chats
openChat: { openChat: {
id: string | undefined; id: string | undefined;

View File

@ -5,7 +5,7 @@ import { ApiChat, ApiUser } from '../api/types';
import { SERVICE_NOTIFICATIONS_USER_ID } from '../config'; import { SERVICE_NOTIFICATIONS_USER_ID } from '../config';
import { import {
isChatArchived, getCanDeleteChat, isUserId, isChatChannel, isChatArchived, getCanDeleteChat, isUserId, isChatChannel, isChatGroup,
} from '../global/helpers'; } from '../global/helpers';
import { compact } from '../util/iteratees'; import { compact } from '../util/iteratees';
import useLang from './useLang'; import useLang from './useLang';
@ -19,6 +19,7 @@ const useChatContextActions = ({
canChangeFolder, canChangeFolder,
handleDelete, handleDelete,
handleChatFolderChange, handleChatFolderChange,
handleReport,
}: { }: {
chat: ApiChat | undefined; chat: ApiChat | undefined;
user: ApiUser | undefined; user: ApiUser | undefined;
@ -28,6 +29,7 @@ const useChatContextActions = ({
canChangeFolder?: boolean; canChangeFolder?: boolean;
handleDelete: () => void; handleDelete: () => void;
handleChatFolderChange: () => void; handleChatFolderChange: () => void;
handleReport?: () => void;
}, isInSearch = false) => { }, isInSearch = false) => {
const lang = useLang(); const lang = useLang();
@ -84,6 +86,11 @@ const useChatContextActions = ({
? { title: lang('Unarchive'), icon: 'unarchive', handler: () => toggleChatArchived({ id: chat.id }) } ? { title: lang('Unarchive'), icon: 'unarchive', handler: () => toggleChatArchived({ id: chat.id }) }
: { title: lang('Archive'), icon: 'archive', handler: () => toggleChatArchived({ id: chat.id }) }; : { title: lang('Archive'), icon: 'archive', handler: () => toggleChatArchived({ id: chat.id }) };
const canReport = handleReport && (isChatChannel(chat) || isChatGroup(chat) || (user && !user.isSelf));
const actionReport = canReport
? { title: lang('ReportPeer.Report'), icon: 'flag', handler: handleReport }
: undefined;
const actionDelete = { const actionDelete = {
title: isUserId(chat.id) title: isUserId(chat.id)
? lang('Delete') ? lang('Delete')
@ -103,11 +110,12 @@ const useChatContextActions = ({
actionPin, actionPin,
!isSelf && actionMute, !isSelf && actionMute,
!isSelf && !isServiceNotifications && !isInFolder && actionArchive, !isSelf && !isServiceNotifications && !isInFolder && actionArchive,
actionReport,
actionDelete, actionDelete,
]); ]);
}, [ }, [
chat, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted, handleDelete, folderId, isSelf, chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted,
isServiceNotifications, handleDelete, handleReport, folderId, isSelf, isServiceNotifications,
]); ]);
}; };

View File

@ -982,6 +982,7 @@ account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings;
account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User; account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User;
account.updateStatus#6628562c offline:Bool = Bool; account.updateStatus#6628562c offline:Bool = Bool;
account.getWallPapers#7967d36 hash:long = account.WallPapers; account.getWallPapers#7967d36 hash:long = account.WallPapers;
account.reportPeer#c5ba3d86 peer:InputPeer reason:ReportReason message:string = Bool;
account.checkUsername#2714d86c username:string = Bool; account.checkUsername#2714d86c username:string = Bool;
account.updateUsername#3e0bdd7c username:string = User; account.updateUsername#3e0bdd7c username:string = User;
account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules;
@ -1002,6 +1003,7 @@ account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:fla
account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper;
account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
account.getContentSettings#8b9b4dae = account.ContentSettings; account.getContentSettings#8b9b4dae = account.ContentSettings;
account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.getFullUser#b60f5918 id:InputUser = users.UserFull;
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;

View File

@ -43,6 +43,8 @@
"account.uploadWallPaper", "account.uploadWallPaper",
"account.setContentSettings", "account.setContentSettings",
"account.getContentSettings", "account.getContentSettings",
"account.reportPeer",
"account.reportProfilePhoto",
"users.getUsers", "users.getUsers",
"users.getFullUser", "users.getFullUser",
"contacts.getContacts", "contacts.getContacts",

View File

@ -197,11 +197,11 @@ $color-message-reaction-own-hover: #b5e0a4;
} }
--z-ui-loader-mask: 2000; --z-ui-loader-mask: 2000;
--z-notification: 1000; --z-notification: 1520;
--z-right-column: 900; --z-right-column: 900;
--z-header-menu: 990; --z-header-menu: 990;
--z-header-menu-backdrop: 980; --z-header-menu-backdrop: 980;
--z-modal: 1000; --z-modal: 1510;
--z-media-viewer: 1500; --z-media-viewer: 1500;
--z-drop-area: 55; --z-drop-area: 55;
--z-animation-fade: 50; --z-animation-fade: 50;