From ade72a0727e4fd7a6fd002252b7cf87ec712126b Mon Sep 17 00:00:00 2001 From: zubiden <19638254+zubiden@users.noreply.github.com> Date: Tue, 13 Jan 2026 01:14:14 +0100 Subject: [PATCH] Monoforum: Fix admin rights check (#6538) --- src/api/types/chats.ts | 1 + src/assets/localization/fallback.strings | 8 +++ src/components/common/DeleteMessageModal.tsx | 9 ++-- .../main/PermissionCheckboxList.tsx | 16 +++--- .../management/ManageGroupAdminRights.tsx | 52 ++++++++++--------- src/global/actions/apiUpdaters/chats.ts | 10 ++++ src/global/reducers/messages.ts | 4 +- src/global/selectors/chats.ts | 2 +- src/global/selectors/peers.ts | 4 +- src/types/language.d.ts | 8 +++ 10 files changed, 73 insertions(+), 41 deletions(-) diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index ab58d2f43..c6261edb9 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -197,6 +197,7 @@ export interface ApiChatAdminRights { postStories?: true; editStories?: true; deleteStories?: true; + manageDirectMessages?: true; } export interface ApiChatBannedRights { diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 52df59eec..534ede7d0 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -955,6 +955,14 @@ "EditAdminBanUsers" = "Ban Users"; "EditAdminPinMessages" = "Pin Messages"; "EditAdminAddAdmins" = "Add New Admins"; +"EditAdminManageDirect" = "Manage Direct Messages"; +"EditAdminPostStories" = "Post Stories"; +"EditAdminEditStories" = "Edit Stories of Others"; +"EditAdminDeleteStories" = "Delete Stories of Others"; +"EditAdminManageTopics" = "Manage Topics"; +"EditAdminUnavailable" = "You can't edit the rights of this admin."; +"EditAdminConfirmDismissText" = "Are you sure you want to dismiss this admin?"; +"EditAdminConfirmDismiss" = "Dismiss Admin"; "StartVoipChatPermission" = "Manage Video Chats"; "EditAdminSendAnonymously" = "Remain Anonymous"; "ChannelEditAdminCannotEdit" = "You can't edit the rights of this admin."; diff --git a/src/components/common/DeleteMessageModal.tsx b/src/components/common/DeleteMessageModal.tsx index f576d1266..30c45dc6b 100644 --- a/src/components/common/DeleteMessageModal.tsx +++ b/src/components/common/DeleteMessageModal.tsx @@ -24,7 +24,8 @@ import { selectBot, selectCanDeleteSelectedMessages, selectChat, - selectChatFullInfo, selectIsChatWithBot, + selectChatFullInfo, + selectIsChatWithBot, selectSenderFromMessage, selectTabState, selectUser, @@ -164,7 +165,7 @@ const DeleteMessageModal: FC = ({ const peerListToBan = useMemo(() => { const isCurrentUserInList = peerList.some((peer) => peer.id === currentUserId); - const shouldReturnEmpty = !canBanUsers || isCurrentUserInList || chat?.isMonoforum; + const shouldReturnEmpty = !canBanUsers || isCurrentUserInList; if (shouldReturnEmpty) { return MEMO_EMPTY_ARRAY; @@ -174,7 +175,7 @@ const DeleteMessageModal: FC = ({ const isAdmin = adminMembersById?.[peer.id]; return isCreator || !isAdmin; }); - }, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById, chat?.isMonoforum]); + }, [peerList, isCreator, currentUserId, canBanUsers, adminMembersById]); const shouldShowAdditionalOptions = useMemo(() => { return Boolean(peerListToDeleteAll.length || peerListToReportSpam.length || peerListToBan.length); @@ -510,7 +511,7 @@ export default memo(withGlobal( : undefined; const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id)); const adminMembersById = chatFullInfo?.adminMembersById; - const canBanUsers = chat && getHasAdminRight(chat, 'banUsers'); + const canBanUsers = chat && getHasAdminRight(chat, 'banUsers') && !chat.isMonoforum; // TODO: Ban in channel in case of monoforum const isCreator = chat?.isCreator; const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined; const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot; diff --git a/src/components/main/PermissionCheckboxList.tsx b/src/components/main/PermissionCheckboxList.tsx index b1017462d..d9d9b9a6d 100644 --- a/src/components/main/PermissionCheckboxList.tsx +++ b/src/components/main/PermissionCheckboxList.tsx @@ -1,5 +1,3 @@ -import type { FC } from '../../lib/teact/teact'; -import type React from '../../lib/teact/teact'; import { memo, useMemo, } from '../../lib/teact/teact'; @@ -19,15 +17,15 @@ import Checkbox from '../ui/Checkbox'; export type OwnProps = { chatId?: string; - handlePermissionChange: (e: React.ChangeEvent) => void; permissions: ApiChatBannedRights; isMediaDropdownOpen: boolean; - setIsMediaDropdownOpen: (open: boolean) => void; className?: string; shiftedClassName?: string; dropdownClassName?: string; withCheckbox?: boolean; permissionGroup?: boolean; + handlePermissionChange: (e: React.ChangeEvent) => void; + setIsMediaDropdownOpen: (open: boolean) => void; getControlIsDisabled?: (key: Exclude) => boolean | undefined; }; @@ -41,20 +39,20 @@ const permissionKeyList: (keyof ApiChatBannedRights)[] = [ 'sendAudios', 'sendDocs', 'sendVoices', 'sendRoundvideos', 'embedLinks', 'sendPolls', ]; -const PermissionCheckboxList: FC = ({ +const PermissionCheckboxList = ({ chat, isMediaDropdownOpen, - setIsMediaDropdownOpen, hasLinkedChat, permissions, - handlePermissionChange, className, shiftedClassName, dropdownClassName, withCheckbox, - getControlIsDisabled, permissionGroup, -}) => { + setIsMediaDropdownOpen, + handlePermissionChange, + getControlIsDisabled, +}: OwnProps & StateProps) => { const { showNotification, } = getActions(); diff --git a/src/components/right/management/ManageGroupAdminRights.tsx b/src/components/right/management/ManageGroupAdminRights.tsx index ed9e9a3ab..36b1a0d97 100644 --- a/src/components/right/management/ManageGroupAdminRights.tsx +++ b/src/components/right/management/ManageGroupAdminRights.tsx @@ -1,5 +1,3 @@ -import type { FC } from '../../../lib/teact/teact'; -import type React from '../../../lib/teact/teact'; import { memo, useCallback, useEffect, useMemo, useState, } from '../../../lib/teact/teact'; @@ -15,7 +13,7 @@ import { selectChat, selectChatFullInfo } from '../../../global/selectors'; import useFlag from '../../../hooks/useFlag'; import useHistoryBack from '../../../hooks/useHistoryBack'; -import useOldLang from '../../../hooks/useOldLang'; +import useLang from '../../../hooks/useLang'; import PrivateChatInfo from '../../common/PrivateChatInfo'; import Checkbox from '../../ui/Checkbox'; @@ -40,15 +38,13 @@ type StateProps = { adminMembersById?: Record; hasFullInfo: boolean; currentUserId?: string; - isChannel: boolean; isFormFullyDisabled: boolean; - isForum?: boolean; defaultRights?: ApiChatAdminRights; }; const CUSTOM_TITLE_MAX_LENGTH = 16; -const ManageGroupAdminRights: FC = ({ +const ManageGroupAdminRights = ({ isActive, isNewAdmin, selectedUserId, @@ -58,12 +54,10 @@ const ManageGroupAdminRights: FC = ({ currentUserId, adminMembersById, hasFullInfo, - isChannel, - isForum, isFormFullyDisabled, onClose, onScreenSelect, -}) => { +}: OwnProps & StateProps) => { const { updateChatAdmin } = getActions(); const [permissions, setPermissions] = useState({}); @@ -71,7 +65,11 @@ const ManageGroupAdminRights: FC = ({ const [isLoading, setIsLoading] = useState(false); const [isDismissConfirmationDialogOpen, openDismissConfirmationDialog, closeDismissConfirmationDialog] = useFlag(); const [customTitle, setCustomTitle] = useState(''); - const lang = useOldLang(); + const lang = useLang(); + + const isChannel = isChatChannel(chat); + const isForum = chat.isForum; + const hasDirectMessages = Boolean(chat.linkedMonoforumId); useHistoryBack({ isActive, @@ -186,7 +184,7 @@ const ManageGroupAdminRights: FC = ({ : undefined; if (promotedByUser) { - return lang('EditAdminPromotedBy', getUserFullName(promotedByUser)); + return lang('EditAdminPromotedBy', { user: getUserFullName(promotedByUser) }); } return lang('ChannelAdmin'); @@ -290,18 +288,28 @@ const ManageGroupAdminRights: FC = ({ onChange={handlePermissionChange} /> - {!isChannel && ( + {hasDirectMessages && (
)} +
+ +
= ({ = ({ {isFormFullyDisabled && (

- {lang('Channel.EditAdmin.CannotEdit')} + {lang('EditAdminUnavailable')}

)} @@ -407,8 +415,8 @@ const ManageGroupAdminRights: FC = ({ @@ -423,16 +431,12 @@ export default memo(withGlobal( const fullInfo = selectChatFullInfo(global, chatId); const { byId: usersById } = global.users; const { currentUserId } = global; - const isChannel = isChatChannel(chat); const isFormFullyDisabled = !(chat.isCreator || isPromotedByCurrentUser); - const isForum = chat.isForum; return { chat, usersById, currentUserId, - isChannel, - isForum, isFormFullyDisabled, defaultRights: chat.adminRights, hasFullInfo: Boolean(fullInfo), diff --git a/src/global/actions/apiUpdaters/chats.ts b/src/global/actions/apiUpdaters/chats.ts index a03f87168..f089411de 100644 --- a/src/global/actions/apiUpdaters/chats.ts +++ b/src/global/actions/apiUpdaters/chats.ts @@ -17,6 +17,7 @@ import { deletePeerPhoto, leaveChat, removeUnreadMentions, + replaceChatMessages, replacePeerPhotos, replacePinnedTopicIds, replaceThreadParam, @@ -69,6 +70,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { global = updatePeerStoriesHidden(global, update.id, update.chat.areStoriesHidden || false); } + const localAdminRights = localChat?.adminRights; + const newAdminRights = update.chat.adminRights; + + if (localAdminRights && localAdminRights.manageDirectMessages && !update.chat.isMin + && newAdminRights?.manageDirectMessages !== localAdminRights.manageDirectMessages + && localChat.linkedMonoforumId) { + global = replaceChatMessages(global, localChat.linkedMonoforumId, {}); + } + setGlobal(global); const updatedChat = selectChat(global, update.id); diff --git a/src/global/reducers/messages.ts b/src/global/reducers/messages.ts index 1edc93278..f9ca32bfc 100644 --- a/src/global/reducers/messages.ts +++ b/src/global/reducers/messages.ts @@ -96,7 +96,9 @@ export function updateCurrentMessageList( }, tabId); } -function replaceChatMessages(global: T, chatId: string, newById: Record): T { +export function replaceChatMessages( + global: T, chatId: string, newById: Record, +): T { return updateMessageStore(global, chatId, { byId: newById, }); diff --git a/src/global/selectors/chats.ts b/src/global/selectors/chats.ts index 909754543..21b4d101e 100644 --- a/src/global/selectors/chats.ts +++ b/src/global/selectors/chats.ts @@ -360,7 +360,7 @@ export function selectIsMonoforumAdmin( const channel = selectMonoforumChannel(global, chatId); if (!channel) return; - return Boolean(chat.isCreator || chat.adminRights || channel.isCreator || channel.adminRights); + return Boolean(chat.isCreator || getHasAdminRight(channel, 'manageDirectMessages')); } /** diff --git a/src/global/selectors/peers.ts b/src/global/selectors/peers.ts index 7c05117b8..f90a46bd7 100644 --- a/src/global/selectors/peers.ts +++ b/src/global/selectors/peers.ts @@ -5,7 +5,7 @@ import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config'; import { isUserId } from '../../util/entities/ids'; import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getHasAdminRight, isChatAdmin, isChatChannel, isDeletedUser } from '../helpers'; -import { selectChat, selectChatFullInfo } from './chats'; +import { selectChat, selectChatFullInfo, selectIsMonoforumAdmin } from './chats'; import { type ProfileCollectionKey } from './payments'; import { selectTabState } from './tabs'; import { selectBot, selectUser, selectUserFullInfo } from './users'; @@ -66,7 +66,7 @@ export function selectPeerPaidMessagesStars( const chat = selectChat(global, peerId); if (!chat) return undefined; - if (isChatAdmin(chat)) return undefined; + if (isChatAdmin(chat) || selectIsMonoforumAdmin(global, chat.id)) return undefined; return chat.paidMessagesStars; } diff --git a/src/types/language.d.ts b/src/types/language.d.ts index 6b1bb309f..4be63cfb2 100644 --- a/src/types/language.d.ts +++ b/src/types/language.d.ts @@ -831,6 +831,14 @@ export interface LangPair { 'EditAdminBanUsers': undefined; 'EditAdminPinMessages': undefined; 'EditAdminAddAdmins': undefined; + 'EditAdminManageDirect': undefined; + 'EditAdminPostStories': undefined; + 'EditAdminEditStories': undefined; + 'EditAdminDeleteStories': undefined; + 'EditAdminManageTopics': undefined; + 'EditAdminUnavailable': undefined; + 'EditAdminConfirmDismissText': undefined; + 'EditAdminConfirmDismiss': undefined; 'StartVoipChatPermission': undefined; 'EditAdminSendAnonymously': undefined; 'ChannelEditAdminCannotEdit': undefined;