Mini Apps: Implement suggesting file downloads (#5269)
This commit is contained in:
parent
f32847eb14
commit
f66cb55dac
@ -593,6 +593,24 @@ export async function fetchPreviewMedias({ bot } : { bot: ApiUser }) {
|
|||||||
return previews;
|
return previews;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function checkBotDownloadFileParams({
|
||||||
|
bot,
|
||||||
|
fileName,
|
||||||
|
url,
|
||||||
|
}: {
|
||||||
|
bot: ApiUser;
|
||||||
|
fileName: string;
|
||||||
|
url: string;
|
||||||
|
}) {
|
||||||
|
return invokeRequest(new GramJs.bots.CheckDownloadFileParams({
|
||||||
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
|
fileName,
|
||||||
|
url,
|
||||||
|
}), {
|
||||||
|
shouldReturnTrue: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
function processInlineBotResult(queryId: string, results: GramJs.TypeBotInlineResult[]) {
|
||||||
return results.map((result) => {
|
return results.map((result) => {
|
||||||
if (result instanceof GramJs.BotInlineMediaResult) {
|
if (result instanceof GramJs.BotInlineMediaResult) {
|
||||||
|
|||||||
@ -1393,6 +1393,9 @@
|
|||||||
"BotSuggestedStatus" = "Do you want to set this emoji status suggested by **{bot}**?";
|
"BotSuggestedStatus" = "Do you want to set this emoji status suggested by **{bot}**?";
|
||||||
"BotSuggestedStatusTitle" = "Set Emoji Status";
|
"BotSuggestedStatusTitle" = "Set Emoji Status";
|
||||||
"BotSuggestedStatusUpdated" = "Your emoji status is updated.";
|
"BotSuggestedStatusUpdated" = "Your emoji status is updated.";
|
||||||
|
"BotDownloadFileTitle" = "Download File";
|
||||||
|
"BotDownloadFileDescription" = "**{bot}** suggests you to download **{filename}**";
|
||||||
|
"BotDownloadFileButton" = "Download";
|
||||||
"PrivacyGifts" = "Gifts";
|
"PrivacyGifts" = "Gifts";
|
||||||
"PrivacyGiftsTitle" = "Who can display gifts on my profile?"
|
"PrivacyGiftsTitle" = "Who can display gifts on my profile?"
|
||||||
"PrivacyGiftsInfo" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile."
|
"PrivacyGiftsInfo" = "Choose whether gifts from specific senders need your approval before they're visible to others on your profile."
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import buildStyle from '../../../util/buildStyle';
|
import buildStyle from '../../../util/buildStyle';
|
||||||
|
import download from '../../../util/download';
|
||||||
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams, validateHexColor } from '../../../util/themeStyle';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
@ -27,6 +28,7 @@ import renderText from '../../common/helpers/renderText';
|
|||||||
import { getIsWebAppsFullscreenSupported } from '../../../hooks/useAppLayout';
|
import { getIsWebAppsFullscreenSupported } from '../../../hooks/useAppLayout';
|
||||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
@ -133,6 +135,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
const [popupParameters, setPopupParameters] = useState<PopupOptions | undefined>();
|
const [popupParameters, setPopupParameters] = useState<PopupOptions | undefined>();
|
||||||
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
|
const [isRequestingPhone, setIsRequestingPhone] = useState(false);
|
||||||
const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false);
|
const [isRequestingWriteAccess, setIsRequestingWriteAccess] = useState(false);
|
||||||
|
const [requestedFileDownload, setRequestedFileDownload] = useState<{ url: string; fileName: string } | undefined>();
|
||||||
const [bottomBarColor, setBottomBarColor] = useState<string | undefined>();
|
const [bottomBarColor, setBottomBarColor] = useState<string | undefined>();
|
||||||
const {
|
const {
|
||||||
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
|
unlockPopupsAt, handlePopupOpened, handlePopupClosed,
|
||||||
@ -190,7 +193,8 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
// 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);
|
||||||
|
|
||||||
const lang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
const isOpen = modal?.isModalOpen || false;
|
const isOpen = modal?.isModalOpen || false;
|
||||||
const isSimple = Boolean(buttonText);
|
const isSimple = Boolean(buttonText);
|
||||||
|
|
||||||
@ -359,6 +363,20 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleRejectFileDownload = useLastCallback((shouldCloseActive?: boolean) => {
|
||||||
|
if (shouldCloseActive) {
|
||||||
|
setRequestedFileDownload(undefined);
|
||||||
|
handlePopupClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'file_download_requested',
|
||||||
|
eventData: {
|
||||||
|
status: 'cancelled',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const handleRejectWriteAccess = useLastCallback(() => {
|
const handleRejectWriteAccess = useLastCallback(() => {
|
||||||
sendEvent({
|
sendEvent({
|
||||||
eventType: 'write_access_requested',
|
eventType: 'write_access_requested',
|
||||||
@ -404,6 +422,41 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
setIsRequestingWriteAccess(!canWrite);
|
setIsRequestingWriteAccess(!canWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleCheckDownloadFile(fileUrl: string, fileName: string) {
|
||||||
|
const canDownload = await callApi('checkBotDownloadFileParams', {
|
||||||
|
bot: bot!,
|
||||||
|
url: fileUrl,
|
||||||
|
fileName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!canDownload) {
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'file_download_requested',
|
||||||
|
eventData: {
|
||||||
|
status: 'cancelled',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRequestedFileDownload({ url: fileUrl, fileName });
|
||||||
|
handlePopupOpened();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownloadFile = useLastCallback(() => {
|
||||||
|
if (!requestedFileDownload) return;
|
||||||
|
setRequestedFileDownload(undefined);
|
||||||
|
handlePopupClosed();
|
||||||
|
|
||||||
|
download(requestedFileDownload.url, requestedFileDownload.fileName);
|
||||||
|
sendEvent({
|
||||||
|
eventType: 'file_download_requested',
|
||||||
|
eventData: {
|
||||||
|
status: 'downloading',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function handleInvokeCustomMethod(requestId: string, method: string, parameters: string) {
|
async function handleInvokeCustomMethod(requestId: string, method: string, parameters: string) {
|
||||||
const result = await callApi('invokeWebViewCustomMethod', {
|
const result = await callApi('invokeWebViewCustomMethod', {
|
||||||
bot: bot!,
|
bot: bot!,
|
||||||
@ -576,6 +629,14 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
const { method, params, req_id: requestId } = eventData;
|
const { method, params, req_id: requestId } = eventData;
|
||||||
handleInvokeCustomMethod(requestId, method, JSON.stringify(params));
|
handleInvokeCustomMethod(requestId, method, JSON.stringify(params));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_request_file_download') {
|
||||||
|
if (requestedFileDownload || unlockPopupsAt > Date.now()) {
|
||||||
|
handleRejectFileDownload();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleCheckDownloadFile(eventData.url, eventData.file_name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true);
|
const mainButtonCurrentColor = useCurrentOrPrev(mainButton?.color, true);
|
||||||
@ -739,7 +800,7 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
isBackButtonVisible && styles.stateBack,
|
isBackButtonVisible && styles.stateBack,
|
||||||
);
|
);
|
||||||
const backButtonCaption = shouldShowAppNameInFullscreen ? activeWebAppName
|
const backButtonCaption = shouldShowAppNameInFullscreen ? activeWebAppName
|
||||||
: lang(isBackButtonVisible ? 'Back' : 'Close');
|
: oldLang(isBackButtonVisible ? 'Back' : 'Close');
|
||||||
|
|
||||||
const hasHeaderElement = headerButtonCaptionRef?.current;
|
const hasHeaderElement = headerButtonCaptionRef?.current;
|
||||||
|
|
||||||
@ -895,22 +956,6 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
<ConfirmDialog
|
|
||||||
isOpen={isRequestingPhone}
|
|
||||||
onClose={handleRejectPhone}
|
|
||||||
title={lang('ShareYouPhoneNumberTitle')}
|
|
||||||
text={lang('AreYouSureShareMyContactInfoBot')}
|
|
||||||
confirmHandler={handleAcceptPhone}
|
|
||||||
confirmLabel={lang('ContactShare')}
|
|
||||||
/>
|
|
||||||
<ConfirmDialog
|
|
||||||
isOpen={isRequestingWriteAccess}
|
|
||||||
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 && (
|
{popupParameters && (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={Boolean(popupParameters)}
|
isOpen={Boolean(popupParameters)}
|
||||||
@ -933,27 +978,58 @@ const WebAppModalTabContent: FC<OwnProps & StateProps> = ({
|
|||||||
// eslint-disable-next-line react/jsx-no-bind
|
// eslint-disable-next-line react/jsx-no-bind
|
||||||
onClick={() => handleAppPopupClose(button.id)}
|
onClick={() => handleAppPopupClose(button.id)}
|
||||||
>
|
>
|
||||||
{button.text || lang(DEFAULT_BUTTON_TEXT[button.type])}
|
{button.text || oldLang(DEFAULT_BUTTON_TEXT[button.type])}
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isRequestingPhone}
|
||||||
|
onClose={handleRejectPhone}
|
||||||
|
title={oldLang('ShareYouPhoneNumberTitle')}
|
||||||
|
text={oldLang('AreYouSureShareMyContactInfoBot')}
|
||||||
|
confirmHandler={handleAcceptPhone}
|
||||||
|
confirmLabel={oldLang('ContactShare')}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={isRequestingWriteAccess}
|
||||||
|
onClose={handleRejectWriteAccess}
|
||||||
|
title={oldLang('lng_bot_allow_write_title')}
|
||||||
|
text={oldLang('lng_bot_allow_write')}
|
||||||
|
confirmHandler={handleAcceptWriteAccess}
|
||||||
|
confirmLabel={oldLang('lng_bot_allow_write_confirm')}
|
||||||
|
/>
|
||||||
|
<ConfirmDialog
|
||||||
|
isOpen={Boolean(requestedFileDownload)}
|
||||||
|
title={lang('BotDownloadFileTitle')}
|
||||||
|
textParts={lang('BotDownloadFileDescription', {
|
||||||
|
bot: bot?.firstName,
|
||||||
|
filename: requestedFileDownload?.fileName,
|
||||||
|
}, {
|
||||||
|
withNodes: true,
|
||||||
|
withMarkdown: true,
|
||||||
|
})}
|
||||||
|
confirmLabel={lang('BotDownloadFileButton')}
|
||||||
|
onClose={handleRejectFileDownload}
|
||||||
|
confirmHandler={handleDownloadFile}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isCloseModalOpen}
|
isOpen={isCloseModalOpen}
|
||||||
onClose={handleHideCloseModal}
|
onClose={handleHideCloseModal}
|
||||||
title={lang('lng_bot_close_warning_title')}
|
title={oldLang('lng_bot_close_warning_title')}
|
||||||
text={lang('lng_bot_close_warning')}
|
text={oldLang('lng_bot_close_warning')}
|
||||||
confirmHandler={handleConfirmCloseModal}
|
confirmHandler={handleConfirmCloseModal}
|
||||||
confirmIsDestructive
|
confirmIsDestructive
|
||||||
confirmLabel={lang('lng_bot_close_warning_sure')}
|
confirmLabel={oldLang('lng_bot_close_warning_sure')}
|
||||||
/>
|
/>
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isRemoveModalOpen}
|
isOpen={isRemoveModalOpen}
|
||||||
onClose={handleHideRemoveModal}
|
onClose={handleHideRemoveModal}
|
||||||
title={lang('BotRemoveFromMenuTitle')}
|
title={oldLang('BotRemoveFromMenuTitle')}
|
||||||
textParts={renderText(lang('BotRemoveFromMenu', bot?.firstName), ['simple_markdown'])}
|
textParts={renderText(oldLang('BotRemoveFromMenu', bot?.firstName), ['simple_markdown'])}
|
||||||
confirmHandler={handleRemoveAttachBot}
|
confirmHandler={handleRemoveAttachBot}
|
||||||
confirmIsDestructive
|
confirmIsDestructive
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1661,6 +1661,7 @@ bots.allowSendMessage#f132e3ef bot:InputUser = Updates;
|
|||||||
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
bots.invokeWebViewCustomMethod#87fc5e7 bot:InputUser custom_method:string params:DataJSON = DataJSON;
|
||||||
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
bots.getPopularAppBots#c2510192 offset:string limit:int = bots.PopularAppBots;
|
||||||
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
bots.getPreviewMedias#a2a5594d bot:InputUser = Vector<BotPreviewMedia>;
|
||||||
|
bots.checkDownloadFileParams#50077589 bot:InputUser file_name:string url:string = Bool;
|
||||||
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm;
|
||||||
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
|
||||||
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo;
|
||||||
|
|||||||
@ -276,6 +276,7 @@
|
|||||||
"bots.getPopularAppBots",
|
"bots.getPopularAppBots",
|
||||||
"bots.setBotInfo",
|
"bots.setBotInfo",
|
||||||
"bots.getPreviewMedias",
|
"bots.getPreviewMedias",
|
||||||
|
"bots.checkDownloadFileParams",
|
||||||
"payments.getPaymentForm",
|
"payments.getPaymentForm",
|
||||||
"payments.getPaymentReceipt",
|
"payments.getPaymentReceipt",
|
||||||
"payments.validateRequestedInfo",
|
"payments.validateRequestedInfo",
|
||||||
|
|||||||
6
src/types/language.d.ts
vendored
6
src/types/language.d.ts
vendored
@ -1159,6 +1159,8 @@ export interface LangPair {
|
|||||||
'VideoConversionView': undefined;
|
'VideoConversionView': undefined;
|
||||||
'BotSuggestedStatusTitle': undefined;
|
'BotSuggestedStatusTitle': undefined;
|
||||||
'BotSuggestedStatusUpdated': undefined;
|
'BotSuggestedStatusUpdated': undefined;
|
||||||
|
'BotDownloadFileTitle': undefined;
|
||||||
|
'BotDownloadFileButton': undefined;
|
||||||
'PrivacyGifts': undefined;
|
'PrivacyGifts': undefined;
|
||||||
'PrivacyGiftsTitle': undefined;
|
'PrivacyGiftsTitle': undefined;
|
||||||
'PrivacyGiftsInfo': undefined;
|
'PrivacyGiftsInfo': undefined;
|
||||||
@ -1563,6 +1565,10 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
|||||||
'BotSuggestedStatus': {
|
'BotSuggestedStatus': {
|
||||||
'bot': V;
|
'bot': V;
|
||||||
};
|
};
|
||||||
|
'BotDownloadFileDescription': {
|
||||||
|
'bot': V;
|
||||||
|
'filename': V;
|
||||||
|
};
|
||||||
'StarsSubscribeBotButtonMonth': {
|
'StarsSubscribeBotButtonMonth': {
|
||||||
'amount': V;
|
'amount': V;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -106,6 +106,10 @@ export type WebAppInboundEvent =
|
|||||||
custom_emoji_id: string;
|
custom_emoji_id: string;
|
||||||
duration?: number;
|
duration?: number;
|
||||||
}> |
|
}> |
|
||||||
|
WebAppEvent<'web_app_request_file_download', {
|
||||||
|
url: string;
|
||||||
|
file_name: string;
|
||||||
|
}> |
|
||||||
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
WebAppEvent<'web_app_request_viewport' | 'web_app_request_theme' | 'web_app_ready' | 'web_app_expand'
|
||||||
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
| 'web_app_request_phone' | 'web_app_close' | 'web_app_close_scan_qr_popup'
|
||||||
| 'web_app_request_write_access' | 'web_app_request_phone' | 'iframe_will_reload'
|
| 'web_app_request_write_access' | 'web_app_request_phone' | 'iframe_will_reload'
|
||||||
@ -194,6 +198,9 @@ export type WebAppOutboundEvent =
|
|||||||
error: 'UNSUPPORTED' | 'USER_DECLINED' | 'SUGGESTED_EMOJI_INVALID'
|
error: 'UNSUPPORTED' | 'USER_DECLINED' | 'SUGGESTED_EMOJI_INVALID'
|
||||||
| 'DURATION_INVALID' | 'SERVER_ERROR' | 'UNKNOWN_ERROR';
|
| 'DURATION_INVALID' | 'SERVER_ERROR' | 'UNKNOWN_ERROR';
|
||||||
}> |
|
}> |
|
||||||
|
WebAppEvent<'file_download_requested', {
|
||||||
|
status: 'cancelled' | 'downloading';
|
||||||
|
}> |
|
||||||
WebAppEvent<'main_button_pressed' |
|
WebAppEvent<'main_button_pressed' |
|
||||||
'secondary_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'
|
'secondary_button_pressed' | 'back_button_pressed' | 'settings_button_pressed' | 'scan_qr_popup_closed'
|
||||||
| 'reload_iframe' | 'emoji_status_set', null>;
|
| 'reload_iframe' | 'emoji_status_set', null>;
|
||||||
|
|||||||
@ -39,6 +39,8 @@ async function processQueue() {
|
|||||||
function downloadOne({ url, filename }: PendingDownload) {
|
function downloadOne({ url, filename }: PendingDownload) {
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = url;
|
link.href = url;
|
||||||
|
link.target = '_blank';
|
||||||
|
link.rel = 'noopener noreferrer';
|
||||||
link.download = filename;
|
link.download = filename;
|
||||||
try {
|
try {
|
||||||
link.click();
|
link.click();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user