Dice: Add permission check (#6697)

This commit is contained in:
zubiden 2026-02-22 23:43:19 +01:00 committed by Alexander Zinchuk
parent 1f0ac5b276
commit cdc4f0396c
16 changed files with 60 additions and 68 deletions

View File

@ -6,7 +6,6 @@ import type { ApiChat } from '../../api/types';
import { import {
getChatTitle, getChatTitle,
getPrivateChatUserId,
getUserFirstOrLastName, getUserFirstOrLastName,
isChatBasicGroup, isChatBasicGroup,
isChatChannel, isChatChannel,
@ -244,12 +243,10 @@ export default memo(withGlobal<OwnProps>(
(global, { chat, isSavedDialog }): Complete<StateProps> => { (global, { chat, isSavedDialog }): Complete<StateProps> => {
const isPrivateChat = isUserId(chat.id); const isPrivateChat = isUserId(chat.id);
const isChatWithSelf = selectIsChatWithSelf(global, chat.id); const isChatWithSelf = selectIsChatWithSelf(global, chat.id);
const user = isPrivateChat && selectUser(global, getPrivateChatUserId(chat)!); const user = selectUser(global, chat.id);
const isBot = user && isUserBot(user) && !chat.isSupport; const isBot = user && isUserBot(user) && !chat.isSupport;
const canDeleteForAll = (isPrivateChat && !isChatWithSelf && !isBot && !isSavedDialog); const canDeleteForAll = (isPrivateChat && !isChatWithSelf && !isBot && !isSavedDialog);
const contactName = isPrivateChat const contactName = isPrivateChat ? getUserFirstOrLastName(user) : undefined;
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
: undefined;
return { return {
isPrivateChat, isPrivateChat,

View File

@ -12,7 +12,6 @@ import type { IRadioOption } from '../ui/CheckboxGroup';
import { import {
getHasAdminRight, getHasAdminRight,
getPrivateChatUserId,
getUserFirstOrLastName, isChatBasicGroup, getUserFirstOrLastName, isChatBasicGroup,
isChatChannel, isChatChannel,
isChatSuperGroup, isChatSuperGroup,
@ -506,14 +505,14 @@ export default memo(withGlobal<OwnProps>(
const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat); const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat);
const isSchedule = deleteMessageModal?.isSchedule; const isSchedule = deleteMessageModal?.isSchedule;
const onConfirm = deleteMessageModal?.onConfirm; const onConfirm = deleteMessageModal?.onConfirm;
const contactName = chat && isUserId(chat.id) const contactName = chatId && isUserId(chatId)
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!)) ? getUserFirstOrLastName(selectUser(global, chatId))
: undefined; : undefined;
const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id)); const chatBot = Boolean(chat && !isSystemBot(chat.id) && selectBot(global, chat.id));
const adminMembersById = chatFullInfo?.adminMembersById; const adminMembersById = chatFullInfo?.adminMembersById;
const canBanUsers = chat && getHasAdminRight(chat, 'banUsers') && !chat.isMonoforum; // TODO: Ban in channel in case of monoforum const canBanUsers = chat && getHasAdminRight(chat, 'banUsers') && !chat.isMonoforum; // TODO: Ban in channel in case of monoforum
const isCreator = chat?.isCreator; const isCreator = chat?.isCreator;
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined; const isChatWithBot = chatId ? selectIsChatWithBot(global, chatId) : undefined;
const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot; const willDeleteForCurrentUserOnly = (chat && isChatBasicGroup(chat) && !canDeleteForAll) || isChatWithBot;
const willDeleteForAll = chat && (isChatSuperGroup(chat) || isChannel); const willDeleteForAll = chat && (isChatSuperGroup(chat) || isChannel);

View File

@ -2,7 +2,6 @@ import { memo, useState } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import { import {
getPrivateChatUserId,
getUserFirstOrLastName, getUserFirstOrLastName,
isChatBasicGroup, isChatBasicGroup,
isChatChannel, isChatChannel,
@ -112,8 +111,8 @@ export default memo(withGlobal<OwnProps>(
const isGroup = Boolean(chat) && isChatBasicGroup(chat); const isGroup = Boolean(chat) && isChatBasicGroup(chat);
const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat); const isSuperGroup = Boolean(chat) && isChatSuperGroup(chat);
const canPinForAll = (isPrivateChat && !isChatWithSelf) || isSuperGroup || isGroup; const canPinForAll = (isPrivateChat && !isChatWithSelf) || isSuperGroup || isGroup;
const contactName = chat && isUserId(chat.id) const contactName = chatId && isUserId(chatId)
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!)) ? getUserFirstOrLastName(selectUser(global, chatId))
: undefined; : undefined;
return { return {

View File

@ -1,10 +1,9 @@
import type { FC } from '../../../lib/teact/teact';
import { memo } from '../../../lib/teact/teact'; import { memo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { import type {
ApiChat, ApiMessage, ApiMessage,
ApiUser, ApiPeer,
} from '../../../api/types'; } from '../../../api/types';
import { import {
@ -12,9 +11,9 @@ import {
getMessageRoundVideo, getMessageRoundVideo,
getMessageSticker, getMessageSticker,
getMessageVideo, getMessageVideo,
getPrivateChatUserId,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { selectChat, selectUser } from '../../../global/selectors'; import { isApiPeerUser } from '../../../global/helpers/peers';
import { selectPeer } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatPastTimeShort } from '../../../util/dates/dateFormat'; import { formatPastTimeShort } from '../../../util/dates/dateFormat';
import { type LangFn } from '../../../util/localization'; import { type LangFn } from '../../../util/localization';
@ -44,17 +43,15 @@ type OwnProps = {
}; };
type StateProps = { type StateProps = {
chat?: ApiChat; peer?: ApiPeer;
privateChatUser?: ApiUser;
}; };
const ChatMessage: FC<OwnProps & StateProps> = ({ const ChatMessage = ({
message, message,
searchQuery, searchQuery,
chatId, chatId,
chat, peer,
privateChatUser, }: OwnProps & StateProps) => {
}) => {
const { focusMessage } = getActions(); const { focusMessage } = getActions();
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
@ -73,11 +70,11 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
const buttonRef = useSelectWithEnter(handleClick); const buttonRef = useSelectWithEnter(handleClick);
if (!chat) { if (!peer) {
return undefined; return undefined;
} }
const peer = privateChatUser || chat; const user = isApiPeerUser(peer) ? peer : undefined;
return ( return (
<ListItem <ListItem
@ -88,14 +85,14 @@ const ChatMessage: FC<OwnProps & StateProps> = ({
> >
<Avatar <Avatar
peer={peer} peer={peer}
isSavedMessages={privateChatUser?.isSelf} isSavedMessages={user?.isSelf}
/> />
<div className="info"> <div className="info">
<div className="info-row"> <div className="info-row">
<FullNameTitle <FullNameTitle
peer={peer} peer={peer}
withEmojiStatus withEmojiStatus
isSavedMessages={chatId === privateChatUser?.id && privateChatUser?.isSelf} isSavedMessages={user?.isSelf}
/> />
<div className="message-date"> <div className="message-date">
<Link className="date"> <Link className="date">
@ -141,17 +138,10 @@ function renderSummary(
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { chatId }): Complete<StateProps> => { (global, { chatId }): Complete<StateProps> => {
const chat = selectChat(global, chatId); const peer = selectPeer(global, chatId);
if (!chat) {
return {} as Complete<StateProps>;
}
const privateChatUserId = getPrivateChatUserId(chat);
const privateChatUser = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
return { return {
chat, peer,
privateChatUser,
}; };
}, },
)(ChatMessage)); )(ChatMessage));

View File

@ -48,6 +48,7 @@ import { AudioOrigin } from '../../../types';
import { EMOJI_STATUS_LOOP_LIMIT, MESSAGE_APPEARANCE_DELAY } from '../../../config'; import { EMOJI_STATUS_LOOP_LIMIT, MESSAGE_APPEARANCE_DELAY } from '../../../config';
import { import {
areReactionsEmpty, areReactionsEmpty,
getAllowedAttachmentOptions,
getIsDownloading, getIsDownloading,
getMainUsername, getMainUsername,
getMessageContent, getMessageContent,
@ -88,6 +89,7 @@ import {
selectFullWebPageFromMessage, selectFullWebPageFromMessage,
selectIsChatProtected, selectIsChatProtected,
selectIsChatRestricted, selectIsChatRestricted,
selectIsChatWithBot,
selectIsChatWithSelf, selectIsChatWithSelf,
selectIsCurrentUserFrozen, selectIsCurrentUserFrozen,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
@ -331,6 +333,7 @@ type StateProps = {
isMediaNsfw?: boolean; isMediaNsfw?: boolean;
isReplyMediaNsfw?: boolean; isReplyMediaNsfw?: boolean;
summary?: TextSummary; summary?: TextSummary;
canSendStickers?: boolean;
}; };
type MetaPosition = type MetaPosition =
@ -458,6 +461,7 @@ const Message = ({
minFutureTime, minFutureTime,
webPage, webPage,
summary, summary,
canSendStickers,
observeIntersectionForBottom, observeIntersectionForBottom,
observeIntersectionForLoading, observeIntersectionForLoading,
observeIntersectionForPlaying, observeIntersectionForPlaying,
@ -1369,6 +1373,7 @@ const Message = ({
)} )}
{dice && ( {dice && (
<DiceWrapper <DiceWrapper
canSendDice={canSendStickers}
isLocal={isLocal} isLocal={isLocal}
dice={dice} dice={dice}
isOutgoing={isOwn} isOutgoing={isOwn}
@ -2015,6 +2020,7 @@ export default memo(withGlobal<OwnProps>(
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
const isChatWithSelf = selectIsChatWithSelf(global, chatId); const isChatWithSelf = selectIsChatWithSelf(global, chatId);
const isChatWithBot = selectIsChatWithBot(global, chatId);
const isSystemBotChat = isSystemBot(chatId); const isSystemBotChat = isSystemBot(chatId);
const isAnonymousForwards = isAnonymousForwardsChat(chatId); const isAnonymousForwards = isAnonymousForwardsChat(chatId);
const isChannel = chat && isChatChannel(chat); const isChannel = chat && isChatChannel(chat);
@ -2144,6 +2150,8 @@ export default memo(withGlobal<OwnProps>(
const summary = selectMessageSummary(global, chatId, message.id, requestedTranslationLanguage); const summary = selectMessageSummary(global, chatId, message.id, requestedTranslationLanguage);
const allowedAttachmentOptions = getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot);
return { return {
theme: selectTheme(global), theme: selectTheme(global),
forceSenderName, forceSenderName,
@ -2244,6 +2252,7 @@ export default memo(withGlobal<OwnProps>(
isReplyMediaNsfw, isReplyMediaNsfw,
webPage, webPage,
summary, summary,
canSendStickers: allowedAttachmentOptions.canSendStickers,
}; };
}, },
)(Message)); )(Message));

View File

@ -1,14 +1,14 @@
.root { .root {
cursor: var(--custom-cursor, pointer);
position: relative; position: relative;
display: block !important; display: block !important;
width: var(--_size); width: var(--_size);
height: var(--_size); height: var(--_size);
} }
.interactive {
cursor: pointer;
}
.sticker { .sticker {
position: absolute; position: absolute;
inset: 0; inset: 0;

View File

@ -29,6 +29,7 @@ const FALLBACK_SIZE = 13 * REM;
const Dice = ({ const Dice = ({
dice, dice,
canSendDice,
idleSticker, idleSticker,
valueSticker, valueSticker,
winEffect, winEffect,
@ -62,6 +63,7 @@ const Dice = ({
}); });
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
if (!canSendDice) return;
showNotification({ showNotification({
message: { message: {
key: 'DiceToast', key: 'DiceToast',
@ -85,7 +87,11 @@ const Dice = ({
}); });
return ( return (
<div className={styles.root} style={`--_size: ${width}px`} onClick={handleClick}> <div
className={buildClassName(styles.root, canSendDice && styles.interactive)}
style={`--_size: ${width}px`}
onClick={handleClick}
>
{idleSticker && ( {idleSticker && (
<div <div
ref={idleContainerRef} ref={idleContainerRef}

View File

@ -13,6 +13,7 @@ export type OwnProps = {
canPlayWinEffect?: boolean; canPlayWinEffect?: boolean;
isLocal?: boolean; isLocal?: boolean;
isOutgoing?: boolean; isOutgoing?: boolean;
canSendDice?: boolean;
onEffectPlayed?: NoneToVoidFunction; onEffectPlayed?: NoneToVoidFunction;
observeIntersectionForLoading?: ObserveFn; observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn; observeIntersectionForPlaying?: ObserveFn;

View File

@ -36,6 +36,7 @@ const WIN_BACKGROUND_DELAY = 700;
const SlotMachine = ({ const SlotMachine = ({
dice, dice,
canSendDice,
canPlayWinEffect, canPlayWinEffect,
isLocal, isLocal,
isOutgoing, isOutgoing,
@ -122,6 +123,7 @@ const SlotMachine = ({
}); });
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
if (!canSendDice) return;
showNotification({ showNotification({
message: { message: {
key: 'DiceToast', key: 'DiceToast',
@ -171,7 +173,12 @@ const SlotMachine = ({
} }
return ( return (
<div ref={ref} className={styles.root} style={`--_size: ${width}px`} onClick={handleClick}> <div
ref={ref}
className={buildClassName(styles.root, canSendDice && styles.interactive)}
style={`--_size: ${width}px`}
onClick={handleClick}
>
{renderSticker(backgroundData, backgroundState !== 'base', false, true)} {renderSticker(backgroundData, backgroundState !== 'base', false, true)}
{renderSticker(frameWinData, backgroundState !== 'win', false, false, undefined, onWinBackgroundFrame)} {renderSticker(frameWinData, backgroundState !== 'win', false, false, undefined, onWinBackgroundFrame)}

View File

@ -173,7 +173,7 @@ export default memo(withGlobal(
const { chatId, threadId } = selectCurrentMessageList(global) || {}; const { chatId, threadId } = selectCurrentMessageList(global) || {};
const chat = chatId ? selectChat(global, chatId) : undefined; const chat = chatId ? selectChat(global, chatId) : undefined;
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined; const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined; const isChatWithBot = chatId ? selectIsChatWithBot(global, chatId) : undefined;
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId); const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined; const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId); const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);

View File

@ -70,6 +70,7 @@ import {
isMessageLocal, isMessageLocal,
isServiceNotificationMessage, isServiceNotificationMessage,
isUserBot, isUserBot,
isUserRightBanned,
splitMessagesForForwarding, splitMessagesForForwarding,
} from '../../helpers'; } from '../../helpers';
import { isApiPeerChat, isApiPeerUser } from '../../helpers/peers'; import { isApiPeerChat, isApiPeerUser } from '../../helpers/peers';
@ -452,9 +453,11 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
}); });
} }
const areStickersDisabled = isUserRightBanned(chat, 'sendStickers');
const diceEmojies = global.appConfig.diceEmojies; const diceEmojies = global.appConfig.diceEmojies;
let dice = payload.dice; let dice = payload.dice;
if (payload.text && !payload.entities?.length && diceEmojies.includes(payload.text)) { if (!areStickersDisabled && payload.text && !payload.entities?.length && diceEmojies.includes(payload.text)) {
dice = payload.text; dice = payload.text;
} }

View File

@ -350,7 +350,7 @@ addActionHandler('showAllowedMessageTypesNotification', (global, actions, payloa
if (!chat) return; if (!chat) return;
const chatFullInfo = selectChatFullInfo(global, chatId); const chatFullInfo = selectChatFullInfo(global, chatId);
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined; const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
const isChatWithBot = chatId ? selectIsChatWithBot(global, chat) : undefined; const isChatWithBot = chatId ? selectIsChatWithBot(global, chatId) : undefined;
const { const {
canSendPlainText, canSendPhotos, canSendVideos, canSendDocuments, canSendAudios, canSendPlainText, canSendPhotos, canSendVideos, canSendDocuments, canSendAudios,

View File

@ -85,13 +85,6 @@ export function getChatTypeLangKey(chat: ApiChat): RegularLangKey {
} }
} }
export function getPrivateChatUserId(chat: ApiChat) {
if (chat.type !== 'chatTypePrivate' && chat.type !== 'chatTypeSecret') {
return undefined;
}
return chat.id;
}
export function getChatTitle(lang: OldLangFn | LangFn, chat: ApiChat, isSelf = false) { export function getChatTitle(lang: OldLangFn | LangFn, chat: ApiChat, isSelf = false) {
if (isSelf) { if (isSelf) {
return lang('SavedMessages'); return lang('SavedMessages');

View File

@ -12,7 +12,6 @@ import { isUserId } from '../../util/entities/ids';
import { getCurrentTabId } from '../../util/establishMultitabRole'; import { getCurrentTabId } from '../../util/establishMultitabRole';
import { import {
getHasAdminRight, getHasAdminRight,
getPrivateChatUserId,
isChatChannel, isChatChannel,
isChatPublic, isChatPublic,
isChatSuperGroup, isChatSuperGroup,
@ -45,21 +44,12 @@ export function selectChatListLoadingParameters<T extends GlobalState>(
return global.chats.loadingParameters[listType]; return global.chats.loadingParameters[listType];
} }
export function selectChatUser<T extends GlobalState>(global: T, chat: ApiChat) {
const userId = getPrivateChatUserId(chat);
if (!userId) {
return false;
}
return selectUser(global, userId);
}
export function selectIsChatWithSelf<T extends GlobalState>(global: T, chatId: string) { export function selectIsChatWithSelf<T extends GlobalState>(global: T, chatId: string) {
return chatId === global.currentUserId; return chatId === global.currentUserId;
} }
export function selectIsChatWithBot<T extends GlobalState>(global: T, chat: ApiChat) { export function selectIsChatWithBot<T extends GlobalState>(global: T, chatId: string) {
const user = selectChatUser(global, chat); const user = selectUser(global, chatId);
return user && isUserBot(user); return user && isUserBot(user);
} }

View File

@ -1374,7 +1374,7 @@ export function selectForwardsCanBeSentToChat<T extends GlobalState>(
const chatMessages = selectChatMessages(global, fromChatId!); const chatMessages = selectChatMessages(global, fromChatId!);
const isSavedMessages = toChatId ? selectIsChatWithSelf(global, toChatId) : undefined; const isSavedMessages = toChatId ? selectIsChatWithSelf(global, toChatId) : undefined;
const isChatWithBot = toChatId ? selectIsChatWithBot(global, chat) : undefined; const isChatWithBot = toChatId ? selectIsChatWithBot(global, toChatId) : undefined;
const options = getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isSavedMessages); const options = getAllowedAttachmentOptions(chat, chatFullInfo, isChatWithBot, isSavedMessages);
return !messageIds!.some((messageId) => сheckMessageSendingDenied(chatMessages[messageId], options)); return !messageIds!.some((messageId) => сheckMessageSendingDenied(chatMessages[messageId], options));
} }

View File

@ -11,7 +11,6 @@ import {
getChatAvatarHash, getChatAvatarHash,
getChatTitle, getChatTitle,
getMessageRecentReaction, getMessageRecentReaction,
getPrivateChatUserId,
getUserFullName, getUserFullName,
} from '../global/helpers'; } from '../global/helpers';
import { getIsChatMuted, getIsChatSilent, getShouldShowMessagePreview } from '../global/helpers/notifications'; import { getIsChatMuted, getIsChatSilent, getShouldShowMessagePreview } from '../global/helpers/notifications';
@ -320,8 +319,7 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage, reaction?: A
} }
const { isScreenLocked } = global.passcode; const { isScreenLocked } = global.passcode;
const privateChatUserId = getPrivateChatUserId(chat); const isSelf = chat.id === global.currentUserId;
const isSelf = privateChatUserId === global.currentUserId;
let body: string; let body: string;
if ( if (