Chat: Fix restriction validation (#6078)

Co-authored-by: Dmitry Kabanov <dmitrykabanovdev@gmail.com>
This commit is contained in:
zubiden 2025-07-29 14:33:49 +02:00 committed by Alexander Zinchuk
parent 6e69765a51
commit 9857dc0ec7
55 changed files with 888 additions and 128 deletions

View File

@ -120,6 +120,11 @@ export interface GramJsAppConfig extends LimitsConfig {
todo_items_max?: number;
todo_title_length_max?: number;
todo_item_length_max?: number;
ignore_restriction_reasons?: string[];
need_age_video_verification?: boolean;
verify_age_bot_username?: string;
verify_age_country?: string;
verify_age_min?: number;
}
function buildEmojiSounds(appConfig: GramJsAppConfig) {
@ -235,5 +240,10 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
todoItemsMax: appConfig.todo_items_max ?? TODO_ITEMS_LIMIT,
todoTitleLengthMax: appConfig.todo_title_length_max ?? TODO_TITLE_LENGTH_LIMIT,
todoItemLengthMax: appConfig.todo_item_length_max ?? TODO_ITEM_LENGTH_LIMIT,
ignoreRestrictionReasons: appConfig.ignore_restriction_reasons,
needAgeVideoVerification: appConfig.need_age_video_verification,
verifyAgeBotUsername: appConfig.verify_age_bot_username,
verifyAgeCountry: appConfig.verify_age_country,
verifyAgeMin: appConfig.verify_age_min,
};
}

View File

@ -23,7 +23,7 @@ import type {
ApiTopic,
} from '../../types';
import { pick, pickTruthy } from '../../../util/iteratees';
import { pickTruthy } from '../../../util/iteratees';
import { getServerTimeOffset } from '../../../util/serverTime';
import { addPhotoToLocalDb, addUserToLocalDb } from '../helpers/localDb';
import { serializeBytes } from '../helpers/misc';
@ -31,7 +31,7 @@ import {
buildApiBotVerification, buildApiFormattedText, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId,
} from './common';
import { omitVirtualClassFields } from './helpers';
import { buildApiPeerNotifySettings } from './misc';
import { buildApiPeerNotifySettings, buildApiRestrictionReasons } from './misc';
import {
buildApiEmojiStatus,
buildApiPeerColor,
@ -196,7 +196,7 @@ function buildApiChatRestrictions(peerEntity: Entity): {
isNotJoined?: boolean;
isForbidden?: boolean;
isRestricted?: boolean;
restrictionReason?: ApiRestrictionReason;
restrictionReasons?: ApiRestrictionReason[];
} {
if (peerEntity instanceof GramJs.ChatForbidden) {
return {
@ -213,11 +213,10 @@ function buildApiChatRestrictions(peerEntity: Entity): {
const restrictions = {};
if ('restricted' in peerEntity && !peerEntity.min) {
const restrictionReason = buildApiChatRestrictionReason(peerEntity.restrictionReason);
const restrictionReasons = buildApiRestrictionReasons(peerEntity.restrictionReason);
Object.assign(restrictions, {
isRestricted: peerEntity.restricted,
restrictionReason,
restrictionReasons,
});
}
@ -261,17 +260,6 @@ function buildApiChatMigrationInfo(peerEntity: Entity): {
return {};
}
function buildApiChatRestrictionReason(
restrictionReasons?: GramJs.RestrictionReason[],
): ApiRestrictionReason | undefined {
if (!restrictionReasons) {
return undefined;
}
const targetReason = restrictionReasons.find(({ platform }) => platform === 'all');
return targetReason ? pick(targetReason, ['reason', 'text']) : undefined;
}
export function buildApiChatFromPreview(
preview: GramJs.TypeChat | GramJs.TypeUser,
isSupport = false,

View File

@ -70,6 +70,7 @@ import {
import { type OmitVirtualFields } from './helpers';
import { buildApiMessageAction } from './messageActions';
import { buildMessageContent, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
import { buildApiRestrictionReasons } from './misc';
import { buildApiPeerColor, buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
import { buildMessageReactions } from './reactions';
@ -230,6 +231,8 @@ export function buildApiMessageWithChatId(
const savedPeerId = mtpMessage.savedPeerId && getApiChatIdFromMtpPeer(mtpMessage.savedPeerId);
const restrictionReasons = buildApiRestrictionReasons(mtpMessage.restrictionReason);
return {
id: mtpMessage.id,
chatId,
@ -277,6 +280,7 @@ export function buildApiMessageWithChatId(
isVideoProcessingPending,
reportDeliveryUntilDate: mtpMessage.reportDeliveryUntilDate,
paidMessageStars: mtpMessage.paidMessageStars?.toJSNumber(),
restrictionReasons,
};
}

View File

@ -10,6 +10,7 @@ import type {
ApiPeerColors,
ApiPeerNotifySettings,
ApiPrivacyKey,
ApiRestrictionReason,
ApiSession,
ApiTimezone,
ApiUrlAuthResult,
@ -347,3 +348,15 @@ export function buildApiCollectibleInfo(info: GramJs.fragment.TypeCollectibleInf
url,
};
}
export function buildApiRestrictionReasons(
restrictionReasons?: GramJs.RestrictionReason[],
): ApiRestrictionReason[] | undefined {
if (!restrictionReasons) {
return undefined;
}
return restrictionReasons.map((
{ reason, text, platform }) =>
({ reason, text, platform }));
}

View File

@ -314,8 +314,6 @@ export async function fetchProfilePhotos({
};
}
if (chat?.isRestricted) return undefined;
const result = await searchMessagesInChat({
peer,
type: 'profilePhoto',

View File

@ -2,7 +2,7 @@ import type { ApiBotCommand } from './bots';
import type {
ApiChatReactions, ApiFormattedText, ApiInputMessageReplyInfo, ApiInputSuggestedPostInfo, ApiPhoto, ApiStickerSet,
} from './messages';
import type { ApiBotVerification, ApiChatInviteImporter, ApiPeerNotifySettings } from './misc';
import type { ApiBotVerification, ApiChatInviteImporter, ApiPeerNotifySettings, ApiRestrictionReason } from './misc';
import type {
ApiEmojiStatusType, ApiFakeType, ApiUser, ApiUsername,
} from './users';
@ -66,7 +66,7 @@ export interface ApiChat {
isCreator?: boolean;
isForbidden?: boolean; // Forbidden - can't send messages (user was kicked, for example)
isRestricted?: boolean; // Restricted - can't access the chat (user was banned or chat is violating rules)
restrictionReason?: ApiRestrictionReason;
restrictionReasons?: ApiRestrictionReason[];
adminRights?: ApiChatAdminRights;
currentUserBannedRights?: ApiChatBannedRights;
defaultBannedRights?: ApiChatBannedRights;
@ -212,11 +212,6 @@ export interface ApiChatBannedRights {
untilDate?: number;
}
export interface ApiRestrictionReason {
reason: string;
text: string;
}
export interface ApiChatFolder {
id: number;
title: ApiFormattedText;

View File

@ -6,6 +6,7 @@ import type {
} from './bots';
import type { ApiPeerColor } from './chats';
import type { ApiMessageAction } from './messageActions';
import type { ApiRestrictionReason } from './misc';
import type {
ApiLabeledPrice,
} from './payments';
@ -642,6 +643,7 @@ export interface ApiMessage {
areReactionsPossible?: true;
reportDeliveryUntilDate?: number;
paidMessageStars?: number;
restrictionReasons?: ApiRestrictionReason[];
}
export interface ApiReactions {

View File

@ -267,6 +267,11 @@ export interface ApiAppConfig {
todoItemsMax?: number;
todoTitleLengthMax?: number;
todoItemLengthMax?: number;
ignoreRestrictionReasons?: string[];
needAgeVideoVerification?: boolean;
verifyAgeBotUsername?: string;
verifyAgeCountry?: string;
verifyAgeMin?: number;
}
export interface ApiConfig {
@ -385,3 +390,9 @@ export type ApiPeerNotifySettings = {
};
export type ApiNotifyPeerType = 'users' | 'groups' | 'channels';
export interface ApiRestrictionReason {
reason: string;
text: string;
platform: string;
}

View File

@ -2141,3 +2141,15 @@
"ButtonTopUpViaFragment" = "Top-Up Via Fragment";
"TonModalHint" = "You can top-up your TON using Fragment.";
"TonGiftReceived" = "TON Top-Up";
"MediaSpoilerSensitive" = "18+";
"TitleSensitiveModal" = "18+ content";
"TextSensitiveModal" = "This media may contain sensitive content suitable only for adults. Do you still want to view it?";
"ButtonSensitiveAlways" = "Always show 18+ media";
"ButtonSensitiveView" = "View Anyway";
"TitleAgeVerificationModal" = "Age Verification";
"TextAgeVerificationModal_one" = "To access such content, you must confirm that you are at least **{count}** year old as required by UK law.";
"TextAgeVerificationModal_other" = "To access such content, you must confirm that you are at least **{count}** years old as required by UK law.";
"DescriptionAgeVerificationModal" = "This is a one-time process using your phone's camera. Your selfie will not be stored by Telegram.";
"TitleAgeCheckFailed" = "Age Check Failed";
"TitleAgeCheckSuccess" = "Age Check Success";
"ButtonAgeVerification" = "Verify My Age";

View File

@ -24,6 +24,7 @@ export { default as SuggestedStatusModal } from '../components/modals/suggestedS
export { default as BoostModal } from '../components/modals/boost/BoostModal';
export { default as GiftCodeModal } from '../components/modals/giftcode/GiftCodeModal';
export { default as DeleteAccountModal } from '../components/modals/deleteAccount/DeleteAccountModal';
export { default as AgeVerificationModal } from '../components/modals/ageVerification/AgeVerificationModal';
export { default as ChatlistModal } from '../components/modals/chatlist/ChatlistModal';
export { default as ChatInviteModal } from '../components/modals/chatInvite/ChatInviteModal';

View File

@ -1,7 +1,7 @@
import type { FC } from '../../lib/teact/teact';
import type React from '../../lib/teact/teact';
import { memo, useEffect, useMemo } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import { getActions, getGlobal, withGlobal } from '../../global';
import type {
ApiChat, ApiThreadInfo, ApiTopic, ApiTypingStatus, ApiUser,
@ -19,6 +19,7 @@ import {
selectChat,
selectChatMessages,
selectChatOnlineCount,
selectIsChatRestricted,
selectMonoforumChannel,
selectThreadInfo,
selectThreadMessagesCount,
@ -126,7 +127,8 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
const isSuperGroup = chat && isChatSuperGroup(chat);
const isTopic = Boolean(chat?.isForum && threadInfo && topic);
const { id: chatId, isMin, isRestricted } = chat || {};
const { id: chatId, isMin } = chat || {};
const isRestricted = selectIsChatRestricted(getGlobal(), chatId!);
useEffect(() => {
if (chatId && !isMin) {

View File

@ -1,11 +1,14 @@
.root {
--click-shift-x: 0px;
--click-shift-y: 0px;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--color-text-secondary); // Fallback before canvas is prepared
}
@ -33,6 +36,28 @@
object-fit: cover;
}
.nsfw {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 0.25rem;
align-items: center;
padding: 0.125rem 0.5rem;
border-radius: 1rem;
color: white;
background-color: rgba(0, 0, 0, 0.25);
}
.nsfwIcon {
font-size: 1.25rem;
}
.dots {
--x-direction: var(--background-size);
--y-direction: 0;

View File

@ -6,15 +6,19 @@ import { requestMutation } from '../../lib/fasterdom/fasterdom';
import buildClassName from '../../util/buildClassName';
import useCanvasBlur from '../../hooks/useCanvasBlur';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import useShowTransitionDeprecated from '../../hooks/useShowTransitionDeprecated';
import Icon from './icons/Icon';
import styles from './MediaSpoiler.module.scss';
type OwnProps = {
isVisible: boolean;
withAnimation?: boolean;
thumbDataUri?: string;
isNsfw?: boolean;
width?: number;
height?: number;
className?: string;
@ -27,12 +31,15 @@ const MediaSpoiler: FC<OwnProps> = ({
isVisible,
withAnimation,
thumbDataUri,
isNsfw,
className,
width,
height,
}) => {
const ref = useRef<HTMLDivElement>();
const lang = useLang();
const { shouldRender, transitionClassNames } = useShowTransitionDeprecated(
isVisible, undefined, true, withAnimation ? false : undefined, undefined, ANIMATION_DURATION,
);
@ -68,6 +75,12 @@ const MediaSpoiler: FC<OwnProps> = ({
height={height}
/>
<div className={styles.dots} />
{isNsfw && (
<span className={styles.nsfw}>
<Icon name="eye-crossed-outline" className={styles.nsfwIcon} />
{lang('MediaSpoilerSensitive')}
</span>
)}
</div>
);
};

View File

@ -0,0 +1,4 @@
.checkBox {
margin-top: 1rem;
margin-inline: -1.125rem;
}

View File

@ -0,0 +1,47 @@
import type { FC } from '../../lib/teact/teact';
import { memo } from '../../lib/teact/teact';
import useLang from '../../hooks/useLang';
import Checkbox from '../ui/Checkbox';
import ConfirmDialog from '../ui/ConfirmDialog';
import styles from './SensitiveContentConfirmModal.module.scss';
type OwnProps = {
isOpen: boolean;
onClose: NoneToVoidFunction;
shouldAlwaysShow: boolean;
onAlwaysShowChanged: (value: boolean) => void;
confirmHandler: NoneToVoidFunction;
};
const SensitiveContentConfirmModal: FC<OwnProps> = ({
isOpen,
onClose,
shouldAlwaysShow,
onAlwaysShowChanged,
confirmHandler,
}) => {
const lang = useLang();
return (
<ConfirmDialog
title={lang('TitleSensitiveModal')}
confirmLabel={lang('ButtonSensitiveView')}
isOpen={isOpen}
onClose={onClose}
confirmHandler={confirmHandler}
>
{lang('TextSensitiveModal')}
<Checkbox
className={styles.checkBox}
label={lang('ButtonSensitiveAlways')}
checked={shouldAlwaysShow}
onCheck={onAlwaysShowChanged}
/>
</ConfirmDialog>
);
};
export default memo(SensitiveContentConfirmModal);

View File

@ -64,6 +64,7 @@ type OwnProps = {
chatTranslations?: ChatTranslatedMessages;
requestedChatTranslationLanguage?: string;
isOpen?: boolean;
isMediaNsfw?: boolean;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
onClick: ((e: React.MouseEvent) => void);
@ -88,6 +89,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
noUserColors,
chatTranslations,
requestedChatTranslationLanguage,
isMediaNsfw,
observeIntersectionForLoading,
observeIntersectionForPlaying,
onClick,
@ -114,7 +116,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
const mediaThumbnail = useThumbnail(containedMedia);
const isRoundVideo = Boolean(containedMedia && getMessageRoundVideo(containedMedia));
const isSpoiler = Boolean(containedMedia && getMessageIsSpoiler(containedMedia));
const isSpoiler = Boolean(containedMedia && getMessageIsSpoiler(containedMedia)) || isMediaNsfw;
const isQuote = Boolean(replyInfo?.type === 'message' && replyInfo.isQuote);
const replyForwardInfo = replyInfo?.type === 'message' ? replyInfo.replyFrom : undefined;

View File

@ -2,7 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
import {
memo, useMemo,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type {
ApiBotVerification,
@ -29,6 +29,7 @@ import {
selectChat,
selectChatFullInfo,
selectCurrentMessageList,
selectIsChatRestricted,
selectNotifyDefaults,
selectNotifyException,
selectTopicLink,
@ -273,7 +274,8 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
),
}, { withNodes: true });
if (chat?.isRestricted || (isSelf && !isInSettings)) {
const isRestricted = chatId ? selectIsChatRestricted(getGlobal(), chatId) : false;
if (isRestricted || (isSelf && !isInSettings)) {
return undefined;
}

View File

@ -20,6 +20,7 @@ import useLastCallback from '../../../hooks/useLastCallback.ts';
import useOldLang from '../../../hooks/useOldLang';
import StarIcon from '../../common/icons/StarIcon';
import Button from '../../ui/Button';
import Checkbox from '../../ui/Checkbox';
import ListItem from '../../ui/ListItem';
@ -43,6 +44,7 @@ type StateProps = {
shouldChargeForMessages: boolean;
canDisplayChatInTitle?: boolean;
isCurrentUserFrozen?: boolean;
needAgeVideoVerification?: boolean;
privacy: GlobalState['settings']['privacy'];
accountDaysTtl?: number;
};
@ -64,6 +66,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
shouldChargeForMessages,
canDisplayChatInTitle,
canSetPasscode,
needAgeVideoVerification,
privacy,
onReset,
isCurrentUserFrozen,
@ -73,7 +76,6 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
openDeleteAccountModal,
loadPrivacySettings,
loadBlockedUsers,
loadContentSettings,
updateContentSettings,
loadGlobalPrivacySettings,
updateGlobalPrivacySettings,
@ -81,13 +83,13 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
setSharedSettingOption,
openSettingsScreen,
loadAccountDaysTtl,
openAgeVerificationModal,
} = getActions();
useEffect(() => {
if (!isCurrentUserFrozen) {
loadBlockedUsers();
loadPrivacySettings();
loadContentSettings();
loadWebAuthorizations();
}
}, [isCurrentUserFrozen]);
@ -123,6 +125,10 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
updateContentSettings({ isSensitiveEnabled: isChecked });
}, [updateContentSettings]);
const handleAgeVerification = useCallback(() => {
openAgeVerificationModal();
}, [openAgeVerificationModal]);
const handleOpenDeleteAccountModal = useLastCallback(() => {
if (!accountDaysTtl) return;
openDeleteAccountModal({ days: accountDaysTtl });
@ -384,7 +390,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
</div>
{canChangeSensitive && (
<div className="settings-item">
<div className="settings-item fluid-container">
<h4 className="settings-item-header" dir={oldLang.isRtl ? 'rtl' : undefined}>
{oldLang('lng_settings_sensitive_title')}
</h4>
@ -392,9 +398,23 @@ const SettingsPrivacy: FC<OwnProps & StateProps> = ({
label={oldLang('lng_settings_sensitive_disable_filtering')}
subLabel={oldLang('lng_settings_sensitive_about')}
checked={Boolean(isSensitiveEnabled)}
disabled={!canChangeSensitive}
disabled={!canChangeSensitive || (!isSensitiveEnabled && needAgeVideoVerification)}
onCheck={handleUpdateContentSettings}
/>
{!isSensitiveEnabled && needAgeVideoVerification && (
<Button
color="primary"
fluid
size="smaller"
noForcedUpperCase
className="settings-unlock-button"
onClick={handleAgeVerification}
>
<span className="settings-unlock-button-title">
{lang('ButtonAgeVerification')}
</span>
</Button>
)}
</div>
)}
@ -475,6 +495,7 @@ export default memo(withGlobal<OwnProps>(
canChangeSensitive,
shouldNewNonContactPeersRequirePremium,
shouldChargeForMessages,
needAgeVideoVerification: Boolean(appConfig?.needAgeVideoVerification),
privacy,
canDisplayChatInTitle,
canSetPasscode: selectCanSetPasscode(global),

View File

@ -257,6 +257,7 @@ const Main = ({
loadAllChats,
loadAllStories,
loadAllHiddenStories,
loadContentSettings,
} = getActions();
if (DEBUG && !DEBUG_isLogged) {
@ -329,6 +330,7 @@ const Main = ({
loadAllChats({ listType: 'saved' });
loadAllStories();
loadAllHiddenStories();
loadContentSettings();
loadRecentReactions();
loadDefaultTagReactions();
loadAttachBots();

View File

@ -22,6 +22,7 @@ import {
selectChat,
selectChatFullInfo,
selectIsChatBotNotStarted,
selectIsChatRestricted,
selectIsChatWithSelf,
selectIsCurrentUserFrozen,
selectIsInSelectMode,
@ -477,7 +478,8 @@ export default memo(withGlobal<OwnProps>(
const isPrivate = isUserId(chatId);
const { doNotTranslate } = global.settings.byKey;
if (!chat || chat.isRestricted || selectIsInSelectMode(global)) {
const isRestricted = selectIsChatRestricted(global, chatId);
if (!chat || isRestricted || selectIsInSelectMode(global)) {
return {
noMenu: true,
language,

View File

@ -32,6 +32,7 @@ import {
selectChat,
selectChatFullInfo,
selectCurrentMessageList,
selectIsChatRestricted,
selectIsChatWithSelf,
selectIsCurrentUserFrozen,
selectIsRightColumnShown,
@ -842,7 +843,8 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>(
(global, { chatId, threadId }): StateProps => {
const chat = selectChat(global, chatId);
if (!chat || chat.isRestricted) {
const isRestricted = selectIsChatRestricted(global, chatId);
if (!chat || isRestricted) {
return {};
}
const isPrivate = isUserId(chat.id);

View File

@ -58,6 +58,8 @@ import {
selectTranslationLanguage,
selectUserFullInfo,
} from '../../global/selectors';
import { selectIsChatRestricted } from '../../global/selectors/chats';
import { selectActiveRestrictionReasons } from '../../global/selectors/messages';
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
import buildClassName from '../../util/buildClassName';
import { isUserId } from '../../util/entities/ids';
@ -125,7 +127,7 @@ type StateProps = {
firstUnreadId?: number;
isViewportNewest?: boolean;
isRestricted?: boolean;
restrictionReason?: ApiRestrictionReason;
restrictionReasons?: ApiRestrictionReason[];
focusingId?: number;
isSelectModeActive?: boolean;
lastMessage?: ApiMessage;
@ -189,7 +191,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
isComments,
isViewportNewest,
isRestricted,
restrictionReason,
restrictionReasons,
isEmptyThread,
focusingId,
isSelectModeActive,
@ -719,7 +721,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
{isRestricted ? (
<div className="empty">
<span>
{restrictionReason ? restrictionReason.text : `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
{restrictionReasons?.[0]?.text || `This is a private ${isChannelChat ? 'channel' : 'chat'}`}
</span>
</div>
) : paidMessagesStars && !hasMessages && !hasCustomGreeting ? (
@ -802,7 +804,8 @@ export default memo(withGlobal<OwnProps>(
return { currentUserId };
}
const { isRestricted, restrictionReason } = chat;
const isRestricted = selectIsChatRestricted(global, chatId);
const restrictionReasons = selectActiveRestrictionReasons(global, chat?.restrictionReasons);
const lastMessage = selectChatLastMessage(global, chatId, isSavedDialog ? 'saved' : 'all');
const focusingId = selectFocusedMessageId(global, chatId);
@ -836,7 +839,7 @@ export default memo(withGlobal<OwnProps>(
areAdsEnabled,
isChatLoaded: true,
isRestricted,
restrictionReason,
restrictionReasons,
isChannelChat: isChatChannel(chat),
isChatMonoforum: isChatMonoforum(chat),
isGroupChat: isChatGroup(chat),

View File

@ -22,6 +22,7 @@ import {
selectForwardedSender,
selectIsChatWithSelf,
selectIsCurrentUserPremium,
selectIsMediaNsfw,
selectSender,
selectTabState,
} from '../../../global/selectors';
@ -67,14 +68,15 @@ type StateProps = {
currentUserId?: string;
forwardMessageIds?: number[];
fromChatId?: string;
isMediaNsfw?: boolean;
};
type OwnProps = {
onClear?: () => void;
shouldForceShowEditing?: boolean;
chatId: string;
threadId: ThreadId;
messageListType: MessageListType;
onClear?: () => void;
};
const CLOSE_DURATION = 350;
@ -94,7 +96,6 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
isCurrentUserPremium,
isContextMenuDisabled,
isReplyToDiscussion,
onClear,
isInChangingRecipientMode,
shouldPreventComposerAnimation,
senderChat,
@ -103,6 +104,8 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
isSenderChannel,
forwardMessageIds,
fromChatId,
isMediaNsfw,
onClear,
}) => {
const {
resetDraftReplyInfo,
@ -301,6 +304,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
className="inside-input"
replyInfo={replyInfo}
suggestedPostInfo={suggestedPostInfo}
isMediaNsfw={isMediaNsfw}
isInComposer
message={strippedMessage}
sender={!noAuthors ? sender : undefined}
@ -495,6 +499,8 @@ export default memo(withGlobal<OwnProps>(
const isReplyToDiscussion = replyInfo?.replyToMsgId === threadId && !replyInfo.replyToPeerId;
const isMediaNsfw = message && selectIsMediaNsfw(global, message);
return {
replyInfo,
suggestedPostInfo,
@ -516,6 +522,7 @@ export default memo(withGlobal<OwnProps>(
isSenderChannel,
forwardMessageIds,
fromChatId,
isMediaNsfw,
};
},
)(ComposerEmbeddedMessage));

View File

@ -86,11 +86,13 @@ import {
selectDefaultReaction,
selectForwardedSender,
selectIsChatProtected,
selectIsChatRestricted,
selectIsChatWithSelf,
selectIsCurrentUserFrozen,
selectIsCurrentUserPremium,
selectIsDocumentGroupSelected,
selectIsInSelectMode,
selectIsMediaNsfw,
selectIsMessageFocused,
selectIsMessageProtected,
selectIsMessageSelected,
@ -211,9 +213,6 @@ type MessagePositionProperties = {
type OwnProps =
{
message: ApiMessage;
observeIntersectionForBottom?: ObserveFn;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
album?: IAlbum;
noAvatars?: boolean;
withAvatar?: boolean;
@ -226,6 +225,9 @@ type OwnProps =
isJustAdded: boolean;
memoFirstUnreadIdRef?: { current: number | undefined };
getIsMessageListReady?: Signal<boolean>;
observeIntersectionForBottom?: ObserveFn;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
}
& MessagePositionProperties;
@ -316,6 +318,8 @@ type StateProps = {
isChatWithUser?: boolean;
isAccountFrozen?: boolean;
minFutureTime?: number;
isMediaNsfw?: boolean;
isReplyMediaNsfw?: boolean;
};
type MetaPosition =
@ -437,11 +441,13 @@ const Message: FC<OwnProps & StateProps> = ({
poll,
maxTimestamp,
lastPlaybackTimestamp,
onIntersectPinnedMessage,
isMediaNsfw,
isReplyMediaNsfw,
paidMessageStars,
isChatWithUser,
isAccountFrozen,
minFutureTime,
onIntersectPinnedMessage,
}) => {
const {
toggleMessageSelection,
@ -1119,6 +1125,7 @@ const Message: FC<OwnProps & StateProps> = ({
senderChat={replyMessageChat}
forwardSender={replyMessageForwardSender}
chatTranslations={chatTranslations}
isMediaNsfw={isReplyMediaNsfw}
requestedChatTranslationLanguage={requestedChatTranslationLanguage}
observeIntersectionForLoading={observeIntersectionForLoading}
observeIntersectionForPlaying={observeIntersectionForPlaying}
@ -1145,6 +1152,7 @@ const Message: FC<OwnProps & StateProps> = ({
shouldLoop={shouldLoopStickers}
shouldPlayEffect={shouldPlayEffect}
withEffect={withAnimatedEffects}
isMediaNsfw={isMediaNsfw}
onStopEffect={hideEffect}
/>
)}
@ -1443,6 +1451,7 @@ const Message: FC<OwnProps & StateProps> = ({
isProtected={isProtected}
asForwarded={asForwarded}
theme={theme}
isMediaNsfw={isMediaNsfw}
forcedWidth={contentWidth}
onClick={handlePhotoMediaClick}
onCancelUpload={handleCancelUpload}
@ -1462,6 +1471,7 @@ const Message: FC<OwnProps & StateProps> = ({
isDownloading={isDownloading}
isProtected={isProtected}
asForwarded={asForwarded}
isMediaNsfw={isMediaNsfw}
lastPlaybackTimestamp={lastPlaybackTimestamp}
onClick={handleVideoMediaClick}
onCancelUpload={handleCancelUpload}
@ -1895,7 +1905,7 @@ export default memo(withGlobal<OwnProps>(
const replyMessageChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined;
const isReplyPrivate = !isSystemBotChat && !isAnonymousForwards && replyMessageChat
&& !isChatPublic(replyMessageChat)
&& (replyMessageChat.isNotJoined || replyMessageChat.isRestricted);
&& (replyMessageChat.isNotJoined || selectIsChatRestricted(global, replyMessageChat.id));
const isReplyToTopicStart = replyMessage?.content.action?.type === 'topicCreate';
const replyStory = storyReplyId && storyReplyPeerId
? selectPeerStory(global, storyReplyPeerId, storyReplyId)
@ -1982,6 +1992,9 @@ export default memo(withGlobal<OwnProps>(
const minFutureTime = global.appConfig?.starsSuggestedPostFutureMin || STARS_SUGGESTED_POST_FUTURE_MIN;
const isMediaNsfw = selectIsMediaNsfw(global, message);
const isReplyMediaNsfw = replyMessage && selectIsMediaNsfw(global, replyMessage);
return {
theme: selectTheme(global),
forceSenderName,
@ -2076,6 +2089,8 @@ export default memo(withGlobal<OwnProps>(
paidMessageStars,
isChatWithUser,
isAccountFrozen,
isMediaNsfw,
isReplyMediaNsfw,
};
},
)(Message));

View File

@ -1,5 +1,6 @@
import type React from '../../../lib/teact/teact';
import { useEffect, useRef, useState } from '../../../lib/teact/teact';
import { memo, useEffect, useRef, useState } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiMediaExtendedPreview, ApiPhoto } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
@ -28,6 +29,7 @@ import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import Icon from '../../common/icons/Icon';
import MediaSpoiler from '../../common/MediaSpoiler';
import SensitiveContentConfirmModal from '../../common/SensitiveContentConfirmModal';
import ProgressSpinner from '../../ui/ProgressSpinner';
export type OwnProps<T> = {
@ -36,7 +38,6 @@ export type OwnProps<T> = {
isInWebPage?: boolean;
messageText?: string;
isOwn?: boolean;
observeIntersection?: ObserveFn;
noAvatars?: boolean;
canAutoLoad?: boolean;
isInSelectMode?: boolean;
@ -53,16 +54,21 @@ export type OwnProps<T> = {
theme: ThemeKey;
className?: string;
clickArg?: T;
isMediaNsfw?: boolean;
observeIntersection?: ObserveFn;
onClick?: (arg: T, e: React.MouseEvent<HTMLElement>) => void;
onCancelUpload?: (arg: T) => void;
};
type StateProps = {
needsAgeVerification?: boolean;
};
const Photo = <T,>({
id,
photo,
messageText,
isOwn,
observeIntersection,
noAvatars,
canAutoLoad,
isInSelectMode,
@ -80,9 +86,12 @@ const Photo = <T,>({
isInWebPage,
clickArg,
className,
isMediaNsfw,
observeIntersection,
onClick,
onCancelUpload,
}: OwnProps<T>) => {
needsAgeVerification,
}: OwnProps<T> & StateProps) => {
const ref = useRef<HTMLDivElement>();
const isPaidPreview = photo.mediaType === 'extendedMediaPreview';
@ -106,15 +115,29 @@ const Photo = <T,>({
const blurredBackgroundRef = useBlurredMediaThumbRef(photo, !withBlurredBackground);
const thumbDataUri = getMediaThumbUri(photo);
const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(isPaidPreview || photo.isSpoiler);
const { updateContentSettings, openAgeVerificationModal } = getActions();
const [isNsfwModalOpen, openNsfwModal, closeNsfwModal] = useFlag();
const [shouldAlwaysShowNsfw, setShouldAlwaysShowNsfw] = useState(false);
const shouldShowSpoiler = isPaidPreview || photo.isSpoiler || isMediaNsfw;
const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(shouldShowSpoiler);
useEffect(() => {
if (isPaidPreview || photo.isSpoiler) {
if (shouldShowSpoiler) {
showSpoiler();
} else {
hideSpoiler();
}
}, [isPaidPreview, photo]);
}, [shouldShowSpoiler]);
const handleNsfwConfirm = useLastCallback(() => {
closeNsfwModal();
hideSpoiler();
if (shouldAlwaysShowNsfw) {
updateContentSettings({ isSensitiveEnabled: true });
}
});
const {
loadProgress: downloadProgress,
@ -162,6 +185,14 @@ const Photo = <T,>({
}
if (isSpoilerShown) {
if (isMediaNsfw) {
if (needsAgeVerification) {
openAgeVerificationModal();
return;
}
openNsfwModal();
return;
}
hideSpoiler();
return;
}
@ -250,6 +281,7 @@ const Photo = <T,>({
width={width}
height={height}
className="media-spoiler"
isNsfw={isMediaNsfw}
/>
{isTransferring && (
<span className="message-transfer-progress">
@ -257,8 +289,22 @@ const Photo = <T,>({
%
</span>
)}
<SensitiveContentConfirmModal
isOpen={isNsfwModalOpen}
onClose={closeNsfwModal}
shouldAlwaysShow={shouldAlwaysShowNsfw}
onAlwaysShowChanged={setShouldAlwaysShowNsfw}
confirmHandler={handleNsfwConfirm}
/>
</div>
);
};
export default Photo;
export default memo(withGlobal((global): StateProps => {
const appConfig = global.appConfig;
const needsAgeVerification = appConfig?.needAgeVideoVerification;
return {
needsAgeVerification,
};
})(Photo));

View File

@ -1,12 +1,12 @@
import type { FC } from '../../../lib/teact/teact';
import { useEffect, useRef } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import { memo, useEffect, useRef, useState } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiMessage } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
import { ApiMediaFormat } from '../../../api/types';
import { getStickerMediaHash } from '../../../global/helpers';
import { getMediaThumbUri, getStickerMediaHash } from '../../../global/helpers';
import { IS_WEBM_SUPPORTED } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName';
import { getStickerDimensions } from '../../common/helpers/mediaDimensions';
@ -20,6 +20,8 @@ import useOldLang from '../../../hooks/useOldLang';
import useOverlayPosition from './hooks/useOverlayPosition';
import AnimatedSticker from '../../common/AnimatedSticker';
import MediaSpoiler from '../../common/MediaSpoiler';
import SensitiveContentConfirmModal from '../../common/SensitiveContentConfirmModal';
import StickerView from '../../common/StickerView';
import Portal from '../../ui/Portal';
@ -30,19 +32,31 @@ const EFFECT_SIZE_MULTIPLIER = 1 + 0.245 * 2;
type OwnProps = {
message: ApiMessage;
observeIntersection: ObserveFn;
observeIntersectionForPlaying: ObserveFn;
shouldLoop?: boolean;
shouldPlayEffect?: boolean;
withEffect?: boolean;
isMediaNsfw?: boolean;
observeIntersection: ObserveFn;
observeIntersectionForPlaying: ObserveFn;
onStopEffect?: VoidFunction;
};
const Sticker: FC<OwnProps> = ({
message, observeIntersection, observeIntersectionForPlaying, shouldLoop,
shouldPlayEffect, withEffect, onStopEffect,
type StateProps = {
needsAgeVerification?: boolean;
};
const Sticker: FC<OwnProps & StateProps> = ({
message,
shouldLoop,
shouldPlayEffect,
withEffect,
isMediaNsfw,
onStopEffect,
observeIntersection,
observeIntersectionForPlaying,
needsAgeVerification,
}) => {
const { showNotification, openStickerSet } = getActions();
const { showNotification, openStickerSet, updateContentSettings, openAgeVerificationModal } = getActions();
const lang = useOldLang();
const { isMobile } = useAppLayout();
@ -55,6 +69,29 @@ const Sticker: FC<OwnProps> = ({
const { stickerSetInfo, isVideo, hasEffect } = sticker;
const isMirrored = !message.isOutgoing;
const [isNsfwModalOpen, openNsfwModal, closeNsfwModal] = useFlag();
const [shouldAlwaysShowNsfw, setShouldAlwaysShowNsfw] = useState(false);
const shouldShowSpoiler = isMediaNsfw;
const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(shouldShowSpoiler);
useEffect(() => {
if (shouldShowSpoiler) {
showSpoiler();
} else {
hideSpoiler();
}
}, [shouldShowSpoiler]);
const handleNsfwConfirm = useLastCallback(() => {
closeNsfwModal();
hideSpoiler();
if (shouldAlwaysShowNsfw) {
updateContentSettings({ isSensitiveEnabled: true });
}
});
const mediaHash = sticker.isPreloadedGlobally ? undefined : (
getStickerMediaHash(sticker, isVideo && !IS_WEBM_SUPPORTED ? 'pictogram' : 'inline')
);
@ -69,6 +106,8 @@ const Sticker: FC<OwnProps> = ({
);
const [isPlayingEffect, startPlayingEffect, stopPlayingEffect] = useFlag();
const thumbDataUri = getMediaThumbUri(sticker);
const handleEffectEnded = useLastCallback(() => {
stopPlayingEffect();
onStopEffect?.();
@ -95,6 +134,19 @@ const Sticker: FC<OwnProps> = ({
});
const handleClick = useLastCallback(() => {
if (isSpoilerShown) {
if (isMediaNsfw) {
if (needsAgeVerification) {
openAgeVerificationModal();
return;
}
openNsfwModal();
return;
}
hideSpoiler();
return;
}
if (hasEffect) {
if (isPlayingEffect || !withEffect) {
showNotification({
@ -143,6 +195,15 @@ const Sticker: FC<OwnProps> = ({
noPlay={!canPlay}
withSharedAnimation
/>
<MediaSpoiler
isVisible={isSpoilerShown}
withAnimation
thumbDataUri={thumbDataUri}
width={width}
height={height}
className="media-spoiler"
isNsfw={isMediaNsfw}
/>
{shouldRenderEffect && (
<Portal>
<AnimatedSticker
@ -158,8 +219,22 @@ const Sticker: FC<OwnProps> = ({
/>
</Portal>
)}
<SensitiveContentConfirmModal
isOpen={isNsfwModalOpen}
onClose={closeNsfwModal}
shouldAlwaysShow={shouldAlwaysShowNsfw}
onAlwaysShowChanged={setShouldAlwaysShowNsfw}
confirmHandler={handleNsfwConfirm}
/>
</div>
);
};
export default Sticker;
export default memo(withGlobal<OwnProps>((global): StateProps => {
const appConfig = global.appConfig;
const needsAgeVerification = appConfig?.needAgeVideoVerification;
return {
needsAgeVerification,
};
})(Sticker));

View File

@ -1,6 +1,6 @@
import type React from '../../../lib/teact/teact';
import { useEffect, useRef, useState } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import { memo, useEffect, useRef, useState } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { ApiMediaExtendedPreview, ApiVideo } from '../../../api/types';
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
@ -29,6 +29,7 @@ import useBlurredMediaThumbRef from './hooks/useBlurredMediaThumbRef';
import Icon from '../../common/icons/Icon';
import MediaSpoiler from '../../common/MediaSpoiler';
import SensitiveContentConfirmModal from '../../common/SensitiveContentConfirmModal';
import OptimizedVideo from '../../ui/OptimizedVideo';
import ProgressSpinner from '../../ui/ProgressSpinner';
@ -38,8 +39,6 @@ export type OwnProps<T> = {
lastPlaybackTimestamp?: number;
isOwn?: boolean;
isInWebPage?: boolean;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
noAvatars?: boolean;
canAutoLoad?: boolean;
canAutoPlay?: boolean;
@ -51,17 +50,22 @@ export type OwnProps<T> = {
isProtected?: boolean;
className?: string;
clickArg?: T;
isMediaNsfw?: boolean;
observeIntersectionForLoading?: ObserveFn;
observeIntersectionForPlaying?: ObserveFn;
onClick?: (arg: T, e: React.MouseEvent<HTMLElement>) => void;
onCancelUpload?: (arg: T) => void;
};
type StateProps = {
needsAgeVerification?: boolean;
};
const Video = <T,>({
id,
video,
isOwn,
isInWebPage,
observeIntersectionForLoading,
observeIntersectionForPlaying,
noAvatars,
canAutoLoad,
canAutoPlay,
@ -74,26 +78,42 @@ const Video = <T,>({
className,
lastPlaybackTimestamp,
clickArg,
isMediaNsfw,
observeIntersectionForLoading,
observeIntersectionForPlaying,
onClick,
onCancelUpload,
}: OwnProps<T>) => {
const { cancelMediaDownload } = getActions();
needsAgeVerification,
}: OwnProps<T> & StateProps) => {
const { cancelMediaDownload, updateContentSettings, openAgeVerificationModal } = getActions();
const ref = useRef<HTMLDivElement>();
const videoRef = useRef<HTMLVideoElement>();
const [isNsfwModalOpen, openNsfwModal, closeNsfwModal] = useFlag();
const [shouldAlwaysShowNsfw, setShouldAlwaysShowNsfw] = useState(false);
const isPaidPreview = video.mediaType === 'extendedMediaPreview';
const localBlobUrl = !isPaidPreview ? video.blobUrl : undefined;
const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(isPaidPreview || video.isSpoiler);
const shouldShowSpoiler = isPaidPreview || video.isSpoiler || isMediaNsfw;
const [isSpoilerShown, showSpoiler, hideSpoiler] = useFlag(shouldShowSpoiler);
useEffect(() => {
if (isPaidPreview || video.isSpoiler) {
if (shouldShowSpoiler) {
showSpoiler();
} else {
hideSpoiler();
}
}, [isPaidPreview, video]);
}, [shouldShowSpoiler]);
const handleNsfwConfirm = useLastCallback(() => {
closeNsfwModal();
hideSpoiler();
if (shouldAlwaysShowNsfw) {
updateContentSettings({ isSensitiveEnabled: true });
}
});
const isIntersectingForLoading = useIsIntersecting(ref, observeIntersectionForLoading);
const isIntersectingForPlaying = (
@ -204,6 +224,14 @@ const Video = <T,>({
}
if (isSpoilerShown) {
if (isMediaNsfw) {
if (needsAgeVerification) {
openAgeVerificationModal();
return;
}
openNsfwModal();
return;
}
hideSpoiler();
return;
}
@ -278,6 +306,7 @@ const Video = <T,>({
isVisible={isSpoilerShown}
withAnimation
thumbDataUri={thumbDataUri}
isNsfw={isMediaNsfw}
width={width}
height={height}
className="media-spoiler"
@ -309,8 +338,22 @@ const Video = <T,>({
style={`--_progress: ${Math.floor((lastPlaybackTimestamp / duration) * 100)}%`}
/>
)}
<SensitiveContentConfirmModal
isOpen={isNsfwModalOpen}
onClose={closeNsfwModal}
shouldAlwaysShow={shouldAlwaysShowNsfw}
onAlwaysShowChanged={setShouldAlwaysShowNsfw}
confirmHandler={handleNsfwConfirm}
/>
</div>
);
};
export default Video;
export default memo(withGlobal((global): StateProps => {
const appConfig = global.appConfig;
const needsAgeVerification = appConfig?.needAgeVideoVerification;
return {
needsAgeVerification,
};
})(Video));

View File

@ -10,6 +10,7 @@ import { pick } from '../../util/iteratees';
import VerificationMonetizationModal from '../common/VerificationMonetizationModal.async';
import WebAppsCloseConfirmationModal from '../main/WebAppsCloseConfirmationModal.async';
import AboutAdsModal from './aboutAds/AboutAdsModal.async';
import AgeVerificationModal from './ageVerification/AgeVerificationModal.async';
import AttachBotInstallModal from './attachBotInstall/AttachBotInstallModal.async';
import BoostModal from './boost/BoostModal.async';
import ChatInviteModal from './chatInvite/ChatInviteModal.async';
@ -89,7 +90,8 @@ type ModalKey = keyof Pick<TabState,
'giftTransferModal' |
'chatRefundModal' |
'isFrozenAccountModalOpen' |
'deleteAccountModal'
'deleteAccountModal' |
'isAgeVerificationModalOpen'
>;
type StateProps = {
@ -145,6 +147,7 @@ const MODALS: ModalRegistry = {
chatRefundModal: ChatRefundModal,
isFrozenAccountModalOpen: FrozenAccountModal,
deleteAccountModal: DeleteAccountModal,
isAgeVerificationModalOpen: AgeVerificationModal,
};
const MODAL_KEYS = Object.keys(MODALS) as ModalKey[];
const MODAL_ENTRIES = Object.entries(MODALS) as Entries<ModalRegistry>;

View File

@ -0,0 +1,18 @@
import type { FC } from '../../../lib/teact/teact';
import { memo } from '../../../lib/teact/teact';
import type { OwnProps } from './AgeVerificationModal';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const AgeVerificationModalAsync: FC<OwnProps> = memo((props) => {
const { modal } = props;
const AgeVerificationModal = useModuleLoader(Bundles.Extra, 'AgeVerificationModal', !modal);
return AgeVerificationModal ? <AgeVerificationModal {...props} /> : undefined;
});
export default AgeVerificationModalAsync;

View File

@ -0,0 +1,41 @@
.root {
:global(.modal-dialog) {
max-width: 25rem;
}
}
.content {
padding: 1rem 0;
text-align: center;
}
.header {
display: flex;
justify-content: center;
margin-bottom: 1.5rem;
}
.iconWrapper {
display: flex;
align-items: center;
justify-content: center;
width: 4rem;
height: 4rem;
border-radius: 50%;
background-color: var(--accent-color);
}
.icon {
position: relative;
z-index: 1;
font-size: 2rem;
color: var(--color-white);
}
.mainText,
.description {
font-size: 0.9375rem;
color: var(--color-text);
}

View File

@ -0,0 +1,93 @@
import type { FC } from '../../../lib/teact/teact';
import { memo } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { TabState } from '../../../global/types';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import Icon from '../../common/icons/Icon';
import Button from '../../ui/Button';
import Modal from '../../ui/Modal';
import styles from './AgeVerificationModal.module.scss';
export type OwnProps = {
modal: TabState['isAgeVerificationModalOpen'];
};
type StateProps = {
verifyAgeBotUsername?: string;
};
const AGE_REQUIRED = 18;
const AgeVerificationModal: FC<OwnProps & StateProps> = ({
modal,
verifyAgeBotUsername,
}) => {
const { closeAgeVerificationModal, openChatByUsername } = getActions();
const lang = useLang();
const isOpen = Boolean(modal);
const ageRequired = AGE_REQUIRED;
const handleVerifyAge = useLastCallback(() => {
if (verifyAgeBotUsername) {
openChatByUsername({
shouldStartMainApp: true,
username: verifyAgeBotUsername,
});
}
closeAgeVerificationModal();
});
const handleClose = useLastCallback(() => {
closeAgeVerificationModal();
});
return (
<Modal
isOpen={isOpen}
onClose={handleClose}
className={styles.root}
>
<div className={styles.content}>
<div className={styles.header}>
<div className={styles.iconWrapper}>
<Icon name="user" className={styles.icon} />
</div>
</div>
<h2 className={styles.title}>
{lang('TitleAgeVerificationModal')}
</h2>
<p className={styles.mainText}>
{lang('TextAgeVerificationModal', { count: ageRequired }, {
withMarkdown: true,
withNodes: true,
pluralValue: ageRequired,
})}
</p>
<p className={styles.description}>
{lang('DescriptionAgeVerificationModal')}
</p>
</div>
<div className="dialog-buttons mt-2">
<Button
onClick={handleVerifyAge}
>
{lang('ButtonAgeVerification')}
</Button>
</div>
</Modal>
);
};
export default memo(withGlobal((global): StateProps => {
const appConfig = global.appConfig;
const verifyAgeBotUsername = appConfig?.verifyAgeBotUsername;
return {
verifyAgeBotUsername,
};
})(AgeVerificationModal));

View File

@ -50,6 +50,7 @@ const useWebAppFrame = (
closeWebApp,
openSuggestedStatusModal,
updateWebApp,
updateContentSettings,
} = getActions();
const isReloadSupported = useRef<boolean>(false);
@ -382,6 +383,25 @@ const useWebAppFrame = (
});
}
if (eventType === 'web_app_verify_age') {
const { passed } = eventData;
if (passed) {
showNotification({
message: {
key: 'TitleAgeCheckSuccess',
},
});
updateContentSettings({ isSensitiveEnabled: true });
} else {
showNotification({
message: {
key: 'TitleAgeCheckFailed',
},
});
}
}
onEvent(data);
} catch (err) {
// Ignore other messages

View File

@ -48,6 +48,7 @@ import {
selectChatFullInfo,
selectChatMessages,
selectCurrentSharedMediaSearch,
selectIsChatRestricted,
selectIsCurrentUserPremium,
selectIsRightColumnShown,
selectMonoforumChannel,
@ -995,6 +996,7 @@ export default memo(withGlobal<OwnProps>(
const peerGifts = selectTabState(global).savedGifts.giftsByPeerId[chatId];
const monoforumChannel = selectMonoforumChannel(global, chatId);
const isRestricted = chat && selectIsChatRestricted(global, chat.id);
return {
theme: selectTheme(global),
@ -1012,7 +1014,7 @@ export default memo(withGlobal<OwnProps>(
canDeleteMembers,
currentUserId: global.currentUserId,
isRightColumnShown: selectIsRightColumnShown(global, isMobile),
isRestricted: chat?.isRestricted,
isRestricted,
activeDownloads,
usersById,
userStatusesById,

View File

@ -3,7 +3,7 @@ import type { FC } from '../../../lib/teact/teact';
import {
memo, useEffect, useMemo, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type {
ApiAvailableReaction, ApiChat, ApiChatFullInfo, ApiExportedInvite,
@ -12,7 +12,7 @@ import { ApiMediaFormat } from '../../../api/types';
import { ManagementProgress, ManagementScreens } from '../../../types';
import { getChatAvatarHash, getHasAdminRight, isChatChannel, isChatPublic } from '../../../global/helpers';
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
import { selectChat, selectChatFullInfo, selectIsChatRestricted, selectTabState } from '../../../global/selectors';
import { formatInteger } from '../../../util/textFormat';
import useFlag from '../../../hooks/useFlag';
@ -217,7 +217,8 @@ const ManageChannel: FC<OwnProps & StateProps> = ({
}, [availableReactions, chatFullInfo?.enabledReactions, lang]);
const isChannelPublic = useMemo(() => isChatPublic(chat), [chat]);
if (chat.isRestricted || chat.isForbidden) {
const isRestricted = selectIsChatRestricted(getGlobal(), chatId);
if (isRestricted || chat.isForbidden) {
return undefined;
}

View File

@ -3,7 +3,7 @@ import type { FC } from '../../../lib/teact/teact';
import {
memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import { getActions, getGlobal, withGlobal } from '../../../global';
import type {
ApiAvailableReaction, ApiChat, ApiChatBannedRights, ApiChatFullInfo, ApiExportedInvite,
@ -17,7 +17,7 @@ import {
isChatBasicGroup,
isChatPublic,
} from '../../../global/helpers';
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
import { selectChat, selectChatFullInfo, selectIsChatRestricted, selectTabState } from '../../../global/selectors';
import { debounce } from '../../../util/schedulers';
import { formatInteger } from '../../../util/textFormat';
import renderText from '../../common/helpers/renderText';
@ -312,7 +312,8 @@ const ManageGroup: FC<OwnProps & StateProps> = ({
openChat({ id: undefined });
});
if (chat.isRestricted || chat.isForbidden) {
const isRestricted = selectIsChatRestricted(getGlobal(), chatId);
if (isRestricted || chat.isForbidden) {
return undefined;
}

View File

@ -344,6 +344,8 @@ export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me',
export const WEB_APP_PLATFORM = 'weba';
export const LANG_PACK = 'weba';
export const NSFW_RESTRICTION_REASON = 'sensitive';
// eslint-disable-next-line @stylistic/max-len
export const COUNTRIES_WITH_12H_TIME_FORMAT = new Set(['AU', 'BD', 'CA', 'CO', 'EG', 'HN', 'IE', 'IN', 'JO', 'MX', 'MY', 'NI', 'NZ', 'PH', 'PK', 'SA', 'SV', 'US']);

View File

@ -1633,7 +1633,8 @@ addActionHandler('acceptChatInvite', async (global, actions, payload): Promise<v
addActionHandler('openChatByUsername', async (global, actions, payload): Promise<void> => {
const {
username, messageId, commentId, startParam, startAttach, attach, threadId, originalParts, startApp, mode,
username, messageId, commentId, startParam, startAttach, attach, threadId, originalParts,
startApp, shouldStartMainApp, mode,
text, onChatChanged, choose, ref, timestamp,
tabId = getCurrentTabId(),
} = payload;
@ -1661,7 +1662,7 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
return;
}
if (startApp !== undefined && !webAppName) {
if ((startApp !== undefined && !webAppName) || shouldStartMainApp) {
const theme = extractCurrentThemeParams();
const chatByUsername = await fetchChatByUsername(global, username);
global = getGlobal();

View File

@ -126,6 +126,7 @@ import {
selectForwardsCanBeSentToChat,
selectForwardsContainVoiceMessages,
selectIsChatBotNotStarted,
selectIsChatRestricted,
selectIsChatWithSelf,
selectIsCurrentUserFrozen,
selectIsCurrentUserPremium,
@ -187,8 +188,9 @@ addActionHandler('loadViewportMessages', (global, actions, payload): ActionRetur
}
const chat = selectChat(global, chatId);
// TODO Revise if `chat.isRestricted` check is needed
if (!chat || chat.isRestricted) {
const isRestricted = selectIsChatRestricted(global, chatId);
// TODO Revise if `isRestricted` check is needed
if (!chat || isRestricted) {
onError?.();
return;
}

View File

@ -31,6 +31,7 @@ import { updateTabState } from '../../reducers/tabs';
import {
selectChat,
selectChatFullInfo,
selectIsChatRestricted,
selectIsCurrentUserFrozen,
selectIsCurrentUserPremium,
selectPeer,
@ -309,6 +310,10 @@ addActionHandler('loadMoreProfilePhotos', async (global, actions, payload): Prom
const user = isPrivate ? selectUser(global, peerId) : undefined;
const chat = !isPrivate ? selectChat(global, peerId) : undefined;
const peer = user || chat;
if (chat && selectIsChatRestricted(global, peerId)) {
return;
}
const profilePhotos = selectPeerPhotos(global, peerId);
if (!peer?.avatarPhotoId) {
return;

View File

@ -44,7 +44,7 @@ import {
const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds
const INVALIDATE_FULL_CHAT_FIELDS = new Set<keyof ApiChat>([
'boostLevel', 'isForum', 'isLinkedInDiscussion', 'fakeType', 'restrictionReason', 'isJoinToSend', 'isJoinRequest',
'boostLevel', 'isForum', 'isLinkedInDiscussion', 'fakeType', 'restrictionReasons', 'isJoinToSend', 'isJoinRequest',
'type',
]);

View File

@ -41,3 +41,19 @@ addActionHandler('closeDeleteAccountModal', (global, actions, payload): ActionRe
deleteAccountModal: undefined,
}, tabId);
});
addActionHandler('openAgeVerificationModal', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
return updateTabState(global, {
isAgeVerificationModalOpen: true,
}, tabId);
});
addActionHandler('closeAgeVerificationModal', (global, actions, payload): ActionReturnType => {
const { tabId = getCurrentTabId() } = payload || {};
return updateTabState(global, {
isAgeVerificationModalOpen: false,
}, tabId);
});

View File

@ -25,6 +25,7 @@ import {
import { formatDateToString, formatTime } from '../../util/dates/dateFormat';
import { getPeerIdDividend, isUserId } from '../../util/entities/ids';
import { getServerTime } from '../../util/serverTime';
import { selectIsChatRestricted } from '../selectors';
import { getGlobal } from '..';
import { isSystemBot } from './bots';
import { getMainUsername } from './users';
@ -155,7 +156,9 @@ export function getCanPostInChat(
}
}
if (chat.isRestricted || chat.isForbidden || chat.migratedTo
const global = getGlobal();
const isRestricted = selectIsChatRestricted(global, chat.id);
if (isRestricted || chat.isForbidden || chat.migratedTo
|| (chat.isNotJoined && !isChatMonoforum(chat) && !isMessageThread)
|| isSystemBot(chat.id) || isAnonymousForwardsChat(chat.id)) {
return false;
@ -380,7 +383,9 @@ export function getGroupStatus(lang: OldLangFn, chat: ApiChat) {
const chatTypeString = lang(getChatTypeString(chat));
const { membersCount } = chat;
if (chat.isRestricted) {
const global = getGlobal();
const isRestricted = selectIsChatRestricted(global, chat.id);
if (isRestricted) {
return chatTypeString === 'Channel' ? 'channel is inaccessible' : 'group is inaccessible';
}

View File

@ -20,6 +20,7 @@ import {
isUserBot,
isUserOnline,
} from '../helpers';
import { selectActiveRestrictionReasons } from './messages';
import { selectTabState } from './tabs';
import {
selectBot, selectIsCurrentUserPremium, selectUser, selectUserFullInfo,
@ -374,3 +375,11 @@ export function selectMonoforumChannel<T extends GlobalState>(
return chat.isMonoforum ? selectChat(global, chat.linkedMonoforumId!) : undefined;
}
export function selectIsChatRestricted<T extends GlobalState>(global: T, chatId: string): boolean {
const chat = selectChat(global, chatId);
if (!chat) return false;
const activeRestrictions = selectActiveRestrictionReasons(global, chat.restrictionReasons);
return activeRestrictions.length > 0;
}

View File

@ -7,7 +7,7 @@ import {
isAnonymousForwardsChat,
isChatAdmin, isChatGroup, isUserBot,
} from '../helpers';
import { selectChat, selectIsChatWithSelf } from './chats';
import { selectChat, selectIsChatRestricted, selectIsChatWithSelf } from './chats';
import { selectCurrentMessageList } from './messages';
import { selectTabState } from './tabs';
import { selectBot, selectUser } from './users';
@ -72,7 +72,8 @@ export function selectCanManage<T extends GlobalState>(
chatId: string,
) {
const chat = selectChat(global, chatId);
if (!chat || chat.isRestricted || chat.isMonoforum) return false;
const isRestricted = selectIsChatRestricted(global, chatId);
if (!chat || isRestricted || chat.isMonoforum) return false;
const isPrivate = isUserId(chat.id);
const user = isPrivate ? selectUser(global, chatId) : undefined;

View File

@ -5,7 +5,7 @@ import type {
ApiMessageEntityCustomEmoji,
ApiMessageForwardInfo,
ApiMessageOutgoingStatus,
ApiPeer, ApiSponsoredMessage,
ApiPeer, ApiRestrictionReason, ApiSponsoredMessage,
ApiStickerSetInfo,
} from '../../api/types';
import type {
@ -22,8 +22,8 @@ import type {
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
import {
ANONYMOUS_USER_ID, API_GENERAL_ID_LIMIT, GENERAL_TOPIC_ID, SERVICE_NOTIFICATIONS_USER_ID,
SVG_EXTENSIONS,
ANONYMOUS_USER_ID, API_GENERAL_ID_LIMIT, GENERAL_TOPIC_ID, NSFW_RESTRICTION_REASON, SERVICE_NOTIFICATIONS_USER_ID,
SVG_EXTENSIONS, WEB_APP_PLATFORM,
} from '../../config';
import { IS_TRANSLATION_SUPPORTED } from '../../util/browser/windowEnvironment';
import { isUserId } from '../../util/entities/ids';
@ -72,11 +72,13 @@ import {
selectChat,
selectChatFullInfo,
selectChatLastMessageId,
selectIsChatRestricted,
selectIsChatWithBot,
selectIsChatWithSelf,
selectRequestedChatTranslationLanguage,
} from './chats';
import { selectPeer, selectPeerPaidMessagesStars } from './peers';
import { selectSettingsKeys } from './settings';
import { selectPeerStory } from './stories';
import { selectIsStickerFavorite } from './symbols';
import { selectTabState } from './tabs';
@ -587,7 +589,8 @@ export function selectThreadIdFromMessage<T extends GlobalState>(global: T, mess
export function selectCanReplyToMessage<T extends GlobalState>(global: T, message: ApiMessage, threadId: ThreadId) {
const chat = selectChat(global, message.chatId);
if (!chat || chat.isRestricted || chat.isForbidden) return false;
const isRestricted = selectIsChatRestricted(global, message.chatId);
if (!chat || isRestricted || chat.isForbidden) return false;
const isLocal = isMessageLocal(message);
const isServiceNotification = isServiceNotificationMessage(message);
@ -632,7 +635,8 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
global: T, message: ApiMessage, threadId: ThreadId,
) {
const chat = selectChat(global, message.chatId);
if (!chat || chat.isRestricted) {
const isRestricted = selectIsChatRestricted(global, message.chatId);
if (!chat || isRestricted) {
return {};
}
@ -1563,3 +1567,31 @@ export function selectMessageLastPlaybackTimestamp<T extends GlobalState>(
) {
return global.messages.playbackByChatId[chatId]?.byId[messageId];
}
export function selectActiveRestrictionReasons<T extends GlobalState>(
global: T, restrictionReasons?: ApiRestrictionReason[],
): ApiRestrictionReason[] {
if (!restrictionReasons) return [];
const { ignoreRestrictionReasons } = global.appConfig || {};
return restrictionReasons.filter((reason) => {
const isForCurrentPlatform = reason.platform === 'all' || reason.platform === WEB_APP_PLATFORM;
if (!isForCurrentPlatform) return false;
const shouldIgnore = ignoreRestrictionReasons?.includes(reason.reason);
return !shouldIgnore;
});
}
export function selectIsMediaNsfw<T extends GlobalState>(global: T, message: ApiMessage) {
const { isSensitiveEnabled } = selectSettingsKeys(global);
const chat = selectChat(global, message.chatId);
if (isSensitiveEnabled) return false;
const chatActiveRestrictions = selectActiveRestrictionReasons(global, chat?.restrictionReasons);
const messageActiveRestrictions = selectActiveRestrictionReasons(global, message.restrictionReasons);
return chatActiveRestrictions.some((reason) => reason.reason === NSFW_RESTRICTION_REASON)
|| messageActiveRestrictions.some((reason) => reason.reason === NSFW_RESTRICTION_REASON);
}

View File

@ -656,6 +656,7 @@ export interface ActionPayloads {
startAttach?: string;
attach?: string;
startApp?: string;
shouldStartMainApp?: boolean;
mode?: string;
choose?: ApiChatType[];
text?: string;
@ -1081,6 +1082,8 @@ export interface ActionPayloads {
days: number;
} & WithTabId | undefined;
closeDeleteAccountModal: WithTabId | undefined;
openAgeVerificationModal: WithTabId | undefined;
closeAgeVerificationModal: WithTabId | undefined;
setAccountTTL: {
days: number;
} & WithTabId | undefined;

View File

@ -754,6 +754,8 @@ export type TabState = {
selfDestructAccountDays: number;
};
isAgeVerificationModalOpen?: boolean;
paidReactionModal?: {
chatId: string;
messageId: number;

View File

@ -4,7 +4,7 @@ import { getActions, getGlobal } from '../../global';
import type { ApiChat, ApiUser } from '../../api/types';
import { isChatChannel, isUserBot } from '../../global/helpers';
import { selectPeer, selectUserStatus } from '../../global/selectors';
import { selectIsChatRestricted, selectPeer, selectUserStatus } from '../../global/selectors';
import { isUserId } from '../../util/entities/ids';
import { throttle } from '../../util/schedulers';
@ -54,7 +54,8 @@ export default function usePeerStoriesPolling(ids?: string[]) {
return !user.isContact && !user.isSelf && !isUserBot(user) && !peer.isSupport && isStatusAvailable;
} else {
const chat = peer as ApiChat;
return isChatChannel(chat) && !chat.isRestricted;
const isRestricted = selectIsChatRestricted(global, chat.id);
return isChatChannel(chat) && !isRestricted;
}
}).map((user) => user.id);
}, [peers]);

View File

@ -12,6 +12,6 @@ for (const tl of Object.values(Api)) {
}
}
export const LAYER = 207;
export const LAYER = 210;
export { tlobjects };

View File

@ -397,6 +397,8 @@ namespace Api {
export type TypeTodoList = TodoList;
export type TypeTodoCompletion = TodoCompletion;
export type TypeSuggestedPost = SuggestedPost;
export type TypeStarsRating = StarsRating;
export type TypeStarGiftCollection = StarGiftCollection;
export type TypeResPQ = ResPQ;
export type TypeP_Q_inner_data = PQInnerData | PQInnerDataDc | PQInnerDataTemp | PQInnerDataTempDc;
export type TypeServer_DH_Params = ServerDHParamsFail | ServerDHParamsOk;
@ -612,6 +614,7 @@ namespace Api {
export type TypeSavedStarGifts = payments.SavedStarGifts;
export type TypeStarGiftWithdrawalUrl = payments.StarGiftWithdrawalUrl;
export type TypeResaleStarGifts = payments.ResaleStarGifts;
export type TypeStarGiftCollections = payments.StarGiftCollectionsNotModified | payments.StarGiftCollections;
}
export namespace phone {
@ -3918,6 +3921,7 @@ namespace Api {
botVerification?: Api.TypeBotVerification;
sendPaidMessagesStars?: long;
disallowedGifts?: Api.TypeDisallowedGiftsSettings;
starsRating?: Api.TypeStarsRating;
}> {
// flags: Api.Type;
blocked?: true;
@ -3969,7 +3973,8 @@ namespace Api {
botVerification?: Api.TypeBotVerification;
sendPaidMessagesStars?: long;
disallowedGifts?: Api.TypeDisallowedGiftsSettings;
CONSTRUCTOR_ID: 2582085701;
starsRating?: Api.TypeStarsRating;
CONSTRUCTOR_ID: 702447806;
SUBCLASS_OF_ID: 524706233;
className: 'UserFull';
@ -13347,6 +13352,7 @@ namespace Api {
quoteText?: string;
quoteEntities?: Api.TypeMessageEntity[];
quoteOffset?: int;
todoItemId?: int;
} | void> {
// flags: Api.Type;
replyToScheduled?: true;
@ -13360,7 +13366,8 @@ namespace Api {
quoteText?: string;
quoteEntities?: Api.TypeMessageEntity[];
quoteOffset?: int;
CONSTRUCTOR_ID: 2948336091;
todoItemId?: int;
CONSTRUCTOR_ID: 1763137035;
SUBCLASS_OF_ID: 1531810151;
className: 'MessageReplyHeader';
@ -15261,6 +15268,7 @@ namespace Api {
quoteEntities?: Api.TypeMessageEntity[];
quoteOffset?: int;
monoforumPeerId?: Api.TypeInputPeer;
todoItemId?: int;
}> {
// flags: Api.Type;
replyToMsgId: int;
@ -15270,7 +15278,8 @@ namespace Api {
quoteEntities?: Api.TypeMessageEntity[];
quoteOffset?: int;
monoforumPeerId?: Api.TypeInputPeer;
CONSTRUCTOR_ID: 2960144560;
todoItemId?: int;
CONSTRUCTOR_ID: 2258615824;
SUBCLASS_OF_ID: 2356220701;
className: 'InputReplyToMessage';
@ -16754,6 +16763,8 @@ namespace Api {
limited?: true;
soldOut?: true;
birthday?: true;
requirePremium?: true;
limitedPerUser?: true;
id: long;
sticker: Api.TypeDocument;
stars: long;
@ -16767,11 +16778,15 @@ namespace Api {
resellMinStars?: long;
title?: string;
releasedBy?: Api.TypePeer;
perUserTotal?: int;
perUserRemains?: int;
}> {
// flags: Api.Type;
limited?: true;
soldOut?: true;
birthday?: true;
requirePremium?: true;
limitedPerUser?: true;
id: long;
sticker: Api.TypeDocument;
stars: long;
@ -16785,7 +16800,9 @@ namespace Api {
resellMinStars?: long;
title?: string;
releasedBy?: Api.TypePeer;
CONSTRUCTOR_ID: 2139438098;
perUserTotal?: int;
perUserRemains?: int;
CONSTRUCTOR_ID: 12386139;
SUBCLASS_OF_ID: 3273414923;
className: 'StarGift';
@ -16793,6 +16810,7 @@ namespace Api {
}
export class StarGiftUnique extends VirtualClass<{
// flags: Api.Type;
requirePremium?: true;
id: long;
title: string;
slug: string;
@ -16808,6 +16826,7 @@ namespace Api {
releasedBy?: Api.TypePeer;
}> {
// flags: Api.Type;
requirePremium?: true;
id: long;
title: string;
slug: string;
@ -17079,6 +17098,7 @@ namespace Api {
transferStars?: long;
canTransferAt?: int;
canResellAt?: int;
collectionId?: int[];
}> {
// flags: Api.Type;
nameHidden?: true;
@ -17098,7 +17118,8 @@ namespace Api {
transferStars?: long;
canTransferAt?: int;
canResellAt?: int;
CONSTRUCTOR_ID: 3755607193;
collectionId?: int[];
CONSTRUCTOR_ID: 514213599;
SUBCLASS_OF_ID: 2385198100;
className: 'SavedStarGift';
@ -17378,6 +17399,44 @@ namespace Api {
static fromReader(reader: Reader): SuggestedPost;
}
export class StarsRating extends VirtualClass<{
// flags: Api.Type;
level: int;
currentLevelStars: long;
stars: long;
nextLevelStars?: long;
}> {
// flags: Api.Type;
level: int;
currentLevelStars: long;
stars: long;
nextLevelStars?: long;
CONSTRUCTOR_ID: 453922567;
SUBCLASS_OF_ID: 1668506656;
className: 'StarsRating';
static fromReader(reader: Reader): StarsRating;
}
export class StarGiftCollection extends VirtualClass<{
// flags: Api.Type;
collectionId: int;
title: string;
icon?: Api.TypeDocument;
giftsCount: int;
hash: long;
}> {
// flags: Api.Type;
collectionId: int;
title: string;
icon?: Api.TypeDocument;
giftsCount: int;
hash: long;
CONSTRUCTOR_ID: 2641040304;
SUBCLASS_OF_ID: 1138805578;
className: 'StarGiftCollection';
static fromReader(reader: Reader): StarGiftCollection;
}
export class ResPQ extends VirtualClass<{
nonce: int128;
serverNonce: int128;
@ -21385,6 +21444,23 @@ namespace Api {
static fromReader(reader: Reader): ResaleStarGifts;
}
export class StarGiftCollectionsNotModified extends VirtualClass<void> {
CONSTRUCTOR_ID: 2696564503;
SUBCLASS_OF_ID: 4028047852;
className: 'StarGiftCollectionsNotModified';
static fromReader(reader: Reader): StarGiftCollectionsNotModified;
}
export class StarGiftCollections extends VirtualClass<{
collections: Api.TypeStarGiftCollection[];
}> {
collections: Api.TypeStarGiftCollection[];
CONSTRUCTOR_ID: 2317955827;
SUBCLASS_OF_ID: 4028047852;
className: 'StarGiftCollections';
static fromReader(reader: Reader): StarGiftCollections;
}
}
export namespace phone {
@ -27128,6 +27204,7 @@ namespace Api {
excludeUnique?: true;
sortByValue?: true;
peer: Api.TypeInputPeer;
collectionId?: int;
offset: string;
limit: int;
}, payments.TypeSavedStarGifts> {
@ -27139,6 +27216,7 @@ namespace Api {
excludeUnique?: true;
sortByValue?: true;
peer: Api.TypeInputPeer;
collectionId?: int;
offset: string;
limit: int;
}
@ -27201,6 +27279,53 @@ namespace Api {
stargift: Api.TypeInputSavedStarGift;
resellStars: long;
}
export class CreateStarGiftCollection extends Request<{
peer: Api.TypeInputPeer;
title: string;
stargift: Api.TypeInputSavedStarGift[];
}, Api.TypeStarGiftCollection> {
peer: Api.TypeInputPeer;
title: string;
stargift: Api.TypeInputSavedStarGift[];
}
export class UpdateStarGiftCollection extends Request<{
// flags: Api.Type;
peer: Api.TypeInputPeer;
collectionId: int;
title?: string;
deleteStargift?: Api.TypeInputSavedStarGift[];
addStargift?: Api.TypeInputSavedStarGift[];
order?: Api.TypeInputSavedStarGift[];
}, Api.TypeStarGiftCollection> {
// flags: Api.Type;
peer: Api.TypeInputPeer;
collectionId: int;
title?: string;
deleteStargift?: Api.TypeInputSavedStarGift[];
addStargift?: Api.TypeInputSavedStarGift[];
order?: Api.TypeInputSavedStarGift[];
}
export class ReorderStarGiftCollections extends Request<{
peer: Api.TypeInputPeer;
order: int[];
}, Bool> {
peer: Api.TypeInputPeer;
order: int[];
}
export class DeleteStarGiftCollection extends Request<{
peer: Api.TypeInputPeer;
collectionId: int;
}, Bool> {
peer: Api.TypeInputPeer;
collectionId: int;
}
export class GetStarGiftCollections extends Request<{
peer: Api.TypeInputPeer;
hash: long;
}, payments.TypeStarGiftCollections> {
peer: Api.TypeInputPeer;
hash: long;
}
}
export namespace stickers {
@ -28196,7 +28321,7 @@ namespace Api {
| help.GetConfig | help.GetNearestDc | help.GetAppUpdate | help.GetInviteText | help.GetSupport | help.SetBotUpdatesStatus | help.GetCdnConfig | help.GetRecentMeUrls | help.GetTermsOfServiceUpdate | help.AcceptTermsOfService | help.GetDeepLinkInfo | help.GetAppConfig | help.SaveAppLog | help.GetPassportConfig | help.GetSupportName | help.GetUserInfo | help.EditUserInfo | help.GetPromoData | help.HidePromoData | help.DismissSuggestion | help.GetCountriesList | help.GetPremiumPromo | help.GetPeerColors | help.GetPeerProfileColors | help.GetTimezonesList
| channels.ReadHistory | channels.DeleteMessages | channels.ReportSpam | channels.GetMessages | channels.GetParticipants | channels.GetParticipant | channels.GetChannels | channels.GetFullChannel | channels.CreateChannel | channels.EditAdmin | channels.EditTitle | channels.EditPhoto | channels.CheckUsername | channels.UpdateUsername | channels.JoinChannel | channels.LeaveChannel | channels.InviteToChannel | channels.DeleteChannel | channels.ExportMessageLink | channels.ToggleSignatures | channels.GetAdminedPublicChannels | channels.EditBanned | channels.GetAdminLog | channels.SetStickers | channels.ReadMessageContents | channels.DeleteHistory | channels.TogglePreHistoryHidden | channels.GetLeftChannels | channels.GetGroupsForDiscussion | channels.SetDiscussionGroup | channels.EditCreator | channels.EditLocation | channels.ToggleSlowMode | channels.GetInactiveChannels | channels.ConvertToGigagroup | channels.GetSendAs | channels.DeleteParticipantHistory | channels.ToggleJoinToSend | channels.ToggleJoinRequest | channels.ReorderUsernames | channels.ToggleUsername | channels.DeactivateAllUsernames | channels.ToggleForum | channels.CreateForumTopic | channels.GetForumTopics | channels.GetForumTopicsByID | channels.EditForumTopic | channels.UpdatePinnedForumTopic | channels.DeleteTopicHistory | channels.ReorderPinnedForumTopics | channels.ToggleAntiSpam | channels.ReportAntiSpamFalsePositive | channels.ToggleParticipantsHidden | channels.UpdateColor | channels.ToggleViewForumAsMessages | channels.GetChannelRecommendations | channels.UpdateEmojiStatus | channels.SetBoostsToUnblockRestrictions | channels.SetEmojiStickers | channels.RestrictSponsoredMessages | channels.SearchPosts | channels.UpdatePaidMessagesPrice | channels.ToggleAutotranslation | channels.GetMessageAuthor
| bots.SendCustomRequest | bots.AnswerWebhookJSONQuery | bots.SetBotCommands | bots.ResetBotCommands | bots.GetBotCommands | bots.SetBotMenuButton | bots.GetBotMenuButton | bots.SetBotBroadcastDefaultAdminRights | bots.SetBotGroupDefaultAdminRights | bots.SetBotInfo | bots.GetBotInfo | bots.ReorderUsernames | bots.ToggleUsername | bots.CanSendMessage | bots.AllowSendMessage | bots.InvokeWebViewCustomMethod | bots.GetPopularAppBots | bots.AddPreviewMedia | bots.EditPreviewMedia | bots.DeletePreviewMedia | bots.ReorderPreviewMedias | bots.GetPreviewInfo | bots.GetPreviewMedias | bots.UpdateUserEmojiStatus | bots.ToggleUserEmojiStatusPermission | bots.CheckDownloadFileParams | bots.GetAdminedBots | bots.UpdateStarRefProgram | bots.SetCustomVerification | bots.GetBotRecommendations
| payments.GetPaymentForm | payments.GetPaymentReceipt | payments.ValidateRequestedInfo | payments.SendPaymentForm | payments.GetSavedInfo | payments.ClearSavedInfo | payments.GetBankCardData | payments.ExportInvoice | payments.AssignAppStoreTransaction | payments.AssignPlayMarketTransaction | payments.GetPremiumGiftCodeOptions | payments.CheckGiftCode | payments.ApplyGiftCode | payments.GetGiveawayInfo | payments.LaunchPrepaidGiveaway | payments.GetStarsTopupOptions | payments.GetStarsStatus | payments.GetStarsTransactions | payments.SendStarsForm | payments.RefundStarsCharge | payments.GetStarsRevenueStats | payments.GetStarsRevenueWithdrawalUrl | payments.GetStarsRevenueAdsAccountUrl | payments.GetStarsTransactionsByID | payments.GetStarsGiftOptions | payments.GetStarsSubscriptions | payments.ChangeStarsSubscription | payments.FulfillStarsSubscription | payments.GetStarsGiveawayOptions | payments.GetStarGifts | payments.SaveStarGift | payments.ConvertStarGift | payments.BotCancelStarsSubscription | payments.GetConnectedStarRefBots | payments.GetConnectedStarRefBot | payments.GetSuggestedStarRefBots | payments.ConnectStarRefBot | payments.EditConnectedStarRefBot | payments.GetStarGiftUpgradePreview | payments.UpgradeStarGift | payments.TransferStarGift | payments.GetUniqueStarGift | payments.GetSavedStarGifts | payments.GetSavedStarGift | payments.GetStarGiftWithdrawalUrl | payments.ToggleChatStarGiftNotifications | payments.ToggleStarGiftsPinnedToTop | payments.CanPurchaseStore | payments.GetResaleStarGifts | payments.UpdateStarGiftPrice
| payments.GetPaymentForm | payments.GetPaymentReceipt | payments.ValidateRequestedInfo | payments.SendPaymentForm | payments.GetSavedInfo | payments.ClearSavedInfo | payments.GetBankCardData | payments.ExportInvoice | payments.AssignAppStoreTransaction | payments.AssignPlayMarketTransaction | payments.GetPremiumGiftCodeOptions | payments.CheckGiftCode | payments.ApplyGiftCode | payments.GetGiveawayInfo | payments.LaunchPrepaidGiveaway | payments.GetStarsTopupOptions | payments.GetStarsStatus | payments.GetStarsTransactions | payments.SendStarsForm | payments.RefundStarsCharge | payments.GetStarsRevenueStats | payments.GetStarsRevenueWithdrawalUrl | payments.GetStarsRevenueAdsAccountUrl | payments.GetStarsTransactionsByID | payments.GetStarsGiftOptions | payments.GetStarsSubscriptions | payments.ChangeStarsSubscription | payments.FulfillStarsSubscription | payments.GetStarsGiveawayOptions | payments.GetStarGifts | payments.SaveStarGift | payments.ConvertStarGift | payments.BotCancelStarsSubscription | payments.GetConnectedStarRefBots | payments.GetConnectedStarRefBot | payments.GetSuggestedStarRefBots | payments.ConnectStarRefBot | payments.EditConnectedStarRefBot | payments.GetStarGiftUpgradePreview | payments.UpgradeStarGift | payments.TransferStarGift | payments.GetUniqueStarGift | payments.GetSavedStarGifts | payments.GetSavedStarGift | payments.GetStarGiftWithdrawalUrl | payments.ToggleChatStarGiftNotifications | payments.ToggleStarGiftsPinnedToTop | payments.CanPurchaseStore | payments.GetResaleStarGifts | payments.UpdateStarGiftPrice | payments.CreateStarGiftCollection | payments.UpdateStarGiftCollection | payments.ReorderStarGiftCollections | payments.DeleteStarGiftCollection | payments.GetStarGiftCollections
| stickers.CreateStickerSet | stickers.RemoveStickerFromSet | stickers.ChangeStickerPosition | stickers.AddStickerToSet | stickers.SetStickerSetThumb | stickers.CheckShortName | stickers.SuggestShortName | stickers.ChangeSticker | stickers.RenameStickerSet | stickers.DeleteStickerSet | stickers.ReplaceSticker
| phone.GetCallConfig | phone.RequestCall | phone.AcceptCall | phone.ConfirmCall | phone.ReceivedCall | phone.DiscardCall | phone.SetCallRating | phone.SaveCallDebug | phone.SendSignalingData | phone.CreateGroupCall | phone.JoinGroupCall | phone.LeaveGroupCall | phone.InviteToGroupCall | phone.DiscardGroupCall | phone.ToggleGroupCallSettings | phone.GetGroupCall | phone.GetGroupParticipants | phone.CheckGroupCall | phone.ToggleGroupCallRecord | phone.EditGroupCallParticipant | phone.EditGroupCallTitle | phone.GetGroupCallJoinAs | phone.ExportGroupCallInvite | phone.ToggleGroupCallStartSubscription | phone.StartScheduledGroupCall | phone.SaveDefaultGroupCallJoinAs | phone.JoinGroupCallPresentation | phone.LeaveGroupCallPresentation | phone.GetGroupCallStreamChannels | phone.GetGroupCallStreamRtmpUrl | phone.SaveCallLog | phone.CreateConferenceCall | phone.DeleteConferenceCallParticipants | phone.SendConferenceCallBroadcast | phone.InviteConferenceCallParticipant | phone.DeclineConferenceCallInvite | phone.GetGroupCallChainBlocks
| langpack.GetLangPack | langpack.GetStrings | langpack.GetDifference | langpack.GetLanguages | langpack.GetLanguage

View File

@ -208,7 +208,7 @@ inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
inputReportReasonFake#f5ddd6e7 = ReportReason;
inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
inputReportReasonPersonalDetails#9ec7863d = ReportReason;
userFull#99e78045 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true display_gifts_button:flags2.16?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long disallowed_gifts:flags2.15?DisallowedGiftsSettings = UserFull;
userFull#29de80be flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true display_gifts_button:flags2.16?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long disallowed_gifts:flags2.15?DisallowedGiftsSettings stars_rating:flags2.17?StarsRating = UserFull;
contact#145ade0b user_id:long mutual:Bool = Contact;
importedContact#c13e3c50 user_id:long client_id:long = ImportedContact;
contactStatus#16d9703b user_id:long status:UserStatus = ContactStatus;
@ -1051,7 +1051,7 @@ help.countriesList#87d0759e countries:Vector<help.Country> hash:int = help.Count
messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:flags.2?MessageReplies = MessageViews;
messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> users:Vector<User> = messages.MessageViews;
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector<MessageEntity> quote_offset:flags.10?int = MessageReplyHeader;
messageReplyHeader#6917560b flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector<MessageEntity> quote_offset:flags.10?int todo_item_id:flags.11?int = MessageReplyHeader;
messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader;
messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
@ -1245,7 +1245,7 @@ storyViewPublicForward#9083670b flags:# blocked:flags.0?true blocked_my_stories_
storyViewPublicRepost#bd74cf49 flags:# blocked:flags.0?true blocked_my_stories_from:flags.1?true peer_id:Peer story:StoryItem = StoryView;
stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count:int reactions_count:int views:Vector<StoryView> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = stories.StoryViewsList;
stories.storyViews#de9eed1d views:Vector<StoryViews> users:Vector<User> = stories.StoryViews;
inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo;
inputReplyToMessage#869fbe10 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer todo_item_id:flags.6?int = InputReplyTo;
inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo;
inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo;
exportedStoryLink#3fc9053b link:string = ExportedStoryLink;
@ -1386,8 +1386,8 @@ starsSubscription#2e6eab1a flags:# canceled:flags.0?true can_refulfill:flags.1?t
messageReactor#4ba3a95a flags:# top:flags.0?true my:flags.1?true anonymous:flags.2?true peer_id:flags.3?Peer count:int = MessageReactor;
starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true stars:long yearly_boosts:int store_product:flags.2?string currency:string amount:long winners:Vector<StarsGiveawayWinnersOption> = StarsGiveawayOption;
starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption;
starGift#7f853c12 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer = StarGift;
starGiftUnique#f63778ae flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift;
starGift#bcff5b flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true require_premium:flags.7?true limited_per_user:flags.8?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer per_user_total:flags.8?int per_user_remains:flags.8?int = StarGift;
starGiftUnique#f63778ae flags:# require_premium:flags.6?true id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift;
payments.starGiftsNotModified#a388a368 = payments.StarGifts;
payments.starGifts#2ed82995 hash:int gifts:Vector<StarGift> chats:Vector<Chat> users:Vector<User> = payments.StarGifts;
messageReportOption#7903e3d9 text:string option:bytes = MessageReportOption;
@ -1416,7 +1416,7 @@ users.users#62d706b8 users:Vector<User> = users.Users;
users.usersSlice#315a4974 count:int users:Vector<User> = users.Users;
payments.uniqueStarGift#caa2f60b gift:StarGift users:Vector<User> = payments.UniqueStarGift;
messages.webPagePreview#b53e8b21 media:MessageMedia users:Vector<User> = messages.WebPagePreview;
savedStarGift#dfda0499 flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long can_transfer_at:flags.13?int can_resell_at:flags.14?int = SavedStarGift;
savedStarGift#1ea646df flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long can_transfer_at:flags.13?int can_resell_at:flags.14?int collection_id:flags.15?Vector<int> = SavedStarGift;
payments.savedStarGifts#95f389b1 flags:# count:int chat_notifications_enabled:flags.1?Bool gifts:Vector<SavedStarGift> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.SavedStarGifts;
inputSavedStarGiftUser#69279795 msg_id:int = InputSavedStarGift;
inputSavedStarGiftChat#f101aa7f peer:InputPeer saved_id:long = InputSavedStarGift;
@ -1445,6 +1445,10 @@ todoItem#cba9a52f id:int title:TextWithEntities = TodoItem;
todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:flags.1?true title:TextWithEntities list:Vector<TodoItem> = TodoList;
todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion;
suggestedPost#e8e37e5 flags:# accepted:flags.1?true rejected:flags.2?true price:flags.3?StarsAmount schedule_date:flags.0?int = SuggestedPost;
starsRating#1b0e4f07 flags:# level:int current_level_stars:long stars:long next_level_stars:flags.0?long = StarsRating;
starGiftCollection#9d6b13b0 flags:# collection_id:int title:string icon:flags.0?Document gifts_count:int hash:long = StarGiftCollection;
payments.starGiftCollectionsNotModified#a0ba4f17 = payments.StarGiftCollections;
payments.starGiftCollections#8a2932f3 collections:Vector<StarGiftCollection> = payments.StarGiftCollections;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
@ -1779,7 +1783,7 @@ payments.getStarGiftUpgradePreview#9c9abcb1 gift_id:long = payments.StarGiftUpgr
payments.upgradeStarGift#aed6e4f5 flags:# keep_original_details:flags.0?true stargift:InputSavedStarGift = Updates;
payments.transferStarGift#7f18176a stargift:InputSavedStarGift to_id:InputPeer = Updates;
payments.getUniqueStarGift#a1974d72 slug:string = payments.UniqueStarGift;
payments.getSavedStarGifts#23830de9 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer offset:string limit:int = payments.SavedStarGifts;
payments.getSavedStarGifts#a319e569 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer collection_id:flags.6?int offset:string limit:int = payments.SavedStarGifts;
payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl;
payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<InputSavedStarGift> = Bool;
payments.getResaleStarGifts#7a5fa236 flags:# sort_by_price:flags.1?true sort_by_num:flags.2?true attributes_hash:flags.0?long gift_id:long attributes:flags.3?Vector<StarGiftAttributeId> offset:string limit:int = payments.ResaleStarGifts;

View File

@ -248,7 +248,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason;
inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
inputReportReasonPersonalDetails#9ec7863d = ReportReason;
userFull#99e78045 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true display_gifts_button:flags2.16?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long disallowed_gifts:flags2.15?DisallowedGiftsSettings = UserFull;
userFull#29de80be flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# sponsored_enabled:flags2.7?true can_view_revenue:flags2.9?true bot_can_manage_emoji_status:flags2.10?true display_gifts_button:flags2.16?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage business_intro:flags2.4?BusinessIntro birthday:flags2.5?Birthday personal_channel_id:flags2.6?long personal_channel_message:flags2.6?int stargifts_count:flags2.8?int starref_program:flags2.11?StarRefProgram bot_verification:flags2.12?BotVerification send_paid_messages_stars:flags2.14?long disallowed_gifts:flags2.15?DisallowedGiftsSettings stars_rating:flags2.17?StarsRating = UserFull;
contact#145ade0b user_id:long mutual:Bool = Contact;
@ -1344,7 +1344,7 @@ messages.messageViews#b6c4f543 views:Vector<MessageViews> chats:Vector<Chat> use
messages.discussionMessage#a6341782 flags:# messages:Vector<Message> max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector<Chat> users:Vector<User> = messages.DiscussionMessage;
messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector<MessageEntity> quote_offset:flags.10?int = MessageReplyHeader;
messageReplyHeader#6917560b flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector<MessageEntity> quote_offset:flags.10?int todo_item_id:flags.11?int = MessageReplyHeader;
messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader;
messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector<Peer> channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
@ -1649,7 +1649,7 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count
stories.storyViews#de9eed1d views:Vector<StoryViews> users:Vector<User> = stories.StoryViews;
inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo;
inputReplyToMessage#869fbe10 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector<MessageEntity> quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer todo_item_id:flags.6?int = InputReplyTo;
inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo;
inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo;
@ -1890,8 +1890,8 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true
starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption;
starGift#7f853c12 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer = StarGift;
starGiftUnique#f63778ae flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift;
starGift#bcff5b flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true require_premium:flags.7?true limited_per_user:flags.8?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string released_by:flags.6?Peer per_user_total:flags.8?int per_user_remains:flags.8?int = StarGift;
starGiftUnique#f63778ae flags:# require_premium:flags.6?true id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long released_by:flags.5?Peer = StarGift;
payments.starGiftsNotModified#a388a368 = payments.StarGifts;
payments.starGifts#2ed82995 hash:int gifts:Vector<StarGift> chats:Vector<Chat> users:Vector<User> = payments.StarGifts;
@ -1940,7 +1940,7 @@ payments.uniqueStarGift#caa2f60b gift:StarGift users:Vector<User> = payments.Uni
messages.webPagePreview#b53e8b21 media:MessageMedia users:Vector<User> = messages.WebPagePreview;
savedStarGift#dfda0499 flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long can_transfer_at:flags.13?int can_resell_at:flags.14?int = SavedStarGift;
savedStarGift#1ea646df flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long can_transfer_at:flags.13?int can_resell_at:flags.14?int collection_id:flags.15?Vector<int> = SavedStarGift;
payments.savedStarGifts#95f389b1 flags:# count:int chat_notifications_enabled:flags.1?Bool gifts:Vector<SavedStarGift> next_offset:flags.0?string chats:Vector<Chat> users:Vector<User> = payments.SavedStarGifts;
@ -1989,6 +1989,13 @@ todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion;
suggestedPost#e8e37e5 flags:# accepted:flags.1?true rejected:flags.2?true price:flags.3?StarsAmount schedule_date:flags.0?int = SuggestedPost;
starsRating#1b0e4f07 flags:# level:int current_level_stars:long stars:long next_level_stars:flags.0?long = StarsRating;
starGiftCollection#9d6b13b0 flags:# collection_id:int title:string icon:flags.0?Document gifts_count:int hash:long = StarGiftCollection;
payments.starGiftCollectionsNotModified#a0ba4f17 = payments.StarGiftCollections;
payments.starGiftCollections#8a2932f3 collections:Vector<StarGiftCollection> = payments.StarGiftCollections;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -2000,7 +2007,7 @@ invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
invokeWithBusinessConnection#dd289f8e {X:Type} connection_id:string query:!X = X;
invokeWithGooglePlayIntegrity#1df92984 {X:Type} nonce:string token:string query:!X = X;
invokeWithApnsSecret#dae54f8 {X:Type} nonce:string secret:string query:!X = X;
invokeWithApnsSecret#0dae54f8 {X:Type} nonce:string secret:string query:!X = X;
invokeWithReCaptcha#adbb0f94 {X:Type} token:string query:!X = X;
auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
@ -2590,7 +2597,7 @@ payments.getStarGiftUpgradePreview#9c9abcb1 gift_id:long = payments.StarGiftUpgr
payments.upgradeStarGift#aed6e4f5 flags:# keep_original_details:flags.0?true stargift:InputSavedStarGift = Updates;
payments.transferStarGift#7f18176a stargift:InputSavedStarGift to_id:InputPeer = Updates;
payments.getUniqueStarGift#a1974d72 slug:string = payments.UniqueStarGift;
payments.getSavedStarGifts#23830de9 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer offset:string limit:int = payments.SavedStarGifts;
payments.getSavedStarGifts#a319e569 flags:# exclude_unsaved:flags.0?true exclude_saved:flags.1?true exclude_unlimited:flags.2?true exclude_limited:flags.3?true exclude_unique:flags.4?true sort_by_value:flags.5?true peer:InputPeer collection_id:flags.6?int offset:string limit:int = payments.SavedStarGifts;
payments.getSavedStarGift#b455a106 stargift:Vector<InputSavedStarGift> = payments.SavedStarGifts;
payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password:InputCheckPasswordSRP = payments.StarGiftWithdrawalUrl;
payments.toggleChatStarGiftNotifications#60eaefa1 flags:# enabled:flags.0?true peer:InputPeer = Bool;
@ -2598,6 +2605,11 @@ payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector<Inpu
payments.canPurchaseStore#4fdc5ea7 purpose:InputStorePaymentPurpose = Bool;
payments.getResaleStarGifts#7a5fa236 flags:# sort_by_price:flags.1?true sort_by_num:flags.2?true attributes_hash:flags.0?long gift_id:long attributes:flags.3?Vector<StarGiftAttributeId> offset:string limit:int = payments.ResaleStarGifts;
payments.updateStarGiftPrice#3baea4e1 stargift:InputSavedStarGift resell_stars:long = Updates;
payments.createStarGiftCollection#1f4a0e87 peer:InputPeer title:string stargift:Vector<InputSavedStarGift> = StarGiftCollection;
payments.updateStarGiftCollection#4fddbee7 flags:# peer:InputPeer collection_id:int title:flags.0?string delete_stargift:flags.1?Vector<InputSavedStarGift> add_stargift:flags.2?Vector<InputSavedStarGift> order:flags.3?Vector<InputSavedStarGift> = StarGiftCollection;
payments.reorderStarGiftCollections#c32af4cc peer:InputPeer order:Vector<int> = Bool;
payments.deleteStarGiftCollection#ad5648e8 peer:InputPeer collection_id:int = Bool;
payments.getStarGiftCollections#981b91dd peer:InputPeer hash:long = payments.StarGiftCollections;
stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector<InputStickerSetItem> software:flags.3?string = messages.StickerSet;
stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
@ -2718,4 +2730,4 @@ smsjobs.getStatus#10a698e8 = smsjobs.Status;
smsjobs.getSmsJob#778d902f job_id:string = SmsJob;
smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;

View File

@ -1600,6 +1600,16 @@ export interface LangPair {
'ButtonTopUpViaFragment': undefined;
'TonModalHint': undefined;
'TonGiftReceived': undefined;
'MediaSpoilerSensitive': undefined;
'TitleSensitiveModal': undefined;
'TextSensitiveModal': undefined;
'ButtonSensitiveAlways': undefined;
'ButtonSensitiveView': undefined;
'TitleAgeVerificationModal': undefined;
'DescriptionAgeVerificationModal': undefined;
'TitleAgeCheckFailed': undefined;
'TitleAgeCheckSuccess': undefined;
'ButtonAgeVerification': undefined;
}
export interface LangPairWithVariables<V = LangVariable> {
@ -3089,6 +3099,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
'MessageActionTodoTaskCount': {
'count': V;
};
'TextAgeVerificationModal': {
'count': V;
};
}
export type RegularLangKey = keyof LangPair;
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;

View File

@ -91,6 +91,7 @@ interface WebAppInboundEventMap {
web_app_biometry_request_auth: { reason: string };
web_app_biometry_update_token: { token: string };
web_app_set_emoji_status: { custom_emoji_id: string; duration?: number };
web_app_verify_age: { passed: boolean; age?: number };
web_app_request_file_download: { url: string; file_name: string };
web_app_send_prepared_message: { id: string };
web_app_device_storage_save_key: {

View File

@ -14,6 +14,7 @@ import {
import { getIsChatMuted } from '../global/helpers/notifications';
import {
selectChatLastMessage,
selectIsChatRestricted,
selectNotifyDefaults,
selectTabState,
selectTopics,
@ -527,10 +528,11 @@ function buildChatSummary<T extends GlobalState>(
isRemovedFromSaved?: boolean,
): ChatSummary {
const {
id, type, isRestricted, isNotJoined, migratedTo, folderId,
id, type, isNotJoined, migratedTo, folderId,
unreadCount: chatUnreadCount, unreadMentionsCount: chatUnreadMentionsCount, hasUnreadMark,
isForum,
} = chat;
const isRestricted = selectIsChatRestricted(global, id);
const topics = selectTopics(global, chat.id);
const { unreadCount, unreadMentionsCount } = isForum