Support Bot Apps (#3041)
This commit is contained in:
parent
ac28fe0162
commit
920b046d2e
@ -3,11 +3,13 @@ import type {
|
|||||||
ApiAttachBot,
|
ApiAttachBot,
|
||||||
ApiAttachBotIcon,
|
ApiAttachBotIcon,
|
||||||
ApiAttachMenuPeerType,
|
ApiAttachMenuPeerType,
|
||||||
|
ApiBotApp,
|
||||||
ApiBotCommand,
|
ApiBotCommand,
|
||||||
ApiBotInfo,
|
ApiBotInfo,
|
||||||
ApiBotInlineMediaResult,
|
ApiBotInlineMediaResult,
|
||||||
ApiBotInlineResult,
|
ApiBotInlineResult,
|
||||||
ApiBotInlineSwitchPm,
|
ApiBotInlineSwitchPm,
|
||||||
|
ApiBotInlineSwitchWebview,
|
||||||
ApiBotMenuButton,
|
ApiBotMenuButton,
|
||||||
ApiInlineResultType,
|
ApiInlineResultType,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
@ -62,6 +64,10 @@ 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 buildBotSwitchWebview(switchWebview?: GramJs.InlineBotWebView) {
|
||||||
|
return switchWebview ? pick(switchWebview, ['text', 'url']) as ApiBotInlineSwitchWebview : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
|
export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
|
||||||
return {
|
return {
|
||||||
id: bot.botId.toString(),
|
id: bot.botId.toString(),
|
||||||
@ -138,3 +144,26 @@ export function buildApiBotMenuButton(menuButton?: GramJs.TypeBotMenuButton): Ap
|
|||||||
type: 'commands',
|
type: 'commands',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiBotApp(botApp: GramJs.messages.BotApp): ApiBotApp | undefined {
|
||||||
|
const { app, inactive, requestWriteAccess } = botApp;
|
||||||
|
if (app instanceof GramJs.BotAppNotModified) return undefined;
|
||||||
|
const {
|
||||||
|
id, accessHash, title, description, shortName, photo, document,
|
||||||
|
} = app;
|
||||||
|
|
||||||
|
const apiPhoto = photo instanceof GramJs.Photo ? buildApiPhoto(photo) : undefined;
|
||||||
|
const apiDocument = document instanceof GramJs.Document ? buildApiDocument(document) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id.toString(),
|
||||||
|
accessHash: accessHash.toString(),
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
shortName,
|
||||||
|
photo: apiPhoto,
|
||||||
|
document: apiDocument,
|
||||||
|
isInactive: inactive,
|
||||||
|
shouldRequestWriteAccess: requestWriteAccess,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -1145,6 +1145,15 @@ function buildReplyButtons(message: UniversalMessage, shouldSkipBuyButton?: bool
|
|||||||
}]],
|
}]],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (media.webpage.type === 'telegram_botapp') {
|
||||||
|
return {
|
||||||
|
inlineButtons: [[{
|
||||||
|
type: 'url',
|
||||||
|
text: 'Open App',
|
||||||
|
url: media.webpage.url,
|
||||||
|
}]],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import type {
|
|||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiReaction,
|
ApiReaction,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
|
ApiBotApp,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
ApiMessageEntityTypes,
|
ApiMessageEntityTypes,
|
||||||
@ -606,3 +607,10 @@ export function buildInputTextWithEntities(formatted: ApiFormattedText) {
|
|||||||
entities: formatted.entities?.map(buildMtpMessageEntity) || [],
|
entities: formatted.entities?.map(buildMtpMessageEntity) || [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildInputBotApp(app: ApiBotApp) {
|
||||||
|
return new GramJs.InputBotAppID({
|
||||||
|
id: BigInt(app.id),
|
||||||
|
accessHash: BigInt(app.accessHash),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -2,15 +2,24 @@ import BigInt from 'big-integer';
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
ApiBotApp,
|
||||||
ApiChat, ApiThemeParameters, ApiUser, OnApiUpdate,
|
ApiChat, ApiThemeParameters, ApiUser, OnApiUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
|
import { WEB_APP_PLATFORM } from '../../../config';
|
||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
import { buildInputPeer, buildInputThemeParams, generateRandomBigInt } from '../gramjsBuilders';
|
import {
|
||||||
|
buildInputBotApp, buildInputEntity, buildInputPeer, buildInputThemeParams, generateRandomBigInt,
|
||||||
|
} from '../gramjsBuilders';
|
||||||
import { buildApiUser } from '../apiBuilders/users';
|
import { buildApiUser } from '../apiBuilders/users';
|
||||||
import {
|
import {
|
||||||
buildApiAttachBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm,
|
buildApiAttachBot,
|
||||||
|
buildApiBotApp,
|
||||||
|
buildApiBotInlineMediaResult,
|
||||||
|
buildApiBotInlineResult,
|
||||||
|
buildBotSwitchPm,
|
||||||
|
buildBotSwitchWebview,
|
||||||
} 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';
|
||||||
@ -102,6 +111,7 @@ export async function fetchInlineBotResults({
|
|||||||
help: bot.botPlaceholder,
|
help: bot.botPlaceholder,
|
||||||
nextOffset: getInlineBotResultsNextOffset(bot.usernames![0].username, result.nextOffset),
|
nextOffset: getInlineBotResultsNextOffset(bot.usernames![0].username, result.nextOffset),
|
||||||
switchPm: buildBotSwitchPm(result.switchPm),
|
switchPm: buildBotSwitchPm(result.switchPm),
|
||||||
|
switchWebview: buildBotSwitchWebview(result.switchWebview),
|
||||||
users: result.users.map(buildApiUser).filter(Boolean),
|
users: result.users.map(buildApiUser).filter(Boolean),
|
||||||
results: processInlineBotResult(String(result.queryId), result.results),
|
results: processInlineBotResult(String(result.queryId), result.results),
|
||||||
cacheTime: result.cacheTime,
|
cacheTime: result.cacheTime,
|
||||||
@ -184,7 +194,7 @@ export async function requestWebView({
|
|||||||
startParam,
|
startParam,
|
||||||
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
||||||
fromBotMenu: isFromBotMenu || undefined,
|
fromBotMenu: isFromBotMenu || undefined,
|
||||||
platform: 'weba',
|
platform: WEB_APP_PLATFORM,
|
||||||
...(threadId && { topMsgId: threadId }),
|
...(threadId && { topMsgId: threadId }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
}));
|
}));
|
||||||
@ -210,7 +220,53 @@ export async function requestSimpleWebView({
|
|||||||
url,
|
url,
|
||||||
bot: buildInputPeer(bot.id, bot.accessHash),
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
||||||
platform: 'weba',
|
platform: WEB_APP_PLATFORM,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return result?.url;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchBotApp({
|
||||||
|
bot,
|
||||||
|
appName,
|
||||||
|
}: {
|
||||||
|
bot: ApiUser;
|
||||||
|
appName: string;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.GetBotApp({
|
||||||
|
app: new GramJs.InputBotAppShortName({
|
||||||
|
botId: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser,
|
||||||
|
shortName: appName,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result || result instanceof GramJs.BotAppNotModified) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildApiBotApp(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function requestAppWebView({
|
||||||
|
peer,
|
||||||
|
app,
|
||||||
|
startParam,
|
||||||
|
theme,
|
||||||
|
isWriteAllowed,
|
||||||
|
}: {
|
||||||
|
peer: ApiChat | ApiUser;
|
||||||
|
app: ApiBotApp;
|
||||||
|
startParam?: string;
|
||||||
|
theme?: ApiThemeParameters;
|
||||||
|
isWriteAllowed?: boolean;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.RequestAppWebView({
|
||||||
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
|
app: buildInputBotApp(app),
|
||||||
|
startParam,
|
||||||
|
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
||||||
|
platform: WEB_APP_PLATFORM,
|
||||||
|
writeAllowed: isWriteAllowed || undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return result?.url;
|
return result?.url;
|
||||||
|
|||||||
@ -71,8 +71,8 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
||||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot,
|
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
|
||||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot,
|
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
|
||||||
} from './bots';
|
} from './bots';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -43,6 +43,11 @@ export interface ApiBotInlineSwitchPm {
|
|||||||
startParam: string;
|
startParam: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiBotInlineSwitchWebview {
|
||||||
|
text: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ApiBotCommand {
|
export interface ApiBotCommand {
|
||||||
botId: string;
|
botId: string;
|
||||||
command: string;
|
command: string;
|
||||||
|
|||||||
@ -637,6 +637,18 @@ export type ApiThemeParameters = {
|
|||||||
secondary_bg_color: string;
|
secondary_bg_color: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiBotApp = {
|
||||||
|
id: string;
|
||||||
|
accessHash: string;
|
||||||
|
title: string;
|
||||||
|
shortName: string;
|
||||||
|
description: string;
|
||||||
|
photo?: ApiPhoto;
|
||||||
|
document?: ApiDocument;
|
||||||
|
isInactive?: boolean;
|
||||||
|
shouldRequestWriteAccess?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export const MAIN_THREAD_ID = -1;
|
export const MAIN_THREAD_ID = -1;
|
||||||
|
|
||||||
// `Symbol` can not be transferred from worker
|
// `Symbol` can not be transferred from worker
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import React, {
|
||||||
import React, { memo, useCallback } from '../../lib/teact/teact';
|
memo, useCallback, useMemo, useState,
|
||||||
|
} 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 { getUserFullName } from '../../global/helpers';
|
import { getUserFullName } from '../../global/helpers';
|
||||||
@ -10,15 +12,20 @@ import renderText from '../common/helpers/renderText';
|
|||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
|
|
||||||
|
import Checkbox from '../ui/Checkbox';
|
||||||
import ConfirmDialog from '../ui/ConfirmDialog';
|
import ConfirmDialog from '../ui/ConfirmDialog';
|
||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
bot?: ApiUser;
|
bot?: ApiUser;
|
||||||
type?: 'game' | 'webApp';
|
type?: 'game' | 'webApp' | 'botApp';
|
||||||
|
shouldRequestWriteAccess?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const BotTrustModal: FC<OwnProps> = ({ bot, type }) => {
|
const BotTrustModal: FC<OwnProps> = ({ bot, type, shouldRequestWriteAccess }) => {
|
||||||
const { cancelBotTrustRequest, markBotTrusted } = getActions();
|
const { cancelBotTrustRequest, markBotTrusted } = getActions();
|
||||||
|
|
||||||
|
const [isWriteAllowed, setIsWriteAllowed] = useState(shouldRequestWriteAccess || false);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
// Keep props a little bit longer, to show correct text on closing animation
|
// Keep props a little bit longer, to show correct text on closing animation
|
||||||
const previousBot = usePrevious(bot, false);
|
const previousBot = usePrevious(bot, false);
|
||||||
@ -27,21 +34,46 @@ const BotTrustModal: FC<OwnProps> = ({ bot, type }) => {
|
|||||||
const currentType = type || previousType;
|
const currentType = type || previousType;
|
||||||
|
|
||||||
const handleBotTrustAccept = useCallback(() => {
|
const handleBotTrustAccept = useCallback(() => {
|
||||||
markBotTrusted({ botId: bot!.id });
|
markBotTrusted({ botId: bot!.id, isWriteAllowed });
|
||||||
}, [markBotTrusted, bot]);
|
}, [markBotTrusted, isWriteAllowed, bot]);
|
||||||
|
|
||||||
|
const handleBotTrustDecline = useCallback(() => {
|
||||||
|
cancelBotTrustRequest();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const title = currentType === 'game' ? lang('AppName') : lang('BotOpenPageTitle');
|
const title = currentType === 'game' ? lang('AppName') : lang('BotOpenPageTitle');
|
||||||
const text = currentType === 'game' ? lang('BotPermissionGameAlert', getUserFullName(currentBot))
|
const text = useMemo(() => {
|
||||||
: lang('BotOpenPageMessage', getUserFullName(currentBot));
|
switch (currentType) {
|
||||||
|
case 'game':
|
||||||
|
return lang('BotPermissionGameAlert', getUserFullName(currentBot));
|
||||||
|
case 'webApp':
|
||||||
|
return lang('BotOpenPageMessage', getUserFullName(currentBot));
|
||||||
|
case 'botApp':
|
||||||
|
default:
|
||||||
|
return lang('BotWebViewStartPermission');
|
||||||
|
}
|
||||||
|
}, [currentBot, currentType, lang]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={Boolean(bot)}
|
isOpen={Boolean(bot)}
|
||||||
onClose={cancelBotTrustRequest}
|
onClose={handleBotTrustDecline}
|
||||||
confirmHandler={handleBotTrustAccept}
|
|
||||||
title={title}
|
title={title}
|
||||||
textParts={renderText(text, ['br', 'simple_markdown', 'emoji'])}
|
confirmHandler={handleBotTrustAccept}
|
||||||
/>
|
>
|
||||||
|
{text}
|
||||||
|
{shouldRequestWriteAccess && (
|
||||||
|
<Checkbox
|
||||||
|
className="dialog-checkbox"
|
||||||
|
checked={isWriteAllowed}
|
||||||
|
label={renderText(
|
||||||
|
lang('WebApp.AddToAttachmentAllowMessages', currentBot?.firstName),
|
||||||
|
['simple_markdown'],
|
||||||
|
)}
|
||||||
|
onCheck={setIsWriteAllowed}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ConfirmDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -54,6 +54,7 @@ const DraftRecipientPicker: FC<OwnProps> = ({
|
|||||||
<RecipientPicker
|
<RecipientPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
searchPlaceholder={lang('ForwardTo')}
|
searchPlaceholder={lang('ForwardTo')}
|
||||||
|
filter={requestedDraft?.filter}
|
||||||
onSelectRecipient={handleSelectRecipient}
|
onSelectRecipient={handleSelectRecipient}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
onCloseAnimationEnd={unmarkIsShown}
|
onCloseAnimationEnd={unmarkIsShown}
|
||||||
|
|||||||
@ -511,7 +511,11 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
<PhoneCall isActive={isPhoneCallActive} />
|
<PhoneCall isActive={isPhoneCallActive} />
|
||||||
<UnreadCount isForAppBadge />
|
<UnreadCount isForAppBadge />
|
||||||
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
|
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
|
||||||
<BotTrustModal bot={botTrustRequestBot} type={botTrustRequest?.type} />
|
<BotTrustModal
|
||||||
|
bot={botTrustRequestBot}
|
||||||
|
type={botTrustRequest?.type}
|
||||||
|
shouldRequestWriteAccess={botTrustRequest?.shouldRequestWriteAccess}
|
||||||
|
/>
|
||||||
<AttachBotInstallModal bot={attachBotToInstall} />
|
<AttachBotInstallModal bot={attachBotToInstall} />
|
||||||
<AttachBotRecipientPicker requestedAttachBotInChat={requestedAttachBotInChat} />
|
<AttachBotRecipientPicker requestedAttachBotInChat={requestedAttachBotInChat} />
|
||||||
<MessageListHistoryHandler />
|
<MessageListHistoryHandler />
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { extractCurrentThemeParams, validateHexColor } from '../../util/themeStyle';
|
import { extractCurrentThemeParams, validateHexColor } from '../../util/themeStyle';
|
||||||
|
import { convertToApiChatType } from '../../global/helpers';
|
||||||
|
|
||||||
import useInterval from '../../hooks/useInterval';
|
import useInterval from '../../hooks/useInterval';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -94,19 +95,26 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
openInvoice,
|
openInvoice,
|
||||||
setWebAppPaymentSlug,
|
setWebAppPaymentSlug,
|
||||||
showNotification,
|
showNotification,
|
||||||
|
switchBotInline,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
||||||
const [isBackButtonVisible, setIsBackButtonVisible] = useState(false);
|
const [isBackButtonVisible, setIsBackButtonVisible] = useState(false);
|
||||||
const [backgroundColor, setBackgroundColor] = useState(extractCurrentThemeParams().bg_color);
|
const [backgroundColor, setBackgroundColor] = useState<string>();
|
||||||
const [headerColor, setHeaderColor] = useState(extractCurrentThemeParams().bg_color);
|
const [headerColor, setHeaderColor] = useState<string>();
|
||||||
const [confirmClose, setConfirmClose] = useState(false);
|
const [confirmClose, setConfirmClose] = useState(false);
|
||||||
const [isCloseModalOpen, openCloseModal, closeCloseModal] = useFlag(false);
|
const [isCloseModalOpen, openCloseModal, closeModal] = useFlag(false);
|
||||||
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
|
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
|
||||||
const [popupParams, setPopupParams] = useState<PopupOptions | undefined>();
|
const [popupParams, setPopupParams] = useState<PopupOptions | undefined>();
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const prevPopupParams = usePrevious(popupParams);
|
const prevPopupParams = usePrevious(popupParams);
|
||||||
const renderingPopupParams = popupParams || prevPopupParams;
|
const renderingPopupParams = popupParams || prevPopupParams;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const themeParams = extractCurrentThemeParams();
|
||||||
|
setBackgroundColor(themeParams.bg_color);
|
||||||
|
setHeaderColor(themeParams.bg_color);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const frameRef = useRef<HTMLIFrameElement>(null);
|
const frameRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
|
||||||
@ -115,7 +123,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
url, buttonText, queryId, replyToMessageId, threadId,
|
url, buttonText, queryId, replyToMessageId, threadId,
|
||||||
} = webApp || {};
|
} = webApp || {};
|
||||||
const isOpen = Boolean(url);
|
const isOpen = Boolean(url);
|
||||||
const isSimple = !queryId;
|
const isSimple = Boolean(buttonText);
|
||||||
|
|
||||||
const handleEvent = useCallback((event: WebAppInboundEvent) => {
|
const handleEvent = useCallback((event: WebAppInboundEvent) => {
|
||||||
const { eventType, eventData } = event;
|
const { eventType, eventData } = event;
|
||||||
@ -198,6 +206,20 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
message: 'Scan QR code is not supported in this client yet',
|
message: 'Scan QR code is not supported in this client yet',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_switch_inline_query') {
|
||||||
|
const filter = eventData.chat_types?.map(convertToApiChatType).filter(Boolean);
|
||||||
|
const isSamePeer = !filter?.length;
|
||||||
|
|
||||||
|
switchBotInline({
|
||||||
|
botId: bot!.id,
|
||||||
|
query: eventData.query,
|
||||||
|
filter,
|
||||||
|
isSamePeer,
|
||||||
|
});
|
||||||
|
|
||||||
|
closeWebApp();
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
bot, buttonText, closeWebApp, openInvoice, openTelegramLink, sendWebViewData, setWebAppPaymentSlug,
|
bot, buttonText, closeWebApp, openInvoice, openTelegramLink, sendWebViewData, setWebAppPaymentSlug,
|
||||||
isPaymentModalOpen, showNotification,
|
isPaymentModalOpen, showNotification,
|
||||||
@ -314,11 +336,11 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setConfirmClose(false);
|
setConfirmClose(false);
|
||||||
closeCloseModal();
|
closeModal();
|
||||||
setPopupParams(undefined);
|
setPopupParams(undefined);
|
||||||
markUnloaded();
|
markUnloaded();
|
||||||
}
|
}
|
||||||
}, [closeCloseModal, isOpen, markUnloaded]);
|
}, [closeModal, isOpen, markUnloaded]);
|
||||||
|
|
||||||
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
|
||||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||||
@ -460,7 +482,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
{confirmClose && (
|
{confirmClose && (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isCloseModalOpen}
|
isOpen={isCloseModalOpen}
|
||||||
onClose={closeCloseModal}
|
onClose={closeModal}
|
||||||
title={lang('lng_bot_close_warning_title')}
|
title={lang('lng_bot_close_warning_title')}
|
||||||
text={lang('lng_bot_close_warning')}
|
text={lang('lng_bot_close_warning')}
|
||||||
confirmHandler={closeWebApp}
|
confirmHandler={closeWebApp}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useEffect, useRef } from '../../../lib/teact/teact';
|
import { useCallback, useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
|
import { getActions } from '../../../lib/teact/teactn';
|
||||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
import useWindowSize from '../../../hooks/useWindowSize';
|
import useWindowSize from '../../../hooks/useWindowSize';
|
||||||
|
|
||||||
@ -83,6 +84,12 @@ export type WebAppInboundEvent = {
|
|||||||
eventData: {
|
eventData: {
|
||||||
req_id: string;
|
req_id: string;
|
||||||
};
|
};
|
||||||
|
} | {
|
||||||
|
eventType: 'web_app_switch_inline_query';
|
||||||
|
eventData: {
|
||||||
|
query: string;
|
||||||
|
chat_types: ('users' | 'bots' | 'groups' | 'channels')[];
|
||||||
|
};
|
||||||
} | {
|
} | {
|
||||||
eventType: 'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
eventType: 'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
||||||
| 'web_app_request_phone' | 'web_app_close' | 'iframe_ready' | 'web_app_close_scan_qr_popup';
|
| 'web_app_request_phone' | 'web_app_close' | 'iframe_ready' | 'web_app_close_scan_qr_popup';
|
||||||
@ -171,6 +178,10 @@ const useWebAppFrame = (
|
|||||||
onEvent: (event: WebAppInboundEvent) => void,
|
onEvent: (event: WebAppInboundEvent) => void,
|
||||||
onLoad?: () => void,
|
onLoad?: () => void,
|
||||||
) => {
|
) => {
|
||||||
|
const {
|
||||||
|
showNotification,
|
||||||
|
} = getActions();
|
||||||
|
|
||||||
const ignoreEventsRef = useRef<boolean>(false);
|
const ignoreEventsRef = useRef<boolean>(false);
|
||||||
const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>();
|
const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>();
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
@ -266,23 +277,24 @@ const useWebAppFrame = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data.eventType === 'web_app_read_text_from_clipboard') {
|
if (data.eventType === 'web_app_read_text_from_clipboard') {
|
||||||
const { req_id: requestId } = data.eventData;
|
sendEvent({
|
||||||
// eslint-disable-next-line no-null/no-null -- Required by spec
|
eventType: 'clipboard_text_received',
|
||||||
window.navigator.clipboard.readText().catch(() => null).then((text) => {
|
eventData: {
|
||||||
sendEvent({
|
req_id: data.eventData.req_id,
|
||||||
eventType: 'clipboard_text_received',
|
// eslint-disable-next-line no-null/no-null
|
||||||
eventData: {
|
data: null,
|
||||||
req_id: requestId,
|
},
|
||||||
data: text,
|
});
|
||||||
},
|
|
||||||
});
|
showNotification({
|
||||||
|
message: 'Clipboard access is not supported in this client yet',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
onEvent(data);
|
onEvent(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore other messages
|
// Ignore other messages
|
||||||
}
|
}
|
||||||
}, [isSimpleView, onEvent, sendCustomStyle, sendEvent, sendTheme, sendViewport, onLoad, windowSize.isResizing]);
|
}, [isSimpleView, sendEvent, onEvent, sendCustomStyle, sendTheme, sendViewport, onLoad, windowSize.isResizing]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { width, height, isResizing } = windowSize;
|
const { width, height, isResizing } = windowSize;
|
||||||
|
|||||||
@ -496,6 +496,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
botId: inlineBotId,
|
botId: inlineBotId,
|
||||||
isGallery: isInlineBotTooltipGallery,
|
isGallery: isInlineBotTooltipGallery,
|
||||||
switchPm: inlineBotSwitchPm,
|
switchPm: inlineBotSwitchPm,
|
||||||
|
switchWebview: inlineBotSwitchWebview,
|
||||||
results: inlineBotResults,
|
results: inlineBotResults,
|
||||||
closeTooltip: closeInlineBotTooltip,
|
closeTooltip: closeInlineBotTooltip,
|
||||||
help: inlineBotHelp,
|
help: inlineBotHelp,
|
||||||
@ -1322,6 +1323,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isGallery={isInlineBotTooltipGallery}
|
isGallery={isInlineBotTooltipGallery}
|
||||||
inlineBotResults={inlineBotResults}
|
inlineBotResults={inlineBotResults}
|
||||||
switchPm={inlineBotSwitchPm}
|
switchPm={inlineBotSwitchPm}
|
||||||
|
switchWebview={inlineBotSwitchWebview}
|
||||||
loadMore={loadMoreForInlineBot}
|
loadMore={loadMoreForInlineBot}
|
||||||
isSavedMessages={isChatWithSelf}
|
isSavedMessages={isChatWithSelf}
|
||||||
canSendGifs={canSendGifs}
|
canSendGifs={canSendGifs}
|
||||||
|
|||||||
@ -1,17 +1,21 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
|
||||||
import React, {
|
import React, {
|
||||||
memo, useCallback, useEffect, useRef,
|
memo, useCallback, useEffect, useRef,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm } from '../../../api/types';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import type {
|
||||||
|
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm, ApiBotInlineSwitchWebview,
|
||||||
|
} from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
import setTooltipItemVisible from '../../../util/setTooltipItemVisible';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
|
|
||||||
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver } from '../../../hooks/useIntersectionObserver';
|
||||||
import usePrevious from '../../../hooks/usePrevious';
|
import usePrevious from '../../../hooks/usePrevious';
|
||||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
@ -35,6 +39,7 @@ export type OwnProps = {
|
|||||||
isGallery?: boolean;
|
isGallery?: boolean;
|
||||||
inlineBotResults?: (ApiBotInlineResult | ApiBotInlineMediaResult)[];
|
inlineBotResults?: (ApiBotInlineResult | ApiBotInlineMediaResult)[];
|
||||||
switchPm?: ApiBotInlineSwitchPm;
|
switchPm?: ApiBotInlineSwitchPm;
|
||||||
|
switchWebview?: ApiBotInlineSwitchWebview;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
canSendGifs?: boolean;
|
canSendGifs?: boolean;
|
||||||
onSelectResult: (
|
onSelectResult: (
|
||||||
@ -51,6 +56,7 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
isGallery,
|
isGallery,
|
||||||
inlineBotResults,
|
inlineBotResults,
|
||||||
switchPm,
|
switchPm,
|
||||||
|
switchWebview,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
canSendGifs,
|
canSendGifs,
|
||||||
loadMore,
|
loadMore,
|
||||||
@ -61,6 +67,7 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
startBot,
|
startBot,
|
||||||
|
requestSimpleWebView,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -99,6 +106,17 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
startBot({ botId: botId!, param: switchPm!.startParam });
|
startBot({ botId: botId!, param: switchPm!.startParam });
|
||||||
}, [botId, openChat, startBot, switchPm]);
|
}, [botId, openChat, startBot, switchPm]);
|
||||||
|
|
||||||
|
const handleOpenWebview = useCallback(() => {
|
||||||
|
const theme = extractCurrentThemeParams();
|
||||||
|
|
||||||
|
requestSimpleWebView({
|
||||||
|
botId: botId!,
|
||||||
|
url: switchWebview!.url,
|
||||||
|
buttonText: switchWebview!.text,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
}, [botId, switchWebview]);
|
||||||
|
|
||||||
const prevInlineBotResults = usePrevious(
|
const prevInlineBotResults = usePrevious(
|
||||||
inlineBotResults?.length
|
inlineBotResults?.length
|
||||||
? inlineBotResults
|
? inlineBotResults
|
||||||
@ -126,6 +144,14 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSwitchWebview() {
|
||||||
|
return (
|
||||||
|
<ListItem ripple className="switch-pm scroll-item" onClick={handleOpenWebview}>
|
||||||
|
<span className="title">{switchWebview!.text}</span>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function renderContent() {
|
function renderContent() {
|
||||||
return renderedInlineBotResults!.map((inlineBotResult, index) => {
|
return renderedInlineBotResults!.map((inlineBotResult, index) => {
|
||||||
switch (inlineBotResult.type) {
|
switch (inlineBotResult.type) {
|
||||||
@ -203,6 +229,7 @@ const InlineBotTooltip: FC<OwnProps> = ({
|
|||||||
sensitiveArea={160}
|
sensitiveArea={160}
|
||||||
>
|
>
|
||||||
{switchPm && renderSwitchPm()}
|
{switchPm && renderSwitchPm()}
|
||||||
|
{switchWebview && renderSwitchWebview()}
|
||||||
{Boolean(renderedInlineBotResults?.length) && renderContent()}
|
{Boolean(renderedInlineBotResults?.length) && renderContent()}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -60,6 +60,7 @@ export default function useInlineBotTooltip(
|
|||||||
const {
|
const {
|
||||||
id: botId,
|
id: botId,
|
||||||
switchPm,
|
switchPm,
|
||||||
|
switchWebview,
|
||||||
offset,
|
offset,
|
||||||
results,
|
results,
|
||||||
isGallery,
|
isGallery,
|
||||||
@ -87,6 +88,7 @@ export default function useInlineBotTooltip(
|
|||||||
botId,
|
botId,
|
||||||
isGallery,
|
isGallery,
|
||||||
switchPm,
|
switchPm,
|
||||||
|
switchWebview,
|
||||||
results,
|
results,
|
||||||
closeTooltip: markManuallyClosed,
|
closeTooltip: markManuallyClosed,
|
||||||
help: canShowHelp && help ? `@${username} ${help}` : undefined,
|
help: canShowHelp && help ? `@${username} ${help}` : undefined,
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export const CUSTOM_EMOJI_PREVIEW_CACHE_DISABLED = false;
|
|||||||
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
||||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
||||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v17';
|
export const LANG_CACHE_NAME = 'tt-lang-packs-v18';
|
||||||
export const ASSET_CACHE_NAME = 'tt-assets';
|
export const ASSET_CACHE_NAME = 'tt-assets';
|
||||||
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
||||||
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
||||||
@ -233,7 +233,7 @@ export const SUPPORTED_TRANSLATION_LANGUAGES = [
|
|||||||
// Official
|
// Official
|
||||||
'en', 'ar', 'be', 'ca', 'zh', 'nl', 'fr', 'de', 'id',
|
'en', 'ar', 'be', 'ca', 'zh', 'nl', 'fr', 'de', 'id',
|
||||||
'it', 'ja', 'ko', 'pl', 'pt', 'ru', 'es', 'uk',
|
'it', 'ja', 'ko', 'pl', 'pt', 'ru', 'es', 'uk',
|
||||||
// Unnofficial
|
// Unofficial
|
||||||
'af', 'sq', 'am', 'hy', 'az', 'eu', 'bn', 'bs', 'bg',
|
'af', 'sq', 'am', 'hy', 'az', 'eu', 'bn', 'bs', 'bg',
|
||||||
'ceb', 'zh-CN', 'zh-TW', 'co', 'hr', 'cs', 'da', 'eo',
|
'ceb', 'zh-CN', 'zh-TW', 'co', 'hr', 'cs', 'da', 'eo',
|
||||||
'et', 'fi', 'fy', 'gl', 'ka', 'el', 'gu', 'ht', 'ha',
|
'et', 'fi', 'fy', 'gl', 'ka', 'el', 'gu', 'ht', 'ha',
|
||||||
@ -257,6 +257,7 @@ export const TME_LINK_PREFIX = 'https://t.me/';
|
|||||||
export const USERNAME_PURCHASE_ERROR = 'USERNAME_PURCHASE_AVAILABLE';
|
export const USERNAME_PURCHASE_ERROR = 'USERNAME_PURCHASE_AVAILABLE';
|
||||||
export const PURCHASE_USERNAME = 'auction';
|
export const PURCHASE_USERNAME = 'auction';
|
||||||
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
export const TME_WEB_DOMAINS = new Set(['t.me', 'web.t.me', 'a.t.me', 'k.t.me', 'z.t.me']);
|
||||||
|
export const WEB_APP_PLATFORM = 'weba';
|
||||||
|
|
||||||
// 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']);
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
|||||||
import PopupManager from '../../../util/PopupManager';
|
import PopupManager from '../../../util/PopupManager';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
|
import { translate } from '../../../util/langProvider';
|
||||||
|
|
||||||
const GAMEE_URL = 'https://prizes.gamee.com/';
|
const GAMEE_URL = 'https://prizes.gamee.com/';
|
||||||
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
const TOP_PEERS_REQUEST_COOLDOWN = 60; // 1 min
|
||||||
@ -290,18 +291,29 @@ addActionHandler('queryInlineBot', async (global, actions, payload): Promise<voi
|
|||||||
|
|
||||||
addActionHandler('switchBotInline', (global, actions, payload): ActionReturnType => {
|
addActionHandler('switchBotInline', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
query, isSamePeer, messageId, tabId = getCurrentTabId(),
|
query, isSamePeer, messageId, filter, tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
let {
|
||||||
|
botId,
|
||||||
} = payload;
|
} = payload;
|
||||||
const chat = selectCurrentChat(global, tabId);
|
const chat = selectCurrentChat(global, tabId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const message = selectChatMessage(global, chat.id, messageId);
|
|
||||||
if (!message) {
|
if (!botId && messageId) {
|
||||||
|
const message = selectChatMessage(global, chat.id, messageId);
|
||||||
|
if (!message) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
botId = message.viaBotId || message.senderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!botId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const botSender = selectUser(global, message.viaBotId || message.senderId!);
|
const botSender = selectUser(global, botId);
|
||||||
if (!botSender) {
|
if (!botSender) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -309,6 +321,7 @@ addActionHandler('switchBotInline', (global, actions, payload): ActionReturnType
|
|||||||
actions.openChatWithDraft({
|
actions.openChatWithDraft({
|
||||||
text: `@${botSender.usernames![0].username} ${query}`,
|
text: `@${botSender.usernames![0].username} ${query}`,
|
||||||
chatId: isSamePeer ? chat.id : undefined,
|
chatId: isSamePeer ? chat.id : undefined,
|
||||||
|
filter,
|
||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -513,6 +526,65 @@ addActionHandler('requestWebView', async (global, actions, payload): Promise<voi
|
|||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('requestAppWebView', async (global, actions, payload): Promise<void> => {
|
||||||
|
const {
|
||||||
|
botId, appName, startApp, theme, isWriteAllowed,
|
||||||
|
tabId = getCurrentTabId(),
|
||||||
|
} = payload;
|
||||||
|
|
||||||
|
const bot = selectUser(global, botId);
|
||||||
|
if (!bot) return;
|
||||||
|
|
||||||
|
const botApp = await callApi('fetchBotApp', {
|
||||||
|
bot,
|
||||||
|
appName,
|
||||||
|
});
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
if (!botApp) {
|
||||||
|
actions.showNotification({ message: translate('lng_username_app_not_found'), tabId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botApp.isInactive && !selectIsTrustedBot(global, botId)) {
|
||||||
|
global = updateTabState(global, {
|
||||||
|
botTrustRequest: {
|
||||||
|
botId,
|
||||||
|
shouldRequestWriteAccess: botApp.shouldRequestWriteAccess,
|
||||||
|
type: 'botApp',
|
||||||
|
onConfirm: {
|
||||||
|
action: 'requestAppWebView',
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const peer = selectCurrentChat(global, tabId);
|
||||||
|
|
||||||
|
const url = await callApi('requestAppWebView', {
|
||||||
|
peer: peer || bot,
|
||||||
|
app: botApp,
|
||||||
|
startParam: startApp,
|
||||||
|
isWriteAllowed,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
if (!url) return;
|
||||||
|
|
||||||
|
global = updateTabState(global, {
|
||||||
|
webApp: {
|
||||||
|
url,
|
||||||
|
botId,
|
||||||
|
buttonText: '',
|
||||||
|
},
|
||||||
|
}, tabId);
|
||||||
|
setGlobal(global);
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('prolongWebView', async (global, actions, payload): Promise<void> => {
|
addActionHandler('prolongWebView', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
botId, peerId, isSilent, replyToMessageId, queryId, threadId,
|
botId, peerId, isSilent, replyToMessageId, queryId, threadId,
|
||||||
@ -582,7 +654,7 @@ addActionHandler('cancelBotTrustRequest', (global, actions, payload): ActionRetu
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('markBotTrusted', (global, actions, payload): ActionReturnType => {
|
addActionHandler('markBotTrusted', (global, actions, payload): ActionReturnType => {
|
||||||
const { botId, tabId = getCurrentTabId() } = payload;
|
const { botId, isWriteAllowed, tabId = getCurrentTabId() } = payload;
|
||||||
const { trustedBotIds } = global;
|
const { trustedBotIds } = global;
|
||||||
|
|
||||||
const newTrustedBotIds = new Set(trustedBotIds);
|
const newTrustedBotIds = new Set(trustedBotIds);
|
||||||
@ -597,7 +669,10 @@ addActionHandler('markBotTrusted', (global, actions, payload): ActionReturnType
|
|||||||
if (tabState.botTrustRequest?.onConfirm) {
|
if (tabState.botTrustRequest?.onConfirm) {
|
||||||
const { action, payload: callbackPayload } = tabState.botTrustRequest.onConfirm;
|
const { action, payload: callbackPayload } = tabState.botTrustRequest.onConfirm;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
actions[action](callbackPayload);
|
actions[action]({
|
||||||
|
...(callbackPayload as {}),
|
||||||
|
isWriteAllowed,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
@ -921,6 +996,7 @@ async function searchInlineBot<T extends GlobalState>(global: T, {
|
|||||||
cacheTime: Date.now() + result.cacheTime * 1000,
|
cacheTime: Date.now() + result.cacheTime * 1000,
|
||||||
...(newResults.length && { isGallery: result.isGallery }),
|
...(newResults.length && { isGallery: result.isGallery }),
|
||||||
...(result.switchPm && { switchPm: result.switchPm }),
|
...(result.switchPm && { switchPm: result.switchPm }),
|
||||||
|
...(result.switchWebview && { switchWebview: result.switchWebview }),
|
||||||
canLoadMore: result.results.length > 0 && Boolean(result.nextOffset),
|
canLoadMore: result.results.length > 0 && Boolean(result.nextOffset),
|
||||||
results: newInlineBotData.offset === '' || newInlineBotData.offset === result.nextOffset
|
results: newInlineBotData.offset === '' || newInlineBotData.offset === result.nextOffset
|
||||||
? result.results
|
? result.results
|
||||||
|
|||||||
@ -77,6 +77,7 @@ import * as langProvider from '../../../util/langProvider';
|
|||||||
import { selectCurrentLimit } from '../../selectors/limits';
|
import { selectCurrentLimit } from '../../selectors/limits';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
|
|
||||||
const TOP_CHAT_MESSAGES_PRELOAD_INTERVAL = 100;
|
const TOP_CHAT_MESSAGES_PRELOAD_INTERVAL = 100;
|
||||||
const INFINITE_LOOP_MARKER = 100;
|
const INFINITE_LOOP_MARKER = 100;
|
||||||
@ -1005,6 +1006,8 @@ addActionHandler('openTelegramLink', (global, actions, payload): ActionReturnTyp
|
|||||||
startParam: params.start,
|
startParam: params.start,
|
||||||
startAttach,
|
startAttach,
|
||||||
attach: params.attach,
|
attach: params.attach,
|
||||||
|
startApp: params.startapp,
|
||||||
|
originalParts: [part1, part2, part3],
|
||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1022,11 +1025,13 @@ addActionHandler('acceptInviteConfirmation', async (global, actions, payload): P
|
|||||||
|
|
||||||
addActionHandler('openChatByUsername', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openChatByUsername', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
username, messageId, commentId, startParam, startAttach, attach, threadId,
|
username, messageId, commentId, startParam, startAttach, attach, threadId, originalParts, startApp,
|
||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload!;
|
} = payload!;
|
||||||
|
|
||||||
const chat = selectCurrentChat(global, tabId);
|
const chat = selectCurrentChat(global, tabId);
|
||||||
|
const webAppName = originalParts?.[1];
|
||||||
|
const isWebApp = webAppName && !Number(webAppName);
|
||||||
|
|
||||||
if (!commentId) {
|
if (!commentId) {
|
||||||
if (!startAttach && messageId && !startParam && chat?.usernames?.some((c) => c.username === username)) {
|
if (!startAttach && messageId && !startParam && chat?.usernames?.some((c) => c.username === username)) {
|
||||||
@ -1035,13 +1040,15 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await openChatByUsername(global, actions, username, threadId, messageId, startParam, startAttach, attach, tabId);
|
if (!isWebApp) {
|
||||||
return;
|
await openChatByUsername(global, actions, username, threadId, messageId, startParam, startAttach, attach, tabId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { chatId, type } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId, type } = selectCurrentMessageList(global, tabId) || {};
|
||||||
const usernameChat = selectChatByUsername(global, username);
|
const usernameChat = selectChatByUsername(global, username);
|
||||||
if (chatId && messageId && usernameChat && type === 'thread') {
|
if (chatId && commentId && messageId && usernameChat && type === 'thread') {
|
||||||
const threadInfo = selectThreadInfo(global, chatId, messageId);
|
const threadInfo = selectThreadInfo(global, chatId, messageId);
|
||||||
|
|
||||||
if (threadInfo && threadInfo.chatId === chatId) {
|
if (threadInfo && threadInfo.chatId === chatId) {
|
||||||
@ -1055,9 +1062,7 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!messageId) return;
|
if (!isWebApp) actions.openChat({ id: TMP_CHAT_ID, tabId });
|
||||||
|
|
||||||
actions.openChat({ id: TMP_CHAT_ID, tabId });
|
|
||||||
|
|
||||||
const chatByUsername = await fetchChatByUsername(global, username);
|
const chatByUsername = await fetchChatByUsername(global, username);
|
||||||
|
|
||||||
@ -1065,6 +1070,21 @@ addActionHandler('openChatByUsername', async (global, actions, payload): Promise
|
|||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
|
if (isWebApp && chatByUsername) {
|
||||||
|
const theme = extractCurrentThemeParams();
|
||||||
|
|
||||||
|
actions.requestAppWebView({
|
||||||
|
appName: webAppName,
|
||||||
|
botId: chatByUsername.id,
|
||||||
|
tabId,
|
||||||
|
startApp,
|
||||||
|
theme,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!messageId) return;
|
||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, chatByUsername.id, messageId);
|
const threadInfo = selectThreadInfo(global, chatByUsername.id, messageId);
|
||||||
let discussionChatId: string | undefined;
|
let discussionChatId: string | undefined;
|
||||||
|
|
||||||
|
|||||||
@ -102,7 +102,7 @@ addActionHandler('openChatWithInfo', (global, actions, payload): ActionReturnTyp
|
|||||||
|
|
||||||
addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnType => {
|
addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, text, threadId, files, tabId = getCurrentTabId(),
|
chatId, text, threadId, files, filter, tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
@ -114,6 +114,7 @@ addActionHandler('openChatWithDraft', (global, actions, payload): ActionReturnTy
|
|||||||
chatId,
|
chatId,
|
||||||
text,
|
text,
|
||||||
files,
|
files,
|
||||||
|
filter,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
import type { ApiPhoto } from '../../api/types';
|
import type { ApiChatType, ApiPhoto } from '../../api/types';
|
||||||
|
|
||||||
export function getBotCoverMediaHash(photo: ApiPhoto) {
|
export function getBotCoverMediaHash(photo: ApiPhoto) {
|
||||||
return `photo${photo.id}?size=x`;
|
return `photo${photo.id}?size=x`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function convertToApiChatType(type: string): ApiChatType | undefined {
|
||||||
|
if (type === 'channels') return 'channels';
|
||||||
|
if (type === 'chats' || type === 'groups') return 'chats';
|
||||||
|
if (type === 'users') return 'users';
|
||||||
|
if (type === 'bots') return 'bots';
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|||||||
@ -452,6 +452,7 @@ export type TabState = {
|
|||||||
chatId?: string;
|
chatId?: string;
|
||||||
text: string;
|
text: string;
|
||||||
files?: File[];
|
files?: File[];
|
||||||
|
filter?: ApiChatType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
pollModal: {
|
pollModal: {
|
||||||
@ -471,7 +472,8 @@ export type TabState = {
|
|||||||
|
|
||||||
botTrustRequest?: {
|
botTrustRequest?: {
|
||||||
botId: string;
|
botId: string;
|
||||||
type: 'game' | 'webApp';
|
type: 'game' | 'webApp' | 'botApp';
|
||||||
|
shouldRequestWriteAccess?: boolean;
|
||||||
onConfirm?: CallbackAction;
|
onConfirm?: CallbackAction;
|
||||||
};
|
};
|
||||||
requestedAttachBotInstall?: {
|
requestedAttachBotInstall?: {
|
||||||
@ -1260,6 +1262,8 @@ export interface ActionPayloads {
|
|||||||
startParam?: string;
|
startParam?: string;
|
||||||
startAttach?: string | boolean;
|
startAttach?: string | boolean;
|
||||||
attach?: string;
|
attach?: string;
|
||||||
|
startApp?: string;
|
||||||
|
originalParts?: string[];
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
requestThreadInfoUpdate: {
|
requestThreadInfoUpdate: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
@ -1644,6 +1648,7 @@ export interface ActionPayloads {
|
|||||||
threadId?: number;
|
threadId?: number;
|
||||||
text: string;
|
text: string;
|
||||||
files?: File[];
|
files?: File[];
|
||||||
|
filter?: ApiChatType[];
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
resetOpenChatWithDraft: WithTabId | undefined;
|
resetOpenChatWithDraft: WithTabId | undefined;
|
||||||
toggleJoinToSend: {
|
toggleJoinToSend: {
|
||||||
@ -2019,9 +2024,11 @@ export interface ActionPayloads {
|
|||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
switchBotInline: {
|
switchBotInline: {
|
||||||
messageId: number;
|
messageId?: number;
|
||||||
|
botId?: string;
|
||||||
query: string;
|
query: string;
|
||||||
isSamePeer?: boolean;
|
isSamePeer?: boolean;
|
||||||
|
filter?: ApiChatType[];
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
openGame: {
|
openGame: {
|
||||||
@ -2055,6 +2062,13 @@ export interface ActionPayloads {
|
|||||||
buttonText: string;
|
buttonText: string;
|
||||||
theme?: ApiThemeParameters;
|
theme?: ApiThemeParameters;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
requestAppWebView: {
|
||||||
|
botId: string;
|
||||||
|
appName: string;
|
||||||
|
theme?: ApiThemeParameters;
|
||||||
|
startApp?: string;
|
||||||
|
isWriteAllowed?: boolean;
|
||||||
|
} & WithTabId;
|
||||||
setWebAppPaymentSlug: {
|
setWebAppPaymentSlug: {
|
||||||
slug?: string;
|
slug?: string;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
@ -2062,6 +2076,7 @@ export interface ActionPayloads {
|
|||||||
cancelBotTrustRequest: WithTabId | undefined;
|
cancelBotTrustRequest: WithTabId | undefined;
|
||||||
markBotTrusted: {
|
markBotTrusted: {
|
||||||
botId: string;
|
botId: string;
|
||||||
|
isWriteAllowed?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
cancelAttachBotInstall: WithTabId | undefined;
|
cancelAttachBotInstall: WithTabId | undefined;
|
||||||
|
|||||||
@ -1304,6 +1304,8 @@ messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions;
|
|||||||
messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
|
messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
|
||||||
messages.clearRecentReactions#9dfeefb4 = Bool;
|
messages.clearRecentReactions#9dfeefb4 = Bool;
|
||||||
messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector<int> = Updates;
|
messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector<int> = Updates;
|
||||||
|
messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp;
|
||||||
|
messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult;
|
||||||
updates.getState#edd4882a = updates.State;
|
updates.getState#edd4882a = updates.State;
|
||||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||||
|
|||||||
@ -163,6 +163,8 @@
|
|||||||
"messages.toggleNoForwards",
|
"messages.toggleNoForwards",
|
||||||
"messages.saveDefaultSendAs",
|
"messages.saveDefaultSendAs",
|
||||||
"messages.getExtendedMedia",
|
"messages.getExtendedMedia",
|
||||||
|
"messages.getBotApp",
|
||||||
|
"messages.requestAppWebView",
|
||||||
"updates.getState",
|
"updates.getState",
|
||||||
"updates.getDifference",
|
"updates.getDifference",
|
||||||
"updates.getChannelDifference",
|
"updates.getChannelDifference",
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { TeactNode } from '../lib/teact/teact';
|
import type { TeactNode } from '../lib/teact/teact';
|
||||||
import type {
|
import type {
|
||||||
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm,
|
ApiBotInlineMediaResult, ApiBotInlineResult, ApiBotInlineSwitchPm,
|
||||||
|
ApiBotInlineSwitchWebview,
|
||||||
ApiChatInviteImporter,
|
ApiChatInviteImporter,
|
||||||
ApiExportedInvite,
|
ApiExportedInvite,
|
||||||
ApiLanguage, ApiMessage, ApiReaction, ApiStickerSet,
|
ApiLanguage, ApiMessage, ApiReaction, ApiStickerSet,
|
||||||
@ -396,5 +397,6 @@ export type InlineBotSettings = {
|
|||||||
results?: (ApiBotInlineResult | ApiBotInlineMediaResult)[];
|
results?: (ApiBotInlineResult | ApiBotInlineMediaResult)[];
|
||||||
isGallery?: boolean;
|
isGallery?: boolean;
|
||||||
switchPm?: ApiBotInlineSwitchPm;
|
switchPm?: ApiBotInlineSwitchPm;
|
||||||
|
switchWebview?: ApiBotInlineSwitchWebview;
|
||||||
cacheTime: number;
|
cacheTime: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export const processDeepLink = (url: string) => {
|
|||||||
case 'resolve': {
|
case 'resolve': {
|
||||||
const {
|
const {
|
||||||
domain, phone, post, comment, voicechat, livestream, start, startattach, attach, thread, topic,
|
domain, phone, post, comment, voicechat, livestream, start, startattach, attach, thread, topic,
|
||||||
|
appname, startapp,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach;
|
const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach;
|
||||||
@ -43,7 +44,13 @@ export const processDeepLink = (url: string) => {
|
|||||||
const threadId = Number(thread) || Number(topic) || undefined;
|
const threadId = Number(thread) || Number(topic) || undefined;
|
||||||
|
|
||||||
if (domain !== 'telegrampassport') {
|
if (domain !== 'telegrampassport') {
|
||||||
if (startAttach && choose) {
|
if (appname) {
|
||||||
|
openChatByUsername({
|
||||||
|
username: domain,
|
||||||
|
startApp: startapp,
|
||||||
|
originalParts: [domain, appname],
|
||||||
|
});
|
||||||
|
} else if (startAttach && choose) {
|
||||||
processAttachBotParameters({
|
processAttachBotParameters({
|
||||||
username: domain,
|
username: domain,
|
||||||
filter: choose,
|
filter: choose,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user