Web Apps: Support new event types (#3818)

This commit is contained in:
Alexander Zinchuk 2023-09-08 18:39:23 +02:00
parent e2aa5197d1
commit c59d468c73
27 changed files with 741 additions and 402 deletions

View File

@ -426,6 +426,8 @@ function buildAction(
if (action.domain) {
text = 'ActionBotAllowed';
translationValues.push(action.domain);
} else if (action.fromRequest) {
text = 'lng_action_webapp_bot_allowed';
} else {
text = 'ActionAttachMenuBotAllowed';
}

View File

@ -468,6 +468,55 @@ export async function acceptLinkUrlAuth({ url, isWriteAllowed }: { url: string;
return authResult;
}
export function fetchBotCanSendMessage({ bot } : { bot: ApiUser }) {
return invokeRequest(new GramJs.bots.CanSendMessage({
bot: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser,
}));
}
export function allowBotSendMessages({ bot } : { bot: ApiUser }) {
return invokeRequest(new GramJs.bots.AllowSendMessage({
bot: buildInputEntity(bot.id, bot.accessHash) as GramJs.InputUser,
}), {
shouldReturnTrue: true,
});
}
export async function invokeWebViewCustomMethod({
bot,
customMethod,
parameters,
}: {
bot: ApiUser;
customMethod: string;
parameters: string;
}): Promise<{
result: object;
} | {
error: string;
}> {
try {
const result = await invokeRequest(new GramJs.bots.InvokeWebViewCustomMethod({
bot: buildInputPeer(bot.id, bot.accessHash),
params: new GramJs.DataJSON({
data: parameters,
}),
customMethod,
}), {
shouldThrow: true,
});
return {
result: JSON.parse(result!.data),
};
} catch (e) {
const error = e as Error;
return {
error: error.message,
};
}
}
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
return results.map((result) => {
if (result instanceof GramJs.BotInlineMediaResult) {

View File

@ -77,6 +77,7 @@ export {
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot, fetchBotApp,
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth, loadAttachBot, requestAppWebView,
allowBotSendMessages, fetchBotCanSendMessage, invokeWebViewCustomMethod,
} from './bots';
export {

View File

@ -7,13 +7,13 @@ export { default as AttachBotRecipientPicker } from '../components/main/AttachBo
export { default as Dialogs } from '../components/main/Dialogs';
export { default as Notifications } from '../components/main/Notifications';
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
export { default as MapModal } from '../components/modals/mapModal/MapModal';
export { default as UrlAuthModal } from '../components/main/UrlAuthModal';
export { default as MapModal } from '../components/modals/map/MapModal';
export { default as UrlAuthModal } from '../components/modals/urlAuth/UrlAuthModal';
export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
export { default as NewContactModal } from '../components/main/NewContactModal';
export { default as WebAppModal } from '../components/main/WebAppModal';
export { default as WebAppModal } from '../components/modals/webApp/WebAppModal';
export { default as BotTrustModal } from '../components/main/BotTrustModal';
export { default as AttachBotInstallModal } from '../components/main/AttachBotInstallModal';
export { default as AttachBotInstallModal } from '../components/modals/attachBotInstall/AttachBotInstallModal';
export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDialog';
export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal';
export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal';

View File

@ -80,11 +80,11 @@ import PhoneCall from '../calls/phone/PhoneCall.async';
import MessageListHistoryHandler from '../middle/MessageListHistoryHandler';
import NewContactModal from './NewContactModal.async';
import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async';
import WebAppModal from './WebAppModal.async';
import WebAppModal from '../modals/webApp/WebAppModal.async';
import BotTrustModal from './BotTrustModal.async';
import AttachBotInstallModal from './AttachBotInstallModal.async';
import AttachBotInstallModal from '../modals/attachBotInstall/AttachBotInstallModal.async';
import ConfettiContainer from './ConfettiContainer';
import UrlAuthModal from './UrlAuthModal.async';
import UrlAuthModal from '../modals/urlAuth/UrlAuthModal.async';
import PremiumMainModal from './premium/PremiumMainModal.async';
import PaymentModal from '../payment/PaymentModal.async';
import ReceiptModal from '../payment/ReceiptModal.async';
@ -96,7 +96,7 @@ import AttachBotRecipientPicker from './AttachBotRecipientPicker.async';
import ReactionPicker from '../middle/message/ReactionPicker.async';
import ChatlistModal from '../modals/chatlist/ChatlistModal.async';
import StoryViewer from '../story/StoryViewer.async';
import MapModal from '../modals/mapModal/MapModal.async';
import MapModal from '../modals/map/MapModal.async';
import './Main.scss';

View File

@ -1,10 +1,10 @@
import type { FC } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import type { FC } from '../../../lib/teact/teact';
import React from '../../../lib/teact/teact';
import { Bundles } from '../../../util/moduleLoader';
import type { OwnProps } from './AttachBotInstallModal';
import useModuleLoader from '../../hooks/useModuleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const AttachBotInstallModalAsync: FC<OwnProps> = (props) => {
const { bot } = props;

View File

@ -1,18 +1,18 @@
import React, {
memo, useCallback, useEffect, useState,
} from '../../lib/teact/teact';
import { getActions } from '../../global';
} from '../../../lib/teact/teact';
import { getActions } from '../../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiAttachBot } from '../../api/types';
import type { FC } from '../../../lib/teact/teact';
import type { ApiAttachBot } from '../../../api/types';
import renderText from '../common/helpers/renderText';
import renderText from '../../common/helpers/renderText';
import useLang from '../../hooks/useLang';
import usePrevious from '../../hooks/usePrevious';
import useLang from '../../../hooks/useLang';
import usePrevious from '../../../hooks/usePrevious';
import ConfirmDialog from '../ui/ConfirmDialog';
import Checkbox from '../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog';
import Checkbox from '../../ui/Checkbox';
export type OwnProps = {
bot?: ApiAttachBot;

View File

@ -1,9 +1,9 @@
import type { FC } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import type { FC } from '../../../lib/teact/teact';
import React from '../../../lib/teact/teact';
import { Bundles } from '../../../util/moduleLoader';
import type { OwnProps } from './UrlAuthModal';
import useModuleLoader from '../../hooks/useModuleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const UrlAuthModalAsync: FC<OwnProps> = (props) => {
const { urlAuth } = props;

View File

@ -1,20 +1,20 @@
import React, {
memo, useCallback, useEffect, useState,
} from '../../lib/teact/teact';
import { getActions, getGlobal } from '../../global';
} from '../../../lib/teact/teact';
import { getActions, getGlobal } from '../../../global';
import type { FC } from '../../lib/teact/teact';
import type { TabState } from '../../global/types';
import type { FC } from '../../../lib/teact/teact';
import type { TabState } from '../../../global/types';
import { ensureProtocol } from '../../util/ensureProtocol';
import renderText from '../common/helpers/renderText';
import { getUserFullName } from '../../global/helpers';
import { ensureProtocol } from '../../../util/ensureProtocol';
import renderText from '../../common/helpers/renderText';
import { getUserFullName } from '../../../global/helpers';
import useLang from '../../hooks/useLang';
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
import useLang from '../../../hooks/useLang';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import ConfirmDialog from '../ui/ConfirmDialog';
import Checkbox from '../ui/Checkbox';
import ConfirmDialog from '../../ui/ConfirmDialog';
import Checkbox from '../../ui/Checkbox';
import styles from './UrlAuthModal.module.scss';

View File

@ -1,10 +1,10 @@
import type { FC } from '../../lib/teact/teact';
import React from '../../lib/teact/teact';
import { Bundles } from '../../util/moduleLoader';
import type { FC } from '../../../lib/teact/teact';
import React from '../../../lib/teact/teact';
import { Bundles } from '../../../util/moduleLoader';
import type { OwnProps } from './WebAppModal';
import useModuleLoader from '../../hooks/useModuleLoader';
import useModuleLoader from '../../../hooks/useModuleLoader';
const WebAppModalAsync: FC<OwnProps> = (props) => {
const { webApp } = props;

View File

@ -1,36 +1,39 @@
import React, {
memo, useCallback, useEffect, useMemo, useRef, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
memo, useEffect, useMemo, useRef, useState,
} from '../../../lib/teact/teact';
import { getActions, withGlobal } from '../../../global';
import type { FC } from '../../lib/teact/teact';
import type { ApiAttachBot, ApiChat, ApiUser } from '../../api/types';
import type { TabState } from '../../global/types';
import type { ThemeKey } from '../../types';
import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame';
import type { FC } from '../../../lib/teact/teact';
import type { ApiAttachBot, ApiChat, ApiUser } from '../../../api/types';
import type { TabState } from '../../../global/types';
import type { ThemeKey } from '../../../types';
import type { PopupOptions, WebAppInboundEvent } from '../../../types/webapp';
import { TME_LINK_PREFIX } from '../../config';
import { callApi } from '../../../api/gramjs';
import { TME_LINK_PREFIX } from '../../../config';
import {
selectCurrentChat, selectTabState, selectTheme, selectUser,
} from '../../global/selectors';
import buildClassName from '../../util/buildClassName';
import { extractCurrentThemeParams, validateHexColor } from '../../util/themeStyle';
import { convertToApiChatType } from '../../global/helpers';
} from '../../../global/selectors';
import buildClassName from '../../../util/buildClassName';
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
import { convertToApiChatType } from '../../../global/helpers';
import useInterval from '../../hooks/useInterval';
import useLang from '../../hooks/useLang';
import useSyncEffect from '../../hooks/useSyncEffect';
import useInterval from '../../../hooks/useInterval';
import useLang from '../../../hooks/useLang';
import useSyncEffect from '../../../hooks/useSyncEffect';
import useWebAppFrame from './hooks/useWebAppFrame';
import usePrevious from '../../hooks/usePrevious';
import useFlag from '../../hooks/useFlag';
import useAppLayout from '../../hooks/useAppLayout';
import usePrevious from '../../../hooks/usePrevious';
import useFlag from '../../../hooks/useFlag';
import useAppLayout from '../../../hooks/useAppLayout';
import useLastCallback from '../../../hooks/useLastCallback';
import usePopupLimit from './hooks/usePopupLimit';
import Modal from '../ui/Modal';
import Button from '../ui/Button';
import DropdownMenu from '../ui/DropdownMenu';
import MenuItem from '../ui/MenuItem';
import Spinner from '../ui/Spinner';
import ConfirmDialog from '../ui/ConfirmDialog';
import Modal from '../../ui/Modal';
import Button from '../../ui/Button';
import DropdownMenu from '../../ui/DropdownMenu';
import MenuItem from '../../ui/MenuItem';
import Spinner from '../../ui/Spinner';
import ConfirmDialog from '../../ui/ConfirmDialog';
import styles from './WebAppModal.module.scss';
@ -61,6 +64,8 @@ const NBSP = '\u00A0';
const MAIN_BUTTON_ANIMATION_TIME = 250;
const PROLONG_INTERVAL = 45000; // 45s
const ANIMATION_WAIT = 400;
const POPUP_SEQUENTIAL_LIMIT = 3;
const POPUP_RESET_DELAY = 2000; // 2s
const SANDBOX_ATTRIBUTES = [
'allow-scripts',
'allow-same-origin',
@ -92,22 +97,29 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
toggleAttachBot,
openTelegramLink,
openChat,
openInvoice,
setWebAppPaymentSlug,
showNotification,
switchBotInline,
sharePhoneWithBot,
} = getActions();
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
const [isBackButtonVisible, setIsBackButtonVisible] = useState(false);
const [backgroundColor, setBackgroundColor] = useState<string>();
const [headerColor, setHeaderColor] = useState<string>();
const [confirmClose, setConfirmClose] = useState(false);
const [isCloseModalOpen, openCloseModal, closeModal] = useFlag(false);
const [shouldConfirmClosing, setShouldConfirmClosing] = useState(false);
const [isCloseModalOpen, openCloseModal, hideCloseModal] = useFlag(false);
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
const [popupParams, setPopupParams] = useState<PopupOptions | undefined>();
const [popupParameters, setPopupParameters] = useState<PopupOptions | undefined>();
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
const [isRequesingWriteAccess, setIsRequestingWriteAccess] = useState(false);
const {
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
} = usePopupLimit(POPUP_SEQUENTIAL_LIMIT, POPUP_RESET_DELAY);
const { isMobile } = useAppLayout();
const prevPopupParams = usePrevious(popupParams);
const renderingPopupParams = popupParams || prevPopupParams;
useEffect(() => {
const themeParams = extractCurrentThemeParams();
@ -125,32 +137,224 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
const isOpen = Boolean(url);
const isSimple = Boolean(buttonText);
const handleEvent = useCallback((event: WebAppInboundEvent) => {
const { eventType, eventData } = event;
if (eventType === 'web_app_close') {
const {
reloadFrame, sendEvent, sendViewport, sendTheme,
} = useWebAppFrame(frameRef, isOpen, isSimple, handleEvent, markLoaded);
const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0;
useInterval(() => {
prolongWebView({
botId: bot!.id,
queryId: queryId!,
peerId: chat!.id,
replyToMessageId,
threadId,
});
}, queryId ? PROLONG_INTERVAL : undefined, true);
const handleMainButtonClick = useLastCallback(() => {
sendEvent({
eventType: 'main_button_pressed',
});
});
const handleSettingsButtonClick = useLastCallback(() => {
sendEvent({
eventType: 'settings_button_pressed',
});
});
const handleRefreshClick = useLastCallback(() => {
reloadFrame(webApp!.url);
});
const handleClose = useLastCallback(() => {
if (shouldConfirmClosing) {
openCloseModal();
} else {
closeWebApp();
}
});
if (eventType === 'web_app_open_invoice') {
setWebAppPaymentSlug({
slug: eventData.slug,
const handleAppPopupClose = useLastCallback((buttonId?: string) => {
setPopupParameters(undefined);
handlePopupClosed();
sendEvent({
eventType: 'popup_closed',
eventData: {
button_id: buttonId,
},
});
});
const handleAppPopupModalClose = useLastCallback(() => {
handleAppPopupClose();
});
// Notify view that height changed
useSyncEffect(() => {
setTimeout(() => {
sendViewport();
}, ANIMATION_WAIT);
}, [mainButton?.isVisible, sendViewport]);
// Notify view that theme changed
useSyncEffect(() => {
setTimeout(() => {
sendTheme();
}, ANIMATION_WAIT);
}, [theme, sendTheme]);
useSyncEffect(([prevIsPaymentModalOpen]) => {
if (isPaymentModalOpen === prevIsPaymentModalOpen) return;
if (webApp?.slug && !isPaymentModalOpen && paymentStatus) {
sendEvent({
eventType: 'invoice_closed',
eventData: {
slug: webApp.slug,
status: paymentStatus,
},
});
openInvoice({
slug: eventData.slug,
setWebAppPaymentSlug({
slug: undefined,
});
}
}, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp]);
const handleToggleClick = useLastCallback(() => {
toggleAttachBot({
botId: bot!.id,
isEnabled: !attachBot,
});
});
const handleBackClick = useLastCallback(() => {
if (isBackButtonVisible) {
sendEvent({
eventType: 'back_button_pressed',
});
} else {
handleClose();
}
});
const handleRejectPhone = useLastCallback(() => {
setIsRequestingPhone(false);
handlePopupClosed();
sendEvent({
eventType: 'phone_requested',
eventData: {
status: 'cancelled',
},
});
});
const handleAcceptPhone = useLastCallback(() => {
sharePhoneWithBot({ botId: bot!.id });
setIsRequestingPhone(false);
handlePopupClosed();
sendEvent({
eventType: 'phone_requested',
eventData: {
status: 'sent',
},
});
});
const handleRejectWriteAccess = useLastCallback(() => {
sendEvent({
eventType: 'write_access_requested',
eventData: {
status: 'cancelled',
},
});
setIsRequestingWriteAccess(false);
handlePopupClosed();
});
const handleAcceptWriteAccess = useLastCallback(async () => {
const result = await callApi('allowBotSendMessages', { bot: bot! });
if (!result) {
handleRejectWriteAccess();
return;
}
sendEvent({
eventType: 'write_access_requested',
eventData: {
status: 'allowed',
},
});
setIsRequestingWriteAccess(false);
handlePopupClosed();
});
async function handleRequestWriteAccess() {
const canWrite = await callApi('fetchBotCanSendMessage', {
bot: bot!,
});
if (canWrite) {
sendEvent({
eventType: 'write_access_requested',
eventData: {
status: 'allowed',
},
});
}
setIsRequestingWriteAccess(!canWrite);
}
async function handleInvokeCustomMethod(requestId: string, method: string, parameters: string) {
const result = await callApi('invokeWebViewCustomMethod', {
bot: bot!,
customMethod: method,
parameters,
});
sendEvent({
eventType: 'custom_method_invoked',
eventData: {
req_id: requestId,
...result,
},
});
}
const openBotChat = useLastCallback(() => {
openChat({
id: bot!.id,
});
closeWebApp();
});
useEffect(() => {
if (!isOpen) {
const themeParams = extractCurrentThemeParams();
setShouldConfirmClosing(false);
hideCloseModal();
setPopupParameters(undefined);
setIsRequestingPhone(false);
setIsRequestingWriteAccess(false);
setMainButton(undefined);
setIsBackButtonVisible(false);
setBackgroundColor(themeParams.bg_color);
setHeaderColor(themeParams.bg_color);
markUnloaded();
}
}, [hideCloseModal, isOpen, markUnloaded]);
function handleEvent(event: WebAppInboundEvent) {
const { eventType, eventData } = event;
if (eventType === 'web_app_open_tg_link' && !isPaymentModalOpen) {
const linkUrl = TME_LINK_PREFIX + eventData.path_full;
openTelegramLink({ url: linkUrl });
closeWebApp();
}
if (eventType === 'web_app_open_link') {
const linkUrl = eventData.url;
window.open(linkUrl, '_blank', 'noreferrer');
}
if (eventType === 'web_app_setup_back_button') {
setIsBackButtonVisible(eventData.is_visible);
}
@ -193,18 +397,19 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
}
if (eventType === 'web_app_setup_closing_behavior') {
setConfirmClose(eventData.need_confirmation);
setShouldConfirmClosing(eventData.need_confirmation);
}
if (eventType === 'web_app_open_popup') {
if (!eventData.message.trim().length || !eventData.buttons?.length || eventData.buttons.length > 3) return;
setPopupParams(eventData);
}
if (popupParameters || !eventData.message.trim().length || !eventData.buttons?.length
|| eventData.buttons.length > 3 || isRequestingPhone || isRequesingWriteAccess
|| unlockPopupsAt > Date.now()) {
handleAppPopupClose(undefined);
return;
}
if (eventType === 'web_app_open_scan_qr_popup') {
showNotification({
message: 'Scan QR code is not supported in this client yet',
});
setPopupParameters(eventData);
handlePopupOpened();
}
if (eventType === 'web_app_switch_inline_query') {
@ -220,127 +425,32 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
closeWebApp();
}
}, [
bot, buttonText, closeWebApp, openInvoice, openTelegramLink, sendWebViewData, setWebAppPaymentSlug,
isPaymentModalOpen, showNotification,
]);
const {
reloadFrame, sendEvent, sendViewport, sendTheme,
} = useWebAppFrame(frameRef, isOpen, isSimple, handleEvent, markLoaded);
if (eventType === 'web_app_request_phone') {
if (popupParameters || isRequesingWriteAccess || unlockPopupsAt > Date.now()) {
handleRejectPhone();
return;
}
const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0;
useInterval(() => {
prolongWebView({
botId: bot!.id,
queryId: queryId!,
peerId: chat!.id,
replyToMessageId,
threadId,
});
}, queryId ? PROLONG_INTERVAL : undefined, true);
const handleMainButtonClick = useCallback(() => {
sendEvent({
eventType: 'main_button_pressed',
});
}, [sendEvent]);
const handleSettingsButtonClick = useCallback(() => {
sendEvent({
eventType: 'settings_button_pressed',
});
}, [sendEvent]);
const handleRefreshClick = useCallback(() => {
reloadFrame(webApp!.url);
}, [reloadFrame, webApp]);
const handleClose = useCallback(() => {
if (confirmClose) {
openCloseModal();
} else {
closeWebApp();
setIsRequestingPhone(true);
handlePopupOpened();
}
}, [confirmClose, openCloseModal, closeWebApp]);
const handlePopupClose = useCallback((buttonId?: string) => {
setPopupParams(undefined);
sendEvent({
eventType: 'popup_closed',
eventData: {
button_id: buttonId,
},
});
}, [sendEvent]);
if (eventType === 'web_app_request_write_access') {
if (popupParameters || isRequestingPhone || unlockPopupsAt > Date.now()) {
handleRejectWriteAccess();
return;
}
const handlePopupModalClose = useCallback(() => {
handlePopupClose();
}, [handlePopupClose]);
// Notify view that height changed
useSyncEffect(() => {
setTimeout(() => {
sendViewport();
}, ANIMATION_WAIT);
}, [mainButton?.isVisible, sendViewport]);
// Notify view that theme changed
useSyncEffect(() => {
setTimeout(() => {
sendTheme();
}, ANIMATION_WAIT);
}, [theme, sendTheme]);
useSyncEffect(([prevIsPaymentModalOpen]) => {
if (isPaymentModalOpen === prevIsPaymentModalOpen) return;
if (webApp?.slug && !isPaymentModalOpen && paymentStatus) {
sendEvent({
eventType: 'invoice_closed',
eventData: {
slug: webApp.slug,
status: paymentStatus,
},
});
setWebAppPaymentSlug({
slug: undefined,
});
handleRequestWriteAccess();
handlePopupOpened();
}
}, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp]);
const handleToggleClick = useCallback(() => {
toggleAttachBot({
botId: bot!.id,
isEnabled: !attachBot,
});
}, [bot, attachBot, toggleAttachBot]);
const handleBackClick = useCallback(() => {
if (isBackButtonVisible) {
sendEvent({
eventType: 'back_button_pressed',
});
} else {
handleClose();
if (eventType === 'web_app_invoke_custom_method') {
const { method, params, req_id: requestId } = eventData;
handleInvokeCustomMethod(requestId, method, JSON.stringify(params));
}
}, [handleClose, isBackButtonVisible, sendEvent]);
const openBotChat = useCallback(() => {
openChat({
id: bot!.id,
});
closeWebApp();
}, [bot, closeWebApp, openChat]);
useEffect(() => {
if (!isOpen) {
setConfirmClose(false);
closeModal();
setPopupParams(undefined);
markUnloaded();
}
}, [closeModal, isOpen, markUnloaded]);
}
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
return ({ onTrigger, isOpen: isMenuOpen }) => (
@ -414,16 +524,6 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
const mainButtonCurrentIsActive = mainButton?.isActive !== undefined ? mainButton.isActive : prevMainButtonIsActive;
const mainButtonCurrentText = mainButton?.text || prevMainButtonText;
useEffect(() => {
if (!isOpen) {
const themeParams = extractCurrentThemeParams();
setMainButton(undefined);
setIsBackButtonVisible(false);
setBackgroundColor(themeParams.bg_color);
setHeaderColor(themeParams.bg_color);
}
}, [isOpen]);
const [shouldDecreaseWebFrameSize, setShouldDecreaseWebFrameSize] = useState(false);
const [shouldHideButton, setShouldHideButton] = useState(true);
@ -479,30 +579,35 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
</Button>
</>
)}
{confirmClose && (
<ConfirmDialog
isOpen={isCloseModalOpen}
onClose={closeModal}
title={lang('lng_bot_close_warning_title')}
text={lang('lng_bot_close_warning')}
confirmHandler={closeWebApp}
confirmIsDestructive
confirmLabel={lang('lng_bot_close_warning_sure')}
/>
)}
{renderingPopupParams && (
<ConfirmDialog
isOpen={isRequestingPhone}
onClose={handleRejectPhone}
title={lang('ShareYouPhoneNumberTitle')}
text={lang('AreYouSureShareMyContactInfoBot')}
confirmHandler={handleAcceptPhone}
confirmLabel={lang('ContactShare')}
/>
<ConfirmDialog
isOpen={isRequesingWriteAccess}
onClose={handleRejectWriteAccess}
title={lang('lng_bot_allow_write_title')}
text={lang('lng_bot_allow_write')}
confirmHandler={handleAcceptWriteAccess}
confirmLabel={lang('lng_bot_allow_write_confirm')}
/>
{popupParameters && (
<Modal
isOpen={Boolean(popupParams)}
title={renderingPopupParams.title || NBSP}
onClose={handlePopupModalClose}
isOpen={Boolean(popupParameters)}
title={popupParameters.title || NBSP}
onClose={handleAppPopupModalClose}
hasCloseButton
className={
buildClassName(styles.webAppPopup, !renderingPopupParams.title?.trim().length && styles.withoutTitle)
buildClassName(styles.webAppPopup, !popupParameters.title?.trim().length && styles.withoutTitle)
}
>
{renderingPopupParams.message}
{popupParameters.message}
<div className="dialog-buttons mt-2">
{renderingPopupParams.buttons.map((button) => (
{popupParameters.buttons.map((button) => (
<Button
key={button.id || button.type}
className="confirm-dialog-button"
@ -510,7 +615,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
isText
size="smaller"
// eslint-disable-next-line react/jsx-no-bind
onClick={() => handlePopupClose(button.id)}
onClick={() => handleAppPopupClose(button.id)}
>
{button.text || lang(DEFAULT_BUTTON_TEXT[button.type])}
</Button>
@ -518,6 +623,16 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
</div>
</Modal>
)}
<ConfirmDialog
isOpen={isCloseModalOpen}
onClose={hideCloseModal}
title={lang('lng_bot_close_warning_title')}
text={lang('lng_bot_close_warning')}
confirmHandler={closeWebApp}
confirmIsDestructive
confirmLabel={lang('lng_bot_close_warning_sure')}
/>
</Modal>
);
};

View File

@ -0,0 +1,34 @@
import { useRef, useState } from '../../../../lib/teact/teact';
import useLastCallback from '../../../../hooks/useLastCallback';
export default function usePopupLimit(sequentialLimit: number, resetAfter: number) {
const [unlockPopupsAt, setUnlockPopupsAt] = useState(0);
const sequentialCalls = useRef(0);
const lastClosedDate = useRef(0);
const handlePopupOpened = useLastCallback(() => {
const now = Date.now();
if (now - lastClosedDate.current > resetAfter) {
sequentialCalls.current = 0;
}
sequentialCalls.current += 1;
if (sequentialCalls.current >= sequentialLimit) {
setUnlockPopupsAt(now + resetAfter);
}
});
const handlePopupClosed = useLastCallback(() => {
if (unlockPopupsAt < Date.now()) { // Prevent confused user from extending lock time
lastClosedDate.current = Date.now();
}
});
return {
unlockPopupsAt,
handlePopupOpened,
handlePopupClosed,
};
}

View File

@ -1,155 +1,11 @@
import { useCallback, useEffect, useRef } from '../../../lib/teact/teact';
import { getActions } from '../../../lib/teact/teactn';
import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useWindowSize from '../../../hooks/useWindowSize';
import { useCallback, useEffect, useRef } from '../../../../lib/teact/teact';
import { getActions } from '../../../../global';
export type PopupOptions = {
title: string;
message: string;
buttons: {
id: string;
type: 'default' | 'ok' | 'close' | 'cancel' | 'destructive';
text: string;
}[];
};
import type { WebAppInboundEvent, WebAppOutboundEvent } from '../../../../types/webapp';
export type WebAppInboundEvent = {
eventType: 'web_app_data_send';
eventData: {
data: string;
};
} | {
eventType: 'web_app_setup_main_button';
eventData: {
is_visible: boolean;
is_active: boolean;
text: string;
color: string;
text_color: string;
is_progress_visible: boolean;
};
} | {
eventType: 'web_app_setup_back_button';
eventData: {
is_visible: boolean;
};
} | {
eventType: 'web_app_open_link';
eventData: {
url: string;
try_instant_view?: boolean;
};
} | {
eventType: 'web_app_open_tg_link';
eventData: {
path_full: string;
};
} | {
eventType: 'web_app_open_invoice';
eventData: {
slug: string;
};
} | {
eventType: 'web_app_trigger_haptic_feedback';
eventData: {
type: 'impact' | 'notification' | 'selection_change';
impact_style?: 'light' | 'medium' | 'heavy';
notification_type?: 'error' | 'success' | 'warning';
};
} | {
eventType: 'web_app_set_background_color';
eventData: {
color: string;
};
} | {
eventType: 'web_app_set_header_color';
eventData: {
color_key: 'bg_color' | 'secondary_bg_color';
};
} | {
eventType: 'web_app_open_popup';
eventData: PopupOptions;
} | {
eventType: 'web_app_setup_closing_behavior';
eventData: {
need_confirmation: boolean;
};
} | {
eventType: 'web_app_open_scan_qr_popup';
eventData: {
text?: string;
};
} | {
eventType: 'web_app_read_text_from_clipboard';
eventData: {
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'
| 'web_app_request_phone' | 'web_app_close' | 'iframe_ready' | 'web_app_close_scan_qr_popup';
eventData: null;
};
import { extractCurrentThemeParams } from '../../../../util/themeStyle';
type WebAppOutboundEvent = {
eventType: 'viewport_changed';
eventData: {
height: number;
width?: number;
is_expanded?: boolean;
is_state_stable?: boolean;
};
} | {
eventType: 'theme_changed';
eventData: {
theme_params: {
bg_color: string;
text_color: string;
hint_color: string;
link_color: string;
button_color: string;
button_text_color: string;
secondary_bg_color: string;
};
};
} | {
eventType: 'set_custom_style';
eventData: string;
} | {
eventType: 'invoice_closed';
eventData: {
slug: string;
status: 'paid' | 'cancelled' | 'pending' | 'failed';
};
} | {
eventType: 'phone_requested';
eventData: {
phone_number: string;
};
} | {
eventType: 'popup_closed';
eventData: {
button_id?: string;
};
} | {
eventType: 'qr_text_received';
eventData: {
data: string;
};
} | {
eventType: 'clipboard_text_received';
eventData: {
req_id: string;
data: string | null;
};
} | {
eventType: 'main_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed';
};
import useWindowSize from '../../../../hooks/useWindowSize';
const SCROLLBAR_STYLE = `* {
scrollbar-width: thin;
@ -180,6 +36,9 @@ const useWebAppFrame = (
) => {
const {
showNotification,
setWebAppPaymentSlug,
openInvoice,
closeWebApp,
} = getActions();
const ignoreEventsRef = useRef<boolean>(false);
@ -253,34 +112,40 @@ const useWebAppFrame = (
try {
const data = JSON.parse(event.data) as WebAppInboundEvent;
const { eventType, eventData } = data;
// Handle some app requests here to simplify hook usage
if (data.eventType === 'web_app_ready') {
if (eventType === 'web_app_ready') {
onLoad?.();
}
if (data.eventType === 'web_app_request_viewport') {
if (eventType === 'web_app_close') {
closeWebApp();
}
if (eventType === 'web_app_request_viewport') {
sendViewport(windowSize.isResizing);
}
if (data.eventType === 'web_app_request_theme') {
if (eventType === 'web_app_request_theme') {
sendTheme();
}
if (data.eventType === 'iframe_ready') {
if (eventType === 'iframe_ready') {
const scrollbarColor = getComputedStyle(document.body).getPropertyValue('--color-scrollbar');
sendCustomStyle(SCROLLBAR_STYLE.replace(/%SCROLLBAR_COLOR%/g, scrollbarColor));
}
if (data.eventType === 'web_app_data_send') {
if (eventType === 'web_app_data_send') {
if (!isSimpleView) return; // Allowed only in simple view
ignoreEventsRef.current = true;
}
if (data.eventType === 'web_app_read_text_from_clipboard') {
// Clipboard access temporarily disabled to address security concerns
if (eventType === 'web_app_read_text_from_clipboard') {
sendEvent({
eventType: 'clipboard_text_received',
eventData: {
req_id: data.eventData.req_id,
req_id: eventData.req_id,
// eslint-disable-next-line no-null/no-null
data: null,
},
@ -290,6 +155,27 @@ const useWebAppFrame = (
message: 'Clipboard access is not supported in this client yet',
});
}
if (eventType === 'web_app_open_scan_qr_popup') {
showNotification({
message: 'Scanning QR code is not supported in this client yet',
});
}
if (eventType === 'web_app_open_invoice') {
setWebAppPaymentSlug({
slug: eventData.slug,
});
openInvoice({
slug: eventData.slug,
});
}
if (eventType === 'web_app_open_link') {
const linkUrl = eventData.url;
window.open(linkUrl, '_blank', 'noreferrer');
}
onEvent(data);
} catch (err) {
// Ignore other messages

View File

@ -49,7 +49,7 @@ export const CUSTOM_EMOJI_PREVIEW_CACHE_DISABLED = false;
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
export const LANG_CACHE_NAME = 'tt-lang-packs-v21';
export const LANG_CACHE_NAME = 'tt-lang-packs-v22';
export const ASSET_CACHE_NAME = 'tt-assets';
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';

View File

@ -417,6 +417,40 @@ addActionHandler('startBot', async (global, actions, payload): Promise<void> =>
});
});
addActionHandler('sharePhoneWithBot', async (global, actions, payload): Promise<void> => {
const { botId } = payload;
const bot = selectUser(global, botId);
if (!bot) {
return;
}
let fullInfo = selectUserFullInfo(global, botId);
if (!fullInfo) {
const result = await callApi('fetchFullUser', { id: bot.id, accessHash: bot.accessHash });
fullInfo = result?.fullInfo;
}
if (fullInfo?.isBlocked) {
await callApi('unblockUser', { user: bot });
}
global = getGlobal();
const chat = selectChat(global, botId);
const currentUser = selectUser(global, global.currentUserId!)!;
if (!chat) return;
await callApi('sendMessage', {
chat,
contact: {
firstName: currentUser.firstName || '',
lastName: currentUser.lastName || '',
phoneNumber: currentUser.phoneNumber || '',
userId: currentUser.id,
},
});
});
addActionHandler('requestSimpleWebView', async (global, actions, payload): Promise<void> => {
const {
url, botId, theme, buttonText,

View File

@ -529,6 +529,7 @@ export type TabState = {
slug?: string;
replyToMessageId?: number;
threadId?: number;
canSendMessages?: boolean;
};
botTrustRequest?: {
@ -2304,6 +2305,9 @@ export interface ActionPayloads {
restartBot: {
chatId: string;
} & WithTabId;
sharePhoneWithBot: {
botId: string;
};
clickBotInlineButton: {
messageId: number;

View File

@ -1,6 +1,6 @@
const api = require('./api');
const LAYER = 161;
const LAYER = 162;
const tlobjects = {};
for (const tl of Object.values(api)) {

View File

@ -1775,11 +1775,13 @@ namespace Api {
export class MessageActionBotAllowed extends VirtualClass<{
// flags: undefined;
attachMenu?: true;
fromRequest?: true;
domain?: string;
app?: Api.TypeBotApp;
} | void> {
// flags: undefined;
attachMenu?: true;
fromRequest?: true;
domain?: string;
app?: Api.TypeBotApp;
};
@ -4092,6 +4094,9 @@ namespace Api {
public?: true;
megagroup?: true;
requestNeeded?: true;
verified?: true;
scam?: true;
fake?: true;
title: string;
about?: string;
photo: Api.TypePhoto;
@ -4104,6 +4109,9 @@ namespace Api {
public?: true;
megagroup?: true;
requestNeeded?: true;
verified?: true;
scam?: true;
fake?: true;
title: string;
about?: string;
photo: Api.TypePhoto;
@ -8311,11 +8319,13 @@ namespace Api {
};
export class StoryViews extends VirtualClass<{
// flags: undefined;
hasViewers?: true;
viewsCount: int;
reactionsCount: int;
recentViewers?: long[];
}> {
// flags: undefined;
hasViewers?: true;
viewsCount: int;
reactionsCount: int;
recentViewers?: long[];
@ -14410,6 +14420,25 @@ namespace Api {
username: string;
active: Bool;
};
export class CanSendMessage extends Request<Partial<{
bot: Api.TypeInputUser;
}>, Bool> {
bot: Api.TypeInputUser;
};
export class AllowSendMessage extends Request<Partial<{
bot: Api.TypeInputUser;
}>, Api.TypeUpdates> {
bot: Api.TypeInputUser;
};
export class InvokeWebViewCustomMethod extends Request<Partial<{
bot: Api.TypeInputUser;
customMethod: string;
params: Api.TypeDataJSON;
}>, Api.TypeDataJSON> {
bot: Api.TypeInputUser;
customMethod: string;
params: Api.TypeDataJSON;
};
}
export namespace payments {
@ -15058,6 +15087,7 @@ namespace Api {
}
export namespace stories {
export class CanSendStory extends Request<void, Bool> {};
export class SendStory extends Request<Partial<{
// flags: undefined;
pinned?: true;
@ -15244,7 +15274,7 @@ namespace Api {
| upload.SaveFilePart | upload.GetFile | upload.SaveBigFilePart | upload.GetWebFile | upload.GetCdnFile | upload.ReuploadCdnFile | upload.GetCdnFileHashes | upload.GetFileHashes
| help.GetConfig | help.GetNearestDc | help.GetAppUpdate | help.GetInviteText | help.GetSupport | help.GetAppChangelog | help.SetBotUpdatesStatus | help.GetCdnConfig | help.GetRecentMeUrls | help.GetTermsOfServiceUpdate | help.AcceptTermsOfService | help.GetDeepLinkInfo | help.GetAppConfig | help.SaveAppLog | help.GetPassportConfig | help.GetSupportName | help.GetUserInfo | help.EditUserInfo | help.GetPromoData | help.HidePromoData | help.DismissSuggestion | help.GetCountriesList | help.GetPremiumPromo
| channels.ReadHistory | channels.DeleteMessages | channels.ReportSpam | channels.GetMessages | channels.GetParticipants | channels.GetParticipant | channels.GetChannels | channels.GetFullChannel | channels.CreateChannel | channels.EditAdmin | channels.EditTitle | channels.EditPhoto | channels.CheckUsername | channels.UpdateUsername | channels.JoinChannel | channels.LeaveChannel | channels.InviteToChannel | channels.DeleteChannel | channels.ExportMessageLink | channels.ToggleSignatures | channels.GetAdminedPublicChannels | channels.EditBanned | channels.GetAdminLog | channels.SetStickers | channels.ReadMessageContents | channels.DeleteHistory | channels.TogglePreHistoryHidden | channels.GetLeftChannels | channels.GetGroupsForDiscussion | channels.SetDiscussionGroup | channels.EditCreator | channels.EditLocation | channels.ToggleSlowMode | channels.GetInactiveChannels | channels.ConvertToGigagroup | channels.ViewSponsoredMessage | channels.GetSponsoredMessages | channels.GetSendAs | channels.DeleteParticipantHistory | channels.ToggleJoinToSend | channels.ToggleJoinRequest | channels.ReorderUsernames | channels.ToggleUsername | channels.DeactivateAllUsernames | channels.ToggleForum | channels.CreateForumTopic | channels.GetForumTopics | channels.GetForumTopicsByID | channels.EditForumTopic | channels.UpdatePinnedForumTopic | channels.DeleteTopicHistory | channels.ReorderPinnedForumTopics | channels.ToggleAntiSpam | channels.ReportAntiSpamFalsePositive | channels.ToggleParticipantsHidden | channels.ClickSponsoredMessage
| bots.SendCustomRequest | bots.AnswerWebhookJSONQuery | bots.SetBotCommands | bots.ResetBotCommands | bots.GetBotCommands | bots.SetBotMenuButton | bots.GetBotMenuButton | bots.SetBotBroadcastDefaultAdminRights | bots.SetBotGroupDefaultAdminRights | bots.SetBotInfo | bots.GetBotInfo | bots.ReorderUsernames | bots.ToggleUsername
| bots.SendCustomRequest | bots.AnswerWebhookJSONQuery | bots.SetBotCommands | bots.ResetBotCommands | bots.GetBotCommands | bots.SetBotMenuButton | bots.GetBotMenuButton | bots.SetBotBroadcastDefaultAdminRights | bots.SetBotGroupDefaultAdminRights | bots.SetBotInfo | bots.GetBotInfo | bots.ReorderUsernames | bots.ToggleUsername | bots.CanSendMessage | bots.AllowSendMessage | bots.InvokeWebViewCustomMethod
| payments.GetPaymentForm | payments.GetPaymentReceipt | payments.ValidateRequestedInfo | payments.SendPaymentForm | payments.GetSavedInfo | payments.ClearSavedInfo | payments.GetBankCardData | payments.ExportInvoice | payments.AssignAppStoreTransaction | payments.AssignPlayMarketTransaction | payments.CanPurchasePremium
| stickers.CreateStickerSet | stickers.RemoveStickerFromSet | stickers.ChangeStickerPosition | stickers.AddStickerToSet | stickers.SetStickerSetThumb | stickers.CheckShortName | stickers.SuggestShortName | stickers.ChangeSticker | stickers.RenameStickerSet | stickers.DeleteStickerSet
| phone.GetCallConfig | phone.RequestCall | phone.AcceptCall | phone.ConfirmCall | phone.ReceivedCall | phone.DiscardCall | phone.SetCallRating | phone.SaveCallDebug | phone.SendSignalingData | phone.CreateGroupCall | phone.JoinGroupCall | phone.LeaveGroupCall | phone.InviteToGroupCall | phone.DiscardGroupCall | phone.ToggleGroupCallSettings | phone.GetGroupCall | phone.GetGroupParticipants | phone.CheckGroupCall | phone.ToggleGroupCallRecord | phone.EditGroupCallParticipant | phone.EditGroupCallTitle | phone.GetGroupCallJoinAs | phone.ExportGroupCallInvite | phone.ToggleGroupCallStartSubscription | phone.StartScheduledGroupCall | phone.SaveDefaultGroupCallJoinAs | phone.JoinGroupCallPresentation | phone.LeaveGroupCallPresentation | phone.GetGroupCallStreamChannels | phone.GetGroupCallStreamRtmpUrl | phone.SaveCallLog
@ -15252,6 +15282,6 @@ namespace Api {
| folders.EditPeerFolders
| stats.GetBroadcastStats | stats.LoadAsyncGraph | stats.GetMegagroupStats | stats.GetMessagePublicForwards | stats.GetMessageStats
| chatlists.ExportChatlistInvite | chatlists.DeleteExportedInvite | chatlists.EditExportedInvite | chatlists.GetExportedInvites | chatlists.CheckChatlistInvite | chatlists.JoinChatlistInvite | chatlists.GetChatlistUpdates | chatlists.JoinChatlistUpdates | chatlists.HideChatlistUpdates | chatlists.GetLeaveChatlistSuggestions | chatlists.LeaveChatlist
| stories.SendStory | stories.EditStory | stories.DeleteStories | stories.TogglePinned | stories.GetAllStories | stories.GetUserStories | stories.GetPinnedStories | stories.GetStoriesArchive | stories.GetStoriesByID | stories.ToggleAllStoriesHidden | stories.GetAllReadUserStories | stories.ReadStories | stories.IncrementStoryViews | stories.GetStoryViewsList | stories.GetStoriesViews | stories.ExportStoryLink | stories.Report | stories.ActivateStealthMode | stories.SendReaction;
| stories.CanSendStory | stories.SendStory | stories.EditStory | stories.DeleteStories | stories.TogglePinned | stories.GetAllStories | stories.GetUserStories | stories.GetPinnedStories | stories.GetStoriesArchive | stories.GetStoriesByID | stories.ToggleAllStoriesHidden | stories.GetAllReadUserStories | stories.ReadStories | stories.IncrementStoryViews | stories.GetStoryViewsList | stories.GetStoriesViews | stories.ExportStoryLink | stories.Report | stories.ActivateStealthMode | stories.SendReaction;
}

View File

@ -124,7 +124,7 @@ messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_
messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction;
messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction;
messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true from_request:flags.3?true domain:flags.0?string app:flags.2?BotApp = MessageAction;
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
messageActionContactSignUp#f3f25f76 = MessageAction;
@ -472,7 +472,7 @@ receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage;
chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite;
chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite;
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet;
@ -1126,7 +1126,7 @@ messagePeerVote#b6cc2d5c peer:Peer option:bytes date:int = MessagePeerVote;
messagePeerVoteInputOption#74cda504 peer:Peer date:int = MessagePeerVote;
messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector<bytes> date:int = MessagePeerVote;
sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage;
storyViews#c64c0b97 flags:# views_count:int reactions_count:int recent_viewers:flags.0?Vector<long> = StoryViews;
storyViews#c64c0b97 flags:# has_viewers:flags.1?true views_count:int reactions_count:int recent_viewers:flags.0?Vector<long> = StoryViews;
storyItemDeleted#51e6ee4f id:int = StoryItem;
storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem;
storyItem#44c457ce flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true id:int date:int expire_date:int caption:flags.0?string entities:flags.1?Vector<MessageEntity> media:MessageMedia media_areas:flags.14?Vector<MediaArea> privacy:flags.2?Vector<PrivacyRule> views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem;
@ -1403,6 +1403,9 @@ channels.editForumTopic#f4dfa185 flags:# channel:InputChannel topic_id:int title
channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates;
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;

View File

@ -216,6 +216,9 @@
"channels.toggleUsername",
"channels.viewSponsoredMessage",
"channels.getSponsoredMessages",
"bots.canSendMessage",
"bots.allowSendMessage",
"bots.invokeWebViewCustomMethod",
"payments.getPaymentForm",
"payments.getPaymentReceipt",
"payments.validateRequestedInfo",

View File

@ -150,7 +150,7 @@ messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_
messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction;
messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction;
messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction;
messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true from_request:flags.3?true domain:flags.0?string app:flags.2?BotApp = MessageAction;
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
messageActionContactSignUp#f3f25f76 = MessageAction;
@ -570,7 +570,7 @@ chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true r
chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite;
chatInviteAlready#5a686d7c chat:Chat = ChatInvite;
chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true verified:flags.7?true scam:flags.8?true fake:flags.9?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector<User> = ChatInvite;
chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite;
inputStickerSetEmpty#ffb62b95 = InputStickerSet;
@ -1528,7 +1528,7 @@ messagePeerVoteMultiple#4628f6e6 peer:Peer options:Vector<bytes> date:int = Mess
sponsoredWebPage#3db8ec63 flags:# url:string site_name:string photo:flags.0?Photo = SponsoredWebPage;
storyViews#c64c0b97 flags:# views_count:int reactions_count:int recent_viewers:flags.0?Vector<long> = StoryViews;
storyViews#c64c0b97 flags:# has_viewers:flags.1?true views_count:int reactions_count:int recent_viewers:flags.0?Vector<long> = StoryViews;
storyItemDeleted#51e6ee4f id:int = StoryItem;
storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem;
@ -2017,6 +2017,9 @@ bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:fla
bots.getBotInfo#dcd914fd flags:# bot:flags.0?InputUser lang_code:string = bots.BotInfo;
bots.reorderUsernames#9709b1c2 bot:InputUser order:Vector<string> = Bool;
bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool;
bots.canSendMessage#1359f4e6 bot:InputUser = Bool;
bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
@ -2099,6 +2102,7 @@ chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool;
chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector<Peer>;
chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector<InputPeer> = Updates;
stories.canSendStory#b100d45d = Bool;
stories.sendStory#d455fcec flags:# pinned:flags.2?true noforwards:flags.4?true media:InputMedia media_areas:flags.5?Vector<MediaArea> caption:flags.0?string entities:flags.1?Vector<MessageEntity> privacy_rules:Vector<InputPrivacyRule> random_id:long period:flags.3?int = Updates;
stories.editStory#a9b91ae4 flags:# id:int media:flags.0?InputMedia media_areas:flags.3?Vector<MediaArea> caption:flags.1?string entities:flags.1?Vector<MessageEntity> privacy_rules:flags.2?Vector<InputPrivacyRule> = Updates;
stories.deleteStories#b5d501d7 id:Vector<int> = Vector<int>;
@ -2117,4 +2121,4 @@ stories.getStoriesViews#9a75d6a6 id:Vector<int> = stories.StoryViews;
stories.exportStoryLink#16e443ce user_id:InputUser id:int = ExportedStoryLink;
stories.report#c95be06a user_id:InputUser id:Vector<int> reason:ReportReason message:string = Bool;
stories.activateStealthMode#57bbd166 flags:# past:flags.0?true future:flags.1?true = Updates;
stories.sendReaction#49aaa9b3 flags:# add_to_recent:flags.0?true user_id:InputUser story_id:int reaction:Reaction = Updates;
stories.sendReaction#49aaa9b3 flags:# add_to_recent:flags.0?true user_id:InputUser story_id:int reaction:Reaction = Updates;

174
src/types/webapp.ts Normal file
View File

@ -0,0 +1,174 @@
export type PopupOptions = {
title: string;
message: string;
buttons: {
id: string;
type: 'default' | 'ok' | 'close' | 'cancel' | 'destructive';
text: string;
}[];
};
export type WebAppInboundEvent = {
eventType: 'web_app_data_send';
eventData: {
data: string;
};
} | {
eventType: 'web_app_setup_main_button';
eventData: {
is_visible: boolean;
is_active: boolean;
text: string;
color: string;
text_color: string;
is_progress_visible: boolean;
};
} | {
eventType: 'web_app_setup_back_button';
eventData: {
is_visible: boolean;
};
} | {
eventType: 'web_app_open_link';
eventData: {
url: string;
try_instant_view?: boolean;
};
} | {
eventType: 'web_app_open_tg_link';
eventData: {
path_full: string;
};
} | {
eventType: 'web_app_open_invoice';
eventData: {
slug: string;
};
} | {
eventType: 'web_app_trigger_haptic_feedback';
eventData: {
type: 'impact' | 'notification' | 'selection_change';
impact_style?: 'light' | 'medium' | 'heavy';
notification_type?: 'error' | 'success' | 'warning';
};
} | {
eventType: 'web_app_set_background_color';
eventData: {
color: string;
};
} | {
eventType: 'web_app_set_header_color';
eventData: {
color_key: 'bg_color' | 'secondary_bg_color';
};
} | {
eventType: 'web_app_open_popup';
eventData: PopupOptions;
} | {
eventType: 'web_app_setup_closing_behavior';
eventData: {
need_confirmation: boolean;
};
} | {
eventType: 'web_app_open_scan_qr_popup';
eventData: {
text?: string;
};
} | {
eventType: 'web_app_read_text_from_clipboard';
eventData: {
req_id: string;
};
} | {
eventType: 'web_app_switch_inline_query';
eventData: {
query: string;
chat_types: ('users' | 'bots' | 'groups' | 'channels')[];
};
} | {
eventType: 'web_app_invoke_custom_method';
eventData: {
req_id: string;
method: string;
params: object;
};
} | {
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_write_access' | 'web_app_request_phone';
eventData: null;
};
export type WebAppOutboundEvent = {
eventType: 'viewport_changed';
eventData: {
height: number;
width?: number;
is_expanded?: boolean;
is_state_stable?: boolean;
};
} | {
eventType: 'theme_changed';
eventData: {
theme_params: {
bg_color: string;
text_color: string;
hint_color: string;
link_color: string;
button_color: string;
button_text_color: string;
secondary_bg_color: string;
};
};
} | {
eventType: 'set_custom_style';
eventData: string;
} | {
eventType: 'invoice_closed';
eventData: {
slug: string;
status: 'paid' | 'cancelled' | 'pending' | 'failed';
};
} | {
eventType: 'phone_requested';
eventData: {
phone_number: string;
};
} | {
eventType: 'popup_closed';
eventData: {
button_id?: string;
};
} | {
eventType: 'qr_text_received';
eventData: {
data: string;
};
} | {
eventType: 'clipboard_text_received';
eventData: {
req_id: string;
data: string | null;
};
} | {
eventType: 'write_access_requested';
eventData: {
status: 'allowed' | 'cancelled';
};
} | {
eventType: 'phone_requested';
eventData: {
status: 'sent' | 'cancelled';
};
} | {
eventType: 'custom_method_invoked';
eventData: {
req_id: string;
} & ({
result: object;
} | {
error: string;
});
} | {
eventType: 'main_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed';
};