Search: Display popular bot apps (#4864)
This commit is contained in:
parent
9daa5f1a19
commit
a4b33eb43a
@ -60,6 +60,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
|
||||
const {
|
||||
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
||||
bot, botActiveUsers, botInlinePlaceholder, botAttachMenu, botCanEdit,
|
||||
} = mtpUser;
|
||||
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
||||
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
||||
@ -80,7 +81,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
type: userType,
|
||||
firstName,
|
||||
lastName,
|
||||
canEditBot: Boolean(mtpUser.botCanEdit),
|
||||
canEditBot: botCanEdit,
|
||||
...(userType === 'userTypeBot' && { canBeInvitedToGroup: !mtpUser.botNochats }),
|
||||
...(usernames && { usernames }),
|
||||
phoneNumber: mtpUser.phone || '',
|
||||
@ -92,8 +93,9 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
areStoriesHidden: Boolean(mtpUser.storiesHidden),
|
||||
maxStoryId: storiesMaxId,
|
||||
hasStories: Boolean(storiesMaxId) && !storiesUnavailable,
|
||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
||||
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachBot: mtpUser.botAttachMenu }),
|
||||
...(bot && botInlinePlaceholder && { botPlaceholder: botInlinePlaceholder }),
|
||||
...(bot && botAttachMenu && { isAttachBot: botAttachMenu }),
|
||||
botActiveUsers,
|
||||
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
||||
};
|
||||
}
|
||||
|
||||
@ -82,6 +82,24 @@ export async function fetchTopInlineBots() {
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchTopBotApps() {
|
||||
const topPeers = await invokeRequest(new GramJs.contacts.GetTopPeers({
|
||||
botsApp: true,
|
||||
}));
|
||||
|
||||
if (!(topPeers instanceof GramJs.contacts.TopPeers)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const users = topPeers.users.map(buildApiUser).filter(Boolean);
|
||||
const ids = users.map(({ id }) => id);
|
||||
|
||||
return {
|
||||
ids,
|
||||
users,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchInlineBot({ username }: { username: string }) {
|
||||
const resolvedPeer = await invokeRequest(new GramJs.contacts.ResolveUsername({ username }));
|
||||
|
||||
@ -582,3 +600,30 @@ export function setBotInfo({
|
||||
shouldReturnTrue: true,
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchPopularAppBots({
|
||||
offset = '', limit,
|
||||
}: {
|
||||
offset?: string;
|
||||
limit?: number;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.bots.GetPopularAppBots({
|
||||
offset,
|
||||
limit,
|
||||
}));
|
||||
|
||||
if (!result) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
addEntitiesToLocalDb(result.users);
|
||||
|
||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||
const chats = result.users.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||
|
||||
return {
|
||||
users,
|
||||
chats,
|
||||
nextOffset: result.nextOffset,
|
||||
};
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ export {
|
||||
|
||||
export {
|
||||
answerCallbackButton, setBotInfo, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults,
|
||||
sendInlineBotResult, startBot,
|
||||
sendInlineBotResult, startBot, fetchPopularAppBots, fetchTopBotApps,
|
||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
||||
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
|
||||
|
||||
@ -40,6 +40,7 @@ export interface ApiUser {
|
||||
maxStoryId?: number;
|
||||
color?: ApiPeerColor;
|
||||
canEditBot?: boolean;
|
||||
botActiveUsers?: number;
|
||||
}
|
||||
|
||||
export interface ApiUserFullInfo {
|
||||
|
||||
@ -462,6 +462,7 @@
|
||||
}
|
||||
|
||||
.bot-menu-text {
|
||||
--emoji-size: 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
|
||||
@ -29,8 +29,6 @@
|
||||
|
||||
.TabList {
|
||||
justify-content: flex-start;
|
||||
padding-left: 0.5625rem;
|
||||
padding-bottom: 1px;
|
||||
border-bottom: 0;
|
||||
z-index: 1;
|
||||
|
||||
@ -45,15 +43,6 @@
|
||||
|
||||
.Tab {
|
||||
flex: 0 0 auto;
|
||||
padding-left: 0.625rem;
|
||||
padding-right: 0.625rem;
|
||||
|
||||
/* stylelint-disable-next-line */
|
||||
> span {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
|
||||
> .Transition {
|
||||
|
||||
@ -8,7 +8,6 @@ import { AudioOrigin, LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import { getIsDownloading, getMessageDownloadableMedia } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
||||
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
@ -58,7 +57,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
|
||||
}, [currentType, searchMessagesGlobal, searchQuery]);
|
||||
|
||||
const foundMessages = useMemo(() => {
|
||||
@ -89,36 +88,35 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const media = getMessageDownloadableMedia(message)!;
|
||||
return (
|
||||
<div
|
||||
className="ListItem small-icon"
|
||||
key={message.id}
|
||||
>
|
||||
<>
|
||||
{shouldDrawDateDivider && (
|
||||
<p
|
||||
className={buildClassName(
|
||||
'section-heading',
|
||||
isFirst && 'section-heading-first',
|
||||
!isFirst && 'section-heading-with-border',
|
||||
)}
|
||||
className="section-heading"
|
||||
key={message.date}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
||||
</p>
|
||||
)}
|
||||
<Audio
|
||||
<div
|
||||
className="ListItem small-icon"
|
||||
key={message.id}
|
||||
theme={theme}
|
||||
message={message}
|
||||
origin={AudioOrigin.Search}
|
||||
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
||||
date={message.date}
|
||||
className="scroll-item"
|
||||
onPlay={handlePlayAudio}
|
||||
onDateClick={handleMessageFocus}
|
||||
canDownload={!chatsById[message.chatId]?.isProtected && !message.isProtected}
|
||||
isDownloading={getIsDownloading(activeDownloads, media)}
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
<Audio
|
||||
key={message.id}
|
||||
theme={theme}
|
||||
message={message}
|
||||
origin={AudioOrigin.Search}
|
||||
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
||||
date={message.date}
|
||||
className="scroll-item"
|
||||
onPlay={handlePlayAudio}
|
||||
onDateClick={handleMessageFocus}
|
||||
canDownload={!chatsById[message.chatId]?.isProtected && !message.isProtected}
|
||||
isDownloading={getIsDownloading(activeDownloads, media)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -126,7 +124,7 @@ const AudioResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div className="LeftSearch">
|
||||
<div className="LeftSearch--content">
|
||||
<InfiniteScroll
|
||||
className="search-content documents-list custom-scroll"
|
||||
items={foundMessages}
|
||||
|
||||
153
src/components/left/search/BotAppResults.tsx
Normal file
153
src/components/left/search/BotAppResults.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useMemo, useRef,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import { filterUsersByName } from '../../../global/helpers';
|
||||
import { selectTabState } from '../../../global/selectors';
|
||||
import { throttle } from '../../../util/schedulers';
|
||||
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||
|
||||
import NothingFound from '../../common/NothingFound';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Link from '../../ui/Link';
|
||||
import Loading from '../../ui/Loading';
|
||||
import LeftSearchResultChat from './LeftSearchResultChat';
|
||||
|
||||
export type OwnProps = {
|
||||
searchQuery?: string;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
isSynced?: boolean;
|
||||
isLoading?: boolean;
|
||||
foundIds?: string[];
|
||||
recentBotIds?: string[];
|
||||
};
|
||||
|
||||
const LESS_LIST_ITEMS_AMOUNT = 5;
|
||||
const runThrottled = throttle((cb) => cb(), 500, true);
|
||||
|
||||
const BotAppResults: FC<OwnProps & StateProps> = ({
|
||||
searchQuery,
|
||||
isSynced,
|
||||
isLoading,
|
||||
foundIds,
|
||||
recentBotIds,
|
||||
}) => {
|
||||
const {
|
||||
searchPopularBotApps,
|
||||
openChatWithInfo,
|
||||
} = getActions();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const lang = useOldLang();
|
||||
|
||||
const [shouldShowMoreMine, setShouldShowMoreMine] = useState<boolean>(false);
|
||||
|
||||
const filteredFoundIds = useMemo(() => {
|
||||
if (!foundIds) return [];
|
||||
const recentSet = new Set(recentBotIds);
|
||||
const withoutRecent = foundIds.filter((id) => !recentSet.has(id));
|
||||
|
||||
const usersById = getGlobal().users.byId;
|
||||
return filterUsersByName(withoutRecent, usersById, searchQuery);
|
||||
}, [foundIds, recentBotIds, searchQuery]);
|
||||
|
||||
const handleChatClick = useLastCallback((id: string) => {
|
||||
openChatWithInfo({ id, shouldReplaceHistory: true });
|
||||
});
|
||||
|
||||
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
|
||||
if (!isSynced) return;
|
||||
if (direction === LoadMoreDirection.Backwards) {
|
||||
runThrottled(() => {
|
||||
searchPopularBotApps();
|
||||
});
|
||||
}
|
||||
}, [isSynced]);
|
||||
|
||||
const handleToggleShowMoreMine = useLastCallback(() => {
|
||||
setShouldShowMoreMine((prev) => !prev);
|
||||
});
|
||||
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch--content">
|
||||
<InfiniteScroll
|
||||
className="search-content custom-scroll"
|
||||
items={filteredFoundIds}
|
||||
onLoadMore={handleLoadMore}
|
||||
noFastList
|
||||
>
|
||||
{!canRenderContents && <Loading />}
|
||||
{canRenderContents && !filteredFoundIds?.length && (
|
||||
<NothingFound
|
||||
text={lang('ChatList.Search.NoResults')}
|
||||
description={lang('ChatList.Search.NoResultsDescription')}
|
||||
/>
|
||||
)}
|
||||
{canRenderContents && !searchQuery && recentBotIds?.length && (
|
||||
<div className="search-section">
|
||||
<h3 className="section-heading">
|
||||
{recentBotIds.length > LESS_LIST_ITEMS_AMOUNT && (
|
||||
<Link className="Link" onClick={handleToggleShowMoreMine}>
|
||||
{lang(shouldShowMoreMine ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
|
||||
</Link>
|
||||
)}
|
||||
{lang('SearchAppsMine')}
|
||||
</h3>
|
||||
{recentBotIds.map((id, index) => {
|
||||
if (!shouldShowMoreMine && index >= LESS_LIST_ITEMS_AMOUNT) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<LeftSearchResultChat
|
||||
chatId={id}
|
||||
onClick={handleChatClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{canRenderContents && filteredFoundIds?.length && (
|
||||
<div className="search-section">
|
||||
<h3 className="section-heading">{lang('SearchAppsPopular')}</h3>
|
||||
{filteredFoundIds.map((id) => {
|
||||
return (
|
||||
<LeftSearchResultChat
|
||||
chatId={id}
|
||||
onClick={handleChatClick}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</InfiniteScroll>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global) => {
|
||||
const globalSearch = selectTabState(global).globalSearch;
|
||||
const foundIds = globalSearch.popularBotApps?.peerIds;
|
||||
|
||||
return {
|
||||
isSynced: global.isSynced,
|
||||
isLoading: !foundIds && globalSearch.fetchingStatus?.botApps,
|
||||
foundIds,
|
||||
recentBotIds: global.topBotApps.userIds,
|
||||
};
|
||||
})(BotAppResults));
|
||||
@ -115,7 +115,7 @@ const ChatMessageResults: FC<OwnProps & StateProps> = ({
|
||||
&& !foundTopicIds?.length;
|
||||
|
||||
return (
|
||||
<div className="LeftSearch">
|
||||
<div className="LeftSearch--content">
|
||||
<InfiniteScroll
|
||||
className="search-content custom-scroll chat-list"
|
||||
items={foundMessages}
|
||||
|
||||
@ -238,7 +238,7 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return (
|
||||
<InfiniteScroll
|
||||
className="LeftSearch custom-scroll"
|
||||
className="LeftSearch--content custom-scroll"
|
||||
items={foundMessages}
|
||||
onLoadMore={handleLoadMore}
|
||||
// To prevent scroll jumps caused by delayed local results rendering
|
||||
|
||||
@ -10,7 +10,6 @@ import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import { getIsDownloading, getMessageDocument } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
||||
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
@ -69,7 +68,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
|
||||
// eslint-disable-next-line react-hooks-static-deps/exhaustive-deps -- `searchQuery` is required to prevent infinite message loading
|
||||
}, [searchQuery]);
|
||||
|
||||
const foundMessages = useMemo(() => {
|
||||
@ -95,36 +94,35 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
||||
const shouldDrawDateDivider = isFirst
|
||||
|| toYearMonth(message.date) !== toYearMonth(foundMessages[index - 1].date);
|
||||
return (
|
||||
<div
|
||||
className="ListItem small-icon"
|
||||
key={message.id}
|
||||
>
|
||||
<>
|
||||
{shouldDrawDateDivider && (
|
||||
<p
|
||||
className={buildClassName(
|
||||
'section-heading',
|
||||
isFirst && 'section-heading-first',
|
||||
!isFirst && 'section-heading-with-border',
|
||||
)}
|
||||
className="section-heading"
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
key={message.date}
|
||||
>
|
||||
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
||||
</p>
|
||||
)}
|
||||
<Document
|
||||
document={getMessageDocument(message)!}
|
||||
message={message}
|
||||
withDate
|
||||
datetime={message.date}
|
||||
smaller
|
||||
sender={getSenderName(lang, message, chatsById, usersById)}
|
||||
className="scroll-item"
|
||||
isDownloading={getIsDownloading(activeDownloads, message.content.document!)}
|
||||
shouldWarnAboutSvg={shouldWarnAboutSvg}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
onDateClick={handleMessageFocus}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="ListItem small-icon"
|
||||
key={message.id}
|
||||
>
|
||||
<Document
|
||||
document={getMessageDocument(message)!}
|
||||
message={message}
|
||||
withDate
|
||||
datetime={message.date}
|
||||
smaller
|
||||
sender={getSenderName(lang, message, chatsById, usersById)}
|
||||
className="scroll-item"
|
||||
isDownloading={getIsDownloading(activeDownloads, message.content.document!)}
|
||||
shouldWarnAboutSvg={shouldWarnAboutSvg}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
onDateClick={handleMessageFocus}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -132,7 +130,7 @@ const FileResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch">
|
||||
<div ref={containerRef} className="LeftSearch--content">
|
||||
<InfiniteScroll
|
||||
className="search-content documents-list custom-scroll"
|
||||
items={foundMessages}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
@use "../../../styles/mixins";
|
||||
|
||||
.LeftSearch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -10,23 +12,36 @@
|
||||
}
|
||||
|
||||
.TabList {
|
||||
padding-bottom: 1px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&--content {
|
||||
padding-top: 0.5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
&--media {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.documents-list {
|
||||
padding: 0 1.25rem 1.25rem;
|
||||
padding-bottom: 1.25rem;
|
||||
padding-left: 0.75rem;
|
||||
|
||||
.ListItem {
|
||||
padding: 0.625rem 0;
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.ListItem + .ListItem {
|
||||
padding-block: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
position: relative;
|
||||
padding-top: 0.25rem;
|
||||
padding-left: 1.25rem;
|
||||
margin: 0 0 0.5rem -1.25rem !important;
|
||||
padding-top: 0.625rem;
|
||||
padding-left: 1.75rem;
|
||||
margin: 0 0 1rem -1.25rem !important;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.9375rem;
|
||||
@ -36,16 +51,6 @@
|
||||
padding-top: 0.75rem;
|
||||
}
|
||||
|
||||
&-with-border::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: calc(100% + 1.25rem);
|
||||
height: 1px;
|
||||
background: var(--color-borders);
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&[dir="rtl"],
|
||||
&[dir="auto"] {
|
||||
padding-left: 0;
|
||||
@ -68,9 +73,8 @@
|
||||
.LeftSearch .search-section .section-heading,
|
||||
.RecentContacts .search-section .section-heading {
|
||||
margin-left: -0.5rem !important;
|
||||
padding-left: 1.5rem;
|
||||
padding-left: 1.125rem;
|
||||
width: calc(100% + 0.625rem);
|
||||
box-shadow: 0 -1px 0 0 var(--color-borders);
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
@ -104,6 +108,9 @@
|
||||
.media-list {
|
||||
display: grid;
|
||||
padding: 0.5rem;
|
||||
|
||||
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-auto-rows: 1fr;
|
||||
grid-gap: 0.25rem;
|
||||
@ -177,27 +184,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.ListItem {
|
||||
margin: 0 -0.125rem 0 -0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.search-section {
|
||||
padding: 0 0.5rem 0.5rem 0.5rem;
|
||||
padding: 0 0.125rem 0 1rem;
|
||||
|
||||
.section-heading {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0 !important;
|
||||
padding-top: 0.875rem;
|
||||
margin-bottom: 0.625rem !important;
|
||||
|
||||
.Link {
|
||||
float: right;
|
||||
color: var(--color-links);
|
||||
font-weight: 400;
|
||||
margin-right: 1rem;
|
||||
margin-right: 1.5rem;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
@ -222,12 +222,14 @@
|
||||
}
|
||||
|
||||
.chat-selection {
|
||||
padding-block: 0.5rem;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
box-shadow: inset 0 -1px 0 0 var(--color-borders);
|
||||
background-color: var(--color-background);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@ -20,6 +20,7 @@ import useOldLang from '../../../hooks/useOldLang';
|
||||
import TabList from '../../ui/TabList';
|
||||
import Transition from '../../ui/Transition';
|
||||
import AudioResults from './AudioResults';
|
||||
import BotAppResults from './BotAppResults';
|
||||
import ChatMessageResults from './ChatMessageResults';
|
||||
import ChatResults from './ChatResults';
|
||||
import FileResults from './FileResults';
|
||||
@ -43,6 +44,7 @@ type StateProps = {
|
||||
const TABS = [
|
||||
{ type: GlobalSearchContent.ChatList, title: 'SearchAllChatsShort' },
|
||||
{ type: GlobalSearchContent.ChannelList, title: 'ChannelsTab' },
|
||||
{ type: GlobalSearchContent.BotApps, title: 'AppsTab' },
|
||||
{ type: GlobalSearchContent.Media, title: 'SharedMediaTab2' },
|
||||
{ type: GlobalSearchContent.Links, title: 'SharedLinksTab2' },
|
||||
{ type: GlobalSearchContent.Files, title: 'SharedFilesTab2' },
|
||||
@ -52,7 +54,7 @@ const TABS = [
|
||||
|
||||
const CHAT_TABS = [
|
||||
{ type: GlobalSearchContent.ChatList, title: 'All Messages' },
|
||||
...TABS.slice(2), // Skip ChatList and ChannelList, replaced with All Messages
|
||||
...TABS.slice(3), // Skip ChatList, ChannelList and BotApps, replaced with All Messages
|
||||
];
|
||||
|
||||
const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||
@ -146,6 +148,13 @@ const LeftSearch: FC<OwnProps & StateProps> = ({
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
);
|
||||
case GlobalSearchContent.BotApps:
|
||||
return (
|
||||
<BotAppResults
|
||||
key="botApps"
|
||||
searchQuery={searchQuery}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { withGlobal } from '../../../global';
|
||||
import type { ApiChat, ApiUser } from '../../../api/types';
|
||||
import { StoryViewerOrigin } from '../../../types';
|
||||
|
||||
import { getPrivateChatUserId, isUserId, selectIsChatMuted } from '../../../global/helpers';
|
||||
import { isUserId, selectIsChatMuted } from '../../../global/helpers';
|
||||
import {
|
||||
selectChat, selectIsChatPinned, selectNotifyExceptions,
|
||||
selectNotifySettings, selectUser,
|
||||
@ -76,10 +76,6 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const buttonRef = useSelectWithEnter(handleClick);
|
||||
|
||||
if (!chat) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
className="chat-item-clickable search-result"
|
||||
@ -92,14 +88,14 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
userId={chatId}
|
||||
withUsername={withUsername}
|
||||
withStory
|
||||
avatarSize="large"
|
||||
avatarSize="medium"
|
||||
storyViewerOrigin={StoryViewerOrigin.SearchResult}
|
||||
/>
|
||||
) : (
|
||||
<GroupChatInfo
|
||||
chatId={chatId}
|
||||
withUsername={withUsername}
|
||||
avatarSize="large"
|
||||
avatarSize="medium"
|
||||
withStory
|
||||
storyViewerOrigin={StoryViewerOrigin.SearchResult}
|
||||
/>
|
||||
@ -127,8 +123,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }): StateProps => {
|
||||
const chat = selectChat(global, chatId);
|
||||
const privateChatUserId = chat && getPrivateChatUserId(chat);
|
||||
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||
const user = selectUser(global, chatId);
|
||||
const isPinned = selectIsChatPinned(global, chatId);
|
||||
const isMuted = chat
|
||||
? selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global))
|
||||
|
||||
@ -9,7 +9,6 @@ import type { StateProps } from './helpers/createMapStateToProps';
|
||||
import { LoadMoreDirection } from '../../../types';
|
||||
|
||||
import { SLIDE_TRANSITION_DURATION } from '../../../config';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatMonthAndYear, toYearMonth } from '../../../util/dates/dateFormat';
|
||||
import { parseSearchResultKey } from '../../../util/keys/searchResultKey';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
@ -92,32 +91,31 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
||||
const shouldDrawDateDivider = isFirst
|
||||
|| toYearMonth(message.date) !== toYearMonth(foundMessages[index - 1].date);
|
||||
return (
|
||||
<div
|
||||
className="ListItem small-icon"
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
key={message.id}
|
||||
>
|
||||
<>
|
||||
{shouldDrawDateDivider && (
|
||||
<p
|
||||
className={buildClassName(
|
||||
'section-heading',
|
||||
isFirst && 'section-heading-first',
|
||||
!isFirst && 'section-heading-with-border',
|
||||
)}
|
||||
className="section-heading"
|
||||
key={message.date}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
{formatMonthAndYear(lang, new Date(message.date * 1000))}
|
||||
</p>
|
||||
)}
|
||||
<WebLink
|
||||
<div
|
||||
className="ListItem small-icon"
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
key={message.id}
|
||||
message={message}
|
||||
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
||||
isProtected={isChatProtected || message.isProtected}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
onMessageClick={handleMessageFocus}
|
||||
/>
|
||||
</div>
|
||||
>
|
||||
<WebLink
|
||||
key={message.id}
|
||||
message={message}
|
||||
senderTitle={getSenderName(lang, message, chatsById, usersById)}
|
||||
isProtected={isChatProtected || message.isProtected}
|
||||
observeIntersection={observeIntersectionForMedia}
|
||||
onMessageClick={handleMessageFocus}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -125,7 +123,7 @@ const LinkResults: FC<OwnProps & StateProps> = ({
|
||||
const canRenderContents = useAsyncRendering([searchQuery], SLIDE_TRANSITION_DURATION) && !isLoading;
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch">
|
||||
<div ref={containerRef} className="LeftSearch--content">
|
||||
<InfiniteScroll
|
||||
className="search-content documents-list custom-scroll"
|
||||
items={foundMessages}
|
||||
|
||||
@ -123,7 +123,7 @@ const MediaResults: FC<OwnProps & StateProps> = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="LeftSearch">
|
||||
<div ref={containerRef} className="LeftSearch--content LeftSearch--media">
|
||||
<InfiniteScroll
|
||||
className={classNames}
|
||||
items={foundMessages}
|
||||
|
||||
@ -77,16 +77,10 @@
|
||||
.recent-chats-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.Button {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
&[dir="rtl"] {
|
||||
.Button {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
.clear-recent-chats {
|
||||
position: absolute;
|
||||
inset-inline-end: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,10 +105,11 @@ const RecentContacts: FC<OwnProps & StateProps> = ({
|
||||
{lang('Recent')}
|
||||
|
||||
<Button
|
||||
className="clear-recent-chats"
|
||||
round
|
||||
size="smaller"
|
||||
color="translucent"
|
||||
ariaLabel="Clear recent chats"
|
||||
ariaLabel={lang('Clear')}
|
||||
onClick={handleClearRecentlyFoundChats}
|
||||
isRtl={lang.isRtl}
|
||||
>
|
||||
|
||||
@ -259,6 +259,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
loadQuickReplies,
|
||||
loadStarStatus,
|
||||
loadAvailableEffects,
|
||||
loadTopBotApps,
|
||||
} = getActions();
|
||||
|
||||
if (DEBUG && !DEBUG_isLogged) {
|
||||
@ -341,6 +342,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
||||
loadGenericEmojiEffects();
|
||||
loadSavedReactionTags();
|
||||
loadAuthorizations();
|
||||
loadTopBotApps();
|
||||
}
|
||||
}, [isMasterTab, isSynced]);
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import type { FC } from '../../../lib/teact/teact';
|
||||
import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
||||
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import renderText from '../../common/helpers/renderText';
|
||||
|
||||
import Button from '../../ui/Button';
|
||||
|
||||
@ -52,7 +53,7 @@ const BotMenuButton: FC<OwnProps> = ({
|
||||
ariaLabel="Open bot command keyboard"
|
||||
>
|
||||
<i className={buildClassName('bot-menu-icon', 'icon', 'icon-webapp', isOpen && 'open')} />
|
||||
<span ref={textRef} className="bot-menu-text">{text}</span>
|
||||
<span ref={textRef} className="bot-menu-text">{renderText(text)}</span>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@ -46,18 +46,6 @@
|
||||
background: var(--color-background);
|
||||
top: -1px;
|
||||
z-index: 1;
|
||||
|
||||
.Tab {
|
||||
padding: 1rem 0.75rem;
|
||||
|
||||
&_inner {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.platform {
|
||||
bottom: -1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Transition {
|
||||
|
||||
@ -225,7 +225,7 @@ function getNodes(origin: StoryViewerOrigin, userId: string) {
|
||||
containerSelector = '#LeftColumn .chat-list';
|
||||
break;
|
||||
case StoryViewerOrigin.SearchResult:
|
||||
containerSelector = '#LeftColumn .LeftSearch';
|
||||
containerSelector = '#LeftColumn .LeftSearch--container';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -223,7 +223,7 @@
|
||||
}
|
||||
|
||||
.ListItem-button {
|
||||
padding: 0.5625rem;
|
||||
padding: 0.375rem;
|
||||
}
|
||||
|
||||
&.contact-list-item {
|
||||
@ -237,7 +237,7 @@
|
||||
}
|
||||
|
||||
.Avatar {
|
||||
margin-right: 0.5rem;
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
@ -270,6 +270,7 @@
|
||||
.typing-status {
|
||||
font-size: 1rem;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
text-align: initial;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
width: auto;
|
||||
margin: 0;
|
||||
border: none;
|
||||
padding: 0.625rem 0.25rem;
|
||||
padding: 0.625rem 1.125rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
border-top-left-radius: var(--border-radius-messages-small);
|
||||
@ -44,7 +44,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
> span {
|
||||
.Tab_inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -85,7 +85,7 @@
|
||||
|
||||
.platform {
|
||||
position: absolute;
|
||||
bottom: calc(-0.625rem - 1px);
|
||||
bottom: -0.625rem;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
background-color: var(--color-primary);
|
||||
|
||||
@ -5,13 +5,16 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 0.875rem;
|
||||
flex-wrap: nowrap;
|
||||
box-shadow: 0 2px 2px var(--color-light-shadow);
|
||||
background-color: var(--color-background);
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
font-size: 0.875rem;
|
||||
|
||||
padding-inline: 0.5rem;
|
||||
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: rgba(0, 0, 0, 0);
|
||||
|
||||
|
||||
@ -261,6 +261,32 @@ addActionHandler('loadTopInlineBots', async (global): Promise<void> => {
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadTopBotApps', async (global): Promise<void> => {
|
||||
const { lastRequestedAt } = global.topBotApps;
|
||||
if (lastRequestedAt && getServerTime() - lastRequestedAt < TOP_PEERS_REQUEST_COOLDOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('fetchTopBotApps');
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { ids, users } = result;
|
||||
|
||||
global = getGlobal();
|
||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||
global = {
|
||||
...global,
|
||||
topBotApps: {
|
||||
...global.topBotApps,
|
||||
userIds: ids,
|
||||
lastRequestedAt: getServerTime(),
|
||||
},
|
||||
};
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('queryInlineBot', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chatId, username, query, offset,
|
||||
|
||||
@ -112,6 +112,38 @@ addActionHandler('searchMessagesGlobal', (global, actions, payload): ActionRetur
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('searchPopularBotApps', async (global, actions, payload): Promise<void> => {
|
||||
const { tabId = getCurrentTabId() } = payload || {};
|
||||
const popularBotApps = selectTabState(global, tabId).globalSearch.popularBotApps;
|
||||
const offset = popularBotApps?.nextOffset;
|
||||
if (popularBotApps?.peerIds && !offset) return; // Already fetched all
|
||||
|
||||
global = updateGlobalSearchFetchingStatus(global, { botApps: true }, tabId);
|
||||
setGlobal(global);
|
||||
|
||||
const result = await callApi('fetchPopularAppBots', { offset });
|
||||
|
||||
global = getGlobal();
|
||||
if (!result) {
|
||||
global = updateGlobalSearchFetchingStatus(global, { botApps: false }, tabId);
|
||||
setGlobal(global);
|
||||
return;
|
||||
}
|
||||
|
||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||
global = addChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||
const peerIds = result.users.map(({ id }) => id);
|
||||
global = updateGlobalSearch(global, {
|
||||
popularBotApps: {
|
||||
peerIds: [...(popularBotApps?.peerIds || []), ...peerIds],
|
||||
nextOffset: result.nextOffset,
|
||||
},
|
||||
}, tabId);
|
||||
global = updateGlobalSearchFetchingStatus(global, { botApps: false }, tabId);
|
||||
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
async function searchMessagesGlobal<T extends GlobalState>(global: T, params: {
|
||||
query?: string;
|
||||
type: ApiGlobalMessageSearchType;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { ActionReturnType } from '../../types';
|
||||
import { GlobalSearchContent } from '../../../types';
|
||||
|
||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import { addActionHandler } from '../../index';
|
||||
@ -8,14 +9,17 @@ import { selectTabState } from '../../selectors';
|
||||
const MAX_RECENTLY_FOUND_IDS = 10;
|
||||
|
||||
addActionHandler('setGlobalSearchQuery', (global, actions, payload): ActionReturnType => {
|
||||
const { query, tabId = getCurrentTabId() } = payload!;
|
||||
const { chatId } = selectTabState(global, tabId).globalSearch;
|
||||
const { query, tabId = getCurrentTabId() } = payload;
|
||||
const { chatId, currentContent } = selectTabState(global, tabId).globalSearch;
|
||||
|
||||
const fetchingStatus = query && currentContent !== GlobalSearchContent.BotApps
|
||||
? { chats: !chatId, messages: true } : undefined;
|
||||
|
||||
return updateGlobalSearch(global, {
|
||||
globalResults: {},
|
||||
localResults: {},
|
||||
resultsByType: undefined,
|
||||
...(query ? { fetchingStatus: { chats: !chatId, messages: true } } : { fetchingStatus: undefined }),
|
||||
fetchingStatus,
|
||||
query,
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
@ -300,6 +300,7 @@ function reduceGlobal<T extends GlobalState>(global: T) {
|
||||
'contactList',
|
||||
'topPeers',
|
||||
'topInlineBots',
|
||||
'topBotApps',
|
||||
'recentEmojis',
|
||||
'recentCustomEmojis',
|
||||
'push',
|
||||
|
||||
@ -16,7 +16,7 @@ export function getRequestInputInvoice<T extends GlobalState>(
|
||||
const {
|
||||
userId, stars, amount, currency,
|
||||
} = inputInvoice;
|
||||
const user = selectUser(global, userId!);
|
||||
const user = selectUser(global, userId);
|
||||
|
||||
if (!user) return undefined;
|
||||
|
||||
|
||||
@ -77,6 +77,9 @@ export function getUserStatus(
|
||||
}
|
||||
|
||||
if (user.type && user.type === 'userTypeBot') {
|
||||
if (user.botActiveUsers) {
|
||||
return lang('BotUsers', user.botActiveUsers, 'i');
|
||||
}
|
||||
return lang('Bot');
|
||||
}
|
||||
|
||||
|
||||
@ -213,6 +213,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
topPeers: {},
|
||||
|
||||
topInlineBots: {},
|
||||
topBotApps: {},
|
||||
|
||||
activeSessions: {
|
||||
byHash: {},
|
||||
|
||||
@ -79,7 +79,7 @@ export function updateGlobalSearchResults<T extends GlobalState>(
|
||||
}
|
||||
|
||||
export function updateGlobalSearchFetchingStatus<T extends GlobalState>(
|
||||
global: T, newState: { chats?: boolean; messages?: boolean },
|
||||
global: T, newState: { chats?: boolean; messages?: boolean; botApps?: boolean },
|
||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
): T {
|
||||
return updateGlobalSearch(global, {
|
||||
|
||||
@ -376,6 +376,7 @@ export type TabState = {
|
||||
fetchingStatus?: {
|
||||
chats?: boolean;
|
||||
messages?: boolean;
|
||||
botApps?: boolean;
|
||||
};
|
||||
isClosing?: boolean;
|
||||
localResults?: {
|
||||
@ -384,6 +385,10 @@ export type TabState = {
|
||||
globalResults?: {
|
||||
peerIds?: string[];
|
||||
};
|
||||
popularBotApps?: {
|
||||
peerIds: string[];
|
||||
nextOffset?: string;
|
||||
};
|
||||
resultsByType?: Partial<Record<ApiGlobalMessageSearchType, {
|
||||
totalCount?: number;
|
||||
nextOffsetId?: number;
|
||||
@ -1120,6 +1125,11 @@ export type GlobalState = {
|
||||
lastRequestedAt?: number;
|
||||
};
|
||||
|
||||
topBotApps: {
|
||||
userIds?: string[];
|
||||
lastRequestedAt?: number;
|
||||
};
|
||||
|
||||
activeSessions: {
|
||||
byHash: Record<string, ApiSession>;
|
||||
orderedHashes: string[];
|
||||
@ -1491,6 +1501,7 @@ export interface ActionPayloads {
|
||||
searchMessagesGlobal: {
|
||||
type: ApiGlobalMessageSearchType;
|
||||
} & WithTabId;
|
||||
searchPopularBotApps: WithTabId | undefined;
|
||||
addRecentlyFoundChatId: {
|
||||
id: string;
|
||||
};
|
||||
@ -2800,6 +2811,7 @@ export interface ActionPayloads {
|
||||
chatId?: string;
|
||||
} & WithTabId;
|
||||
loadTopInlineBots: undefined;
|
||||
loadTopBotApps: undefined;
|
||||
queryInlineBot: {
|
||||
chatId: string;
|
||||
username: string;
|
||||
|
||||
@ -1617,6 +1617,7 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla
|
||||
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
|
||||
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||
|
||||
@ -228,6 +228,7 @@
|
||||
"bots.canSendMessage",
|
||||
"bots.allowSendMessage",
|
||||
"bots.invokeWebViewCustomMethod",
|
||||
"bots.getPopularAppBots",
|
||||
"bots.setBotInfo",
|
||||
"payments.getPaymentForm",
|
||||
"payments.getPaymentReceipt",
|
||||
|
||||
@ -291,6 +291,7 @@ export enum LeftColumnContent {
|
||||
export enum GlobalSearchContent {
|
||||
ChatList,
|
||||
ChannelList,
|
||||
BotApps,
|
||||
Media,
|
||||
Links,
|
||||
Files,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user