Chat Picker: Support web bot and share deep links, refactoring (#2037)

This commit is contained in:
Alexander Zinchuk 2022-09-24 01:40:29 +02:00
parent d896bb507d
commit 45a42f0d6c
38 changed files with 792 additions and 459 deletions

View File

@ -1,7 +1,7 @@
import { Api as GramJs } from '../../../lib/gramjs'; import { Api as GramJs } from '../../../lib/gramjs';
import type { import type {
ApiAttachMenuBot, ApiAttachBot,
ApiAttachMenuBotIcon, ApiAttachBotIcon,
ApiAttachMenuPeerType, ApiAttachMenuPeerType,
ApiBotCommand, ApiBotCommand,
ApiBotInfo, ApiBotInfo,
@ -62,7 +62,7 @@ export function buildBotSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) {
return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined; return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined;
} }
export function buildApiAttachMenuBot(bot: GramJs.AttachMenuBot): ApiAttachMenuBot { export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
return { return {
id: bot.botId.toString(), id: bot.botId.toString(),
hasSettings: bot.hasSettings, hasSettings: bot.hasSettings,
@ -73,15 +73,15 @@ export function buildApiAttachMenuBot(bot: GramJs.AttachMenuBot): ApiAttachMenuB
} }
function buildApiAttachMenuPeerType(peerType: GramJs.TypeAttachMenuPeerType): ApiAttachMenuPeerType { function buildApiAttachMenuPeerType(peerType: GramJs.TypeAttachMenuPeerType): ApiAttachMenuPeerType {
if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return 'bot'; if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return 'bots';
if (peerType instanceof GramJs.AttachMenuPeerTypePM) return 'private'; if (peerType instanceof GramJs.AttachMenuPeerTypePM) return 'users';
if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return 'chat'; if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return 'chats';
if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return 'channel'; if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return 'channels';
if (peerType instanceof GramJs.AttachMenuPeerTypeSameBotPM) return 'self'; if (peerType instanceof GramJs.AttachMenuPeerTypeSameBotPM) return 'self';
return undefined!; // Never reached return undefined!; // Never reached
} }
function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachMenuBotIcon | undefined { function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachBotIcon | undefined {
if (!(icon.icon instanceof GramJs.Document)) return undefined; if (!(icon.icon instanceof GramJs.Document)) return undefined;
const document = buildApiDocument(icon.icon); const document = buildApiDocument(icon.icon);

View File

@ -68,7 +68,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
...(avatarHash && { avatarHash }), ...(avatarHash && { avatarHash }),
hasVideoAvatar, hasVideoAvatar,
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }), ...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachMenuBot: mtpUser.botAttachMenu }), ...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachBot: mtpUser.botAttachMenu }),
}; };
} }

View File

@ -10,7 +10,7 @@ import { invokeRequest } from './client';
import { buildInputPeer, buildInputThemeParams, generateRandomBigInt } from '../gramjsBuilders'; import { buildInputPeer, buildInputThemeParams, generateRandomBigInt } from '../gramjsBuilders';
import { buildApiUser } from '../apiBuilders/users'; import { buildApiUser } from '../apiBuilders/users';
import { import {
buildApiAttachMenuBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm, buildApiAttachBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm,
} from '../apiBuilders/bots'; } from '../apiBuilders/bots';
import { buildApiChatFromPreview } from '../apiBuilders/chats'; import { buildApiChatFromPreview } from '../apiBuilders/chats';
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers'; import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
@ -249,7 +249,7 @@ export async function sendWebViewData({
}), true); }), true);
} }
export async function loadAttachMenuBots({ export async function loadAttachBots({
hash, hash,
}: { }: {
hash?: string; hash?: string;
@ -262,13 +262,13 @@ export async function loadAttachMenuBots({
addEntitiesWithPhotosToLocalDb(result.users); addEntitiesWithPhotosToLocalDb(result.users);
return { return {
hash: result.hash.toString(), hash: result.hash.toString(),
bots: buildCollectionByKey(result.bots.map(buildApiAttachMenuBot), 'id'), bots: buildCollectionByKey(result.bots.map(buildApiAttachBot), 'id'),
}; };
} }
return undefined; return undefined;
} }
export function toggleBotInAttachMenu({ export function toggleAttachBot({
bot, bot,
isEnabled, isEnabled,
}: { }: {

View File

@ -68,7 +68,7 @@ export {
export { export {
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot, answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachMenuBots, toggleBotInAttachMenu, requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot,
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth,
} from './bots'; } from './bots';

View File

@ -1,5 +1,6 @@
import type { ApiDocument, ApiPhoto } from './messages'; import type { ApiDocument, ApiPhoto } from './messages';
import type { ApiBotInfo } from './bots'; import type { ApiBotInfo } from './bots';
import type { API_CHAT_TYPES } from '../../config';
export interface ApiUser { export interface ApiUser {
id: string; id: string;
@ -26,7 +27,7 @@ export interface ApiUser {
isFullyLoaded: boolean; isFullyLoaded: boolean;
}; };
fakeType?: ApiFakeType; fakeType?: ApiFakeType;
isAttachMenuBot?: boolean; isAttachBot?: boolean;
// Obtained from GetFullUser / UserFullInfo // Obtained from GetFullUser / UserFullInfo
fullInfo?: ApiUserFullInfo; fullInfo?: ApiUserFullInfo;
@ -56,17 +57,18 @@ export interface ApiUserStatus {
expires?: number; expires?: number;
} }
export type ApiAttachMenuPeerType = 'self' | 'bot' | 'private' | 'chat' | 'channel'; export type ApiChatType = typeof API_CHAT_TYPES[number];
export type ApiAttachMenuPeerType = 'self' | ApiChatType;
export interface ApiAttachMenuBot { export interface ApiAttachBot {
id: string; id: string;
hasSettings?: boolean; hasSettings?: boolean;
shortName: string; shortName: string;
peerTypes: ApiAttachMenuPeerType[]; peerTypes: ApiAttachMenuPeerType[];
icons: ApiAttachMenuBotIcon[]; icons: ApiAttachBotIcon[];
} }
export interface ApiAttachMenuBotIcon { export interface ApiAttachBotIcon {
name: string; name: string;
document: ApiDocument; document: ApiDocument;
} }

View File

@ -1,7 +1,9 @@
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
export { default as MediaViewer } from '../components/mediaViewer/MediaViewer'; export { default as MediaViewer } from '../components/mediaViewer/MediaViewer';
export { default as ForwardPicker } from '../components/main/ForwardPicker'; export { default as ForwardRecipientPicker } from '../components/main/ForwardRecipientPicker';
export { default as DraftRecipientPicker } from '../components/main/DraftRecipientPicker';
export { default as AttachBotRecipientPicker } from '../components/main/AttachBotRecipientPicker';
export { default as Dialogs } from '../components/main/Dialogs'; export { default as Dialogs } from '../components/main/Dialogs';
export { default as Notifications } from '../components/main/Notifications'; export { default as Notifications } from '../components/main/Notifications';
export { default as SafeLinkModal } from '../components/main/SafeLinkModal'; export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
@ -10,7 +12,7 @@ export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
export { default as NewContactModal } from '../components/main/NewContactModal'; export { default as NewContactModal } from '../components/main/NewContactModal';
export { default as WebAppModal } from '../components/main/WebAppModal'; export { default as WebAppModal } from '../components/main/WebAppModal';
export { default as BotTrustModal } from '../components/main/BotTrustModal'; export { default as BotTrustModal } from '../components/main/BotTrustModal';
export { default as BotAttachModal } from '../components/main/BotAttachModal'; export { default as AttachBotInstallModal } from '../components/main/AttachBotInstallModal';
export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDialog'; export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDialog';
export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal'; export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal';
export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal'; export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal';

View File

@ -1,4 +1,3 @@
import type { RefObject } from 'react';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import React, { memo, useRef, useCallback } from '../../lib/teact/teact'; import React, { memo, useRef, useCallback } from '../../lib/teact/teact';
@ -24,11 +23,10 @@ export type OwnProps = {
currentUserId?: string; currentUserId?: string;
chatOrUserIds: string[]; chatOrUserIds: string[];
isOpen: boolean; isOpen: boolean;
filterRef: RefObject<HTMLInputElement>; searchPlaceholder: string;
filterPlaceholder: string; search: string;
filter: string;
loadMore?: NoneToVoidFunction; loadMore?: NoneToVoidFunction;
onFilterChange: (filter: string) => void; onSearchChange: (search: string) => void;
onSelectChatOrUser: (chatOrUserId: string) => void; onSelectChatOrUser: (chatOrUserId: string) => void;
onClose: NoneToVoidFunction; onClose: NoneToVoidFunction;
onCloseAnimationEnd?: NoneToVoidFunction; onCloseAnimationEnd?: NoneToVoidFunction;
@ -38,28 +36,29 @@ const ChatOrUserPicker: FC<OwnProps> = ({
isOpen, isOpen,
currentUserId, currentUserId,
chatOrUserIds, chatOrUserIds,
filterRef, search,
filter, searchPlaceholder,
filterPlaceholder,
loadMore, loadMore,
onFilterChange, onSearchChange,
onSelectChatOrUser, onSelectChatOrUser,
onClose, onClose,
onCloseAnimationEnd, onCloseAnimationEnd,
}) => { }) => {
const lang = useLang(); const lang = useLang();
const [viewportIds, getMore] = useInfiniteScroll(loadMore, chatOrUserIds, Boolean(filter)); const [viewportIds, getMore] = useInfiniteScroll(loadMore, chatOrUserIds, Boolean(search));
// eslint-disable-next-line no-null/no-null
const searchRef = useRef<HTMLInputElement>(null);
const resetFilter = useCallback(() => { const resetSearch = useCallback(() => {
onFilterChange(''); onSearchChange('');
}, [onFilterChange]); }, [onSearchChange]);
useInputFocusOnOpen(filterRef, isOpen, resetFilter); useInputFocusOnOpen(searchRef, isOpen, resetSearch);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const handleFilterChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => { const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
onFilterChange(e.currentTarget.value); onSearchChange(e.currentTarget.value);
}, [onFilterChange]); }, [onSearchChange]);
const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => { const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => {
if (viewportIds && viewportIds.length > 0) { if (viewportIds && viewportIds.length > 0) {
onSelectChatOrUser(viewportIds[index === -1 ? 0 : index]); onSelectChatOrUser(viewportIds[index === -1 ? 0 : index]);
@ -78,11 +77,11 @@ const ChatOrUserPicker: FC<OwnProps> = ({
<i className="icon-close" /> <i className="icon-close" />
</Button> </Button>
<InputText <InputText
ref={filterRef} ref={searchRef}
value={filter} value={search}
onChange={handleFilterChange} onChange={handleSearchChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder={filterPlaceholder} placeholder={searchPlaceholder}
/> />
</div> </div>
); );

View File

@ -0,0 +1,128 @@
import React, { memo, useMemo, useState } from '../../lib/teact/teact';
import { getGlobal, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiChat, ApiChatType } from '../../api/types';
import { MAIN_THREAD_ID } from '../../api/types';
import { API_CHAT_TYPES } from '../../config';
import { unique } from '../../util/iteratees';
import {
filterChatsByName,
filterUsersByName,
getCanPostInChat,
isDeletedUser,
sortChatIds,
} from '../../global/helpers';
import useLang from '../../hooks/useLang';
import ChatOrUserPicker from './ChatOrUserPicker';
import { filterChatIdsByType } from '../../global/selectors';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
export type OwnProps = {
isOpen: boolean;
searchPlaceholder: string;
filter?: ApiChatType[];
loadMore?: NoneToVoidFunction;
onSelectRecipient: (peerId: string) => void;
onClose: NoneToVoidFunction;
onCloseAnimationEnd?: NoneToVoidFunction;
};
type StateProps = {
currentUserId?: string;
chatsById: Record<string, ApiChat>;
activeListIds?: string[];
archivedListIds?: string[];
pinnedIds?: string[];
contactIds?: string[];
};
const RecipientPicker: FC<OwnProps & StateProps> = ({
isOpen,
currentUserId,
chatsById,
activeListIds,
archivedListIds,
pinnedIds,
contactIds,
filter = API_CHAT_TYPES,
searchPlaceholder,
loadMore,
onSelectRecipient,
onClose,
onCloseAnimationEnd,
}) => {
const lang = useLang();
const [search, setSearch] = useState('');
const ids = useMemo(() => {
if (!isOpen) return undefined;
let priorityIds = pinnedIds || [];
if (currentUserId) {
priorityIds = unique([currentUserId, ...priorityIds]);
}
// No need for expensive global updates on users, so we avoid them
const global = getGlobal();
const usersById = global.users.byId;
const chatIds = [
...(activeListIds || []),
...((search && archivedListIds) || []),
].filter((id) => {
const chat = chatsById[id];
const user = usersById[id];
if (user && isDeletedUser(user)) return false;
return chat && getCanPostInChat(chat, MAIN_THREAD_ID);
});
const sorted = sortChatIds(unique([
...filterChatsByName(lang, chatIds, chatsById, search, currentUserId),
...(contactIds && filter.includes('users') ? filterUsersByName(contactIds, usersById, search) : []),
]), chatsById, undefined, priorityIds);
return filterChatIdsByType(global, sorted, filter);
}, [pinnedIds, currentUserId, activeListIds, search, archivedListIds, lang, chatsById, contactIds, filter, isOpen]);
const renderingIds = useCurrentOrPrev(ids, true)!;
return (
<ChatOrUserPicker
isOpen={isOpen}
chatOrUserIds={renderingIds}
searchPlaceholder={searchPlaceholder}
search={search}
onSearchChange={setSearch}
loadMore={loadMore}
onSelectChatOrUser={onSelectRecipient}
onClose={onClose}
onCloseAnimationEnd={onCloseAnimationEnd}
/>
);
};
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const {
chats: {
byId: chatsById,
listIds,
orderedPinnedIds,
},
currentUserId,
} = global;
return {
chatsById,
activeListIds: listIds.active,
archivedListIds: listIds.archived,
pinnedIds: orderedPinnedIds.active,
contactIds: global.contactList?.userIds,
currentUserId,
};
},
)(RecipientPicker));

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
useMemo, useState, memo, useRef, useCallback, useEffect, useMemo, useState, memo, useCallback, useEffect,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global'; import { getActions, withGlobal } from '../../../global';
@ -40,13 +40,11 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
const [filter, setFilter] = useState(''); const [search, setSearch] = useState('');
// eslint-disable-next-line no-null/no-null
const filterRef = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
setUserSearchQuery({ query: filter }); setUserSearchQuery({ query: search });
}, [filter, setUserSearchQuery]); }, [search, setUserSearchQuery]);
const filteredContactIds = useMemo(() => { const filteredContactIds = useMemo(() => {
const availableContactIds = unique([ const availableContactIds = unique([
@ -56,14 +54,14 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
return contactId !== currentUserId && !blockedIds.includes(contactId); return contactId !== currentUserId && !blockedIds.includes(contactId);
})); }));
return filterUsersByName(availableContactIds, usersById, filter) return filterUsersByName(availableContactIds, usersById, search)
.sort((firstId, secondId) => { .sort((firstId, secondId) => {
const firstName = getUserFullName(usersById[firstId]) || ''; const firstName = getUserFullName(usersById[firstId]) || '';
const secondName = getUserFullName(usersById[secondId]) || ''; const secondName = getUserFullName(usersById[secondId]) || '';
return firstName.localeCompare(secondName); return firstName.localeCompare(secondName);
}); });
}, [blockedIds, contactIds, currentUserId, filter, localContactIds, usersById]); }, [blockedIds, contactIds, currentUserId, search, localContactIds, usersById]);
const handleRemoveUser = useCallback((userId: string) => { const handleRemoveUser = useCallback((userId: string) => {
const { id: contactId, accessHash } = usersById[userId] || {}; const { id: contactId, accessHash } = usersById[userId] || {};
@ -78,10 +76,9 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
<ChatOrUserPicker <ChatOrUserPicker
isOpen={isOpen} isOpen={isOpen}
chatOrUserIds={filteredContactIds} chatOrUserIds={filteredContactIds}
filterRef={filterRef} searchPlaceholder={lang('BlockedUsers.BlockUser')}
filterPlaceholder={lang('BlockedUsers.BlockUser')} search={search}
filter={filter} onSearchChange={setSearch}
onFilterChange={setFilter}
onSelectChatOrUser={handleRemoveUser} onSelectChatOrUser={handleRemoveUser}
onClose={onClose} onClose={onClose}
/> />

View File

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

View File

@ -1,7 +1,7 @@
import type { FC } from '../../lib/teact/teact'; import React, { memo } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import { getActions } from '../../global'; import { getActions } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiUser } from '../../api/types'; import type { ApiUser } from '../../api/types';
import useLang from '../../hooks/useLang'; import useLang from '../../hooks/useLang';
@ -12,10 +12,10 @@ export type OwnProps = {
bot?: ApiUser; bot?: ApiUser;
}; };
const BotAttachModal: FC<OwnProps> = ({ const AttachBotInstallModal: FC<OwnProps> = ({
bot, bot,
}) => { }) => {
const { closeBotAttachRequestModal, confirmBotAttachRequest } = getActions(); const { cancelAttachBotInstall, confirmAttachBotInstall } = getActions();
const lang = useLang(); const lang = useLang();
@ -24,12 +24,12 @@ const BotAttachModal: FC<OwnProps> = ({
return ( return (
<ConfirmDialog <ConfirmDialog
isOpen={Boolean(bot)} isOpen={Boolean(bot)}
onClose={closeBotAttachRequestModal} onClose={cancelAttachBotInstall}
confirmHandler={confirmBotAttachRequest} confirmHandler={confirmAttachBotInstall}
title={name} title={name}
textParts={lang('WebApp.AddToAttachmentText', name)} textParts={lang('WebApp.AddToAttachmentText', name)}
/> />
); );
}; };
export default BotAttachModal; export default memo(AttachBotInstallModal);

View File

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

View File

@ -0,0 +1,53 @@
import React, { memo, useCallback, useEffect } from '../../lib/teact/teact';
import { getActions } from '../../global';
import type { FC } from '../../lib/teact/teact';
import type { GlobalState } from '../../global/types';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import RecipientPicker from '../common/RecipientPicker';
export type OwnProps = {
requestedAttachBotInChat?: GlobalState['requestedAttachBotInChat'];
};
const AttachBotRecipientPicker: FC<OwnProps> = ({
requestedAttachBotInChat,
}) => {
const { cancelAttachBotInChat, callAttachBot } = getActions();
const lang = useLang();
const isOpen = Boolean(requestedAttachBotInChat);
const [isShown, markIsShown, unmarkIsShown] = useFlag();
useEffect(() => {
if (isOpen) {
markIsShown();
}
}, [isOpen, markIsShown]);
const { botId, filter, startParam } = requestedAttachBotInChat || {};
const handlePeerRecipient = useCallback((recipientId: string) => {
callAttachBot({ botId: botId!, chatId: recipientId, startParam });
cancelAttachBotInChat();
}, [botId, callAttachBot, cancelAttachBotInChat, startParam]);
if (!isOpen && !isShown) {
return undefined;
}
return (
<RecipientPicker
isOpen={isOpen}
searchPlaceholder={lang('Search')}
filter={filter}
onSelectRecipient={handlePeerRecipient}
onClose={cancelAttachBotInChat}
onCloseAnimationEnd={unmarkIsShown}
/>
);
};
export default memo(AttachBotRecipientPicker);

View File

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

View File

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

View File

@ -0,0 +1,59 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
import type { GlobalState } from '../../global/types';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import RecipientPicker from '../common/RecipientPicker';
export type OwnProps = {
requestedDraft?: GlobalState['requestedDraft'];
};
const DraftRecipientPicker: FC<OwnProps> = ({
requestedDraft,
}) => {
const isOpen = Boolean(requestedDraft && !requestedDraft.chatId);
const {
openChatWithDraft,
resetOpenChatWithDraft,
} = getActions();
const lang = useLang();
const [isShown, markIsShown, unmarkIsShown] = useFlag();
useEffect(() => {
if (isOpen) {
markIsShown();
}
}, [isOpen, markIsShown]);
const handleSelectRecipient = useCallback((recipientId: string) => {
openChatWithDraft({ chatId: recipientId, text: requestedDraft!.text });
}, [openChatWithDraft, requestedDraft]);
const handleClose = useCallback(() => {
resetOpenChatWithDraft();
}, [resetOpenChatWithDraft]);
if (!isOpen && !isShown) {
return undefined;
}
return (
<RecipientPicker
isOpen={isOpen}
searchPlaceholder={lang('ForwardTo')}
onSelectRecipient={handleSelectRecipient}
onClose={handleClose}
onCloseAnimationEnd={unmarkIsShown}
/>
);
};
export default memo(DraftRecipientPicker);

View File

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

View File

@ -1,154 +0,0 @@
import type { FC } from '../../lib/teact/teact';
import React, {
useMemo, useState, memo, useRef, useCallback, useEffect,
} from '../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../global';
import type { ApiChat } from '../../api/types';
import { MAIN_THREAD_ID } from '../../api/types';
import type { GlobalState } from '../../global/types';
import {
filterChatsByName,
filterUsersByName,
getCanPostInChat,
sortChatIds,
} from '../../global/helpers';
import { unique } from '../../util/iteratees';
import useLang from '../../hooks/useLang';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useFlag from '../../hooks/useFlag';
import ChatOrUserPicker from '../common/ChatOrUserPicker';
export type OwnProps = {
isOpen: boolean;
};
type StateProps = {
chatsById: Record<string, ApiChat>;
activeListIds?: string[];
archivedListIds?: string[];
pinnedIds?: string[];
contactIds?: string[];
currentUserId?: string;
switchBotInline?: GlobalState['switchBotInline'];
};
const ForwardPicker: FC<OwnProps & StateProps> = ({
chatsById,
activeListIds,
archivedListIds,
pinnedIds,
contactIds,
currentUserId,
isOpen,
switchBotInline,
}) => {
const {
setForwardChatId,
exitForwardMode,
openChatWithText,
resetSwitchBotInline,
} = getActions();
const lang = useLang();
const [filter, setFilter] = useState('');
// eslint-disable-next-line no-null/no-null
const filterRef = useRef<HTMLInputElement>(null);
const [isShown, markIsShown, unmarkIsShown] = useFlag();
useEffect(() => {
if (isOpen) {
markIsShown();
}
}, [isOpen, markIsShown]);
const chatAndContactIds = useMemo(() => {
if (!isOpen) {
return undefined;
}
let priorityIds = pinnedIds || [];
if (currentUserId) {
priorityIds = unique([currentUserId, ...priorityIds]);
}
const chatIds = [
...(activeListIds || []),
...((filter && archivedListIds) || []),
].filter((id) => {
const chat = chatsById[id];
return chat && getCanPostInChat(chat, MAIN_THREAD_ID);
});
// No need for expensive global updates on users, so we avoid them
const usersById = getGlobal().users.byId;
return sortChatIds(unique([
...filterChatsByName(lang, chatIds, chatsById, filter, currentUserId),
...(contactIds ? filterUsersByName(contactIds, usersById, filter) : []),
]), chatsById, undefined, priorityIds);
}, [activeListIds, archivedListIds, chatsById, contactIds, currentUserId, filter, isOpen, lang, pinnedIds]);
const handleSelectUser = useCallback((userId: string) => {
if (switchBotInline) {
const text = `@${switchBotInline.botUsername} ${switchBotInline.query}`;
openChatWithText({ chatId: userId, text });
resetSwitchBotInline();
} else {
setForwardChatId({ id: userId });
}
}, [openChatWithText, resetSwitchBotInline, setForwardChatId, switchBotInline]);
const handleClose = useCallback(() => {
exitForwardMode();
resetSwitchBotInline();
}, [exitForwardMode, resetSwitchBotInline]);
const renderingChatAndContactIds = useCurrentOrPrev(chatAndContactIds, true)!;
if (!isOpen && !isShown) {
return undefined;
}
return (
<ChatOrUserPicker
currentUserId={currentUserId}
isOpen={isOpen}
chatOrUserIds={renderingChatAndContactIds}
filterRef={filterRef}
filterPlaceholder={lang('ForwardTo')}
filter={filter}
onFilterChange={setFilter}
onSelectChatOrUser={handleSelectUser}
onClose={handleClose}
onCloseAnimationEnd={unmarkIsShown}
/>
);
};
export default memo(withGlobal<OwnProps>(
(global): StateProps => {
const {
chats: {
byId: chatsById,
listIds,
orderedPinnedIds,
},
currentUserId,
switchBotInline,
} = global;
return {
chatsById,
activeListIds: listIds.active,
archivedListIds: listIds.archived,
pinnedIds: orderedPinnedIds.active,
contactIds: global.contactList?.userIds,
currentUserId,
switchBotInline,
};
},
)(ForwardPicker));

View File

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

View File

@ -0,0 +1,56 @@
import type { FC } from '../../lib/teact/teact';
import React, {
memo, useCallback, useEffect,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
import useLang from '../../hooks/useLang';
import useFlag from '../../hooks/useFlag';
import RecipientPicker from '../common/RecipientPicker';
export type OwnProps = {
isOpen: boolean;
};
const ForwardRecipientPicker: FC<OwnProps> = ({
isOpen,
}) => {
const {
setForwardChatId,
exitForwardMode,
} = getActions();
const lang = useLang();
const [isShown, markIsShown, unmarkIsShown] = useFlag();
useEffect(() => {
if (isOpen) {
markIsShown();
}
}, [isOpen, markIsShown]);
const handleSelectRecipient = useCallback((recipientId: string) => {
setForwardChatId({ id: recipientId });
}, [setForwardChatId]);
const handleClose = useCallback(() => {
exitForwardMode();
}, [exitForwardMode]);
if (!isOpen && !isShown) {
return undefined;
}
return (
<RecipientPicker
isOpen={isOpen}
searchPlaceholder={lang('ForwardTo')}
onSelectRecipient={handleSelectRecipient}
onClose={handleClose}
onCloseAnimationEnd={unmarkIsShown}
/>
);
};
export default memo(ForwardRecipientPicker);

View File

@ -52,7 +52,7 @@ import DownloadManager from './DownloadManager';
import GameModal from './GameModal'; import GameModal from './GameModal';
import Notifications from './Notifications.async'; import Notifications from './Notifications.async';
import Dialogs from './Dialogs.async'; import Dialogs from './Dialogs.async';
import ForwardPicker from './ForwardPicker.async'; import ForwardRecipientPicker from './ForwardRecipientPicker.async';
import SafeLinkModal from './SafeLinkModal.async'; import SafeLinkModal from './SafeLinkModal.async';
import HistoryCalendar from './HistoryCalendar.async'; import HistoryCalendar from './HistoryCalendar.async';
import GroupCall from '../calls/group/GroupCall.async'; import GroupCall from '../calls/group/GroupCall.async';
@ -63,7 +63,7 @@ import NewContactModal from './NewContactModal.async';
import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async'; import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async';
import WebAppModal from './WebAppModal.async'; import WebAppModal from './WebAppModal.async';
import BotTrustModal from './BotTrustModal.async'; import BotTrustModal from './BotTrustModal.async';
import BotAttachModal from './BotAttachModal.async'; import AttachBotInstallModal from './AttachBotInstallModal.async';
import ConfettiContainer from './ConfettiContainer'; import ConfettiContainer from './ConfettiContainer';
import UrlAuthModal from './UrlAuthModal.async'; import UrlAuthModal from './UrlAuthModal.async';
import PremiumMainModal from './premium/PremiumMainModal.async'; import PremiumMainModal from './premium/PremiumMainModal.async';
@ -72,6 +72,8 @@ import ReceiptModal from '../payment/ReceiptModal.async';
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async'; import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
import DeleteFolderDialog from './DeleteFolderDialog.async'; import DeleteFolderDialog from './DeleteFolderDialog.async';
import CustomEmojiSetsModal from '../common/CustomEmojiSetsModal.async'; import CustomEmojiSetsModal from '../common/CustomEmojiSetsModal.async';
import DraftRecipientPicker from './DraftRecipientPicker.async';
import AttachBotRecipientPicker from './AttachBotRecipientPicker.async';
import './Main.scss'; import './Main.scss';
@ -109,7 +111,9 @@ type StateProps = {
isPremiumModalOpen?: boolean; isPremiumModalOpen?: boolean;
botTrustRequest?: GlobalState['botTrustRequest']; botTrustRequest?: GlobalState['botTrustRequest'];
botTrustRequestBot?: ApiUser; botTrustRequestBot?: ApiUser;
botAttachRequestBot?: ApiUser; attachBotToInstall?: ApiUser;
requestedAttachBotInChat?: GlobalState['requestedAttachBotInChat'];
requestedDraft?: GlobalState['requestedDraft'];
currentUser?: ApiUser; currentUser?: ApiUser;
urlAuth?: GlobalState['urlAuth']; urlAuth?: GlobalState['urlAuth'];
limitReached?: ApiLimitTypeWithModal; limitReached?: ApiLimitTypeWithModal;
@ -158,7 +162,9 @@ const Main: FC<StateProps> = ({
isRatePhoneCallModalOpen, isRatePhoneCallModalOpen,
botTrustRequest, botTrustRequest,
botTrustRequestBot, botTrustRequestBot,
botAttachRequestBot, attachBotToInstall,
requestedAttachBotInChat,
requestedDraft,
webApp, webApp,
currentUser, currentUser,
urlAuth, urlAuth,
@ -186,7 +192,7 @@ const Main: FC<StateProps> = ({
closeCustomEmojiSets, closeCustomEmojiSets,
checkVersionNotification, checkVersionNotification,
loadAppConfig, loadAppConfig,
loadAttachMenuBots, loadAttachBots,
loadContactList, loadContactList,
loadCustomEmojis, loadCustomEmojis,
closePaymentModal, closePaymentModal,
@ -219,14 +225,14 @@ const Main: FC<StateProps> = ({
loadNotificationExceptions(); loadNotificationExceptions();
loadTopInlineBots(); loadTopInlineBots();
loadEmojiKeywords({ language: BASE_EMOJI_KEYWORD_LANG }); loadEmojiKeywords({ language: BASE_EMOJI_KEYWORD_LANG });
loadAttachMenuBots(); loadAttachBots();
loadContactList(); loadContactList();
loadPremiumGifts(); loadPremiumGifts();
checkAppVersion(); checkAppVersion();
} }
}, [ }, [
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings, lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachMenuBots, loadContactList, loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
loadPremiumGifts, checkAppVersion, loadPremiumGifts, checkAppVersion,
]); ]);
@ -423,7 +429,8 @@ const Main: FC<StateProps> = ({
<MiddleColumn /> <MiddleColumn />
<RightColumn /> <RightColumn />
<MediaViewer isOpen={isMediaViewerOpen} /> <MediaViewer isOpen={isMediaViewerOpen} />
<ForwardPicker isOpen={isForwardModalOpen} /> <ForwardRecipientPicker isOpen={isForwardModalOpen} />
<DraftRecipientPicker requestedDraft={requestedDraft} />
<Notifications isOpen={hasNotifications} /> <Notifications isOpen={hasNotifications} />
<Dialogs isOpen={hasDialogs} /> <Dialogs isOpen={hasDialogs} />
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />} {audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
@ -454,7 +461,8 @@ const Main: FC<StateProps> = ({
<UnreadCount isForAppBadge /> <UnreadCount isForAppBadge />
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} /> <RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
<BotTrustModal bot={botTrustRequestBot} type={botTrustRequest?.type} /> <BotTrustModal bot={botTrustRequestBot} type={botTrustRequest?.type} />
<BotAttachModal bot={botAttachRequestBot} /> <AttachBotInstallModal bot={attachBotToInstall} />
<AttachBotRecipientPicker requestedAttachBotInChat={requestedAttachBotInChat} />
<MessageListHistoryHandler /> <MessageListHistoryHandler />
{isPremiumModalOpen && <PremiumMainModal isOpen={isPremiumModalOpen} />} {isPremiumModalOpen && <PremiumMainModal isOpen={isPremiumModalOpen} />}
<PremiumLimitReachedModal limit={limitReached} /> <PremiumLimitReachedModal limit={limitReached} />
@ -494,8 +502,19 @@ export default memo(withGlobal(
animationLevel, language, wasTimeFormatSetManually, animationLevel, language, wasTimeFormatSetManually,
}, },
}, },
connectionState,
botTrustRequest, botTrustRequest,
botAttachRequest, requestedAttachBotInstall,
requestedAttachBotInChat,
requestedDraft,
urlAuth,
webApp,
safeLinkModalUrl,
authState,
lastSyncTime,
openedStickerSetShortName,
openedCustomEmojiSetIds,
shouldSkipHistoryAnimations,
} = global; } = global;
const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer; const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer;
const audioMessage = audioChatId && audioMessageId const audioMessage = audioChatId && audioMessageId
@ -507,9 +526,9 @@ export default memo(withGlobal(
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined; const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
return { return {
connectionState: global.connectionState, connectionState,
authState: global.authState, authState,
lastSyncTime: global.lastSyncTime, lastSyncTime,
isLeftColumnOpen: global.isLeftColumnShown, isLeftColumnOpen: global.isLeftColumnShown,
isRightColumnOpen: selectIsRightColumnShown(global), isRightColumnOpen: selectIsRightColumnShown(global),
isMediaViewerOpen: selectIsMediaViewerOpen(global), isMediaViewerOpen: selectIsMediaViewerOpen(global),
@ -517,11 +536,11 @@ export default memo(withGlobal(
hasNotifications: Boolean(global.notifications.length), hasNotifications: Boolean(global.notifications.length),
hasDialogs: Boolean(global.dialogs.length), hasDialogs: Boolean(global.dialogs.length),
audioMessage, audioMessage,
safeLinkModalUrl: global.safeLinkModalUrl, safeLinkModalUrl,
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt), isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations, shouldSkipHistoryAnimations,
openedStickerSetShortName: global.openedStickerSetShortName, openedStickerSetShortName,
openedCustomEmojiSetIds: global.openedCustomEmojiSetIds, openedCustomEmojiSetIds,
isServiceChatReady: selectIsServiceChatReady(global), isServiceChatReady: selectIsServiceChatReady(global),
activeGroupCallId: global.groupCalls.activeGroupCallId, activeGroupCallId: global.groupCalls.activeGroupCallId,
animationLevel, animationLevel,
@ -537,15 +556,17 @@ export default memo(withGlobal(
isRatePhoneCallModalOpen: Boolean(global.ratingPhoneCall), isRatePhoneCallModalOpen: Boolean(global.ratingPhoneCall),
botTrustRequest, botTrustRequest,
botTrustRequestBot: botTrustRequest && selectUser(global, botTrustRequest.botId), botTrustRequestBot: botTrustRequest && selectUser(global, botTrustRequest.botId),
botAttachRequestBot: botAttachRequest && selectUser(global, botAttachRequest.botId), attachBotToInstall: requestedAttachBotInstall && selectUser(global, requestedAttachBotInstall.botId),
webApp: global.webApp, requestedAttachBotInChat,
webApp,
currentUser, currentUser,
urlAuth: global.urlAuth, urlAuth,
isPremiumModalOpen: global.premiumModal?.isOpen, isPremiumModalOpen: global.premiumModal?.isOpen,
limitReached: global.limitReachedModal?.limit, limitReached: global.limitReachedModal?.limit,
isPaymentModalOpen: global.payment.isPaymentModalOpen, isPaymentModalOpen: global.payment.isPaymentModalOpen,
isReceiptModalOpen: Boolean(global.payment.receipt), isReceiptModalOpen: Boolean(global.payment.receipt),
deleteFolderDialogId: global.deleteFolderDialogModal, deleteFolderDialogId: global.deleteFolderDialogModal,
requestedDraft,
}; };
}, },
)(Main)); )(Main));

View File

@ -4,7 +4,7 @@ import React, {
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { FC } from '../../lib/teact/teact'; import type { FC } from '../../lib/teact/teact';
import type { ApiAttachMenuBot, ApiChat, ApiUser } from '../../api/types'; import type { ApiAttachBot, ApiChat, ApiUser } from '../../api/types';
import type { GlobalState } from '../../global/types'; import type { GlobalState } from '../../global/types';
import type { ThemeKey } from '../../types'; import type { ThemeKey } from '../../types';
import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame'; import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame';
@ -48,7 +48,7 @@ export type OwnProps = {
type StateProps = { type StateProps = {
chat?: ApiChat; chat?: ApiChat;
bot?: ApiUser; bot?: ApiUser;
attachMenuBot?: ApiAttachMenuBot; attachBot?: ApiAttachBot;
theme?: ThemeKey; theme?: ThemeKey;
isPaymentModalOpen?: boolean; isPaymentModalOpen?: boolean;
paymentStatus?: GlobalState['payment']['status']; paymentStatus?: GlobalState['payment']['status'];
@ -78,7 +78,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
webApp, webApp,
chat, chat,
bot, bot,
attachMenuBot, attachBot,
theme, theme,
isPaymentModalOpen, isPaymentModalOpen,
paymentStatus, paymentStatus,
@ -87,7 +87,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
closeWebApp, closeWebApp,
sendWebViewData, sendWebViewData,
prolongWebView, prolongWebView,
toggleBotInAttachMenu, toggleAttachBot,
openTelegramLink, openTelegramLink,
openChat, openChat,
openInvoice, openInvoice,
@ -279,11 +279,11 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
}, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp] as const); }, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp] as const);
const handleToggleClick = useCallback(() => { const handleToggleClick = useCallback(() => {
toggleBotInAttachMenu({ toggleAttachBot({
botId: bot!.id, botId: bot!.id,
isEnabled: !attachMenuBot, isEnabled: !attachBot,
}); });
}, [bot, attachMenuBot, toggleBotInAttachMenu]); }, [bot, attachBot, toggleAttachBot]);
const handleBackClick = useCallback(() => { const handleBackClick = useCallback(() => {
if (isBackButtonVisible) { if (isBackButtonVisible) {
@ -353,16 +353,16 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
<MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem> <MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem>
)} )}
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem> <MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem>
{bot?.isAttachMenuBot && ( {bot?.isAttachBot && (
<MenuItem <MenuItem
icon={attachMenuBot ? 'stop' : 'install'} icon={attachBot ? 'stop' : 'install'}
onClick={handleToggleClick} onClick={handleToggleClick}
destructive={Boolean(attachMenuBot)} destructive={Boolean(attachBot)}
> >
{lang(attachMenuBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')} {lang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
</MenuItem> </MenuItem>
)} )}
{attachMenuBot?.hasSettings && ( {attachBot?.hasSettings && (
<MenuItem icon="settings" onClick={handleSettingsButtonClick}> <MenuItem icon="settings" onClick={handleSettingsButtonClick}>
{lang('Settings')} {lang('Settings')}
</MenuItem> </MenuItem>
@ -371,7 +371,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
</div> </div>
); );
}, [ }, [
lang, handleBackClick, bot, MoreMenuButton, chat, openBotChat, handleRefreshClick, attachMenuBot, lang, handleBackClick, bot, MoreMenuButton, chat, openBotChat, handleRefreshClick, attachBot,
handleToggleClick, handleSettingsButtonClick, isBackButtonVisible, headerColor, backButtonClassName, handleToggleClick, handleSettingsButtonClick, isBackButtonVisible, headerColor, backButtonClassName,
]); ]);
@ -494,14 +494,14 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(
(global, { webApp }): StateProps => { (global, { webApp }): StateProps => {
const { botId } = webApp || {}; const { botId } = webApp || {};
const attachMenuBot = botId ? global.attachMenu.bots[botId] : undefined; const attachBot = botId ? global.attachMenu.bots[botId] : undefined;
const bot = botId ? selectUser(global, botId) : undefined; const bot = botId ? selectUser(global, botId) : undefined;
const chat = selectCurrentChat(global); const chat = selectCurrentChat(global);
const theme = selectTheme(global); const theme = selectTheme(global);
const { isPaymentModalOpen, status } = global.payment; const { isPaymentModalOpen, status } = global.payment;
return { return {
attachMenuBot, attachBot,
bot, bot,
chat, chat,
theme, theme,

View File

@ -39,7 +39,7 @@ type StateProps = {
canReportMessages?: boolean; canReportMessages?: boolean;
canDownloadMessages?: boolean; canDownloadMessages?: boolean;
hasProtectedMessage?: boolean; hasProtectedMessage?: boolean;
isForwardModalOpen?: boolean; isAnyModalOpen?: boolean;
selectedMessageIds?: number[]; selectedMessageIds?: number[];
}; };
@ -53,7 +53,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
canReportMessages, canReportMessages,
canDownloadMessages, canDownloadMessages,
hasProtectedMessage, hasProtectedMessage,
isForwardModalOpen, isAnyModalOpen,
selectedMessageIds, selectedMessageIds,
}) => { }) => {
const { const {
@ -70,7 +70,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
useCopySelectedMessages(Boolean(isActive), copySelectedMessages); useCopySelectedMessages(Boolean(isActive), copySelectedMessages);
useEffect(() => { useEffect(() => {
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isForwardModalOpen return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen
? captureKeyboardListeners({ ? captureKeyboardListeners({
onBackspace: canDeleteMessages ? openDeleteModal : undefined, onBackspace: canDeleteMessages ? openDeleteModal : undefined,
onDelete: canDeleteMessages ? openDeleteModal : undefined, onDelete: canDeleteMessages ? openDeleteModal : undefined,
@ -78,7 +78,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
}) })
: undefined; : undefined;
}, [ }, [
isActive, isDeleteModalOpen, isReportModalOpen, openDeleteModal, exitMessageSelectMode, isForwardModalOpen, isActive, isDeleteModalOpen, isReportModalOpen, openDeleteModal, exitMessageSelectMode, isAnyModalOpen,
canDeleteMessages, canDeleteMessages,
]); ]);
@ -183,6 +183,8 @@ export default memo(withGlobal<OwnProps>(
const { messageIds: selectedMessageIds } = global.selectedMessages || {}; const { messageIds: selectedMessageIds } = global.selectedMessages || {};
const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false; const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false;
const isForwardModalOpen = global.forwardMessages.isModalShown; const isForwardModalOpen = global.forwardMessages.isModalShown;
const isAnyModalOpen = Boolean(isForwardModalOpen || global.requestedDraft
|| global.requestedAttachBotInChat || global.requestedAttachBotInstall);
return { return {
isSchedule, isSchedule,
@ -192,7 +194,7 @@ export default memo(withGlobal<OwnProps>(
canDownloadMessages: canDownload, canDownloadMessages: canDownload,
selectedMessageIds, selectedMessageIds,
hasProtectedMessage, hasProtectedMessage,
isForwardModalOpen, isAnyModalOpen,
}; };
}, },
)(MessageSelectToolbar)); )(MessageSelectToolbar));

View File

@ -10,7 +10,7 @@ import useMedia from '../../../hooks/useMedia';
import { getDocumentMediaHash } from '../../../global/helpers'; import { getDocumentMediaHash } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import styles from './AttachmentMenuBotIcon.module.scss'; import styles from './AttachBotIcon.module.scss';
type OwnProps = { type OwnProps = {
icon: ApiDocument; icon: ApiDocument;
@ -22,7 +22,7 @@ const DARK_THEME_COLOR = 'rgb(170, 170, 170)';
const LIGHT_THEME_COLOR = 'rgb(112, 117, 121)'; const LIGHT_THEME_COLOR = 'rgb(112, 117, 121)';
const COLOR_REPLACE_PATTERN = /#fff/gi; const COLOR_REPLACE_PATTERN = /#fff/gi;
const AttachmentMenuBotIcon: FC<OwnProps> = ({ const AttachBotIcon: FC<OwnProps> = ({
icon, theme, icon, theme,
}) => { }) => {
const mediaData = useMedia(getDocumentMediaHash(icon), false, ApiMediaFormat.Text); const mediaData = useMedia(getDocumentMediaHash(icon), false, ApiMediaFormat.Text);
@ -48,4 +48,4 @@ const AttachmentMenuBotIcon: FC<OwnProps> = ({
); );
}; };
export default memo(AttachmentMenuBotIcon); export default memo(AttachBotIcon);

View File

@ -5,7 +5,7 @@ import React, {
import { getActions } from '../../../global'; import { getActions } from '../../../global';
import type { IAnchorPosition, ISettings } from '../../../types'; import type { IAnchorPosition, ISettings } from '../../../types';
import type { ApiAttachMenuBot } from '../../../api/types'; import type { ApiAttachBot } from '../../../api/types';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -13,24 +13,24 @@ import useLang from '../../../hooks/useLang';
import Portal from '../../ui/Portal'; import Portal from '../../ui/Portal';
import Menu from '../../ui/Menu'; import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem'; import MenuItem from '../../ui/MenuItem';
import AttachmentMenuBotIcon from './AttachmentMenuBotIcon'; import AttachBotIcon from './AttachBotIcon';
type OwnProps = { type OwnProps = {
bot: ApiAttachMenuBot; bot: ApiAttachBot;
theme: ISettings['theme']; theme: ISettings['theme'];
chatId: string; chatId: string;
onMenuOpened: VoidFunction; onMenuOpened: VoidFunction;
onMenuClosed: VoidFunction; onMenuClosed: VoidFunction;
}; };
const AttachmentMenuBotItem: FC<OwnProps> = ({ const AttachBotItem: FC<OwnProps> = ({
bot, bot,
theme, theme,
chatId, chatId,
onMenuOpened, onMenuOpened,
onMenuClosed, onMenuClosed,
}) => { }) => {
const { callAttachMenuBot, toggleBotInAttachMenu } = getActions(); const { callAttachBot, toggleAttachBot } = getActions();
const lang = useLang(); const lang = useLang();
@ -59,19 +59,19 @@ const AttachmentMenuBotItem: FC<OwnProps> = ({
}, []); }, []);
const handleRemoveBot = useCallback(() => { const handleRemoveBot = useCallback(() => {
toggleBotInAttachMenu({ toggleAttachBot({
botId: bot.id, botId: bot.id,
isEnabled: false, isEnabled: false,
}); });
}, [bot.id, toggleBotInAttachMenu]); }, [bot.id, toggleAttachBot]);
return ( return (
<MenuItem <MenuItem
key={bot.id} key={bot.id}
customIcon={icon && <AttachmentMenuBotIcon icon={icon} theme={theme} />} customIcon={icon && <AttachBotIcon icon={icon} theme={theme} />}
icon={!icon ? 'bots' : undefined} icon={!icon ? 'bots' : undefined}
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => callAttachMenuBot({ onClick={() => callAttachBot({
botId: bot.id, botId: bot.id,
chatId, chatId,
})} })}
@ -98,4 +98,4 @@ const AttachmentMenuBotItem: FC<OwnProps> = ({
); );
}; };
export default memo(AttachmentMenuBotItem); export default memo(AttachBotItem);

View File

@ -18,7 +18,7 @@ import useFlag from '../../../hooks/useFlag';
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton'; import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
import Menu from '../../ui/Menu'; import Menu from '../../ui/Menu';
import MenuItem from '../../ui/MenuItem'; import MenuItem from '../../ui/MenuItem';
import AttachmentMenuBotItem from './AttachmentMenuBotItem'; import AttachBotItem from './AttachBotItem';
import './AttachMenu.scss'; import './AttachMenu.scss';
@ -28,7 +28,7 @@ export type OwnProps = {
canAttachMedia: boolean; canAttachMedia: boolean;
canAttachPolls: boolean; canAttachPolls: boolean;
isScheduled?: boolean; isScheduled?: boolean;
attachMenuBots: GlobalState['attachMenu']['bots']; attachBots: GlobalState['attachMenu']['bots'];
peerType?: ApiAttachMenuPeerType; peerType?: ApiAttachMenuPeerType;
onFileSelect: (files: File[], isQuick: boolean) => void; onFileSelect: (files: File[], isQuick: boolean) => void;
onPollCreate: () => void; onPollCreate: () => void;
@ -40,7 +40,7 @@ const AttachMenu: FC<OwnProps> = ({
isButtonVisible, isButtonVisible,
canAttachMedia, canAttachMedia,
canAttachPolls, canAttachPolls,
attachMenuBots, attachBots,
peerType, peerType,
isScheduled, isScheduled,
onFileSelect, onFileSelect,
@ -85,14 +85,14 @@ const AttachMenu: FC<OwnProps> = ({
}, [handleFileSelect]); }, [handleFileSelect]);
const bots = useMemo(() => { const bots = useMemo(() => {
return Object.values(attachMenuBots).filter((bot) => { return Object.values(attachBots).filter((bot) => {
if (!peerType) return false; if (!peerType) return false;
if (peerType === 'bot' && bot.id === chatId && bot.peerTypes.includes('self')) { if (peerType === 'bots' && bot.id === chatId && bot.peerTypes.includes('self')) {
return true; return true;
} }
return bot.peerTypes.includes(peerType); return bot.peerTypes.includes(peerType);
}); });
}, [attachMenuBots, chatId, peerType]); }, [attachBots, chatId, peerType]);
const lang = useLang(); const lang = useLang();
@ -146,7 +146,7 @@ const AttachMenu: FC<OwnProps> = ({
)} )}
{canAttachMedia && !isScheduled && bots.map((bot) => ( {canAttachMedia && !isScheduled && bots.map((bot) => (
<AttachmentMenuBotItem <AttachBotItem
bot={bot} bot={bot}
chatId={chatId} chatId={chatId}
theme={theme} theme={theme}

View File

@ -54,7 +54,7 @@ import {
selectTheme, selectTheme,
selectCurrentMessageList, selectCurrentMessageList,
selectIsCurrentUserPremium, selectIsCurrentUserPremium,
selectAttachMenuPeerType, selectChatType,
} from '../../../global/selectors'; } from '../../../global/selectors';
import { import {
getAllowedAttachmentOptions, getAllowedAttachmentOptions,
@ -169,7 +169,7 @@ type StateProps =
sendAsId?: string; sendAsId?: string;
editingDraft?: ApiFormattedText; editingDraft?: ApiFormattedText;
requestedText?: string; requestedText?: string;
attachMenuBots: GlobalState['attachMenu']['bots']; attachBots: GlobalState['attachMenu']['bots'];
attachMenuPeerType?: ApiAttachMenuPeerType; attachMenuPeerType?: ApiAttachMenuPeerType;
theme: ISettings['theme']; theme: ISettings['theme'];
fileSizeLimit: number; fileSizeLimit: number;
@ -249,7 +249,7 @@ const Composer: FC<OwnProps & StateProps> = ({
editingDraft, editingDraft,
requestedText, requestedText,
botMenuButton, botMenuButton,
attachMenuBots, attachBots,
attachMenuPeerType, attachMenuPeerType,
theme, theme,
}) => { }) => {
@ -268,8 +268,8 @@ const Composer: FC<OwnProps & StateProps> = ({
sendInlineBotResult, sendInlineBotResult,
loadSendAs, loadSendAs,
loadFullChat, loadFullChat,
resetOpenChatWithText, resetOpenChatWithDraft,
callAttachMenuBot, callAttachBot,
openLimitReachedModal, openLimitReachedModal,
showNotification, showNotification,
} = getActions(); } = getActions();
@ -674,10 +674,10 @@ const Composer: FC<OwnProps & StateProps> = ({
const handleClickBotMenu = useCallback(() => { const handleClickBotMenu = useCallback(() => {
if (botMenuButton?.type !== 'webApp') return; if (botMenuButton?.type !== 'webApp') return;
callAttachMenuBot({ callAttachBot({
botId: chatId, chatId, isFromBotMenu: true, url: botMenuButton.url, botId: chatId, chatId, isFromBotMenu: true, url: botMenuButton.url,
}); });
}, [botMenuButton, callAttachMenuBot, chatId]); }, [botMenuButton, callAttachBot, chatId]);
const handleActivateBotCommandMenu = useCallback(() => { const handleActivateBotCommandMenu = useCallback(() => {
closeSymbolMenu(); closeSymbolMenu();
@ -727,13 +727,13 @@ const Composer: FC<OwnProps & StateProps> = ({
useEffect(() => { useEffect(() => {
if (requestedText) { if (requestedText) {
setHtml(requestedText); setHtml(requestedText);
resetOpenChatWithText(); resetOpenChatWithDraft();
requestAnimationFrame(() => { requestAnimationFrame(() => {
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!; const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;
focusEditableElement(messageInput, true); focusEditableElement(messageInput, true);
}); });
} }
}, [requestedText, resetOpenChatWithText]); }, [requestedText, resetOpenChatWithDraft]);
const handleStickerSelect = useCallback(( const handleStickerSelect = useCallback((
sticker: ApiSticker, isSilent?: boolean, isScheduleRequested?: boolean, shouldPreserveInput = false, sticker: ApiSticker, isSilent?: boolean, isScheduleRequested?: boolean, shouldPreserveInput = false,
@ -1211,7 +1211,7 @@ const Composer: FC<OwnProps & StateProps> = ({
onFileSelect={handleFileSelect} onFileSelect={handleFileSelect}
onPollCreate={openPollModal} onPollCreate={openPollModal}
isScheduled={shouldSchedule} isScheduled={shouldSchedule}
attachMenuBots={attachMenuBots} attachBots={attachBots}
peerType={attachMenuPeerType} peerType={attachMenuPeerType}
theme={theme} theme={theme}
/> />
@ -1377,8 +1377,8 @@ export default memo(withGlobal<OwnProps>(
sendAsId, sendAsId,
editingDraft, editingDraft,
requestedText, requestedText,
attachMenuBots: global.attachMenu.bots, attachBots: global.attachMenu.bots,
attachMenuPeerType: selectAttachMenuPeerType(global, chatId), attachMenuPeerType: selectChatType(global, chatId),
theme: selectTheme(global), theme: selectTheme(global),
fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE, fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE,
captionLimit: selectCurrentLimit(global, 'captionLength'), captionLimit: selectCurrentLimit(global, 'captionLength'),

View File

@ -54,10 +54,12 @@ export default function useInlineBotTooltip(
}, [query, isAllowed, queryInlineBot, chatId, usernameLowered]); }, [query, isAllowed, queryInlineBot, chatId, usernameLowered]);
const loadMore = useCallback(() => { const loadMore = useCallback(() => {
queryInlineBot({ if (isAllowed && usernameLowered && chatId) {
chatId, username: usernameLowered, query, offset, queryInlineBot({
}); chatId, username: usernameLowered, query, offset,
}, [offset, chatId, query, queryInlineBot, usernameLowered]); });
}
}, [isAllowed, usernameLowered, chatId, queryInlineBot, query, offset]);
useEffect(() => { useEffect(() => {
if (isAllowed && botId && (switchPm || (results?.length))) { if (isAllowed && botId && (switchPm || (results?.length))) {

View File

@ -1,6 +1,6 @@
import type { FC } from '../../../lib/teact/teact'; import type { FC } from '../../../lib/teact/teact';
import React, { import React, {
useMemo, useState, memo, useRef, useCallback, useMemo, useState, memo, useCallback,
} from '../../../lib/teact/teact'; } from '../../../lib/teact/teact';
import { getActions, getGlobal, withGlobal } from '../../../global'; import { getActions, getGlobal, withGlobal } from '../../../global';
@ -33,9 +33,7 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
} = getActions(); } = getActions();
const lang = useLang(); const lang = useLang();
const [filter, setFilter] = useState(''); const [search, setSearch] = useState('');
// eslint-disable-next-line no-null/no-null
const filterRef = useRef<HTMLInputElement>(null);
const usersId = useMemo(() => { const usersId = useMemo(() => {
const availableMemberIds = (chat.fullInfo?.members || []) const availableMemberIds = (chat.fullInfo?.members || [])
@ -49,8 +47,8 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
// No need for expensive global updates on users, so we avoid them // No need for expensive global updates on users, so we avoid them
const usersById = getGlobal().users.byId; const usersById = getGlobal().users.byId;
return filterUsersByName(availableMemberIds, usersById, filter); return filterUsersByName(availableMemberIds, usersById, search);
}, [chat.fullInfo?.members, currentUserId, filter]); }, [chat.fullInfo?.members, currentUserId, search]);
const handleRemoveUser = useCallback((userId: string) => { const handleRemoveUser = useCallback((userId: string) => {
deleteChatMember({ chatId: chat.id, userId }); deleteChatMember({ chatId: chat.id, userId });
@ -61,10 +59,9 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
<ChatOrUserPicker <ChatOrUserPicker
isOpen={isOpen} isOpen={isOpen}
chatOrUserIds={usersId} chatOrUserIds={usersId}
filterRef={filterRef} searchPlaceholder={lang('ChannelBlockUser')}
filterPlaceholder={lang('ChannelBlockUser')} search={search}
filter={filter} onSearchChange={setSearch}
onFilterChange={setFilter}
loadMore={loadMoreMembers} loadMore={loadMoreMembers}
onSelectChatOrUser={handleRemoveUser} onSelectChatOrUser={handleRemoveUser}
onClose={onClose} onClose={onClose}

View File

@ -193,7 +193,7 @@ export const CONTENT_NOT_SUPPORTED = 'The message is not supported on this versi
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
export const RE_LINK_TEMPLATE = '((ftp|https?):\\/\\/)?((www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,63})\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)'; export const RE_LINK_TEMPLATE = '((ftp|https?):\\/\\/)?((www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,63})\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)';
export const RE_MENTION_TEMPLATE = '(@[\\w\\d_-]+)'; export const RE_MENTION_TEMPLATE = '(@[\\w\\d_-]+)';
export const RE_TG_LINK = /^tg:(\/\/)?([?=&\d\w_-]+)?/; export const RE_TG_LINK = /^tg:(\/\/)?/;
export const RE_TME_LINK = /^(https?:\/\/)?([-a-zA-Z0-9@:%_+~#=]{1,32}\.)?t\.me/; export const RE_TME_LINK = /^(https?:\/\/)?([-a-zA-Z0-9@:%_+~#=]{1,32}\.)?t\.me/;
export const RE_TELEGRAM_LINK = /^(https?:\/\/)?telegram\.org\//; export const RE_TELEGRAM_LINK = /^(https?:\/\/)?telegram\.org\//;
export const TME_LINK_PREFIX = 'https://t.me/'; export const TME_LINK_PREFIX = 'https://t.me/';
@ -201,6 +201,8 @@ export const TME_LINK_PREFIX = 'https://t.me/';
// eslint-disable-next-line max-len // eslint-disable-next-line 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']); 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']);
export const API_CHAT_TYPES = ['bots', 'channels', 'chats', 'users'] as const;
// MTProto constants // MTProto constants
export const SERVICE_NOTIFICATIONS_USER_ID = '777000'; export const SERVICE_NOTIFICATIONS_USER_ID = '777000';
export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must be equal to 708513 export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must be equal to 708513

View File

@ -3,7 +3,7 @@ import {
} from '../../index'; } from '../../index';
import type { import type {
ApiChat, ApiContact, ApiUrlAuthResult, ApiUser, ApiChat, ApiChatType, ApiContact, ApiUrlAuthResult, ApiUser,
} from '../../../api/types'; } from '../../../api/types';
import type { InlineBotSettings } from '../../../types'; import type { InlineBotSettings } from '../../../types';
@ -287,27 +287,11 @@ addActionHandler('switchBotInline', (global, actions, payload) => {
return undefined; return undefined;
} }
const text = `@${botSender.username} ${query}`; actions.openChatWithDraft({
text: `@${botSender.username} ${query}`,
if (isSamePeer) { chatId: isSamePeer ? chat.id : undefined,
actions.openChatWithText({ chatId: chat.id, text }); });
return undefined; return undefined;
}
return {
...global,
switchBotInline: {
query,
botUsername: botSender.username,
},
};
});
addActionHandler('resetSwitchBotInline', (global) => {
return {
...global,
switchBotInline: undefined,
};
}); });
addActionHandler('sendInlineBotResult', (global, actions, payload) => { addActionHandler('sendInlineBotResult', (global, actions, payload) => {
@ -557,28 +541,28 @@ addActionHandler('markBotTrusted', (global, actions, payload) => {
} }
}); });
addActionHandler('loadAttachMenuBots', async (global, actions, payload) => { addActionHandler('loadAttachBots', async (global, actions, payload) => {
const { hash } = payload || {}; const { hash } = payload || {};
await loadAttachMenuBots(hash); await loadAttachBots(hash);
}); });
addActionHandler('toggleBotInAttachMenu', async (global, actions, payload) => { addActionHandler('toggleAttachBot', async (global, actions, payload) => {
const { botId, isEnabled } = payload; const { botId, isEnabled } = payload;
const bot = selectUser(global, botId); const bot = selectUser(global, botId);
if (!bot) return; if (!bot) return;
await toggleBotInAttachMenu(bot, isEnabled); await toggleAttachBot(bot, isEnabled);
}); });
async function toggleBotInAttachMenu(bot: ApiUser, isEnabled: boolean) { async function toggleAttachBot(bot: ApiUser, isEnabled: boolean) {
await callApi('toggleBotInAttachMenu', { bot, isEnabled }); await callApi('toggleAttachBot', { bot, isEnabled });
await loadAttachMenuBots(); await loadAttachBots();
} }
async function loadAttachMenuBots(hash?: string) { async function loadAttachBots(hash?: string) {
const result = await callApi('loadAttachMenuBots', { hash }); const result = await callApi('loadAttachBots', { hash });
if (!result) { if (!result) {
return; return;
} }
@ -593,7 +577,7 @@ async function loadAttachMenuBots(hash?: string) {
}); });
} }
addActionHandler('callAttachMenuBot', (global, actions, payload) => { addActionHandler('callAttachBot', (global, actions, payload) => {
const { const {
chatId, botId, isFromBotMenu, url, startParam, chatId, botId, isFromBotMenu, url, startParam,
} = payload; } = payload;
@ -601,14 +585,17 @@ addActionHandler('callAttachMenuBot', (global, actions, payload) => {
if (!isFromBotMenu && !bots[botId]) { if (!isFromBotMenu && !bots[botId]) {
return { return {
...global, ...global,
botAttachRequest: { requestedAttachBotInstall: {
botId, botId,
chatId, onConfirm: {
startParam, action: 'callAttachBot',
payload: { chatId, botId, startParam },
},
}, },
}; };
} }
const theme = extractCurrentThemeParams(); const theme = extractCurrentThemeParams();
actions.openChat({ id: chatId });
actions.requestWebView({ actions.requestWebView({
url, url,
peerId: chatId, peerId: chatId,
@ -622,29 +609,67 @@ addActionHandler('callAttachMenuBot', (global, actions, payload) => {
return undefined; return undefined;
}); });
addActionHandler('confirmBotAttachRequest', async (global, actions) => { addActionHandler('confirmAttachBotInstall', async (global) => {
const { botAttachRequest } = global; const { requestedAttachBotInstall } = global;
if (!botAttachRequest) return;
const { botId, chatId, startParam } = botAttachRequest; const { botId, onConfirm } = requestedAttachBotInstall!;
setGlobal({ setGlobal({
...global, ...global,
botAttachRequest: undefined, requestedAttachBotInstall: undefined,
}); });
const bot = selectUser(global, botId); const bot = selectUser(global, botId);
if (!bot) return; if (!bot) return;
await toggleBotInAttachMenu(bot, true); await toggleAttachBot(bot, true);
if (onConfirm) {
actions.callAttachMenuBot({ chatId, botId, startParam }); const { action, payload } = onConfirm;
getActions()[action](payload);
}
}); });
addActionHandler('closeBotAttachRequestModal', (global) => { addActionHandler('cancelAttachBotInstall', (global) => {
return { return {
...global, ...global,
botAttachRequest: undefined, requestedAttachBotInstall: undefined,
};
});
addActionHandler('requestAttachBotInChat', (global, actions, payload) => {
const { botId, filter, startParam } = payload;
const currentChatId = selectCurrentMessageList(global)?.chatId;
const { attachMenu: { bots } } = global;
const bot = bots[botId];
if (!bot) return;
const supportedFilters = bot.peerTypes.filter((type): type is ApiChatType => (
type !== 'self' && filter.includes(type)
));
if (!supportedFilters.length) {
actions.callAttachBot({
chatId: currentChatId || botId,
botId,
startParam,
});
return;
}
setGlobal({
...global,
requestedAttachBotInChat: {
botId,
filter: supportedFilters,
startParam,
},
});
});
addActionHandler('cancelAttachBotInChat', (global) => {
return {
...global,
requestedAttachBotInChat: undefined,
}; };
}); });

View File

@ -7,7 +7,7 @@ import type {
} from '../../../api/types'; } from '../../../api/types';
import { MAIN_THREAD_ID } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types';
import { NewChatMembersProgress, ChatCreationProgress, ManagementProgress } from '../../../types'; import { NewChatMembersProgress, ChatCreationProgress, ManagementProgress } from '../../../types';
import type { GlobalActions } from '../../types'; import type { GlobalActions, GlobalState } from '../../types';
import { import {
ARCHIVED_FOLDER_ID, ARCHIVED_FOLDER_ID,
@ -35,9 +35,14 @@ import {
import { buildCollectionByKey, omit } from '../../../util/iteratees'; import { buildCollectionByKey, omit } from '../../../util/iteratees';
import { debounce, pause, throttle } from '../../../util/schedulers'; import { debounce, pause, throttle } from '../../../util/schedulers';
import { import {
isChatSummaryOnly, isChatArchived, isChatBasicGroup, isUserBot, isChatChannel, isChatSuperGroup, isChatSummaryOnly,
isChatArchived,
isChatBasicGroup,
isChatChannel,
isChatSuperGroup,
isUserBot,
} from '../../helpers'; } from '../../helpers';
import { processDeepLink } from '../../../util/deeplink'; import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
import { updateGroupCall } from '../../reducers/calls'; import { updateGroupCall } from '../../reducers/calls';
import { selectGroupCall } from '../../selectors/calls'; import { selectGroupCall } from '../../selectors/calls';
import { getOrderedIds } from '../../../util/folderManager'; import { getOrderedIds } from '../../../util/folderManager';
@ -586,10 +591,21 @@ addActionHandler('openChatByPhoneNumber', async (global, actions, payload) => {
addActionHandler('openTelegramLink', (global, actions, payload) => { addActionHandler('openTelegramLink', (global, actions, payload) => {
const { url } = payload!; const { url } = payload!;
const {
openChatByPhoneNumber,
openChatByInvite,
openStickerSet,
openChatWithDraft,
joinVoiceChatByLink,
showNotification,
focusMessage,
openInvoice,
processAttachBotParameters,
openChatByUsername: openChatByUsernameAction,
} = actions;
const tgLinkMatch = url.match(RE_TG_LINK); if (url.match(RE_TG_LINK)) {
if (tgLinkMatch) { processDeepLink(url);
processDeepLink(tgLinkMatch[0]);
return; return;
} }
@ -611,9 +627,10 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
} }
const startAttach = params.hasOwnProperty('startattach') && !params.startattach ? true : params.startattach; const startAttach = params.hasOwnProperty('startattach') && !params.startattach ? true : params.startattach;
const choose = parseChooseParameter(params.choose);
if (part1.match(/^\+([0-9]+)(\?|$)/)) { if (part1.match(/^\+([0-9]+)(\?|$)/)) {
actions.openChatByPhoneNumber({ openChatByPhoneNumber({
phoneNumber: part1.substr(1, part1.length - 1), phoneNumber: part1.substr(1, part1.length - 1),
startAttach, startAttach,
attach: params.attach, attach: params.attach,
@ -626,12 +643,12 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
} }
if (hash) { if (hash) {
actions.openChatByInvite({ hash }); openChatByInvite({ hash });
return; return;
} }
if (part1 === 'addstickers' || part1 === 'addemoji') { if (part1 === 'addstickers' || part1 === 'addemoji') {
actions.openStickerSet({ openStickerSet({
stickerSetInfo: { stickerSetInfo: {
shortName: part2, shortName: part2,
}, },
@ -643,8 +660,11 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
const messageId = part3 ? Number(part3) : undefined; const messageId = part3 ? Number(part3) : undefined;
const commentId = params.comment ? Number(params.comment) : undefined; const commentId = params.comment ? Number(params.comment) : undefined;
if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) { if (part1 === 'share') {
actions.joinVoiceChatByLink({ const text = formatShareText(params.url, params.text);
openChatWithDraft({ text });
} else if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
joinVoiceChatByLink({
username: part1, username: part1,
inviteHash: params.voicechat || params.livestream, inviteHash: params.voicechat || params.livestream,
}); });
@ -652,24 +672,30 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
const chatId = `-${chatOrChannelPostId}`; const chatId = `-${chatOrChannelPostId}`;
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) { if (!chat) {
actions.showNotification({ message: 'Chat does not exist' }); showNotification({ message: 'Chat does not exist' });
return; return;
} }
actions.focusMessage({ focusMessage({
chatId, chatId,
messageId, messageId,
}); });
} else if (part1.startsWith('$')) { } else if (part1.startsWith('$')) {
actions.openInvoice({ openInvoice({
slug: part1.substring(1), slug: part1.substring(1),
}); });
} else if (part1 === 'invoice') { } else if (part1 === 'invoice') {
actions.openInvoice({ openInvoice({
slug: part2, slug: part2,
}); });
} else if (startAttach && choose) {
processAttachBotParameters({
username: part1,
filter: choose,
...(typeof startAttach === 'string' && { startParam: startAttach }),
});
} else { } else {
actions.openChatByUsername({ openChatByUsernameAction({
username: part1, username: part1,
messageId: messageId || Number(chatOrChannelPostId), messageId: messageId || Number(chatOrChannelPostId),
commentId, commentId,
@ -1002,10 +1028,10 @@ addActionHandler('setActiveChatFolder', (global, actions, payload) => {
}; };
}); });
addActionHandler('resetOpenChatWithText', (global) => { addActionHandler('resetOpenChatWithDraft', (global) => {
return { return {
...global, ...global,
openChatWithText: undefined, requestedDraft: undefined,
}; };
}); });
@ -1115,6 +1141,38 @@ addActionHandler('toggleJoinRequest', async (global, actions, payload) => {
await callApi('toggleJoinRequest', chat, isEnabled); await callApi('toggleJoinRequest', chat, isEnabled);
}); });
addActionHandler('processAttachBotParameters', async (global, actions, payload) => {
const { username, filter, startParam } = payload;
const bot = await getAttachBotOrNotify(global, username);
if (!bot) return;
global = getGlobal();
const { attachMenu: { bots } } = global;
if (!bots[bot.id]) {
setGlobal({
...global,
requestedAttachBotInstall: {
botId: bot.id,
onConfirm: {
action: 'requestAttachBotInChat',
payload: {
botId: bot.id,
filter,
startParam,
},
},
},
});
return;
}
getActions().requestAttachBotInChat({
botId: bot.id,
filter,
startParam,
});
});
async function loadChats( async function loadChats(
listType: 'active' | 'archived', offsetId?: string, offsetDate?: number, shouldReplace = false, listType: 'active' | 'archived', offsetId?: string, offsetDate?: number, shouldReplace = false,
) { ) {
@ -1504,6 +1562,22 @@ export async function fetchChatByPhoneNumber(phoneNumber: string) {
return chat; return chat;
} }
async function getAttachBotOrNotify(global: GlobalState, username: string) {
const chat = await fetchChatByUsername(username);
if (!chat) return undefined;
const user = selectUser(global, chat.id);
if (!user) return undefined;
const isBot = isUserBot(user);
if (!isBot || !user.isAttachBot) {
getActions().showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
return undefined;
}
return user;
}
async function openChatByUsername( async function openChatByUsername(
actions: GlobalActions, actions: GlobalActions,
username: string, username: string,
@ -1512,29 +1586,16 @@ async function openChatByUsername(
startAttach?: string | boolean, startAttach?: string | boolean,
attach?: string, attach?: string,
) { ) {
let global = getGlobal(); const global = getGlobal();
const currentChat = selectCurrentChat(global); const currentChat = selectCurrentChat(global);
// Attach in the current chat // Attach in the current chat
if (startAttach && !attach) { if (startAttach && !attach) {
const chat = await fetchChatByUsername(username); const user = await getAttachBotOrNotify(global, username);
if (!chat) return;
global = getGlobal(); if (!currentChat || !user) return;
const user = selectUser(global, chat.id); actions.callAttachBot({
if (!user) return;
const isBot = isUserBot(user);
if (!isBot || !user.isAttachMenuBot) {
actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
return;
}
if (!currentChat) return;
actions.callAttachMenuBot({
botId: user.id, botId: user.id,
chatId: currentChat.id, chatId: currentChat.id,
...(typeof startAttach === 'string' && { startParam: startAttach }), ...(typeof startAttach === 'string' && { startParam: startAttach }),
@ -1582,12 +1643,12 @@ async function openAttachMenuFromLink(
const botChat = await fetchChatByUsername(attach); const botChat = await fetchChatByUsername(attach);
if (!botChat) return; if (!botChat) return;
const botUser = selectUser(getGlobal(), botChat.id); const botUser = selectUser(getGlobal(), botChat.id);
if (!botUser || !botUser.isAttachMenuBot) { if (!botUser || !botUser.isAttachBot) {
actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') }); actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
return; return;
} }
actions.callAttachMenuBot({ actions.callAttachBot({
botId: botUser.id, botId: botUser.id,
chatId, chatId,
...(typeof startAttach === 'string' && { startParam: startAttach }), ...(typeof startAttach === 'string' && { startParam: startAttach }),

View File

@ -70,14 +70,16 @@ addActionHandler('openChatWithInfo', (global, actions, payload) => {
actions.openChat(payload); actions.openChat(payload);
}); });
addActionHandler('openChatWithText', (global, actions, payload) => { addActionHandler('openChatWithDraft', (global, actions, payload) => {
const { chatId, text } = payload; const { chatId, text } = payload;
actions.openChat({ id: chatId }); if (chatId) {
actions.openChat({ id: chatId });
}
return { return {
...global, ...global,
openChatWithText: { requestedDraft: {
chatId, chatId,
text, text,
}, },

View File

@ -1,4 +1,4 @@
import type { ApiAttachMenuPeerType, ApiChat } from '../../api/types'; import type { ApiChatType, ApiChat } from '../../api/types';
import { MAIN_THREAD_ID } from '../../api/types'; import { MAIN_THREAD_ID } from '../../api/types';
import type { GlobalState } from '../types'; import type { GlobalState } from '../types';
@ -74,25 +74,25 @@ export function selectIsTrustedBot(global: GlobalState, botId: string) {
return bot && (bot.isVerified || global.trustedBotIds.includes(botId)); return bot && (bot.isVerified || global.trustedBotIds.includes(botId));
} }
export function selectAttachMenuPeerType(global: GlobalState, chatId: string) : ApiAttachMenuPeerType | undefined { export function selectChatType(global: GlobalState, chatId: string) : ApiChatType | undefined {
const chat = selectChat(global, chatId); const chat = selectChat(global, chatId);
if (!chat) return undefined; if (!chat) return undefined;
const bot = selectBot(global, chatId); const bot = selectBot(global, chatId);
if (bot) { if (bot) {
return 'bot'; return 'bots';
} }
const user = selectChatUser(global, chat); const user = selectChatUser(global, chat);
if (user) { if (user) {
return 'private'; return 'users';
} }
if (isChatChannel(chat)) { if (isChatChannel(chat)) {
return 'channel'; return 'channels';
} }
return 'chat'; return 'chats';
} }
export function selectIsChatBotNotStarted(global: GlobalState, chatId: string) { export function selectIsChatBotNotStarted(global: GlobalState, chatId: string) {
@ -194,8 +194,18 @@ export function selectSendAs(global: GlobalState, chatId: string) {
} }
export function selectRequestedText(global: GlobalState, chatId: string) { export function selectRequestedText(global: GlobalState, chatId: string) {
if (global.openChatWithText?.chatId === chatId) { if (global.requestedDraft?.chatId === chatId) {
return global.openChatWithText.text; return global.requestedDraft.text;
} }
return undefined; return undefined;
} }
export function filterChatIdsByType(global: GlobalState, chatIds: string[], filter: readonly ApiChatType[]) {
return chatIds.filter((id) => {
const type = selectChatType(global, id);
if (!type) {
return false;
}
return filter.includes(type);
});
}

View File

@ -673,8 +673,8 @@ export function selectIsPollResultsOpen(global: GlobalState) {
} }
export function selectIsForwardModalOpen(global: GlobalState) { export function selectIsForwardModalOpen(global: GlobalState) {
const { forwardMessages, switchBotInline } = global; const { forwardMessages } = global;
return Boolean(switchBotInline || forwardMessages.isModalShown); return Boolean(forwardMessages.isModalShown);
} }
export function selectCommonBoxChatId(global: GlobalState, messageId: number) { export function selectCommonBoxChatId(global: GlobalState, messageId: number) {

View File

@ -35,7 +35,7 @@ import type {
ApiPhoto, ApiPhoto,
ApiKeyboardButton, ApiKeyboardButton,
ApiThemeParameters, ApiThemeParameters,
ApiAttachMenuBot, ApiAttachBot,
ApiPhoneCall, ApiPhoneCall,
ApiWebSession, ApiWebSession,
ApiPremiumPromo, ApiPremiumPromo,
@ -43,6 +43,7 @@ import type {
ApiInputInvoice, ApiInputInvoice,
ApiInvoice, ApiInvoice,
ApiStickerSetInfo, ApiStickerSetInfo,
ApiChatType,
} from '../api/types'; } from '../api/types';
import type { import type {
FocusDirection, FocusDirection,
@ -585,13 +586,8 @@ export type GlobalState = {
messageId: number; messageId: number;
}; };
switchBotInline?: { requestedDraft?: {
query: string; chatId?: string;
botUsername: string;
};
openChatWithText?: {
chatId: string;
text: string; text: string;
}; };
@ -614,18 +610,25 @@ export type GlobalState = {
type: 'game' | 'webApp'; type: 'game' | 'webApp';
onConfirm?: { onConfirm?: {
action: keyof GlobalActions; action: keyof GlobalActions;
payload: any; // TODO add TS support payload: any; // TODO Add TS support
}; };
}; };
botAttachRequest?: { requestedAttachBotInstall?: {
botId: string; botId: string;
chatId: string; onConfirm?: {
action: keyof GlobalActions;
payload: any; // TODO Add TS support
};
};
requestedAttachBotInChat?: {
botId: string;
filter: ApiChatType[];
startParam?: string; startParam?: string;
}; };
attachMenu: { attachMenu: {
hash?: string; hash?: string;
bots: Record<string, ApiAttachMenuBot>; bots: Record<string, ApiAttachBot>;
}; };
confetti?: { confetti?: {
@ -722,11 +725,11 @@ export interface ActionPayloads {
shouldReplaceHistory?: boolean; shouldReplaceHistory?: boolean;
}; };
openChatWithText: { openChatWithDraft: {
chatId: string; chatId?: string;
text: string; text: string;
}; };
resetOpenChatWithText: never; resetOpenChatWithDraft: never;
toggleJoinToSend: { toggleJoinToSend: {
chatId: string; chatId: string;
@ -956,8 +959,6 @@ export interface ActionPayloads {
isSamePeer?: boolean; isSamePeer?: boolean;
}; };
resetSwitchBotInline: never;
openGame: { openGame: {
url: string; url: string;
chatId: string; chatId: string;
@ -998,8 +999,20 @@ export interface ActionPayloads {
botId: string; botId: string;
}; };
closeBotAttachRequestModal: never; cancelAttachBotInstall: never;
confirmBotAttachRequest: never; confirmAttachBotInstall: never;
processAttachBotParameters: {
username: string;
filter: ApiChatType[];
startParam?: string;
};
requestAttachBotInChat: {
botId: string;
filter: ApiChatType[];
startParam?: string;
};
cancelAttachBotInChat: never;
sendWebViewData: { sendWebViewData: {
bot: ApiUser; bot: ApiUser;
@ -1007,16 +1020,16 @@ export interface ActionPayloads {
buttonText: string; buttonText: string;
}; };
loadAttachMenuBots: { loadAttachBots: {
hash?: string; hash?: string;
}; };
toggleBotInAttachMenu: { toggleAttachBot: {
botId: string; botId: string;
isEnabled: boolean; isEnabled: boolean;
}; };
callAttachMenuBot: { callAttachBot: {
chatId: string; chatId: string;
botId: string; botId: string;
isFromBotMenu?: boolean; isFromBotMenu?: boolean;

View File

@ -1,4 +1,8 @@
import { getActions } from '../global'; import { getActions } from '../global';
import type { ApiChatType } from '../api/types';
import { API_CHAT_TYPES } from '../config';
import { IS_SAFARI } from './environment'; import { IS_SAFARI } from './environment';
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' | type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
@ -20,14 +24,13 @@ export const processDeepLink = (url: string) => {
focusMessage, focusMessage,
joinVoiceChatByLink, joinVoiceChatByLink,
openInvoice, openInvoice,
processAttachBotParameters,
openChatWithDraft,
} = getActions(); } = getActions();
// Safari thinks the path in tg://path links is hostname for some reason // Safari thinks the path in tg://path links is hostname for some reason
const method = (IS_SAFARI ? hostname : pathname).replace(/^\/\//, '') as DeepLinkMethod; const method = (IS_SAFARI ? hostname : pathname).replace(/^\/\//, '') as DeepLinkMethod;
const params: Record<string, string> = {}; const params = Object.fromEntries(searchParams);
searchParams.forEach((value, key) => {
params[key] = value;
});
switch (method) { switch (method) {
case 'resolve': { case 'resolve': {
@ -36,9 +39,16 @@ export const processDeepLink = (url: string) => {
} = params; } = params;
const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach; const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach;
const choose = parseChooseParameter(params.choose);
if (domain !== 'telegrampassport') { if (domain !== 'telegrampassport') {
if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) { if (startAttach && choose) {
processAttachBotParameters({
username: domain,
filter: choose,
...(typeof startAttach === 'string' && { startParam: startAttach }),
});
} else if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
joinVoiceChatByLink({ joinVoiceChatByLink({
username: domain, username: domain,
inviteHash: voicechat || livestream, inviteHash: voicechat || livestream,
@ -93,8 +103,10 @@ export const processDeepLink = (url: string) => {
break; break;
} }
case 'share': case 'share':
case 'msg': { case 'msg':
// const { url, text } = params; case 'msg_url': {
const { url: urlParam, text } = params;
openChatWithDraft({ text: formatShareText(urlParam, text) });
break; break;
} }
case 'login': { case 'login': {
@ -113,3 +125,13 @@ export const processDeepLink = (url: string) => {
break; break;
} }
}; };
export function parseChooseParameter(choose?: string) {
if (!choose) return undefined;
const types = choose.toLowerCase().split(' ');
return types.filter((type): type is ApiChatType => API_CHAT_TYPES.includes(type as ApiChatType));
}
export function formatShareText(url?: string, text?: string) {
return [url, text].filter(Boolean).join('\n');
}