Global Search: Show sponsored results (#5844)

Co-authored-by: Dmitry Kabanov <dmitrykabanovdev@gmail.com>
Co-authored-by: Dmitry Kabanov <153344039+dmitrykabanovdev@users.noreply.github.com>
This commit is contained in:
zubiden 2025-04-23 18:59:25 +02:00 committed by Alexander Zinchuk
parent 3087fb85cc
commit 97bd26df00
26 changed files with 352 additions and 127 deletions

View File

@ -18,6 +18,7 @@ import type {
ApiRestrictionReason, ApiRestrictionReason,
ApiSendAsPeerId, ApiSendAsPeerId,
ApiSponsoredMessageReportResult, ApiSponsoredMessageReportResult,
ApiSponsoredPeer,
ApiStarsSubscriptionPricing, ApiStarsSubscriptionPricing,
ApiTopic, ApiTopic,
} from '../../types'; } from '../../types';
@ -711,3 +712,16 @@ export function buildApiStarsSubscriptionPricing(
amount: pricing.amount.toJSNumber(), amount: pricing.amount.toJSNumber(),
}; };
} }
export function buildApiSponsoredPeer(sponsoredPeer: GramJs.SponsoredPeer): ApiSponsoredPeer {
const {
peer, randomId, additionalInfo, sponsorInfo,
} = sponsoredPeer;
return {
peerId: getApiChatIdFromMtpPeer(peer),
randomId: serializeBytes(randomId),
additionalInfo,
sponsorInfo,
};
}

View File

@ -45,6 +45,7 @@ import {
buildApiChatlistInvite, buildApiChatlistInvite,
buildApiChatReactions, buildApiChatReactions,
buildApiMissingInvitedUser, buildApiMissingInvitedUser,
buildApiSponsoredPeer,
buildApiTopic, buildApiTopic,
buildChatMember, buildChatMember,
buildChatMembers, buildChatMembers,
@ -2020,3 +2021,9 @@ export async function fetchChannelRecommendations({ chat }: { chat?: ApiChat })
count: result instanceof GramJs.messages.ChatsSlice ? result.count : similarChannels.length, count: result instanceof GramJs.messages.ChatsSlice ? result.count : similarChannels.length,
}; };
} }
export async function fetchSponsoredPeer({ query }: { query: string }) {
const result = await invokeRequest(new GramJs.contacts.GetSponsoredPeers({ q: query }));
if (!result || result instanceof GramJs.contacts.SponsoredPeersEmpty) return undefined;
return buildApiSponsoredPeer(result.peers[0]);
}

View File

@ -313,3 +313,10 @@ export type ApiDraft = {
effectId?: string; effectId?: string;
isLocal?: boolean; isLocal?: boolean;
}; };
export type ApiSponsoredPeer = {
randomId: string;
peerId: string;
sponsorInfo?: string;
additionalInfo?: string;
};

View File

@ -782,6 +782,7 @@
"MessageRecommendedLabel" = "recommended"; "MessageRecommendedLabel" = "recommended";
"SponsoredMessageAd" = "Ad"; "SponsoredMessageAd" = "Ad";
"SponsoredMessageAdWhatIsThis" = "what's this?"; "SponsoredMessageAdWhatIsThis" = "what's this?";
"SponsoredPeerBadge" = "Ad";
"PremiumStickerTooltip" = "This set contains premium stickers like this one."; "PremiumStickerTooltip" = "This set contains premium stickers like this one.";
"ViewAction" = "View"; "ViewAction" = "View";
"Loading" = "Loading..."; "Loading" = "Loading...";
@ -1923,4 +1924,4 @@
"ApiMessageActionPaidMessagesRefundedOutgoing" = "You refunded **{stars}** to {user}"; "ApiMessageActionPaidMessagesRefundedOutgoing" = "You refunded **{stars}** to {user}";
"ApiMessageActionPaidMessagesRefundedIncoming" = "{user} refunded **{stars}** to you"; "ApiMessageActionPaidMessagesRefundedIncoming" = "{user} refunded **{stars}** to you";
"NotificationTitleNotSupportedInFrozenAccount" = "Your account is frozen"; "NotificationTitleNotSupportedInFrozenAccount" = "Your account is frozen";
"NotificationMessageNotSupportedInFrozenAccount" = "This action is not available"; "NotificationMessageNotSupportedInFrozenAccount" = "This action is not available";

View File

@ -58,8 +58,7 @@ export { default as ChatFolderModal } from '../components/left/ChatFolderModal';
export { default as MuteChatModal } from '../components/left/MuteChatModal'; export { default as MuteChatModal } from '../components/left/MuteChatModal';
export { default as ContextMenuContainer } from '../components/middle/message/ContextMenuContainer'; export { default as ContextMenuContainer } from '../components/middle/message/ContextMenuContainer';
export { default as SponsoredMessageContextMenuContainer } export { default as SponsoredContextMenuContainer } from '../components/middle/message/SponsoredContextMenuContainer';
from '../components/middle/message/SponsoredMessageContextMenuContainer';
export { default as StickerSetModal } from '../components/common/StickerSetModal'; export { default as StickerSetModal } from '../components/common/StickerSetModal';
export { default as CustomEmojiSetsModal } from '../components/common/CustomEmojiSetsModal'; export { default as CustomEmojiSetsModal } from '../components/common/CustomEmojiSetsModal';
export { default as HeaderMenuContainer } from '../components/middle/HeaderMenuContainer'; export { default as HeaderMenuContainer } from '../components/middle/HeaderMenuContainer';

View File

@ -8,15 +8,21 @@ type OwnProps = {
children: React.ReactNode; children: React.ReactNode;
className?: string; className?: string;
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void; onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
}; };
const BadgeButton = ({ const BadgeButton = ({
children, children,
className, className,
onClick, onClick,
onMouseDown,
}: OwnProps) => { }: OwnProps) => {
return ( return (
<div className={buildClassName(styles.root, onClick && styles.clickable, className)} onClick={onClick}> <div
className={buildClassName(styles.root, onClick && styles.clickable, className)}
onClick={onClick}
onMouseDown={onMouseDown}
>
{children} {children}
</div> </div>
); );

View File

@ -5,7 +5,7 @@ import React, {
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
import type { ApiMessage, ApiMessageSearchContext } from '../../../api/types'; import type { ApiMessage, ApiMessageSearchContext, ApiSponsoredPeer } from '../../../api/types';
import { LoadMoreDirection } from '../../../types'; import { LoadMoreDirection } from '../../../types';
import { ALL_FOLDER_ID, GLOBAL_SUGGESTED_CHANNELS_ID } from '../../../config'; import { ALL_FOLDER_ID, GLOBAL_SUGGESTED_CHANNELS_ID } from '../../../config';
@ -27,6 +27,7 @@ import useAppLayout from '../../../hooks/useAppLayout';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useEffectOnce from '../../../hooks/useEffectOnce'; import useEffectOnce from '../../../hooks/useEffectOnce';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
@ -43,6 +44,7 @@ import Transition from '../../ui/Transition';
import ChatMessage from './ChatMessage'; import ChatMessage from './ChatMessage';
import DateSuggest from './DateSuggest'; import DateSuggest from './DateSuggest';
import LeftSearchResultChat from './LeftSearchResultChat'; import LeftSearchResultChat from './LeftSearchResultChat';
import LeftSearchResultSponsored from './LeftSearchResultSponsored';
import RecentContacts from './RecentContacts'; import RecentContacts from './RecentContacts';
import './ChatResults.scss'; import './ChatResults.scss';
@ -62,6 +64,7 @@ type StateProps = {
accountPeerIds?: string[]; accountPeerIds?: string[];
globalPeerIds?: string[]; globalPeerIds?: string[];
foundIds?: SearchResultKey[]; foundIds?: SearchResultKey[];
sponsoredPeer?: ApiSponsoredPeer;
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>; globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
fetchingStatus?: { chats?: boolean; messages?: boolean }; fetchingStatus?: { chats?: boolean; messages?: boolean };
suggestedChannelIds?: string[]; suggestedChannelIds?: string[];
@ -69,6 +72,7 @@ type StateProps = {
const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4; const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4;
const LESS_LIST_ITEMS_AMOUNT = 5; const LESS_LIST_ITEMS_AMOUNT = 5;
const INTERSECTION_THROTTLE = 200;
const runThrottled = throttle((cb) => cb(), 500, false); const runThrottled = throttle((cb) => cb(), 500, false);
@ -85,6 +89,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
globalMessagesByChatId, globalMessagesByChatId,
fetchingStatus, fetchingStatus,
suggestedChannelIds, suggestedChannelIds,
sponsoredPeer,
onReset, onReset,
onSearchDateSelect, onSearchDateSelect,
}) => { }) => {
@ -93,6 +98,8 @@ const ChatResults: FC<OwnProps & StateProps> = ({
setGlobalSearchChatId, loadChannelRecommendations, setGlobalSearchChatId, loadChannelRecommendations,
} = getActions(); } = getActions();
// eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const chatSelectionRef = useRef<HTMLDivElement>(null); const chatSelectionRef = useRef<HTMLDivElement>(null);
@ -335,7 +342,15 @@ const ChatResults: FC<OwnProps & StateProps> = ({
&& !localResults.length && !globalResults.length && !actualFoundIds.length; && !localResults.length && !globalResults.length && !actualFoundIds.length;
const isMessagesFetching = fetchingStatus?.messages; const isMessagesFetching = fetchingStatus?.messages;
if (!searchQuery && !searchDate && !isChannelList) { const shouldRenderTopPeers = !searchQuery && !searchDate && !isChannelList;
const { observe } = useIntersectionObserver({
rootRef: containerRef,
throttleMs: INTERSECTION_THROTTLE,
isDisabled: !shouldRenderTopPeers,
});
if (shouldRenderTopPeers) {
return <RecentContacts onReset={onReset} />; return <RecentContacts onReset={onReset} />;
} }
@ -343,6 +358,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
return ( return (
<InfiniteScroll <InfiniteScroll
ref={containerRef}
className="LeftSearch--content custom-scroll" className="LeftSearch--content custom-scroll"
items={actualFoundIds} items={actualFoundIds}
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
@ -415,6 +431,9 @@ const ChatResults: FC<OwnProps & StateProps> = ({
)} )}
{oldLang('DialogList.SearchSectionGlobal')} {oldLang('DialogList.SearchSectionGlobal')}
</h3> </h3>
{sponsoredPeer && (
<LeftSearchResultSponsored sponsoredPeer={sponsoredPeer} observeIntersection={observe} />
)}
{globalResults.map((id, index) => { {globalResults.map((id, index) => {
if (!shouldShowMoreGlobal && index >= LESS_LIST_ITEMS_AMOUNT) { if (!shouldShowMoreGlobal && index >= LESS_LIST_ITEMS_AMOUNT) {
return undefined; return undefined;
@ -493,7 +512,7 @@ export default memo(withGlobal<OwnProps>(
} }
const { const {
fetchingStatus, globalResults, localResults, resultsByType, fetchingStatus, globalResults, localResults, resultsByType, sponsoredPeer,
} = selectTabState(global).globalSearch; } = selectTabState(global).globalSearch;
const { peerIds: globalPeerIds } = globalResults || {}; const { peerIds: globalPeerIds } = globalResults || {};
const { peerIds: accountPeerIds } = localResults || {}; const { peerIds: accountPeerIds } = localResults || {};
@ -509,6 +528,7 @@ export default memo(withGlobal<OwnProps>(
foundIds, foundIds,
globalMessagesByChatId, globalMessagesByChatId,
fetchingStatus, fetchingStatus,
sponsoredPeer,
suggestedChannelIds: similarChannelIds, suggestedChannelIds: similarChannelIds,
}; };
}, },

View File

@ -183,6 +183,11 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
.search-sponsored-badge {
display: flex;
align-self: flex-start;
}
} }
.search-section { .search-section {

View File

@ -0,0 +1,116 @@
import type { FC } from '../../../lib/teact/teact';
import React, { memo, useRef } from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { ApiSponsoredPeer } from '../../../api/types';
import { StoryViewerOrigin } from '../../../types';
import { isUserId } from '../../../global/helpers';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import { useFastClick } from '../../../hooks/useFastClick';
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback';
import useSelectWithEnter from '../../../hooks/useSelectWithEnter';
import BadgeButton from '../../common/BadgeButton';
import GroupChatInfo from '../../common/GroupChatInfo';
import Icon from '../../common/icons/Icon';
import PrivateChatInfo from '../../common/PrivateChatInfo';
import SponsoredMessageContextMenuContainer from '../../middle/message/SponsoredContextMenuContainer';
import ListItem from '../../ui/ListItem';
type OwnProps = {
sponsoredPeer: ApiSponsoredPeer;
observeIntersection?: ObserveFn;
};
const LeftSearchResultSponsored: FC<OwnProps> = ({
sponsoredPeer,
observeIntersection,
}) => {
// eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null);
const { clickSponsored, viewSponsored, openChat } = getActions();
const lang = useLang();
const {
peerId, randomId, additionalInfo, sponsorInfo,
} = sponsoredPeer;
useOnIntersect(ref, observeIntersection, (entry) => {
if (entry.intersectionRatio === 1) {
viewSponsored({ randomId });
}
});
const handleClick = useLastCallback(() => {
clickSponsored({ randomId });
openChat({ id: peerId });
});
const {
isContextMenuOpen, contextMenuAnchor,
handleBeforeContextMenu, handleContextMenu,
handleContextMenuClose, handleContextMenuHide,
} = useContextMenuHandlers(ref);
const {
handleClick: handleBadgeClick,
handleMouseDown: handleBadgeMouseDown,
} = useFastClick((e: React.MouseEvent) => {
e.stopPropagation();
handleContextMenu(e);
});
const buttonRef = useSelectWithEnter(handleClick);
return (
<ListItem
ref={ref}
className="chat-item-clickable search-result"
onClick={handleClick}
onMouseDown={handleBeforeContextMenu}
onContextMenu={handleContextMenu}
buttonRef={buttonRef}
>
{isUserId(peerId) ? (
<PrivateChatInfo
userId={peerId}
withUsername
withStory
avatarSize="medium"
storyViewerOrigin={StoryViewerOrigin.SearchResult}
/>
) : (
<GroupChatInfo
chatId={peerId}
withUsername
avatarSize="medium"
withStory
storyViewerOrigin={StoryViewerOrigin.SearchResult}
/>
)}
<BadgeButton className="search-sponsored-badge" onMouseDown={handleBadgeMouseDown} onClick={handleBadgeClick}>
{lang('SponsoredPeerBadge')}
<Icon name="more" />
</BadgeButton>
{contextMenuAnchor && (
<SponsoredMessageContextMenuContainer
isOpen={isContextMenuOpen}
anchor={contextMenuAnchor}
triggerRef={ref}
randomId={randomId}
additionalInfo={additionalInfo}
canReport
sponsorInfo={sponsorInfo}
onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide}
/>
)}
</ListItem>
);
};
export default memo(LeftSearchResultSponsored);

View File

@ -122,7 +122,7 @@ const MediaViewer = ({
toggleChatInfo, toggleChatInfo,
searchChatMediaMessages, searchChatMediaMessages,
loadMoreProfilePhotos, loadMoreProfilePhotos,
clickSponsoredMessage, clickSponsored,
openUrl, openUrl,
} = getActions(); } = getActions();
@ -265,7 +265,7 @@ const MediaViewer = ({
const handleSponsoredClick = useLastCallback((isFromMedia?: boolean) => { const handleSponsoredClick = useLastCallback((isFromMedia?: boolean) => {
if (!sponsoredMessage || !chatId) return; if (!sponsoredMessage || !chatId) return;
clickSponsoredMessage({ isMedia: isFromMedia, isFullscreen: true, peerId: chatId }); clickSponsored({ isMedia: isFromMedia, isFullscreen: true, randomId: sponsoredMessage.randomId });
openUrl({ url: sponsoredMessage!.url }); openUrl({ url: sponsoredMessage!.url });
closeMediaViewer(); closeMediaViewer();
}); });

View File

@ -3,9 +3,6 @@ import React, {
memo, useRef, memo, useRef,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import type {
ApiSponsoredMessage,
} from '../../../api/types';
import type { IAnchorPosition } from '../../../types'; import type { IAnchorPosition } from '../../../types';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
@ -20,7 +17,8 @@ import './MessageContextMenu.scss';
type OwnProps = { type OwnProps = {
isOpen: boolean; isOpen: boolean;
anchor: IAnchorPosition; anchor: IAnchorPosition;
message: ApiSponsoredMessage; sponsorInfo?: string;
canReport?: boolean;
triggerRef: React.RefObject<HTMLElement>; triggerRef: React.RefObject<HTMLElement>;
shouldSkipAbout?: boolean; shouldSkipAbout?: boolean;
onClose: NoneToVoidFunction; onClose: NoneToVoidFunction;
@ -31,9 +29,10 @@ type OwnProps = {
onSponsoredReport?: NoneToVoidFunction; onSponsoredReport?: NoneToVoidFunction;
}; };
const SponsoredMessageContextMenu: FC<OwnProps> = ({ const SponsoredContextMenu: FC<OwnProps> = ({
isOpen, isOpen,
message, sponsorInfo,
canReport,
anchor, anchor,
triggerRef, triggerRef,
shouldSkipAbout, shouldSkipAbout,
@ -53,7 +52,7 @@ const SponsoredMessageContextMenu: FC<OwnProps> = ({
const getMenuElement = useLastCallback(() => menuRef.current); const getMenuElement = useLastCallback(() => menuRef.current);
const getRootElement = useLastCallback(() => document.body); const getRootElement = useLastCallback(() => document.body);
const isSeparatorNeeded = message.sponsorInfo || !shouldSkipAbout || message.canReport; const isSeparatorNeeded = sponsorInfo || !shouldSkipAbout || canReport;
return ( return (
<Menu <Menu
@ -69,15 +68,15 @@ const SponsoredMessageContextMenu: FC<OwnProps> = ({
onClose={onClose} onClose={onClose}
onCloseAnimationEnd={onCloseAnimationEnd} onCloseAnimationEnd={onCloseAnimationEnd}
> >
{message.sponsorInfo && onSponsorInfo && ( {sponsorInfo && onSponsorInfo && (
<MenuItem icon="channel" onClick={onSponsorInfo}>{lang('SponsoredMessageSponsor')}</MenuItem> <MenuItem icon="channel" onClick={onSponsorInfo}>{lang('SponsoredMessageSponsor')}</MenuItem>
)} )}
{!shouldSkipAbout && ( {!shouldSkipAbout && (
<MenuItem icon="info" onClick={onAboutAdsClick}> <MenuItem icon="info" onClick={onAboutAdsClick}>
{lang(message.canReport ? 'AboutRevenueSharingAds' : 'SponsoredMessageInfo')} {lang(canReport ? 'AboutRevenueSharingAds' : 'SponsoredMessageInfo')}
</MenuItem> </MenuItem>
)} )}
{message.canReport && onSponsoredReport && ( {canReport && onSponsoredReport && (
<MenuItem icon="hand-stop" onClick={onSponsoredReport}> <MenuItem icon="hand-stop" onClick={onSponsoredReport}>
{lang('ReportAd')} {lang('ReportAd')}
</MenuItem> </MenuItem>
@ -90,4 +89,4 @@ const SponsoredMessageContextMenu: FC<OwnProps> = ({
); );
}; };
export default memo(SponsoredMessageContextMenu); export default memo(SponsoredContextMenu);

View File

@ -0,0 +1,20 @@
import type { FC } from '../../../lib/teact/teact';
import React from '../../../lib/teact/teact';
import type { OwnProps } from './SponsoredContextMenuContainer';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const SponsoredContextMenuContainerAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const SponsoredContextMenuContainer = useModuleLoader(
Bundles.Extra, 'SponsoredContextMenuContainer', !isOpen,
);
// eslint-disable-next-line react/jsx-props-no-spreading
return SponsoredContextMenuContainer ? <SponsoredContextMenuContainer {...props} /> : undefined;
};
export default SponsoredContextMenuContainerAsync;

View File

@ -2,17 +2,19 @@ import type { FC } from '../../../lib/teact/teact';
import React, { memo } from '../../../lib/teact/teact'; import React, { memo } from '../../../lib/teact/teact';
import { getActions } from '../../../global'; import { getActions } from '../../../global';
import type { ApiSponsoredMessage } from '../../../api/types';
import type { IAnchorPosition } from '../../../types'; import type { IAnchorPosition } from '../../../types';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useShowTransition from '../../../hooks/useShowTransition'; import useShowTransition from '../../../hooks/useShowTransition';
import SponsoredMessageContextMenu from './SponsoredMessageContextMenu'; import SponsoredContextMenu from './SponsoredContextMenu';
export type OwnProps = { export type OwnProps = {
isOpen: boolean; isOpen: boolean;
message: ApiSponsoredMessage; randomId: string;
sponsorInfo?: string;
additionalInfo?: string;
canReport?: boolean;
anchor: IAnchorPosition; anchor: IAnchorPosition;
triggerRef: React.RefObject<HTMLElement>; triggerRef: React.RefObject<HTMLElement>;
shouldSkipAbout?: boolean; shouldSkipAbout?: boolean;
@ -23,7 +25,10 @@ export type OwnProps = {
const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
isOpen, isOpen,
message, randomId,
sponsorInfo,
additionalInfo,
canReport,
anchor, anchor,
triggerRef, triggerRef,
shouldSkipAbout, shouldSkipAbout,
@ -34,8 +39,8 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
const { const {
openAboutAdsModal, openAboutAdsModal,
showDialog, showDialog,
reportSponsoredMessage, reportSponsored,
hideSponsoredMessages, hideSponsored,
} = getActions(); } = getActions();
const { ref } = useShowTransition({ const { ref } = useShowTransition({
@ -49,26 +54,31 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
}); });
const handleAboutAdsOpen = useLastCallback(() => { const handleAboutAdsOpen = useLastCallback(() => {
openAboutAdsModal({ chatId: message.chatId }); openAboutAdsModal({
randomId,
additionalInfo,
canReport,
sponsorInfo,
});
handleItemClick(); handleItemClick();
}); });
const handleSponsoredHide = useLastCallback(() => { const handleSponsoredHide = useLastCallback(() => {
hideSponsoredMessages(); hideSponsored();
handleItemClick(); handleItemClick();
}); });
const handleSponsorInfo = useLastCallback(() => { const handleSponsorInfo = useLastCallback(() => {
showDialog({ showDialog({
data: { data: {
message: [message.sponsorInfo, message.additionalInfo].join('\n'), message: [sponsorInfo, additionalInfo].filter(Boolean).join('\n'),
}, },
}); });
handleItemClick(); handleItemClick();
}); });
const handleReportSponsoredMessage = useLastCallback(() => { const handleReportSponsoredMessage = useLastCallback(() => {
reportSponsoredMessage({ peerId: message.chatId, randomId: message.randomId }); reportSponsored({ randomId });
handleItemClick(); handleItemClick();
}); });
@ -78,11 +88,12 @@ const SponsoredMessageContextMenuContainer: FC<OwnProps> = ({
return ( return (
<div ref={ref} className="ContextMenuContainer"> <div ref={ref} className="ContextMenuContainer">
<SponsoredMessageContextMenu <SponsoredContextMenu
isOpen={isOpen} isOpen={isOpen}
anchor={anchor} anchor={anchor}
triggerRef={triggerRef} triggerRef={triggerRef}
message={message} canReport={canReport}
sponsorInfo={sponsorInfo}
shouldSkipAbout={shouldSkipAbout} shouldSkipAbout={shouldSkipAbout}
onClose={onClose} onClose={onClose}
onCloseAnimationEnd={onClose} onCloseAnimationEnd={onClose}

View File

@ -38,7 +38,7 @@ import PeerColorWrapper from '../../common/PeerColorWrapper';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import MessageAppendix from './MessageAppendix'; import MessageAppendix from './MessageAppendix';
import Photo from './Photo'; import Photo from './Photo';
import SponsoredMessageContextMenuContainer from './SponsoredMessageContextMenuContainer.async'; import SponsoredContextMenuContainer from './SponsoredContextMenuContainer.async';
import Video from './Video'; import Video from './Video';
import './SponsoredMessage.scss'; import './SponsoredMessage.scss';
@ -72,10 +72,10 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
canAutoPlayMedia, canAutoPlayMedia,
}) => { }) => {
const { const {
viewSponsoredMessage, viewSponsored,
openUrl, openUrl,
hideSponsoredMessages, hideSponsored,
clickSponsoredMessage, clickSponsored,
openMediaViewer, openMediaViewer,
openAboutAdsModal, openAboutAdsModal,
} = getActions(); } = getActions();
@ -103,11 +103,11 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
return shouldObserve ? observeIntersection(contentRef.current!, (target) => { return shouldObserve ? observeIntersection(contentRef.current!, (target) => {
if (target.isIntersecting) { if (target.isIntersecting && message?.randomId) {
viewSponsoredMessage({ peerId: chatId }); viewSponsored({ randomId: message.randomId });
} }
}) : undefined; }) : undefined;
}, [chatId, shouldObserve, observeIntersection, viewSponsoredMessage]); }, [message?.randomId, shouldObserve, observeIntersection, viewSponsored]);
const handleMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => { const handleMouseDown = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
preventMessageInputBlur(e); preventMessageInputBlur(e);
@ -115,7 +115,7 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
}; };
const handleHideSponsoredMessage = useLastCallback(() => { const handleHideSponsoredMessage = useLastCallback(() => {
hideSponsoredMessages(); hideSponsored();
}); });
const { const {
@ -128,12 +128,13 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
if (!message) return; if (!message) return;
clickSponsoredMessage({ isMedia: photo || isGif ? true : undefined, peerId: chatId }); clickSponsored({ randomId: message.randomId, isMedia: photo || isGif ? true : undefined });
openUrl({ url: message.url, shouldSkipModal: true }); openUrl({ url: message.url, shouldSkipModal: true });
}); });
const handleOpenMedia = useLastCallback(() => { const handleOpenMedia = useLastCallback(() => {
clickSponsoredMessage({ isMedia: true, peerId: chatId }); if (!message) return;
clickSponsored({ randomId: message.randomId, isMedia: true });
openMediaViewer({ openMediaViewer({
origin: MediaViewerOrigin.SponsoredMessage, origin: MediaViewerOrigin.SponsoredMessage,
chatId, chatId,
@ -142,7 +143,13 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
}); });
const handleOpenAboutAdsModal = useLastCallback(() => { const handleOpenAboutAdsModal = useLastCallback(() => {
openAboutAdsModal({ chatId }); if (!message) return;
openAboutAdsModal({
randomId: message.randomId,
canReport: message.canReport,
additionalInfo: message.additionalInfo,
sponsorInfo: message.sponsorInfo,
});
}); });
const extraPadding = 0; const extraPadding = 0;
@ -315,11 +322,14 @@ const SponsoredMessage: FC<OwnProps & StateProps> = ({
</div> </div>
</div> </div>
{contextMenuAnchor && ( {contextMenuAnchor && (
<SponsoredMessageContextMenuContainer <SponsoredContextMenuContainer
isOpen={isContextMenuOpen} isOpen={isContextMenuOpen}
anchor={contextMenuAnchor} anchor={contextMenuAnchor}
triggerRef={ref} triggerRef={ref}
message={message!} randomId={message.randomId}
canReport={message.canReport}
sponsorInfo={message.sponsorInfo}
additionalInfo={message.additionalInfo}
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide} onCloseAnimationEnd={handleContextMenuHide}
/> />

View File

@ -1,20 +0,0 @@
import type { FC } from '../../../lib/teact/teact';
import React from '../../../lib/teact/teact';
import type { OwnProps } from './SponsoredMessageContextMenuContainer';
import { Bundles } from '../../../util/moduleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const SponsoredMessageContextMenuContainerAsync: FC<OwnProps> = (props) => {
const { isOpen } = props;
const SponsoredMessageContextMenuContainer = useModuleLoader(
Bundles.Extra, 'SponsoredMessageContextMenuContainer', !isOpen,
);
// eslint-disable-next-line react/jsx-props-no-spreading
return SponsoredMessageContextMenuContainer ? <SponsoredMessageContextMenuContainer {...props} /> : undefined;
};
export default SponsoredMessageContextMenuContainerAsync;

View File

@ -17,7 +17,7 @@ import useHeaderPane, { type PaneState } from '../hooks/useHeaderPane';
import Avatar from '../../common/Avatar'; import Avatar from '../../common/Avatar';
import BadgeButton from '../../common/BadgeButton'; import BadgeButton from '../../common/BadgeButton';
import SponsoredMessageContextMenuContainer from '../message/SponsoredMessageContextMenuContainer'; import SponsoredMessageContextMenuContainer from '../message/SponsoredContextMenuContainer';
import styles from './BotAdPane.module.scss'; import styles from './BotAdPane.module.scss';
@ -40,9 +40,9 @@ const BotAdPane = ({
onPaneStateChange, onPaneStateChange,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { const {
viewSponsoredMessage, viewSponsored,
openUrl, openUrl,
clickSponsoredMessage, clickSponsored,
openAboutAdsModal, openAboutAdsModal,
} = getActions(); } = getActions();
@ -67,25 +67,38 @@ const BotAdPane = ({
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
if (!renderingSponsoredMessage) return; if (!renderingSponsoredMessage) return;
clickSponsoredMessage({ peerId: chatId }); clickSponsored({ randomId: renderingSponsoredMessage.randomId });
openUrl({ url: renderingSponsoredMessage.url, shouldSkipModal: true }); openUrl({ url: renderingSponsoredMessage.url, shouldSkipModal: true });
}); });
const handleAboutClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => { const handleAboutClick = useLastCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!renderingSponsoredMessage) return;
const {
randomId, additionalInfo, canReport, sponsorInfo,
} = renderingSponsoredMessage;
e.stopPropagation(); e.stopPropagation();
openAboutAdsModal({ chatId }); openAboutAdsModal({
randomId,
additionalInfo,
canReport,
sponsorInfo,
});
}); });
useEffect(() => { useEffect(() => {
if (shouldRender && sponsoredMessage) { if (shouldRender && renderingSponsoredMessage) {
viewSponsoredMessage({ peerId: chatId }); viewSponsored({ randomId: renderingSponsoredMessage.randomId });
} }
}, [shouldRender, sponsoredMessage, chatId]); }, [shouldRender, renderingSponsoredMessage, chatId]);
if (!shouldRender || !renderingSponsoredMessage) { if (!shouldRender || !renderingSponsoredMessage) {
return undefined; return undefined;
} }
const {
randomId, canReport, additionalInfo, sponsorInfo,
} = renderingSponsoredMessage;
const { const {
peerColor, peerColor,
content, content,
@ -132,7 +145,10 @@ const BotAdPane = ({
isOpen={isContextMenuOpen} isOpen={isContextMenuOpen}
anchor={contextMenuAnchor} anchor={contextMenuAnchor}
triggerRef={ref} triggerRef={ref}
message={renderingSponsoredMessage} randomId={randomId}
additionalInfo={additionalInfo}
canReport={canReport}
sponsorInfo={sponsorInfo}
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
onCloseAnimationEnd={handleContextMenuHide} onCloseAnimationEnd={handleContextMenuHide}
/> />

View File

@ -1,11 +1,9 @@
import React, { memo, useMemo, useRef } from '../../../lib/teact/teact'; import React, { memo, useMemo, useRef } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
import type { ApiSponsoredMessage } from '../../../api/types';
import type { TabState } from '../../../global/types'; import type { TabState } from '../../../global/types';
import type { TableAboutData } from '../common/TableAboutModal'; import type { TableAboutData } from '../common/TableAboutModal';
import { selectSponsoredMessage } from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
@ -16,7 +14,7 @@ import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
import SafeLink from '../../common/SafeLink'; import SafeLink from '../../common/SafeLink';
import SponsoredMessageContextMenuContainer from '../../middle/message/SponsoredMessageContextMenuContainer'; import SponsoredMessageContextMenuContainer from '../../middle/message/SponsoredContextMenuContainer';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
import Modal from '../../ui/Modal'; import Modal from '../../ui/Modal';
import TableAboutModal from '../common/TableAboutModal'; import TableAboutModal from '../common/TableAboutModal';
@ -29,18 +27,21 @@ export type OwnProps = {
}; };
type StateProps = { type StateProps = {
message?: ApiSponsoredMessage;
minLevelToRestrictAds?: number; minLevelToRestrictAds?: number;
}; };
const AboutAdsModal = ({ message, minLevelToRestrictAds }: OwnProps & StateProps) => { const AboutAdsModal = ({ modal, minLevelToRestrictAds }: OwnProps & StateProps) => {
const { closeAboutAdsModal } = getActions(); const { closeAboutAdsModal } = getActions();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const moreMenuRef = useRef<HTMLButtonElement>(null); const moreMenuRef = useRef<HTMLButtonElement>(null);
const isOpen = Boolean(message); const isOpen = Boolean(modal);
const isMonetizationSharing = message?.canReport; const renderingModal = useCurrentOrPrev(modal);
const {
canReport, randomId, additionalInfo, sponsorInfo,
} = renderingModal || {};
const isMonetizationSharing = canReport;
const renderingIsNewDesign = useCurrentOrPrev(isMonetizationSharing); const renderingIsNewDesign = useCurrentOrPrev(isMonetizationSharing);
@ -139,12 +140,15 @@ const AboutAdsModal = ({ message, minLevelToRestrictAds }: OwnProps & StateProps
buttonText={oldLang('RevenueSharingAdsUnderstood')} buttonText={oldLang('RevenueSharingAdsUnderstood')}
onClose={handleClose} onClose={handleClose}
/> />
{contextMenuAnchor && message && ( {contextMenuAnchor && randomId && (
<SponsoredMessageContextMenuContainer <SponsoredMessageContextMenuContainer
isOpen={isContextMenuOpen} isOpen={isContextMenuOpen}
anchor={contextMenuAnchor} anchor={contextMenuAnchor}
triggerRef={moreMenuRef} triggerRef={moreMenuRef}
message={message} randomId={randomId}
additionalInfo={additionalInfo}
canReport={canReport}
sponsorInfo={sponsorInfo}
shouldSkipAbout shouldSkipAbout
onItemClick={handleClose} onItemClick={handleClose}
onClose={handleContextMenuClose} onClose={handleContextMenuClose}
@ -174,12 +178,10 @@ const AboutAdsModal = ({ message, minLevelToRestrictAds }: OwnProps & StateProps
}; };
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { modal }): StateProps => { (global): StateProps => {
const message = modal?.chatId ? selectSponsoredMessage(global, modal.chatId) : undefined;
const minLevelToRestrictAds = global.appConfig?.channelRestrictAdsLevelMin; const minLevelToRestrictAds = global.appConfig?.channelRestrictAdsLevelMin;
return { return {
message,
minLevelToRestrictAds, minLevelToRestrictAds,
}; };
}, },

View File

@ -30,7 +30,7 @@ const ReportAdModal = ({
modal, modal,
}: OwnProps) => { }: OwnProps) => {
const { const {
reportSponsoredMessage, closeReportAdModal, openPreviousReportAdModal, reportSponsored, closeReportAdModal, openPreviousReportAdModal,
} = getActions(); } = getActions();
const lang = useOldLang(); const lang = useOldLang();
const isOpen = Boolean(modal); const isOpen = Boolean(modal);
@ -40,7 +40,7 @@ const ReportAdModal = ({
const handleOptionClick = useLastCallback((e, option: string) => { const handleOptionClick = useLastCallback((e, option: string) => {
const { chatId, randomId } = modal!; const { chatId, randomId } = modal!;
reportSponsoredMessage({ peerId: chatId, randomId, option }); reportSponsored({ peerId: chatId, randomId, option });
}); });
const [renderingSection, renderingDepth] = useMemo(() => { const [renderingSection, renderingDepth] = useMemo(() => {

View File

@ -33,11 +33,14 @@ addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionRetur
if (query && !chatId) { if (query && !chatId) {
void searchThrottled(async () => { void searchThrottled(async () => {
const result = await callApi('searchChats', { query }); const [searchResult, sponsoredResult] = await Promise.all([
callApi('searchChats', { query }),
callApi('fetchSponsoredPeer', { query }),
]);
global = getGlobal(); global = getGlobal();
const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId); const currentSearchQuery = selectCurrentGlobalSearchQuery(global, tabId);
if (!result || !currentSearchQuery || (query !== currentSearchQuery)) { if (!searchResult || !currentSearchQuery || (query !== currentSearchQuery)) {
global = updateGlobalSearchFetchingStatus(global, { chats: false }, tabId); global = updateGlobalSearchFetchingStatus(global, { chats: false }, tabId);
setGlobal(global); setGlobal(global);
return; return;
@ -45,7 +48,7 @@ addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionRetur
const { const {
accountResultIds, globalResultIds, accountResultIds, globalResultIds,
} = result; } = searchResult;
global = updateGlobalSearchFetchingStatus(global, { chats: false }, tabId); global = updateGlobalSearchFetchingStatus(global, { chats: false }, tabId);
global = updateGlobalSearch(global, { global = updateGlobalSearch(global, {
@ -56,6 +59,7 @@ addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionRetur
...selectTabState(global, tabId).globalSearch.globalResults, ...selectTabState(global, tabId).globalSearch.globalResults,
peerIds: globalResultIds, peerIds: globalResultIds,
}, },
sponsoredPeer: sponsoredResult,
}, tabId); }, tabId);
setGlobal(global); setGlobal(global);

View File

@ -84,6 +84,7 @@ import {
updateChat, updateChat,
updateChatFullInfo, updateChatFullInfo,
updateChatMessage, updateChatMessage,
updateGlobalSearch,
updateListedIds, updateListedIds,
updateMessageTranslation, updateMessageTranslation,
updateOutlyingLists, updateOutlyingLists,
@ -135,7 +136,6 @@ import {
selectReplyCanBeSentToChat, selectReplyCanBeSentToChat,
selectScheduledMessage, selectScheduledMessage,
selectSendAs, selectSendAs,
selectSponsoredMessage,
selectTabState, selectTabState,
selectThreadIdFromMessage, selectThreadIdFromMessage,
selectTopic, selectTopic,
@ -1867,38 +1867,24 @@ addActionHandler('loadSponsoredMessages', async (global, actions, payload): Prom
setGlobal(global); setGlobal(global);
}); });
addActionHandler('viewSponsoredMessage', (global, actions, payload): ActionReturnType => { addActionHandler('viewSponsored', (global, actions, payload): ActionReturnType => {
const { peerId } = payload; const { randomId } = payload;
const peer = selectPeer(global, peerId);
const message = selectSponsoredMessage(global, peerId);
if (!peer || !message) {
return;
}
void callApi('viewSponsoredMessage', { random: message.randomId }); void callApi('viewSponsoredMessage', { random: randomId });
}); });
addActionHandler('clickSponsoredMessage', (global, actions, payload): ActionReturnType => { addActionHandler('clickSponsored', (global, actions, payload): ActionReturnType => {
const { peerId, isMedia, isFullscreen } = payload; const { randomId, isMedia, isFullscreen } = payload;
const peer = selectPeer(global, peerId);
const message = selectSponsoredMessage(global, peerId);
if (!peer || !message) {
return;
}
void callApi('clickSponsoredMessage', { void callApi('clickSponsoredMessage', {
random: message.randomId, isMedia, isFullscreen, random: randomId, isMedia, isFullscreen,
}); });
}); });
addActionHandler('reportSponsoredMessage', async (global, actions, payload): Promise<void> => { addActionHandler('reportSponsored', async (global, actions, payload): Promise<void> => {
const { const {
peerId, randomId, option = '', tabId = getCurrentTabId(), peerId, randomId, option = '', tabId = getCurrentTabId(),
} = payload; } = payload;
const peer = selectPeer(global, peerId);
if (!peer) {
return;
}
const result = await callApi('reportSponsoredMessage', { randomId, option }); const result = await callApi('reportSponsoredMessage', { randomId, option });
@ -1918,7 +1904,13 @@ addActionHandler('reportSponsoredMessage', async (global, actions, payload): Pro
actions.closeReportAdModal({ tabId }); actions.closeReportAdModal({ tabId });
global = getGlobal(); global = getGlobal();
global = deleteSponsoredMessage(global, peerId); if (peerId) {
global = deleteSponsoredMessage(global, peerId);
} else {
global = updateGlobalSearch(global, {
sponsoredPeer: undefined,
}, tabId);
}
setGlobal(global); setGlobal(global);
return; return;
} }
@ -1943,7 +1935,7 @@ addActionHandler('reportSponsoredMessage', async (global, actions, payload): Pro
} }
}); });
addActionHandler('hideSponsoredMessages', async (global, actions, payload): Promise<void> => { addActionHandler('hideSponsored', async (global, actions, payload): Promise<void> => {
const { tabId = getCurrentTabId() } = payload || {}; const { tabId = getCurrentTabId() } = payload || {};
const isCurrentUserPremium = selectIsCurrentUserPremium(global); const isCurrentUserPremium = selectIsCurrentUserPremium(global);
if (!isCurrentUserPremium) { if (!isCurrentUserPremium) {

View File

@ -1064,11 +1064,16 @@ addActionHandler('closeDeleteMessageModal', (global, actions, payload): ActionRe
}); });
addActionHandler('openAboutAdsModal', (global, actions, payload): ActionReturnType => { addActionHandler('openAboutAdsModal', (global, actions, payload): ActionReturnType => {
const { chatId, tabId = getCurrentTabId() } = payload || {}; const {
randomId, additionalInfo, canReport, sponsorInfo, tabId = getCurrentTabId(),
} = payload || {};
return updateTabState(global, { return updateTabState(global, {
aboutAdsModal: { aboutAdsModal: {
chatId, randomId,
canReport,
additionalInfo,
sponsorInfo,
}, },
}, tabId); }, tabId);
}); });

View File

@ -504,21 +504,24 @@ export interface ActionPayloads {
loadSponsoredMessages: { loadSponsoredMessages: {
peerId: string; peerId: string;
}; };
viewSponsoredMessage: { viewSponsored: {
peerId: string; randomId: string;
}; };
clickSponsoredMessage: { clickSponsored: {
peerId: string; randomId: string;
isMedia?: boolean; isMedia?: boolean;
isFullscreen?: boolean; isFullscreen?: boolean;
}; };
reportSponsoredMessage: { reportSponsored: {
peerId: string; peerId?: string;
randomId: string; randomId: string;
option?: string; option?: string;
} & WithTabId; } & WithTabId;
openAboutAdsModal: { openAboutAdsModal: {
chatId: string; randomId: string;
canReport?: boolean;
sponsorInfo?: string;
additionalInfo?: string;
} & WithTabId; } & WithTabId;
closeAboutAdsModal: WithTabId | undefined; closeAboutAdsModal: WithTabId | undefined;
openPreparedInlineMessageModal: { openPreparedInlineMessageModal: {
@ -542,7 +545,7 @@ export interface ActionPayloads {
openPreviousReportModal: WithTabId | undefined; openPreviousReportModal: WithTabId | undefined;
closeReportAdModal: WithTabId | undefined; closeReportAdModal: WithTabId | undefined;
closeReportModal: WithTabId | undefined; closeReportModal: WithTabId | undefined;
hideSponsoredMessages: WithTabId | undefined; hideSponsored: WithTabId | undefined;
loadSendAs: { loadSendAs: {
chatId: string; chatId: string;
}; };

View File

@ -35,6 +35,7 @@ import type {
ApiReceiptRegular, ApiReceiptRegular,
ApiSavedGifts, ApiSavedGifts,
ApiSavedStarGift, ApiSavedStarGift,
ApiSponsoredPeer,
ApiStarGift, ApiStarGift,
ApiStarGiftAttribute, ApiStarGiftAttribute,
ApiStarGiveawayOption, ApiStarGiveawayOption,
@ -186,7 +187,10 @@ export type TabState = {
}; };
aboutAdsModal?: { aboutAdsModal?: {
chatId: string; randomId: string;
canReport?: boolean;
sponsorInfo?: string;
additionalInfo?: string;
}; };
reactionPicker?: { reactionPicker?: {
@ -220,6 +224,7 @@ export type TabState = {
currentContent?: GlobalSearchContent; currentContent?: GlobalSearchContent;
chatId?: string; chatId?: string;
foundTopicIds?: number[]; foundTopicIds?: number[];
sponsoredPeer?: ApiSponsoredPeer;
fetchingStatus?: { fetchingStatus?: {
chats?: boolean; chats?: boolean;
messages?: boolean; messages?: boolean;
@ -440,7 +445,7 @@ export type TabState = {
openedCustomEmojiSetIds?: string[]; openedCustomEmojiSetIds?: string[];
reportAdModal?: { reportAdModal?: {
chatId: string; chatId?: string;
randomId: string; randomId: string;
sections: { sections: {
title: string; title: string;

View File

@ -1498,6 +1498,7 @@ contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.
contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates;
contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer; contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer;
contacts.editCloseFriends#ba6705f0 id:Vector<long> = Bool; contacts.editCloseFriends#ba6705f0 id:Vector<long> = Bool;
contacts.getSponsoredPeers#b6c8c393 q:string = contacts.SponsoredPeers;
messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages; messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs;
messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;

View File

@ -77,6 +77,7 @@
"contacts.addContact", "contacts.addContact",
"contacts.resolvePhone", "contacts.resolvePhone",
"contacts.editCloseFriends", "contacts.editCloseFriends",
"contacts.getSponsoredPeers",
"messages.getMessages", "messages.getMessages",
"messages.getDialogs", "messages.getDialogs",
"messages.getHistory", "messages.getHistory",

View File

@ -685,6 +685,7 @@ export interface LangPair {
'MessageRecommendedLabel': undefined; 'MessageRecommendedLabel': undefined;
'SponsoredMessageAd': undefined; 'SponsoredMessageAd': undefined;
'SponsoredMessageAdWhatIsThis': undefined; 'SponsoredMessageAdWhatIsThis': undefined;
'SponsoredPeerBadge': undefined;
'PremiumStickerTooltip': undefined; 'PremiumStickerTooltip': undefined;
'ViewAction': undefined; 'ViewAction': undefined;
'Loading': undefined; 'Loading': undefined;